From 8d14a1705e5dc311ace6d18cf968187601d4ec45 Mon Sep 17 00:00:00 2001 From: tyneises Date: Fri, 29 Sep 2023 11:05:09 -0500 Subject: [PATCH 01/85] calculate and assign system capacity in cmod --- ssc/cmod_fresnel_physical.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/ssc/cmod_fresnel_physical.cpp b/ssc/cmod_fresnel_physical.cpp index 6e722c57b..1e39309bf 100644 --- a/ssc/cmod_fresnel_physical.cpp +++ b/ssc/cmod_fresnel_physical.cpp @@ -303,6 +303,12 @@ static var_info _cm_vtab_fresnel_physical[] = { // OUTPUTS // Design Point Outputs + // System capacity required by downstream financial model + { SSC_OUTPUT, SSC_NUMBER, "system_capacity", "System capacity", "kWe", "", "System Design", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "cp_system_nameplate", "System capacity for capacity payments", "MWe", "", "System Design", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "cp_battery_nameplate", "Battery nameplate", "MWe", "", "System Design", "*", "", "" }, + + // System Design { SSC_OUTPUT, SSC_NUMBER, "solar_mult", "Actual solar multiple", "", "", "System Design Calc", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "total_Ap", "Actual field aperture", "m2", "", "System Design Calc", "*", "", "" }, @@ -1347,9 +1353,9 @@ class cm_fresnel_physical : public compute_module } // Design point is complete, assign technology design outputs - double Q_tes; - double total_land_area; - double nameplate_des; + double Q_tes = std::numeric_limits::quiet_NaN(); + double total_land_area = std::numeric_limits::quiet_NaN(); + double nameplate = std::numeric_limits::quiet_NaN(); { // System Design Calcs double eta_ref = as_double("eta_ref"); //[-] @@ -1521,17 +1527,21 @@ class cm_fresnel_physical : public compute_module csp_solver.get_design_parameters(W_dot_bop_design, W_dot_fixed_parasitic_design); double gross_net_conversion_des = as_number("gross_net_conversion_factor"); - nameplate_des = W_dot_cycle_des * gross_net_conversion_des; + nameplate = W_dot_cycle_des * gross_net_conversion_des; // Assign { - assign("nameplate", nameplate_des); + assign("nameplate", nameplate); assign("W_dot_bop_design", W_dot_bop_design); assign("W_dot_fixed", W_dot_fixed_parasitic_design); assign("solar_mult", c_fresnel.m_solar_mult); assign("nLoops", c_fresnel.m_nLoops); assign("total_Ap", c_fresnel.m_Ap_tot); + + assign("system_capacity", nameplate*1.E3); //[kWe] + assign("cp_system_nameplate", nameplate); //[MWe] + assign("cp_battery_nameplate", 0.0); //[MWe] } // System Control @@ -1589,7 +1599,7 @@ class cm_fresnel_physical : public compute_module // Calculate Costs N_mspt::calculate_mslf_costs(site_improvements_area, site_improvements_spec_cost, solar_field_area, solar_field_spec_cost, htf_system_area, htf_system_spec_cost, Q_tes, storage_spec_cost, fossil_backup_mwe, - fossil_spec_cost, power_plant_mwe, power_plant_spec_cost, bop_mwe, bop_spec_cost, contingency_percent, total_land_area, nameplate_des, epc_cost_per_acre, epc_cost_percent_direct, epc_cost_per_watt, + fossil_spec_cost, power_plant_mwe, power_plant_spec_cost, bop_mwe, bop_spec_cost, contingency_percent, total_land_area, nameplate, epc_cost_per_acre, epc_cost_percent_direct, epc_cost_per_watt, epc_cost_fixed, plm_cost_per_acre, plm_cost_percent_direct, plm_cost_per_watt, plm_cost_fixed, sales_tax_rate, sales_tax_percent, power_plant_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, htf_system_cost_out, fossil_backup_cost_out, contingency_cost_out, @@ -1858,8 +1868,6 @@ class cm_fresnel_physical : public compute_module assign("conversion_factor", convfactor); double kWh_per_kW = 0.0; - double system_capacity = as_double("P_ref") * as_double("gross_net_conversion_factor") * 1.E3; //[kWe] - double nameplate = system_capacity; //[kWe] if (nameplate > 0.0) kWh_per_kW = ae / nameplate; From 91b5d52fb79becc8d84fe0906d76230b6c303a0a Mon Sep 17 00:00:00 2001 From: tyneises Date: Fri, 29 Sep 2023 11:14:04 -0500 Subject: [PATCH 02/85] add system capacity outputs --- ssc/cmod_fresnel_physical_iph.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index adffc0d39..c99bb017d 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -262,6 +262,12 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { // OUTPUTS // Design Point Outputs + // System capacity required by downstream financial model + { SSC_OUTPUT, SSC_NUMBER, "system_capacity", "System capacity", "kWt", "", "System Design", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "cp_system_nameplate", "System capacity for capacity payments", "MWt", "", "System Design", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "cp_battery_nameplate", "Battery nameplate", "MWt", "", "System Design", "*", "", "" }, + + // System Design { SSC_OUTPUT, SSC_NUMBER, "solar_mult", "Actual solar multiple", "", "", "System Design Calc", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "total_Ap", "Actual field aperture", "m2", "", "System Design Calc", "*", "", "" }, @@ -1011,8 +1017,8 @@ class cm_fresnel_physical_iph : public compute_module } // Design point is complete, assign technology design outputs - double total_land_area; - double nameplate_des; + double total_land_area = std::numeric_limits::quiet_NaN(); + double nameplate = std::numeric_limits::quiet_NaN(); { // System Design Calcs //double eta_ref = as_double("eta_ref"); //[-] @@ -1184,17 +1190,21 @@ class cm_fresnel_physical_iph : public compute_module double W_dot_bop_design, W_dot_fixed_parasitic_design; //[MWe] csp_solver.get_design_parameters(W_dot_bop_design, W_dot_fixed_parasitic_design); - nameplate_des = q_dot_pc_des * 1.E3; // [kWt] + nameplate = q_dot_pc_des * 1.E3; // [kWt] // Assign { - assign("nameplate", nameplate_des * 1.E-3); // [MWt] + assign("nameplate", nameplate * 1.E-3); // [MWt] assign("W_dot_bop_design", W_dot_bop_design); assign("W_dot_fixed", W_dot_fixed_parasitic_design); assign("solar_mult", c_fresnel.m_solar_mult); assign("nLoops", c_fresnel.m_nLoops); assign("total_Ap", c_fresnel.m_Ap_tot); + + assign("system_capacity", nameplate * 1.E3); //[kWt] + assign("cp_system_nameplate", nameplate); //[MWt] + assign("cp_battery_nameplate", 0.0); //[MWt] } // System Control @@ -1252,7 +1262,7 @@ class cm_fresnel_physical_iph : public compute_module // Calculate Costs N_mspt::calculate_mslf_costs(site_improvements_area, site_improvements_spec_cost, solar_field_area, solar_field_spec_cost, htf_system_area, htf_system_spec_cost, Q_tes, storage_spec_cost,0,0, - heat_sink_mwt, heat_sink_spec_cost, bop_mwt, bop_spec_cost, contingency_percent, total_land_area, nameplate_des, epc_cost_per_acre, epc_cost_percent_direct, epc_cost_per_watt, + heat_sink_mwt, heat_sink_spec_cost, bop_mwt, bop_spec_cost, contingency_percent, total_land_area, nameplate, epc_cost_per_acre, epc_cost_percent_direct, epc_cost_per_watt, epc_cost_fixed, plm_cost_per_acre, plm_cost_percent_direct, plm_cost_per_watt, plm_cost_fixed, sales_tax_rate, sales_tax_percent, heat_sink_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, htf_system_cost_out, dummy, contingency_cost_out, @@ -1394,7 +1404,6 @@ class cm_fresnel_physical_iph : public compute_module ssc_number_t ae = as_number("annual_energy"); //[kWt-hr] - double nameplate = q_dot_pc_des * 1.E3; //[kWt] double kWh_per_kW = ae / nameplate; assign("capacity_factor", (ssc_number_t)(kWh_per_kW / 8760. * 100.)); assign("kwh_per_kw", (ssc_number_t)kWh_per_kW); From a6642f60056571ed1b06290d65de5b8b40a1afb5 Mon Sep 17 00:00:00 2001 From: tyneises Date: Fri, 29 Sep 2023 11:27:21 -0500 Subject: [PATCH 03/85] calculate construction costs in mslf cmod --- ssc/cmod_fresnel_physical.cpp | 123 +++++++++++++++++++++++++++++++--- 1 file changed, 114 insertions(+), 9 deletions(-) diff --git a/ssc/cmod_fresnel_physical.cpp b/ssc/cmod_fresnel_physical.cpp index 1e39309bf..db9ca3ecb 100644 --- a/ssc/cmod_fresnel_physical.cpp +++ b/ssc/cmod_fresnel_physical.cpp @@ -300,6 +300,30 @@ static var_info _cm_vtab_fresnel_physical[] = { /*Fin Tax and Insurace*/{ SSC_INPUT, SSC_NUMBER, "sales_tax_rate", "Sales Tax Rate", "%", "", "Capital_Costs", "?=0", "", "" }, + // Construction financing inputs/outputs (SSC variable table from cmod_cb_construction_financing) + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate1", "Interest rate, loan 1", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate2", "Interest rate, loan 2", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate3", "Interest rate, loan 3", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate4", "Interest rate, loan 4", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate5", "Interest rate, loan 5", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months1", "Months prior to operation, loan 1", "", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months2", "Months prior to operation, loan 2", "", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months3", "Months prior to operation, loan 3", "", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months4", "Months prior to operation, loan 4", "", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months5", "Months prior to operation, loan 5", "", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent1", "Percent of total installed cost, loan 1", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent2", "Percent of total installed cost, loan 2", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent3", "Percent of total installed cost, loan 3", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent4", "Percent of total installed cost, loan 4", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent5", "Percent of total installed cost, loan 5", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate1", "Upfront fee on principal, loan 1", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate2", "Upfront fee on principal, loan 2", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate3", "Upfront fee on principal, loan 3", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate4", "Upfront fee on principal, loan 4", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate5", "Upfront fee on principal, loan 5", "%", "", "Financial Parameters", "*", "", "" }, + + + // OUTPUTS // Design Point Outputs @@ -407,15 +431,30 @@ static var_info _cm_vtab_fresnel_physical[] = { { SSC_OUTPUT, SSC_NUMBER, "total_installed_cost", "Total installed cost", "$", "", "Capital Costs", "", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "installed_per_capacity", "Estimated total installed cost per net capacity ($/kW)", "$/kW", "", "Capital Costs", "", "", "" }, - - - - - - - // Simulation outputs - - + // Financing + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal1", "Principal, loan 1", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal2", "Principal, loan 2", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal3", "Principal, loan 3", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal4", "Principal, loan 4", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal5", "Principal, loan 5", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest1", "Interest cost, loan 1", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest2", "Interest cost, loan 2", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest3", "Interest cost, loan 3", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest4", "Interest cost, loan 4", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest5", "Interest cost, loan 5", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total1", "Total financing cost, loan 1", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total2", "Total financing cost, loan 2", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total3", "Total financing cost, loan 3", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total4", "Total financing cost, loan 4", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total5", "Total financing cost, loan 5", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_percent_total", "Total percent of installed costs, all loans", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal_total", "Total principal, all loans", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest_total", "Total interest costs, all loans", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "construction_financing_cost", "Total construction financing cost", "$", "", "Financial Parameters", "*", "", "" }, + + // **************************************************************************************************************************************** + // Timeseries Simulation Outputs here (sim_type = 1): + // **************************************************************************************************************************************** // Simulation Kernel { SSC_OUTPUT, SSC_ARRAY, "time_hr", "Time at end of timestep", "hr", "", "solver", "sim_type=1", "", "" }, @@ -1625,6 +1664,72 @@ class cm_fresnel_physical : public compute_module assign("total_installed_cost", total_installed_cost_out); assign("installed_per_capacity", installed_per_capacity_out); } + + // Update construction financing costs, specifically, update: "construction_financing_cost" + double const_per_interest_rate1 = as_double("const_per_interest_rate1"); + double const_per_interest_rate2 = as_double("const_per_interest_rate2"); + double const_per_interest_rate3 = as_double("const_per_interest_rate3"); + double const_per_interest_rate4 = as_double("const_per_interest_rate4"); + double const_per_interest_rate5 = as_double("const_per_interest_rate5"); + double const_per_months1 = as_double("const_per_months1"); + double const_per_months2 = as_double("const_per_months2"); + double const_per_months3 = as_double("const_per_months3"); + double const_per_months4 = as_double("const_per_months4"); + double const_per_months5 = as_double("const_per_months5"); + double const_per_percent1 = as_double("const_per_percent1"); + double const_per_percent2 = as_double("const_per_percent2"); + double const_per_percent3 = as_double("const_per_percent3"); + double const_per_percent4 = as_double("const_per_percent4"); + double const_per_percent5 = as_double("const_per_percent5"); + double const_per_upfront_rate1 = as_double("const_per_upfront_rate1"); + double const_per_upfront_rate2 = as_double("const_per_upfront_rate2"); + double const_per_upfront_rate3 = as_double("const_per_upfront_rate3"); + double const_per_upfront_rate4 = as_double("const_per_upfront_rate4"); + double const_per_upfront_rate5 = as_double("const_per_upfront_rate5"); + + double const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5; + double const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5; + double const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5; + double const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost; + + const_per_principal1 = const_per_principal2 = const_per_principal3 = const_per_principal4 = const_per_principal5 = + const_per_interest1 = const_per_interest2 = const_per_interest3 = const_per_interest4 = const_per_interest5 = + const_per_total1 = const_per_total2 = const_per_total3 = const_per_total4 = const_per_total5 = + const_per_percent_total = const_per_principal_total = const_per_interest_total = construction_financing_cost = + std::numeric_limits::quiet_NaN(); + + N_financial_parameters::construction_financing_total_cost(total_installed_cost_out, + const_per_interest_rate1, const_per_interest_rate2, const_per_interest_rate3, const_per_interest_rate4, const_per_interest_rate5, + const_per_months1, const_per_months2, const_per_months3, const_per_months4, const_per_months5, + const_per_percent1, const_per_percent2, const_per_percent3, const_per_percent4, const_per_percent5, + const_per_upfront_rate1, const_per_upfront_rate2, const_per_upfront_rate3, const_per_upfront_rate4, const_per_upfront_rate5, + const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5, + const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5, + const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5, + const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost); + + assign("const_per_principal1", (ssc_number_t)const_per_principal1); + assign("const_per_principal2", (ssc_number_t)const_per_principal2); + assign("const_per_principal3", (ssc_number_t)const_per_principal3); + assign("const_per_principal4", (ssc_number_t)const_per_principal4); + assign("const_per_principal5", (ssc_number_t)const_per_principal5); + assign("const_per_interest1", (ssc_number_t)const_per_interest1); + assign("const_per_interest2", (ssc_number_t)const_per_interest2); + assign("const_per_interest3", (ssc_number_t)const_per_interest3); + assign("const_per_interest4", (ssc_number_t)const_per_interest4); + assign("const_per_interest5", (ssc_number_t)const_per_interest5); + assign("const_per_total1", (ssc_number_t)const_per_total1); + assign("const_per_total2", (ssc_number_t)const_per_total2); + assign("const_per_total3", (ssc_number_t)const_per_total3); + assign("const_per_total4", (ssc_number_t)const_per_total4); + assign("const_per_total5", (ssc_number_t)const_per_total5); + assign("const_per_percent_total", (ssc_number_t)const_per_percent_total); + assign("const_per_principal_total", (ssc_number_t)const_per_principal_total); + assign("const_per_interest_total", (ssc_number_t)const_per_interest_total); + assign("construction_financing_cost", (ssc_number_t)construction_financing_cost); + + + } From 915a670f6258eabd7f709deb861d9a4ca12df20a Mon Sep 17 00:00:00 2001 From: tyneises Date: Fri, 29 Sep 2023 11:33:28 -0500 Subject: [PATCH 04/85] calculate construction costs in mslf iph cmod --- ssc/cmod_fresnel_physical_iph.cpp | 124 +++++++++++++++++++++++++++--- 1 file changed, 113 insertions(+), 11 deletions(-) diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index c99bb017d..7383db00e 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -258,6 +258,28 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { /*Capital Costs*/{ SSC_INPUT, SSC_NUMBER, "sales_tax_percent", "Sales Tax Percentage of Direct Cost", "%", "", "Capital_Costs", "?=0", "", "" }, /*Fin Tax and Insurace*/{ SSC_INPUT, SSC_NUMBER, "sales_tax_rate", "Sales Tax Rate", "%", "", "Capital_Costs", "?=0", "", "" }, + // Construction financing inputs/outputs (SSC variable table from cmod_cb_construction_financing) + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate1", "Interest rate, loan 1", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate2", "Interest rate, loan 2", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate3", "Interest rate, loan 3", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate4", "Interest rate, loan 4", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate5", "Interest rate, loan 5", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months1", "Months prior to operation, loan 1", "", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months2", "Months prior to operation, loan 2", "", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months3", "Months prior to operation, loan 3", "", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months4", "Months prior to operation, loan 4", "", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months5", "Months prior to operation, loan 5", "", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent1", "Percent of total installed cost, loan 1", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent2", "Percent of total installed cost, loan 2", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent3", "Percent of total installed cost, loan 3", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent4", "Percent of total installed cost, loan 4", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent5", "Percent of total installed cost, loan 5", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate1", "Upfront fee on principal, loan 1", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate2", "Upfront fee on principal, loan 2", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate3", "Upfront fee on principal, loan 3", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate4", "Upfront fee on principal, loan 4", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate5", "Upfront fee on principal, loan 5", "%", "", "Financial Parameters", "*", "", "" }, + // OUTPUTS // Design Point Outputs @@ -364,17 +386,32 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { // Total Installed Costs { SSC_OUTPUT, SSC_NUMBER, "total_installed_cost", "Total installed cost", "$", "", "Capital Costs", "", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "installed_per_capacity", "Estimated total installed cost per net capacity ($/kW)", "$/kW", "", "Capital Costs", "", "", "" }, - - - - - - - - // Simulation outputs - - - + + // Financing + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal1", "Principal, loan 1", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal2", "Principal, loan 2", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal3", "Principal, loan 3", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal4", "Principal, loan 4", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal5", "Principal, loan 5", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest1", "Interest cost, loan 1", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest2", "Interest cost, loan 2", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest3", "Interest cost, loan 3", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest4", "Interest cost, loan 4", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest5", "Interest cost, loan 5", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total1", "Total financing cost, loan 1", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total2", "Total financing cost, loan 2", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total3", "Total financing cost, loan 3", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total4", "Total financing cost, loan 4", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total5", "Total financing cost, loan 5", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_percent_total", "Total percent of installed costs, all loans", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal_total", "Total principal, all loans", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest_total", "Total interest costs, all loans", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "construction_financing_cost", "Total construction financing cost", "$", "", "Financial Parameters", "*", "", "" }, + + // **************************************************************************************************************************************** + // Timeseries Simulation Outputs here (sim_type = 1): + // **************************************************************************************************************************************** + // Simulation Kernel { SSC_OUTPUT, SSC_ARRAY, "time_hr", "Time at end of timestep", "hr", "", "solver", "sim_type=1", "", "" }, @@ -1287,6 +1324,71 @@ class cm_fresnel_physical_iph : public compute_module assign("total_installed_cost", total_installed_cost_out); assign("installed_per_capacity", installed_per_capacity_out); } + + // Update construction financing costs, specifically, update: "construction_financing_cost" + double const_per_interest_rate1 = as_double("const_per_interest_rate1"); + double const_per_interest_rate2 = as_double("const_per_interest_rate2"); + double const_per_interest_rate3 = as_double("const_per_interest_rate3"); + double const_per_interest_rate4 = as_double("const_per_interest_rate4"); + double const_per_interest_rate5 = as_double("const_per_interest_rate5"); + double const_per_months1 = as_double("const_per_months1"); + double const_per_months2 = as_double("const_per_months2"); + double const_per_months3 = as_double("const_per_months3"); + double const_per_months4 = as_double("const_per_months4"); + double const_per_months5 = as_double("const_per_months5"); + double const_per_percent1 = as_double("const_per_percent1"); + double const_per_percent2 = as_double("const_per_percent2"); + double const_per_percent3 = as_double("const_per_percent3"); + double const_per_percent4 = as_double("const_per_percent4"); + double const_per_percent5 = as_double("const_per_percent5"); + double const_per_upfront_rate1 = as_double("const_per_upfront_rate1"); + double const_per_upfront_rate2 = as_double("const_per_upfront_rate2"); + double const_per_upfront_rate3 = as_double("const_per_upfront_rate3"); + double const_per_upfront_rate4 = as_double("const_per_upfront_rate4"); + double const_per_upfront_rate5 = as_double("const_per_upfront_rate5"); + + double const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5; + double const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5; + double const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5; + double const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost; + + const_per_principal1 = const_per_principal2 = const_per_principal3 = const_per_principal4 = const_per_principal5 = + const_per_interest1 = const_per_interest2 = const_per_interest3 = const_per_interest4 = const_per_interest5 = + const_per_total1 = const_per_total2 = const_per_total3 = const_per_total4 = const_per_total5 = + const_per_percent_total = const_per_principal_total = const_per_interest_total = construction_financing_cost = + std::numeric_limits::quiet_NaN(); + + N_financial_parameters::construction_financing_total_cost(total_installed_cost_out, + const_per_interest_rate1, const_per_interest_rate2, const_per_interest_rate3, const_per_interest_rate4, const_per_interest_rate5, + const_per_months1, const_per_months2, const_per_months3, const_per_months4, const_per_months5, + const_per_percent1, const_per_percent2, const_per_percent3, const_per_percent4, const_per_percent5, + const_per_upfront_rate1, const_per_upfront_rate2, const_per_upfront_rate3, const_per_upfront_rate4, const_per_upfront_rate5, + const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5, + const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5, + const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5, + const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost); + + assign("const_per_principal1", (ssc_number_t)const_per_principal1); + assign("const_per_principal2", (ssc_number_t)const_per_principal2); + assign("const_per_principal3", (ssc_number_t)const_per_principal3); + assign("const_per_principal4", (ssc_number_t)const_per_principal4); + assign("const_per_principal5", (ssc_number_t)const_per_principal5); + assign("const_per_interest1", (ssc_number_t)const_per_interest1); + assign("const_per_interest2", (ssc_number_t)const_per_interest2); + assign("const_per_interest3", (ssc_number_t)const_per_interest3); + assign("const_per_interest4", (ssc_number_t)const_per_interest4); + assign("const_per_interest5", (ssc_number_t)const_per_interest5); + assign("const_per_total1", (ssc_number_t)const_per_total1); + assign("const_per_total2", (ssc_number_t)const_per_total2); + assign("const_per_total3", (ssc_number_t)const_per_total3); + assign("const_per_total4", (ssc_number_t)const_per_total4); + assign("const_per_total5", (ssc_number_t)const_per_total5); + assign("const_per_percent_total", (ssc_number_t)const_per_percent_total); + assign("const_per_principal_total", (ssc_number_t)const_per_principal_total); + assign("const_per_interest_total", (ssc_number_t)const_per_interest_total); + assign("construction_financing_cost", (ssc_number_t)construction_financing_cost); + + } From 95a0ae57c89905d90179eb5d942520c8d490c99e Mon Sep 17 00:00:00 2001 From: tyneises Date: Fri, 29 Sep 2023 16:16:28 -0500 Subject: [PATCH 05/85] enable single owner for mslf iph --- ssc/cmod_fresnel_physical_iph.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 7383db00e..182f4fe22 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -891,7 +891,7 @@ class cm_fresnel_physical_iph : public compute_module int csp_financial_model = as_integer("csp_financial_model"); if (sim_type == 1) { - if (csp_financial_model == 8 || csp_financial_model == 7) { // No Financial Model or LCOH + if (csp_financial_model == 8 || csp_financial_model == 7 || csp_financial_model == 1) { // No Financial Model or LCOH; Single Owner in progress 9/2023 if (is_dispatch) { throw exec_error("fresnel_physical_iph", "Can't select dispatch optimization if No Financial model"); } @@ -900,10 +900,14 @@ class cm_fresnel_physical_iph : public compute_module tou_params->mc_pricing.mc_weekdays.resize_fill(12, 24, 1.); tou_params->mc_pricing.mc_weekends.resize_fill(12, 24, 1.); tou_params->mc_pricing.mvv_tou_arrays[C_block_schedule_pricing::MULT_PRICE].resize(9, -1.0); + + if (csp_financial_model == 1) { + log("The MSLF IPH model does not control dispatch with respect to input pricing signals"); + } } } else { - throw exec_error("fresnel_physical_iph", "csp_financial_model must 8"); + throw exec_error("fresnel_physical_iph", "csp_financial_model must 1, 7, or 8"); } From 44c88d77a3e89b3445f6bb46a9590b05f65ace82 Mon Sep 17 00:00:00 2001 From: tyneises Date: Fri, 29 Sep 2023 16:31:53 -0500 Subject: [PATCH 06/85] make nameplate units consistent in mslf --- ssc/cmod_fresnel_physical.cpp | 4 ++-- ssc/cmod_fresnel_physical_iph.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ssc/cmod_fresnel_physical.cpp b/ssc/cmod_fresnel_physical.cpp index db9ca3ecb..d19f1513f 100644 --- a/ssc/cmod_fresnel_physical.cpp +++ b/ssc/cmod_fresnel_physical.cpp @@ -1566,7 +1566,7 @@ class cm_fresnel_physical : public compute_module csp_solver.get_design_parameters(W_dot_bop_design, W_dot_fixed_parasitic_design); double gross_net_conversion_des = as_number("gross_net_conversion_factor"); - nameplate = W_dot_cycle_des * gross_net_conversion_des; + nameplate = W_dot_cycle_des * gross_net_conversion_des; //[MWe] // Assign { @@ -1974,7 +1974,7 @@ class cm_fresnel_physical : public compute_module double kWh_per_kW = 0.0; if (nameplate > 0.0) - kWh_per_kW = ae / nameplate; + kWh_per_kW = ae / (nameplate*1.E3); // convert nameplate to kW assign("capacity_factor", (ssc_number_t)(kWh_per_kW / ((double)n_steps_fixed / (double)steps_per_hour) * 100.)); assign("kwh_per_kw", (ssc_number_t)kWh_per_kW); diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 182f4fe22..95127df10 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -1231,7 +1231,7 @@ class cm_fresnel_physical_iph : public compute_module double W_dot_bop_design, W_dot_fixed_parasitic_design; //[MWe] csp_solver.get_design_parameters(W_dot_bop_design, W_dot_fixed_parasitic_design); - nameplate = q_dot_pc_des * 1.E3; // [kWt] + nameplate = q_dot_pc_des; // [MWt] // Assign { @@ -1510,7 +1510,7 @@ class cm_fresnel_physical_iph : public compute_module ssc_number_t ae = as_number("annual_energy"); //[kWt-hr] - double kWh_per_kW = ae / nameplate; + double kWh_per_kW = ae / (nameplate*1.E3); // convert nameplate to kW assign("capacity_factor", (ssc_number_t)(kWh_per_kW / 8760. * 100.)); assign("kwh_per_kw", (ssc_number_t)kWh_per_kW); From 4d5686e75cea6b6a292d837f4a7afd542ba409fa Mon Sep 17 00:00:00 2001 From: tyneises Date: Sat, 30 Sep 2023 14:25:19 -0500 Subject: [PATCH 07/85] update mspt iph cmod for single owner --- ssc/cmod_fresnel_physical_iph.cpp | 2 +- ssc/cmod_mspt_iph.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 95127df10..2e14c3482 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -907,7 +907,7 @@ class cm_fresnel_physical_iph : public compute_module } } else { - throw exec_error("fresnel_physical_iph", "csp_financial_model must 1, 7, or 8"); + throw exec_error("fresnel_physical_iph", "csp_financial_model must be 1, 7, or 8"); } diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index 02500fec1..1a3f16052 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -1574,7 +1574,7 @@ class cm_mspt_iph : public compute_module double ppa_price_year1 = std::numeric_limits::quiet_NaN(); if (sim_type == 1) { - if (csp_financial_model == 8 || csp_financial_model == 7) { // No Financial Model or LCOH + if (csp_financial_model == 8 || csp_financial_model == 7 || csp_financial_model == 1) { // No Financial Model or LCOH if (is_dispatch) { throw exec_error("tcsmolten_salt", "Can't select dispatch optimization if No Financial model"); } @@ -1584,9 +1584,13 @@ class cm_mspt_iph : public compute_module tou_params->mc_pricing.mc_weekends.resize_fill(12, 24, 1.); tou_params->mc_pricing.mvv_tou_arrays[C_block_schedule_pricing::MULT_PRICE].resize(9, -1.0); } + + if (csp_financial_model == 1) { + log("The MSLF IPH model does not control dispatch with respect to input pricing signals"); + } } else { - throw exec_error("mspt_iph", "csp_financial_model must 8"); + throw exec_error("mspt_iph", "csp_financial_model must be 1, 7, or 8"); } } else if (sim_type == 2) { From 2827b1156eec82ab886a1533c3cc485a00755d4e Mon Sep 17 00:00:00 2001 From: tyneises Date: Sun, 1 Oct 2023 15:45:33 -0500 Subject: [PATCH 08/85] add dispatch optimization for mspt iph --- ssc/cmod_mspt_iph.cpp | 160 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 144 insertions(+), 16 deletions(-) diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index 1a3f16052..d89363f35 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -250,6 +250,37 @@ static var_info _cm_vtab_mspt_iph[] = { { SSC_INPUT, SSC_NUMBER, "q_rec_standby", "Receiver standby energy consumption", "kWt", "", "System Control", "?=9e99", "", "SIMULATION_PARAMETER"}, { SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWe-hr", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER"}, +// System Control + // Required if dispatch +{ SSC_INPUT, SSC_NUMBER, "disp_rsu_cost_rel", "Receiver startup cost", "$/MWt/start", "", "System Control", "", "", "" }, +{ SSC_INPUT, SSC_NUMBER, "disp_horizon", "Time horizon for dispatch optimization", "hour", "", "System Control", "is_dispatch=1", "", "" }, +{ SSC_INPUT, SSC_NUMBER, "disp_frequency", "Frequency for dispatch optimization calculations", "hour", "", "System Control", "is_dispatch=1", "", "" }, +{ SSC_INPUT, SSC_NUMBER, "disp_max_iter", "Max number of dispatch optimization iterations", "", "", "System Control", "is_dispatch=1", "", "" }, +{ SSC_INPUT, SSC_NUMBER, "disp_timeout", "Max dispatch optimization solve duration", "s", "", "System Control", "is_dispatch=1", "", "" }, +{ SSC_INPUT, SSC_NUMBER, "disp_mip_gap", "Dispatch optimization solution tolerance", "", "", "System Control", "is_dispatch=1", "", "" }, +{ SSC_INPUT, SSC_NUMBER, "disp_time_weighting", "Dispatch optimization future time discounting factor", "", "", "System Control", "is_dispatch=1", "", "" }, + // Optional for custom scripting +{ SSC_INPUT, SSC_NUMBER, "disp_steps_per_hour", "Time steps per hour for dispatch optimization calculations", "", "", "System Control", "?=1", "", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_NUMBER, "disp_spec_presolve", "Dispatch optimization presolve heuristic", "", "", "System Control", "?=-1", "", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_NUMBER, "disp_spec_bb", "Dispatch optimization B&B heuristic", "", "", "System Control", "?=-1", "", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_NUMBER, "disp_spec_scaling", "Dispatch optimization scaling heuristic", "", "", "System Control", "?=-1", "", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_NUMBER, "disp_reporting", "Dispatch optimization reporting level", "", "", "System Control", "?=-1", "", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_NUMBER, "is_write_ampl_dat", "Write AMPL data files for dispatch run", "", "", "System Control", "?=0", "", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_NUMBER, "is_ampl_engine", "Run dispatch optimization with external AMPL engine", "", "", "System Control", "?=0", "", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_STRING, "ampl_data_dir", "AMPL data file directory", "", "", "System Control", "?=''", "", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_STRING, "ampl_exec_call", "System command to run AMPL code", "", "", "System Control", "?='ampl sdk_solution.run'", "", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_NUMBER, "disp_inventory_incentive", "Dispatch storage terminal inventory incentive multiplier", "", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER" }, + + +// Pricing schedules (copied from electricity - eventually need to resolve electricity vs. heat) +{ SSC_INPUT, SSC_NUMBER, "ppa_multiplier_model", "PPA multiplier model 0: dispatch factors dispatch_factorX, 1: hourly multipliers dispatch_factors_ts", "0/1", "0=diurnal,1=timestep", "Time of Delivery Factors", "?=0", /*need a default so this var works in required_if*/ "INTEGER,MIN=0", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_NUMBER, "ppa_soln_mode", "PPA solution mode (0=Specify IRR target, 1=Specify PPA price)", "", "", "Financial Solution Mode", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_ARRAY, "dispatch_factors_ts", "Dispatch payment factor array", "", "", "Time of Delivery Factors", "ppa_multiplier_model=1&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekday", "PPA pricing weekday schedule, 12x24", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekend", "PPA pricing weekend schedule, 12x24", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_ARRAY, "dispatch_tod_factors", "TOD factors for periods 1 through 9", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, + // Costs { SSC_INPUT, SSC_NUMBER, "tower_fixed_cost", "Tower fixed cost", "$", "", "System Costs", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "tower_exp", "Tower cost scaling exponent", "", "", "System Costs", "*", "", "" }, @@ -657,7 +688,7 @@ class cm_mspt_iph : public compute_module weather_reader.m_azimuth = 0.0; // Initialize to get weather file info weather_reader.init(); - if (weather_reader.has_error()) throw exec_error("tcsmolten_salt", weather_reader.get_error()); + if (weather_reader.has_error()) throw exec_error("mspt_iph", weather_reader.get_error()); // Get info from the weather reader initialization double site_elevation = weather_reader.ms_solved_params.m_elev; //[m] @@ -730,7 +761,7 @@ class cm_mspt_iph : public compute_module assign("n_flux_x", 2); // n_flux_x represents *per panel* the number subsurfaces in x direction } else { - throw exec_error("tcsmolten_salt", "receiver_type must be 1 (external) or 0 (cavity)"); + throw exec_error("mspt_iph", "receiver_type must be 1 (external) or 0 (cavity)"); } if ((field_model_type == 0 || field_model_type == 1) && sim_type == 1) // Auto-design. Generate a new system (is_optimize = true) or field layout @@ -1281,9 +1312,9 @@ class cm_mspt_iph : public compute_module double W_dot_rec_target = std::numeric_limits::quiet_NaN(); if (rec_clearsky_model > 4) - throw exec_error("tcsmolten_salt", "Invalid specification for 'rec_clearsky_model'"); + throw exec_error("mspt_iph", "Invalid specification for 'rec_clearsky_model'"); if (rec_clearsky_model == -1 && as_double("rec_clearsky_fraction") >= 0.0001) - throw exec_error("tcsmolten_salt", "'rec_clearsky_model' must be specified when 'rec_clearsky_fraction' > 0.0."); + throw exec_error("mspt_iph", "'rec_clearsky_model' must be specified when 'rec_clearsky_fraction' > 0.0."); if (!as_boolean("is_rec_model_trans") && !as_boolean("is_rec_startup_trans")) { //std::unique_ptr ss_receiver = std::make_unique(); // new to C++14 @@ -1313,7 +1344,7 @@ class cm_mspt_iph : public compute_module //trans_receiver->m_is_startup_from_solved_profile = as_boolean("is_rec_startup_from_T_soln"); if (as_boolean("is_rec_startup_trans") && as_boolean("is_rec_startup_from_T_soln")) - throw exec_error("tcsmolten_salt", "Receiver startup from solved temperature profiles is only available when receiver transient startup model is enabled"); + throw exec_error("mspt_iph", "Receiver startup from solved temperature profiles is only available when receiver transient startup model is enabled"); //trans_receiver->m_is_enforce_min_startup = as_boolean("is_rec_enforce_min_startup"); if (as_boolean("is_rec_startup_trans") && !as_boolean("is_rec_startup_from_T_soln") && !is_enforce_min_startup) @@ -1388,7 +1419,7 @@ class cm_mspt_iph : public compute_module size_t n_csky = 0; ssc_number_t* csky = as_array("rec_clearsky_dni", &n_csky); if (n_csky != n_steps_full) - throw exec_error("tcsmolten_salt", "Invalid clear-sky DNI data. Array must have " + util::to_string((int)n_steps_full) + " rows."); + throw exec_error("mspt_iph", "Invalid clear-sky DNI data. Array must have " + util::to_string((int)n_steps_full) + " rows."); clearsky_data.resize(n_steps_full); for (size_t i = 0; i < n_steps_full; i++) @@ -1399,7 +1430,7 @@ class cm_mspt_iph : public compute_module //Load the solar field adjustment factors adjustment_factors sf_haf(this, "sf_adjust"); if (!sf_haf.setup((int)n_steps_full)) - throw exec_error("tcsmolten_salt", "failed to setup sf adjustment factors: " + sf_haf.error()); + throw exec_error("mspt_iph", "failed to setup sf adjustment factors: " + sf_haf.error()); //allocate array to pass to tcs heliostatfield.ms_params.m_sf_adjust.resize(sf_haf.size()); for (int i = 0; i < sf_haf.size(); i++) @@ -1473,7 +1504,7 @@ class cm_mspt_iph : public compute_module if (!is_dispatch && sim_type == 1) { if (!as_boolean("allow_heater_no_dispatch_opt")) { - throw exec_error("tcsmolten_salt", "When the molten salt power tower case has an electric HTF charger, dispatch optimization must be selected"); + throw exec_error("mspt_iph", "When the molten salt power tower case has an electric HTF charger, dispatch optimization must be selected"); } } @@ -1574,9 +1605,9 @@ class cm_mspt_iph : public compute_module double ppa_price_year1 = std::numeric_limits::quiet_NaN(); if (sim_type == 1) { - if (csp_financial_model == 8 || csp_financial_model == 7 || csp_financial_model == 1) { // No Financial Model or LCOH + if (csp_financial_model == 8 || csp_financial_model == 7) { // No Financial Model or LCOH if (is_dispatch) { - throw exec_error("tcsmolten_salt", "Can't select dispatch optimization if No Financial model"); + throw exec_error("mspt_iph", "Can't select dispatch optimization if No Financial model"); } else { // if no dispatch optimization, don't need an input pricing schedule // If electricity pricing data is not available, then dispatch to a uniform schedule @@ -1589,6 +1620,82 @@ class cm_mspt_iph : public compute_module log("The MSLF IPH model does not control dispatch with respect to input pricing signals"); } } + else if (csp_financial_model == 1) { // Single owner + + // Get first year base ppa price + bool is_ppa_price_input_assigned = is_assigned("ppa_price_input"); + if (is_dispatch && !is_ppa_price_input_assigned) { + throw exec_error("mspt_iph", "\n\nYou selected dispatch optimization which requires that the array input ppa_price_input is defined\n"); + } + + if (is_ppa_price_input_assigned) { + size_t count_ppa_price_input; + ssc_number_t* ppa_price_input_array = as_array("ppa_price_input", &count_ppa_price_input); + ppa_price_year1 = (double)ppa_price_input_array[0]; // [$/kWh] + } + else { + ppa_price_year1 = 1.0; //[-] don't need ppa multiplier if not optimizing + } + + int ppa_soln_mode = as_integer("ppa_soln_mode"); // PPA solution mode (0=Specify IRR target, 1=Specify PPA price) + if (ppa_soln_mode == 0 && is_dispatch) { + throw exec_error("mspt_iph", "\n\nYou selected dispatch optimization and the Specify IRR Target financial solution mode, " + "but dispatch optimization requires known absolute electricity prices. Dispatch optimization requires " + "the Specify PPA Price financial solution mode. You can continue using dispatch optimization and iteratively " + "solve for the PPA that results in a target IRR by running a SAM Parametric analysis or script.\n"); + } + + // Time-of-Delivery multipliers by time step: + int ppa_mult_model = as_integer("ppa_multiplier_model"); + if (ppa_mult_model == 1) // use dispatch_ts input + { + tou_params->mc_pricing.mv_is_diurnal = false; + + if (is_assigned("dispatch_factors_ts") || is_dispatch) { + size_t nmultipliers; + ssc_number_t* multipliers = as_array("dispatch_factors_ts", &nmultipliers); + tou_params->mc_pricing.mvv_tou_arrays[C_block_schedule_pricing::MULT_PRICE].resize(nmultipliers, 0.0); + for (size_t ii = 0; ii < nmultipliers; ii++) + tou_params->mc_pricing.mvv_tou_arrays[C_block_schedule_pricing::MULT_PRICE][ii] = multipliers[ii]; + } + else { // if no dispatch optimization, don't need an input pricing schedule + tou_params->mc_pricing.mvv_tou_arrays[C_block_schedule_pricing::MULT_PRICE].resize(n_steps_fixed, -1.0); + } + } + else if (ppa_mult_model == 0) // standard diurnal input + { + tou_params->mc_pricing.mv_is_diurnal = true; + + // Most likely use case is to use schedules and TOD. So assume if at least one is provided, then user intended to use this approach + // the 'else' option applies non-feasible electricity prices, so we want to guard against selecting that it appears users + // are trying to use the schedules. + bool is_one_assigned = is_assigned("dispatch_sched_weekday") || is_assigned("dispatch_sched_weekend") || is_assigned("dispatch_tod_factors"); + + if (is_one_assigned || is_dispatch) { + + tou_params->mc_pricing.mc_weekdays = as_matrix("dispatch_sched_weekday"); + if (tou_params->mc_pricing.mc_weekdays.ncells() == 1) { tou_params->mc_pricing.mc_weekdays.resize_fill(12, 24, 1.); }; + tou_params->mc_pricing.mc_weekends = as_matrix("dispatch_sched_weekend"); + if (tou_params->mc_pricing.mc_weekends.ncells() == 1) { tou_params->mc_pricing.mc_weekends.resize_fill(12, 24, 1.); }; + + auto dispatch_tod_factors = as_vector_double("dispatch_tod_factors"); + if (dispatch_tod_factors.size() != 9) + throw exec_error("mspt_iph", util::format("\n\nDispatch TOD factors has %d periods instead of the expected 9.\n", (int)dispatch_tod_factors.size())); + + tou_params->mc_pricing.mvv_tou_arrays[C_block_schedule_pricing::MULT_PRICE].resize(9, 0.0); + + for (size_t i = 0; i < 9; i++) + tou_params->mc_pricing.mvv_tou_arrays[C_block_schedule_pricing::MULT_PRICE][i] = dispatch_tod_factors[i]; + + } + else { + // If electricity pricing data is not available, then dispatch to a uniform schedule + tou_params->mc_pricing.mc_weekdays.resize_fill(12, 24, 1.); + tou_params->mc_pricing.mc_weekends.resize_fill(12, 24, 1.); + tou_params->mc_pricing.mvv_tou_arrays[C_block_schedule_pricing::MULT_PRICE].resize(9, -1.0); + } + } + } else { throw exec_error("mspt_iph", "csp_financial_model must be 1, 7, or 8"); } @@ -1613,7 +1720,30 @@ class cm_mspt_iph : public compute_module // ***************************************************** // System dispatch csp_dispatch_opt dispatch; - dispatch.solver_params.dispatch_optimize = false; + + if (is_dispatch) { + + double heater_startup_cost = 0.0; + + dispatch.solver_params.set_user_inputs(is_dispatch, as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), + as_integer("disp_max_iter"), as_double("disp_mip_gap"), as_double("disp_timeout"), + as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting"), + as_boolean("is_write_ampl_dat"), as_boolean("is_ampl_engine"), as_string("ampl_data_dir"), as_string("ampl_exec_call")); + + bool can_cycle_use_standby = false; + double disp_csu_cost_calc = 0.0; + double disp_pen_ramping = 0.0; + double q_rec_standby = 9e99; + double q_rec_heattrace = 0.; + + double disp_rsu_cost_calc = as_double("disp_rsu_cost_rel") * q_dot_rec_des; //[$/start] + dispatch.params.set_user_params(can_cycle_use_standby, as_double("disp_time_weighting"), + disp_rsu_cost_calc, heater_startup_cost, disp_csu_cost_calc, disp_pen_ramping, + as_double("disp_inventory_incentive"), q_rec_standby, q_rec_heattrace, ppa_price_year1); + } + else { + dispatch.solver_params.dispatch_optimize = false; + } // Instantiate Solver C_csp_solver csp_solver(weather_reader, @@ -1628,7 +1758,6 @@ class cm_mspt_iph : public compute_module ssc_cmod_update, (void*)(this)); - // Set solver reporting outputs csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::TIME_FINAL, allocate("time_hr", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::ERR_M_DOT, allocate("m_dot_balance", n_steps_fixed), n_steps_fixed); @@ -1638,7 +1767,6 @@ class cm_mspt_iph : public compute_module csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::OP_MODE_2, allocate("op_mode_2", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::OP_MODE_3, allocate("op_mode_3", n_steps_fixed), n_steps_fixed); - csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::TOU_PERIOD, allocate("tou_value", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::PRICING_MULT, allocate("pricing_mult", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::PC_Q_DOT_SB, allocate("q_dot_pc_sb", n_steps_fixed), n_steps_fixed); @@ -1723,7 +1851,7 @@ class cm_mspt_iph : public compute_module log(out_msg, out_type); } - throw exec_error("tcsmolten_salt", csp_exception.m_error_message); + throw exec_error("mspt_iph", csp_exception.m_error_message); } // If no exception, then report messages @@ -2171,7 +2299,7 @@ class cm_mspt_iph : public compute_module log(out_msg); } - throw exec_error("tcsmolten_salt", csp_exception.m_error_message); + throw exec_error("mspt_iph", csp_exception.m_error_message); } // If no exception, then report messages @@ -2186,7 +2314,7 @@ class cm_mspt_iph : public compute_module // 'adjustment_factors' class stores factors in hourly array, so need to index as such adjustment_factors haf(this, "adjust"); if (!haf.setup(count)) - throw exec_error("tcsmolten_salt", "failed to setup adjustment factors: " + haf.error()); + throw exec_error("mspt_iph", "failed to setup adjustment factors: " + haf.error()); ssc_number_t* p_gen = allocate("gen", count); ssc_number_t* p_W_dot_parasitic_tot = as_array("W_dot_parasitic_tot", &count); From b5d096067a9496475e37bb4f127aa830d4327fca Mon Sep 17 00:00:00 2001 From: tyneises Date: Tue, 3 Oct 2023 13:56:25 -0500 Subject: [PATCH 09/85] add dispatch optimization for mslf iph --- ssc/cmod_fresnel_physical_iph.cpp | 146 ++++++++++++++++++++++++------ ssc/cmod_mspt_iph.cpp | 4 +- 2 files changed, 119 insertions(+), 31 deletions(-) diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 2e14c3482..1268fbb92 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -173,67 +173,59 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { // System Control + /*LK Only*/{ SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "?", "", "SIMULATION_PARAMETER" }, /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "pb_fixed_par", "Fixed parasitic load - runs at all times", "", "", "Sys_Control", "*", "", "" }, /*Sys Control*/{ SSC_INPUT, SSC_ARRAY, "bop_array", "Balance of plant parasitic power fraction", "", "", "Sys_Control", "*", "", "" }, /*Sys Control*/{ SSC_INPUT, SSC_ARRAY, "aux_array", "Aux heater, boiler parasitic", "", "", "Sys_Control", "*", "", "" }, /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "is_dispatch", "Allow dispatch optimization?", /*TRUE=1*/ "-", "", "Sys_Control", "?=0", "", "" }, - /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "is_dispatch_series", "Use time-series dispatch factors", "", "", "Sys_Control", "?=1", "", "" }, - /*Sys Control*/{ SSC_INPUT, SSC_ARRAY, "dispatch_series", "Time series dispatch factors", "", "", "Sys_Control", "", "", "" }, - /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "disp_frequency", "Frequency for dispatch optimization calculations", "hour", "", "Sys_Control", "is_dispatch=1", "", "" }, + + /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "disp_rsu_cost_rel", "Receiver startup cost", "$/MWt/start", "", "Sys_Control", "is_dispatch=1", "", "" }, /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "disp_horizon", "Time horizon for dispatch optimization", "hour", "", "Sys_Control", "is_dispatch=1", "", "" }, + /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "disp_frequency", "Frequency for dispatch optimization calculations", "hour", "", "Sys_Control", "is_dispatch=1", "", "" }, /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "disp_max_iter", "Max. no. dispatch optimization iterations", "-", "", "Sys_Control", "is_dispatch=1", "", "" }, /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "disp_timeout", "Max. dispatch optimization solve duration", "s", "", "Sys_Control", "is_dispatch=1", "", "" }, /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "disp_mip_gap", "Dispatch optimization solution tolerance", "-", "", "Sys_Control", "is_dispatch=1", "", "" }, /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "disp_time_weighting", "Dispatch optimization future time discounting factor", "-", "", "Sys_Control", "?=0.99", "", "" }, - /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "disp_rsu_cost_rel", "Receiver startup cost", "$/MWt/start", "", "Sys_Control", "is_dispatch=1", "", "" }, - /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "disp_csu_cost_rel", "Cycle startup cost", "$/MWe-cycle/start", "", "Sys_Control", "is_dispatch=1", "", "" }, - /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "disp_pen_ramping", "Dispatch cycle production change penalty", "$/MWe-change", "", "Sys_Control", "is_dispatch=1", "", "" }, /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "is_write_ampl_dat", "Write AMPL data files for dispatch run", "-", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "is_ampl_engine", "Run dispatch optimization with external AMPL engine", "-", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, /*LK Only*/{ SSC_INPUT, SSC_STRING, "ampl_data_dir", "AMPL data file directory", "-", "", "tou", "?=''", "", "SIMULATION_PARAMETER" }, /*LK Only*/{ SSC_INPUT, SSC_STRING, "ampl_exec_call", "System command to run AMPL code", "-", "", "tou", "?='ampl sdk_solution.run'", "", "SIMULATION_PARAMETER" }, - /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "can_cycle_use_standby", "Can the cycle use standby operation?", "", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "disp_steps_per_hour", "Time steps per hour for dispatch optimization calculations", "-", "", "tou", "?=1", "", "SIMULATION_PARAMETER" }, /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "disp_spec_presolve", "Dispatch optimization presolve heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "disp_spec_bb", "Dispatch optimization B&B heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "disp_reporting", "Dispatch optimization reporting level", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "disp_spec_scaling", "Dispatch optimization scaling heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "disp_inventory_incentive", "Dispatch storage terminal inventory incentive multiplier", "", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER" }, - /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "q_rec_standby", "Receiver standby energy consumption", "kWt", "", "tou", "?=9e99", "", "SIMULATION_PARAMETER" }, + + // Receiver control /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWe-hr", "", "tou", "?=0.0", "", "SIMULATION_PARAMETER" }, - /*LK Only*/{ SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "?", "", "SIMULATION_PARAMETER" }, + /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "q_rec_standby", "Receiver standby energy consumption", "kWt", "", "tou", "?=9e99", "", "SIMULATION_PARAMETER" }, - // Financials - /*Sys Design*/{SSC_INPUT, SSC_NUMBER, "csp_financial_model", "", "1-8", "", "Financial Model", "?=1", "INTEGER,MIN=0", ""}, + // Financials and Pricing schedules (copied from electricity - eventually need to resolve electricity vs. heat) + /*Sys Design*/{SSC_INPUT, SSC_NUMBER, "csp_financial_model", "", "1-8", "", "Financial Model", "*", "INTEGER,MIN=0", ""}, + /*Financial TOD Factors*/{ SSC_INPUT, SSC_NUMBER, "ppa_multiplier_model", "PPA multiplier model 0: dispatch factors dispatch_factorX, 1: hourly multipliers dispatch_factors_ts", "0/1", "", "tou", "?=0", /*need a default so this var works in required_if*/ "INTEGER,MIN=0", "SIMULATION_PARAMETER" }, + /*Fin Sol Mode Sing Own*/{ SSC_INPUT, SSC_NUMBER, "ppa_soln_mode", "PPA solution mode (0=Specify IRR target, 1=Specify PPA price)", "", "", "Financial Solution Mode", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, + /*Financial TOD Factors*/{ SSC_INPUT, SSC_ARRAY, "dispatch_factors_ts", "Dispatch payment factor array", "", "", "tou", "ppa_multiplier_model=1&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, + /*Financial TOD Factors*/{ SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekday", "PPA pricing weekday schedule, 12x24", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, + /*Financial TOD Factors*/{ SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekend", "PPA pricing weekend schedule, 12x24", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "dispatch_tod_factors", "TOD factors for periods 1 through 9", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, + /*Fin Sol Mode Sing Own*/{ SSC_INPUT, SSC_ARRAY, "ppa_price_input", "PPA solution mode (0=Specify IRR target, 1=Specify PPA price)", "", "", "Financial Solution Mode", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, + + // System Control /*Dipatch*/{ SSC_INPUT, SSC_MATRIX, "weekday_schedule", "12x24 Time of Use Values for week days", "", "", "Sys_Control", "*", "", "" }, /*Dipatch*/{ SSC_INPUT, SSC_MATRIX, "weekend_schedule", "12x24 Time of Use Values for week end days", "", "", "Sys_Control", "*", "", "" }, /*Dipatch*/{ SSC_INPUT, SSC_NUMBER, "is_tod_pc_target_also_pc_max","Is the TOD target cycle heat input also the max cycle heat input?", "", "", "tou", "?=0", "", "" }, /*Dipatch*/{ SSC_INPUT, SSC_ARRAY, "f_turb_tou_periods", "Dispatch logic for turbine load fraction", "-", "", "tou", "*", "", "" }, - /*Startup Script*/{ SSC_INPUT, SSC_NUMBER, "en_electricity_rates", "Enable electricity rates for grid purchase", "0/1", "", "Electricity Rates", "?=0", "", "SIMULATION_PARAMETER" }, - - - /*Financial TOD Factors*/{ SSC_INPUT, SSC_NUMBER, "ppa_multiplier_model", "PPA multiplier model 0: dispatch factors dispatch_factorX, 1: hourly multipliers dispatch_factors_ts", "0/1", "", "tou", "?=0", /*need a default so this var works in required_if*/ "INTEGER,MIN=0", "SIMULATION_PARAMETER" }, - /*Financial TOD Factors*/{ SSC_INPUT, SSC_ARRAY, "dispatch_factors_ts", "Dispatch payment factor array", "", "", "tou", "ppa_multiplier_model=1&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, - /*Financial TOD Factors*/{ SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekday", "PPA pricing weekday schedule, 12x24", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, - /*Financial TOD Factors*/{ SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekend", "PPA pricing weekend schedule, 12x24", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "dispatch_tod_factors", "TOD factors for periods 1 through 9", "", - "We added this array input after SAM 2022.12.21 to replace the functionality of former single value inputs dispatch_factor1 through dispatch_factor9", "Time of Delivery Factors","ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1","", "SIMULATION_PARAMETER" }, - - /*Fin Sol Mode Sing Own*/{ SSC_INPUT, SSC_NUMBER, "ppa_soln_mode", "PPA solution mode (0=Specify IRR target, 1=Specify PPA price)", "", "", "Financial Solution Mode", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, - /*Fin Sol Mode Sing Own*/{ SSC_INPUT, SSC_ARRAY, "ppa_price_input", "PPA solution mode (0=Specify IRR target, 1=Specify PPA price)", "", "", "Financial Solution Mode", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, - - /*Fin Merc Plant Energy*/{ SSC_INPUT, SSC_MATRIX, "mp_energy_market_revenue", "Energy market revenue input", "", "Lifetime x 2[Cleared Capacity(MW),Price($ / MWh)]", "Revenue", "csp_financial_model=6&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, // Capital Costs - // Direct Capital Costs /*Capital Costs*/{ SSC_INPUT, SSC_NUMBER, "site_improvements_spec_cost", "Site Improvement Cost per m2", "$/m2", "", "Capital_Costs", "?=0", "", "" }, /*Capital Costs*/{ SSC_INPUT, SSC_NUMBER, "solar_field_spec_cost", "Solar Field Cost per m2", "$/m2", "", "Capital_Costs", "?=0", "", "" }, @@ -599,6 +591,9 @@ class cm_fresnel_physical_iph : public compute_module void exec() { + FILE* fp = fopen("fresnel_iph_cmod_to_lk.lk", "w"); + write_cmod_to_lk_script(fp, m_vartab); + // Common Parameters bool is_dispatch = as_boolean("is_dispatch"); int sim_type = as_number("sim_type"); @@ -891,7 +886,7 @@ class cm_fresnel_physical_iph : public compute_module int csp_financial_model = as_integer("csp_financial_model"); if (sim_type == 1) { - if (csp_financial_model == 8 || csp_financial_model == 7 || csp_financial_model == 1) { // No Financial Model or LCOH; Single Owner in progress 9/2023 + if (csp_financial_model == 8 || csp_financial_model == 7) { // No Financial Model or LCOH; Single Owner in progress 9/2023 if (is_dispatch) { throw exec_error("fresnel_physical_iph", "Can't select dispatch optimization if No Financial model"); } @@ -906,6 +901,79 @@ class cm_fresnel_physical_iph : public compute_module } } } + else if (csp_financial_model == 1) { // Single Owner + + // Get first year base ppa price + bool is_ppa_price_input_assigned = is_assigned("ppa_price_input"); + if (is_dispatch && !is_ppa_price_input_assigned) { + throw exec_error("fresnel_physical_iph", "\n\nYou selected dispatch optimization which requires that the array input ppa_price_input is defined\n"); + } + + if (is_ppa_price_input_assigned) { + size_t count_ppa_price_input; + ssc_number_t* ppa_price_input_array = as_array("ppa_price_input", &count_ppa_price_input); + ppa_price_year1 = (double)ppa_price_input_array[0]; // [$/kWh] + } + else { + ppa_price_year1 = 1.0; //[-] don't need ppa multiplier if not optimizing + } + + int ppa_soln_mode = as_integer("ppa_soln_mode"); // PPA solution mode (0=Specify IRR target, 1=Specify PPA price) + if (ppa_soln_mode == 0 && is_dispatch) { + throw exec_error("fresnel_physical_iph", "\n\nYou selected dispatch optimization and the Specify IRR Target financial solution mode, " + "but dispatch optimization requires known absolute electricity prices. Dispatch optimization requires " + "the Specify PPA Price financial solution mode. You can continue using dispatch optimization and iteratively " + "solve for the PPA that results in a target IRR by running a SAM Parametric analysis or script.\n"); + } + + // Time-of-Delivery factors by time step: + int ppa_mult_model = as_integer("ppa_multiplier_model"); + if (ppa_mult_model == 1) // use dispatch_ts input + { + tou_params->mc_pricing.mv_is_diurnal = false; + + if (is_assigned("dispatch_factors_ts")) { + size_t nmultipliers; + ssc_number_t* multipliers = as_array("dispatch_factors_ts", &nmultipliers); + tou_params->mc_pricing.mvv_tou_arrays[C_block_schedule_pricing::MULT_PRICE].resize(nmultipliers, 0.0); + for (size_t ii = 0; ii < nmultipliers; ii++) + tou_params->mc_pricing.mvv_tou_arrays[C_block_schedule_pricing::MULT_PRICE][ii] = multipliers[ii]; + } + else { + tou_params->mc_pricing.mvv_tou_arrays[C_block_schedule_pricing::MULT_PRICE].resize(n_steps_fixed, -1.0); + } + } + else if (ppa_mult_model == 0) // standard diuranal input + { + tou_params->mc_pricing.mv_is_diurnal = true; + + // Most likely use case is to use schedules and TOD. So assume if at least one is provided, then user intended to use this approach + // the 'else' option applies non-feasible electricity prices, so we want to guard against selecting that it appears users + // are trying to use the schedules. + bool is_one_assigned = is_assigned("dispatch_sched_weekday") || is_assigned("dispatch_sched_weekend") || is_assigned("dispatch_tod_factors"); + + if (is_one_assigned || is_dispatch) { + + tou_params->mc_pricing.mc_weekdays = as_matrix("dispatch_sched_weekday"); + tou_params->mc_pricing.mc_weekends = as_matrix("dispatch_sched_weekend"); + + auto dispatch_tod_factors = as_vector_double("dispatch_tod_factors"); + if (dispatch_tod_factors.size() != 9) + throw exec_error("fresnel_physical_iph", util::format("\n\nDispatch TOD factors has %d periods instead of the expected 9.\n", (int)dispatch_tod_factors.size())); + + tou_params->mc_pricing.mvv_tou_arrays[C_block_schedule_pricing::MULT_PRICE].resize(9, 0.0); + for (size_t i = 0; i < 9; i++) + tou_params->mc_pricing.mvv_tou_arrays[C_block_schedule_pricing::MULT_PRICE][i] = dispatch_tod_factors[i]; + + } + else { + tou_params->mc_pricing.mc_weekdays.resize_fill(12, 24, 1.); + tou_params->mc_pricing.mc_weekends.resize_fill(12, 24, 1.); + tou_params->mc_pricing.mvv_tou_arrays[C_block_schedule_pricing::MULT_PRICE].resize(9, -1.0); + } + } + + } else { throw exec_error("fresnel_physical_iph", "csp_financial_model must be 1, 7, or 8"); } @@ -936,8 +1004,30 @@ class cm_fresnel_physical_iph : public compute_module // System Dispatch csp_dispatch_opt dispatch; - dispatch.solver_params.dispatch_optimize = false; + if (is_dispatch) { + + double heater_startup_cost = 0.0; + + double q_dot_rec_des = q_dot_pc_des * c_fresnel.m_solar_mult; //[MWt] + + dispatch.solver_params.set_user_inputs(is_dispatch, as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), + as_integer("disp_max_iter"), as_double("disp_mip_gap"), as_double("disp_timeout"), + as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting"), + as_boolean("is_write_ampl_dat"), as_boolean("is_ampl_engine"), as_string("ampl_data_dir"), as_string("ampl_exec_call")); + + bool can_cycle_use_standby = false; + double disp_csu_cost_calc = 0.0; + double disp_pen_ramping = 0.0; + + double disp_rsu_cost_calc = as_double("disp_rsu_cost_rel") * q_dot_rec_des; //[$/start] + dispatch.params.set_user_params(can_cycle_use_standby, as_double("disp_time_weighting"), + disp_rsu_cost_calc, heater_startup_cost, disp_csu_cost_calc, disp_pen_ramping, + as_double("disp_inventory_incentive"), as_double("q_rec_standby"), as_double("q_rec_heattrace"), ppa_price_year1); + } + else { + dispatch.solver_params.dispatch_optimize = false; + } // Instantiate Solver C_csp_solver csp_solver(weather_reader, diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index d89363f35..04f5e7847 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -1733,13 +1733,11 @@ class cm_mspt_iph : public compute_module bool can_cycle_use_standby = false; double disp_csu_cost_calc = 0.0; double disp_pen_ramping = 0.0; - double q_rec_standby = 9e99; - double q_rec_heattrace = 0.; double disp_rsu_cost_calc = as_double("disp_rsu_cost_rel") * q_dot_rec_des; //[$/start] dispatch.params.set_user_params(can_cycle_use_standby, as_double("disp_time_weighting"), disp_rsu_cost_calc, heater_startup_cost, disp_csu_cost_calc, disp_pen_ramping, - as_double("disp_inventory_incentive"), q_rec_standby, q_rec_heattrace, ppa_price_year1); + as_double("disp_inventory_incentive"), as_double("q_rec_standby"), as_double("q_rec_heattrace"), ppa_price_year1); } else { dispatch.solver_params.dispatch_optimize = false; From 4ffbf3c95fd201993f22ce6ce52bf2891c8533ab Mon Sep 17 00:00:00 2001 From: tyneises Date: Tue, 3 Oct 2023 14:03:35 -0500 Subject: [PATCH 10/85] comment line that writes cmod to file --- ssc/cmod_fresnel_physical_iph.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 1268fbb92..ee05ea4d2 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -591,8 +591,9 @@ class cm_fresnel_physical_iph : public compute_module void exec() { - FILE* fp = fopen("fresnel_iph_cmod_to_lk.lk", "w"); - write_cmod_to_lk_script(fp, m_vartab); + // Uncomment following 2 lines to write cmod inputs to LK script + //FILE* fp = fopen("fresnel_iph_cmod_to_lk.lk", "w"); + //write_cmod_to_lk_script(fp, m_vartab); // Common Parameters bool is_dispatch = as_boolean("is_dispatch"); From 0d5b825972db620e460013434c95a80a18184416 Mon Sep 17 00:00:00 2001 From: tyneises Date: Sat, 7 Oct 2023 22:11:06 -0500 Subject: [PATCH 11/85] add electricity purchases operating expense for heat models --- ssc/cmod_singleowner.cpp | 34 +++++++++++++++++++++++++++++----- ssc/common.cpp | 4 ++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/ssc/cmod_singleowner.cpp b/ssc/cmod_singleowner.cpp index ca52a892b..fdd3f6b3f 100644 --- a/ssc/cmod_singleowner.cpp +++ b/ssc/cmod_singleowner.cpp @@ -55,10 +55,13 @@ static var_info _cm_vtab_singleowner[] = { { SSC_INPUT, SSC_ARRAY, "gen", "Net power to or from the grid", "kW", "", "System Output", "*", "", "" }, - { SSC_INPUT, SSC_ARRAY, "gen_without_battery", "Electricity to or from the renewable system, without the battery", "kW", "", "System Output", "", "", "" }, + { SSC_INPUT, SSC_ARRAY, "gen_without_battery", "Electricity to or from the renewable system, without the battery", "kW", "", "System Output", "", "", "" }, + + { SSC_INPUT, SSC_ARRAY, "degradation", "Annual energy degradation", "", "", "System Output", "system_use_lifetime_output=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "system_capacity", "System nameplate capacity", "kW", "", "System Output", "?=0", "", "" }, + + { SSC_INPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption w/ avail derate", "kWe-hr", "", "Heat Model Output", "?=0", "", ""}, - { SSC_INPUT, SSC_ARRAY, "degradation", "Annual energy degradation", "", "", "System Output", "system_use_lifetime_output=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "system_capacity", "System nameplate capacity", "kW", "", "System Output", "?=0", "", "" }, /* PPA Buy Rate values */ { SSC_INPUT, SSC_ARRAY, "utility_bill_w_sys", "Electricity bill with system", "$", "", "Utility Bill", "", "", "" }, @@ -539,7 +542,10 @@ static var_info _cm_vtab_singleowner[] = { { SSC_OUTPUT, SSC_ARRAY, "cf_om_production2_expense", "O&M fuel cell production-based expense", "$", "", "Cash Flow Expenses", "", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_om_capacity2_expense", "O&M fuel cell capacity-based expense", "$", "", "Cash Flow Expenses", "", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_om_fuel_expense", "Fuel expense", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, - { SSC_OUTPUT, SSC_ARRAY, "cf_om_opt_fuel_1_expense", "Feedstock biomass expense", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_om_elec_price_for_heat_techs", "Electricity expense in heat models", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_om_opt_fuel_1_expense", "Feedstock biomass expense", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_om_opt_fuel_2_expense", "Feedstock coal expense", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_property_tax_assessed_value", "Property tax net assessed value", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, @@ -685,6 +691,7 @@ enum { CF_om_production2_expense, CF_om_capacity2_expense, CF_om_fuel_expense, + CF_om_elec_price_for_heat_techs, CF_om_opt_fuel_2_expense, CF_om_opt_fuel_1_expense, @@ -855,6 +862,8 @@ enum { CF_energy_sales, CF_energy_purchases, + CF_elec_purchases_for_heat_techs, + CF_energy_without_battery, CF_battery_discharged, @@ -999,6 +1008,8 @@ class cm_singleowner : public compute_module // In conjunction with SAM - take installed costs and salestax costs (for deducting if necessary) double cost_prefinancing = as_double("total_installed_cost"); + double annual_electricity_consumption_heat_model = as_double("annual_electricity_consumption"); + // use named range names for variables whenever possible double nameplate = as_double("system_capacity"); double year1_fuel_use = as_double("annual_fuel_usage"); // kWht @@ -1045,6 +1056,8 @@ class cm_singleowner : public compute_module escal_or_annual( CF_om_capacity_expense, nyears, "om_capacity", inflation_rate, 1.0, false, as_double("om_capacity_escal")*0.01 ); escal_or_annual( CF_om_fuel_expense, nyears, "om_fuel_cost", inflation_rate, as_double("system_heat_rate")*0.001, false, as_double("om_fuel_cost_escal")*0.01 ); + escal_or_annual(CF_om_elec_price_for_heat_techs, nyears, "om_elec_price_for_heat_techs", inflation_rate, 1.0, false, as_double("om_elec_price_for_heat_techs_escal")*0.01 ); + escal_or_annual( CF_om_opt_fuel_1_expense, nyears, "om_opt_fuel_1_cost", inflation_rate, 1.0, false, as_double("om_opt_fuel_1_cost_escal")*0.01 ); escal_or_annual( CF_om_opt_fuel_2_expense, nyears, "om_opt_fuel_2_cost", inflation_rate, 1.0, false, as_double("om_opt_fuel_2_cost_escal")*0.01 ); @@ -1181,6 +1194,7 @@ class cm_singleowner : public compute_module double first_year_energy = 0.0; double first_year_sales = 0.0; double first_year_purchases = 0.0; + double first_year_elec_purchases_for_heat_techs = as_double("annual_electricity_consumption"); //[kWe-hr] // degradation @@ -1211,6 +1225,9 @@ class cm_singleowner : public compute_module // dispatch if (as_integer("system_use_lifetime_output") == 1) { + if (first_year_elec_purchases_for_heat_techs > 0.0) + throw exec_error("singleowner", "system_use_lifetime_output must be 0 if using electricity purchases for heat technologies option"); + // hourly_enet includes all curtailment, availability for (size_t y = 1; y <= (size_t)nyears; y++) { @@ -1232,10 +1249,12 @@ class cm_singleowner : public compute_module cf.at(CF_energy_net, 1) = first_year_energy; cf.at(CF_energy_sales, 1) = first_year_sales; cf.at(CF_energy_purchases, 1) = first_year_purchases; + cf.at(CF_elec_purchases_for_heat_techs, 1) = first_year_elec_purchases_for_heat_techs; for (i = 1; i <= nyears; i++) { cf.at(CF_energy_net, i) = first_year_energy * cf.at(CF_degradation, i); cf.at(CF_energy_sales, i) = first_year_sales * cf.at(CF_degradation, i); cf.at(CF_energy_purchases, i) = first_year_purchases * cf.at(CF_degradation, i); + cf.at(CF_elec_purchases_for_heat_techs, i) = first_year_elec_purchases_for_heat_techs * cf.at(CF_degradation, i); } } @@ -1390,6 +1409,8 @@ class cm_singleowner : public compute_module cf.at(CF_om_capacity2_expense, i) *= nameplate2; cf.at(CF_om_fuel_expense,i) *= fuel_use[i]; + cf.at(CF_om_elec_price_for_heat_techs, i) *= cf.at(CF_elec_purchases_for_heat_techs, i); + //Battery Production OM Costs cf.at(CF_om_production1_expense, i) *= battery_discharged[i - 1]; //$/MWh * 0.001 MWh/kWh * kWh = $ cf.at(CF_om_production2_expense, i) *= fuelcell_discharged[i]; @@ -1603,6 +1624,7 @@ class cm_singleowner : public compute_module + cf.at(CF_om_production2_expense, i) + cf.at(CF_om_capacity2_expense, i) + cf.at(CF_om_fuel_expense, i) + + cf.at(CF_om_elec_price_for_heat_techs, i) + cf.at(CF_om_opt_fuel_1_expense, i) + cf.at(CF_om_opt_fuel_2_expense, i) + cf.at(CF_land_lease_expense, i) @@ -3434,6 +3456,7 @@ class cm_singleowner : public compute_module } save_cf( CF_om_fuel_expense, nyears, "cf_om_fuel_expense" ); + save_cf( CF_om_elec_price_for_heat_techs, nyears, "cf_om_elec_price_for_heat_techs"); save_cf( CF_om_opt_fuel_1_expense, nyears, "cf_om_opt_fuel_1_expense" ); save_cf( CF_om_opt_fuel_2_expense, nyears, "cf_om_opt_fuel_2_expense" ); save_cf(CF_land_lease_expense, nyears, "cf_land_lease_expense"); @@ -3858,11 +3881,12 @@ class cm_singleowner : public compute_module double pvFixedOandM = npv(CF_om_capacity_expense, nyears, nom_discount_rate); double pvVariableOandM = npv(CF_om_production_expense, nyears, nom_discount_rate); double pvFuelOandM = npv(CF_om_fuel_expense, nyears, nom_discount_rate); + double pvElec_price_for_heat_techs = npv(CF_om_elec_price_for_heat_techs, nyears, nom_discount_rate); double pvOptFuel1OandM = npv(CF_om_opt_fuel_1_expense, nyears, nom_discount_rate); double pvOptFuel2OandM = npv(CF_om_opt_fuel_2_expense, nyears, nom_discount_rate); // double pvWaterOandM = NetPresentValue(sv[svNominalDiscountRate], cf[cfAnnualWaterCost], analysis_period); - assign( "present_value_oandm", var_data((ssc_number_t)(pvAnnualOandM + pvFixedOandM + pvVariableOandM + pvFuelOandM))); // + pvWaterOandM); + assign( "present_value_oandm", var_data((ssc_number_t)(pvAnnualOandM + pvFixedOandM + pvVariableOandM + pvFuelOandM + pvElec_price_for_heat_techs))); // + pvWaterOandM); assign( "present_value_oandm_nonfuel", var_data((ssc_number_t)(pvAnnualOandM + pvFixedOandM + pvVariableOandM))); assign( "present_value_fuel", var_data((ssc_number_t)(pvFuelOandM + pvOptFuel1OandM + pvOptFuel2OandM))); diff --git a/ssc/common.cpp b/ssc/common.cpp index 675f89f8d..895d8f22d 100644 --- a/ssc/common.cpp +++ b/ssc/common.cpp @@ -113,6 +113,10 @@ var_info vtab_oandm[] = { { SSC_INPUT, SSC_NUMBER, "annual_fuel_usage", "Fuel usage (yr 1)", "kWht", "", "System Costs", "?=0", "MIN=0", "" }, { SSC_INPUT, SSC_ARRAY, "annual_fuel_usage_lifetime", "Fuel usage (lifetime)", "kWht", "", "System Costs", "", "", "" }, +{ SSC_INPUT, SSC_ARRAY, "om_elec_price_for_heat_techs", "Electricity price for purchases in heat model", "$/kWh", "", "System Costs", "?=0.0", "", "" }, +{ SSC_INPUT, SSC_NUMBER, "om_elec_price_for_heat_techs_escal", "Escalation for electricity price for purchases in heat model", "%/year", "", "System Costs", "?=0.0", "", "" }, + + // replacements { SSC_INPUT,SSC_ARRAY , "om_batt_replacement_cost" , "Replacement cost 1" , "$/kWh" , "" , "System Costs" , "?=0.0" , "" , ""}, { SSC_INPUT,SSC_ARRAY , "om_fuelcell_replacement_cost" , "Replacement cost 2" , "$/kW" , "" , "System Costs" , "?=0.0" , "" , ""}, From 5fb2b5bc89581a8fc09ab9632ad9fcd46b5ad005 Mon Sep 17 00:00:00 2001 From: tyneises Date: Mon, 18 Mar 2024 11:13:40 -0500 Subject: [PATCH 12/85] update cmod for required if for new auto exec check --- ssc/cmod_mspt_iph.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index b3759fbef..c291db8ff 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -165,8 +165,7 @@ static var_info _cm_vtab_mspt_iph[] = { { SSC_INPUT, SSC_NUMBER, "cav_rec_passive_eps", "Cavity receiver passive surface thermal emissivity", "", "", "Tower and Receiver", "receiver_type=1", "", "" }, -// New variables replacing deprecated variable "piping_loss". Variable currently not required so exec() can check if assigned and throw a more detailed error -{ SSC_INPUT, SSC_NUMBER, "piping_loss_coefficient", "Thermal loss per meter of piping", "Wt/m2-K", "", "Tower and Receiver", "", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "piping_loss_coefficient", "Thermal loss per meter of piping", "Wt/m2-K", "", "Tower and Receiver", "*", "", ""}, { SSC_INPUT, SSC_NUMBER, "rec_clearsky_model", "Clearsky model: None = -1, User-defined data = 0, Meinel = 1; Hottel = 2; Allen = 3; Moon = 4", "", "", "Tower and Receiver", "?=-1", "", "SIMULATION_PARAMETER"}, { SSC_INPUT, SSC_ARRAY, "rec_clearsky_dni", "User-defined clear-sky DNI", "W/m2", "", "Tower and Receiver", "rec_clearsky_model=0", "", "SIMULATION_PARAMETER"}, @@ -211,7 +210,7 @@ static var_info _cm_vtab_mspt_iph[] = { { SSC_INPUT, SSC_NUMBER, "allow_heater_no_dispatch_opt", "Allow heater with no dispatch optimization? SAM UI relies on cmod default", "", "", "System Costs", "?=0", "", "SIMULATION_PARAMETER" }, // TES parameters - general -{ SSC_INPUT, SSC_NUMBER, "tes_init_hot_htf_percent", "Initial fraction of available volume that is hot", "%", "", "Thermal Storage", "", /*not required because replacing deprecated var and checked in cmod*/ "", ""}, +{ SSC_INPUT, SSC_NUMBER, "tes_init_hot_htf_percent", "Initial fraction of available volume that is hot", "%", "", "Thermal Storage", "*", "", ""}, { SSC_INPUT, SSC_NUMBER, "h_tank", "Total height of tank (height of HTF when tank is full)", "m", "", "Thermal Storage", "*", "", ""}, { SSC_INPUT, SSC_NUMBER, "cold_tank_max_heat", "Rated heater capacity for cold tank heating", "MW", "", "Thermal Storage", "*", "", ""}, { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "Thermal Storage", "*", "", ""}, From 6b2dd7af863838f244c0284e568130910088894d Mon Sep 17 00:00:00 2001 From: tyneises Date: Mon, 18 Mar 2024 13:02:56 -0500 Subject: [PATCH 13/85] start modifying cmod for dispatch current state won't pass sam tests --- ssc/cmod_trough_physical_iph.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index f527fb80a..ef25f1a21 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -62,7 +62,8 @@ static var_info _cm_vtab_trough_physical_iph[] = { /* VARTYPE DATATYPE NAME LABEL UNITS META GROUP REQUIRED_IF CONSTRAINTS UI_HINTS*/ - { SSC_INPUT, SSC_NUMBER, "sim_type", "1 (default): timeseries, 2: design only", "", "", "System Control", "?=1", "", "SIMULATION_PARAMETER"}, + { SSC_INPUT, SSC_NUMBER, "is_dispatch", "Allow dispatch optimization?", "", "", "System Control", "?=0", "", ""}, + { SSC_INPUT, SSC_NUMBER, "sim_type", "1 (default): timeseries, 2: design only", "", "", "System Control", "?=1", "", "SIMULATION_PARAMETER"}, // Weather Reader { SSC_INPUT, SSC_STRING, "file_name", "Local weather file with path", "none", "", "weather", "?", "LOCAL_FILE", "" }, @@ -268,16 +269,6 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_ARRAY, "trough_loop_control", "trough_loop_control", "-", "", "controller", "*", "", "" }, - // **************************************************************************************************************************************** - // DEPRECATED INPUTS -- exec() checks if a) variable is assigned and b) if replacement variable is assigned. throws exception if a=true and b=false - // **************************************************************************************************************************************** - { SSC_INPUT, SSC_NUMBER, "piping_loss", "Thermal loss per meter of piping", "Wt/m", "", "Tower and Receiver", "", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "disp_csu_cost", "Cycle startup cost", "$", "", "System Control", "", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "disp_rsu_cost", "Receiver startup cost", "$", "", "System Control", "", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "disp_pen_delta_w", "Dispatch cycle production change penalty", "$/kWe-change", "", "tou", "", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "P_boil", "Boiler operating pressure", "bar", "", "powerblock", "", "", "SIMULATION_PARAMETER" }, - - // ADDED For Design Point { SSC_INPUT, SSC_NUMBER, "lat", "Latitude", "degree", "", "", "*", "", "" }, From 816e29bd4baf3360188339c496f1666e9a82e4a2 Mon Sep 17 00:00:00 2001 From: tyneises Date: Mon, 18 Mar 2024 15:34:32 -0500 Subject: [PATCH 14/85] update for iph single owner --- ssc/cmod_fresnel_physical_iph.cpp | 3 ++- ssc/cmod_trough_physical_iph.cpp | 25 +++++++++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index e70c81447..6f4878a92 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -279,6 +279,7 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { // System capacity required by downstream financial model { SSC_OUTPUT, SSC_NUMBER, "system_capacity", "System capacity", "kWt", "", "System Design", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "nameplate", "Nameplate capacity", "MWt", "", "System Design Calc", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "cp_system_nameplate", "System capacity for capacity payments", "MWt", "", "System Design", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "cp_battery_nameplate", "Battery nameplate", "MWt", "", "System Design", "*", "", "" }, @@ -1332,7 +1333,7 @@ class cm_fresnel_physical_iph : public compute_module // Assign { - assign("nameplate", nameplate * 1.E-3); // [MWt] + assign("nameplate", nameplate); // [MWt] assign("W_dot_bop_design", W_dot_bop_design); assign("W_dot_fixed", W_dot_fixed_parasitic_design); diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index ef25f1a21..02d16bf76 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -189,13 +189,30 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_MATRIX, "weekend_schedule", "12x24 CSP operation Time-of-Use Weekend schedule", "-", "", "tou", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "is_tod_pc_target_also_pc_max", "Is the TOD target cycle heat input also the max cycle heat input?", "", "", "tou", "?=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "can_cycle_use_standby", "Can the cycle use standby operation?", "", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "q_rec_standby", "Receiver standby energy consumption", "kWt", "", "tou", "?=9e99", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWe-hr", "", "tou", "?=0.0", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "f_turb_tou_periods", "Dispatch logic for turbine load fraction", "-", "", "tou", "*", "", "" }, + + // Dispatch optimization + { SSC_INPUT, SSC_NUMBER, "disp_rsu_cost_rel", "Receiver startup cost", "$/MWt/start", "", "Sys_Control", "is_dispatch=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_horizon", "Time horizon for dispatch optimization", "hour", "", "Sys_Control", "is_dispatch=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_frequency", "Frequency for dispatch optimization calculations", "hour", "", "Sys_Control", "is_dispatch=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_max_iter", "Max. no. dispatch optimization iterations", "-", "", "Sys_Control", "is_dispatch=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_timeout", "Max. dispatch optimization solve duration", "s", "", "Sys_Control", "is_dispatch=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_mip_gap", "Dispatch optimization solution tolerance", "-", "", "Sys_Control", "is_dispatch=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_time_weighting", "Dispatch optimization future time discounting factor", "-", "", "Sys_Control", "?=0.99", "", "" }, + { SSC_INPUT, SSC_NUMBER, "is_write_ampl_dat", "Write AMPL data files for dispatch run", "-", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "is_ampl_engine", "Run dispatch optimization with external AMPL engine", "-", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_STRING, "ampl_data_dir", "AMPL data file directory", "-", "", "tou", "?=''", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_STRING, "ampl_exec_call", "System command to run AMPL code", "-", "", "tou", "?='ampl sdk_solution.run'", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "q_rec_standby", "Receiver standby energy consumption", "kWt", "", "tou", "?=9e99", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWe-hr", "", "tou", "?=0.0", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "f_turb_tou_periods", "Dispatch logic for turbine load fraction", "-", "", "tou", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_steps_per_hour", "Time steps per hour for dispatch optimization calculations", "-", "", "tou", "?=1", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "disp_spec_presolve", "Dispatch optimization presolve heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "disp_spec_bb", "Dispatch optimization B&B heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "disp_reporting", "Dispatch optimization reporting level", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "disp_spec_scaling", "Dispatch optimization scaling heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "disp_inventory_incentive", "Dispatch storage terminal inventory incentive multiplier", "", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "csp_financial_model", "", "1-8", "", "Financial Model", "?=1", "INTEGER,MIN=0", "" }, { SSC_INPUT, SSC_NUMBER, "ppa_multiplier_model", "PPA multiplier model 0: dispatch factors dispatch_factorX, 1: hourly multipliers dispatch_factors_ts", "0/1", "0=diurnal,1=timestep", "tou", "?=0", /*need a default so this var works in required_if*/ "INTEGER,MIN=0", "SIMULATION_PARAMETER" }, @@ -669,7 +686,7 @@ class cm_trough_physical_iph : public compute_module double tshours = as_double("tshours"); //[-] double q_dot_pc_des = as_double("q_pb_design"); //[MWt] HEAT SINK design thermal power double Q_tes = q_dot_pc_des * tshours; //[MWt-hr] - int is_dispatch = 0; + int is_dispatch = as_boolean("is_dispatch"); // ***************************************************** // System Design Parameters From 638521ea6e57b6f71f28cca5d161e23e2af02b7c Mon Sep 17 00:00:00 2001 From: tyneises Date: Tue, 19 Mar 2024 14:56:36 -0500 Subject: [PATCH 15/85] get commercial fin model working --- ssc/cmod_trough_physical_iph.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 25b14b382..0af1a5b18 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -218,7 +218,6 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "ppa_multiplier_model", "PPA multiplier model 0: dispatch factors dispatch_factorX, 1: hourly multipliers dispatch_factors_ts", "0/1", "0=diurnal,1=timestep", "tou", "?=0", /*need a default so this var works in required_if*/ "INTEGER,MIN=0", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_ARRAY, "dispatch_factors_ts", "Dispatch payment factor array", "", "", "tou", "ppa_multiplier_model=1&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "ppa_soln_mode", "PPA solution mode (0=Specify IRR target, 1=Specify PPA price)", "", "", "Financial Solution Mode","ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "en_electricity_rates", "Enable electricity rates for grid purchase", "0/1", "", "Electricity Rates", "?=0", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekday", "12x24 PPA pricing Weekday schedule", "", "", "tou", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekend", "12x24 PPA pricing Weekend schedule", "", "", "tou", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_ARRAY, "dispatch_tod_factors", "TOD factors for periods 1 through 9", "", @@ -1272,6 +1271,13 @@ class cm_trough_physical_iph : public compute_module } } + else if (csp_financial_model == 5) { // Commercial + + // Need to figure out dispatch, but for now, just use something so that annual simulation solves + tou_params->mc_pricing.mc_weekdays.resize_fill(12, 24, 1.); + tou_params->mc_pricing.mc_weekends.resize_fill(12, 24, 1.); + tou_params->mc_pricing.mvv_tou_arrays[C_block_schedule_pricing::MULT_PRICE].resize(9, -1.0); + } else { throw exec_error("trough_physical_iph", "csp_financial_model must be 1, 7, or 8"); } From 1aaf6335c9799dc8d9426f0978ba80bc020845d0 Mon Sep 17 00:00:00 2001 From: Neises Date: Thu, 4 Apr 2024 14:47:02 -0500 Subject: [PATCH 16/85] add simulation_parameter to utility rate common copy inflation rate to utility rate common add utility rate common cmod to trough iph and load rate structures add example file to run trough iph commercial through sdktool --- samples/trough-iph-utility-rates/load.csv | 8760 +++++++++++++++++ samples/trough-iph-utility-rates/sscapi.h | 475 + .../timestep_load_fractions.csv | 8760 +++++++++++++++++ .../trough-commercial-default.lk | 635 ++ .../ur_ts_buy_rate.csv | 8760 +++++++++++++++++ .../ur_ts_sell_rate.csv | 8760 +++++++++++++++++ ssc/cmod_trough_physical_iph.cpp | 12 + ssc/common.cpp | 80 +- 8 files changed, 36207 insertions(+), 35 deletions(-) create mode 100644 samples/trough-iph-utility-rates/load.csv create mode 100644 samples/trough-iph-utility-rates/sscapi.h create mode 100644 samples/trough-iph-utility-rates/timestep_load_fractions.csv create mode 100644 samples/trough-iph-utility-rates/trough-commercial-default.lk create mode 100644 samples/trough-iph-utility-rates/ur_ts_buy_rate.csv create mode 100644 samples/trough-iph-utility-rates/ur_ts_sell_rate.csv diff --git a/samples/trough-iph-utility-rates/load.csv b/samples/trough-iph-utility-rates/load.csv new file mode 100644 index 000000000..0e9f75b52 --- /dev/null +++ b/samples/trough-iph-utility-rates/load.csv @@ -0,0 +1,8760 @@ +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 +5000 diff --git a/samples/trough-iph-utility-rates/sscapi.h b/samples/trough-iph-utility-rates/sscapi.h new file mode 100644 index 000000000..04c6aea27 --- /dev/null +++ b/samples/trough-iph-utility-rates/sscapi.h @@ -0,0 +1,475 @@ +/* +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. +*/ + + +/** + \file sscapi.h + + \brief SSC: SAM Simulation Core + + A general purpose simulation input/output framework. + Cross-platform (Windows/MacOSX/Unix) and is 32 and 64-bit compatible. + + Be sure to use the correct library for your operating platform: ssc32 + or ssc64. Opaque pointer types will be 4-byte pointer on 32-bit architectures, + and 8-byte pointer on 64-bit architectures. + + Shared libraries have the .dll file extension on Windows, + .dylib on MacOSX, and .so on Linux/Unix. + + \copyright 2012 National Renewable Energy Laboratory + \authors Aron Dobos, Steven Janzou + */ + +#ifndef __ssc_api_h +#define __ssc_api_h + +#if defined(__WINDOWS__)&&defined(__DLL__) +#define SSCEXPORT __declspec(dllexport) +#else +#define SSCEXPORT +#endif + +#ifndef __SSCLINKAGECPP__ + +#ifdef __cplusplus +extern "C" { +#endif + +#endif // __SSCLINKAGECPP__ + +/** Returns the library version number as an integer. Version numbers start at 1. */ +SSCEXPORT int ssc_version(); + +/** Returns information about the build configuration of this particular SSC library binary as a text string that lists the compiler, platform, build date/time and other information. */ +SSCEXPORT const char *ssc_build_info(); + +/** @name Data types: + * Possible data types for ssc_var_t in an ssc_data_t: +*/ +/**@{*/ +#define SSC_INVALID 0 +#define SSC_STRING 1 +#define SSC_NUMBER 2 +#define SSC_ARRAY 3 // only numeric entries +#define SSC_MATRIX 4 // only numeric entries +#define SSC_TABLE 5 +#define SSC_DATARR 6 // entries may be any SSC type +#define SSC_DATMAT 7 // entries may be any SSC type +/**@}*/ + +/** + * An opaque reference to a structure that holds a hash table of variables. This structure can contain any number of + * variables (ssc_var_t) referenced by name, which can hold strings, numbers, arrays, matrices, and tables. + * Matrices are stored in row-major order, where the array size is nrows*ncols, and the array index is calculated by + * r*ncols+c. + * + * An ssc_data_t object holds all input and output variables for a simulation. It does not distinguish + * between input, output, and input variables - that is handled at the model context level. + * + * Example of assigning an SSC_STRING: + * ssc_data_t p_data = ssc_data_create(); + * ssc_data_set_string(p_data, "string"); + * ... + * ssc_data_free(p_data); + */ +typedef void* ssc_data_t; + +/** + * An opaque reference to a structure that holds a variable of the possible types above. This structure provides a way + * to collect variables of different types into a single container, the ssc_data_t hash table. Each entry in a ssc_data_t + * can be worked with as ssc_var_t or indirectly from the ssc_data_* functions below. + * + * Example of assigning an SSC_DATARR: + * \verbatim + * ssc_data_t p_data = ssc_data_create(); + * ssc_var_t datarr[2]; + * for (size_t i = 0; i < 2; i++){ + * vd[i] = ssc_var_create(); + * ssc_var_set_string(vd[i], "string"); + * } + * ssc_data_set_data_array(data, "array", &vd[0], 2); + * ... + * # free p_data, datarr + * \endverbatim + */ +typedef void* ssc_var_t; + +/** The numeric type used in the SSC API. All numeric values are stored in this format. SSC uses 64-bit double numbers + * at the library interface and calculations inside compute modules generally are performed with double-precision + * 64-bit floating point internally. */ +typedef double ssc_number_t; + +/** The boolean type used internally in SSC. Zero values represent false; non-zero represents true. */ +typedef int ssc_bool_t; + +/** Returns an empty ssc_var_t which will need to be freed after use. */ +SSCEXPORT ssc_var_t ssc_var_create(); + +SSCEXPORT void ssc_var_free(ssc_var_t p_var); + +/** Clears all of the values in a var object. Type is reset to SSC_INVALID */ +SSCEXPORT void ssc_var_clear( ssc_var_t p_var ); + +/** Get type of variable as defined above, which will determine which setter and getter function must be used. */ +SSCEXPORT int ssc_var_query(ssc_var_t p_var); + +/** Invalid types are 0x0. Strings and numbers are 1x1. Arrays and tables are nx1 and matrices are nxm. */ +SSCEXPORT void ssc_var_size(ssc_var_t p_var, int* nrows, int* ncols); + +/** Assigns a copy of a string value to the p_var variable. Type becomes SSC_STRING. */ +SSCEXPORT void ssc_var_set_string( ssc_var_t p_var, const char *value ); + +/** Assigns a copy of a numeric value to the p_var variable. Type becomes SSC_NUMBER. */ +SSCEXPORT void ssc_var_set_number( ssc_var_t p_var, ssc_number_t value ); + +/** Assigns a copy of an array of doubles of given length to the p_var variable. Type becomes SSC_ARRAY. */ +SSCEXPORT void ssc_var_set_array( ssc_var_t p_var, ssc_number_t *pvalues, int length ); + +/** Assigns a copy of a matrix of doubles of given dimension to the p_var variable. Type becomes SSC_MATRIX. */ +SSCEXPORT void ssc_var_set_matrix( ssc_var_t p_var, ssc_number_t *pvalues, int nrows, int ncols ); + +/** Assigns a copy of a table to the p_var variable. Type becomes SSC_TABLE. */ +SSCEXPORT void ssc_var_set_table( ssc_var_t p_var, ssc_data_t table ); + +/** Assigns a copy of a variable to the r-th entry in an array of ssc_var_t. Type becomes SSC_DATARR. */ +SSCEXPORT void ssc_var_set_data_array(ssc_var_t p_var, ssc_var_t p_var_entry, int r ); + +/** Assigns a copy of a variable to the r,c-th entry in a matrix of ssc_var_t. Type becomes SSC_DATMAT. */ +SSCEXPORT void ssc_var_set_data_matrix(ssc_var_t p_var, ssc_var_t p_var_entry, int r, int c ); + +/** Returns a copy of the string value. */ +SSCEXPORT const char *ssc_var_get_string( ssc_var_t p_var); + +/** Returns a copy of the numeric value. */ +SSCEXPORT ssc_number_t ssc_var_get_number( ssc_var_t p_var ); + +/** Returns a reference to the numeric array and gets the length. */ +SSCEXPORT ssc_number_t *ssc_var_get_array(ssc_var_t p_var, int *length ); + +/** Returns a reference to the numeric matrix and gets the dimensions. */ +SSCEXPORT ssc_number_t *ssc_var_get_matrix( ssc_var_t p_var, int *nrows, int *ncols ); + +/** Returns a reference to the ssc_data_t table stored in p_var. */ +SSCEXPORT ssc_data_t ssc_var_get_table( ssc_var_t p_var); + +/** Returns a reference to the r-th variable entry in the variant array. */ +SSCEXPORT ssc_var_t ssc_var_get_var_array(ssc_var_t p_var, int r); + +/** Returns a reference to the r,c-th variable entry in the variant matrix. */ +SSCEXPORT ssc_var_t ssc_var_get_var_matrix(ssc_var_t p_var, int r, int c); + +/** Creates a new data object in memory. A data object stores a table of named values, where each value can be of any SSC datatype. */ +SSCEXPORT ssc_data_t ssc_data_create(); + +/** Frees the memory associated with a data object, where p_data is the data container to free. */ +SSCEXPORT void ssc_data_free( ssc_data_t p_data ); + +/** Clears all of the variables in a data object. Type becomes SSC_INVALID. */ +SSCEXPORT void ssc_data_clear( ssc_data_t p_data ); + +/** Unassigns the variable with the specified name. */ +SSCEXPORT void ssc_data_unassign( ssc_data_t p_data, const char *name ); + +/** Rename a variable in the data table. returns 1 if succeeded*/ +SSCEXPORT int ssc_data_rename( ssc_data_t p_data, const char *oldname, const char *newname ); + +/** Querys the data object for the data type of the variable with the specified name. Returns the data object's data type, or SSC_INVALID if that variable was not found. */ +SSCEXPORT int ssc_data_query( ssc_data_t p_data, const char *name ); + +/** Returns the name of the first variable in the table, or 0 (NULL) if the data object is empty. */ +SSCEXPORT const char *ssc_data_first( ssc_data_t p_data ); + +/** Returns the name of the next variable in the table, or 0 (NULL) if there are no more variables in the table. ssc_data_first must be called first. Example that iterates over all variables in a data object: + + \verbatim + const char *key = ssc_data_first( my_data ); + while (key != 0) + { + int type = ssc_data_query( my_data, key ); + key = ssc_data_next( my_data ); + } + \endverbatim + + */ +SSCEXPORT const char *ssc_data_next( ssc_data_t p_data ); + +SSCEXPORT ssc_var_t ssc_data_lookup(ssc_data_t p_data, const char *name); + +/** Returns a reference to a stored variable by case-matching the name. */ +SSCEXPORT ssc_var_t ssc_data_lookup_case(ssc_data_t p_data, const char *name); + +/** @name Assigning variable values. +The following functions do not take ownership of the data pointers for arrays, matrices, and tables. A deep copy is made into the internal SSC engine. You must remember to free the table that you create to pass into +ssc_data_set_table( ) for example. +*/ +/**@{*/ + +SSCEXPORT void ssc_data_set_var(ssc_data_t p_data, const char* name, ssc_var_t p_var); +SSCEXPORT void ssc_data_set_var_match_case(ssc_data_t p_data, const char* name, ssc_var_t p_var); + +/** Assigns value of type @a SSC_STRING */ +SSCEXPORT void ssc_data_set_string( ssc_data_t p_data, const char *name, const char *value ); + +/** Assigns value of type @a SSC_NUMBER */ +SSCEXPORT void ssc_data_set_number( ssc_data_t p_data, const char *name, ssc_number_t value ); + +/** Assigns value of type @a SSC_ARRAY */ +SSCEXPORT void ssc_data_set_array( ssc_data_t p_data, const char *name, ssc_number_t *pvalues, int length ); + +/** Assigns value of type @a SSC_MATRIX . Matrices are specified as a continuous array, in row-major order. Example: the matrix [[5,2,3],[9,1,4]] is stored as [5,2,3,9,1,4]. */ +SSCEXPORT void ssc_data_set_matrix( ssc_data_t p_data, const char *name, ssc_number_t *pvalues, int nrows, int ncols ); + +/** Assigns value of type @a SSC_TABLE. */ +SSCEXPORT void ssc_data_set_table( ssc_data_t p_data, const char *name, ssc_data_t table ); + +/** Assigns value of type @a SSC_DATAARR. */ +SSCEXPORT void ssc_data_set_data_array(ssc_data_t p_data, const char *name, ssc_var_t *data_array, int nrows ); + +/** Assigns value of type @a SSC_DATAMAT. */ +SSCEXPORT void ssc_data_set_data_matrix(ssc_data_t p_data, const char *name, ssc_var_t *data_matrix, int nrows, int ncols ); +/**@}*/ + +/** @name Retrieving variable values. +The following functions return internal references to memory, and the returned string, array, matrix, and tables should not be freed by the user. +*/ +/**@{*/ +/** Returns the value of a @a SSC_STRING variable with the given name. */ +SSCEXPORT const char *ssc_data_get_string( ssc_data_t p_data, const char *name ); + +/** Returns the value of a @a SSC_NUMBER variable with the given name. */ +SSCEXPORT ssc_bool_t ssc_data_get_number( ssc_data_t p_data, const char *name, ssc_number_t *value ); + +/** Returns the reference of a @a SSC_ARRAY variable with the given name. */ +SSCEXPORT ssc_number_t *ssc_data_get_array( ssc_data_t p_data, const char *name, int *length ); + +/** Returns the reference of a @a SSC_MATRIX variable with the given name. Matrices are specified as a continuous array, in row-major order. Example: the matrix [[5,2,3],[9,1,4]] is stored as [5,2,3,9,1,4]. */ +SSCEXPORT ssc_number_t *ssc_data_get_matrix( ssc_data_t p_data, const char *name, int *nrows, int *ncols ); + +/** Returns the reference of a @a SSC_TABLE variable with the given name. */ +SSCEXPORT ssc_data_t ssc_data_get_table( ssc_data_t p_data, const char *name ); + +/** Returns the reference of a @a SSC_DATAARR variable with the given name. */ +SSCEXPORT ssc_var_t ssc_data_get_data_array(ssc_data_t p_data, const char *name, int *nrows); + +/** Returns the reference of a @a SSC_DATAMAT variable with the given name. */ +SSCEXPORT ssc_var_t ssc_data_get_data_matrix(ssc_data_t p_data, const char *name, int* nrows, int* ncols ); + +SSCEXPORT ssc_bool_t ssc_data_deep_copy(ssc_data_t source, ssc_data_t dest); + +/**@}*/ + + +/** RapidJSON and ssc_data_t conversion functions + * + * Numerical json values (int, bool, real) map to SSC_NUMBER type. + * Json strings map to SSC_STRING type. + * Json arrays map to SSC_ARRAY, SSC_MATRIX, or SSC_DATARR type. + * Json objects map to SSC_TABLE type + */ +SSCEXPORT ssc_data_t json_to_ssc_data(const char* json_str); + +SSCEXPORT const char* ssc_data_to_json(ssc_data_t p_data); + + + +/** The opaque data structure that stores information about a compute module. */ +typedef void* ssc_entry_t; + +/** Returns compute module information for the i-th module in the SSC library. Returns 0 (NULL) for an invalid index. Example: + + \verbatim + int i=0; + ssc_entry_t p_entry; + while( p_entry = ssc_module_entry(i++) ) + { + printf("Compute Module '%s': \n", + ssc_entry_name(p_entry), + ssc_entry_description(p_entry) ); + } + \endverbatim +*/ +SSCEXPORT ssc_entry_t ssc_module_entry( int index ); + +/** Returns the name of a compute module. This is the name that is used to create a new compute module. */ +SSCEXPORT const char *ssc_entry_name( ssc_entry_t p_entry ); + +/** Returns a short text description of a compute module. */ +SSCEXPORT const char *ssc_entry_description( ssc_entry_t p_entry ); + +/** Returns version information about a compute module. */ +SSCEXPORT int ssc_entry_version( ssc_entry_t p_entry ); + +/** An opaque reference to a computation module. A computation module performs a transformation on a ssc_data_t. It usually is used to calculate output variables given a set of input variables, but it can also be used to change the values of variables defined as INOUT. Modules types have unique names, and store information about what input variables are required, what outputs can be expected, along with specific data type, unit, label, and meta information about each variable. */ +typedef void* ssc_module_t; + +/** An opaque reference to variable information. A compute module defines its input/output variables. */ +typedef void* ssc_info_t; + +/** Creates an instance of a compute module with the given name. Returns 0 (NULL) if invalid name given and the module could not be created */ +SSCEXPORT ssc_module_t ssc_module_create( const char *name ); + +/** Releases an instance of a compute module created with ssc_module_create */ +SSCEXPORT void ssc_module_free( ssc_module_t p_mod ); + +/** @name Variable types:*/ +/**@{*/ +#define SSC_INPUT 1 +#define SSC_OUTPUT 2 +#define SSC_INOUT 3 +/**@}*/ + +/** Returns references to variable info objects. Returns NULL for invalid index. Note that the ssc_info_* functions that return strings may return NULL if the computation module has not specified a value, i.e. no units or no grouping name. Example for a previously created 'p_mod' object: + + \verbatim + int i=0; + const ssc_info_t p_inf = NULL; + while ( p_inf = ssc_module_var_info( p_mod, i++ ) ) + { + int var_type = ssc_info_var_type( p_inf ); // SSC_INPUT, SSC_OUTPUT, SSC_INOUT + int data_type = ssc_info_data_type( p_inf ); // SSC_STRING, SSC_NUMBER, SSC_ARRAY, SSC_MATRIX + + const char *name = ssc_info_name( p_inf ); + const char *label = ssc_info_label( p_inf ); + const char *units = ssc_info_units( p_inf ); + const char *meta = ssc_info_meta( p_inf ); + const char *group = ssc_info_group( p_inf ); + } + \endverbatim +*/ +SSCEXPORT const ssc_info_t ssc_module_var_info( ssc_module_t p_mod, int index ); + +/** Returns variable type information: SSC_INPUT, SSC_OUTPUT, or SSC_INOUT */ +SSCEXPORT int ssc_info_var_type( ssc_info_t p_inf ); + +/** Returns the data type of a variable: SSC_STRING, SSC_NUMBER, SSC_ARRAY, SSC_MATRIX, SSC_TABLE */ +SSCEXPORT int ssc_info_data_type( ssc_info_t p_inf ); + +/** Returns the name of a variable */ +SSCEXPORT const char *ssc_info_name( ssc_info_t p_inf ); + +/** Returns the short label description of the variable */ +SSCEXPORT const char *ssc_info_label( ssc_info_t p_inf ); + +/** Returns the units of the values for the variable */ +SSCEXPORT const char *ssc_info_units( ssc_info_t p_inf ); + +/** Returns any extra information about a variable */ +SSCEXPORT const char *ssc_info_meta( ssc_info_t p_inf ); + +/** Returns any grouping information. Variables can be assigned to groups for presentation to the user, for example */ +SSCEXPORT const char *ssc_info_group( ssc_info_t p_inf ); + +/** Returns information about whether a variable is required to be assigned for +a compute module to run. It may alternatively be given a default value, specified as '?='. */ +SSCEXPORT const char *ssc_info_required( ssc_info_t p_inf ); + +/** Returns constraints on the values accepted. For example, MIN, MAX, BOOLEAN, INTEGER, POSITIVE are possible constraints. */ +SSCEXPORT const char *ssc_info_constraints( ssc_info_t p_inf ); + +/** Returns additional information for use in a target application about how to show the variable to the user. */ +SSCEXPORT const char *ssc_info_uihint( ssc_info_t p_inf ); + +/** Specify whether the built-in execution handler prints messages and progress updates to the command line console. */ +SSCEXPORT void ssc_module_exec_set_print( int print ); + +/** The simplest way to run a computation module over a data set. Simply specify the name of the module, and a data set. If the whole process succeeded, the function returns 1, otherwise 0. No error messages are available. This function can be thread-safe, depending on the computation module used. If the computation module requires the execution of external binary executables, it is not thread-safe. However, simpler implementations that do all calculations internally are probably thread-safe. Unfortunately there is no standard way to report the thread-safety of a particular computation module. */ +SSCEXPORT ssc_bool_t ssc_module_exec_simple( const char *name, ssc_data_t p_data ); + +/** Another very simple way to run a computation module over a data set. The function returns NULL on success. If something went wrong, the first error message is returned. Because the returned string references a common internal data container, this function is never thread-safe. */ +SSCEXPORT const char *ssc_module_exec_simple_nothread( const char *name, ssc_data_t p_data ); + +/** @name Action/notification types that can be sent to a handler function: + * SSC_LOG: Log a message in the handler. f0: (int)message type, f1: time, s0: message text, s1: unused. + * SSC_UPDATE: Notify simulation progress update. f0: percent done, f1: time, s0: current action text, s1: unused. +*/ +/**@{*/ +#define SSC_LOG 0 +#define SSC_UPDATE 1 +/**@}*/ + +/** Runs an instantiated computation module over the specified data set. Returns Boolean: 1 or 0. Detailed notices, warnings, and errors can be retrieved using the ssc_module_log function. */ +SSCEXPORT ssc_bool_t ssc_module_exec( ssc_module_t p_mod, ssc_data_t p_data ); /* uses default internal built-in handler */ + +/** An opaque pointer for transferring external executable output back to SSC */ +typedef void* ssc_handler_t; + +/** A full-featured way to run a compute module with a callback function to handle custom logging, progress updates, and cancelation requests. Returns Boolean: 1 or 0 indicating success or failure. */ +SSCEXPORT ssc_bool_t ssc_module_exec_with_handler( + ssc_module_t p_mod, + ssc_data_t p_data, + ssc_bool_t (*pf_handler)( ssc_module_t, ssc_handler_t, int action, float f0, float f1, const char *s0, const char *s1, void *user_data ), + void *pf_user_data ); + +/** @name Message types:*/ +/**@{*/ +#define SSC_NOTICE 1 +#define SSC_WARNING 2 +#define SSC_ERROR 3 +/**@}*/ + +/** Add a var info vartable to a compute module. */ +SSCEXPORT ssc_bool_t ssc_module_add_var_info(ssc_module_t, ssc_info_t); + +/** Adds the input variables required for a technology module to be used in a cmod_hybrid simulation. */ +SSCEXPORT ssc_bool_t ssc_module_hybridize(ssc_module_t p_mod); + +/** Retrive notices, warnings, and error messages from the simulation. Returns a NULL-terminated ASCII C string with the message text, or NULL if the index passed in was invalid. */ +SSCEXPORT const char *ssc_module_log( ssc_module_t p_mod, int index, int *item_type, float *time ); + +/** DO NOT CALL THIS FUNCTION: immediately causes a segmentation fault within the library. This is only useful for testing crash handling from an external application that is dynamically linked to the SSC library */ +SSCEXPORT void __ssc_segfault(); + +/** + * Functions for calling python as an external process with python_handler + */ + +SSCEXPORT int set_python_path(const char* abs_path); + +SSCEXPORT const char *get_python_path(); + +/** + * Functions for calling stateful compute modules + */ + +// returns 1 if successful, otherwise 0 with errors stored in log and retrieved with `ssc_module_log` +SSCEXPORT int ssc_stateful_module_setup(ssc_module_t p_mod, ssc_data_t p_data); + +#ifndef __SSCLINKAGECPP__ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif // __SSCLINKAGECPP__ + +#endif diff --git a/samples/trough-iph-utility-rates/timestep_load_fractions.csv b/samples/trough-iph-utility-rates/timestep_load_fractions.csv new file mode 100644 index 000000000..683519cd3 --- /dev/null +++ b/samples/trough-iph-utility-rates/timestep_load_fractions.csv @@ -0,0 +1,8760 @@ +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 diff --git a/samples/trough-iph-utility-rates/trough-commercial-default.lk b/samples/trough-iph-utility-rates/trough-commercial-default.lk new file mode 100644 index 000000000..914be5f6b --- /dev/null +++ b/samples/trough-iph-utility-rates/trough-commercial-default.lk @@ -0,0 +1,635 @@ +clear(); +var( 'is_dispatch', 0 ); +var( 'file_name', '../../test/input_cases/moltensalt_data/tucson_az_32.116521_-110.933042_psmv3_60_tmy.csv' ); +var( 'q_pb_design', 5.1900000000000004 ); +var( 'nHCEt', 4 ); +var( 'nColt', 4 ); +var( 'nHCEVar', 4 ); +var( 'FieldConfig', 1 ); +var( 'eta_pump', 0.84999999999999998 ); +var( 'Fluid', 31 ); +var( 'accept_loc', 1 ); +var( 'HDR_rough', 4.57e-05 ); +var( 'theta_stow', 170 ); +var( 'theta_dep', 10 ); +var( 'Row_Distance', 15 ); +var( 'T_loop_in_des', 90 ); +var( 'T_loop_out', 150 ); +var( 'm_dot_htfmin', 1 ); +var( 'm_dot_htfmax', 12 ); +var( 'field_fl_props', +[ [ 20, 4.1799999999999997, 999, 0.001, 9.9999999999999995e-07, 0.58699999999999997, 85.299999999999997 ], +[ 40, 4.1799999999999997, 993, 0.00065300000000000004, 6.5799999999999999e-07, 0.61799999999999999, 169 ], +[ 60, 4.1799999999999997, 984, 0.00046700000000000002, 4.75e-07, 0.64200000000000002, 252 ], +[ 80, 4.1900000000000004, 972, 0.00035500000000000001, 3.65e-07, 0.65700000000000003, 336 ], +[ 100, 4.21, 959, 0.00028200000000000002, 2.9400000000000001e-07, 0.66600000000000004, 420 ], +[ 120, 4.25, 944, 0.000233, 2.4600000000000001e-07, 0.67000000000000004, 505 ], +[ 140, 4.2800000000000002, 927, 0.00019699999999999999, 2.1199999999999999e-07, 0.67000000000000004, 590 ], +[ 160, 4.3399999999999999, 908, 0.00017100000000000001, 1.8799999999999999e-07, 0.66700000000000004, 676 ], +[ 180, 4.4000000000000004, 887, 0.00014999999999999999, 1.6899999999999999e-07, 0.66100000000000003, 764 ], +[ 200, 4.4900000000000002, 865, 0.000134, 1.55e-07, 0.65100000000000002, 852 ], +[ 220, 4.5800000000000001, 842, 0.000118, 1.4100000000000001e-07, 0.64100000000000001, 941 ] ] ); +var( 'T_fp', 10 ); +var( 'I_bn_des', 950 ); +var( 'Pipe_hl_coef', 0.45000000000000001 ); +var( 'SCA_drives_elec', 125 ); +var( 'tilt', 0 ); +var( 'azimuth', 0 ); +var( 'wind_stow_speed', 25 ); +var( 'accept_mode', 0 ); +var( 'accept_init', 0 ); +var( 'mc_bal_hot', 0.20000000000000001 ); +var( 'mc_bal_cold', 0.20000000000000001 ); +var( 'mc_bal_sca', 4.5 ); +var( 'W_aperture', [ 6, 6, 6, 6 ] ); +var( 'A_aperture', [ 656, 656, 656, 656 ] ); +var( 'TrackingError', [ 0.98799999999999999, 0.98799999999999999, 0.98799999999999999, 0.98799999999999999 ] ); +var( 'GeomEffects', [ 0.95199999999999996, 0.95199999999999996, 0.95199999999999996, 0.95199999999999996 ] ); +var( 'Rho_mirror_clean', [ 0.93000000000000005, 0.93000000000000005, 0.93000000000000005, 0.93000000000000005 ] ); +var( 'Dirt_mirror', [ 0.96999999999999997, 0.96999999999999997, 0.96999999999999997, 0.96999999999999997 ] ); +var( 'Error', [ 1, 1, 1, 1 ] ); +var( 'Ave_Focal_Length', [ 2.1499999999999999, 2.1499999999999999, 2.1499999999999999, 2.1499999999999999 ] ); +var( 'L_SCA', [ 115, 115, 115, 115 ] ); +var( 'L_aperture', [ 14.375, 14.375, 14.375, 14.375 ] ); +var( 'ColperSCA', [ 8, 8, 8, 8 ] ); +var( 'Distance_SCA', [ 1, 1, 1, 1 ] ); +var( 'IAM_matrix', +[ [ 1, 0.0327, -0.1351 ], +[ 1, 0.0327, -0.1351 ], +[ 1, 0.0327, -0.1351 ], +[ 1, 0.0327, -0.1351 ] ] ); +var( 'HCE_FieldFrac', +[ [ 1, 0, 0, 0 ], +[ 1, 0, 0, 0 ], +[ 1, 0, 0, 0 ], +[ 1, 0, 0, 0 ] ] ); +var( 'D_2', +[ [ 0.075999999999999998, 0.075999999999999998, 0.075999999999999998, 0.075999999999999998 ], +[ 0.075999999999999998, 0.075999999999999998, 0.075999999999999998, 0.075999999999999998 ], +[ 0.075999999999999998, 0.075999999999999998, 0.075999999999999998, 0.075999999999999998 ], +[ 0.075999999999999998, 0.075999999999999998, 0.075999999999999998, 0.075999999999999998 ] ] ); +var( 'D_3', +[ [ 0.080000000000000002, 0.080000000000000002, 0.080000000000000002, 0.080000000000000002 ], +[ 0.080000000000000002, 0.080000000000000002, 0.080000000000000002, 0.080000000000000002 ], +[ 0.080000000000000002, 0.080000000000000002, 0.080000000000000002, 0.080000000000000002 ], +[ 0.080000000000000002, 0.080000000000000002, 0.080000000000000002, 0.080000000000000002 ] ] ); +var( 'D_4', +[ [ 0.115, 0.115, 0.115, 0.115 ], +[ 0.115, 0.115, 0.115, 0.115 ], +[ 0.115, 0.115, 0.115, 0.115 ], +[ 0.115, 0.115, 0.115, 0.115 ] ] ); +var( 'D_5', +[ [ 0.12, 0.12, 0.12, 0.12 ], +[ 0.12, 0.12, 0.12, 0.12 ], +[ 0.12, 0.12, 0.12, 0.12 ], +[ 0.12, 0.12, 0.12, 0.12 ] ] ); +var( 'D_p', +[ [ 0, 0, 0, 0 ], +[ 0, 0, 0, 0 ], +[ 0, 0, 0, 0 ], +[ 0, 0, 0, 0 ] ] ); +var( 'Flow_type', +[ [ 1, 1, 1, 1 ], +[ 1, 1, 1, 1 ], +[ 1, 1, 1, 1 ], +[ 1, 1, 1, 1 ] ] ); +var( 'Rough', +[ [ 4.5000000000000003e-05, 4.5000000000000003e-05, 4.5000000000000003e-05, 4.5000000000000003e-05 ], +[ 4.5000000000000003e-05, 4.5000000000000003e-05, 4.5000000000000003e-05, 4.5000000000000003e-05 ], +[ 4.5000000000000003e-05, 4.5000000000000003e-05, 4.5000000000000003e-05, 4.5000000000000003e-05 ], +[ 4.5000000000000003e-05, 4.5000000000000003e-05, 4.5000000000000003e-05, 4.5000000000000003e-05 ] ] ); +var( 'alpha_env', +[ [ 0.02, 0.02, 0, 0 ], +[ 0.02, 0.02, 0, 0 ], +[ 0.02, 0.02, 0, 0 ], +[ 0.02, 0.02, 0, 0 ] ] ); +var( 'epsilon_3_11', +[ [ 100, 0.064000000000000001 ], +[ 150, 0.066500000000000004 ], +[ 200, 0.070000000000000007 ], +[ 250, 0.074499999999999997 ], +[ 300, 0.080000000000000002 ], +[ 350, 0.086499999999999994 ], +[ 400, 0.094 ], +[ 450, 0.10249999999999999 ], +[ 500, 0.112 ] ] ); +var( 'epsilon_3_12', +[ [ 0.65000000000000002 ] ] ); +var( 'epsilon_3_13', +[ [ 0.65000000000000002 ] ] ); +var( 'epsilon_3_14', +[ [ 0 ] ] ); +var( 'epsilon_3_21', +[ [ 100, 0.064000000000000001 ], +[ 150, 0.066500000000000004 ], +[ 200, 0.070000000000000007 ], +[ 250, 0.074499999999999997 ], +[ 300, 0.080000000000000002 ], +[ 350, 0.086499999999999994 ], +[ 400, 0.094 ], +[ 450, 0.10249999999999999 ], +[ 500, 0.112 ] ] ); +var( 'epsilon_3_22', +[ [ 0.65000000000000002 ] ] ); +var( 'epsilon_3_23', +[ [ 0.65000000000000002 ] ] ); +var( 'epsilon_3_24', +[ [ 0 ] ] ); +var( 'epsilon_3_31', +[ [ 100, 0.064000000000000001 ], +[ 150, 0.066500000000000004 ], +[ 200, 0.070000000000000007 ], +[ 250, 0.074499999999999997 ], +[ 300, 0.080000000000000002 ], +[ 350, 0.086499999999999994 ], +[ 400, 0.094 ], +[ 450, 0.10249999999999999 ], +[ 500, 0.112 ] ] ); +var( 'epsilon_3_32', +[ [ 0.65000000000000002 ] ] ); +var( 'epsilon_3_33', +[ [ 0.65000000000000002 ] ] ); +var( 'epsilon_3_34', +[ [ 0 ] ] ); +var( 'epsilon_3_41', +[ [ 100, 0.064000000000000001 ], +[ 150, 0.066500000000000004 ], +[ 200, 0.070000000000000007 ], +[ 250, 0.074499999999999997 ], +[ 300, 0.080000000000000002 ], +[ 350, 0.086499999999999994 ], +[ 400, 0.094 ], +[ 450, 0.10249999999999999 ], +[ 500, 0.112 ] ] ); +var( 'epsilon_3_42', +[ [ 0.65000000000000002 ] ] ); +var( 'epsilon_3_43', +[ [ 0.65000000000000002 ] ] ); +var( 'epsilon_3_44', +[ [ 0 ] ] ); +var( 'alpha_abs', +[ [ 0.96299999999999997, 0.96299999999999997, 0.80000000000000004, 0 ], +[ 0.96299999999999997, 0.96299999999999997, 0.80000000000000004, 0 ], +[ 0.96299999999999997, 0.96299999999999997, 0.80000000000000004, 0 ], +[ 0.96299999999999997, 0.96299999999999997, 0.80000000000000004, 0 ] ] ); +var( 'Tau_envelope', +[ [ 0.96399999999999997, 0.96399999999999997, 1, 0 ], +[ 0.96399999999999997, 0.96399999999999997, 1, 0 ], +[ 0.96399999999999997, 0.96399999999999997, 1, 0 ], +[ 0.96399999999999997, 0.96399999999999997, 1, 0 ] ] ); +var( 'EPSILON_4', +[ [ 0.85999999999999999, 0.85999999999999999, 1, 0 ], +[ 0.85999999999999999, 0.85999999999999999, 1, 0 ], +[ 0.85999999999999999, 0.85999999999999999, 1, 0 ], +[ 0.85999999999999999, 0.85999999999999999, 1, 0 ] ] ); +var( 'EPSILON_5', +[ [ 0.85999999999999999, 0.85999999999999999, 1, 0 ], +[ 0.85999999999999999, 0.85999999999999999, 1, 0 ], +[ 0.85999999999999999, 0.85999999999999999, 1, 0 ], +[ 0.85999999999999999, 0.85999999999999999, 1, 0 ] ] ); +var( 'GlazingIntactIn', +[ [ 1, 1, 0, 1 ], +[ 1, 1, 0, 1 ], +[ 1, 1, 0, 1 ], +[ 1, 1, 0, 1 ] ] ); +var( 'P_a', +[ [ 0.0001, 750, 750, 0 ], +[ 0.0001, 750, 750, 0 ], +[ 0.0001, 750, 750, 0 ], +[ 0.0001, 750, 750, 0 ] ] ); +var( 'AnnulusGas', +[ [ 27, 1, 1, 1 ], +[ 27, 1, 1, 1 ], +[ 27, 1, 1, 27 ], +[ 27, 1, 1, 27 ] ] ); +var( 'AbsorberMaterial', +[ [ 1, 1, 1, 1 ], +[ 1, 1, 1, 1 ], +[ 1, 1, 1, 1 ], +[ 1, 1, 1, 1 ] ] ); +var( 'Shadowing', +[ [ 0.93500000000000005, 0.93500000000000005, 0.93500000000000005, 0.96299999999999997 ], +[ 0.93500000000000005, 0.93500000000000005, 0.93500000000000005, 0.96299999999999997 ], +[ 0.93500000000000005, 0.93500000000000005, 0.93500000000000005, 0.96299999999999997 ], +[ 0.93500000000000005, 0.93500000000000005, 0.93500000000000005, 0.96299999999999997 ] ] ); +var( 'Dirt_HCE', +[ [ 0.97999999999999998, 0.97999999999999998, 1, 0.97999999999999998 ], +[ 0.97999999999999998, 0.97999999999999998, 1, 0.97999999999999998 ], +[ 0.97999999999999998, 0.97999999999999998, 1, 0.97999999999999998 ], +[ 0.97999999999999998, 0.97999999999999998, 1, 0.97999999999999998 ] ] ); +var( 'Design_loss', +[ [ 190, 1270, 1500, 0 ], +[ 190, 1270, 1500, 0 ], +[ 190, 1270, 1500, 0 ], +[ 190, 1270, 1500, 0 ] ] ); +var( 'rec_su_delay', 0.20000000000000001 ); +var( 'rec_qf_delay', 0.25 ); +var( 'p_start', 0.021000000000000001 ); +var( 'pb_pump_coef', 0.55000000000000004 ); +var( 'store_fluid', 31 ); +var( 'store_fl_props', +[ [ 1 ] ] ); +var( 'tshours', 6 ); +var( 'h_tank', 15 ); +var( 'u_tank', 0.29999999999999999 ); +var( 'tank_pairs', 1 ); +var( 'hot_tank_Thtr', 110 ); +var( 'hot_tank_max_heat', 1 ); +var( 'cold_tank_Thtr', 60 ); +var( 'cold_tank_max_heat', 0.5 ); +var( 'dt_hot', 5 ); +var( 'h_tank_min', 0.5 ); +var( 'init_hot_htf_percent', 30 ); +var( 'weekday_schedule', +[ [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] ] ); +var( 'weekend_schedule', +[ [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ], +[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] ] ); +var( 'is_tod_pc_target_also_pc_max', 0 ); +var( 'f_turb_tou_periods', [ 1, 1, 1, 1, 1, 1, 1, 1, 1 ] ); +var( 'disp_rsu_cost_rel', -123 ); +var( 'disp_horizon', -123 ); +var( 'disp_frequency', -123 ); +var( 'disp_max_iter', 0 ); +var( 'disp_timeout', -123 ); +var( 'disp_mip_gap', -123 ); +var( 'disp_time_weighting', -123 ); +var( 'csp_financial_model', 5 ); +var( 'is_dispatch_series', 0 ); +var( 'dispatch_series', [ 0 ] ); +var( 'is_timestep_load_fractions', 0 ); +var( 'timestep_load_fractions', real_array(read_text_file('timestep_load_fractions.csv'))); +var( 'pb_fixed_par', 0.0054999999999999997 ); +var( 'bop_array', [ 0, 1, 0, 0.48299999999999998, 0 ] ); +var( 'aux_array', [ 0.023, 1, 0.48299999999999998, 0.57099999999999995, 0 ] ); +var( 'water_usage_per_wash', 0.69999999999999996 ); +var( 'washing_frequency', 12 ); +var( 'calc_design_pipe_vals', 1 ); +var( 'V_hdr_cold_max', 3 ); +var( 'V_hdr_cold_min', 2 ); +var( 'V_hdr_hot_max', 3 ); +var( 'V_hdr_hot_min', 2 ); +var( 'N_max_hdr_diams', 10 ); +var( 'L_rnr_pb', 25 ); +var( 'L_rnr_per_xpan', 70 ); +var( 'L_xpan_hdr', 20 ); +var( 'L_xpan_rnr', 20 ); +var( 'Min_rnr_xpans', 1 ); +var( 'northsouth_field_sep', 20 ); +var( 'N_hdr_per_xpan', 2 ); +var( 'offset_xpan_hdr', 1 ); +var( 'custom_sf_pipe_sizes', 0 ); +var( 'sf_rnr_diams', +[ [ -1 ] ] ); +var( 'sf_rnr_wallthicks', +[ [ -1 ] ] ); +var( 'sf_rnr_lengths', +[ [ -1 ] ] ); +var( 'sf_hdr_diams', +[ [ -1 ] ] ); +var( 'sf_hdr_wallthicks', +[ [ -1 ] ] ); +var( 'sf_hdr_lengths', +[ [ -1 ] ] ); +var( 'tanks_in_parallel', 1 ); +var( 'has_hot_tank_bypass', 0 ); +var( 'T_tank_hot_inlet_min', 400 ); +var( 'tes_pump_coef', 0.14999999999999999 ); +var( 'V_tes_des', 1.8500000000000001 ); +var( 'custom_tes_p_loss', 0 ); +var( 'k_tes_loss_coeffs', +[ [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ] ); +var( 'custom_tes_pipe_sizes', 0 ); +var( 'tes_diams', +[ [ -1 ] ] ); +var( 'tes_wallthicks', +[ [ -1 ] ] ); +var( 'use_solar_mult_or_aperture_area', 0 ); +var( 'specified_solar_multiple', 2.5 ); +var( 'specified_total_aperture', 20000 ); +var( 'non_solar_field_land_area_multiplier', 1.1000000000000001 ); +var( 'trough_loop_control', [ 4, 1, 1, 4, 1, 1, 3, 1, 1, 2, 1, 1, 1 ] ); +var( 'csp.dtr.cost.site_improvements.cost_per_m2', 25 ); +var( 'csp.dtr.cost.solar_field.cost_per_m2', 150 ); +var( 'csp.dtr.cost.htf_system.cost_per_m2', 60 ); +var( 'csp.dtr.cost.storage.cost_per_kwht', 62 ); +var( 'csp.dtr.cost.heat_sink.cost_per_kwe', 120 ); +var( 'csp.dtr.cost.bop_per_kwe', 90 ); +var( 'csp.dtr.cost.contingency_percent', 7 ); +var( 'csp.dtr.cost.epc.per_acre', 0 ); +var( 'csp.dtr.cost.epc.percent', 11 ); +var( 'csp.dtr.cost.epc.per_watt', 0 ); +var( 'csp.dtr.cost.epc.fixed', 0 ); +var( 'csp.dtr.cost.plm.per_acre', 10000 ); +var( 'csp.dtr.cost.plm.percent', 0 ); +var( 'csp.dtr.cost.plm.per_watt', 0 ); +var( 'csp.dtr.cost.plm.fixed', 0 ); +var( 'csp.dtr.cost.sales_tax.percent', 80 ); +var( 'sales_tax_rate', 5 ); +var( 'const_per_interest_rate1', 0 ); +var( 'const_per_interest_rate2', 0 ); +var( 'const_per_interest_rate3', 0 ); +var( 'const_per_interest_rate4', 0 ); +var( 'const_per_interest_rate5', 0 ); +var( 'const_per_months1', 0 ); +var( 'const_per_months2', 0 ); +var( 'const_per_months3', 0 ); +var( 'const_per_months4', 0 ); +var( 'const_per_months5', 0 ); +var( 'const_per_percent1', 0 ); +var( 'const_per_percent2', 0 ); +var( 'const_per_percent3', 0 ); +var( 'const_per_percent4', 0 ); +var( 'const_per_percent5', 0 ); +var( 'const_per_upfront_rate1', 0 ); +var( 'const_per_upfront_rate2', 0 ); +var( 'const_per_upfront_rate3', 0 ); +var( 'const_per_upfront_rate4', 0 ); +var( 'const_per_upfront_rate5', 0 ); +var( 'adjust_constant', 4 ); +var( 'adjust_en_timeindex', 0 ); +var( 'adjust_en_periods', 0 ); +var( 'adjust_timeindex', [ 0 ] ); +var( 'adjust_periods', +[ [ 0, 0, 0 ] ] ); +var( 'rate_escalation', [ 0 ] ); +var( 'ur_metering_option', 4 ); +var( 'ur_nm_yearend_sell_rate', 0 ); +var( 'ur_nm_credit_month', 11 ); +var( 'ur_nm_credit_rollover', 0 ); +var( 'ur_monthly_fixed_charge', 30 ); +var( 'ur_monthly_min_charge', 0 ); +var( 'ur_annual_min_charge', 0 ); +var( 'ur_en_ts_sell_rate', 0 ); +var( 'ur_ts_sell_rate', real_array(read_text_file('ur_ts_sell_rate.csv'))); +var( 'ur_en_ts_buy_rate', 0 ); +var( 'ur_ts_buy_rate', real_array(read_text_file('ur_ts_buy_rate.csv'))); +var( 'ur_ec_sched_weekday', +[ [ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 4 ], +[ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 4 ], +[ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 4 ], +[ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 4 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 4 ], +[ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 4 ] ] ); +var( 'ur_ec_sched_weekend', +[ [ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 ], +[ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 ], +[ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 ], +[ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 ], +[ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 ] ] ); +var( 'ur_ec_tou_mat', +[ [ 1, 1, 9.9999999999999998e+37, 0, 0.050000000000000003, 0 ], +[ 2, 1, 9.9999999999999998e+37, 0, 0.074999999999999997, 0 ], +[ 3, 1, 9.9999999999999998e+37, 0, 0.059999999999999998, 0 ], +[ 4, 1, 9.9999999999999998e+37, 0, 0.050000000000000003, 0 ] ] ); +var( 'ur_dc_enable', 1 ); +var( 'ur_dc_sched_weekday', +[ [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2 ] ] ); +var( 'ur_dc_sched_weekend', +[ [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ], +[ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ] ] ); +var( 'ur_dc_tou_mat', +[ [ 1, 1, 100, 20 ], +[ 1, 2, 9.9999999999999998e+37, 15 ], +[ 2, 1, 100, 10 ], +[ 2, 2, 9.9999999999999998e+37, 5 ] ] ); +var( 'ur_dc_flat_mat', +[ [ 0, 1, 9.9999999999999998e+37, 0 ], +[ 1, 1, 9.9999999999999998e+37, 0 ], +[ 2, 1, 9.9999999999999998e+37, 0 ], +[ 3, 1, 9.9999999999999998e+37, 0 ], +[ 4, 1, 9.9999999999999998e+37, 0 ], +[ 5, 1, 9.9999999999999998e+37, 0 ], +[ 6, 1, 9.9999999999999998e+37, 0 ], +[ 7, 1, 9.9999999999999998e+37, 0 ], +[ 8, 1, 9.9999999999999998e+37, 0 ], +[ 9, 1, 9.9999999999999998e+37, 0 ], +[ 10, 1, 9.9999999999999998e+37, 0 ], +[ 11, 1, 9.9999999999999998e+37, 0 ] ] ); +var( 'ur_enable_billing_demand', 0 ); +var( 'ur_billing_demand_minimum', 100 ); +var( 'ur_billing_demand_lookback_period', 11 ); +var( 'ur_billing_demand_lookback_percentages', +[ [ 60, 0 ], +[ 60, 0 ], +[ 60, 0 ], +[ 60, 0 ], +[ 60, 0 ], +[ 95, 1 ], +[ 95, 1 ], +[ 95, 1 ], +[ 95, 1 ], +[ 60, 0 ], +[ 60, 0 ], +[ 60, 0 ] ] ); +var( 'ur_dc_billing_demand_periods', +[ [ 1, 1 ], +[ 2, 1 ] ] ); +var( 'ur_yearzero_usage_peaks', [ 234.67599999999999, 173.422, 172.00700000000001, 191.434, 198.29499999999999, 236.46899999999999, 274.23099999999999, 260.33600000000001, 226.751, 185.12299999999999, 156.19999999999999, 184.05000000000001 ] ); +var( 'analysis_period', 25 ); +var( 'system_use_lifetime_output', 0 ); +var( 'load', real_array(read_text_file('load.csv'))); +var( 'inflation_rate', 2.5 ); +var( 'degradation', [ 0 ] ); +var( 'load_escalation', [ 0 ] ); +var( 'federal_tax_rate', [ 21 ] ); +var( 'state_tax_rate', [ 7 ] ); +var( 'property_tax_rate', 0 ); +var( 'prop_tax_cost_assessed_percent', 100 ); +var( 'prop_tax_assessed_decline', 0 ); +var( 'real_discount_rate', 6.4000000000000004 ); +var( 'insurance_rate', 0 ); +var( 'system_capacity', 5190 ); +var( 'loan_term', 25 ); +var( 'loan_rate', 7 ); +var( 'debt_fraction', 100 ); +var( 'depr_fed_type', 1 ); +var( 'depr_fed_sl_years', 7 ); +var( 'depr_fed_custom', [ 0 ] ); +var( 'depr_sta_type', 1 ); +var( 'depr_sta_sl_years', 7 ); +var( 'depr_sta_custom', [ 0 ] ); +var( 'itc_fed_amount', [ 0 ] ); +var( 'itc_fed_amount_deprbas_fed', 1 ); +var( 'itc_fed_amount_deprbas_sta', 1 ); +var( 'itc_sta_amount', [ 0 ] ); +var( 'itc_sta_amount_deprbas_fed', 0 ); +var( 'itc_sta_amount_deprbas_sta', 0 ); +var( 'itc_fed_percent', [ 30 ] ); +var( 'itc_fed_percent_maxvalue', [ 9.9999999999999998e+37 ] ); +var( 'itc_fed_percent_deprbas_fed', 1 ); +var( 'itc_fed_percent_deprbas_sta', 1 ); +var( 'itc_sta_percent', [ 0 ] ); +var( 'itc_sta_percent_maxvalue', [ 9.9999999999999998e+37 ] ); +var( 'itc_sta_percent_deprbas_fed', 0 ); +var( 'itc_sta_percent_deprbas_sta', 0 ); +var( 'ptc_fed_amount', [ 0 ] ); +var( 'ptc_fed_term', 10 ); +var( 'ptc_fed_escal', 0 ); +var( 'ptc_sta_amount', [ 0 ] ); +var( 'ptc_sta_term', 10 ); +var( 'ptc_sta_escal', 0 ); +var( 'ibi_fed_amount', 0 ); +var( 'ibi_fed_amount_tax_fed', 1 ); +var( 'ibi_fed_amount_tax_sta', 1 ); +var( 'ibi_fed_amount_deprbas_fed', 0 ); +var( 'ibi_fed_amount_deprbas_sta', 0 ); +var( 'ibi_sta_amount', 0 ); +var( 'ibi_sta_amount_tax_fed', 1 ); +var( 'ibi_sta_amount_tax_sta', 1 ); +var( 'ibi_sta_amount_deprbas_fed', 0 ); +var( 'ibi_sta_amount_deprbas_sta', 0 ); +var( 'ibi_uti_amount', 0 ); +var( 'ibi_uti_amount_tax_fed', 1 ); +var( 'ibi_uti_amount_tax_sta', 1 ); +var( 'ibi_uti_amount_deprbas_fed', 0 ); +var( 'ibi_uti_amount_deprbas_sta', 0 ); +var( 'ibi_oth_amount', 0 ); +var( 'ibi_oth_amount_tax_fed', 1 ); +var( 'ibi_oth_amount_tax_sta', 1 ); +var( 'ibi_oth_amount_deprbas_fed', 0 ); +var( 'ibi_oth_amount_deprbas_sta', 0 ); +var( 'ibi_fed_percent', 0 ); +var( 'ibi_fed_percent_maxvalue', 9.9999999999999998e+37 ); +var( 'ibi_fed_percent_tax_fed', 1 ); +var( 'ibi_fed_percent_tax_sta', 1 ); +var( 'ibi_fed_percent_deprbas_fed', 0 ); +var( 'ibi_fed_percent_deprbas_sta', 0 ); +var( 'ibi_sta_percent', 0 ); +var( 'ibi_sta_percent_maxvalue', 9.9999999999999998e+37 ); +var( 'ibi_sta_percent_tax_fed', 1 ); +var( 'ibi_sta_percent_tax_sta', 1 ); +var( 'ibi_sta_percent_deprbas_fed', 0 ); +var( 'ibi_sta_percent_deprbas_sta', 0 ); +var( 'ibi_uti_percent', 0 ); +var( 'ibi_uti_percent_maxvalue', 9.9999999999999998e+37 ); +var( 'ibi_uti_percent_tax_fed', 1 ); +var( 'ibi_uti_percent_tax_sta', 1 ); +var( 'ibi_uti_percent_deprbas_fed', 0 ); +var( 'ibi_uti_percent_deprbas_sta', 0 ); +var( 'ibi_oth_percent', 0 ); +var( 'ibi_oth_percent_maxvalue', 9.9999999999999998e+37 ); +var( 'ibi_oth_percent_tax_fed', 1 ); +var( 'ibi_oth_percent_tax_sta', 1 ); +var( 'ibi_oth_percent_deprbas_fed', 0 ); +var( 'ibi_oth_percent_deprbas_sta', 0 ); +var( 'cbi_fed_amount', 0 ); +var( 'cbi_fed_maxvalue', 9.9999999999999998e+37 ); +var( 'cbi_fed_tax_fed', 1 ); +var( 'cbi_fed_tax_sta', 1 ); +var( 'cbi_fed_deprbas_fed', 0 ); +var( 'cbi_fed_deprbas_sta', 0 ); +var( 'cbi_sta_amount', 0 ); +var( 'cbi_sta_maxvalue', 9.9999999999999998e+37 ); +var( 'cbi_sta_tax_fed', 1 ); +var( 'cbi_sta_tax_sta', 1 ); +var( 'cbi_sta_deprbas_fed', 0 ); +var( 'cbi_sta_deprbas_sta', 0 ); +var( 'cbi_uti_amount', 0 ); +var( 'cbi_uti_maxvalue', 9.9999999999999998e+37 ); +var( 'cbi_uti_tax_fed', 1 ); +var( 'cbi_uti_tax_sta', 1 ); +var( 'cbi_uti_deprbas_fed', 0 ); +var( 'cbi_uti_deprbas_sta', 0 ); +var( 'cbi_oth_amount', 0 ); +var( 'cbi_oth_maxvalue', 9.9999999999999998e+37 ); +var( 'cbi_oth_tax_fed', 1 ); +var( 'cbi_oth_tax_sta', 1 ); +var( 'cbi_oth_deprbas_fed', 0 ); +var( 'cbi_oth_deprbas_sta', 0 ); +var( 'pbi_fed_amount', [ 0 ] ); +var( 'pbi_fed_term', 10 ); +var( 'pbi_fed_escal', 0 ); +var( 'pbi_fed_tax_fed', 1 ); +var( 'pbi_fed_tax_sta', 1 ); +var( 'pbi_sta_amount', [ 0 ] ); +var( 'pbi_sta_term', 10 ); +var( 'pbi_sta_escal', 0 ); +var( 'pbi_sta_tax_fed', 1 ); +var( 'pbi_sta_tax_sta', 1 ); +var( 'pbi_uti_amount', [ 0 ] ); +var( 'pbi_uti_term', 10 ); +var( 'pbi_uti_escal', 0 ); +var( 'pbi_uti_tax_fed', 1 ); +var( 'pbi_uti_tax_sta', 1 ); +var( 'pbi_oth_amount', [ 0 ] ); +var( 'pbi_oth_term', 10 ); +var( 'pbi_oth_escal', 0 ); +var( 'pbi_oth_tax_fed', 1 ); +var( 'pbi_oth_tax_sta', 1 ); +var( 'total_installed_cost', 9929673.7379999999 ); +var( 'salvage_percentage', 0 ); +var( 'batt_salvage_percentage', 0 ); +run('trough_physical_iph'); +run('utilityrate5'); +run('cashloan'); +outln('Annual energy (year 1) kWh-t ' + var('annual_energy')); +outln('Capacity factor% ' + var('capacity_factor')); +outln('Annual electricity load (year 1) kWh-e ' + var('annual_electricity_consumption')); +outln('Annual Water Usage m^3 ' + var('annual_total_water_use')); +outln('LCOE Levelized cost of energy nominal ¢/kWh ' + var('lcoe_nom')); +outln('LCOE Levelized cost of energy real ¢/kWh ' + var('lcoe_real')); +outln('Electricity bill without system (year 1)$ ' + var('elec_cost_without_system_year1')); +outln('Electricity bill with system (year 1)$ ' + var('elec_cost_with_system_year1')); +outln('Net savings with system (year 1)$ ' + var('savings_year1')); +outln('Net present value$ ' + var('npv')); +outln('Simple payback period years ' + var('payback')); +outln('Discounted payback period years ' + var('discounted_payback')); +outln('Net capital cost$ ' + var('adjusted_installed_cost')); +outln('Equity$ ' + var('first_cost')); +outln('Debt$ ' + var('loan_amount')); diff --git a/samples/trough-iph-utility-rates/ur_ts_buy_rate.csv b/samples/trough-iph-utility-rates/ur_ts_buy_rate.csv new file mode 100644 index 000000000..705ebee6d --- /dev/null +++ b/samples/trough-iph-utility-rates/ur_ts_buy_rate.csv @@ -0,0 +1,8760 @@ +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 diff --git a/samples/trough-iph-utility-rates/ur_ts_sell_rate.csv b/samples/trough-iph-utility-rates/ur_ts_sell_rate.csv new file mode 100644 index 000000000..705ebee6d --- /dev/null +++ b/samples/trough-iph-utility-rates/ur_ts_sell_rate.csv @@ -0,0 +1,8760 @@ +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 0af1a5b18..f48b9d904 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -52,6 +52,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +// for utility rates +#include "cmod_utilityrate5.h" + // signed/unsigned mismatch #pragma warning (disable : 4388) @@ -664,6 +667,9 @@ class cm_trough_physical_iph : public compute_module add_var_info( _cm_vtab_trough_physical_iph ); add_var_info( vtab_adjustment_factors ); add_var_info(vtab_technology_outputs); + + add_var_info(vtab_utility_rate_common); // Required for dispatch w/ utility rates + } void exec( ) @@ -1273,6 +1279,12 @@ class cm_trough_physical_iph : public compute_module } else if (csp_financial_model == 5) { // Commercial + bool is_ur_assigned = is_assigned("ur_en_ts_sell_rate"); + + // rate data setup from ~ line 1336 in cmod_battery.cpp + rate_data* util_rate_data = new rate_data(); + rate_setup::setup(m_vartab, 8760, 1, *util_rate_data, "cmod_trough_physical_iph"); + // Need to figure out dispatch, but for now, just use something so that annual simulation solves tou_params->mc_pricing.mc_weekdays.resize_fill(12, 24, 1.); tou_params->mc_pricing.mc_weekends.resize_fill(12, 24, 1.); diff --git a/ssc/common.cpp b/ssc/common.cpp index 50e170fbd..b21979926 100644 --- a/ssc/common.cpp +++ b/ssc/common.cpp @@ -1002,65 +1002,75 @@ var_info_invalid }; var_info vtab_utility_rate_common[] = { -/* VARTYPE DATATYPE NAME LABEL UNITS META GROUP REQUIRED_IF CONSTRAINTS UI_HINTS */ +/* VARTYPE DATATYPE NAME LABEL UNITS META GROUP REQUIRED_IF CONSTRAINTS UI_HINTS */ - { SSC_INPUT, SSC_NUMBER, "en_electricity_rates", "Optionally enable/disable electricity_rate", "years", "", "Electricity Rates", "", "INTEGER,MIN=0,MAX=1", "" }, // Required for battery to use retail rates + { SSC_INPUT, SSC_NUMBER, "en_electricity_rates", "Optionally enable/disable electricity_rate", "years", "", "Electricity Rates", "", "INTEGER,MIN=0,MAX=1", "SIMULATION_PARAMETER" }, // Required for battery to use retail rates - { SSC_INPUT, SSC_ARRAY, "rate_escalation", "Annual electricity rate escalation", "%/year", "", "Electricity Rates", "?=0", "", "" }, + // 24.04.04 twn added to access setup() + { SSC_INPUT, SSC_NUMBER, "inflation_rate", "Inflation rate", "%", "", "Lifetime", "*", "MIN=-99", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "ur_metering_option", "Metering options", "0=net energy metering,1=net energy metering with $ credits,2=net billing,3=net billing with carryover to next month,4=buy all - sell all", "Net metering monthly excess", "Electricity Rates", "?=0", "INTEGER,MIN=0,MAX=4", "" }, + // ----------------------------- - { SSC_INPUT, SSC_NUMBER, "ur_nm_yearend_sell_rate", "Net metering true-up credit sell rate", "$/kWh", "", "Electricity Rates", "?=0.0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "ur_nm_credit_month", "Month of year end payout (true-up)", "mn", "", "Electricity Rates", "?=11", "INTEGER,MIN=0,MAX=11", "" }, - { SSC_INPUT, SSC_NUMBER, "ur_nm_credit_rollover", "Apply net metering true-up credits to future bills", "0/1", "0=disable,1=enable", "Electricity Rates", "?=0", "INTEGER,MIN=0,MAX=1", "" }, - { SSC_INPUT, SSC_NUMBER, "ur_monthly_fixed_charge", "Monthly fixed charge", "$", "", "Electricity Rates", "?=0.0", "", "" }, - - // optional input that allows sell rates to be overridden with buy rates - defaults to not override - { SSC_INPUT, SSC_NUMBER, "ur_sell_eq_buy", "Set sell rate equal to buy rate", "0/1", "Optional override", "Electricity Rates", "?=0", "BOOLEAN", "" }, + { SSC_INPUT, SSC_ARRAY, "rate_escalation", "Annual electricity rate escalation", "%/year", "", "Electricity Rates", "?=0", "", "SIMULATION_PARAMETER" }, + + { SSC_INPUT, SSC_NUMBER, "ur_metering_option", "Metering options", "0=net energy metering,1=net energy metering with $ credits,2=net billing,3=net billing with carryover to next month,4=buy all - sell all", // continued on next row + "Net metering monthly excess","Electricity Rates", "?=0", "INTEGER,MIN=0,MAX=4", "SIMULATION_PARAMETER" }, + + { SSC_INPUT, SSC_NUMBER, "ur_nm_yearend_sell_rate", "Net metering true-up credit sell rate", "$/kWh", "", "Electricity Rates", "?=0.0", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "ur_nm_credit_month", "Month of year end payout (true-up)", "mn", "", "Electricity Rates", "?=11", "INTEGER,MIN=0,MAX=11", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "ur_nm_credit_rollover", "Apply net metering true-up credits to future bills", "0/1", "0=disable,1=enable", "Electricity Rates", "?=0", "INTEGER,MIN=0,MAX=1", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "ur_monthly_fixed_charge", "Monthly fixed charge", "$", "", "Electricity Rates", "?=0.0", "", "SIMULATION_PARAMETER" }, - // urdb minimums - { SSC_INPUT, SSC_NUMBER, "ur_monthly_min_charge", "Monthly minimum charge", "$", "", "Electricity Rates", "?=0.0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "ur_annual_min_charge", "Annual minimum charge", "$", "", "Electricity Rates", "?=0.0", "", "" }, - - // time step rates - { SSC_INPUT, SSC_NUMBER, "ur_en_ts_sell_rate", "Enable time step sell rates", "0/1", "0=disable,1=enable", "Electricity Rates", "?=0", "BOOLEAN", "" }, - { SSC_INPUT, SSC_ARRAY, "ur_ts_sell_rate", "Time step sell rates", "$/kWh", "", "Electricity Rates", "", "", "" }, - // add separately to UI - { SSC_INPUT, SSC_NUMBER, "ur_en_ts_buy_rate", "Enable time step buy rates", "0/1", "0=disable,1=enable", "Electricity Rates", "?=0", "BOOLEAN", "" }, - { SSC_INPUT, SSC_ARRAY, "ur_ts_buy_rate", "Time step buy rates", "$/kWh", "", "Electricity Rates", "", "", "" }, + // optional input that allows sell rates to be overridden with buy rates - defaults to not override + { SSC_INPUT, SSC_NUMBER, "ur_sell_eq_buy", "Set sell rate equal to buy rate", "0/1", "Optional override", "Electricity Rates", "?=0", "BOOLEAN", "SIMULATION_PARAMETER" }, + + + // urdb minimums + { SSC_INPUT, SSC_NUMBER, "ur_monthly_min_charge", "Monthly minimum charge", "$", "", "Electricity Rates", "?=0.0", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "ur_annual_min_charge", "Annual minimum charge", "$", "", "Electricity Rates", "?=0.0", "", "SIMULATION_PARAMETER" }, + + // time step rates + { SSC_INPUT, SSC_NUMBER, "ur_en_ts_sell_rate", "Enable time step sell rates", "0/1", "0=disable,1=enable", "Electricity Rates", "?=0", "BOOLEAN", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "ur_ts_sell_rate", "Time step sell rates", "$/kWh", "", "Electricity Rates", "", "", "SIMULATION_PARAMETER" }, + // add separately to UI + { SSC_INPUT, SSC_NUMBER, "ur_en_ts_buy_rate", "Enable time step buy rates", "0/1", "0=disable,1=enable", "Electricity Rates", "?=0", "BOOLEAN", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "ur_ts_buy_rate", "Time step buy rates", "$/kWh", "", "Electricity Rates", "", "", "SIMULATION_PARAMETER" }, // Energy Charge Inputs - { SSC_INPUT, SSC_MATRIX, "ur_ec_sched_weekday", "Energy charge weekday schedule", "Periods defined in ur_ec_tou_mat", "12x24", "Electricity Rates", "", "", "" }, - { SSC_INPUT, SSC_MATRIX, "ur_ec_sched_weekend", "Energy charge weekend schedule", "Periods defined in ur_ec_tou_mat", "12x24", "Electricity Rates", "", "", "" }, + { SSC_INPUT, SSC_MATRIX, "ur_ec_sched_weekday", "Energy charge weekday schedule", "Periods defined in ur_ec_tou_mat", "12x24", "Electricity Rates", "", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_MATRIX, "ur_ec_sched_weekend", "Energy charge weekend schedule", "Periods defined in ur_ec_tou_mat", "12x24", "Electricity Rates", "", "", "SIMULATION_PARAMETER" }, // ur_ec_tou_mat has 6 columns period, tier, max usage, max usage units, buy rate, sell rate // replaces 12(P)*6(T)*(max usage+buy+sell) = 216 single inputs - { SSC_INPUT, SSC_MATRIX, "ur_ec_tou_mat", "Energy rates table", "col 0=period no, col 1=tier no, col 2=max usage, col 3=max usage units (0=kWh, 1=kWh/kW, 2=kWh daily, 3=kWh/kW daily), col 4=buy rate ($/kWh), col 5=sell rate ($/kWh)", "nx6", "Electricity Rates", "", "", "" }, + { SSC_INPUT, SSC_MATRIX, "ur_ec_tou_mat", "Energy rates table", "col 0=period no, col 1=tier no, col 2=max usage, col 3=max usage units (0=kWh, 1=kWh/kW, 2=kWh daily, 3=kWh/kW daily), col 4=buy rate ($/kWh), col 5=sell rate ($/kWh)", + "nx6", "Electricity Rates", "", "", "SIMULATION_PARAMETER" }, // Demand Charge Inputs - { SSC_INPUT, SSC_NUMBER, "ur_dc_enable", "Enable demand charge", "0/1", "0=disable,1=enable", "Electricity Rates", "?=0", "BOOLEAN", "" }, + { SSC_INPUT, SSC_NUMBER, "ur_dc_enable", "Enable demand charge", "0/1", "0=disable,1=enable", "Electricity Rates", "?=0", "BOOLEAN", "SIMULATION_PARAMETER" }, // TOU demand charge - { SSC_INPUT, SSC_MATRIX, "ur_dc_sched_weekday", "Demand charge weekday schedule", "Periods defined in ur_dc_tou_mat", "12x24", "Electricity Rates", "", "", "" }, - { SSC_INPUT, SSC_MATRIX, "ur_dc_sched_weekend", "Demand charge weekend schedule", "Periods defined in ur_dc_tou_mat", "12x24", "Electricity Rates", "", "", "" }, + { SSC_INPUT, SSC_MATRIX, "ur_dc_sched_weekday", "Demand charge weekday schedule", "Periods defined in ur_dc_tou_mat", "12x24", "Electricity Rates", "", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_MATRIX, "ur_dc_sched_weekend", "Demand charge weekend schedule", "Periods defined in ur_dc_tou_mat", "12x24", "Electricity Rates", "", "", "SIMULATION_PARAMETER" }, // ur_dc_tou_mat has 4 columns period, tier, peak demand (kW), demand charge // replaces 12(P)*6(T)*(peak+charge) = 144 single inputs - { SSC_INPUT, SSC_MATRIX, "ur_dc_tou_mat", "Demand rates (TOU) table", "col 0=period no, col 1=tier no, col 2=tier peak (kW), col 3=charge ($/kW)", "nx4", "Electricity Rates", "ur_dc_enable=1", "", "" }, + { SSC_INPUT, SSC_MATRIX, "ur_dc_tou_mat", "Demand rates (TOU) table", "col 0=period no, col 1=tier no, col 2=tier peak (kW), col 3=charge ($/kW)", + "nx4", "Electricity Rates", "ur_dc_enable=1", "", "SIMULATION_PARAMETER" }, // flat demand charge // ur_dc_tou_flat has 4 columns month, tier, peak demand (kW), demand charge // replaces 12(P)*6(T)*(peak+charge) = 144 single inputs - { SSC_INPUT, SSC_MATRIX, "ur_dc_flat_mat", "Demand rates (flat) table", "col 0=month, col 1=tier no, col 2=tier peak (kW), col 3=charge ($/kW)", "nx4", "Electricity Rates", "ur_dc_enable=1", "", "" }, + { SSC_INPUT, SSC_MATRIX, "ur_dc_flat_mat", "Demand rates (flat) table", "col 0=month, col 1=tier no, col 2=tier peak (kW), col 3=charge ($/kW)", + "nx4", "Electricity Rates", "ur_dc_enable=1", "", "SIMULATION_PARAMETER" }, // Ratcheting demand charges - { SSC_INPUT, SSC_NUMBER, "ur_enable_billing_demand", "Enable billing demand ratchets", "0/1", "0=disable,1=enable", "Electricity Rates", "?=0", "INTEGER,MIN=0,MAX=1", "" }, - { SSC_INPUT, SSC_NUMBER, "ur_billing_demand_minimum", "Minimum billing demand", "kW", "", "Electricity Rates", "ur_enable_billing_demand=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "ur_billing_demand_lookback_period", "Billing demand lookback period", "mn", "", "Electricity Rates", "ur_enable_billing_demand=1", "INTEGER,MIN=0,MAX=12", "" }, - { SSC_INPUT, SSC_MATRIX, "ur_billing_demand_lookback_percentages", "Billing demand lookback percentages by month and consider actual peak demand", "%", "12x2", "Electricity Rates", "ur_enable_billing_demand=1", "", "" }, - { SSC_INPUT, SSC_MATRIX, "ur_dc_billing_demand_periods", "Billing demand applicability to a given demand charge time of use period", "", "", "Electricity Rates", "ur_enable_billing_demand=1", "", "" }, - { SSC_INPUT, SSC_ARRAY, "ur_yearzero_usage_peaks", "Peak usage by month for year zero", "kW", "12", "Electricity Rates", "ur_enable_billing_demand=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "ur_enable_billing_demand", "Enable billing demand ratchets", "0/1", "0=disable,1=enable", "Electricity Rates", "?=0", "INTEGER,MIN=0,MAX=1", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "ur_billing_demand_minimum", "Minimum billing demand", "kW", "", "Electricity Rates", "ur_enable_billing_demand=1", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "ur_billing_demand_lookback_period", "Billing demand lookback period", "mn", "", "Electricity Rates", "ur_enable_billing_demand=1", "INTEGER,MIN=0,MAX=12", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_MATRIX, "ur_billing_demand_lookback_percentages", "Billing demand lookback percentages by month and consider actual peak demand", "%", "12x2", "Electricity Rates", "ur_enable_billing_demand=1", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_MATRIX, "ur_dc_billing_demand_periods", "Billing demand applicability to a given demand charge time of use period", "", "", "Electricity Rates", "ur_enable_billing_demand=1", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "ur_yearzero_usage_peaks", "Peak usage by month for year zero", "kW", "12", "Electricity Rates", "ur_enable_billing_demand=1", "", "SIMULATION_PARAMETER" }, var_info_invalid From 8dbba57dad5a4069fee6f598b7b640c256fc68cf Mon Sep 17 00:00:00 2001 From: tyneises Date: Thu, 4 Apr 2024 16:20:30 -0500 Subject: [PATCH 17/85] make inflation rate in utility rate common an optional input --- ssc/common.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssc/common.cpp b/ssc/common.cpp index b21979926..ce44e21ed 100644 --- a/ssc/common.cpp +++ b/ssc/common.cpp @@ -1007,7 +1007,7 @@ var_info vtab_utility_rate_common[] = { { SSC_INPUT, SSC_NUMBER, "en_electricity_rates", "Optionally enable/disable electricity_rate", "years", "", "Electricity Rates", "", "INTEGER,MIN=0,MAX=1", "SIMULATION_PARAMETER" }, // Required for battery to use retail rates // 24.04.04 twn added to access setup() - { SSC_INPUT, SSC_NUMBER, "inflation_rate", "Inflation rate", "%", "", "Lifetime", "*", "MIN=-99", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "inflation_rate", "Inflation rate", "%", "", "Lifetime", "", "MIN=-99", "SIMULATION_PARAMETER" }, // ----------------------------- From 817ee73186a04d219f9d58bb752d38688a62464d Mon Sep 17 00:00:00 2001 From: tyneises Date: Wed, 22 May 2024 10:31:26 -0500 Subject: [PATCH 18/85] clean up trough iph cmod --- ssc/cmod_trough_physical_iph.cpp | 81 +++++++++++--------------------- 1 file changed, 28 insertions(+), 53 deletions(-) diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index f48b9d904..fabde209c 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -71,7 +71,6 @@ static var_info _cm_vtab_trough_physical_iph[] = { // Weather Reader { SSC_INPUT, SSC_STRING, "file_name", "Local weather file with path", "none", "", "weather", "?", "LOCAL_FILE", "" }, { SSC_INPUT, SSC_TABLE, "solar_resource_data", "Weather resource data in memory", "", "", "weather", "?", "", "SIMULATION_PARAMETER" }, - //{ SSC_INPUT, SSC_NUMBER, "track_mode", "Tracking mode", "none", "", "weather", "*", "", "" }, // Solar Field, Trough @@ -83,8 +82,6 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "FieldConfig", "Number of subfield headers", "none", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "eta_pump", "HTF pump efficiency", "none", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "Fluid", "Field HTF fluid ID number", "none", "", "solar_field", "*", "", "" }, - //{ SSC_INPUT, SSC_NUMBER, "fthrok", "Flag to allow partial defocusing of the collectors", "W/SCA", "", "solar_field", "*", "INTEGER", "" }, - //{ SSC_INPUT, SSC_NUMBER, "fthrctrl", "Defocusing strategy", "none", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "accept_loc", "In acceptance testing mode - temperature sensor location", "1/2", "hx/loop", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "HDR_rough", "Header pipe roughness", "m", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "theta_stow", "Stow angle", "deg", "", "solar_field", "*", "", "" }, @@ -181,21 +178,10 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "cold_tank_Thtr", "Minimum allowable cold tank HTF temp", "C", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "cold_tank_max_heat", "Rated heater capacity for cold tank heating", "MWe", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "dt_hot", "Hot side HX approach temp", "C", "", "TES", "*", "", "" }, - //{ SSC_INPUT, SSC_NUMBER, "dt_cold", "Cold side HX approach temp", "C", "", "TES", "*", "", "" }, - //{ SSC_INPUT, SSC_NUMBER, "T_tank_hot_ini", "Initial hot tank fluid tmeperature", "C", "", "TES", "*", "", "" }, - //{ SSC_INPUT, SSC_NUMBER, "T_tank_cold_ini", "Initial cold tank fluid tmeperature", "C", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "init_hot_htf_percent", "Initial fraction of avail. vol that is hot", "%", "", "TES", "*", "", "" }, - // TOU - { SSC_INPUT, SSC_MATRIX, "weekday_schedule", "12x24 CSP operation Time-of-Use Weekday schedule", "-", "", "tou", "*", "", "" }, - { SSC_INPUT, SSC_MATRIX, "weekend_schedule", "12x24 CSP operation Time-of-Use Weekend schedule", "-", "", "tou", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "is_tod_pc_target_also_pc_max", "Is the TOD target cycle heat input also the max cycle heat input?", "", "", "tou", "?=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "can_cycle_use_standby", "Can the cycle use standby operation?", "", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "q_rec_standby", "Receiver standby energy consumption", "kWt", "", "tou", "?=9e99", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWe-hr", "", "tou", "?=0.0", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "f_turb_tou_periods", "Dispatch logic for turbine load fraction", "-", "", "tou", "*", "", "" }, - + // Dispatch optimization { SSC_INPUT, SSC_NUMBER, "disp_rsu_cost_rel", "Receiver startup cost", "$/MWt/start", "", "Sys_Control", "is_dispatch=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "disp_horizon", "Time horizon for dispatch optimization", "hour", "", "Sys_Control", "is_dispatch=1", "", "" }, @@ -215,23 +201,35 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "disp_reporting", "Dispatch optimization reporting level", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "disp_spec_scaling", "Dispatch optimization scaling heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "disp_inventory_incentive", "Dispatch storage terminal inventory incentive multiplier", "", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER" }, - + + { SSC_INPUT, SSC_NUMBER, "q_rec_standby", "Receiver standby energy consumption - for dispatch", "kWt", "", "tou", "?=9e99", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup - for dispatch", "kWe-hr", "", "tou", "?=0.0", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "csp_financial_model", "", "1-8", "", "Financial Model", "?=1", "INTEGER,MIN=0", "" }, - { SSC_INPUT, SSC_NUMBER, "ppa_multiplier_model", "PPA multiplier model 0: dispatch factors dispatch_factorX, 1: hourly multipliers dispatch_factors_ts", "0/1", "0=diurnal,1=timestep", "tou", "?=0", /*need a default so this var works in required_if*/ "INTEGER,MIN=0", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "dispatch_factors_ts", "Dispatch payment factor array", "", "", "tou", "ppa_multiplier_model=1&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "ppa_soln_mode", "PPA solution mode (0=Specify IRR target, 1=Specify PPA price)", "", "", "Financial Solution Mode","ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekday", "12x24 PPA pricing Weekday schedule", "", "", "tou", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekend", "12x24 PPA pricing Weekend schedule", "", "", "tou", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "dispatch_tod_factors", "TOD factors for periods 1 through 9", "", - "We added this array input after SAM 2022.12.21 to replace the functionality of former single value inputs dispatch_factor1 through dispatch_factor9", "Time of Delivery Factors","ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, - - { SSC_INPUT, SSC_NUMBER, "is_dispatch_series", "Use time-series dispatch factors", "", "", "tou", "?=1", "", "" }, - { SSC_INPUT, SSC_ARRAY, "dispatch_series", "Time series dispatch factors", "", "", "tou", "", "", "" }, - { SSC_INPUT, SSC_NUMBER, "is_timestep_load_fractions","Use turbine load fraction for each timestep instead of block dispatch?", "", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "?", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_MATRIX, "mp_energy_market_revenue", "Energy market revenue input", "", "Lifetime x 2[Cleared Capacity(MW),Price($/MWh)]", "Revenue", "csp_financial_model=6&is_dispatch=1", "", "SIMULATION_PARAMETER" }, + + // Prices for *electricity* purchases + { SSC_INPUT, SSC_NUMBER, "ppa_multiplier_model", "PPA multiplier model 0: dispatch factors dispatch_factorX, 1: hourly multipliers dispatch_factors_ts", "0/1", "0=diurnal,1=timestep", "tou", "?=0", /*need a default so this var works in required_if*/ "INTEGER,MIN=0", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "ppa_soln_mode", "PPA solution mode (0=Specify IRR target, 1=Specify PPA price)", "", "", "Financial Solution Mode", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, + // *Electricity* hourly price multipliers from Block Schedule + { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekday", "12x24 PPA pricing Weekday schedule", "", "", "tou", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekend", "12x24 PPA pricing Weekend schedule", "", "", "tou", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "dispatch_tod_factors", "TOD factors for periods 1 through 9", "", + "We added this array input after SAM 2022.12.21 to replace the functionality of former single value inputs dispatch_factor1 through dispatch_factor9", "Time of Delivery Factors","ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, + // *Electricity* hourly price multipliers from time series input + { SSC_INPUT, SSC_ARRAY, "dispatch_factors_ts", "Time series electricity price multipliers", "", "", "tou", "ppa_multiplier_model=1&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, + + // Control for *heat* output + { SSC_INPUT, SSC_NUMBER, "is_timestep_load_fractions","Use turbine load fraction for each timestep instead of block dispatch?", "", "", "tou", "?=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Heat sink load fraction for each timestep, alternative to block dispatch", "", "", "tou", "is_timestep_load_fractions=1", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_MATRIX, "weekday_schedule", "12x24 CSP operation Time-of-Use Weekday schedule", "", "", "tou", "is_timestep_load_fractions=0", "", "" }, + { SSC_INPUT, SSC_MATRIX, "weekend_schedule", "12x24 CSP operation Time-of-Use Weekend schedule", "", "", "tou", "is_timestep_load_fractions=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "f_turb_tou_periods", "Time series heat sink load fractions", "", "", "tou", "is_timestep_load_fractions=0", "", "" }, + // + { SSC_INPUT, SSC_NUMBER, "is_tod_pc_target_also_pc_max", "Is the TOD target cycle heat input also the max cycle heat input?", "", "", "tou", "?=0", "", "" }, + + // System { SSC_INPUT, SSC_NUMBER, "pb_fixed_par", "Fraction of rated gross power constantly consumed", "MWe/MWcap", "", "system", "*", "", "" }, @@ -239,8 +237,6 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_ARRAY, "aux_array", "Auxiliary heater, mult frac and const, linear and quad coeff", "", "", "system", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "water_usage_per_wash", "Water usage per wash", "L/m2_aper", "", "system", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "washing_frequency", "Mirror washing frequency", "-/year", "", "system", "*", "", "" }, - //{ SSC_INPUT, SSC_NUMBER, "system_capacity", "Nameplate capacity", "kW", "", "system", "*", "", "" }, - //{ SSC_INPUT, SSC_NUMBER, "disp_frequency", "Frequency for dispatch optimization calculations", "hour", "", "Sys_Control", "is_dispatch=1", "", "" }, // Newly added { SSC_INPUT, SSC_NUMBER, "calc_design_pipe_vals", "Calculate temps and pressures at design conditions for runners and headers", "none", "", "solar_field", "*", "", "" }, @@ -257,10 +253,6 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "northsouth_field_sep", "North/south separation between subfields. 0 = SCAs are touching", "m", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "N_hdr_per_xpan", "Number of collector loops per expansion loop", "none", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "offset_xpan_hdr", "Location of first header expansion loop. 1 = after first collector loop", "none", "", "solar_field", "*", "", "" }, - //{ SSC_INPUT, SSC_MATRIX, "K_cpnt", "Interconnect component minor loss coefficients, row=intc, col=cpnt", "none", "", "solar_field", "*", "", "" }, - //{ SSC_INPUT, SSC_MATRIX, "D_cpnt", "Interconnect component diameters, row=intc, col=cpnt", "none", "", "solar_field", "*", "", "" }, - //{ SSC_INPUT, SSC_MATRIX, "L_cpnt", "Interconnect component lengths, row=intc, col=cpnt", "none", "", "solar_field", "*", "", "" }, - //{ SSC_INPUT, SSC_MATRIX, "Type_cpnt", "Interconnect component type, row=intc, col=cpnt", "none", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "custom_sf_pipe_sizes", "Use custom solar field pipe diams, wallthks, and lengths", "none", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_MATRIX, "sf_rnr_diams", "Custom runner diameters", "m", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_MATRIX, "sf_rnr_wallthicks", "Custom runner wall thicknesses", "m", "", "solar_field", "*", "", "" }, @@ -524,19 +516,6 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "pipe_runner_P_dsn", "Field piping runner pressure at design", "bar", "", "solar_field", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "pipe_loop_T_dsn", "Field piping loop temperature at design", "C", "", "solar_field", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "pipe_loop_P_dsn", "Field piping loop pressure at design", "bar", "", "solar_field", "sim_type=1", "", "" }, - - //// Power Block - //{ SSC_OUTPUT, SSC_ARRAY, "eta", "PC efficiency: gross", "", "", "powerblock", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "q_pb", "PC input energy", "MWt", "", "powerblock", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "m_dot_pc", "PC HTF mass flow rate", "kg/s", "", "powerblock", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "q_dot_pc_startup", "PC startup thermal power", "MWt", "", "powerblock", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "P_cycle", "PC electrical power output: gross", "MWe", "", "powerblock", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "T_pc_in", "PC HTF inlet temperature", "C", "", "powerblock", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "T_pc_out", "PC HTF outlet temperature", "C", "", "powerblock", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "m_dot_water_pc", "PC water consumption: makeup + cooling", "kg/s", "", "powerblock", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "q_pc_startup", "PC startup thermal energy", "MWht", "", "powerblock", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "cycle_htf_pump_power", "PC HTF pump power", "MWe", "", "powerblock", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "P_cooling_tower_tot", "Parasitic power condenser operation", "MWe", "", "powerblock", "sim_type=1", "", "" }, // Heat Sink { SSC_OUTPUT, SSC_ARRAY, "q_dot_to_heat_sink", "Heat sink thermal power", "MWt", "", "Heat_Sink", "sim_type=1", "", "" }, @@ -565,10 +544,6 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "m_dot_cold_tank_to_hot_tank", "Mass flow: cold tank to hot tank", "kg/s", "", "TES", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "tes_htf_pump_power", "TES HTF pump power", "MWe", "", "TES", "sim_type=1", "", "" }, - - //{ SSC_OUTPUT, SSC_ARRAY, "m_dot_tes_dc", "TES discharge mass flow rate", "kg/s", "", "TES", "*", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "m_dot_tes_ch", "TES charge mass flow rate", "kg/s", "", "TES", "*", "", "" }, - // SYSTEM { SSC_OUTPUT, SSC_ARRAY, "W_dot_parasitic_tot", "System total electrical parasitic", "MWe", "", "system", "sim_type=1", "", "" }, From 76c967fb9a9adde8c8d360b00666c5784131f57e Mon Sep 17 00:00:00 2001 From: tyneises Date: Thu, 30 May 2024 15:08:32 -0500 Subject: [PATCH 19/85] fix commercial finance model option in cmod --- ssc/cmod_trough_physical_iph.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 5b69b13d3..8552ab3aa 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -1162,18 +1162,7 @@ class cm_trough_physical_iph : public compute_module size_t count_ppa_price_input; ssc_number_t* ppa_price_input_array = as_array("ppa_price_input", &count_ppa_price_input); ppa_price_year1 = (double)ppa_price_input_array[0]; // [$/kWh] - } - else if (csp_financial_model == 5) { // Commercial - - bool is_ur_assigned = is_assigned("ur_en_ts_sell_rate"); - - // rate data setup from ~ line 1336 in cmod_battery.cpp - rate_data* util_rate_data = new rate_data(); - rate_setup::setup(m_vartab, 8760, 1, *util_rate_data, "cmod_trough_physical_iph"); - - // Need to figure out dispatch, but for now, just use something so that annual simulation solves - elec_pricing_schedule = C_timeseries_schedule_inputs(-1.0); - } + } else { ppa_price_year1 = 1.0; //[-] don't need ppa multiplier if not optimizing } @@ -1217,6 +1206,17 @@ class cm_trough_physical_iph : public compute_module } } + else if (csp_financial_model == 5) { // Commercial + + bool is_ur_assigned = is_assigned("ur_en_ts_sell_rate"); + + // rate data setup from ~ line 1336 in cmod_battery.cpp + rate_data* util_rate_data = new rate_data(); + rate_setup::setup(m_vartab, 8760, 1, *util_rate_data, "cmod_trough_physical_iph"); + + // Need to figure out dispatch, but for now, just use something so that annual simulation solves + elec_pricing_schedule = C_timeseries_schedule_inputs(-1.0); + } else { throw exec_error("trough_physical_iph", "csp_financial_model must be 1, 7, or 8"); } From 14fa59142f674db5fba74f7da497b5b6458774f5 Mon Sep 17 00:00:00 2001 From: tyneises Date: Fri, 31 May 2024 09:50:51 -0500 Subject: [PATCH 20/85] fix issue from merging develop --- ssc/cmod_trough_physical_iph.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 75f627552..ff0dabc2f 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -1216,7 +1216,7 @@ class cm_trough_physical_iph : public compute_module rate_setup::setup(m_vartab, 8760, 1, *util_rate_data, "cmod_trough_physical_iph"); // Need to figure out dispatch, but for now, just use something so that annual simulation solves - elec_pricing_schedule = C_timeseries_schedule_inputs(-1.0); + elec_pricing_schedule = C_timeseries_schedule_inputs(-1.0, std::numeric_limits::quiet_NaN()); } else { throw exec_error("trough_physical_iph", "csp_financial_model must be 1, 7, or 8"); From 43c93c249934b7f1e47b101b84cde822e762a9c2 Mon Sep 17 00:00:00 2001 From: tyneises Date: Fri, 31 May 2024 12:58:51 -0500 Subject: [PATCH 21/85] add electricity pricing inputs for trough iph lcoh model calculate a single electricity rate from annual electricity purchases and consumption that can be used downstream in lcoh model --- ssc/cmod_trough_physical_iph.cpp | 45 ++++++++++++++++++++++++++++++-- tcs/csp_solver_core.cpp | 7 +++-- tcs/csp_solver_core.h | 1 + 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index ff0dabc2f..bcb7e0bb9 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -570,6 +570,7 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "n_op_modes", "Operating modes in reporting timestep", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "tou_value", "CSP operating Time-of-use value", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "pricing_mult", "PPA price multiplier", "", "", "solver", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "elec_price_out", "Electricity price at timestep", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "q_dot_pc_sb", "Thermal power for PC standby", "MWt", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "q_dot_pc_min", "Thermal power for PC min operation", "MWt", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "q_dot_pc_target", "Target thermal power to PC", "MWt", "", "solver", "sim_type=1", "", "" }, @@ -628,6 +629,8 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_P_dsn", "Pressure in TES pipes at design conditions", "bar", "", "TES", "sim_type=1", "", "" }, //{ SSC_OUTPUT, SSC_ARRAY, "defocus", "Field optical focus fraction", "", "", "solver", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "electricity_rate", "Electricity price = calculated annual elec cost / total elec energy consusmed", "$/kWe-hr", "", "Post-process", "sim_type=1&csp_financial_model=7", "", "" }, + var_info_invalid }; @@ -1140,7 +1143,7 @@ class cm_trough_physical_iph : public compute_module if (sim_type == 1) { - if (csp_financial_model == 8 || csp_financial_model == 7) { // No Financial Model or LCOH; Single Owner in progress 9/2023 + if (csp_financial_model == 8) { // No Financial Model if (is_dispatch) { throw exec_error("trough_physical_iph", "Can't select dispatch optimization if No Financial model"); } @@ -1149,6 +1152,25 @@ class cm_trough_physical_iph : public compute_module elec_pricing_schedule = C_timeseries_schedule_inputs(-1.0, std::numeric_limits::quiet_NaN()); } } + else if (csp_financial_model == 7) { // LCOH + + size_t count_ppa_price_input; + ssc_number_t* ppa_price_input_array = as_array("ppa_price_input", &count_ppa_price_input); + double ppa_price_year1 = (double)ppa_price_input_array[0]; // [$/kWh] + + // Time-of-Delivery factors by time step: + int ppa_mult_model = as_integer("ppa_multiplier_model"); + if (ppa_mult_model == 1) // use dispatch_ts input + { + auto vec = as_vector_double("dispatch_factors_ts"); + elec_pricing_schedule = C_timeseries_schedule_inputs(vec, ppa_price_year1); + } + else if (ppa_mult_model == 0) // standard diuranal input + { + elec_pricing_schedule = C_timeseries_schedule_inputs(as_matrix("dispatch_sched_weekday"), + as_matrix("dispatch_sched_weekend"), as_vector_double("dispatch_tod_factors"), ppa_price_year1); + } + } else if (csp_financial_model == 1) { // Single Owner double ppa_price_year1 = std::numeric_limits::quiet_NaN(); @@ -1337,6 +1359,7 @@ class cm_trough_physical_iph : public compute_module csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::N_OP_MODES, allocate("n_op_modes", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::TOU_PERIOD, allocate("tou_value", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::PRICING_MULT, allocate("pricing_mult", n_steps_fixed), n_steps_fixed); + csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::ELEC_PRICE, allocate("elec_price_out", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::PC_Q_DOT_SB, allocate("q_dot_pc_sb", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::PC_Q_DOT_MIN, allocate("q_dot_pc_min", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::PC_Q_DOT_TARGET, allocate("q_dot_pc_target", n_steps_fixed), n_steps_fixed); @@ -1817,6 +1840,10 @@ class cm_trough_physical_iph : public compute_module if ((int)count != n_steps_fixed) throw exec_error("trough_physical", "The number of fixed steps does not match the length of output data arrays3"); + ssc_number_t* p_elec_price_out = as_array("elec_price_out", &count); + if((int)count != n_steps_fixed) + throw exec_error("trough_physical", "The number of fixed steps does not match the length of output data arrays4"); + //ssc_number_t *p_m_dot_tes_dc = as_array("m_dot_tes_dc", &count); //if ((int)count != n_steps_fixed) // throw exec_error("trough_physical", "The number of fixed steps for 'm_dot_tes_dc' does not match the length of output data arrays"); @@ -1824,6 +1851,11 @@ class cm_trough_physical_iph : public compute_module //ssc_number_t *p_m_dot_tes_ch = as_array("m_dot_tes_ch", &count); //if ((int)count != n_steps_fixed) // throw exec_error("trough_physical", "The number of fixed steps for 'm_dot_tes_ch' does not match the length of output data arrays"); + + ssc_number_t* p_elec_purchase_cost = allocate("elec_purchase_cost", n_steps_fixed); + + double annual_elec_cost = 0.0; //[$] + double i_elec_cost = std::numeric_limits::quiet_NaN(); for(int i = 0; i < n_steps_fixed; i++) { size_t hour = (size_t)ceil(p_time_final_hr[i]); @@ -1833,8 +1865,13 @@ class cm_trough_physical_iph : public compute_module p_q_dot_defocus_est[i] = (ssc_number_t)(1.0 - p_SCAs_def[i])*p_q_dot_htf_sf_out[i]; //[MWt] //p_m_dot_tes_dc[i] = (ssc_number_t)(p_m_dot_tes_dc[i] / 3600.0); //[kg/s] convert from kg/hr //p_m_dot_tes_ch[i] = (ssc_number_t)(p_m_dot_tes_ch[i] / 3600.0); //[kg/s] convert from kg/hr - + + i_elec_cost = p_elec_price_out[i] * p_W_dot_par_tot_haf[i]; //[$] + p_elec_purchase_cost[i] = i_elec_cost; //[$] + annual_elec_cost += i_elec_cost; //[$] } + + ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour); // Non-timeseries array outputs double P_adj = storage.P_in_des; // slightly adjust all field design pressures to account for pressure drop in TES before hot tank @@ -1914,6 +1951,10 @@ class cm_trough_physical_iph : public compute_module // This term currently includes TES freeze protection accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWe-hr] + double annual_electricity_consumption = as_double("annual_electricity_consumption"); //[kWe-hr] + double electricity_rate_calc = annual_elec_cost / annual_electricity_consumption; //[$/kWe-hr] + assign("electricity_rate", electricity_rate_calc); + ssc_number_t annual_field_fp = accumulate_annual_for_year("q_dot_freeze_prot", "annual_field_freeze_protection", sim_setup.m_report_step / 3600.0*1.E3, steps_per_hour); //[kWt-hr] ssc_number_t annual_tes_fp = accumulate_annual_for_year("q_tes_heater", "annual_tes_freeze_protection", sim_setup.m_report_step / 3600.0*1.E3, steps_per_hour); //[kWt-hr] diff --git a/tcs/csp_solver_core.cpp b/tcs/csp_solver_core.cpp index 54373f04f..861a0a0d1 100644 --- a/tcs/csp_solver_core.cpp +++ b/tcs/csp_solver_core.cpp @@ -185,7 +185,8 @@ static C_csp_reported_outputs::S_output_info S_solver_output_info[] = {C_csp_solver::C_solver_outputs::OP_MODE_2, C_csp_reported_outputs::TS_1ST}, //[-] Operating mode in second subtimestep {C_csp_solver::C_solver_outputs::OP_MODE_3, C_csp_reported_outputs::TS_1ST}, //[-] Operating mode in third subtimestep {C_csp_solver::C_solver_outputs::TOU_PERIOD, C_csp_reported_outputs::TS_1ST}, //[-] CSP operating TOU period - {C_csp_solver::C_solver_outputs::PRICING_MULT, C_csp_reported_outputs::TS_1ST}, //[-] PPA price multiplier + {C_csp_solver::C_solver_outputs::PRICING_MULT, C_csp_reported_outputs::TS_1ST}, //[-] PPA price multiplier + {C_csp_solver::C_solver_outputs::ELEC_PRICE, C_csp_reported_outputs::TS_1ST}, //[-] Electricity price in absolute units {C_csp_solver::C_solver_outputs::PC_Q_DOT_SB, C_csp_reported_outputs::TS_1ST}, //[MWt] PC required standby thermal power {C_csp_solver::C_solver_outputs::PC_Q_DOT_MIN, C_csp_reported_outputs::TS_1ST}, //[MWt] PC required min thermal power {C_csp_solver::C_solver_outputs::PC_Q_DOT_TARGET, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] PC target thermal power @@ -614,6 +615,7 @@ void C_csp_solver::Ssimulate(C_csp_solver::S_sim_setup & sim_setup) mc_kernel.mc_sim_info.m_tou = f_turb_tou_period; //[base 1] used ONLY by power cycle model for hybrid cooling - may also want to move this to controller double f_turbine_tou = mc_tou_outputs.m_f_turbine; //[-] double pricing_mult = mc_tou_outputs.m_price_mult; //[-] + double elec_price = mc_tou_outputs.m_elec_price; //[$/kWh-e] double purchase_mult = pricing_mult; //if (!mc_tou.mc_dispatch_params.m_is_purchase_mult_same_as_price) { // throw(C_csp_exception("CSP Solver not yet setup to handle purchase schedule separate from price schedule")); @@ -1075,7 +1077,8 @@ void C_csp_solver::Ssimulate(C_csp_solver::S_sim_setup & sim_setup) mc_reported_outputs.value(C_solver_outputs::TOU_PERIOD, (double)f_turb_tou_period); //[-] mc_reported_outputs.value(C_solver_outputs::PRICING_MULT, pricing_mult); //[-] - mc_reported_outputs.value(C_solver_outputs::PC_Q_DOT_SB, q_pc_sb); //[MW] + mc_reported_outputs.value(C_solver_outputs::ELEC_PRICE, elec_price); //[$/kWh-e] + mc_reported_outputs.value(C_solver_outputs::PC_Q_DOT_SB, q_pc_sb); //[MW] mc_reported_outputs.value(C_solver_outputs::PC_Q_DOT_MIN, q_pc_min); //[MW] mc_reported_outputs.value(C_solver_outputs::PC_Q_DOT_TARGET, q_pc_target); //[MW] mc_reported_outputs.value(C_solver_outputs::PC_Q_DOT_MAX, m_q_dot_pc_max); //[MW] diff --git a/tcs/csp_solver_core.h b/tcs/csp_solver_core.h index d483ade0c..6243ca3a7 100644 --- a/tcs/csp_solver_core.h +++ b/tcs/csp_solver_core.h @@ -844,6 +844,7 @@ class C_csp_solver // ************************************************************** TOU_PERIOD, //[-] CSP operating TOU period PRICING_MULT, //[-] PPA price multiplier + ELEC_PRICE, //[$/kWh-e] Electricity price in absolute units PC_Q_DOT_SB, //[MWt] PC required standby thermal power PC_Q_DOT_MIN, //[MWt] PC required min thermal power PC_Q_DOT_TARGET, //[MWt] PC target thermal power From 4c8c7ec1fd9132fb92f3bb4860fe32ca2701ae34 Mon Sep 17 00:00:00 2001 From: tyneises Date: Fri, 31 May 2024 13:13:20 -0500 Subject: [PATCH 22/85] add heat pricing schedule --- ssc/cmod_trough_physical_iph.cpp | 7 ++++++- tcs/csp_solver_core.h | 11 +++++++++-- tcs/csp_solver_tou_block_schedules.cpp | 3 +++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index bcb7e0bb9..82cb497cc 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -1261,7 +1261,12 @@ class cm_trough_physical_iph : public compute_module bool is_offtaker_frac_also_max = as_boolean("is_tod_pc_target_also_pc_max"); C_csp_tou tou(offtaker_schedule, elec_pricing_schedule, dispatch_model_type, is_offtaker_frac_also_max); - + + // Placeholder for heat price schedule + double heat_price = 0.02; //[$/kWh-t] + C_timeseries_schedule_inputs heat_pricing_schedule = C_timeseries_schedule_inputs(1.0, heat_price); + tou.mc_heat_pricing_schedule = heat_pricing_schedule; + // System parameters C_csp_solver::S_csp_system_params system; { diff --git a/tcs/csp_solver_core.h b/tcs/csp_solver_core.h index 6243ca3a7..534f318a8 100644 --- a/tcs/csp_solver_core.h +++ b/tcs/csp_solver_core.h @@ -309,13 +309,18 @@ class C_csp_tou double m_price_mult; //[-] double m_elec_price; //[$/kWhe] + int m_heat_tou; + double m_heat_mult; //[-] + double m_heat_price; //[$/kWh-t] + double m_wlim_dispatch; //[-] S_csp_tou_outputs() { - m_csp_op_tou = m_pricing_tou = -1; + m_csp_op_tou = m_pricing_tou = m_heat_tou = -1; - m_f_turbine = m_price_mult = m_elec_price = m_wlim_dispatch = std::numeric_limits::quiet_NaN(); + m_f_turbine = m_price_mult = m_elec_price = + m_heat_mult = m_heat_price = m_wlim_dispatch = std::numeric_limits::quiet_NaN(); } }; @@ -337,6 +342,8 @@ class C_csp_tou C_timeseries_schedule_inputs mc_offtaker_schedule; C_timeseries_schedule_inputs mc_elec_pricing_schedule; + C_timeseries_schedule_inputs mc_heat_pricing_schedule; + C_csp_tou(C_timeseries_schedule_inputs c_offtaker_schedule, C_timeseries_schedule_inputs c_elec_pricing_schedule, C_csp_tou::C_dispatch_model_type::E_dispatch_model_type dispatch_model_type, diff --git a/tcs/csp_solver_tou_block_schedules.cpp b/tcs/csp_solver_tou_block_schedules.cpp index e697fdcdc..4d250543c 100644 --- a/tcs/csp_solver_tou_block_schedules.cpp +++ b/tcs/csp_solver_tou_block_schedules.cpp @@ -158,6 +158,9 @@ void C_csp_tou::call(double time_s, C_csp_tou::S_csp_tou_outputs& tou_outputs) mc_elec_pricing_schedule.get_timestep_data(time_s, tou_outputs.m_price_mult, tou_outputs.m_elec_price, tou_outputs.m_pricing_tou); + mc_heat_pricing_schedule.get_timestep_data(time_s, tou_outputs.m_heat_mult, tou_outputs.m_heat_price, + tou_outputs.m_heat_tou); + if (m_is_tod_pc_target_also_pc_max) { tou_outputs.m_wlim_dispatch = tou_outputs.m_f_turbine; } From 26e8f5abc7cc48736e32b4047cf847dfad6b93ac Mon Sep 17 00:00:00 2001 From: tyneises Date: Fri, 31 May 2024 14:33:46 -0500 Subject: [PATCH 23/85] fix heat pricing schedule --- tcs/csp_solver_core.h | 3 +++ test/main.cpp | 2 ++ 2 files changed, 5 insertions(+) diff --git a/tcs/csp_solver_core.h b/tcs/csp_solver_core.h index 534f318a8..46662e14b 100644 --- a/tcs/csp_solver_core.h +++ b/tcs/csp_solver_core.h @@ -354,6 +354,9 @@ class C_csp_tou m_dispatch_model_type = dispatch_model_type; m_is_tod_pc_target_also_pc_max = is_offtaker_frac_also_max; + mc_heat_pricing_schedule = C_timeseries_schedule_inputs(std::numeric_limits::quiet_NaN(), + std::numeric_limits::quiet_NaN()); + // Set defaults on heuristic rule values. No one at the cmod level knows what to do with these m_use_rule_1 = true; m_standby_off_buffer = 2.0; diff --git a/test/main.cpp b/test/main.cpp index eafe1a724..01f9f51ca 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -56,6 +56,8 @@ GTEST_API_ int main(int argc, char **argv) { // ::testing::GTEST_FLAG(filter) = "CmodPVWatts*:CMPvwatts*"; //::testing::GTEST_FLAG(filter) = "CmodHybridTest*"; + ::testing::GTEST_FLAG(filter) = "csp_tower.PowerTowerCmod.Default_NoFinancial"; + // ::testing::GTEST_FLAG(filter) = "CmodCashLoanTest*:CmodSingleOwnerTest*"; //::testing::GTEST_FLAG(filter) = "CMGeothermal*:GeothermalPlantAnalyzer*"; From c56e43fc155e73b21776c12f1a707fa811cc7b70 Mon Sep 17 00:00:00 2001 From: qualand <34353104+qualand@users.noreply.github.com> Date: Fri, 31 May 2024 15:53:17 -0600 Subject: [PATCH 24/85] cleaning up dispatch update_horizon_parameters() --- ssc/cmod_trough_physical_iph.cpp | 10 ++++----- tcs/csp_dispatch.cpp | 36 +++++++++++--------------------- tcs/etes_dispatch.cpp | 16 +++++++------- 3 files changed, 25 insertions(+), 37 deletions(-) diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 82cb497cc..ef509807e 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -1274,11 +1274,11 @@ class cm_trough_physical_iph : public compute_module size_t nval_bop_array = 0; ssc_number_t* bop_array = as_array("bop_array", &nval_bop_array); if (nval_bop_array != 5) throw exec_error("trough_physical", "Should be 5 elements in bop_array, has " + util::to_string((int)nval_bop_array) + "."); - system.m_bop_par = bop_array[0]; //as_double("bop_par"); - system.m_bop_par_f = bop_array[1]; //as_double("bop_par_f"); - system.m_bop_par_0 = bop_array[2]; //as_double("bop_par_0"); - system.m_bop_par_1 = bop_array[3]; //as_double("bop_par_1"); - system.m_bop_par_2 = bop_array[4]; //as_double("bop_par_2"); + system.m_bop_par = bop_array[0]; + system.m_bop_par_f = bop_array[1]; + system.m_bop_par_0 = bop_array[2]; + system.m_bop_par_1 = bop_array[3]; + system.m_bop_par_2 = bop_array[4]; } diff --git a/tcs/csp_dispatch.cpp b/tcs/csp_dispatch.cpp index 3e52c6269..cec00f3ab 100644 --- a/tcs/csp_dispatch.cpp +++ b/tcs/csp_dispatch.cpp @@ -174,33 +174,21 @@ bool csp_dispatch_opt::check_setup(int nstep) bool csp_dispatch_opt::update_horizon_parameters(C_csp_tou& mc_tou) { - //get the new price signal + //get price signal and electricity generation limits + int num_steps = solver_params.optimize_horizon * solver_params.steps_per_hour; params.sell_price.clear(); - params.sell_price.resize(solver_params.optimize_horizon * solver_params.steps_per_hour, 1.); - - for (int t = 0; t < solver_params.optimize_horizon * solver_params.steps_per_hour; t++) - { - C_csp_tou::S_csp_tou_outputs mc_tou_outputs; - - mc_tou.call(pointers.siminfo->ms_ts.m_time + t * 3600. / (double)solver_params.steps_per_hour, mc_tou_outputs); - params.sell_price.at(t) = mc_tou_outputs.m_elec_price * 1000.0; // $/kWhe -> $/Mhe //.m_price_mult * params.ppa_price_y1; - } - - // get the new electricity generation limits + params.sell_price.resize(num_steps, 1.); params.w_lim.clear(); - params.w_lim.resize((int)solver_params.optimize_horizon * (int)solver_params.steps_per_hour, 1.e99); - int hour_start = (int)(ceil(pointers.siminfo->ms_ts.m_time / 3600. - 1.e-6)) - 1; - for (int t = 0; t < solver_params.optimize_horizon * solver_params.steps_per_hour; t++) - { - for (int d = 0; d < solver_params.steps_per_hour; d++) { - double W_dot_max = params.q_pb_max * params.eta_pb_des; //[kWe] - C_csp_tou::S_csp_tou_outputs tou_outputs; - mc_tou.call((hour_start + t + 1)*3600.0, tou_outputs); - params.w_lim.at(t * solver_params.steps_per_hour + d) = tou_outputs.m_wlim_dispatch * W_dot_max; - //params.w_lim.at(t * solver_params.steps_per_hour + d) = mc_tou.mc_dispatch_params.m_w_lim_full.at(hour_start + t); - } + params.w_lim.resize(num_steps, 1.e99); + + double sec_per_step = 3600. / (double)solver_params.steps_per_hour; + double W_dot_max = params.q_pb_max * params.eta_pb_des; //[kWe] + for (int t = 0; t < num_steps; t++) { + C_csp_tou::S_csp_tou_outputs tou_outputs; + mc_tou.call(pointers.siminfo->ms_ts.m_time + t * sec_per_step, tou_outputs); + params.sell_price.at(t) = tou_outputs.m_elec_price * 1000.0; // $/kWhe -> $/Mhe + params.w_lim.at(t) = tou_outputs.m_wlim_dispatch * W_dot_max; } - return true; } diff --git a/tcs/etes_dispatch.cpp b/tcs/etes_dispatch.cpp index 99b721eea..117b96972 100644 --- a/tcs/etes_dispatch.cpp +++ b/tcs/etes_dispatch.cpp @@ -114,17 +114,17 @@ bool etes_dispatch_opt::check_setup(int nstep) bool etes_dispatch_opt::update_horizon_parameters(C_csp_tou& mc_tou) { //get the new price signal + int num_steps = solver_params.optimize_horizon * solver_params.steps_per_hour; params.sell_price.clear(); - params.sell_price.resize(solver_params.optimize_horizon * solver_params.steps_per_hour, 1.); + params.sell_price.resize(num_steps, 1.); params.buy_price.clear(); - params.buy_price.resize(solver_params.optimize_horizon * solver_params.steps_per_hour, 1.); + params.buy_price.resize(num_steps, 1.); - for (int t = 0; t < solver_params.optimize_horizon * solver_params.steps_per_hour; t++) - { - C_csp_tou::S_csp_tou_outputs mc_tou_outputs; - - mc_tou.call(pointers.siminfo->ms_ts.m_time + t * 3600. / (double)solver_params.steps_per_hour, mc_tou_outputs); - params.sell_price.at(t) = mc_tou_outputs.m_elec_price * 1000.0; // $/kWhe -> $/Mhe mc_tou_outputs.m_price_mult * params.ppa_price_y1; + double sec_per_step = 3600. / (double)solver_params.steps_per_hour; + for (int t = 0; t < num_steps; t++) { + C_csp_tou::S_csp_tou_outputs tou_outputs; + mc_tou.call(pointers.siminfo->ms_ts.m_time + t * sec_per_step, tou_outputs); + params.sell_price.at(t) = tou_outputs.m_elec_price * 1000.0; // $/kWhe -> $/MWhe params.buy_price.at(t) = params.sell_price.at(t); //TODO: make these unique if specified by user } return true; From e5d9db6b128bc6b5b0c13cd13fbe5053395bde75 Mon Sep 17 00:00:00 2001 From: qualand <34353104+qualand@users.noreply.github.com> Date: Tue, 4 Jun 2024 08:42:35 -0600 Subject: [PATCH 25/85] removed old TOU member setting --- ssc/cmod_etes_electric_resistance.cpp | 4 ---- ssc/cmod_etes_ptes.cpp | 4 ---- ssc/cmod_fresnel_physical.cpp | 13 +++++-------- ssc/cmod_fresnel_physical_iph.cpp | 13 +++++-------- ssc/cmod_mspt_iph.cpp | 2 -- ssc/cmod_tcsmolten_salt.cpp | 10 ---------- ssc/cmod_trough_physical.cpp | 6 ------ ssc/cmod_trough_physical_iph.cpp | 3 +-- 8 files changed, 11 insertions(+), 44 deletions(-) diff --git a/ssc/cmod_etes_electric_resistance.cpp b/ssc/cmod_etes_electric_resistance.cpp index 3b206a802..41c73fad8 100644 --- a/ssc/cmod_etes_electric_resistance.cpp +++ b/ssc/cmod_etes_electric_resistance.cpp @@ -709,10 +709,6 @@ class cm_etes_electric_resistance : public compute_module bool is_offtaker_frac_also_max = true; C_csp_tou tou(offtaker_schedule, elec_pricing_schedule, dispatch_model_type, is_offtaker_frac_also_max); - - //tou.mc_dispatch_params.m_is_tod_pc_target_also_pc_max = true; - //tou.mc_dispatch_params.m_is_block_dispatch = false; - //tou.mc_dispatch_params.m_is_arbitrage_policy = !as_boolean("is_dispatch"); // ***************************************************** // ***************************************************** diff --git a/ssc/cmod_etes_ptes.cpp b/ssc/cmod_etes_ptes.cpp index 9414d4c36..8960e0a3d 100644 --- a/ssc/cmod_etes_ptes.cpp +++ b/ssc/cmod_etes_ptes.cpp @@ -807,10 +807,6 @@ class cm_etes_ptes : public compute_module bool is_offtaker_frac_also_max = true; C_csp_tou tou(offtaker_schedule, elec_pricing_schedule, dispatch_model_type, is_offtaker_frac_also_max); - - //tou.mc_dispatch_params.m_is_tod_pc_target_also_pc_max = true; - //tou.mc_dispatch_params.m_is_block_dispatch = false; - //tou.mc_dispatch_params.m_is_arbitrage_policy = !as_boolean("is_dispatch"); // ***************************************************** // ***************************************************** diff --git a/ssc/cmod_fresnel_physical.cpp b/ssc/cmod_fresnel_physical.cpp index bcefb1999..8b0a2f1cb 100644 --- a/ssc/cmod_fresnel_physical.cpp +++ b/ssc/cmod_fresnel_physical.cpp @@ -1187,9 +1187,6 @@ class cm_fresnel_physical : public compute_module C_csp_tou tou(offtaker_schedule, elec_pricing_schedule, dispatch_model_type, is_offtaker_frac_also_max); - //tou.mc_dispatch_params.m_is_tod_pc_target_also_pc_max = as_boolean("is_tod_pc_target_also_pc_max"); - //tou.mc_dispatch_params.m_is_block_dispatch = !is_dispatch; - // System Parameters C_csp_solver::S_csp_system_params system; { @@ -1197,11 +1194,11 @@ class cm_fresnel_physical : public compute_module size_t nval_bop_array = 0; ssc_number_t* bop_array = as_array("bop_array", &nval_bop_array); if (nval_bop_array != 5) throw exec_error("fresnel_physical", "Should be 5 elements in bop_array, has " + util::to_string((int)nval_bop_array) + "."); - system.m_bop_par = bop_array[0]; //as_double("bop_par"); - system.m_bop_par_f = bop_array[1]; //as_double("bop_par_f"); - system.m_bop_par_0 = bop_array[2]; //as_double("bop_par_0"); - system.m_bop_par_1 = bop_array[3]; //as_double("bop_par_1"); - system.m_bop_par_2 = bop_array[4]; //as_double("bop_par_2"); + system.m_bop_par = bop_array[0]; + system.m_bop_par_f = bop_array[1]; + system.m_bop_par_0 = bop_array[2]; + system.m_bop_par_1 = bop_array[3]; + system.m_bop_par_2 = bop_array[4]; } // System Dispatch diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 600e1e04f..630a56b2a 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -966,9 +966,6 @@ class cm_fresnel_physical_iph : public compute_module // TOU C_csp_tou tou(offtaker_schedule, elec_pricing_schedule, dispatch_model_type, is_offtaker_frac_also_max); - //tou.mc_dispatch_params.m_is_tod_pc_target_also_pc_max = as_boolean("is_tod_pc_target_also_pc_max"); - //tou.mc_dispatch_params.m_is_block_dispatch = !is_dispatch; //mw - // System Parameters C_csp_solver::S_csp_system_params system; { @@ -976,11 +973,11 @@ class cm_fresnel_physical_iph : public compute_module size_t nval_bop_array = 0; ssc_number_t* bop_array = as_array("bop_array", &nval_bop_array); if (nval_bop_array != 5) throw exec_error("fresnel_physical", "Should be 5 elements in bop_array, has " + util::to_string((int)nval_bop_array) + "."); - system.m_bop_par = bop_array[0]; //as_double("bop_par"); - system.m_bop_par_f = bop_array[1]; //as_double("bop_par_f"); - system.m_bop_par_0 = bop_array[2]; //as_double("bop_par_0"); - system.m_bop_par_1 = bop_array[3]; //as_double("bop_par_1"); - system.m_bop_par_2 = bop_array[4]; //as_double("bop_par_2"); + system.m_bop_par = bop_array[0]; + system.m_bop_par_f = bop_array[1]; + system.m_bop_par_0 = bop_array[2]; + system.m_bop_par_1 = bop_array[3]; + system.m_bop_par_2 = bop_array[4]; } // System Dispatch diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index fd087176d..ce9b2bc07 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -1682,8 +1682,6 @@ class cm_mspt_iph : public compute_module // TOU parameters C_csp_tou tou(offtaker_schedule, elec_pricing_schedule, dispatch_model_type, is_offtaker_frac_also_max); - //tou.mc_dispatch_params.m_is_tod_pc_target_also_pc_max = as_boolean("is_tod_pc_target_also_pc_max"); - //tou.mc_dispatch_params.m_is_block_dispatch = !as_boolean("is_dispatch"); //mw // ***************************************************** // diff --git a/ssc/cmod_tcsmolten_salt.cpp b/ssc/cmod_tcsmolten_salt.cpp index 7dc7aadee..7aaee9c8e 100644 --- a/ssc/cmod_tcsmolten_salt.cpp +++ b/ssc/cmod_tcsmolten_salt.cpp @@ -2198,16 +2198,6 @@ class cm_tcsmolten_salt : public compute_module C_csp_tou tou(offtaker_schedule, elec_pricing_schedule, dispatch_model_type, is_offtaker_frac_also_max); - //tou.mc_dispatch_params.m_is_tod_pc_target_also_pc_max = as_boolean("is_tod_pc_target_also_pc_max"); - //tou.mc_dispatch_params.m_is_block_dispatch = !(as_boolean("is_dispatch") || as_boolean("is_dispatch_targets")); - //tou.mc_dispatch_params.m_use_rule_1 = true; - //tou.mc_dispatch_params.m_standby_off_buffer = 2.0; - //tou.mc_dispatch_params.m_use_rule_2 = false; - //tou.mc_dispatch_params.m_q_dot_rec_des_mult = -1.23; - //tou.mc_dispatch_params.m_f_q_dot_pc_overwrite = -1.23; - - - //tou.mc_dispatch_params.m_is_dispatch_targets = is_dispatch_targets; if (is_dispatch_targets) { int n_expect = (int)ceil((sim_setup.m_sim_time_end - sim_setup.m_sim_time_start) / 3600. * steps_per_hour); diff --git a/ssc/cmod_trough_physical.cpp b/ssc/cmod_trough_physical.cpp index 2a44655c4..6fa46bbd9 100644 --- a/ssc/cmod_trough_physical.cpp +++ b/ssc/cmod_trough_physical.cpp @@ -1510,10 +1510,6 @@ class cm_trough_physical : public compute_module C_csp_tou tou(offtaker_schedule, elec_pricing_schedule, dispatch_model_type, is_offtaker_frac_also_max); { - //tou.mc_dispatch_params.m_is_tod_pc_target_also_pc_max = as_boolean("is_tod_pc_target_also_pc_max"); - //tou.mc_dispatch_params.m_is_block_dispatch = !(as_boolean("is_dispatch") || as_boolean("is_dispatch_targets")); - - //tou.mc_dispatch_params.m_is_dispatch_targets = is_dispatch_targets; if (is_dispatch_targets) { int n_expect = (int)ceil((sim_setup.m_sim_time_end - sim_setup.m_sim_time_start) / 3600. * steps_per_hour); @@ -1560,8 +1556,6 @@ class cm_trough_physical : public compute_module } } - - } diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index ef509807e..f7e7ddf80 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -645,7 +645,6 @@ class cm_trough_physical_iph : public compute_module add_var_info( _cm_vtab_trough_physical_iph ); add_var_info( vtab_adjustment_factors ); add_var_info(vtab_technology_outputs); - add_var_info(vtab_utility_rate_common); // Required for dispatch w/ utility rates } @@ -1304,7 +1303,7 @@ class cm_trough_physical_iph : public compute_module double disp_rsu_cost_calc = as_double("disp_rsu_cost_rel") * q_dot_rec_des; //[$/start] dispatch.params.set_user_params(can_cycle_use_standby, as_double("disp_time_weighting"), disp_rsu_cost_calc, heater_startup_cost, disp_csu_cost_calc, disp_pen_ramping, - as_double("disp_inventory_incentive"), as_double("q_rec_standby"), as_double("q_rec_heattrace")); // , ppa_price_year1); + as_double("disp_inventory_incentive"), as_double("q_rec_standby"), as_double("q_rec_heattrace")); } else { dispatch.solver_params.dispatch_optimize = false; From 8a6e605b4a494506799df3d55f04ec8d9ddb0515 Mon Sep 17 00:00:00 2001 From: qualand <34353104+qualand@users.noreply.github.com> Date: Tue, 4 Jun 2024 12:16:41 -0600 Subject: [PATCH 26/85] cleaning up dispatch inputs - removed is_dispatch boolean input - removed dispatch_optimize from solver_params - removed AMPL options for all cmods expect salt tower and trough - moved AMPL inputs to a separate setter function --- ssc/cmod_etes_electric_resistance.cpp | 8 +--- ssc/cmod_etes_ptes.cpp | 8 +--- ssc/cmod_fresnel_physical.cpp | 12 +----- ssc/cmod_fresnel_physical_iph.cpp | 12 +----- ssc/cmod_linear_fresnel_dsg_iph.cpp | 1 - ssc/cmod_mspt_iph.cpp | 14 ++----- ssc/cmod_tcsmolten_salt.cpp | 8 ++-- ssc/cmod_trough_physical.cpp | 9 ++--- ssc/cmod_trough_physical_iph.cpp | 37 +++++++----------- tcs/base_dispatch.h | 1 + tcs/csp_dispatch.cpp | 15 -------- tcs/csp_dispatch.h | 1 + tcs/csp_solver_core.cpp | 4 +- tcs/dispatch_builder.cpp | 47 +++++++++++------------ tcs/dispatch_builder.h | 9 ++--- tcs/etes_dispatch.cpp | 34 ---------------- tcs/etes_dispatch.h | 4 -- test/main.cpp | 2 +- test/shared_test/csp_solver_core_test.cpp | 1 - 19 files changed, 60 insertions(+), 167 deletions(-) diff --git a/ssc/cmod_etes_electric_resistance.cpp b/ssc/cmod_etes_electric_resistance.cpp index 41c73fad8..ead6c78ba 100644 --- a/ssc/cmod_etes_electric_resistance.cpp +++ b/ssc/cmod_etes_electric_resistance.cpp @@ -733,16 +733,12 @@ class cm_etes_electric_resistance : public compute_module etes_dispatch_opt dispatch; if (as_boolean("is_dispatch")) { - dispatch.solver_params.set_user_inputs(as_boolean("is_dispatch"), as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), + dispatch.solver_params.set_user_inputs(as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), as_integer("disp_max_iter"), as_double("disp_mip_gap"), as_double("disp_timeout"), - as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting"), - false, false, "", ""); + as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting")); dispatch.params.set_user_params(as_double("disp_time_weighting"), as_double("disp_csu_cost")*W_dot_cycle_des, as_double("disp_pen_delta_w"), as_double("disp_hsu_cost") * q_dot_heater_des, as_double("disp_down_time_min"), as_double("disp_up_time_min")); // , ppa_price_year1); } - else { - dispatch.solver_params.dispatch_optimize = false; - } // ***************************************************** // ***************************************************** diff --git a/ssc/cmod_etes_ptes.cpp b/ssc/cmod_etes_ptes.cpp index 8960e0a3d..cf611d7dc 100644 --- a/ssc/cmod_etes_ptes.cpp +++ b/ssc/cmod_etes_ptes.cpp @@ -831,16 +831,12 @@ class cm_etes_ptes : public compute_module etes_dispatch_opt dispatch; if (as_boolean("is_dispatch")) { - dispatch.solver_params.set_user_inputs(as_boolean("is_dispatch"), as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), + dispatch.solver_params.set_user_inputs(as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), as_integer("disp_max_iter"), as_double("disp_mip_gap"), as_double("disp_timeout"), - as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting"), - false, false, "", ""); + as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting")); dispatch.params.set_user_params(as_double("disp_time_weighting"), as_double("disp_csu_cost")*W_dot_gen_thermo, as_double("disp_pen_delta_w"), as_double("disp_hsu_cost") * q_dot_hot_out_charge, as_double("disp_down_time_min"), as_double("disp_up_time_min")); // , ppa_price_year1); } - else { - dispatch.solver_params.dispatch_optimize = false; - } // ***************************************************** // ***************************************************** diff --git a/ssc/cmod_fresnel_physical.cpp b/ssc/cmod_fresnel_physical.cpp index 8b0a2f1cb..895b18d1f 100644 --- a/ssc/cmod_fresnel_physical.cpp +++ b/ssc/cmod_fresnel_physical.cpp @@ -216,10 +216,6 @@ static var_info _cm_vtab_fresnel_physical[] = { /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "disp_csu_cost_rel", "Cycle startup cost", "$/MWe-cycle/start", "", "Sys_Control", "is_dispatch=1", "", "" }, /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "disp_pen_ramping", "Dispatch cycle production change penalty", "$/MWe-change", "", "Sys_Control", "is_dispatch=1", "", "" }, - /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "is_write_ampl_dat", "Write AMPL data files for dispatch run", "-", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, - /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "is_ampl_engine", "Run dispatch optimization with external AMPL engine", "-", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, - /*LK Only*/{ SSC_INPUT, SSC_STRING, "ampl_data_dir", "AMPL data file directory", "-", "", "tou", "?=''", "", "SIMULATION_PARAMETER" }, - /*LK Only*/{ SSC_INPUT, SSC_STRING, "ampl_exec_call", "System command to run AMPL code", "-", "", "tou", "?='ampl sdk_solution.run'", "", "SIMULATION_PARAMETER" }, /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "can_cycle_use_standby", "Can the cycle use standby operation?", "", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "disp_steps_per_hour", "Time steps per hour for dispatch optimization calculations", "-", "", "tou", "?=1", "", "SIMULATION_PARAMETER" }, /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "disp_spec_presolve", "Dispatch optimization presolve heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, @@ -1214,10 +1210,9 @@ class cm_fresnel_physical : public compute_module double q_dot_cycle_des = W_dot_cycle_des / eta_cycle; //[MWt] double q_dot_rec_des = q_dot_cycle_des * c_fresnel.m_solar_mult; //[MWt] - dispatch.solver_params.set_user_inputs(is_dispatch, as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), + dispatch.solver_params.set_user_inputs(as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), as_integer("disp_max_iter"), as_double("disp_mip_gap"), as_double("disp_timeout"), - as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting"), - as_boolean("is_write_ampl_dat"), as_boolean("is_ampl_engine"), as_string("ampl_data_dir"), as_string("ampl_exec_call")); + as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting")); double disp_csu_cost_calc = as_double("disp_csu_cost_rel") * W_dot_cycle_des; //[$/start] double disp_rsu_cost_calc = as_double("disp_rsu_cost_rel") * q_dot_rec_des; //[$/start] @@ -1225,9 +1220,6 @@ class cm_fresnel_physical : public compute_module disp_rsu_cost_calc, 0.0, disp_csu_cost_calc, as_double("disp_pen_ramping"), as_double("disp_inventory_incentive"), as_double("q_rec_standby"), as_double("q_rec_heattrace")); // , ppa_price_year1); } - else { - dispatch.solver_params.dispatch_optimize = false; - } } diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 630a56b2a..678b67512 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -189,10 +189,6 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "disp_mip_gap", "Dispatch optimization solution tolerance", "-", "", "Sys_Control", "is_dispatch=1", "", "" }, /*Sys Control*/{ SSC_INPUT, SSC_NUMBER, "disp_time_weighting", "Dispatch optimization future time discounting factor", "-", "", "Sys_Control", "?=0.99", "", "" }, - /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "is_write_ampl_dat", "Write AMPL data files for dispatch run", "-", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, - /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "is_ampl_engine", "Run dispatch optimization with external AMPL engine", "-", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, - /*LK Only*/{ SSC_INPUT, SSC_STRING, "ampl_data_dir", "AMPL data file directory", "-", "", "tou", "?=''", "", "SIMULATION_PARAMETER" }, - /*LK Only*/{ SSC_INPUT, SSC_STRING, "ampl_exec_call", "System command to run AMPL code", "-", "", "tou", "?='ampl sdk_solution.run'", "", "SIMULATION_PARAMETER" }, /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "disp_steps_per_hour", "Time steps per hour for dispatch optimization calculations", "-", "", "tou", "?=1", "", "SIMULATION_PARAMETER" }, /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "disp_spec_presolve", "Dispatch optimization presolve heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "disp_spec_bb", "Dispatch optimization B&B heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, @@ -989,10 +985,9 @@ class cm_fresnel_physical_iph : public compute_module double q_dot_rec_des = q_dot_pc_des * c_fresnel.m_solar_mult; //[MWt] - dispatch.solver_params.set_user_inputs(is_dispatch, as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), + dispatch.solver_params.set_user_inputs(as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), as_integer("disp_max_iter"), as_double("disp_mip_gap"), as_double("disp_timeout"), - as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting"), - as_boolean("is_write_ampl_dat"), as_boolean("is_ampl_engine"), as_string("ampl_data_dir"), as_string("ampl_exec_call")); + as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting")); bool can_cycle_use_standby = false; double disp_csu_cost_calc = 0.0; @@ -1003,9 +998,6 @@ class cm_fresnel_physical_iph : public compute_module disp_rsu_cost_calc, heater_startup_cost, disp_csu_cost_calc, disp_pen_ramping, as_double("disp_inventory_incentive"), as_double("q_rec_standby"), as_double("q_rec_heattrace")); // , ppa_price_year1); } - else { - dispatch.solver_params.dispatch_optimize = false; - } // Instantiate Solver C_csp_solver csp_solver(weather_reader, diff --git a/ssc/cmod_linear_fresnel_dsg_iph.cpp b/ssc/cmod_linear_fresnel_dsg_iph.cpp index 24aec2c77..ea81d53a0 100644 --- a/ssc/cmod_linear_fresnel_dsg_iph.cpp +++ b/ssc/cmod_linear_fresnel_dsg_iph.cpp @@ -482,7 +482,6 @@ class cm_linear_fresnel_dsg_iph : public compute_module // ***************************************************** // System dispatch csp_dispatch_opt dispatch; - dispatch.solver_params.dispatch_optimize = false; // ******************************** // ******************************** diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index ce9b2bc07..9864d5bd8 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -264,10 +264,6 @@ static var_info _cm_vtab_mspt_iph[] = { { SSC_INPUT, SSC_NUMBER, "disp_spec_bb", "Dispatch optimization B&B heuristic", "", "", "System Control", "?=-1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "disp_spec_scaling", "Dispatch optimization scaling heuristic", "", "", "System Control", "?=-1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "disp_reporting", "Dispatch optimization reporting level", "", "", "System Control", "?=-1", "", "SIMULATION_PARAMETER" }, -{ SSC_INPUT, SSC_NUMBER, "is_write_ampl_dat", "Write AMPL data files for dispatch run", "", "", "System Control", "?=0", "", "SIMULATION_PARAMETER" }, -{ SSC_INPUT, SSC_NUMBER, "is_ampl_engine", "Run dispatch optimization with external AMPL engine", "", "", "System Control", "?=0", "", "SIMULATION_PARAMETER" }, -{ SSC_INPUT, SSC_STRING, "ampl_data_dir", "AMPL data file directory", "", "", "System Control", "?=''", "", "SIMULATION_PARAMETER" }, -{ SSC_INPUT, SSC_STRING, "ampl_exec_call", "System command to run AMPL code", "", "", "System Control", "?='ampl sdk_solution.run'", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "disp_inventory_incentive", "Dispatch storage terminal inventory incentive multiplier", "", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER" }, @@ -1702,10 +1698,9 @@ class cm_mspt_iph : public compute_module double heater_startup_cost = 0.0; - dispatch.solver_params.set_user_inputs(is_dispatch, as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), + dispatch.solver_params.set_user_inputs(as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), as_integer("disp_max_iter"), as_double("disp_mip_gap"), as_double("disp_timeout"), - as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting"), - as_boolean("is_write_ampl_dat"), as_boolean("is_ampl_engine"), as_string("ampl_data_dir"), as_string("ampl_exec_call")); + as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting")); bool can_cycle_use_standby = false; double disp_csu_cost_calc = 0.0; @@ -1714,10 +1709,7 @@ class cm_mspt_iph : public compute_module double disp_rsu_cost_calc = as_double("disp_rsu_cost_rel") * q_dot_rec_des; //[$/start] dispatch.params.set_user_params(can_cycle_use_standby, as_double("disp_time_weighting"), disp_rsu_cost_calc, heater_startup_cost, disp_csu_cost_calc, disp_pen_ramping, - as_double("disp_inventory_incentive"), as_double("q_rec_standby"), as_double("q_rec_heattrace")); // , ppa_price_year1); - } - else { - dispatch.solver_params.dispatch_optimize = false; + as_double("disp_inventory_incentive"), as_double("q_rec_standby"), as_double("q_rec_heattrace")); } // Instantiate Solver diff --git a/ssc/cmod_tcsmolten_salt.cpp b/ssc/cmod_tcsmolten_salt.cpp index 7aaee9c8e..1a391d1fe 100644 --- a/ssc/cmod_tcsmolten_salt.cpp +++ b/ssc/cmod_tcsmolten_salt.cpp @@ -2271,8 +2271,6 @@ class cm_tcsmolten_salt : public compute_module // System dispatch csp_dispatch_opt dispatch; - dispatch.solver_params.dispatch_optimize = is_dispatch; - if (is_dispatch && sim_type == 1){ double heater_startup_cost = 0.0; @@ -2282,10 +2280,10 @@ class cm_tcsmolten_salt : public compute_module heater_startup_cost = as_double("disp_hsu_cost_rel") * q_dot_heater_des; //[$/start] } - dispatch.solver_params.set_user_inputs(is_dispatch, as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), + dispatch.solver_params.set_user_inputs(as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), as_integer("disp_max_iter"), as_double("disp_mip_gap"), as_double("disp_timeout"), - as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting"), - as_boolean("is_write_ampl_dat"), as_boolean("is_ampl_engine"), as_string("ampl_data_dir"), as_string("ampl_exec_call")); + as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting")); + dispatch.solver_params.set_ampl_inputs(as_boolean("is_write_ampl_dat"), as_boolean("is_ampl_engine"), as_string("ampl_data_dir"), as_string("ampl_exec_call")); double disp_csu_cost_calc = as_double("disp_csu_cost_rel")*W_dot_cycle_des; //[$/start] double disp_rsu_cost_calc = as_double("disp_rsu_cost_rel")*q_dot_rec_des; //[$/start] diff --git a/ssc/cmod_trough_physical.cpp b/ssc/cmod_trough_physical.cpp index 6fa46bbd9..2b859174a 100644 --- a/ssc/cmod_trough_physical.cpp +++ b/ssc/cmod_trough_physical.cpp @@ -1582,10 +1582,10 @@ class cm_trough_physical : public compute_module double q_dot_rec_des = q_dot_cycle_des*c_trough.m_solar_mult; //[MWt] - dispatch.solver_params.set_user_inputs(as_boolean("is_dispatch"), as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), + dispatch.solver_params.set_user_inputs(as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), as_integer("disp_max_iter"), as_double("disp_mip_gap"), as_double("disp_timeout"), - as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting"), - as_boolean("is_write_ampl_dat"), as_boolean("is_ampl_engine"), as_string("ampl_data_dir"), as_string("ampl_exec_call")); + as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting")); + dispatch.solver_params.set_ampl_inputs(as_boolean("is_write_ampl_dat"), as_boolean("is_ampl_engine"), as_string("ampl_data_dir"), as_string("ampl_exec_call")); double disp_csu_cost_calc = as_double("disp_csu_cost_rel") * W_dot_cycle_des; //[$/start] double disp_rsu_cost_calc = as_double("disp_rsu_cost_rel") * q_dot_rec_des; //[$/start] @@ -1593,9 +1593,6 @@ class cm_trough_physical : public compute_module disp_rsu_cost_calc, 0.0, disp_csu_cost_calc, as_double("disp_pen_ramping"), as_double("disp_inventory_incentive"), as_double("q_rec_standby"), as_double("q_rec_heattrace")); // , ppa_price_year1); } - else { - dispatch.solver_params.dispatch_optimize = false; - } } diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index f7e7ddf80..b30a01431 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -187,10 +187,6 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "disp_mip_gap", "Dispatch optimization solution tolerance", "-", "", "Sys_Control", "is_dispatch=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "disp_time_weighting", "Dispatch optimization future time discounting factor", "-", "", "Sys_Control", "?=0.99", "", "" }, - { SSC_INPUT, SSC_NUMBER, "is_write_ampl_dat", "Write AMPL data files for dispatch run", "-", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "is_ampl_engine", "Run dispatch optimization with external AMPL engine", "-", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_STRING, "ampl_data_dir", "AMPL data file directory", "-", "", "tou", "?=''", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_STRING, "ampl_exec_call", "System command to run AMPL code", "-", "", "tou", "?='ampl sdk_solution.run'", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "disp_steps_per_hour", "Time steps per hour for dispatch optimization calculations", "-", "", "tou", "?=1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "disp_spec_presolve", "Dispatch optimization presolve heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "disp_spec_bb", "Dispatch optimization B&B heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, @@ -661,8 +657,8 @@ class cm_trough_physical_iph : public compute_module double T_htf_cold_des = as_double("T_loop_in_des"); //[C] double T_htf_hot_des = as_double("T_loop_out"); //[C] double tshours = as_double("tshours"); //[-] - double q_dot_pc_des = as_double("q_pb_design"); //[MWt] HEAT SINK design thermal power - double Q_tes = q_dot_pc_des * tshours; //[MWt-hr] + double q_dot_hs_des = as_double("q_pb_design"); //[MWt] HEAT SINK design thermal power + double Q_tes = q_dot_hs_des * tshours; //[MWt-hr] int is_dispatch = as_boolean("is_dispatch"); // ***************************************************** @@ -784,7 +780,7 @@ class cm_trough_physical_iph : public compute_module c_trough.m_mc_bal_cold_per_MW = as_double("mc_bal_cold"); //[kWht/K-MWt] The heat capacity of the balance of plant on the cold side c_trough.m_mc_bal_sca = as_double("mc_bal_sca"); //[Wht/K-m] Non-HTF heat capacity associated with each SCA - per meter basis - c_trough.m_P_ref = q_dot_pc_des * 1e6; //[W] Design Turbine Net Output + c_trough.m_P_ref = q_dot_hs_des * 1e6; //[W] Design Turbine Net Output c_trough.m_eta_ref = 1; //[] Design cycle thermal efficiency c_trough.m_non_solar_field_land_area_multiplier = as_double("non_solar_field_land_area_multiplier"); //[] @@ -1016,7 +1012,7 @@ class cm_trough_physical_iph : public compute_module c_heat_sink.ms_params.m_T_htf_hot_des = T_htf_hot_des; //[C] FIELD design outlet temperature c_heat_sink.ms_params.m_T_htf_cold_des = T_htf_cold_des; //[C] FIELD design inlet temperature - c_heat_sink.ms_params.m_q_dot_des = q_dot_pc_des; //[MWt] HEAT SINK design thermal power (could have field solar multiple...) + c_heat_sink.ms_params.m_q_dot_des = q_dot_hs_des; //[MWt] HEAT SINK design thermal power (could have field solar multiple...) // 9.18.2016 twn: assume for now there's no pressure drop though heat sink c_heat_sink.ms_params.m_htf_pump_coef = as_double("pb_pump_coef"); //[kWe/kg/s] c_heat_sink.ms_params.m_max_frac = f_turbine_max1; @@ -1070,7 +1066,7 @@ class cm_trough_physical_iph : public compute_module c_trough.m_field_fl_props, as_integer("store_fluid"), as_matrix("store_fl_props"), - q_dot_pc_des, + q_dot_hs_des, c_trough.m_solar_mult, Q_tes, as_double("h_tank"), @@ -1287,14 +1283,12 @@ class cm_trough_physical_iph : public compute_module if (is_dispatch) { - double heater_startup_cost = 0.0; + double heater_startup_cost = 0.0; // TODO: Should we add an heater to this model? + double q_dot_rec_des = q_dot_hs_des * c_trough.m_solar_mult; //[MWt] - double q_dot_rec_des = q_dot_pc_des * c_trough.m_solar_mult; //[MWt] - - dispatch.solver_params.set_user_inputs(is_dispatch, as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), + dispatch.solver_params.set_user_inputs(as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), as_integer("disp_max_iter"), as_double("disp_mip_gap"), as_double("disp_timeout"), - as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting"), - as_boolean("is_write_ampl_dat"), as_boolean("is_ampl_engine"), as_string("ampl_data_dir"), as_string("ampl_exec_call")); + as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting")); bool can_cycle_use_standby = false; double disp_csu_cost_calc = 0.0; @@ -1305,9 +1299,6 @@ class cm_trough_physical_iph : public compute_module disp_rsu_cost_calc, heater_startup_cost, disp_csu_cost_calc, disp_pen_ramping, as_double("disp_inventory_incentive"), as_double("q_rec_standby"), as_double("q_rec_heattrace")); } - else { - dispatch.solver_params.dispatch_optimize = false; - } // Instantiate Solver C_csp_solver csp_solver(weather_reader, @@ -1439,7 +1430,7 @@ class cm_trough_physical_iph : public compute_module double nameplate = std::numeric_limits::quiet_NaN(); { // System Design - nameplate = q_dot_pc_des; + nameplate = q_dot_hs_des; { assign("nameplate", nameplate); // [MWt] assign("system_capacity", nameplate * 1.E3); //[kWt] @@ -1630,9 +1621,9 @@ class cm_trough_physical_iph : public compute_module //csp_solver.get_design_parameters(W_dot_bop_design, W_dot_fixed_parasitic_design); vector bop_vec = as_vector_double("bop_array"); - double bop_design = bop_vec[0] * bop_vec[1] * (bop_vec[2] + bop_vec[3] + bop_vec[4]) * q_dot_pc_des; + double bop_design = bop_vec[0] * bop_vec[1] * (bop_vec[2] + bop_vec[3] + bop_vec[4]) * q_dot_hs_des; vector aux_vec = as_vector_double("aux_array"); - double aux_design = aux_vec[0] * aux_vec[1] * (aux_vec[2] + aux_vec[3] + aux_vec[4]) * q_dot_pc_des; + double aux_design = aux_vec[0] * aux_vec[1] * (aux_vec[2] + aux_vec[3] + aux_vec[4]) * q_dot_hs_des; assign("bop_design", bop_design); // MWe assign("aux_design", aux_design); // MWe @@ -1668,8 +1659,8 @@ class cm_trough_physical_iph : public compute_module double solar_field_area = c_trough.m_Ap_tot; double htf_system_area = c_trough.m_Ap_tot; //Q_tes - double heat_sink_mwe = q_dot_pc_des; // MWe - double bop_mwe = q_dot_pc_des; // MWe + double heat_sink_mwe = q_dot_hs_des; // MWe + double bop_mwe = q_dot_hs_des; // MWe // total_land_area // m2 double sales_tax_rate = as_double("sales_tax_rate"); diff --git a/tcs/base_dispatch.h b/tcs/base_dispatch.h index 99c684481..7bf440fde 100644 --- a/tcs/base_dispatch.h +++ b/tcs/base_dispatch.h @@ -183,6 +183,7 @@ class base_dispatch_opt //Functions to write AMPL data files and solve AMPL model virtual std::string write_ampl(); + virtual bool optimize_ampl(); //Populated dispatch outputs for csp solver core diff --git a/tcs/csp_dispatch.cpp b/tcs/csp_dispatch.cpp index cec00f3ab..c7fb09587 100644 --- a/tcs/csp_dispatch.cpp +++ b/tcs/csp_dispatch.cpp @@ -35,17 +35,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include "csp_dispatch.h" -//#include "lp_lib.h" -//#include "lib_util.h" - -// TODO: get rid of all the defines -//#define _WRITE_AMPL_DATA 1 -#define SOS_NONE // What does this do? -//#define SOS_SEQUENCE -//#define SOS_MANUAL -//#define SOS_LPSOLVE - -//#define MOD_CYCLE_SHUTDOWN /* @@ -1580,10 +1569,6 @@ bool csp_dispatch_opt::optimize() lp = NULL; print_dispatch_update(); - //TODO: why is this here? - //if(return_ok) - // write_ampl(); - return return_ok; } catch(std::exception &e) diff --git a/tcs/csp_dispatch.h b/tcs/csp_dispatch.h index e37dd22e7..59b5f2195 100644 --- a/tcs/csp_dispatch.h +++ b/tcs/csp_dispatch.h @@ -253,6 +253,7 @@ class csp_dispatch_opt : public base_dispatch_opt bool optimize(); std::string write_ampl(); + bool optimize_ampl(); // Set outputs struct based on LP solution -> could move to outputs struct diff --git a/tcs/csp_solver_core.cpp b/tcs/csp_solver_core.cpp index 861a0a0d1..96c2db5b3 100644 --- a/tcs/csp_solver_core.cpp +++ b/tcs/csp_solver_core.cpp @@ -468,9 +468,7 @@ void C_csp_solver::init() if (mc_tou.m_dispatch_model_type == C_csp_tou::C_dispatch_model_type::E_dispatch_model_type::UNDEFINED) { throw(C_csp_exception("Either heuristic, imported dispatch targets, or dispatch optimization must be specified", "CSP Solver")); } - - if (mc_dispatch.solver_params.dispatch_optimize) - { + else if (mc_tou.m_dispatch_model_type == C_csp_tou::C_dispatch_model_type::E_dispatch_model_type::DISPATCH_OPTIMIZATION) { mc_dispatch.pointers.set_pointers(mc_weather, &mc_collector_receiver, &mc_power_cycle, &mc_tes, &mc_csp_messages, &mc_kernel.mc_sim_info, mp_heater); mc_dispatch.init(m_cycle_q_dot_des, m_cycle_eta_des); } diff --git a/tcs/dispatch_builder.cpp b/tcs/dispatch_builder.cpp index 694186f2b..6b7c12f0e 100644 --- a/tcs/dispatch_builder.cpp +++ b/tcs/dispatch_builder.cpp @@ -89,7 +89,6 @@ s_solver_params::s_solver_params() obj_relaxed = std::numeric_limits::quiet_NaN(); //user settings - dispatch_optimize = false; steps_per_hour = 1; optimize_frequency = 24; optimize_horizon = 48; @@ -109,34 +108,32 @@ s_solver_params::s_solver_params() ampl_exec_call = ""; } -void s_solver_params::set_user_inputs(bool is_dispatch, int disp_steps_per_hour, int disp_frequency, int disp_horizon, +void s_solver_params::set_user_inputs(int disp_steps_per_hour, int disp_frequency, int disp_horizon, int disp_max_iter, double disp_mip_gap, double disp_timeout, - int disp_spec_presolve, int disp_spec_bb, int disp_spec_scaling, int disp_spec_reporting, - bool is_write_ampl_dat_spec, bool is_ampl_engine_spec, std::string ampl_data_dir_spec, std::string ampl_exec_call_spec) + int disp_spec_presolve, int disp_spec_bb, int disp_spec_scaling, int disp_spec_reporting) { //user settings - dispatch_optimize = is_dispatch; + steps_per_hour = disp_steps_per_hour; + optimize_frequency = disp_frequency; + optimize_horizon = disp_horizon; + + max_bb_iter = disp_max_iter; + mip_gap = disp_mip_gap; + solution_timeout = disp_timeout; + + presolve_type = disp_spec_presolve; + bb_type = disp_spec_bb; + scaling_type = disp_spec_scaling; + disp_reporting = disp_spec_reporting; +} - if (dispatch_optimize) - { - steps_per_hour = disp_steps_per_hour; - optimize_frequency = disp_frequency; - optimize_horizon = disp_horizon; - - max_bb_iter = disp_max_iter; - mip_gap = disp_mip_gap; - solution_timeout = disp_timeout; - - presolve_type = disp_spec_presolve; - bb_type = disp_spec_bb; - scaling_type = disp_spec_scaling; - disp_reporting = disp_spec_reporting; - - is_write_ampl_dat = is_write_ampl_dat_spec; - is_ampl_engine = is_ampl_engine_spec; - ampl_data_dir = ampl_data_dir_spec; - ampl_exec_call = ampl_exec_call_spec; - } + +void s_solver_params::set_ampl_inputs(bool is_write_ampl_dat_spec, bool is_ampl_engine_spec, std::string ampl_data_dir_spec, std::string ampl_exec_call_spec) +{ + is_write_ampl_dat = is_write_ampl_dat_spec; + is_ampl_engine = is_ampl_engine_spec; + ampl_data_dir = ampl_data_dir_spec; + ampl_exec_call = ampl_exec_call_spec; } void s_solver_params::reset() diff --git a/tcs/dispatch_builder.h b/tcs/dispatch_builder.h index 64156307a..625eda8b8 100644 --- a/tcs/dispatch_builder.h +++ b/tcs/dispatch_builder.h @@ -40,8 +40,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "../lpsolve/lp_lib.h" #include -//#include "glpk\src\glpk.h" - void __WINAPI opt_logfunction(lprec* lp, void* userhandle, char* buf); int __WINAPI opt_abortfunction(lprec* lp, void* userhandle); void __WINAPI opt_iter_function(lprec* lp, void* userhandle, int msg); @@ -53,7 +51,6 @@ struct s_solver_params double obj_relaxed; //user settings - bool dispatch_optimize; //is dispatch optimize selected? int steps_per_hour; //[-] Number of time steps per hour int optimize_frequency; int optimize_horizon; @@ -72,10 +69,10 @@ struct s_solver_params std::string ampl_exec_call; //system call for running ampl s_solver_params(); - void set_user_inputs(bool is_dispatch, int disp_steps_per_hour, int disp_frequency, int disp_horizon, + void set_user_inputs(int disp_steps_per_hour, int disp_frequency, int disp_horizon, int disp_max_iter, double disp_mip_gap, double disp_timeout, - int disp_spec_presolve, int disp_spec_bb, int disp_spec_scaling, int disp_spec_reporting, - bool is_write_ampl_dat_spec, bool is_ampl_engine_spec, std::string ampl_data_dir_spec, std::string ampl_exec_call_spec); + int disp_spec_presolve, int disp_spec_bb, int disp_spec_scaling, int disp_spec_reporting); + void set_ampl_inputs(bool is_write_ampl_dat_spec, bool is_ampl_engine_spec, std::string ampl_data_dir_spec, std::string ampl_exec_call_spec); void reset(); }; diff --git a/tcs/etes_dispatch.cpp b/tcs/etes_dispatch.cpp index 117b96972..e17a0d31a 100644 --- a/tcs/etes_dispatch.cpp +++ b/tcs/etes_dispatch.cpp @@ -37,9 +37,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "lp_lib.h" #include "lib_util.h" -#define SOS_NONE -//#define ALT_ETES_FORM - #undef min #undef max @@ -1159,37 +1156,6 @@ bool etes_dispatch_opt::optimize() return false; } -// ======================================== -// Exporting the problem to AMPL -// ======================================== - -std::string etes_dispatch_opt::write_ampl() -{ - /* - Write the par file for ampl input - - return name of output file, if error, return empty string. - */ - throw std::runtime_error((std::string)__func__ + " is not implemented."); - return ""; -} - -bool etes_dispatch_opt::optimize_ampl() -{ - /* - handle the process of writing an input file, running ampl, handling results, and loading solution - - writes - dat_.dat - runs - sdk_dispatch.run - expects - sdk_solution.txt input file - */ - throw std::runtime_error((std::string)__func__ + " is not implemented."); - return false; -} - void etes_dispatch_opt::set_outputs_from_lp_solution(lprec* lp, unordered_map& params) { int nt = (int)m_nstep_opt; diff --git a/tcs/etes_dispatch.h b/tcs/etes_dispatch.h index 13b9772fd..3bf7a2462 100644 --- a/tcs/etes_dispatch.h +++ b/tcs/etes_dispatch.h @@ -214,10 +214,6 @@ class etes_dispatch_opt : public base_dispatch_opt //declare dispatch function in etes_dispatch.cpp bool optimize(); - //Functions to write AMPL data files and solve AMPL model - std::string write_ampl(); - bool optimize_ampl(); - // Set outputs struct based on LP solution -> could move to outputs struct void set_outputs_from_lp_solution(lprec* lp, unordered_map& params); diff --git a/test/main.cpp b/test/main.cpp index 01f9f51ca..4b64eb83d 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -56,7 +56,7 @@ GTEST_API_ int main(int argc, char **argv) { // ::testing::GTEST_FLAG(filter) = "CmodPVWatts*:CMPvwatts*"; //::testing::GTEST_FLAG(filter) = "CmodHybridTest*"; - ::testing::GTEST_FLAG(filter) = "csp_tower.PowerTowerCmod.Default_NoFinancial"; + //::testing::GTEST_FLAG(filter) = "csp_tower.PowerTowerCmod.Default_NoFinancial"; // ::testing::GTEST_FLAG(filter) = "CmodCashLoanTest*:CmodSingleOwnerTest*"; //::testing::GTEST_FLAG(filter) = "CMGeothermal*:GeothermalPlantAnalyzer*"; diff --git a/test/shared_test/csp_solver_core_test.cpp b/test/shared_test/csp_solver_core_test.cpp index d12563e93..1a980d8b0 100644 --- a/test/shared_test/csp_solver_core_test.cpp +++ b/test/shared_test/csp_solver_core_test.cpp @@ -265,7 +265,6 @@ class DefaultCaseCspSolverCore : public CspSolverCoreTest { void SetUp() { CspSolverCoreTest::SetUp(); // adjust heliostatfield parameters - dispatch.solver_params.dispatch_optimize = 1; solver->Ssimulate(sim_setup); } }; From 3cc372414628e4313403829bc5d345d68ba9249a Mon Sep 17 00:00:00 2001 From: qualand <34353104+qualand@users.noreply.github.com> Date: Thu, 6 Jun 2024 10:45:17 -0600 Subject: [PATCH 27/85] replacing array to vector with as_vector() - For dispatch flag and gap --- ssc/cmod_etes_electric_resistance.cpp | 14 ++------------ ssc/cmod_etes_ptes.cpp | 14 ++------------ ssc/cmod_fresnel_physical.cpp | 14 ++------------ ssc/cmod_fresnel_physical_iph.cpp | 14 ++------------ ssc/cmod_tcsmolten_salt.cpp | 16 +++------------- ssc/cmod_trough_physical.cpp | 14 ++------------ ssc/cmod_trough_physical_iph.cpp | 14 ++------------ 7 files changed, 15 insertions(+), 85 deletions(-) diff --git a/ssc/cmod_etes_electric_resistance.cpp b/ssc/cmod_etes_electric_resistance.cpp index ead6c78ba..5a92d9a23 100644 --- a/ssc/cmod_etes_electric_resistance.cpp +++ b/ssc/cmod_etes_electric_resistance.cpp @@ -1154,18 +1154,8 @@ class cm_etes_electric_resistance : public compute_module accumulate_annual_for_year("disp_solve_state", "disp_solve_state_ann", sim_setup.m_report_step / 3600. / as_double("disp_frequency"), steps_per_hour, 1, n_steps_fixed / steps_per_hour); // Reporting dispatch solution counts - size_t n_flag, n_gap = 0; - ssc_number_t* subopt_flag = as_array("disp_subopt_flag", &n_flag); - ssc_number_t* rel_mip_gap = as_array("disp_rel_mip_gap", &n_gap); - - std::vector flag; - std::vector gap; - flag.resize(n_flag); - gap.resize(n_flag); - for (size_t i = 0; i < n_flag; i++) { - flag[i] = (int)subopt_flag[i]; - gap[i] = (double)rel_mip_gap[i]; - } + std::vector flag = as_vector_integer("disp_subopt_flag"); + std::vector gap = as_vector_double("disp_rel_mip_gap"); double avg_gap = 0; if (as_boolean("is_dispatch")) { diff --git a/ssc/cmod_etes_ptes.cpp b/ssc/cmod_etes_ptes.cpp index cf611d7dc..d1d23df04 100644 --- a/ssc/cmod_etes_ptes.cpp +++ b/ssc/cmod_etes_ptes.cpp @@ -1268,18 +1268,8 @@ class cm_etes_ptes : public compute_module accumulate_annual_for_year("disp_solve_state", "disp_solve_state_ann", sim_setup.m_report_step / 3600. / as_double("disp_frequency"), steps_per_hour, 1, n_steps_fixed / steps_per_hour); // Reporting dispatch solution counts - size_t n_flag, n_gap = 0; - ssc_number_t* subopt_flag = as_array("disp_subopt_flag", &n_flag); - ssc_number_t* rel_mip_gap = as_array("disp_rel_mip_gap", &n_gap); - - std::vector flag; - std::vector gap; - flag.resize(n_flag); - gap.resize(n_flag); - for (size_t i = 0; i < n_flag; i++) { - flag[i] = (int)subopt_flag[i]; - gap[i] = (double)rel_mip_gap[i]; - } + std::vector flag = as_vector_integer("disp_subopt_flag"); + std::vector gap = as_vector_double("disp_rel_mip_gap"); double avg_gap = 0; if (as_boolean("is_dispatch")) { diff --git a/ssc/cmod_fresnel_physical.cpp b/ssc/cmod_fresnel_physical.cpp index 895b18d1f..b456bf2a7 100644 --- a/ssc/cmod_fresnel_physical.cpp +++ b/ssc/cmod_fresnel_physical.cpp @@ -1886,18 +1886,8 @@ class cm_fresnel_physical : public compute_module assign("annual_thermal_consumption", annual_thermal_consumption); // Reporting dispatch solution counts - size_t n_flag, n_gap = 0; - ssc_number_t* subopt_flag = as_array("disp_subopt_flag", &n_flag); - ssc_number_t* rel_mip_gap = as_array("disp_rel_mip_gap", &n_gap); - - std::vector flag; - std::vector gap; - flag.resize(n_flag); - gap.resize(n_flag); - for (size_t i = 0; i < n_flag; i++) { - flag[i] = (int)subopt_flag[i]; - gap[i] = (double)rel_mip_gap[i]; - } + std::vector flag = as_vector_integer("disp_subopt_flag"); + std::vector gap = as_vector_double("disp_rel_mip_gap"); double avg_gap = 0; if (is_dispatch) { diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 678b67512..a56a91b52 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -1546,18 +1546,8 @@ class cm_fresnel_physical_iph : public compute_module assign("annual_thermal_consumption", annual_thermal_consumption); // Reporting dispatch solution counts - size_t n_flag, n_gap = 0; - ssc_number_t* subopt_flag = as_array("disp_subopt_flag", &n_flag); - ssc_number_t* rel_mip_gap = as_array("disp_rel_mip_gap", &n_gap); - - std::vector flag; - std::vector gap; - flag.resize(n_flag); - gap.resize(n_flag); - for (size_t i = 0; i < n_flag; i++) { - flag[i] = (int)subopt_flag[i]; - gap[i] = (double)rel_mip_gap[i]; - } + std::vector flag = as_vector_integer("disp_subopt_flag"); + std::vector gap = as_vector_double("disp_rel_mip_gap"); double avg_gap = 0; if (is_dispatch) { diff --git a/ssc/cmod_tcsmolten_salt.cpp b/ssc/cmod_tcsmolten_salt.cpp index 1a391d1fe..be9b8f5ad 100644 --- a/ssc/cmod_tcsmolten_salt.cpp +++ b/ssc/cmod_tcsmolten_salt.cpp @@ -3021,19 +3021,9 @@ class cm_tcsmolten_salt : public compute_module accumulate_annual_for_year("disp_solve_state", "disp_solve_state_ann", sim_setup.m_report_step / 3600.0 / as_double("disp_frequency"), steps_per_hour, 1, n_steps_fixed / steps_per_hour); // Reporting dispatch solution counts - size_t n_flag, n_gap = 0; - ssc_number_t* subopt_flag = as_array("disp_subopt_flag", &n_flag); - ssc_number_t* rel_mip_gap = as_array("disp_rel_mip_gap", &n_gap); - - std::vector flag; - std::vector gap; - flag.resize(n_flag); - gap.resize(n_flag); - for (size_t i = 0; i < n_flag; i++) { - flag[i] = (int)subopt_flag[i]; - gap[i] = (double)rel_mip_gap[i]; - } - + std::vector flag = as_vector_integer("disp_subopt_flag"); + std::vector gap = as_vector_double("disp_rel_mip_gap"); + double avg_gap = 0; if (as_boolean("is_dispatch")) { std::string disp_sum_msg; diff --git a/ssc/cmod_trough_physical.cpp b/ssc/cmod_trough_physical.cpp index 2b859174a..c1d829839 100644 --- a/ssc/cmod_trough_physical.cpp +++ b/ssc/cmod_trough_physical.cpp @@ -2243,18 +2243,8 @@ class cm_trough_physical : public compute_module assign("annual_thermal_consumption", annual_thermal_consumption); // Reporting dispatch solution counts - size_t n_flag, n_gap = 0; - ssc_number_t* subopt_flag = as_array("disp_subopt_flag", &n_flag); - ssc_number_t* rel_mip_gap = as_array("disp_rel_mip_gap", &n_gap); - - std::vector flag; - std::vector gap; - flag.resize(n_flag); - gap.resize(n_flag); - for (size_t i = 0; i < n_flag; i++) { - flag[i] = (int)subopt_flag[i]; - gap[i] = (double)rel_mip_gap[i]; - } + std::vector flag = as_vector_integer("disp_subopt_flag"); + std::vector gap = as_vector_double("disp_rel_mip_gap"); double avg_gap = 0; if (as_boolean("is_dispatch")) { diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index b30a01431..1b9407e03 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -1957,18 +1957,8 @@ class cm_trough_physical_iph : public compute_module assign("annual_thermal_consumption", annual_thermal_consumption); // Reporting dispatch solution counts - size_t n_flag, n_gap = 0; - ssc_number_t* subopt_flag = as_array("disp_subopt_flag", &n_flag); - ssc_number_t* rel_mip_gap = as_array("disp_rel_mip_gap", &n_gap); - - std::vector flag; - std::vector gap; - flag.resize(n_flag); - gap.resize(n_flag); - for (size_t i = 0; i < n_flag; i++) { - flag[i] = (int)subopt_flag[i]; - gap[i] = (double)rel_mip_gap[i]; - } + std::vector flag = as_vector_integer("disp_subopt_flag"); + std::vector gap = as_vector_double("disp_rel_mip_gap"); double avg_gap = 0; if (is_dispatch) { From c088b2cae8559fec9abd6f85d8b9a72d9800e581 Mon Sep 17 00:00:00 2001 From: qualand <34353104+qualand@users.noreply.github.com> Date: Thu, 6 Jun 2024 12:50:12 -0600 Subject: [PATCH 28/85] adding cst iph dispatch to trough IPH model --- ssc/cmod_trough_physical_iph.cpp | 69 +- tcs/CMakeLists.txt | 8 +- tcs/csp_solver_trough_collector_receiver.cpp | 2 + tcs/cst_iph_dispatch.cpp | 976 +++++++++++++++++++ tcs/cst_iph_dispatch.h | 202 ++++ 5 files changed, 1209 insertions(+), 48 deletions(-) create mode 100644 tcs/cst_iph_dispatch.cpp create mode 100644 tcs/cst_iph_dispatch.h diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 1b9407e03..89857c7a7 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -43,7 +43,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "csp_solver_trough_collector_receiver.h" #include "csp_solver_pc_heat_sink.h" #include "csp_solver_two_tank_tes.h" -#include "csp_dispatch.h" +#include "cst_iph_dispatch.h" #include "csp_system_costs.h" //#include "cmod_csp_common_eqns.h" @@ -179,24 +179,18 @@ static var_info _cm_vtab_trough_physical_iph[] = { // Dispatch optimization - { SSC_INPUT, SSC_NUMBER, "disp_rsu_cost_rel", "Receiver startup cost", "$/MWt/start", "", "Sys_Control", "is_dispatch=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "disp_horizon", "Time horizon for dispatch optimization", "hour", "", "Sys_Control", "is_dispatch=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "disp_frequency", "Frequency for dispatch optimization calculations", "hour", "", "Sys_Control", "is_dispatch=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "disp_max_iter", "Max. no. dispatch optimization iterations", "-", "", "Sys_Control", "is_dispatch=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "disp_timeout", "Max. dispatch optimization solve duration", "s", "", "Sys_Control", "is_dispatch=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "disp_mip_gap", "Dispatch optimization solution tolerance", "-", "", "Sys_Control", "is_dispatch=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "disp_time_weighting", "Dispatch optimization future time discounting factor", "-", "", "Sys_Control", "?=0.99", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_horizon", "Time horizon for dispatch optimization", "hour", "", "Sys_Control", "", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "disp_frequency", "Frequency for dispatch optimization calculations", "hour", "", "Sys_Control", "", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "disp_max_iter", "Max. no. dispatch optimization iterations", "-", "", "Sys_Control", "", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "disp_timeout", "Max. dispatch optimization solve duration", "s", "", "Sys_Control", "", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "disp_mip_gap", "Dispatch optimization solution tolerance", "-", "", "Sys_Control", "", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "disp_time_weighting", "Dispatch optimization future time discounting factor", "-", "", "Sys_Control", "?=0.999", "", "" }, { SSC_INPUT, SSC_NUMBER, "disp_steps_per_hour", "Time steps per hour for dispatch optimization calculations", "-", "", "tou", "?=1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "disp_spec_presolve", "Dispatch optimization presolve heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "disp_spec_bb", "Dispatch optimization B&B heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "disp_reporting", "Dispatch optimization reporting level", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "disp_spec_scaling", "Dispatch optimization scaling heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "disp_inventory_incentive", "Dispatch storage terminal inventory incentive multiplier", "", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER" }, - - { SSC_INPUT, SSC_NUMBER, "q_rec_standby", "Receiver standby energy consumption - for dispatch", "kWt", "", "tou", "?=9e99", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup - for dispatch", "kWe-hr", "", "tou", "?=0.0", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "csp_financial_model", "", "1-8", "", "Financial Model", "?=1", "INTEGER,MIN=0", "" }, @@ -584,24 +578,20 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "operating_modes_b", "Next 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "operating_modes_c", "Final 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_rel_mip_gap", "Dispatch relative MIP gap", "", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_solve_state", "Dispatch solver state", "", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_subopt_flag", "Dispatch suboptimal solution flag", "", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_solve_iter", "Dispatch iterations count", "", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_objective", "Dispatch objective function value", "", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_obj_relax", "Dispatch objective function - relaxed max", "", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_qsf_expected", "Dispatch expected solar field available energy", "MWt", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_qsfprod_expected", "Dispatch expected solar field generation", "MWt", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_qsfsu_expected", "Dispatch expected solar field startup enegy", "MWt", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_tes_expected", "Dispatch expected TES charge level", "MWht", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_pceff_expected", "Dispatch expected power cycle efficiency adj.", "", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_thermeff_expected", "Dispatch expected SF thermal efficiency adj.", "", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_qpbsu_expected", "Dispatch expected power cycle startup energy", "MWht", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_wpb_expected", "Dispatch expected power generation", "MWe", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_rev_expected", "Dispatch expected revenue factor", "", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_presolve_nconstr", "Dispatch number of constraints in problem", "", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_presolve_nvar", "Dispatch number of variables in problem", "", "", "tou", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_ARRAY, "disp_solve_time", "Dispatch solver time", "sec", "", "tou", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "disp_rel_mip_gap", "Dispatch relative MIP gap", "", "", "tou", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "disp_solve_state", "Dispatch solver state", "", "", "tou", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "disp_subopt_flag", "Dispatch suboptimal solution flag", "", "", "tou", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "disp_solve_iter", "Dispatch iterations count", "", "", "tou", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "disp_objective", "Dispatch objective function value", "", "", "tou", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "disp_obj_relax", "Dispatch objective function - relaxed max", "", "", "tou", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "disp_qsf_expected", "Dispatch expected solar field available energy", "MWt", "", "tou", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "disp_qsfprod_expected", "Dispatch expected solar field generation", "MWt", "", "tou", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "disp_qsfsu_expected", "Dispatch expected solar field startup energy", "MWt", "", "tou", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "disp_tes_expected", "Dispatch expected TES charge level", "MWht", "", "tou", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "disp_thermeff_expected", "Dispatch expected SF thermal efficiency adj.", "", "", "tou", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "disp_presolve_nconstr", "Dispatch number of constraints in problem", "", "", "tou", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "disp_presolve_nvar", "Dispatch number of variables in problem", "", "", "tou", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "disp_solve_time", "Dispatch solver time", "sec", "", "tou", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "avg_suboptimal_rel_mip_gap","Average suboptimal relative MIP gap", "%", "", "tou", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "P_fixed", "Parasitic power fixed load", "MWe", "", "system", "sim_type=1", "", "" }, @@ -1279,9 +1269,9 @@ class cm_trough_physical_iph : public compute_module // ***************************************************** // System dispatch - csp_dispatch_opt dispatch; + cst_iph_dispatch_opt dispatch; - if (is_dispatch) { + if (is_dispatch && sim_type == 1) { double heater_startup_cost = 0.0; // TODO: Should we add an heater to this model? double q_dot_rec_des = q_dot_hs_des * c_trough.m_solar_mult; //[MWt] @@ -1290,14 +1280,7 @@ class cm_trough_physical_iph : public compute_module as_integer("disp_max_iter"), as_double("disp_mip_gap"), as_double("disp_timeout"), as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting")); - bool can_cycle_use_standby = false; - double disp_csu_cost_calc = 0.0; - double disp_pen_ramping = 0.0; - - double disp_rsu_cost_calc = as_double("disp_rsu_cost_rel") * q_dot_rec_des; //[$/start] - dispatch.params.set_user_params(can_cycle_use_standby, as_double("disp_time_weighting"), - disp_rsu_cost_calc, heater_startup_cost, disp_csu_cost_calc, disp_pen_ramping, - as_double("disp_inventory_incentive"), as_double("q_rec_standby"), as_double("q_rec_heattrace")); + dispatch.params.set_user_params(as_double("disp_time_weighting"), 0.0); } // Instantiate Solver @@ -1382,11 +1365,7 @@ class cm_trough_physical_iph : public compute_module csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::DISPATCH_QSFPROD_EXPECT, allocate("disp_qsfprod_expected", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::DISPATCH_QSFSU_EXPECT, allocate("disp_qsfsu_expected", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::DISPATCH_TES_EXPECT, allocate("disp_tes_expected", n_steps_fixed), n_steps_fixed); - csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::DISPATCH_PCEFF_EXPECT, allocate("disp_pceff_expected", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::DISPATCH_SFEFF_EXPECT, allocate("disp_thermeff_expected", n_steps_fixed), n_steps_fixed); - csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::DISPATCH_QPBSU_EXPECT, allocate("disp_qpbsu_expected", n_steps_fixed), n_steps_fixed); - csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::DISPATCH_WPB_EXPECT, allocate("disp_wpb_expected", n_steps_fixed), n_steps_fixed); - csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::DISPATCH_REV_EXPECT, allocate("disp_rev_expected", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::DISPATCH_PRES_NCONSTR, allocate("disp_presolve_nconstr", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::DISPATCH_PRES_NVAR, allocate("disp_presolve_nvar", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::DISPATCH_SOLVE_TIME, allocate("disp_solve_time", n_steps_fixed), n_steps_fixed); diff --git a/tcs/CMakeLists.txt b/tcs/CMakeLists.txt index 6ae3fd0dc..f3d5327a0 100644 --- a/tcs/CMakeLists.txt +++ b/tcs/CMakeLists.txt @@ -17,7 +17,7 @@ set(TCS_SRC csp_solver_core.cpp csp_solver_cr_electric_resistance.cpp csp_solver_cr_heat_pump.cpp - csp_solver_fresnel_collector_receiver.cpp + csp_solver_fresnel_collector_receiver.cpp csp_solver_gen_collector_receiver.cpp csp_solver_lf_dsg_collector_receiver.cpp csp_solver_mono_eq_methods.cpp @@ -38,6 +38,7 @@ set(TCS_SRC csp_solver_util.cpp csp_solver_weatherreader.cpp csp_system_costs.cpp + cst_iph_dispatch.cpp direct_steam_receivers.cpp dispatch_builder.cpp etes_dispatch.cpp @@ -48,7 +49,7 @@ set(TCS_SRC interconnect.cpp nlopt_callbacks.cpp numeric_solvers.cpp - ptes_solver_design_point.cpp + ptes_solver_design_point.cpp sam_type250_input_generator.cpp sco2_cycle_components.cpp sco2_partialcooling_cycle.cpp @@ -110,7 +111,7 @@ set(TCS_SRC csp_solver_core.h csp_solver_cr_electric_resistance.h csp_solver_cr_heat_pump.h - csp_solver_fresnel_collector_receiver.h + csp_solver_fresnel_collector_receiver.h csp_solver_gen_collector_receiver.h csp_solver_lf_dsg_collector_receiver.h csp_solver_mspt_collector_receiver.h @@ -128,6 +129,7 @@ set(TCS_SRC csp_solver_two_tank_tes.h csp_solver_util.h csp_system_costs.h + cst_iph_dispatch.h direct_steam_receivers.h dispatch_builder.h etes_dispatch.h diff --git a/tcs/csp_solver_trough_collector_receiver.cpp b/tcs/csp_solver_trough_collector_receiver.cpp index 99a8e60ac..0c433b50b 100644 --- a/tcs/csp_solver_trough_collector_receiver.cpp +++ b/tcs/csp_solver_trough_collector_receiver.cpp @@ -825,7 +825,9 @@ double C_csp_trough_collector_receiver::get_startup_energy() { // Note: C_csp_trough_collector_receiver::startup() is called after this function return m_rec_qf_delay * m_q_design_actual * 1.e-6; // MWh + // TODO: can we better estimate the energy based on the loop temperature at midnight? This is not easy... } + double C_csp_trough_collector_receiver::get_pumping_parasitic_coef() { double T_amb_des = 42. + 273.15; diff --git a/tcs/cst_iph_dispatch.cpp b/tcs/cst_iph_dispatch.cpp new file mode 100644 index 000000000..3d9a02e35 --- /dev/null +++ b/tcs/cst_iph_dispatch.cpp @@ -0,0 +1,976 @@ +/* +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 +#include +#include +#include +#include "cst_iph_dispatch.h" + +/* + +Careful with namespaces in this file.. importing the LPsolve library introduces new macro definitions +and function definitions. + +*/ + +cst_iph_dispatch_opt::cst_iph_dispatch_opt() +{ + outputs.clear(); + params.clear(); +} + +void cst_iph_dispatch_opt::init(double hs_q_dot_des, double hs_eta_des) +{ + set_default_solver_parameters(); + + params.clear(); + + params.dt = 1. / (double)solver_params.steps_per_hour; //hr + + params.q_hs_max = pointers.mpc_pc->get_max_thermal_power(); + params.q_hs_min = pointers.mpc_pc->get_min_thermal_power(); + params.w_hs_pump = pointers.mpc_pc->get_htf_pumping_parasitic_coef(); + + params.dt_rec_startup = pointers.col_rec->get_startup_time() / 3600.; + params.e_rec_startup = pointers.col_rec->get_startup_energy(); + params.q_rec_min = pointers.col_rec->get_min_power_delivery(); + params.w_rec_pump = pointers.col_rec->get_pumping_parasitic_coef(); + params.w_track = pointers.col_rec->get_tracking_power(); + params.w_stow = pointers.col_rec->get_col_startup_power(); + + params.e_tes0 = pointers.tes->get_initial_charge_energy(); + params.e_tes_min = pointers.tes->get_min_charge_energy(); + params.e_tes_max = pointers.tes->get_max_charge_energy(); + params.tes_degrade_rate = pointers.tes->get_degradation_rate(); + + //heater params + if (pointers.par_htr != NULL) { + params.q_eh_min = pointers.par_htr->get_min_power_delivery() * ( 1 + 1e-8 ); // ensures controller doesn't shut down heater at minimum load + params.q_eh_max = pointers.par_htr->get_max_power_delivery(std::numeric_limits::quiet_NaN()); + params.eta_eh = pointers.par_htr->get_design_electric_to_heat_cop(); + params.is_parallel_heater = true; + } + else { + params.is_parallel_heater = false; + } + + params.q_hs_des = hs_q_dot_des; + params.eta_hs_des = hs_eta_des; + +} + +void cst_iph_dispatch_opt::set_default_solver_parameters() +{ + /* + The pre-solve options have been tested and show that the optimal combination of options is as set below. + + Optimality was measured by observing the number of constraints + number of variables that resulted an an + annual-averaged basis from each combination. + */ + + /* + From the genetic algorithm: + + Presolve 512 + Branch&Bound 0 32 64 128 256 1024 + Scaling 7 16 32 64 128 + + + ----- keep a record of what's been tried historically for each setting ---- + + >>> set_presolve + PRESOLVE_ROWS + PRESOLVE_COLS + PRESOLVE_REDUCEMIP + PRESOLVE_ELIMEQ2 :: original from 2015 + PRESOLVE_ROWS + PRESOLVE_COLS + PRESOLVE_ELIMEQ2 + PRESOLVE_PROBEFIX :: version used as of 12/5/2016 + PRESOLVE_IMPLIEDFREE :: genetic algorithm from 2015 + + >> set_bb_rule + -- combos set for older problem formulation, appropriate as of mid-2015 + NODE_PSEUDOCOSTSELECT + NODE_RCOSTFIXING :: original + NODE_PSEUDORATIOSELECT + NODE_BREADTHFIRSTMODE :: original v2 + NODE_PSEUDONONINTSELECT + NODE_GREEDYMODE + NODE_DYNAMICMODE + NODE_RCOSTFIXING :: 5m30s, 10.24c + NODE_PSEUDOCOSTSELECT + NODE_RANDOMIZEMODE + NODE_RCOSTFIXING :: 5m20s, 10.17c + NODE_GREEDYMODE + NODE_PSEUDOCOSTMODE + NODE_DEPTHFIRSTMODE + NODE_RANDOMIZEMODE + NODE_DYNAMICMODE :: optimal from genetic algorithm + NODE_PSEUDOCOSTSELECT + NODE_RANDOMIZEMODE :: optimal from independent optimization, THIS VERSION CURRENT AS OF 12/5/2016 + */ + // If user did not set solver parameters, set defaults specific to CSP dispatch model + if (solver_params.presolve_type < 0) + solver_params.presolve_type = PRESOLVE_ROWS + PRESOLVE_COLS + PRESOLVE_ELIMEQ2 + PRESOLVE_PROBEFIX; + if (solver_params.bb_type < 0) + solver_params.bb_type = NODE_PSEUDOCOSTSELECT + NODE_DYNAMICMODE; + //solver_params.bb_type = NODE_PSEUDOCOSTSELECT + NODE_AUTOORDER; + if (solver_params.scaling_type < 0) + solver_params.scaling_type = SCALE_MEAN + SCALE_LOGARITHMIC + SCALE_POWER2 + SCALE_EQUILIBRATE + SCALE_INTEGERS; + //SCALE_CURTISREID + SCALE_LOGARITHMIC + SCALE_POWER2 + SCALE_EQUILIBRATE + SCALE_INTEGERS //genetic algorithm +} + +bool cst_iph_dispatch_opt::check_setup(int num_step) +{ + //check parameters and inputs to make sure everything has been set up correctly + if ((int)params.elec_price.size() < num_step) return false; + if ((int)params.heat_cost.size() < num_step) return false; + if ((int)params.heat_load.size() < num_step) return false; + + if ((int)params.q_sfavail_expected.size() < num_step) return false; + + return base_dispatch_opt::check_setup(); +} + +bool cst_iph_dispatch_opt::update_horizon_parameters(C_csp_tou& mc_tou) +{ + //get price signal and electricity generation limits + int num_steps = solver_params.optimize_horizon * solver_params.steps_per_hour; + params.elec_price.clear(); + params.elec_price.resize(num_steps, 1.); + params.heat_cost.clear(); + params.heat_cost.resize(num_steps, 1.); + params.heat_load.clear(); + params.heat_load.resize(num_steps, 1.e99); + + double sec_per_step = 3600. / (double)solver_params.steps_per_hour; + double W_dot_max = params.q_hs_max * params.eta_hs_des; //[kWe] TODO: Change this to heat only (remove efficiency) + for (int t = 0; t < num_steps; t++) { + C_csp_tou::S_csp_tou_outputs tou_outputs; + mc_tou.call(pointers.siminfo->ms_ts.m_time + t * sec_per_step, tou_outputs); + params.elec_price.at(t) = tou_outputs.m_elec_price * 1000.0; // $/kWhe -> $/MWhe + params.heat_cost.at(t) = tou_outputs.m_heat_price * 1000.0; // $/kWht -> $/MWht + params.heat_load.at(t) = tou_outputs.m_wlim_dispatch * W_dot_max; + } + return true; +} + +void cst_iph_dispatch_opt::update_initial_conditions(double q_dot_to_pb, double T_htf_cold_des, double pc_state_persist) +{ + //note the states of the power cycle and receiver + params.is_pb_operating0 = pointers.mpc_pc->get_operating_state() == C_csp_power_cycle::ON; + params.is_pb_standby0 = pointers.mpc_pc->get_operating_state() == C_csp_power_cycle::STANDBY; + params.is_rec_operating0 = pointers.col_rec->get_operating_state() == C_csp_collector_receiver::ON; + + params.q_pb0 = q_dot_to_pb; + + //Note the state of the thermal energy storage system + double q_disch, m_dot_disch, T_tes_return; + pointers.tes->discharge_avail_est(T_htf_cold_des, pointers.siminfo->ms_ts.m_step, q_disch, m_dot_disch, T_tes_return); + params.e_tes0 = q_disch * pointers.siminfo->ms_ts.m_step / 3600. + params.e_tes_min; //MWh + if (params.e_tes0 < params.e_tes_min) + params.e_tes0 = params.e_tes_min; + if (params.e_tes0 > params.e_tes_max) + params.e_tes0 = params.e_tes_max; +} + +bool cst_iph_dispatch_opt::predict_performance(int step_start, int ntimeints, int divs_per_int) +{ + //Step number - 1-based index for first hour of the year. + + //save step count + m_nstep_opt = ntimeints; + + //Predict performance out nstep values. + params.eta_sf_expected.clear(); //thermal efficiency + params.q_sfavail_expected.clear(); //predicted field energy output + + //create the sim info + C_csp_solver_sim_info simloc; + simloc.ms_ts.m_step = pointers.siminfo->ms_ts.m_step; + + double Asf = pointers.col_rec->get_collector_area(); + + double ave_weight = 1./(double)divs_per_int; + + for(int i=0; i 90. || dni < 0. ) + dni = 0.; + + //get optical efficiency + double opt_eff = pointers.col_rec->calculate_optical_efficiency(pointers.m_weather.ms_outputs, simloc); + + double q_inc = Asf * opt_eff * dni * 1.e-6; //MW + + //get thermal efficiency + double therm_eff = pointers.col_rec->calculate_thermal_efficiency_approx(pointers.m_weather.ms_outputs, q_inc, simloc); + therm_eff_ave += therm_eff * ave_weight; + + //store the predicted field energy output + // use the cold tank temperature as a surrogate for the loop inlet temperature, as it + // closely follows the loop inlet temperature, and is more representative over the + // two-day lookahead period than the loop inlet temperature (design or actual) at the + // same point in time + double T_tank_cold = pointers.tes->get_cold_temp() - 273.15; // [C] + double q_max = pointers.col_rec->get_max_power_delivery(T_tank_cold); // [kW] + q_inc_ave += (std::min)(q_max, q_inc * therm_eff * ave_weight); + + simloc.ms_ts.m_time += simloc.ms_ts.m_step; + pointers.m_weather.converged(); + } + + //-----report hourly averages + //thermal efficiency + params.eta_sf_expected.push_back(therm_eff_ave); + //predicted field energy output + params.q_sfavail_expected.push_back( q_inc_ave ); + } + + if(! check_setup(m_nstep_opt) ) + throw C_csp_exception("Dispatch optimization precheck failed."); + + return true; +} + +void cst_iph_dispatch_opt::calculate_parameters(unordered_map &pars) +{ + /* + A central location for making sure the parameters from the model are accurately calculated for use in + the dispatch optimization model. + */ + pars["delta"] = params.dt; + pars["Eu"] = params.e_tes_max ; + pars["Er"] = params.e_rec_startup ; + pars["Qu"] = params.q_hs_des ; + pars["Ql"] = params.q_hs_min ; + pars["Qru"] = params.e_rec_startup / params.dt_rec_startup; + pars["Qrl"] = params.q_rec_min ; + pars["Lr"] = params.w_rec_pump ; + pars["Lc"] = params.w_hs_pump; + pars["Wh"] = params.w_track; + pars["Ehs"] = params.w_stow; + pars["Wrsb"] = params.w_rec_ht; + + if (params.is_parallel_heater) { + pars["Qehu"] = params.q_eh_max; + pars["Qehl"] = params.q_eh_min; + pars["eta_eh"] = params.eta_eh; + } + + // Initial conditions + pars["s0"] = params.e_tes0; + + // Receiver start-up time - TODO: We might be able to remove this + params.delta_rs.resize(m_nstep_opt); + for(int t=0; t going to ignore to start + - What should be assumed for the auxiliary heating technology? Natural gas? + - What costs are we minimizing? + - Field pumps + - Field Tracking + - TES pumps + - Electric heat charging -> TES + - Fuel costs for back-up + - Parallel Heater -> directly meeting load instead of charging TES + - Value of heat not the cost of heat? + */ + lprec *lp; + int ret = 0; + + + try{ + + //Calculate the number of variables + int nt = (int)m_nstep_opt; + + unordered_map P; + calculate_parameters(P); + + //set up the variable structure + optimization_vars O; + O.add_var("xr", optimization_vars::VAR_TYPE::REAL_T, optimization_vars::VAR_DIM::DIM_T, nt, 0.); + O.add_var("xrsu", optimization_vars::VAR_TYPE::REAL_T, optimization_vars::VAR_DIM::DIM_T, nt, 0., P["Qru"]); + O.add_var("ursu", optimization_vars::VAR_TYPE::REAL_T, optimization_vars::VAR_DIM::DIM_T, nt, 0., P["Er"] * 1.0001); + O.add_var("yr", optimization_vars::VAR_TYPE::BINARY_T, optimization_vars::VAR_DIM::DIM_T, nt); + O.add_var("yrsu", optimization_vars::VAR_TYPE::BINARY_T, optimization_vars::VAR_DIM::DIM_T, nt); + + O.add_var("x", optimization_vars::VAR_TYPE::REAL_T, optimization_vars::VAR_DIM::DIM_T, nt, 0., P["Qu"]); + O.add_var("s", optimization_vars::VAR_TYPE::REAL_T, optimization_vars::VAR_DIM::DIM_T, nt, 0., P["Eu"]); + + if (params.is_parallel_heater) { + O.add_var("qeh", optimization_vars::VAR_TYPE::REAL_T, optimization_vars::VAR_DIM::DIM_T, nt, 0., P["Qehu"]); + O.add_var("yeh", optimization_vars::VAR_TYPE::BINARY_T, optimization_vars::VAR_DIM::DIM_T, nt); + O.add_var("yreh", optimization_vars::VAR_TYPE::BINARY_T, optimization_vars::VAR_DIM::DIM_T, nt); + } + + // Construct LP model and set up variable properties + lp = construct_lp_model(&O); + + /* + -------------------------------------------------------------------------------- + set up the objective function first (per lpsolve guidance) + -------------------------------------------------------------------------------- + */ + { + REAL* row = new REAL[8 * nt + 1]; + int *col = new int[8 * nt + 1]; + double tadj = P["disp_time_weighting"]; + int i = 0; + + //calculate the mean price to appropriately weight the receiver production timing derate + double pmean =0; + for(int t=0; t<(int)params.elec_price.size(); t++) + pmean += params.elec_price.at(t); + pmean /= (double)params.elec_price.size(); + + for(int t=0; t 0) + { + row[i] = -1.; + col[i++] = O.column("ursu", t - 1); + } + + add_constraintex(lp, i, row, col, LE, 0); + } + + // Receiver inventory bound when starting + // ursu[t] <= Er * yrsu[t] + { + i = 0; + + row[i] = 1.; + col[i++] = O.column("ursu", t); + + row[i] = -P["Er"]; + col[i++] = O.column("yrsu", t); + + add_constraintex(lp, i, row, col, LE, 0.); + // NOTES: Turning off this constraint helps align the trough model when multiple starts occur in a day. + // However, it does result in more starts and stops within the solution. This could be fixed by reintroducting the field startup cost + } + + // Receiver operation allowed when start-up is complete or if receiver was operating + // NOTE: tighter formulation when Er is distributed + // yr[t] <= ursu[t] / Er + yr[t-1] + { + i = 0; + row[i] = P["Er"]; + col[i++] = O.column("yr", t); + + row[i] = -1.0; + col[i++] = O.column("ursu", t); + + double rhs = 0.; + if (t > 0) + { + row[i] = -P["Er"]; + col[i++] = O.column("yr", t - 1); + } + else + { + rhs = (params.is_rec_operating0 ? P["Er"] : 0.); + } + + add_constraintex(lp, i, row, col, LE, rhs); + } + + // Receiver startup can't be enabled after a time step where the Receiver was operating + // yrsu[t] + yr[t-1] <= 1 + { + if (t > 0) { + i = 0; + row[i] = 1.; + col[i++] = O.column("yrsu", t); + + row[i] = 1.; + col[i++] = O.column("yr", t - 1); + + add_constraintex(lp, i, row, col, LE, 1.); + } + } + + // Receiver startup energy limit + // xrsu[t] <= Qru * yrsu[t] + { + i = 0; + row[i] = 1.; + col[i++] = O.column("xrsu", t); + + row[i] = -P["Qru"]; + col[i++] = O.column("yrsu", t); + + add_constraintex(lp, i, row, col, LE, 0.); + } + + // Receiver startup and operation consumption limit + // xr[t] + xrsu[t] <= Qin[t] + { + i = 0; + row[i] = 1.; + col[i++] = O.column("xr", t); + + row[i] = 1.; + col[i++] = O.column("xrsu", t); + + add_constraintex(lp, i, row, col, LE, params.q_sfavail_expected.at(t)); + } + + // Receiver maximum operation limit + // xr[t] <= Qin[t] * yr[t] + { + i = 0; + row[i] = 1.; + col[i++] = O.column("xr", t); + + row[i] = -params.q_sfavail_expected.at(t); + col[i++] = O.column("yr", t); + + add_constraintex(lp, i, row, col, LE, 0.); + } + + // Receiver minimum operation limit + // xr[t] >= Qrl * yr[t] + { + i = 0; + row[i] = 1.; + col[i++] = O.column("xr", t); + + row[i] = -P["Qrl"]; + col[i++] = O.column("yr", t); + + add_constraintex(lp, i, row, col, GE, 0.); + } + + // Receiver startup only during solar positive periods + // TODO: This relies on pre-solve to remove variables (might want to create a special set of solar hours) + // yrsu[t] <= 0 when Qin[t] = 0 + { + i = 0; + row[i] = 1.; + col[i++] = O.column("yrsu", t); + + add_constraintex(lp, i, row, col, LE, (std::min)(P["Qru"] * params.q_sfavail_expected.at(t), 1.0)); + } + + // Receiver can't continue operating when no energy is available + // TODO: Can we combine these two constraints? + // yr[t] <= Qin[t] / Qrl + { + i = 0; + row[i] = 1.; + col[i++] = O.column("yr", t); + + add_constraintex(lp, i, row, col, LE, (std::min)(floor(params.q_sfavail_expected.at(t) / P["Qrl"]), 1.0)); //tighter formulation + } + } + } + + // ******************** Electric Heater constraints **************** + if (params.is_parallel_heater) + { + REAL row[5]; + int col[5]; + + for (int t = 0; t < nt; t++) + { + int i = 0; // row and column index + + // Heater power limit + // qeh[t] <= Qehu * yeh[t] + { + i = 0; + row[i] = 1.; + col[i++] = O.column("qeh", t); + + row[i] = -P["Qehu"]; + col[i++] = O.column("yeh", t); + + add_constraintex(lp, i, row, col, LE, 0.); + } + + // Heater minimum operation requirement + // qeh[t] >= Qehl * yeh[t] + { + i = 0; + row[i] = 1.; + col[i++] = O.column("qeh", t); + + row[i] = -P["Qehl"]; + col[i++] = O.column("yeh", t); + + add_constraintex(lp, i, row, col, GE, 0.); + } + + // Heaters must be off before field defocus + // xr[t] + xrsu[t] >= Qin[t] * yreh[t] + { + i = 0; + row[i] = 1.; + col[i++] = O.column("xr", t); + + row[i] = 1.; + col[i++] = O.column("xrsu", t); + + row[i] = -params.q_sfavail_expected.at(t); + col[i++] = O.column("yreh", t); + + add_constraintex(lp, i, row, col, GE, 0.); + } + + //******* linearization of yreh[t] = yr[t] * yeh[t] ****** + + // Upper bound with yr + // yreh[t] <= yr[t] + { + i = 0; + row[i] = 1.; + col[i++] = O.column("yreh", t); + + row[i] = -1.; + col[i++] = O.column("yr", t); + + add_constraintex(lp, i, row, col, LE, 0.); + } + + // Upper bound with yeh + // yreh[t] <= yeh[t] + { + i = 0; + row[i] = 1.; + col[i++] = O.column("yreh", t); + + row[i] = -1.; + col[i++] = O.column("yeh", t); + + add_constraintex(lp, i, row, col, LE, 0.); + } + + // Lower bound + // yreh[t] >= yr[t] + yeh[t] - 1 + { + i = 0; + row[i] = 1.; + col[i++] = O.column("yreh", t); + + row[i] = -1.; + col[i++] = O.column("yr", t); + + row[i] = -1.; + col[i++] = O.column("yeh", t); + + add_constraintex(lp, i, row, col, GE, -1); + } + } + } + + // ******************** Power cycle constraints ******************* + { + REAL row[7]; + int col[7]; + + for (int t = 0; t < nt; t++) + { + int i = 0; // row and column index + + // Heat sink maximum operation limit + // x[t] <= Qu + { + i = 0; + row[i] = 1.; + col[i++] = O.column("x", t); + + add_constraintex(lp, i, row, col, LE, P["Qu"]); + } + + // Heat sink load maximum operation limit + // x[t] <= Q_hl + { + i = 0; + row[i] = 1.; + col[i++] = O.column("x", t); + + add_constraintex(lp, i, row, col, LE, params.heat_load.at(t)); + } + + // Heat sink minimum operation limit + // x[t] >= Ql + { + i = 0; + row[i] = 1.; + col[i++] = O.column("x", t); + + add_constraintex(lp, i, row, col, GE, P["Ql"]); + } + } + } + + // ******************** TES Balance constraints ******************* + { + REAL row[10]; + int col[10]; + + for(int t=0; t 0) + { + row[i] = 1.; + col[i++] = O.column("s", t - 1); + } + else + { + rhs += - P["s0"]; //initial storage state (kWh) + } + + add_constraintex(lp, i, row, col, EQ, rhs); + } + + // Max cycle thermal input is required in time periods where cycle operates and receiver is starting up + // x[t+1] + Qb * ycsb[t+1] <= s[t] / delta_rs[t+1] - M * ( -3 + yrsu[t+1] + y[t] + y[t+1] ) + //{ + // if (t < nt - 1) + // { + // double t_rec_startup = params.delta_rs.at(t) * P["delta"]; + + // i = 0; + // row[i] = 1.; + // col[i++] = O.column("x", t + 1); + + // row[i] = -1. / t_rec_startup; + // col[i++] = O.column("s", t); + + // row[i] = P["Qu"]; //tighter formulation + // col[i++] = O.column("yrsu", t + 1); + + // row[i] = 1.0; + // col[i++] = O.column("x", t); + + // row[i] = 1.0; + // col[i++] = O.column("x", t + 1); + + // add_constraintex(lp, i, row, col, LE, 3.0 * P["Qu"]); + // } + //} + + } + } + + //Set problem to minimize (operating cost) + set_minim(lp); + + setup_solver_presolve_bbrules(lp); + bool return_ok = problem_scaling_solve_loop(lp); + set_lp_solve_outputs(lp); + + // Saving problem and solution for DEBUGGING formulation + //save_problem_solution_debug(lp); + //if (solver_params.disp_reporting > 4) + // print_log_to_file(); + + if(return_ok) + set_outputs_from_lp_solution(lp, P); + + delete_lp(lp); + lp = NULL; + print_dispatch_update(); + + return return_ok; + } + catch(std::exception &e) + { + //clean up memory and pass on the exception + if( lp != NULL ) + delete_lp(lp); + + throw e; + } + catch(...) + { + //clean up memory + if( lp != NULL ) + delete_lp(lp); + + return false; + } + + return false; +} + +void cst_iph_dispatch_opt::set_outputs_from_lp_solution(lprec* lp, unordered_map& model_params) +{ + int nt = (int)m_nstep_opt; + + outputs.clear(); + outputs.resize(nt); + + int ncols = get_Norig_columns(lp); + int nrows = get_Norig_rows(lp); + + for (int c = 1; c <= ncols; c++) + { + char* colname = get_origcol_name(lp, c); + if (!colname) continue; + + char root[15]; + char ind[4]; + if (parse_column_name(colname, root, ind)) continue; //a 2D variable + + int t = atoi(ind); + double val = get_var_primalresult(lp, nrows + c); + + if (strcmp(root, "x") == 0) // cycle thermal energy consumption + { + outputs.q_pb_target.at(t) = val; + outputs.pb_operation.at(t) = val > 0.0 ? 1.0 : 0.0; + } + else if (strcmp(root, "yrsu") == 0) // is receiver starting + { + outputs.rec_operation.at(t) = outputs.rec_operation.at(t) || (std::abs(1 - val) < 0.001); + } + else if (strcmp(root, "xrsu") == 0) // receiver startup energy + { + outputs.q_rec_startup.at(t) = val; + } + else if (strcmp(root, "yr") == 0) // is receiver operating + { + outputs.rec_operation.at(t) = outputs.rec_operation.at(t) || (std::abs(1 - val) < 0.001); + } + else if (strcmp(root, "s") == 0) // thermal storage charge state + { + outputs.tes_charge_expected.at(t) = val; + } + else if (strcmp(root, "xr") == 0) // receiver production + { + outputs.q_sf_expected.at(t) = val; + } + else if (strcmp(root, "yeh") == 0) // is parallel heater on + { + outputs.htr_operation.at(t) = outputs.htr_operation.at(t) || (std::abs(1 - val) < 0.001); + } + else if (strcmp(root, "qeh") == 0) // heater target power + { + outputs.q_eh_target.at(t) = val; + } + } +} + +bool cst_iph_dispatch_opt::set_dispatch_outputs() +{ + if (lp_outputs.last_opt_successful && m_current_read_step < (int)outputs.q_pb_target.size()) + { + //calculate the current read step, account for number of dispatch steps per hour and the simulation time step + m_current_read_step = (int)(pointers.siminfo->ms_ts.m_time * solver_params.steps_per_hour / 3600. - .001) + % (solver_params.optimize_frequency * solver_params.steps_per_hour); + + disp_outputs.is_rec_su_allowed = outputs.rec_operation.at(m_current_read_step); + disp_outputs.is_pc_sb_allowed = outputs.pb_standby.at(m_current_read_step); + disp_outputs.is_pc_su_allowed = outputs.pb_operation.at(m_current_read_step) || disp_outputs.is_pc_sb_allowed; + + disp_outputs.q_pc_target = outputs.q_pb_target.at(m_current_read_step); + + // Artificially set target higher to deal with end of TES effects + if (m_current_read_step > 1) { + if ((outputs.tes_charge_expected.at(m_current_read_step - 1) > 0.) + && (outputs.tes_charge_expected.at(m_current_read_step) == 0.)) { // Did we run out of TES this time step? + disp_outputs.q_pc_target = params.heat_load.at(m_current_read_step); // Set to heat load this time step + } + else if ((outputs.tes_charge_expected.at(m_current_read_step - 1) == 0.0) // Did we run out of TES last time step? + && (outputs.q_pb_target.at(m_current_read_step - 1) > 0.0)) { // and we generating last time step? + disp_outputs.q_pc_target = params.heat_load.at(m_current_read_step); // Set to heat load this time step + disp_outputs.is_pc_su_allowed = true; + } + } + + if (disp_outputs.q_pc_target + 1.e-5 < params.q_hs_min) { + disp_outputs.is_pc_su_allowed = false; + disp_outputs.q_pc_target = 0.0; + } + disp_outputs.q_dot_pc_max = disp_outputs.q_pc_target; + + disp_outputs.q_dot_elec_to_CR_heat = outputs.q_sf_expected.at(m_current_read_step); // TODO: I don't really understand what this one does to the solver... + + disp_outputs.q_eh_target = outputs.q_eh_target.at(m_current_read_step); + disp_outputs.is_eh_su_allowed = outputs.htr_operation.at(m_current_read_step); + + disp_outputs.etasf_expect = params.eta_sf_expected.at(m_current_read_step); + disp_outputs.qsf_expect = params.q_sfavail_expected.at(m_current_read_step); + disp_outputs.qsfprod_expect = outputs.q_sf_expected.at(m_current_read_step); + disp_outputs.qsfsu_expect = outputs.q_rec_startup.at(m_current_read_step); + disp_outputs.tes_expect = outputs.tes_charge_expected.at(m_current_read_step); + + if (m_current_read_step > solver_params.optimize_frequency* solver_params.steps_per_hour) + throw C_csp_exception("Counter synchronization error in dispatch optimization routine.", "csp_dispatch"); + } + disp_outputs.time_last = pointers.siminfo->ms_ts.m_time; + + return true; +} diff --git a/tcs/cst_iph_dispatch.h b/tcs/cst_iph_dispatch.h new file mode 100644 index 000000000..14d4e4830 --- /dev/null +++ b/tcs/cst_iph_dispatch.h @@ -0,0 +1,202 @@ +/* +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. +*/ +#pragma once +#pragma warning(disable: 4290) // ignore warning: 'C++ exception specification ignored except to indicate a function is not __declspec(nothrow)' + +#include "base_dispatch.h" + +class cst_iph_dispatch_opt : public base_dispatch_opt +{ +public: + struct s_params + { + // Time dependent parameters + std::vector elec_price; //[$/MWhe] Electricity price + std::vector heat_cost; //[$/MWht] Cost of back-up heat + std::vector heat_load; //[MWt] Limit on net electricity production + std::vector q_sfavail_expected; //[MWt] Expected available solar field energy + std::vector delta_rs; //[hr] Expected proportion of time step used for receiver start up + std::vector eta_sf_expected; //[-] Expected solar field thermal efficiency (normalized) + + // Parameters + double dt; //[hr] Time step + double e_tes_min; //[MWht] minimum allowable energy capacity in TES + double e_tes_max; //[MWht] maximum allowable energy capacity in TES + double e_rec_startup; //[MWht] energy requirement to start up the receiver + double dt_rec_startup; //[hr] time requirement to start up the receiver + double tes_degrade_rate; //IN [1/hr] Fractional energy loss from tes per hour -> NOT Used + double q_hs_des; //[kWe] design cycle thermal power input + double eta_hs_des; //[-] design cycle efficiency + double q_hs_max; //[MWt] Maximum allowable thermal energy rate to the heat sink (load) + double q_hs_min; //[MWt] Minimum allowable thermal energy rate to the heat sink (load) + double q_rec_min; //[MWt] Minimum allowable power delivery by the receiver when operating + double w_rec_pump; //[MWe/MWt] Pumping parasitic power per thermal energy produced + double time_weighting; //[-] Weighting factor that discounts future decisions over more imminent ones + + bool is_parallel_heater; //[-] Is there a heater parallel to the receiver? + double q_eh_max; //[MWt] Maximum allowable power delivery by the electrical heaters when operating + double q_eh_min; //[MWt] Minimum allowable power delivery by the electrical heaters when operating + double eta_eh; //[-] Electric resistance heating sub-system efficiency + + // Initial Conditions + bool is_rec_operating0; //receiver is operating at the initial time step + bool is_pb_operating0; //Power block is operating at the initial time step + bool is_pb_standby0; //Power block is in standby at the initial time step + double q_pb0; //[MWt] Thermal power consumption in the cycle entering the initial time step + double e_tes0; //[MWht] current stored energy capacity + + // Parasitic loads + double w_rec_ht; //[MW-hr] Heat trace power during receiver startup + double w_track; //[MWe] Heliostat tracing power + double w_stow; //[MWe-hr] Heliostat stow electricity requirement + double w_hs_pump; //[MWe/MWt] Heat sink (load) HTF pumping power per thermal energy consumed + + s_params() { + is_pb_operating0 = false; + is_pb_standby0 = false; + is_rec_operating0 = false; + dt = 1.; + q_pb0 = std::numeric_limits::quiet_NaN(); + e_tes0 = std::numeric_limits::quiet_NaN(); + e_tes_min = std::numeric_limits::quiet_NaN(); + e_tes_max = std::numeric_limits::quiet_NaN(); + e_rec_startup = std::numeric_limits::quiet_NaN(); + dt_rec_startup = std::numeric_limits::quiet_NaN(); + tes_degrade_rate = std::numeric_limits::quiet_NaN(); + q_hs_max = std::numeric_limits::quiet_NaN(); + q_hs_min = std::numeric_limits::quiet_NaN(); + q_rec_min = std::numeric_limits::quiet_NaN(); + w_rec_pump = std::numeric_limits::quiet_NaN(); + q_hs_des = std::numeric_limits::quiet_NaN(); + eta_hs_des = std::numeric_limits::quiet_NaN(); + + time_weighting = 0.99; + w_rec_ht = 0.0; + w_track = std::numeric_limits::quiet_NaN(); + w_stow = std::numeric_limits::quiet_NaN(); + w_hs_pump = std::numeric_limits::quiet_NaN(); + is_parallel_heater = false; + q_eh_max = 0.0; + q_eh_min = 0.0; + eta_eh = 1.0; + } + + void clear() + { + elec_price.clear(); + heat_cost.clear(); + heat_load.clear(); + q_sfavail_expected.clear(); + delta_rs.clear(); + eta_sf_expected.clear(); + } + + void set_user_params(double disp_time_weighting, double rec_heattrace) + { + time_weighting = disp_time_weighting; + w_rec_ht = rec_heattrace; //TODO: why are this grouped here? - We should create a getter function for the receiver-collector class + } + } params; + + struct s_outputs + { + std::vector rec_operation; // [-] Receiver startup ok? + std::vector pb_operation; // [-] Power block startup ok? + std::vector pb_standby; // [-] Power block standby ok? + std::vector q_pb_target; // [MWt] Optimized energy generation (less startup loss) + std::vector q_sf_expected; // [MWt] Expected solar field energy generation + std::vector tes_charge_expected; // [MWht] Expected thermal energy storage charge state + std::vector q_rec_startup; // [MWt] Thermal Power going to startup + + std::vector htr_operation; // [-] is heater allowed to operate + std::vector q_eh_target; // [MWt] Heater target thermal power + + void clear() { + rec_operation.clear(); + pb_operation.clear(); + pb_standby.clear(); + q_pb_target.clear(); + q_sf_expected.clear(); + tes_charge_expected.clear(); + q_rec_startup.clear(); + + htr_operation.clear(); + q_eh_target.clear(); + } + + void resize(int nt) { + rec_operation.resize(nt, false); + pb_operation.resize(nt, false); + pb_standby.resize(nt, false); + q_pb_target.resize(nt, 0.); + q_sf_expected.resize(nt, 0.); + tes_charge_expected.resize(nt, 0.); + q_rec_startup.resize(nt, 0.); + + htr_operation.resize(nt, false); + q_eh_target.resize(nt, 0.); + } + + } outputs; + + //----- public member functions ---- + + cst_iph_dispatch_opt(); + + void init(double cycle_q_dot_des, double cycle_eta_des); + + // Set default solver parameters if user did not set them + void set_default_solver_parameters(); + + //check parameters and inputs to make sure everything has been set up correctly + bool check_setup(int nstep); + + //Update parameter values within the horizon + bool update_horizon_parameters(C_csp_tou &mc_tou); + + // update dispatch initial conditions + void update_initial_conditions(double q_dot_to_pb, double T_htf_cold_des, double pc_state_persist); + + //Predict performance out nstep values. + bool predict_performance(int step_start, int ntimeints, int divs_per_int); + + // Calculate parameter values + void calculate_parameters(unordered_map& pars); + + //declare dispatch function in csp_dispatch.cpp + bool optimize(); + + // Set outputs struct based on LP solution -> could move to outputs struct + void set_outputs_from_lp_solution(lprec* lp, unordered_map& params); + + bool set_dispatch_outputs(); +}; From 6a0409bbd251bbd9b452df6e0301279b64f66094 Mon Sep 17 00:00:00 2001 From: qualand <34353104+qualand@users.noreply.github.com> Date: Fri, 7 Jun 2024 11:25:56 -0600 Subject: [PATCH 29/85] heat load capacity factor for IPH trough --- ssc/cmod_trough_physical_iph.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 89857c7a7..ab76af5e2 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -604,6 +604,7 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "sim_duration", "Computational time of timeseries simulation", "s", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "W_dot_par_tot_haf", "Adjusted parasitic power", "kWe", "", "system", "sim_type=1", "", "" }, //{ SSC_OUTPUT, SSC_NUMBER, "q_dot_defocus_est", "Thermal energy intentionally lost by defocusing", "MWt", "", "system", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "heat_load_capacity_factor", "Percentage of heat load met", "%", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "recirculating", "Field recirculating (bypass valve open)", "-", "", "solar_field", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_diams", "Pipe diameters in TES", "m", "", "TES", "sim_type=1", "", "" }, @@ -1962,6 +1963,21 @@ class cm_trough_physical_iph : public compute_module assign("capacity_factor", (ssc_number_t)(kWh_per_kW / ((double)n_steps_fixed / (double)steps_per_hour)*100.)); assign("kwh_per_kw", (ssc_number_t)kWh_per_kW); + + // Calculate percentage of heat load met + std::vector gen = as_vector_double("gen"); + double tot_heat_load = 0.0, heat_load = 0.0, load_met = 0.0; + double step_s, hl_nondim_val, temp; + int temp_int; + double sec_per_ts = 3600. / steps_per_hour; + for (int i = 0; i < gen.size(); i++) { + step_s = (i+1) * sec_per_ts; + offtaker_schedule.get_timestep_data(step_s, hl_nondim_val, temp, temp_int); + heat_load = hl_nondim_val * nameplate * 1.e3; + tot_heat_load += heat_load; + load_met += gen[i] > heat_load ? heat_load : gen[i]; // Only get credit for the load itself + } + assign("heat_load_capacity_factor", (ssc_number_t)load_met * 100. / tot_heat_load); } template From 1a6b9c7e2cee8ebc47bf9be531790718fc6d33a2 Mon Sep 17 00:00:00 2001 From: tyneises Date: Thu, 13 Jun 2024 08:37:07 -0500 Subject: [PATCH 30/85] replace cmod inputs presumably lost in develop merge --- ssc/cmod_fresnel_physical.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ssc/cmod_fresnel_physical.cpp b/ssc/cmod_fresnel_physical.cpp index 3f6ea928e..0d760f786 100644 --- a/ssc/cmod_fresnel_physical.cpp +++ b/ssc/cmod_fresnel_physical.cpp @@ -203,9 +203,18 @@ static var_info _cm_vtab_fresnel_physical[] = { { SSC_INPUT, SSC_NUMBER, "V_tes_des", "Design-point velocity to size the TES pipe diameters", "m/s", "", "Storage", "?=1.85", "", "SIMULATION_PARAMETER" }, - // System Control - + { SSC_INPUT, SSC_NUMBER, "is_dispatch", "Allow dispatch optimization?", /*TRUE=1*/ "-", "", "Sys_Control", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_frequency", "Frequency for dispatch optimization calculations", "hour", "", "Sys_Control", "is_dispatch=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_horizon", "Time horizon for dispatch optimization", "hour", "", "Sys_Control", "is_dispatch=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_max_iter", "Max. no. dispatch optimization iterations", "-", "", "Sys_Control", "is_dispatch=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_timeout", "Max. dispatch optimization solve duration", "s", "", "Sys_Control", "is_dispatch=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_mip_gap", "Dispatch optimization solution tolerance", "-", "", "Sys_Control", "is_dispatch=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_time_weighting", "Dispatch optimization future time discounting factor", "-", "", "Sys_Control", "?=0.99", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_rsu_cost_rel", "Receiver startup cost", "$/MWt/start", "", "Sys_Control", "is_dispatch=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_csu_cost_rel", "Cycle startup cost", "$/MWe-cycle/start", "", "Sys_Control", "is_dispatch=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "disp_pen_ramping", "Dispatch cycle production change penalty", "$/MWe-change", "", "Sys_Control", "is_dispatch=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "pb_fixed_par", "Fixed parasitic load - runs at all times", "", "", "Sys_Control", "*", "", "" }, { SSC_INPUT, SSC_ARRAY, "bop_array", "Balance of plant parasitic power fraction", "", "", "Sys_Control", "*", "", "" }, { SSC_INPUT, SSC_ARRAY, "aux_array", "Aux heater, boiler parasitic", "", "", "Sys_Control", "*", "", "" }, From fc0a2f6176f3a706bd7810fa621fef2d728957c5 Mon Sep 17 00:00:00 2001 From: qualand <34353104+qualand@users.noreply.github.com> Date: Tue, 18 Jun 2024 12:00:24 -0600 Subject: [PATCH 31/85] adjusted heat load capacity factor - removes "adjust" losses within the calculation --- ssc/cmod_trough_physical_iph.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 2b664dde2..86e48d104 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -1992,17 +1992,18 @@ class cm_trough_physical_iph : public compute_module assign("kwh_per_kw", (ssc_number_t)kWh_per_kW); // Calculate percentage of heat load met - std::vector gen = as_vector_double("gen"); + //std::vector gen = as_vector_double("gen"); + std::vector heat_sink_q = as_vector_double("q_dot_to_heat_sink"); double tot_heat_load = 0.0, heat_load = 0.0, load_met = 0.0; double step_s, hl_nondim_val, temp; int temp_int; double sec_per_ts = 3600. / steps_per_hour; - for (int i = 0; i < gen.size(); i++) { + for (int i = 0; i < heat_sink_q.size(); i++) { step_s = (i+1) * sec_per_ts; offtaker_schedule.get_timestep_data(step_s, hl_nondim_val, temp, temp_int); - heat_load = hl_nondim_val * nameplate * 1.e3; + heat_load = hl_nondim_val * nameplate; tot_heat_load += heat_load; - load_met += gen[i] > heat_load ? heat_load : gen[i]; // Only get credit for the load itself + load_met += heat_sink_q[i] > heat_load ? heat_load : heat_sink_q[i]; // Only get credit for the load itself } assign("heat_load_capacity_factor", (ssc_number_t)load_met * 100. / tot_heat_load); } From 619dda5fd46bb17c0b69f271135d07c3f5111d03 Mon Sep 17 00:00:00 2001 From: tyneises Date: Tue, 30 Jul 2024 22:00:33 -0500 Subject: [PATCH 32/85] remove SIMULATION PARAMETER label for timestep_load_fractions --- ssc/cmod_fresnel_physical.cpp | 2 +- ssc/cmod_fresnel_physical_iph.cpp | 2 +- ssc/cmod_mspt_iph.cpp | 2 +- ssc/cmod_tcsmolten_salt.cpp | 2 +- ssc/cmod_trough_physical.cpp | 2 +- ssc/cmod_trough_physical_iph.cpp | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ssc/cmod_fresnel_physical.cpp b/ssc/cmod_fresnel_physical.cpp index 0d760f786..d68f45412 100644 --- a/ssc/cmod_fresnel_physical.cpp +++ b/ssc/cmod_fresnel_physical.cpp @@ -228,7 +228,7 @@ static var_info _cm_vtab_fresnel_physical[] = { { SSC_INPUT, SSC_NUMBER, "disp_inventory_incentive", "Dispatch storage terminal inventory incentive multiplier", "", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "q_rec_standby", "Receiver standby energy consumption", "kWt", "", "tou", "?=9e99", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWe-hr", "", "tou", "?=0.0", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "?", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "?", "", "" }, diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 11b6cf014..bafe23405 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -178,7 +178,7 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { // System Control { SSC_INPUT, SSC_NUMBER, "is_timestep_load_fractions", "Use turbine load fraction for each timestep instead of block dispatch?", "", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "?", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "is_timestep_load_fractions=1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "pb_fixed_par", "Fixed parasitic load - runs at all times", "", "", "Sys_Control", "*", "", "" }, { SSC_INPUT, SSC_ARRAY, "bop_array", "Balance of plant parasitic power fraction", "", "", "Sys_Control", "*", "", "" }, diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index 9864d5bd8..0535b1d66 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -239,7 +239,7 @@ static var_info _cm_vtab_mspt_iph[] = { // System Control { SSC_INPUT, SSC_NUMBER, "is_timestep_load_fractions", "Use turbine load fraction for each timestep instead of block dispatch?", "", "", "System Control", "?=0", "", "SIMULATION_PARAMETER" }, -{ SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "System Control", "?", "", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "System Control", "is_timestep_load_fractions=1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_ARRAY, "f_turb_tou_periods", "Dispatch logic for turbine load fraction", "", "", "System Control", "*", "", ""}, { SSC_INPUT, SSC_MATRIX, "weekday_schedule", "12x24 CSP operation Time-of-Use Weekday schedule", "", "", "System Control", "*", "", ""}, { SSC_INPUT, SSC_MATRIX, "weekend_schedule", "12x24 CSP operation Time-of-Use Weekend schedule", "", "", "System Control", "*", "", ""}, diff --git a/ssc/cmod_tcsmolten_salt.cpp b/ssc/cmod_tcsmolten_salt.cpp index be9b8f5ad..69bad123a 100644 --- a/ssc/cmod_tcsmolten_salt.cpp +++ b/ssc/cmod_tcsmolten_salt.cpp @@ -326,7 +326,7 @@ static var_info _cm_vtab_tcsmolten_salt[] = { { SSC_INPUT, SSC_NUMBER, "bop_par_2", "Balance of plant parasitic power fraction - quadratic coeff", "", "", "System Control", "*", "", "" }, // System Control - { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "System Control", "?", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "System Control", "?", "", ""}, { SSC_INPUT, SSC_ARRAY, "f_turb_tou_periods", "Dispatch logic for turbine load fraction", "", "", "System Control", "*", "", ""}, { SSC_INPUT, SSC_MATRIX, "weekday_schedule", "12x24 CSP operation Time-of-Use Weekday schedule", "", "", "System Control", "*", "", ""}, { SSC_INPUT, SSC_MATRIX, "weekend_schedule", "12x24 CSP operation Time-of-Use Weekend schedule", "", "", "System Control", "*", "", ""}, diff --git a/ssc/cmod_trough_physical.cpp b/ssc/cmod_trough_physical.cpp index bce0eb792..97057d26d 100644 --- a/ssc/cmod_trough_physical.cpp +++ b/ssc/cmod_trough_physical.cpp @@ -277,7 +277,7 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_INPUT, SSC_ARRAY, "dispatch_tod_factors", "TOD factors for periods 1 through 9", "", "We added this array input after SAM 2022.12.21 to replace the functionality of former single value inputs dispatch_factor1 through dispatch_factor9", "Time of Delivery Factors","sim_type=1&ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "?", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "?", "", "" }, { SSC_INPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "sim_type=1&ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_MATRIX, "mp_energy_market_revenue", "Energy market revenue input", "", "Lifetime x 2[Cleared Capacity(MW),Price($/MWh)]", "Revenue", "sim_type=1&csp_financial_model=6&is_dispatch=1", "", "SIMULATION_PARAMETER" }, diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 86e48d104..be125e7f4 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -215,7 +215,7 @@ static var_info _cm_vtab_trough_physical_iph[] = { // Control for *heat* output { SSC_INPUT, SSC_NUMBER, "is_timestep_load_fractions","Use turbine load fraction for each timestep instead of block dispatch?", "", "", "tou", "?=0", "", "" }, - { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Heat sink load fraction for each timestep, alternative to block dispatch", "", "", "tou", "is_timestep_load_fractions=1", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Heat sink load fraction for each timestep, alternative to block dispatch", "", "", "tou", "is_timestep_load_fractions=1", "", "" }, { SSC_INPUT, SSC_MATRIX, "weekday_schedule", "12x24 CSP operation Time-of-Use Weekday schedule", "", "", "tou", "is_timestep_load_fractions=0", "", "" }, { SSC_INPUT, SSC_MATRIX, "weekend_schedule", "12x24 CSP operation Time-of-Use Weekend schedule", "", "", "tou", "is_timestep_load_fractions=0", "", "" }, { SSC_INPUT, SSC_ARRAY, "f_turb_tou_periods", "Time series heat sink load fractions", "", "", "tou", "is_timestep_load_fractions=0", "", "" }, From b2ed64d6baf38974e998bba2dd03966f74142c59 Mon Sep 17 00:00:00 2001 From: tyneises Date: Wed, 31 Jul 2024 14:41:21 -0500 Subject: [PATCH 33/85] have C_CR_DF__PC_MAX__TES_OFF__AUX_OFF turn off solar field upon failure to converge --- tcs/csp_solver_core.cpp | 6 ++++++ tcs/csp_solver_core.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/tcs/csp_solver_core.cpp b/tcs/csp_solver_core.cpp index 96c2db5b3..55b4454ce 100644 --- a/tcs/csp_solver_core.cpp +++ b/tcs/csp_solver_core.cpp @@ -2387,6 +2387,12 @@ void C_csp_solver::C_CR_DF__PC_MAX__TES_FULL__AUX_OFF::handle_solve_error(double is_turn_off_rec_su = true; } +void C_csp_solver::C_CR_DF__PC_MAX__TES_OFF__AUX_OFF::handle_solve_error(double time /*hr*/, bool& is_turn_off_rec_su) +{ + m_is_mode_available = false; + is_turn_off_rec_su = true; +} + void C_csp_solver::C_CR_ON__PC_RM_HI__TES_OFF__AUX_OFF::handle_solve_error(double time /*hr*/, bool& is_turn_off_rec_su) { m_is_HI_SIDE_mode_available = false; diff --git a/tcs/csp_solver_core.h b/tcs/csp_solver_core.h index 46662e14b..501a8fe0e 100644 --- a/tcs/csp_solver_core.h +++ b/tcs/csp_solver_core.h @@ -1864,6 +1864,8 @@ class C_csp_solver C_CR_DF__PC_MAX__TES_OFF__AUX_OFF() : C_operating_mode_core(C_csp_collector_receiver::ON, C_csp_power_cycle::ON, C_MEQ__m_dot_tes::E__TO_PC__PC_MAX, C_MEQ__timestep::E_STEP_FIXED, true, "CR_DF__PC_MAX__TES_OFF__AUX_OFF", QUIETNAN, false) {} + + void handle_solve_error(double time /*hr*/, bool& is_rec_su_unchanged); }; class C_CR_ON__PC_RM_HI__TES_OFF__AUX_OFF : public C_operating_mode_core From bbcef4f1ec17a0a0ac3e1b64557616bd9d304fc0 Mon Sep 17 00:00:00 2001 From: Taylor Brown Date: Tue, 26 Mar 2024 12:56:50 -0600 Subject: [PATCH 34/85] Add startup temp and shutdown temp as user inputs. --- ssc/cmod_trough_physical.cpp | 10 ++++++++-- tcs/csp_solver_trough_collector_receiver.cpp | 11 ++++++++--- tcs/csp_solver_trough_collector_receiver.h | 3 ++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/ssc/cmod_trough_physical.cpp b/ssc/cmod_trough_physical.cpp index 97057d26d..9d790f7e1 100644 --- a/ssc/cmod_trough_physical.cpp +++ b/ssc/cmod_trough_physical.cpp @@ -98,6 +98,9 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_INPUT, SSC_NUMBER, "f_htfmin", "Minimum loop mass flow rate fraction of design", "", "", "solar_field", "use_abs_or_rel_mdot_limit=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "f_htfmax", "Maximum loop mass flow rate fraction of design", "", "", "solar_field", "use_abs_or_rel_mdot_limit=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "T_startup", "Required temperature of the system before the power block can be switched on", "C", "", "solar_field", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "T_shutdown", "Temperature when solar field begins recirculating", "C", "", "solar_field", "*", "", "" }, + { SSC_INPUT, SSC_MATRIX, "field_fl_props", "User defined field fluid property data", "-", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "T_fp", "Freeze protection temperature (heat trace activation temperature)", "none", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "I_bn_des", "Solar irradiation at design", "C", "", "solar_field", "*", "", "" }, @@ -884,8 +887,11 @@ class cm_trough_physical : public compute_module { T_startup_min = T_loop_out_des - 70.0; } - double T_startup = max(T_startup_min, 0.67 * T_loop_in_des + 0.33 * T_loop_out_des); //[C] - c_trough.m_T_startup = T_startup; //[C] The required temperature (converted to K in init) of the system before the power block can be switched on + double T_startup_old = max(T_startup_min, 0.67 * T_loop_in_des + 0.33 * T_loop_out_des); //[C] + //c_trough.m_T_startup = T_startup; //[C] The required temperature (converted to K in init) of the system before the power block can be switched on + + c_trough.m_T_startup = as_double("T_startup"); //[C] The required temperature (converted to K in init) of the system before the power block can be switched on + c_trough.m_T_shutdown = as_double("T_shutdown"); //[C] c_trough.m_use_abs_or_rel_mdot_limit = as_integer("use_abs_or_rel_mdot_limit"); // Use mass flow abs (0) or relative (1) limits c_trough.m_m_dot_htfmin_in = as_double("m_dot_htfmin"); //[kg/s] Minimum loop HTF flow rate diff --git a/tcs/csp_solver_trough_collector_receiver.cpp b/tcs/csp_solver_trough_collector_receiver.cpp index f8fbec9ff..08af2870b 100644 --- a/tcs/csp_solver_trough_collector_receiver.cpp +++ b/tcs/csp_solver_trough_collector_receiver.cpp @@ -355,11 +355,16 @@ void C_csp_trough_collector_receiver::init(const C_csp_collector_receiver::S_csp m_theta_dep *= m_d2r; m_theta_dep = max(m_theta_dep, 1.e-6); m_T_startup += 273.15; //[K] convert from C - m_T_loop_in_des += 273.15; //[K] convert from C - m_T_loop_out_des += 273.15; //[K] convert from C + m_T_loop_in_des += 273.15; //[K] convert from C + m_T_loop_out_des += 273.15; //[K] convert from C m_T_fp += 273.15; //[K] convert from C m_mc_bal_sca *= 3.6e3; //[Wht/K-m] -> [J/K-m] + if (std::isnan(m_T_shutdown)) + m_T_shutdown = m_T_startup; //[K] + else + m_T_shutdown += 273.15; //[K] + /*--- Do any initialization calculations here ---- */ //Allocate space for the loop simulation objects @@ -4103,7 +4108,7 @@ void C_csp_trough_collector_receiver::converged() m_ss_init_complete = true; // Check that, if trough is ON, if outlet temperature at the end of the timestep is colder than the Startup Temperature - if (m_operating_mode == ON && m_T_sys_h_t_end < m_T_startup) + if (m_operating_mode == ON && m_T_sys_h_t_end < m_T_shutdown) { if (m_dni < 1.0) m_operating_mode = OFF; diff --git a/tcs/csp_solver_trough_collector_receiver.h b/tcs/csp_solver_trough_collector_receiver.h index cafdd9223..f2972683d 100644 --- a/tcs/csp_solver_trough_collector_receiver.h +++ b/tcs/csp_solver_trough_collector_receiver.h @@ -342,7 +342,8 @@ class C_csp_trough_collector_receiver : public C_csp_collector_receiver double m_theta_dep = std::numeric_limits::quiet_NaN(); //[deg] deploy angle double m_Row_Distance = std::numeric_limits::quiet_NaN(); //[m] Spacing between rows (centerline to centerline) double m_T_startup = std::numeric_limits::quiet_NaN(); //[C] The required temperature (converted to K in init) of the system before the power block can be switched on - double m_T_loop_in_des = std::numeric_limits::quiet_NaN(); //[C] Design loop inlet temperature, converted to K in init + double m_T_shutdown = std::numeric_limits::quiet_NaN(); //[C] The temperature at which the field stops operating (converted to K in init) + double m_T_loop_in_des = std::numeric_limits::quiet_NaN(); //[C] Design loop inlet temperature, converted to K in init double m_T_loop_out_des = std::numeric_limits::quiet_NaN();//[C] Target loop outlet temperature, converted to K in init int m_Fluid = std::numeric_limits::quiet_NaN(); //[-] Field HTF fluid number From da4fefec495a3730666e92bbeedda5b97ef3961f Mon Sep 17 00:00:00 2001 From: Taylor Brown Date: Thu, 1 Aug 2024 11:51:57 -0600 Subject: [PATCH 35/85] Add checks for missing T_startup or T_shutdown --- ssc/cmod_trough_physical.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/ssc/cmod_trough_physical.cpp b/ssc/cmod_trough_physical.cpp index 9d790f7e1..21d2b0ac9 100644 --- a/ssc/cmod_trough_physical.cpp +++ b/ssc/cmod_trough_physical.cpp @@ -98,8 +98,8 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_INPUT, SSC_NUMBER, "f_htfmin", "Minimum loop mass flow rate fraction of design", "", "", "solar_field", "use_abs_or_rel_mdot_limit=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "f_htfmax", "Maximum loop mass flow rate fraction of design", "", "", "solar_field", "use_abs_or_rel_mdot_limit=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "T_startup", "Required temperature of the system before the power block can be switched on", "C", "", "solar_field", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "T_shutdown", "Temperature when solar field begins recirculating", "C", "", "solar_field", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "T_startup", "Required temperature of the system before the power block can be switched on", "C", "", "solar_field", "", "", "" }, + { SSC_INPUT, SSC_NUMBER, "T_shutdown", "Temperature when solar field begins recirculating", "C", "", "solar_field", "", "", "" }, { SSC_INPUT, SSC_MATRIX, "field_fl_props", "User defined field fluid property data", "-", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "T_fp", "Freeze protection temperature (heat trace activation temperature)", "none", "", "solar_field", "*", "", "" }, @@ -887,11 +887,23 @@ class cm_trough_physical : public compute_module { T_startup_min = T_loop_out_des - 70.0; } + + // Startup and Shutdown temperatures double T_startup_old = max(T_startup_min, 0.67 * T_loop_in_des + 0.33 * T_loop_out_des); //[C] - //c_trough.m_T_startup = T_startup; //[C] The required temperature (converted to K in init) of the system before the power block can be switched on + double T_startup = T_startup_old; + if (is_assigned("T_startup") == true) + { + T_startup = as_double("T_startup"); //[C] The required temperature (converted to K in init) of the system before the power block can be switched on + } + + double T_shutdown = std::numeric_limits::quiet_NaN(); + if (is_assigned("T_shutdown") == true) + { + T_shutdown = as_double("T_shutdown"); + } - c_trough.m_T_startup = as_double("T_startup"); //[C] The required temperature (converted to K in init) of the system before the power block can be switched on - c_trough.m_T_shutdown = as_double("T_shutdown"); //[C] + c_trough.m_T_startup = T_startup; + c_trough.m_T_shutdown = T_shutdown; //[C] c_trough.m_use_abs_or_rel_mdot_limit = as_integer("use_abs_or_rel_mdot_limit"); // Use mass flow abs (0) or relative (1) limits c_trough.m_m_dot_htfmin_in = as_double("m_dot_htfmin"); //[kg/s] Minimum loop HTF flow rate From 0c3271a3a054c2397e70e415beefbf0e99472e1e Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu, 1 Aug 2024 13:14:57 -0600 Subject: [PATCH 36/85] Add optional startup and shutdown temperatures to iph cmod. --- ssc/cmod_trough_physical_iph.cpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index be125e7f4..17fbeb0bb 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -94,6 +94,9 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "f_htfmin", "Minimum loop mass flow rate fraction of design", "", "", "solar_field", "use_abs_or_rel_mdot_limit=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "f_htfmax", "Maximum loop mass flow rate fraction of design", "", "", "solar_field", "use_abs_or_rel_mdot_limit=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "T_startup", "Required temperature of the system before the power block can be switched on", "C", "", "solar_field", "", "", "" }, + { SSC_INPUT, SSC_NUMBER, "T_shutdown", "Temperature when solar field begins recirculating", "C", "", "solar_field", "", "", "" }, + { SSC_INPUT, SSC_MATRIX, "field_fl_props", "User defined field fluid property data", "-", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "T_fp", "Freeze protection temperature (heat trace activation temperature)", "none", "", "solar_field", "*", "", "" }, @@ -758,13 +761,29 @@ class cm_trough_physical_iph : public compute_module c_trough.m_T_loop_in_des = T_loop_in_des; //[C] Design loop inlet temperature, converted to K in init double T_loop_out_des = as_double("T_loop_out"); //[C] Target loop outlet temperature, converted to K in init c_trough.m_T_loop_out_des = T_loop_out_des; //[C] Target loop outlet temperature, converted to K in init + + // Startup and Shutdown temperatures double T_startup_min = T_loop_in_des; if (T_loop_out_des > 600.0) { T_startup_min = T_loop_out_des - 70.0; } - double T_startup = max(T_startup_min, 0.67 * T_loop_in_des + 0.33 * T_loop_out_des); //[C] - c_trough.m_T_startup = T_startup; //[C] The required temperature (converted to K in init) of the system before the power block can be switched on + double T_startup_old = max(T_startup_min, 0.67 * T_loop_in_des + 0.33 * T_loop_out_des); //[C] + + double T_startup = T_startup_old; + if (is_assigned("T_startup") == true) + { + T_startup = as_double("T_startup"); //[C] The required temperature (converted to K in init) of the system before the power block can be switched on + } + + double T_shutdown = std::numeric_limits::quiet_NaN(); + if (is_assigned("T_shutdown") == true) + { + T_shutdown = as_double("T_shutdown"); + } + + c_trough.m_T_startup = T_startup; + c_trough.m_T_shutdown = T_shutdown; //[C] c_trough.m_use_abs_or_rel_mdot_limit = as_integer("use_abs_or_rel_mdot_limit"); // Use mass flow abs (0) or relative (1) limits c_trough.m_m_dot_htfmin_in = as_double("m_dot_htfmin"); //[kg/s] Minimum loop HTF flow rate From ecec61a7a9e8ed3693792666344f2550f8229f0f Mon Sep 17 00:00:00 2001 From: tyneises Date: Fri, 2 Aug 2024 10:24:49 -0500 Subject: [PATCH 37/85] change from ? required to empty --- ssc/cmod_fresnel_physical.cpp | 2 +- ssc/cmod_tcsmolten_salt.cpp | 2 +- ssc/cmod_trough_physical.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ssc/cmod_fresnel_physical.cpp b/ssc/cmod_fresnel_physical.cpp index d68f45412..d789cf444 100644 --- a/ssc/cmod_fresnel_physical.cpp +++ b/ssc/cmod_fresnel_physical.cpp @@ -228,7 +228,7 @@ static var_info _cm_vtab_fresnel_physical[] = { { SSC_INPUT, SSC_NUMBER, "disp_inventory_incentive", "Dispatch storage terminal inventory incentive multiplier", "", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "q_rec_standby", "Receiver standby energy consumption", "kWt", "", "tou", "?=9e99", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWe-hr", "", "tou", "?=0.0", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "?", "", "" }, + { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "", "", "" }, diff --git a/ssc/cmod_tcsmolten_salt.cpp b/ssc/cmod_tcsmolten_salt.cpp index 69bad123a..50633c700 100644 --- a/ssc/cmod_tcsmolten_salt.cpp +++ b/ssc/cmod_tcsmolten_salt.cpp @@ -326,7 +326,7 @@ static var_info _cm_vtab_tcsmolten_salt[] = { { SSC_INPUT, SSC_NUMBER, "bop_par_2", "Balance of plant parasitic power fraction - quadratic coeff", "", "", "System Control", "*", "", "" }, // System Control - { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "System Control", "?", "", ""}, + { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "System Control", "", "", ""}, { SSC_INPUT, SSC_ARRAY, "f_turb_tou_periods", "Dispatch logic for turbine load fraction", "", "", "System Control", "*", "", ""}, { SSC_INPUT, SSC_MATRIX, "weekday_schedule", "12x24 CSP operation Time-of-Use Weekday schedule", "", "", "System Control", "*", "", ""}, { SSC_INPUT, SSC_MATRIX, "weekend_schedule", "12x24 CSP operation Time-of-Use Weekend schedule", "", "", "System Control", "*", "", ""}, diff --git a/ssc/cmod_trough_physical.cpp b/ssc/cmod_trough_physical.cpp index 21d2b0ac9..08584bc23 100644 --- a/ssc/cmod_trough_physical.cpp +++ b/ssc/cmod_trough_physical.cpp @@ -280,7 +280,7 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_INPUT, SSC_ARRAY, "dispatch_tod_factors", "TOD factors for periods 1 through 9", "", "We added this array input after SAM 2022.12.21 to replace the functionality of former single value inputs dispatch_factor1 through dispatch_factor9", "Time of Delivery Factors","sim_type=1&ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "?", "", "" }, + { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "", "", "" }, { SSC_INPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "sim_type=1&ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_MATRIX, "mp_energy_market_revenue", "Energy market revenue input", "", "Lifetime x 2[Cleared Capacity(MW),Price($/MWh)]", "Revenue", "sim_type=1&csp_financial_model=6&is_dispatch=1", "", "SIMULATION_PARAMETER" }, From 48f89fde65b7c59bf77332898807855c8cede7a3 Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed, 11 Sep 2024 09:34:41 -0600 Subject: [PATCH 38/85] Fix bug from merge. --- ssc/common.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ssc/common.cpp b/ssc/common.cpp index 2be8b2953..9e6a6dffc 100644 --- a/ssc/common.cpp +++ b/ssc/common.cpp @@ -1015,6 +1015,7 @@ var_info vtab_utility_rate_common[] = { { SSC_INPUT, SSC_ARRAY, "rate_escalation", "Annual electricity rate escalation", "%/year", "", "Electricity Rates", "?=0", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "ur_metering_option", "Metering options", "0=net energy metering,1=net energy metering with $ credits,2=net billing,3=net billing with carryover to next month,4=buy all - sell all", // continued on next row + "Net metering monthly excess","Electricity Rates", "?=0", "INTEGER,MIN=0,MAX=4", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "ur_nm_yearend_sell_rate", "Net metering true-up credit sell rate", "$/kWh", "", "Electricity Rates", "?=0.0", "", "" }, { SSC_INPUT, SSC_NUMBER, "ur_nm_credit_month", "Month of year end payout (true-up)", "mn", "", "Electricity Rates", "?=11", "INTEGER,MIN=0,MAX=11", "" }, { SSC_INPUT, SSC_NUMBER, "ur_nm_credit_rollover", "Apply net metering true-up credits to future bills", "0/1", "0=disable,1=enable", "Electricity Rates", "?=0", "INTEGER,MIN=0,MAX=1", "" }, From 0d98ff811437e1cca6e3b08e5c896d930b5293bc Mon Sep 17 00:00:00 2001 From: Taylor Brown Date: Thu, 12 Sep 2024 10:08:01 -0600 Subject: [PATCH 39/85] Squashed commit of the following: commit f980d06bba3d3b5427324cef626fd25af83e4a10 Merge: 2fbda919f e8f8d3eab Author: Taylor Brown Date: Mon Sep 9 20:36:20 2024 -0600 Merge branch 'nt_suntrap_uptodate' into tes_pb_nt commit e8f8d3eab4f31fff0f793a6e88b4f3b6e8ecc00a Author: Taylor Brown Date: Mon Sep 9 13:33:55 2024 -0600 Add T_startup and shutdown to iph trough cmod. commit c5ec78660d0537bb9b1dff00f3fde342e28f9e23 Author: Taylor Brown Date: Wed Sep 4 16:52:44 2024 -0600 Update csp subcomponent test inputs and vartable require_if. commit 2779bf7d7dde00700684ce124fd1882c92a2641d Merge: 21f6cd01f 274560a9e Author: Taylor Brown Date: Wed Sep 4 15:56:52 2024 -0600 Merge branch 'develop' into nt_suntrap_uptodate commit 21f6cd01f20d259f547633d3abb9421af7937a86 Author: Taylor Brown Date: Wed Sep 4 13:28:00 2024 -0600 Fix required vartable variables for ssc tests. commit 2fbda919f981affdae0c930908a9448832a8fede Author: Taylor Brown Date: Fri Aug 30 11:10:59 2024 -0600 Combine NT and packed bed subtimestep variable commit 892d5cd252b83393144a14c9eae720cc2f39886a Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Aug 29 14:55:26 2024 -0600 Add startup/shutdown temps to IPH trough cmod. commit 7c6f40a090113320b168c64812107657b1cd4e37 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Aug 29 13:06:47 2024 -0600 Fix MSPT two tank bug. Fix trough CSP formatting bug from merge. commit 0d71c7fa10e5cf1ad5452bae495ff3bb04a7aad6 Merge: 539564b39 5b0ce64f9 Author: Taylor Brown Date: Thu Aug 29 11:17:26 2024 -0600 Merge branch 'nt_suntrap' into tes_pb_nt commit 5b0ce64f9aa6b06ab412eb07002efd05e2af6236 Author: Taylor Brown Date: Thu Aug 29 11:16:52 2024 -0600 Isolate custom pressure losses to only two tank tes. commit 539564b390e83fb19dfb620d1587df725aa6c895 Merge: 880607ae9 274560a9e Author: Taylor Brown Date: Thu Aug 29 09:13:49 2024 -0600 Merge branch 'develop' into tes_pb_nt commit 880607ae92b8049b69f3a59e373f7dc35f1d5e8b Merge: 7e6ef0fa7 36e7587ee Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 28 16:37:32 2024 -0600 Merge branch 'nt_suntrap' into tes_pb_nt commit 7e6ef0fa78b48f1377b6868b3e0bdb7a496fa1d9 Merge: d09c280ad f2d6aedd3 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 28 11:11:33 2024 -0600 Merge branch 'patch' into tes_pb_nt commit 36e7587eef07b26755556b3458cf3c800544a669 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 28 11:03:53 2024 -0600 Remove unused member variables from NT TES. commit 805763fbc9a5af59ee04e19f12d73368677d1618 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 28 10:23:02 2024 -0600 Remove unused NT TES variables. commit 9a991fdf17888b04c3429ed48871ff4ead4a1da7 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 28 10:00:38 2024 -0600 Remove hot and cold individual error calc outputs from NT TES. commit d09c280ad885131cab431839298d97d22450ad7e Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Aug 26 15:28:45 2024 -0600 Update htf properties during each subtimestep for packed bed tes commit 59f3310febc6a7e67ac60b0f2db25495fca98b31 Author: Taylor Brown Date: Thu Aug 22 11:09:15 2024 -0600 Add pumping power calc to packed bed tes commit e8160d90eba3cb40801178159f4e00252f947dfc Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Aug 19 11:12:42 2024 -0600 Make size of subtimestep consistent, regardless of timestep size. commit 17d2a500d823e19958ebbe9d7af43a1b4f88a568 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Aug 19 09:07:44 2024 -0600 Separate charge/discharge temperature estimate temperature with allowable charge temp. commit 15351cc877fa070c5084fbed4778fe1b3d8db88f Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 14 17:05:09 2024 -0600 Add average temperature gradient results to cmod. Remove full temperature gradient output. commit 67b162d533da55a76db71bca4fc4a1a960b96c66 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 14 11:45:53 2024 -0600 Fix thermal capacity reported output for packed bed. commit cfd202d2c2b9dbe94609509b37037ccf318d7f5d Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 14 11:22:34 2024 -0600 Add htf thermal mass to sizing and energy estimates. commit 3684c31f08d8481d57ee6eeec433b851875c71b1 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue Aug 13 11:48:12 2024 -0600 Add charge inlet temperature check. Adjust charge/discharge energy estimate. Change outlet gradient to K commit 14b5760691fd964a6c79eab3ae6db1f676b391bd Author: Taylor Brown Date: Mon Aug 12 15:23:10 2024 -0600 Add temporary temperature gradient output. commit c9479bc6277fda1071769a428e93c6e5328dd837 Author: Taylor Brown Date: Mon Aug 12 14:32:28 2024 -0600 Fix packed bed result reporting bug. commit 6dc7c373faaecc49caf5b15943c89c0fd8cc0c2d Author: Taylor Brown Date: Sun Aug 11 12:12:42 2024 -0600 Add packed bed outputs. commit 20b8fbb08b0d10b7c9e21679f6ced68ab3b6bb39 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Aug 8 16:57:59 2024 -0600 Add packed bed tes to trough CSP cmod. Update energy estimates and initial temp calculations. commit f0c146e12c47be62c55a778f82eb42d67003badd Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Aug 8 10:23:47 2024 -0600 Rename packed bed cmod inputs. commit a013cd2e99258882a6518af1229fd6fe8f6b7704 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 7 16:56:14 2024 -0600 Include oversize factor in sizing calculation. commit c8ad72d02ca9f9ce9ae29158e7989f1b4d33c0d4 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 7 16:53:30 2024 -0600 Implement packed bed sizing. commit 9672907c3d349e2ab09b672151ca99ccd110a170 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 7 15:05:10 2024 -0600 Remove unused NT methods. commit 4b1596ad3d4f1a9a1da8fc4214c07efc1b2c3535 Author: Taylor Brown Date: Tue Aug 6 14:18:34 2024 -0600 Implement charge/discharge estimate equations. commit fe9e7823577572fe50d0351f29effd40cfd57923 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue Aug 6 08:38:36 2024 -0600 Rename packed bed tes commit b58e7c130343cb573d9ec44b0f9cdf98f3c371f1 Merge: 449407850 d43113ceb Author: Taylor Brown Date: Thu Jul 25 10:56:13 2024 -0600 Merge branch 'nt_suntrap' into nt_suntrap_startup commit d43113cebe457a1f15d76131c84da5f16e500b20 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri Jun 28 16:29:57 2024 -0600 Add helpful outputs to csp subcomponent. commit 0df20f673c3fa1da8dff5d91ace986ab963d39b3 Author: Taylor Brown Date: Thu Jun 27 14:15:35 2024 -0600 Add hot tank mass percent to output. Add tes height to cmod. commit e721c499e353fa2eaa01e01875efe4102a74bceb Merge: 14c0f6aaf 06750a976 Author: Taylor Brown Date: Tue Jun 25 15:41:04 2024 -0600 Merge branch 'csp_hourly_schedule' into nt_suntrap commit 06750a9767a2881cd6060254b5f0838c35907420 Author: Taylor Brown Date: Tue Jun 25 15:19:00 2024 -0600 Add hourly turbine output option to csp MSPT. commit 3a7a18b8417b29036af19b1c019934ffa4802b29 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue Jun 25 11:06:04 2024 -0600 Add hourly timestep fraction option to CSP linear fresnel commit a09f30aa660c8151bed35fa37e299abe7971729b Author: Taylor Brown Date: Mon Jun 24 10:40:25 2024 -0600 Update timestep_load_fractions to follow IPH process commit 41d167afa9303bf80afe977f904f560547973b49 Author: Taylor Brown Date: Mon Jun 24 09:56:25 2024 -0600 Add bool input for hourly timestep load fractions for csp trough. commit 14c0f6aaf9fa0757496f9339281aeebf23d8f9f4 Author: Taylor Brown Date: Wed Jun 19 11:19:41 2024 -0600 Fix merge bugs. commit e5902c4fa9c213f3eedf42d88b63efde6671b897 Merge: b025005ac ec251419d Author: Taylor Brown Date: Tue Jun 18 11:33:33 2024 -0600 Merge branch 'develop' into nt_suntrap commit f76a6a14da33df0ce14039a600a1e07128084e12 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Jun 17 15:50:19 2024 -0600 Add particle properties to cmod. Fix bug with superficial velocity and cp. commit f97a95d5f49476657c790e41ea2f3dc5146c698d Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri Jun 14 13:42:47 2024 -0600 Thread particle properties inputs through cmod commit 83dd21971ecbc49d209f5fec3087179a2c9c807a Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri Jun 14 13:08:40 2024 -0600 Add height as input to particle model commit bfedef1aceaf9188e3f61bc2a92a7b77a30db4f6 Author: Taylor Brown Date: Fri Jun 14 08:16:58 2024 -0600 Thread tes outputs out of particle class. commit fc448bbc6c48d5c8d214ee21ae12469d2d6657f5 Author: Taylor Brown Date: Thu Jun 13 13:09:43 2024 -0600 Add tes_pump_coef from cmod commit c0a8ad8918047f925439b58363e49d8b89f62924 Author: Taylor Brown Date: Thu Jun 13 13:08:06 2024 -0600 Finish adding outputs to Charge and Discharge calc. commit cd1b128cd95a9d688465d6225c681800c9f4d1d2 Author: Taylor Brown Date: Wed Jun 12 08:31:49 2024 -0600 Fix temperature gradient unit bug. commit 5a88a630be91517e499650c34aa42868d7415b45 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Jun 10 15:24:29 2024 -0600 Add initial temperature gradient option commit b025005ac1c4859a2cd04b218b239d7f482163da Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Jun 10 09:14:51 2024 -0600 Calculate 'main tank' volume using hot density. commit 4af2cf3fbe2fac662e0967fa36e90846cae773a2 Author: Taylor Brown Date: Wed Jun 5 09:31:47 2024 -0600 Add temperature gradient to output. commit d03536192ed88168697e8cdd6671e0b162042fb8 Author: Taylor Brown Date: Mon Jun 3 20:17:02 2024 -0600 Remove analytical calculations from isolated TES cmod. commit 5667e30021f827cdf9afcc8e9a216ff449241924 Author: Taylor Brown Date: Mon Jun 3 20:14:17 2024 -0600 Add tank wall density modifier to account for insulation. commit 7ed61aa8242df8925c837261b1669decc98d5602 Author: Taylor Brown Date: Mon Jun 3 19:21:09 2024 -0600 Add simple discharge cycle. commit 0136e7b8d47b61ee3bde92db9d1c87dd9ce037c5 Author: Taylor Brown Date: Mon Jun 3 12:57:02 2024 -0600 Change NT sizing to special function based on cold density. commit f873eda6888e0601a87971d5fa6f806fed6b47aa Author: Taylor Brown Date: Mon Jun 3 12:03:40 2024 -0600 Add expansion tank calculations. commit 9227784ae5088beda72c2d1150bb730c0e498aba Author: Taylor Brown Date: Mon Jun 3 10:26:35 2024 -0600 Update active fluid mass calculation, to fill with cold. Add nominal wall volume and mass members. commit c33407dc93104c9e9bfb54e8cdf8fef93601b0e3 Author: Taylor Brown Date: Sun Jun 2 09:36:42 2024 -0600 Update TES csp subcomponent to allow height or diameter sizing. commit 70b388ec18315b572d88eea9841e56513a3e5700 Author: Taylor Brown Date: Fri May 31 14:32:22 2024 -0600 Remove all thermocline inputs. Get simple charge case running. commit b54b194c7a466a4f6f959f477477f8363a9932ba Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue May 28 15:48:49 2024 -0600 Set TES fluid mass using cold density, rather than avg. commit 11da5de3c7a7c04ed9cbfde1b656f792644bcb54 Author: Taylor Brown Date: Tue May 21 18:28:23 2024 -0600 Adjust core calculation to fix wall cp issue. commit 7c03e71382f5dba840860677e5a8a4d9b5eb7e2f Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue May 21 10:57:39 2024 -0600 Remove unused variables in thermocline constructor. Add new thermocline model to csp subcomponent cmod. commit c50bf16600a17b1a5c4dfa1d4e62e76ff260a381 Author: Taylor Brown Date: Mon May 20 09:13:27 2024 -0600 Begin creating class for particle thermocline TES commit 85c36a46d92e7fecb2a599614610ea9bc6c07377 Merge: 5e4136d75 ec424fb2d Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed May 15 11:12:17 2024 -0600 Merge branch 'nt_suntrap' into tes_particle_therm commit ec424fb2dbdcc6092bceaea765b9fb3b7433dfee Author: Taylor Brown Date: Tue May 14 10:37:05 2024 -0600 Begin investigating wall cp issue. commit 81a7d7ac7ad491fd4293873e64dab8186aa245fb Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon May 13 09:04:08 2024 -0600 Clean code. commit d0c6cb73d1251cc5a7dd7eab788ece278d5d7224 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon May 13 08:49:10 2024 -0600 Move error calculations to inside iteration. commit b3f48ff3e0204c41c243561d4bed2bca63384664 Author: Taylor Brown Date: Sun May 12 16:32:54 2024 -0600 Fix wall error calculation. Add energy balance error adjusted for wall and leakage. commit 0f31db453dae43aea4cd5c233f1853ef2fe53b7b Author: Taylor Brown Date: Sun May 12 11:13:23 2024 -0600 Change energy balance error percent to divide by design charge power. commit 7270baf5516835a5d4177471faedcd0671e75b43 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri May 10 10:58:07 2024 -0600 Add wall temperature assumption error to outputs. commit faa698dccd451d584b8023a540df6bb83f996522 Author: Taylor Brown Date: Wed May 8 15:23:16 2024 -0600 Add initial temps to storage isolated cmod. Add error output accounting for leakage assumption. commit 34b1ec1910b2bd80c63eaf0a3f43d77af9232251 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue May 7 11:11:14 2024 -0600 Add TES energy balance errors. commit d57e4c34b1d2583afc58ffe9d819c620a53385d7 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri May 3 14:55:35 2024 -0600 Add energy balance error to cmod outputs. commit 17fea126b54793d7f51fc8fd41851e7beea48d34 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Apr 8 10:46:57 2024 -0600 Remove debug code. commit 449407850798d7649aec1f68136d79faddd5ab3f Merge: d89e99cc4 ad55aea77 Author: Taylor Brown Date: Thu Mar 28 13:20:48 2024 -0600 Merge branch 'trough_startup_temp' into nt_suntrap_startup commit d89e99cc4744f3eb6a7f4c8d076827df2ba4661f Merge: 284b73d5d 990f35172 Author: Taylor Brown Date: Thu Mar 28 12:55:01 2024 -0600 Merge branch 'patch' into nt_suntrap_startup commit ad55aea77c0d2f9ca2b4e91029afb0859b0192f9 Author: Taylor Brown Date: Tue Mar 26 12:56:50 2024 -0600 Add startup temp and shutdown temp as user inputs. commit 284b73d5de422469302e31e0e05d6422aeae8071 Author: Taylor Brown Date: Tue Mar 26 08:29:58 2024 -0600 Remove serial tanks option for heattrap tes commit d2916633474a88f5aca627d5df0866a892297310 Author: Taylor Brown Date: Sat Mar 16 15:55:58 2024 -0600 Add option to TES to specify diameter, rather than height. commit 851d4a76d002f9fb4946f3e8084471fadfc26b99 Merge: c5eaca6fd 028722445 Author: Taylor Brown Date: Mon Mar 11 11:26:46 2024 -0600 Merge branch 'patch' into nt_suntrap commit c5eaca6fd1e5def708f353555053523f59aab0ff Author: Taylor Brown Date: Tue Feb 20 17:28:04 2024 -0700 Fix Ave Temp bug commit 9d6e8bde072e9344d4564d3828b308cfee25dc37 Author: Taylor Brown Date: Tue Feb 20 16:07:40 2024 -0700 Add TES error percent to outputs. commit 60d75fb4903fc3a2581aa1d30eafeb23c81d513d Author: Taylor Brown Date: Sun Feb 18 11:03:51 2024 -0700 Add energy balance error to annual output commit 6f699125eb46df2bc124ac677de7708ff4f567d3 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Feb 5 15:53:51 2024 -0700 Begin calculating TES energy balance error. commit 1918d0b80cfc1550dfd1b71bd0c86c8efa4f24de Author: Taylor Brown Date: Thu Feb 1 15:16:12 2024 -0700 Add output to TES subcomponent model. Add distributed commercial. commit fb2064e35d2936e2e473633668ec89655fce9976 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue Jan 30 15:27:57 2024 -0700 Add analytical TES balance with leakage to subcomponent model. commit 39b9ab89170512f7885dc17e3e1e5c3545cfdfce Author: Taylor Brown Date: Thu Jan 18 13:48:12 2024 -0700 Add polynomial loss to subcomponent model. Update variable names. commit 0c4e1c9a296c00e43481fdb440d51f9d7ae6b974 Author: Taylor Brown Date: Wed Jan 17 11:50:12 2024 -0700 Finish adding piston leakage losses commit 39bbd5b1ce32b9257cd74bde52624639a02d0135 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Jan 11 13:45:06 2024 -0700 Add comment commit 8b1b48ac44373df14d1468934f68d392efea1e8c Author: Taylor Brown Date: Wed Jan 10 12:37:22 2024 -0700 Begin including piston losses. commit 3b4654048700da4fb1db8ede6bf6e1df7415232a Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue Jan 9 11:27:44 2024 -0700 Expand testing for NT heat trap TES. commit a7af1fe0daae35e4552801900b902f72663a39a8 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Jan 8 14:08:24 2024 -0700 Update subcomponent to test NT tes system commit b6e9bad806a07b477f4122dd46f27a1b6919686f Author: Taylor Brown Date: Fri Jan 5 19:17:19 2024 -0700 Fix dynamic volume calculation. Fix inlet temperature weight calculation. commit d4b8e1c93b178f780de9c0bde1055b4d9f5dd59b Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Jan 4 14:26:04 2024 -0700 Remove unused functions commit d89b2e1fbb185d5871bd77663f79b647bf38a5c1 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Jan 4 13:55:25 2024 -0700 Fix energy balance iterated previous temperature. commit 0d495ebeec376759951ecc7c05fcd4f3882c8591 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Jan 4 13:10:38 2024 -0700 Fix mass flow issue for tank energy balance. Add tank surface area to annual output. commit 206b946a4f32106a3159855e84a4503cd7d0363d Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue Jan 2 15:53:56 2024 -0700 Add NT energy balance time step to UI commit f97944e9d594a7fd2ee6c1cb323ed2c1d76aea55 Author: Taylor Brown Date: Tue Jan 2 09:27:18 2024 -0700 Add NT specific TES outputs commit c47373c74222e4f225c0348bdaa8745f635b8945 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Dec 20 15:14:37 2023 -0700 Add total volume output for two tank commit c5acfd58eab553a7b2656a2dd10cc85527503e23 Author: Taylor Brown Date: Tue Dec 19 15:13:40 2023 -0700 Add helpful annual outputs to NT tank commit a1269543760bd92db39e78995ac2a8bf8af1a894 Author: Taylor Brown Date: Tue Dec 19 10:54:23 2023 -0700 Add Norwich Tank parameters to cmod commit a90518158615bc47af6133760a3bb8b8c0ec2b53 Author: Taylor Brown Date: Tue Dec 19 09:04:50 2023 -0700 Add NT TES wall thickness to energy balance. commit bf4e4adcf1750f3e35e16fd4ec4832383ec0cdf0 Author: Taylor Brown Date: Sun Dec 17 17:08:05 2023 -0700 Fix bug with energy balance mdot difference. Results are close to original with no wall thickness, however the TES heat loss is too low at certain times. commit 97b047b4af4a578fad7d08399fce1b2861614758 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri Dec 15 16:08:50 2023 -0700 Modify thermal storage to account for storage mass commit e7018360e096afc1be04f7b386319fc6ea18744d Author: Taylor Brown Date: Thu Dec 14 22:07:37 2023 -0700 Move storage classes out of shared solver_tes_core commit 1ff74dc1f9700d9c6a34aea6ff87bda7086e9617 Author: Taylor Brown Date: Thu Dec 14 21:20:40 2023 -0700 Add missing implementations of NT tes functions. commit bb5bb87247d58aa228f954713d5d3a54c5b020a2 Author: Taylor Brown Date: Thu Dec 14 10:57:44 2023 -0700 Move fields from two tank model to parent C_csp_tes class. Add files to CMake commit 87f4807f691bf0115bad64eb7462d721ad82ad68 Author: Taylor Brown Date: Wed Dec 13 18:47:18 2023 -0700 Add 'core' tes class to isolate shared csp tes funcitons. Continue developing NT Heat Trap model commit b4ce8e74716c24f936a02e204a871358966da7f4 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Dec 11 11:13:24 2023 -0700 Continue to develop NT storage tank commit e470db0cf09f87e915dfd1bcae0ac22a1e495222 Author: Taylor Brown Date: Tue Dec 5 10:51:53 2023 -0700 Create new HeatTrap TES model skeleton. --- .../cmod_tcstrough_physical_csp_solver.cpp | 2 + code_attic/cmod_trough_physical_iph_old.cpp | 2 + ssc/cmod_csp_subcomponent.cpp | 456 +- ssc/cmod_etes_electric_resistance.cpp | 6 +- ssc/cmod_etes_ptes.cpp | 12 +- ssc/cmod_fresnel_physical.cpp | 32 +- ssc/cmod_fresnel_physical_iph.cpp | 11 +- ssc/cmod_mspt_iph.cpp | 6 +- ssc/cmod_tcsmolten_salt.cpp | 6 +- ssc/cmod_trough_physical.cpp | 406 +- ssc/cmod_trough_physical_iph.cpp | 37 +- tcs/CMakeLists.txt | 6 + tcs/csp_solver_NTHeatTrap_tes.cpp | 2094 +++++++++ tcs/csp_solver_NTHeatTrap_tes.h | 443 ++ tcs/csp_solver_packedbed_tes.cpp | 1004 +++++ tcs/csp_solver_packedbed_tes.h | 239 + tcs/csp_solver_tes_core.cpp | 362 ++ tcs/csp_solver_tes_core.h | 72 + tcs/csp_solver_trough_collector_receiver.cpp | 4 + tcs/csp_solver_two_tank_tes.cpp | 3902 ++++++++--------- tcs/csp_solver_two_tank_tes.h | 641 ++- test/input_cases/trough_physical_defaults.h | 2 +- .../trough_physical_iph_defaults.h | 2 +- test/shared_test/lib_csp_tes_test.cpp | 4 +- 24 files changed, 7234 insertions(+), 2517 deletions(-) create mode 100644 tcs/csp_solver_NTHeatTrap_tes.cpp create mode 100644 tcs/csp_solver_NTHeatTrap_tes.h create mode 100644 tcs/csp_solver_packedbed_tes.cpp create mode 100644 tcs/csp_solver_packedbed_tes.h create mode 100644 tcs/csp_solver_tes_core.cpp create mode 100644 tcs/csp_solver_tes_core.h diff --git a/code_attic/cmod_tcstrough_physical_csp_solver.cpp b/code_attic/cmod_tcstrough_physical_csp_solver.cpp index 3efbaf9a9..11ffec6e5 100644 --- a/code_attic/cmod_tcstrough_physical_csp_solver.cpp +++ b/code_attic/cmod_tcstrough_physical_csp_solver.cpp @@ -780,7 +780,9 @@ class cm_trough_physical_csp_solver : public compute_module as_double("W_pb_design") / as_double("eta_ref"), //[MWt] as_double("solar_mult"), //[-] 0.0, //[MWht] + true, //Use fixed tank height as_double("h_tank"), //[m] + 0.0, // No input diameter (it is calculated) as_double("u_tank"), //[W/m^2-K] as_integer("tank_pairs"), //[-] as_double("hot_tank_Thtr"), //[C] diff --git a/code_attic/cmod_trough_physical_iph_old.cpp b/code_attic/cmod_trough_physical_iph_old.cpp index f4172b166..fdf3241f6 100644 --- a/code_attic/cmod_trough_physical_iph_old.cpp +++ b/code_attic/cmod_trough_physical_iph_old.cpp @@ -724,7 +724,9 @@ class cm_trough_physical_process_heat : public compute_module c_heat_sink.ms_params.m_q_dot_des / 1.0, //[MWt] as_double("solar_mult"), //[-] c_heat_sink.ms_params.m_q_dot_des / 1.0 * as_double("tshours"), //[hr] + true, as_double("h_tank"), //[m] + 0.0, as_double("u_tank"), //[W/m^2-K] as_integer("tank_pairs"), //[-] as_double("hot_tank_Thtr"), //[C] diff --git a/ssc/cmod_csp_subcomponent.cpp b/ssc/cmod_csp_subcomponent.cpp index 12c0057a1..30e39aec0 100644 --- a/ssc/cmod_csp_subcomponent.cpp +++ b/ssc/cmod_csp_subcomponent.cpp @@ -33,6 +33,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "core.h" #include "csp_solver_two_tank_tes.h" +#include "csp_solver_NTHeatTrap_tes.h" +#include "csp_solver_packedbed_tes.h" // Forward declarations double C_to_K(double T); @@ -48,6 +50,9 @@ static var_info _cm_vtab_csp_subcomponent[] = { { SSC_INPUT, SSC_ARRAY, "hot_tank_bypassed", "Is mass flow from source going straight to cold tank?", "-", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_ARRAY, "T_src_out", "Temperature from heat source", "C", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_ARRAY, "T_sink_out", "Temperature from heat sink or power block", "C", "", "TES", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "T_tank_hot_ini", "Temperature of fluid in hot tank at beginning of step", "C", "", "TES", "", "", "" }, + { SSC_INPUT, SSC_NUMBER, "T_tank_cold_ini", "Temperature of fluid in cold tank at beginning of step", "C", "", "TES", "", "", "" }, + // TES { SSC_INPUT, SSC_NUMBER, "Fluid", "Field HTF fluid ID number", "-", "", "solar_field", "*", "", "" }, @@ -58,7 +63,9 @@ static var_info _cm_vtab_csp_subcomponent[] = { { SSC_INPUT, SSC_NUMBER, "eta_ref", "Power cycle efficiency at design", "none", "", "powerblock", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "solar_mult", "Actual solar multiple of system", "-", "", "system", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "tshours", "Equivalent full-load thermal storage hours", "hr", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "h_tank", "Total height of tank (height of HTF when tank is full", "m", "", "TES", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "is_h_tank_fixed", "[1] Use fixed height (calculate diameter) [0] Use fixed diameter", "-", "", "TES", "?=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "h_tank_in", "Total height of tank (height of HTF when tank is full", "m", "", "TES", "is_h_tank_fixed=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "d_tank_in", "Tank diameter input", "m", "", "TES", "is_h_tank_fixed=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "tank_pairs", "Number of equivalent tank pairs", "-", "", "TES", "*", "INTEGER", "" }, { SSC_INPUT, SSC_NUMBER, "hot_tank_Thtr", "Minimum allowable hot tank HTF temp", "C", "", "TES", "*", "", "" }, @@ -87,11 +94,67 @@ static var_info _cm_vtab_csp_subcomponent[] = { { SSC_INPUT, SSC_NUMBER, "HDR_rough", "Header pipe roughness", "m", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "DP_SGS", "Pressure drop within the steam generator", "bar", "", "controller", "*", "", "" }, + // Added Inputs for NT System + { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), HeatTrap Single Tank (1), Packed Bed (2)", "-", "", "TES", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_tank_thick", "Tank wall thickness (used for Norwich HeatTrap)", "m", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_tank_cp", "Tank wall cp (used for Norwich HeatTrap)", "kJ/kg-K", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_tank_dens", "Tank wall thickness (used for Norwich HeatTrap)", "kg/m3", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_NT_nstep", "Number of time steps for energy balance (used for Norwich HeatTrap)", "", "", "TES", "?=1", "", "" }, + { SSC_INPUT, SSC_ARRAY, "tes_NT_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_tank_insul_percent", "Percent additional wall mass due to insulation", "%", "", "TES", "?=0", "", "" }, + + + // Packed bed Specific Inputs + { SSC_INPUT, SSC_NUMBER, "tes_pb_n_xsteps", "Number of spatial segments", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_n_tsteps", "Number of subtimesteps", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_ARRAY, "tes_pb_T_grad_ini", "TES Temperature gradient at beginning of timestep", "C", "", "TES", "?=[-274]", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_k_eff", "TES packed bed effective conductivity", "W/m K", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_void_frac", "TES particle packed bed void fraction", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_dens_solid", "TES particle density", "kg/m3", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_cp_solid", "TES particle specific heat", "J/kg K", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_hot_delta", "Max allowable decrease in hot discharge temp", "C", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_cold_delta", "Max allowable increase in cold discharge temp", "C", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_charge_min", "Min charge temp", "C", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_size_type", "(0) use fixed diameter, (1) use fixed height, (2) use preset inputs", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_f_oversize", "Packed bed oversize factor", "", "", "TES", "tes_type=2", "", "" }, + + // Outputs { SSC_OUTPUT, SSC_ARRAY, "T_src_in", "Temperature to heat source", "C", "", "TES", "*", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "T_sink_in", "Temperature to heat sink or power block", "C", "", "TES", "*", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_tank_cold", "Temperature of cold tank (average)", "C", "", "TES", "*", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_tank_hot", "Temperature of hot tank (average)", "C", "", "TES", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_tank_cold", "Temperature of cold tank (end of timestep)", "C", "", "TES", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_tank_hot", "Temperature of hot tank (end of timestep)", "C", "", "TES", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "tes_diameter", "TES Diameter", "m", "", "TES", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "tes_radius", "TES Radius", "m", "", "TES", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "tes_height", "TES Height", "m", "", "TES", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "hot_tank_vol_frac", "Hot tank volume fraction of total", "", "", "TES", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "q_dot_dc_to_htf", "Thermal power to HTF from storage", "MWt", "", "TES", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "q_dot_ch_from_htf", "Thermal power from the HTF to storage", "MWt", "", "TES", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "q_dc_to_htf", "Thermal energy to HTF from storage", "MJt", "", "TES", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "q_ch_from_htf", "Thermal energy from the HTF to storage", "MJt", "", "TES", "*", "", "" }, + + + + { SSC_OUTPUT, SSC_ARRAY, "tes_error", "TES energy balance error", "MW", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error_percent", "TES energy balance error percent", "%", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "piston_loc", "Piston Location (distance from left cold side)", "m", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "piston_frac", "Piston Fraction (distance from left cold side)", "", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_leak_error", "TES energy balance error due to leakage assumption", "MWt", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_E_hot", "TES hot side internal energy", "MJ", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_E_cold", "TES cold side internal energy", "MJ", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_wall_error", "TES energy balance error due to wall temperature assumption", "MWt", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error_corrected", "TES energy balance error, accounting for wall and temperature assumption error", "MWt", "", "TES", "tes_type=1", "", "" }, + + { SSC_OUTPUT, SSC_MATRIX, "T_grad_final", "TES Temperature gradient at end of timestep", "C", "", "TES", "tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_exp_wall_mass", "TES expansion tank effective wall mass", "kg", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_exp_length", "TES expansion tank effective length", "m", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_mass_cold", "TES cold fluid mass", "kg", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_mass_hot", "TES hot fluid mass", "kg", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_V_cold", "TES cold fluid volume", "kg", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_V_hot", "TES hot fluid volume", "kg", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "hot_tank_mass_perc", "TES hot tank mass percent of total (end)", "kg", "", "TES", "*", "", "" }, + + var_info_invalid }; @@ -105,6 +168,8 @@ class cm_csp_subcomponent : public compute_module void exec() { + int tes_type = as_integer("tes_type"); + util::matrix_t tes_lengths; if (is_assigned("tes_lengths")) { tes_lengths = as_matrix("tes_lengths"); //[m] @@ -113,52 +178,213 @@ class cm_csp_subcomponent : public compute_module double vals1[11] = { 0., 90., 100., 120., 0., 30., 90., 80., 80., 120., 80. }; tes_lengths.assign(vals1, 11); } - C_csp_two_tank_tes tes( - as_integer("Fluid"), // [-] field fluid identifier - as_matrix("field_fl_props"), // [-] field fluid properties - as_integer("store_fluid"), // [-] tes fluid identifier - as_matrix("store_fl_props"), // [-] tes fluid properties - as_double("P_ref") / as_double("eta_ref"), // [MWt] Design heat rate in and out of tes - as_double("solar_mult"), // [-] the max design heat rate as a fraction of the nominal - as_double("P_ref") / as_double("eta_ref") * as_double("tshours"), // [MWt-hr] design storage capacity - as_double("h_tank"), // [m] tank height - as_double("u_tank"), // [W/m^2-K] - as_integer("tank_pairs"), // [-] - as_double("hot_tank_Thtr"), // [C] convert to K in init() - as_double("hot_tank_max_heat"), // [MW] - as_double("cold_tank_Thtr"), // [C] convert to K in init() - as_double("cold_tank_max_heat"), // [MW] - as_double("dt_hot"), // [C] Temperature difference across heat exchanger - assume hot and cold deltaTs are equal - as_double("T_loop_in_des"), // [C] convert to K in init() - as_double("T_loop_out"), // [C] convert to K in init() - as_double("T_loop_out"), // [C] Initial temperature in hot storage tank - as_double("T_loop_in_des"), // [C] Initial temperature in cold storage cold - as_double("h_tank_min"), // [m] Minimum allowable HTF height in storage tank - as_double("init_hot_htf_percent"), // [%] Initial fraction of available volume that is hot - as_double("pb_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through power cycle - as_boolean("tanks_in_parallel"), // [-] Whether the tanks are in series or parallel with the solar field. Series means field htf must go through storage tanks. - as_double("V_tes_des"), // [m/s] Design-point velocity for sizing the diameters of the TES piping - as_boolean("calc_design_pipe_vals"), // [-] Should the HTF state be calculated at design conditions - as_double("tes_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop - as_double("eta_pump"), // [-] Pump efficiency, for newer pumping calculations - as_boolean("has_hot_tank_bypass"), // [-] True if the bypass valve causes the field htf to bypass just the hot tank and enter the cold tank before flowing back to the field. - as_double("T_tank_hot_inlet_min"), // [C] Minimum field htf temperature that may enter the hot tank - as_boolean("custom_tes_p_loss"), // [-] True if the TES piping losses should be calculated using the TES pipe lengths and minor loss coeffs, false if using the pumping loss parameters - as_boolean("custom_tes_pipe_sizes"), // [-] True if the TES diameters and wall thicknesses parameters should be used instead of calculating them - as_matrix("k_tes_loss_coeffs"), // [-] Combined minor loss coefficients of the fittings and valves in the collection (including bypass) and generation loops in the TES - as_matrix("tes_diams"), // [m] Imported inner diameters for the TES piping as read from the modified output files - as_matrix("tes_wallthicks"), // [m] Imported wall thicknesses for the TES piping as read from the modified output files - tes_lengths, // [m] Imported lengths for the TES piping as read from the modified output files - as_double("HDR_rough"), // [m] Pipe absolute roughness - as_double("DP_SGS") // [bar] Pressure drop on the TES discharge side (e.g., within the steam generator) - ); + + + C_csp_tes* storage_pointer; + C_csp_two_tank_tes storage_two_tank; + C_csp_NTHeatTrap_tes storage_NT; + C_csp_packedbed_tes storage_packedbed; + + double P_ref = as_double("P_ref"); + double eta_ref = as_double("eta_ref"); + double tshours = as_double("tshours"); + + // Two Tank + if (tes_type == 0) + { + double T_tank_hot_ini = is_assigned("T_tank_hot_ini") == true ? as_double("T_tank_hot_ini") : as_double("T_loop_out"); + double T_tank_cold_ini = is_assigned("T_tank_cold_ini") == true ? as_double("T_tank_cold_ini") : as_double("T_loop_in_des"); + + double h_tank_in = is_assigned("h_tank_in") == true ? as_double("h_tank_in") : std::numeric_limits::quiet_NaN(); + double d_tank_in = is_assigned("d_tank_in") == true ? as_double("d_tank_in") : std::numeric_limits::quiet_NaN(); + + storage_two_tank = C_csp_two_tank_tes( + as_integer("Fluid"), // [-] field fluid identifier + as_matrix("field_fl_props"), // [-] field fluid properties + as_integer("store_fluid"), // [-] tes fluid identifier + as_matrix("store_fl_props"), // [-] tes fluid properties + as_double("P_ref") / as_double("eta_ref"), // [MWt] Design heat rate in and out of tes + as_double("solar_mult"), // [-] the max design heat rate as a fraction of the nominal + as_double("P_ref") / as_double("eta_ref") * as_double("tshours"), // [MWt-hr] design storage capacity + as_boolean("is_h_tank_fixed"), // Use input height + h_tank_in, // [m] tank height input + d_tank_in, // [m] tank diameter input + as_double("u_tank"), // [W/m^2-K] + as_integer("tank_pairs"), // [-] + as_double("hot_tank_Thtr"), // [C] convert to K in init() + as_double("hot_tank_max_heat"), // [MW] + as_double("cold_tank_Thtr"), // [C] convert to K in init() + as_double("cold_tank_max_heat"), // [MW] + as_double("dt_hot"), // [C] Temperature difference across heat exchanger - assume hot and cold deltaTs are equal + as_double("T_loop_in_des"), // [C] convert to K in init() + as_double("T_loop_out"), // [C] convert to K in init() + T_tank_hot_ini, // [C] Initial temperature in hot storage tank + T_tank_cold_ini, // [C] Initial temperature in cold storage cold + as_double("h_tank_min"), // [m] Minimum allowable HTF height in storage tank + as_double("init_hot_htf_percent"), // [%] Initial fraction of available volume that is hot + as_double("pb_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through power cycle + as_boolean("tanks_in_parallel"), // [-] Whether the tanks are in series or parallel with the solar field. Series means field htf must go through storage tanks. + as_double("V_tes_des"), // [m/s] Design-point velocity for sizing the diameters of the TES piping + as_boolean("calc_design_pipe_vals"), // [-] Should the HTF state be calculated at design conditions + as_double("tes_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop + as_double("eta_pump"), // [-] Pump efficiency, for newer pumping calculations + as_boolean("has_hot_tank_bypass"), // [-] True if the bypass valve causes the field htf to bypass just the hot tank and enter the cold tank before flowing back to the field. + as_double("T_tank_hot_inlet_min"), // [C] Minimum field htf temperature that may enter the hot tank + as_boolean("custom_tes_p_loss"), // [-] True if the TES piping losses should be calculated using the TES pipe lengths and minor loss coeffs, false if using the pumping loss parameters + as_boolean("custom_tes_pipe_sizes"), // [-] True if the TES diameters and wall thicknesses parameters should be used instead of calculating them + as_matrix("k_tes_loss_coeffs"), // [-] Combined minor loss coefficients of the fittings and valves in the collection (including bypass) and generation loops in the TES + as_matrix("tes_diams"), // [m] Imported inner diameters for the TES piping as read from the modified output files + as_matrix("tes_wallthicks"), // [m] Imported wall thicknesses for the TES piping as read from the modified output files + tes_lengths, // [m] Imported lengths for the TES piping as read from the modified output files + as_double("HDR_rough"), // [m] Pipe absolute roughness + as_double("DP_SGS") // [bar] Pressure drop on the TES discharge side (e.g., within the steam generator) + ); + + storage_pointer = &storage_two_tank; + } + // Norwich HeatTrap + else if (tes_type == 1) + { + + int nstep = as_integer("tes_NT_nstep"); + + bool custom_tes_pipe_sizes = as_boolean("custom_tes_pipe_sizes"); + util::matrix_t tes_wallthicks; + if (!is_assigned("tes_wallthicks")) + { + double tes_wallthicks_val[1] = { -1 }; + tes_wallthicks.assign(tes_wallthicks_val, 1); + } + util::matrix_t tes_diams; + if (!is_assigned("tes_diams")) + { + double tes_diams_val[1] = { -1 }; + tes_diams.assign(tes_diams_val, 1); + } + + bool tanks_in_parallel = as_boolean("tanks_in_parallel"); + if (tanks_in_parallel == false) + { + throw exec_error("csp_subcomponent", "TES model requires tanks in parallel"); + } + + // Modify wall density to account for insulation mass + double mass_factor = 1.0 + (0.01 * as_double("tes_tank_insul_percent")); + double dens_orig = as_double("tes_tank_dens"); + double dens_w_insulation = dens_orig * mass_factor; + + double T_tank_hot_ini = is_assigned("T_tank_hot_ini") == true ? as_double("T_tank_hot_ini") : as_double("T_loop_out"); + double T_tank_cold_ini = is_assigned("T_tank_cold_ini") == true ? as_double("T_tank_cold_ini") : as_double("T_loop_in_des"); + + double h_tank_in = is_assigned("h_tank_in") == true ? as_double("h_tank_in") : std::numeric_limits::quiet_NaN(); + double d_tank_in = is_assigned("d_tank_in") == true ? as_double("d_tank_in") : std::numeric_limits::quiet_NaN(); + + storage_NT = C_csp_NTHeatTrap_tes( + as_integer("Fluid"), // [-] field fluid identifier + as_matrix("field_fl_props"), // [-] field fluid properties + as_integer("store_fluid"), // [-] tes fluid identifier + as_matrix("store_fl_props"), // [-] tes fluid properties + as_double("P_ref") / as_double("eta_ref"), // [MWt] Design heat rate in and out of tes + as_double("solar_mult"), // [-] the max design heat rate as a fraction of the nominal + as_double("P_ref") / as_double("eta_ref") * as_double("tshours"), // [MWt-hr] design storage capacity + as_boolean("is_h_tank_fixed"), // Use input height + h_tank_in, // [m] tank height input + d_tank_in, // [m] tank diameter input + as_double("u_tank"), // [W/m^2-K] + as_integer("tank_pairs"), // [-] + as_double("hot_tank_Thtr"), // [C] convert to K in init() + as_double("hot_tank_max_heat"), // [MW] + as_double("cold_tank_Thtr"), // [C] convert to K in init() + as_double("cold_tank_max_heat"), // [MW] + as_double("T_loop_in_des"), // [C] convert to K in init() + as_double("T_loop_out"), // [C] convert to K in init() + T_tank_hot_ini, // [C] Initial temperature in hot storage tank + T_tank_cold_ini, // [C] Initial temperature in cold storage cold + as_double("h_tank_min"), // [m] Minimum allowable HTF height in storage tank + as_double("init_hot_htf_percent"), // [%] Initial fraction of available volume that is hot + as_double("pb_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through power cycle + as_double("tes_tank_cp") * 1000, // convert to J/kgK + dens_w_insulation, // Tank Wall density + as_double("tes_tank_thick"), // Tank wall thickness + nstep, // Number subtimesteps + as_vector_double("tes_NT_piston_loss_poly"), // Leakage polynomial (%) + as_double("V_tes_des"), // [m/s] Design-point velocity for sizing the diameters of the TES piping + as_boolean("calc_design_pipe_vals"), // [-] Should the HTF state be calculated at design conditions + as_double("tes_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop + as_double("eta_pump"), // [-] Pump efficiency, for newer pumping calculations + as_boolean("has_hot_tank_bypass"), // [-] True if the bypass valve causes the field htf to bypass just the hot tank and enter the cold tank before flowing back to the field. + as_double("T_tank_hot_inlet_min"), // [C] Minimum field htf temperature that may enter the hot tank + false, // [-] True if the TES piping losses should be calculated using the TES pipe lengths and minor loss coeffs, false if using the pumping loss parameters + false, // [-] True if the TES diameters and wall thicknesses parameters should be used instead of calculating them + as_matrix("k_tes_loss_coeffs"), // [-] Combined minor loss coefficients of the fittings and valves in the collection (including bypass) and generation loops in the TES + tes_diams, // [m] Imported inner diameters for the TES piping as read from the modified output files + tes_wallthicks, // [m] Imported wall thicknesses for the TES piping as read from the modified output files + tes_lengths, // [m] Imported lengths for the TES piping as read from the modified output files + as_double("HDR_rough"), // [m] Pipe absolute roughness + as_double("DP_SGS") // [bar] Pressure drop on the TES discharge side (e.g., within the steam generator) + ); + + storage_pointer = &storage_NT; + } + // Packed Bed + else if (tes_type == 2) + { + + storage_packedbed = C_csp_packedbed_tes( + as_integer("Fluid"), // [-] field fluid identifier + as_matrix("field_fl_props"), // [-] field fluid properties + as_double("P_ref") / as_double("eta_ref") * as_double("tshours"), // [MWt-hr] design storage capacity + as_integer("tes_pb_size_type"), // [] Sizing Method (0) use fixed diameter, (1) use fixed height, (2) use preset inputs + as_double("h_tank_in"), // [m] Tank height + as_double("d_tank_in"), // [m] Tank diameter + as_double("tes_pb_f_oversize"), // [] Oversize factor + as_double("T_loop_in_des"), // [C] Cold design temperature + as_double("T_loop_out"), // [C] hot design temperature + as_double("T_tank_hot_ini"), // [C] Initial temperature in hot storage tank + as_double("T_tank_cold_ini"), // [C] Initial temperature in cold storage cold + as_double("init_hot_htf_percent"), // [%] Initial fraction of available volume that is hot + as_integer("tes_pb_n_xsteps"), // number spatial sub steps + as_integer("tes_pb_n_tsteps"), // number subtimesteps + as_double("tes_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop + as_double("tes_pb_k_eff"), // [W/m K] Effective thermal conductivity + as_double("tes_pb_void_frac"), // [] Packed bed void fraction + as_double("tes_pb_dens_solid"), // [kg/m3] solid specific heat + as_double("tes_pb_cp_solid"), // [J/kg K] solid specific heat + as_double("tes_pb_T_hot_delta"), // [C] Max allowable decrease in hot discharge temp + as_double("tes_pb_T_cold_delta"), // [C] Max allowable increase in cold discharge temp + as_double("tes_pb_T_charge_min") // [C] Min allowable charge temperature + ); + + + vector T_grad_ini = as_vector_double("tes_pb_T_grad_ini"); + if (T_grad_ini.size() > 1 && T_grad_ini[0] != -274) + { + if (T_grad_ini.size() != as_integer("tes_pb_n_xsteps") + 1) + { + throw exec_error("csp_subcomponent", "Initial temperature gradient should be length tes_pb_n_xsteps + 1"); + } + else + { + storage_packedbed.set_T_grad_init(T_grad_ini); + } + } + + storage_pointer = &storage_packedbed; + + } + else + { + throw exec_error("csp_subcomponent", "tes_type must be 0-2"); + } + + // Initialization -> this is necessary to fully instantiate the TES C_csp_tes::S_csp_tes_init_inputs init_inputs; init_inputs.T_to_cr_at_des = C_to_K(as_double("T_loop_in_des")); // [K] init_inputs.T_from_cr_at_des = C_to_K(as_double("T_loop_out")); // [K] init_inputs.P_to_cr_at_des = 19.64; // [bar] - tes.init(init_inputs); + storage_pointer->init(init_inputs); // Get inputs double t_step = as_double("t_step"); @@ -178,11 +404,61 @@ class cm_csp_subcomponent : public compute_module throw exec_error("csp_subcomponent", "Input arrays not equal in size."); } + // Get Design Point Outputs + double V_tes_htf_avail_calc /*m3*/, V_tes_htf_total_calc /*m3*/, + h_tank_calc /*m*/, d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, + Q_tes_des_calc /*MWt-hr*/; + + if (tes_type == 0) + { + storage_two_tank.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, + h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + } + else if (tes_type == 1) + { + storage_NT.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, + h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + } + // Allocate outputs double* T_src_in = allocate("T_src_in", n_steps); double* T_sink_in = allocate("T_sink_in", n_steps); double* T_tank_cold = allocate("T_tank_cold", n_steps); double* T_tank_hot = allocate("T_tank_hot", n_steps); + double* hot_tank_vol_frac = allocate("hot_tank_vol_frac", n_steps); + double* W_dot_elec_in_tot = allocate("W_dot_elec_in_tot", n_steps); + double* hot_tank_mass_perc = allocate("hot_tank_mass_perc", n_steps); + double* exp_wall_mass = allocate("tes_exp_wall_mass", n_steps); + double* exp_length = allocate("tes_exp_length", n_steps); + double* mass_hot = allocate("tes_mass_hot", n_steps); + double* mass_cold = allocate("tes_mass_cold", n_steps); + double* V_hot = allocate("tes_V_hot", n_steps); + double* V_cold = allocate("tes_V_cold", n_steps); + double* q_dot_dc_to_htf = allocate("q_dot_dc_to_htf", n_steps); + double* q_dot_ch_from_htf = allocate("q_dot_ch_from_htf", n_steps); + double* q_dc_to_htf = allocate("q_dc_to_htf", n_steps); + double* q_ch_from_htf = allocate("q_ch_from_htf", n_steps); + + vector piston_loc_vec; + vector piston_frac_vec; + vector T_hot_calc_vec; + vector T_cold_calc_vec; + vector tes_error_vec; + vector tes_error_percent_vec; + vector tes_error_hot_vec; + vector tes_error_cold_vec; + vector tes_error_leakage_vec; + vector tes_E_hot_vec; + vector tes_E_cold_vec; + vector tes_wall_error_vec; + vector tes_error_corrected_vec; + util::matrix_t tes_T_grad_mat; + + + if (tes_type == 2) + { + tes_T_grad_mat.resize(n_steps, as_integer("tes_pb_n_xsteps") + 1); + } // Simulate for (size_t i = 0; i < n_steps; i++) { @@ -190,7 +466,7 @@ class cm_csp_subcomponent : public compute_module double mdot_src_to_cold_tank = hot_tank_bypassed.at(i) ? mdot_src.at(i) : 0.; double T_src_in_K, T_sink_in_K; C_csp_tes::S_csp_tes_outputs tes_outputs; - int result = tes.solve_tes_off_design( + int result = storage_pointer->solve_tes_off_design( t_step, /*s*/ C_to_K(T_amb.at(i)), /*K*/ mdot_src_to_hot_tank, /*kg/s*/ @@ -202,14 +478,100 @@ class cm_csp_subcomponent : public compute_module T_sink_in_K, /*K*/ T_src_in_K, /*K*/ tes_outputs); - tes.converged(); + storage_pointer->converged(); // Set outputs T_src_in[i] = K_to_C(T_src_in_K); T_sink_in[i] = K_to_C(T_sink_in_K); - T_tank_cold[i] = K_to_C(tes.get_cold_temp()); - T_tank_hot[i] = K_to_C(tes.get_hot_temp()); + T_tank_cold[i] = K_to_C(storage_pointer->get_cold_temp()); + T_tank_hot[i] = K_to_C(storage_pointer->get_hot_temp()); + assign("tes_diameter", d_tank_calc); + assign("tes_radius", d_tank_calc / 2.0); + assign("tes_height", h_tank_calc); + q_dot_dc_to_htf[i] = tes_outputs.m_q_dot_dc_to_htf; //[MWt] + q_dot_ch_from_htf[i] = tes_outputs.m_q_dot_ch_from_htf; //[MWt] + q_dc_to_htf[i] = tes_outputs.m_q_dot_dc_to_htf * t_step; //[MJt] + q_ch_from_htf[i] = tes_outputs.m_q_dot_ch_from_htf * t_step; //[MJt] + + + hot_tank_vol_frac[i] = storage_pointer->get_hot_tank_vol_frac(); + + + // Add NT specific outputs + if (tes_type == 1) + { + double piston_location, piston_fraction; + storage_NT.calc_piston_location(piston_location, piston_fraction); + + piston_loc_vec.push_back(piston_location); + piston_frac_vec.push_back(piston_fraction); + + double tes_error = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_ERROR); + double tes_error_percent = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_ERROR_PERCENT); + double tes_error_leak = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_LEAK_ERROR); + double tes_E_hot = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_E_HOT); + double tes_E_cold = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_E_COLD); + double tes_wall_error = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_WALL_ERROR); + double tes_error_corrected = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_ERROR_CORRECTED); + + tes_error_vec.push_back(tes_error); + tes_error_percent_vec.push_back(tes_error_percent); + tes_error_leakage_vec.push_back(tes_error_leak); + tes_E_hot_vec.push_back(tes_E_hot); + tes_E_cold_vec.push_back(tes_E_cold); + tes_wall_error_vec.push_back(tes_wall_error); + tes_error_corrected_vec.push_back(tes_error_corrected); + + hot_tank_mass_perc[i] = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_HOT_TANK_HTF_PERC_FINAL); + exp_wall_mass[i] = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_EXP_WALL_MASS); + exp_length[i] = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_EXP_LENGTH); + mass_hot[i] = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_MASS_HOT_TANK); + mass_cold[i] = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_MASS_COLD_TANK); + V_cold[i] = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_VOL_COLD); + V_hot[i] = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_VOL_HOT); + } + + // Add packed bed specific outputs + { + std::vector T_prev_vec = storage_packedbed.get_T_prev_vec(); + for (int j = 0; j < T_prev_vec.size(); j++) + { + tes_T_grad_mat.at(i, j) = T_prev_vec[j] - 273.15; // [C] Convert from K + } + } + } + + if (tes_type == 1) + { + set_vector("piston_loc", piston_loc_vec); + set_vector("piston_frac", piston_frac_vec); + set_vector("T_hot_calc", T_hot_calc_vec); + set_vector("T_cold_calc", T_cold_calc_vec); + set_vector("tes_error", tes_error_vec); + set_vector("tes_error_percent", tes_error_percent_vec); + set_vector("tes_leak_error", tes_error_leakage_vec); + set_vector("tes_E_hot", tes_E_hot_vec); + set_vector("tes_E_cold", tes_E_cold_vec); + set_vector("tes_wall_error", tes_wall_error_vec); + set_vector("tes_error_corrected", tes_error_corrected_vec); + } + + + if (tes_type == 2) + { + assign("T_grad_final", tes_T_grad_mat); + } + + } + + template + void set_vector(const std::string& name, const vector vec) + { + int size = vec.size(); + ssc_number_t* alloc_vals = allocate(name, size); + for (int i = 0; i < size; i++) + alloc_vals[i] = vec[i]; // [] } }; diff --git a/ssc/cmod_etes_electric_resistance.cpp b/ssc/cmod_etes_electric_resistance.cpp index 5a92d9a23..0198da06d 100644 --- a/ssc/cmod_etes_electric_resistance.cpp +++ b/ssc/cmod_etes_electric_resistance.cpp @@ -609,7 +609,9 @@ class cm_etes_electric_resistance : public compute_module W_dot_cycle_des / eta_cycle, //[MWt] heater_mult, //[-] W_dot_cycle_des / eta_cycle * tshours, //[MWht] + true, as_double("h_tank"), + 0.0, as_double("u_tank"), as_integer("tank_pairs"), as_double("hot_tank_Thtr"), @@ -875,8 +877,8 @@ class cm_etes_electric_resistance : public compute_module c_electric_resistance.get_design_parameters(E_heater_su_des, W_dot_heater_des_calc); // TES - double V_tes_htf_avail /*m3*/, V_tes_htf_total /*m3*/, d_tank /*m*/, q_dot_loss_tes_des /*MWt*/, dens_store_htf_at_T_ave /*kg/m3*/, Q_tes_des_tes_class; - storage.get_design_parameters(V_tes_htf_avail, V_tes_htf_total, d_tank, + double V_tes_htf_avail /*m3*/, V_tes_htf_total /*m3*/, h_tank /*m*/, d_tank /*m*/, q_dot_loss_tes_des /*MWt*/, dens_store_htf_at_T_ave /*kg/m3*/, Q_tes_des_tes_class; + storage.get_design_parameters(V_tes_htf_avail, V_tes_htf_total, h_tank, d_tank, q_dot_loss_tes_des, dens_store_htf_at_T_ave, Q_tes_des_tes_class); // System diff --git a/ssc/cmod_etes_ptes.cpp b/ssc/cmod_etes_ptes.cpp index d1d23df04..a413b6782 100644 --- a/ssc/cmod_etes_ptes.cpp +++ b/ssc/cmod_etes_ptes.cpp @@ -650,7 +650,9 @@ class cm_etes_ptes : public compute_module q_dot_hot_in_gen, //[MWt] heater_mult, //[-] q_dot_hot_in_gen* tshours, //[MWht] + true, as_double("h_tank"), + 0.0, as_double("u_tank"), as_integer("tank_pairs"), as_double("hot_tank_Thtr"), @@ -706,7 +708,9 @@ class cm_etes_ptes : public compute_module q_dot_CT_des__discharge_basis, //[MWt] heater_mult, //[-] q_dot_CT_des__discharge_basis * tshours, //[MWt-hr] + true, as_double("CT_h_tank"), + 0.0, as_double("CT_u_tank"), as_integer("CT_tank_pairs"), hot_tank_Thtr, //[C] @@ -1002,19 +1006,19 @@ class cm_etes_ptes : public compute_module // HT TES double V_tes_htf_avail_calc /*m3*/, V_tes_htf_total_calc /*m3*/, - d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, + h_tank_calc /*m*/, d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, Q_tes_des_calc /*MWt-hr*/; c_HT_TES.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, - d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); // CT TES double CT_V_tes_htf_avail_calc /*m3*/, CT_V_tes_htf_total_calc /*m3*/, - CT_d_tank_calc /*m*/, CT_q_dot_loss_tes_des_calc /*MWt*/, CT_dens_store_htf_at_T_ave_calc /*kg/m3*/, + CT_h_tank_calc /*m*/, CT_d_tank_calc /*m*/, CT_q_dot_loss_tes_des_calc /*MWt*/, CT_dens_store_htf_at_T_ave_calc /*kg/m3*/, CT_Q_tes_des_calc /*MWt-hr*/; c_CT_TES->get_design_parameters(CT_V_tes_htf_avail_calc, CT_V_tes_htf_total_calc, - CT_d_tank_calc, CT_q_dot_loss_tes_des_calc, CT_dens_store_htf_at_T_ave_calc, + CT_h_tank_calc, CT_d_tank_calc, CT_q_dot_loss_tes_des_calc, CT_dens_store_htf_at_T_ave_calc, CT_Q_tes_des_calc); // System diff --git a/ssc/cmod_fresnel_physical.cpp b/ssc/cmod_fresnel_physical.cpp index d789cf444..fce04356a 100644 --- a/ssc/cmod_fresnel_physical.cpp +++ b/ssc/cmod_fresnel_physical.cpp @@ -204,6 +204,14 @@ static var_info _cm_vtab_fresnel_physical[] = { { SSC_INPUT, SSC_NUMBER, "V_tes_des", "Design-point velocity to size the TES pipe diameters", "m/s", "", "Storage", "?=1.85", "", "SIMULATION_PARAMETER" }, // System Control + + { SSC_INPUT, SSC_NUMBER, "is_timestep_load_fractions", "Use turbine load fraction for each timestep instead of block dispatch?", "", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "?", "", "SIMULATION_PARAMETER" }, + + { SSC_INPUT, SSC_NUMBER, "pb_fixed_par", "Fixed parasitic load - runs at all times", "", "", "Sys_Control", "*", "", "" }, + { SSC_INPUT, SSC_ARRAY, "bop_array", "Balance of plant parasitic power fraction", "", "", "Sys_Control", "*", "", "" }, + { SSC_INPUT, SSC_ARRAY, "aux_array", "Aux heater, boiler parasitic", "", "", "Sys_Control", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "is_dispatch", "Allow dispatch optimization?", /*TRUE=1*/ "-", "", "Sys_Control", "?=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "disp_frequency", "Frequency for dispatch optimization calculations", "hour", "", "Sys_Control", "is_dispatch=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "disp_horizon", "Time horizon for dispatch optimization", "hour", "", "Sys_Control", "is_dispatch=1", "", "" }, @@ -214,11 +222,7 @@ static var_info _cm_vtab_fresnel_physical[] = { { SSC_INPUT, SSC_NUMBER, "disp_rsu_cost_rel", "Receiver startup cost", "$/MWt/start", "", "Sys_Control", "is_dispatch=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "disp_csu_cost_rel", "Cycle startup cost", "$/MWe-cycle/start", "", "Sys_Control", "is_dispatch=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "disp_pen_ramping", "Dispatch cycle production change penalty", "$/MWe-change", "", "Sys_Control", "is_dispatch=1", "", "" }, - - { SSC_INPUT, SSC_NUMBER, "pb_fixed_par", "Fixed parasitic load - runs at all times", "", "", "Sys_Control", "*", "", "" }, - { SSC_INPUT, SSC_ARRAY, "bop_array", "Balance of plant parasitic power fraction", "", "", "Sys_Control", "*", "", "" }, - { SSC_INPUT, SSC_ARRAY, "aux_array", "Aux heater, boiler parasitic", "", "", "Sys_Control", "*", "", "" }, - + { SSC_INPUT, SSC_NUMBER, "can_cycle_use_standby", "Can the cycle use standby operation?", "", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "disp_steps_per_hour", "Time steps per hour for dispatch optimization calculations", "-", "", "tou", "?=1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "disp_spec_presolve", "Dispatch optimization presolve heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, @@ -228,7 +232,6 @@ static var_info _cm_vtab_fresnel_physical[] = { { SSC_INPUT, SSC_NUMBER, "disp_inventory_incentive", "Dispatch storage terminal inventory incentive multiplier", "", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "q_rec_standby", "Receiver standby energy consumption", "kWt", "", "tou", "?=9e99", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWe-hr", "", "tou", "?=0.0", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "", "", "" }, @@ -961,7 +964,9 @@ class cm_fresnel_physical : public compute_module as_double("P_ref") / as_double("eta_ref"), c_fresnel.m_solar_mult, as_double("P_ref") / as_double("eta_ref") * as_double("tshours"), + true, as_double("h_tank"), + 0.0, as_double("u_tank"), as_integer("tank_pairs"), as_double("hot_tank_Thtr"), @@ -1028,11 +1033,7 @@ class cm_fresnel_physical : public compute_module // Off-taker schedule C_timeseries_schedule_inputs offtaker_schedule; - bool assigned_is_timestep_fractions = is_assigned("is_timestep_load_fractions"); - bool is_timestep_load_fractions = false; - if (assigned_is_timestep_fractions) { - is_timestep_load_fractions = as_boolean("is_timestep_load_fractions"); - } + bool is_timestep_load_fractions = as_boolean("is_timestep_load_fractions"); if (is_timestep_load_fractions) { auto vec = as_vector_double("timestep_load_fractions"); C_timeseries_schedule_inputs offtaker_series = C_timeseries_schedule_inputs(vec, std::numeric_limits::quiet_NaN()); @@ -1465,13 +1466,16 @@ class cm_fresnel_physical : public compute_module // Storage double V_tes_htf_avail_calc /*m3*/, V_tes_htf_total_calc /*m3*/, - d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, + h_tank_calc /*m*/, d_tank_calc /*m*/, + q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, Q_tes_des_calc /*MWt-hr*/; storage.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, - d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + h_tank_calc, d_tank_calc, + q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + - double vol_min = V_tes_htf_total_calc * (storage.m_h_tank_min / storage.m_h_tank); + double vol_min = V_tes_htf_total_calc * (storage.m_h_tank_min / h_tank_calc); double tes_htf_min_temp = storage.get_min_storage_htf_temp() - 273.15; double tes_htf_max_temp = storage.get_max_storage_htf_temp() - 273.15; double tes_htf_dens = storage.get_storage_htf_density(); diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index bafe23405..c1e77db2a 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -827,7 +827,9 @@ class cm_fresnel_physical_iph : public compute_module q_dot_pc_des, c_fresnel.m_solar_mult, Q_tes, + true, as_double("h_tank"), + 0.0, as_double("u_tank"), as_integer("tank_pairs"), as_double("hot_tank_Thtr"), @@ -1252,13 +1254,16 @@ class cm_fresnel_physical_iph : public compute_module // Storage double V_tes_htf_avail_calc /*m3*/, V_tes_htf_total_calc /*m3*/, - d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, + h_tank_calc /*m*/, d_tank_calc /*m*/, + q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, Q_tes_des_calc /*MWt-hr*/; storage.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, - d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + h_tank_calc, d_tank_calc, + q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); - double vol_min = V_tes_htf_total_calc * (storage.m_h_tank_min / storage.m_h_tank); + + double vol_min = V_tes_htf_total_calc * (storage.m_h_tank_min / h_tank_calc); double tes_htf_min_temp = storage.get_min_storage_htf_temp() - 273.15; double tes_htf_max_temp = storage.get_max_storage_htf_temp() - 273.15; double tes_htf_dens = storage.get_storage_htf_density(); diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index 0535b1d66..fa5845798 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -1537,7 +1537,9 @@ class cm_mspt_iph : public compute_module q_dot_pc_des, //[MWt] as_double("solarm"), //[-] Q_tes, + true, as_double("h_tank"), + 0.0, as_double("u_tank"), as_integer("tank_pairs"), as_double("hot_tank_Thtr"), @@ -1913,11 +1915,11 @@ class cm_mspt_iph : public compute_module // ************************* // Thermal Energy Storage double V_tes_htf_avail_calc /*m3*/, V_tes_htf_total_calc /*m3*/, - d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, + h_tank_calc /*m*/, d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, Q_tes_des_calc /*MWt-hr*/; storage.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, - d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); assign("Q_tes_des", Q_tes_des_calc); //[MWt-hr] assign("V_tes_htf_avail_des", V_tes_htf_avail_calc); //[m3] diff --git a/ssc/cmod_tcsmolten_salt.cpp b/ssc/cmod_tcsmolten_salt.cpp index 50633c700..f49fccf09 100644 --- a/ssc/cmod_tcsmolten_salt.cpp +++ b/ssc/cmod_tcsmolten_salt.cpp @@ -2009,7 +2009,9 @@ class cm_tcsmolten_salt : public compute_module as_double("P_ref") / as_double("design_eff"), //[MWt] as_double("solarm"), //[-] as_double("P_ref") / as_double("design_eff") * as_double("tshours"), + true, // Fixed height as_double("h_tank"), + 0.0, //[m] No input diameter (use height instead) as_double("u_tank"), as_integer("tank_pairs"), as_double("hot_tank_Thtr"), @@ -2497,11 +2499,11 @@ class cm_tcsmolten_salt : public compute_module // ************************* // Thermal Energy Storage double V_tes_htf_avail_calc /*m3*/, V_tes_htf_total_calc /*m3*/, - d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, + h_tank_calc /*m*/, d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, Q_tes_des_calc /*MWt-hr*/; storage.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, - d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); assign("Q_tes_des", Q_tes_des_calc); //[MWt-hr] assign("V_tes_htf_avail_des", V_tes_htf_avail_calc); //[m3] diff --git a/ssc/cmod_trough_physical.cpp b/ssc/cmod_trough_physical.cpp index 08584bc23..2ba6b85d7 100644 --- a/ssc/cmod_trough_physical.cpp +++ b/ssc/cmod_trough_physical.cpp @@ -43,6 +43,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "csp_solver_trough_collector_receiver.h" #include "csp_solver_pc_Rankine_indirect_224.h" #include "csp_solver_two_tank_tes.h" +#include "csp_solver_NTHeatTrap_tes.h" +#include "csp_solver_packedbed_tes.h" +//#include "csp_solver_tou_block_schedules.h" #include "csp_dispatch.h" #include "csp_system_costs.h" //#include "cmod_csp_common_eqns.h" @@ -90,7 +93,8 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_INPUT, SSC_NUMBER, "Row_Distance", "Spacing between rows (centerline to centerline)", "m", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "T_loop_in_des", "Design loop inlet temperature", "C", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "T_loop_out", "Target loop outlet temperature", "C", "", "solar_field", "*", "", "" }, - //{ SSC_INPUT, SSC_NUMBER, "T_startup", "Required temperature of the system before the power block can be switched on", "C", "", "solar_field", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "T_startup", "Required temperature of the system before the power block can be switched on", "C", "", "solar_field", "?", "", "" }, + { SSC_INPUT, SSC_NUMBER, "T_shutdown", "Temperature when solar field begins recirculating", "C", "", "solar_field", "?", "", "" }, { SSC_INPUT, SSC_NUMBER, "use_abs_or_rel_mdot_limit", "Use mass flow abs (0) or relative (1) limits", "", "", "solar_field", "?=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "m_dot_htfmin", "Minimum loop HTF flow rate", "kg/s", "", "solar_field", "use_abs_or_rel_mdot_limit=0", "", "" }, @@ -201,10 +205,14 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_INPUT, SSC_MATRIX, "ud_ind_od", "Off design user-defined power cycle performance as function of T_htf, m_dot_htf [ND], and T_amb", "", "", "powerblock", "pc_config=1", "", "" }, // TES + { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), HeatTrap Single Tank (1)", "-", "", "TES", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "store_fluid", "Material number for storage fluid", "-", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_MATRIX, "store_fl_props", "User defined storage fluid property data", "-", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "tshours", "Equivalent full-load thermal storage hours", "hr", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "h_tank", "Total height of tank (height of HTF when tank is full", "m", "", "TES", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "is_h_tank_fixed", "[1] Use fixed height (calculate diameter) [0] Use fixed diameter", "-", "", "TES", "?=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "h_tank_in", "Total height of tank input (height of HTF when tank is full", "m", "", "TES", "is_h_tank_fixed=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "d_tank_in", "Tank diameter input", "m", "", "TES", "is_h_tank_fixed=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "tank_pairs", "Number of equivalent tank pairs", "-", "", "TES", "*", "INTEGER", "" }, { SSC_INPUT, SSC_NUMBER, "hot_tank_Thtr", "Minimum allowable hot tank HTF temp", "C", "", "TES", "*", "", "" }, @@ -217,6 +225,26 @@ static var_info _cm_vtab_trough_physical[] = { //{ SSC_INPUT, SSC_NUMBER, "T_tank_cold_ini", "Initial cold tank fluid temperature", "C", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "init_hot_htf_percent", "Initial fraction of avail. vol that is hot", "%", "", "TES", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_n_tsteps", "Number of subtimesteps (for NT and packed bed)", "", "", "TES", "tes_type=2", "", "" }, + + // TES Norwich HeatTrap + { SSC_INPUT, SSC_NUMBER, "tes_tank_thick", "Tank wall thickness (used for Norwich HeatTrap)", "m", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_tank_cp", "Tank wall cp (used for Norwich HeatTrap)", "kJ/kg-K", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_tank_dens", "Tank wall thickness (used for Norwich HeatTrap)", "kg/m3", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_ARRAY, "tes_NT_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_tank_insul_percent", "Percent additional wall mass due to insulation", "%", "", "TES", "?=0", "", "" }, + + // TES Packed Bed + { SSC_INPUT, SSC_NUMBER, "tes_pb_n_xsteps", "Number of spatial segments", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_k_eff", "TES packed bed effective conductivity", "W/m K", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_void_frac", "TES particle packed bed void fraction", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_dens_solid", "TES particle density", "kg/m3", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_cp_solid", "TES particle specific heat", "J/kg K", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_hot_delta", "Max allowable decrease in hot discharge temp", "C", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_cold_delta", "Max allowable increase in cold discharge temp", "C", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_charge_min", "Min charge temp", "C", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_f_oversize", "Packed bed oversize factor", "", "", "TES", "tes_type=2", "", "" }, + // Optional Component Initialization (state at start of first timestep) // Trough field @@ -280,7 +308,8 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_INPUT, SSC_ARRAY, "dispatch_tod_factors", "TOD factors for periods 1 through 9", "", "We added this array input after SAM 2022.12.21 to replace the functionality of former single value inputs dispatch_factor1 through dispatch_factor9", "Time of Delivery Factors","sim_type=1&ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "", "", "" }, + { SSC_INPUT, SSC_NUMBER, "is_timestep_load_fractions","Use turbine load fraction for each timestep instead of block dispatch?", "", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "?", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "sim_type=1&ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_MATRIX, "mp_energy_market_revenue", "Energy market revenue input", "", "Lifetime x 2[Cleared Capacity(MW),Price($/MWh)]", "Revenue", "sim_type=1&csp_financial_model=6&is_dispatch=1", "", "SIMULATION_PARAMETER" }, @@ -439,6 +468,7 @@ static var_info _cm_vtab_trough_physical[] = { // Thermal Storage { SSC_OUTPUT, SSC_NUMBER, "vol_tank", "Total tank volume", "m3", "", "Thermal Storage","*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "q_tes", "TES design capacity", "MWt-hr", "", "Thermal Storage","*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "csp_pt_tes_tank_height", "Tank height", "m", "", "Thermal Storage","*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "csp_pt_tes_tank_diameter", "Tank diameter", "m", "", "Thermal Storage","*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "q_dot_tes_est", "Estimated TES Heat Loss", "MW", "", "Thermal Storage","*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "csp_pt_tes_htf_density", "Storage htf density", "kg/m3", "", "Thermal Storage","*", "", "" }, @@ -450,7 +480,6 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_OUTPUT, SSC_NUMBER, "tes_htf_min_temp", "Minimum storage htf temp", "C", "", "Power Cycle", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "tes_htf_max_temp", "Maximum storage htf temp", "C", "", "Power Cycle", "*", "", "" }, - // Collector { SSC_OUTPUT, SSC_MATRIX, "csp_dtr_sca_ap_lengths", "Length of single module", "m", "", "Collector", "?=0", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "csp_dtr_sca_calc_zenith", "Calculated zenith", "degree", "", "Collector", "?=0", "", "" }, @@ -599,6 +628,39 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_OUTPUT, SSC_ARRAY, "m_dot_cycle_to_field", "Mass flow: cycle to field", "kg/s", "", "TES", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "m_dot_cold_tank_to_hot_tank", "Mass flow: cold tank to hot tank", "kg/s", "", "TES", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "tes_htf_pump_power", "TES HTF pump power", "MWe", "", "TES", "sim_type=1", "", "" }, + + // NT TES + { SSC_OUTPUT, SSC_ARRAY, "vol_tes_cold", "TES cold fluid volume", "m3", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "vol_tes_hot", "TES hot fluid volume", "m3" , "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "vol_tes_tot", "TES total fluid volume", "m3", "", "TES", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_piston_loc", "TES piston distance from left (cold) side", "m", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_piston_frac", "TES piston fraction of cold distance over total", "", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_cold_vol_frac", "TES volume fraction of cold over total", "", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_mass_tot", "TES total fluid mass", "kg", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_SA_cold", "TES cold side surface area", "m2", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_SA_hot", "TES hot side surface area", "m2", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_SA_tot", "TES total surface area", "m2", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error", "TES energy balance error", "MWt", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error_percent", "TES energy balance error percent", "%", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_leak_error", "TES energy balance error due to leakage assumption", "MWt", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_wall_error", "TES energy balance error due to wall temperature assumption", "MWt", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error_corrected", "TES energy balance error, accounting for wall and temperature assumption error", "MWt", "", "TES", "sim_type=1&tes_type=1", "", "" }, + + // Packed Bed TES + { SSC_OUTPUT, SSC_ARRAY, "T_grad_0", "TES Temperature gradient 0 indice", "C", "", "TES", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_1", "TES Temperature gradient 1 indice", "C", "", "TES", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_2", "TES Temperature gradient 2 indice", "C", "", "TES", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_3", "TES Temperature gradient 3 indice", "C", "", "TES", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_4", "TES Temperature gradient 4 indice", "C", "", "TES", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_5", "TES Temperature gradient 5 indice", "C", "", "TES", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_6", "TES Temperature gradient 6 indice", "C", "", "TES", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_7", "TES Temperature gradient 7 indice", "C", "", "TES", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_8", "TES Temperature gradient 8 indice", "C", "", "TES", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_9", "TES Temperature gradient 9 indice", "C", "", "TES", "", "", "" }, + + + //{ SSC_OUTPUT, SSC_ARRAY, "m_dot_tes_dc", "TES discharge mass flow rate", "kg/s", "", "TES", "*", "", "" }, + //{ SSC_OUTPUT, SSC_ARRAY, "m_dot_tes_ch", "TES charge mass flow rate", "kg/s", "", "TES", "*", "", "" }, // SYSTEM @@ -672,13 +734,13 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_OUTPUT, SSC_NUMBER, "sim_duration", "Computational time of timeseries simulation", "s", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "recirculating", "Field recirculating (bypass valve open)", "-", "", "solar_field", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_diams", "Pipe diameters in TES", "m", "", "TES", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_wallthk", "Pipe wall thickness in TES", "m", "", "TES", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_lengths", "Pipe lengths in TES", "m", "", "TES", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_mdot_dsn", "Mass flow TES pipes at design conditions", "kg/s", "", "TES", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_vel_dsn", "Velocity in TES pipes at design conditions", "m/s", "", "TES", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_T_dsn", "Temperature in TES pipes at design conditions", "C", "", "TES", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_P_dsn", "Pressure in TES pipes at design conditions", "bar", "", "TES", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_diams", "Pipe diameters in TES", "m", "", "TES", "sim_type=1&tes_type=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_wallthk", "Pipe wall thickness in TES", "m", "", "TES", "sim_type=1&tes_type=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_lengths", "Pipe lengths in TES", "m", "", "TES", "sim_type=1&tes_type=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_mdot_dsn", "Mass flow TES pipes at design conditions", "kg/s", "", "TES", "sim_type=1&tes_type=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_vel_dsn", "Velocity in TES pipes at design conditions", "m/s", "", "TES", "sim_type=1&tes_type=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_T_dsn", "Temperature in TES pipes at design conditions", "C", "", "TES", "sim_type=1&tes_type=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_P_dsn", "Pressure in TES pipes at design conditions", "bar", "", "TES", "sim_type=1&tes_type=0", "", "" }, //{ SSC_OUTPUT, SSC_ARRAY, "defocus", "Field optical focus fraction", "", "", "solver", "*", "", "" }, @@ -724,6 +786,8 @@ class cm_trough_physical : public compute_module int sim_type = as_integer("sim_type"); int csp_financial_model = as_integer("csp_financial_model"); + int tes_type = as_integer("tes_type"); + // ***************************************************** // Check deprecated variables if (is_dispatch) { @@ -887,22 +951,23 @@ class cm_trough_physical : public compute_module { T_startup_min = T_loop_out_des - 70.0; } - - // Startup and Shutdown temperatures double T_startup_old = max(T_startup_min, 0.67 * T_loop_in_des + 0.33 * T_loop_out_des); //[C] + //c_trough.m_T_startup = T_startup; //[C] The required temperature (converted to K in init) of the system before the power block can be switched on + double T_startup = T_startup_old; - if (is_assigned("T_startup") == true) + if (is_assigned("T_startup")) { - T_startup = as_double("T_startup"); //[C] The required temperature (converted to K in init) of the system before the power block can be switched on + T_startup = as_double("T_startup"); } - double T_shutdown = std::numeric_limits::quiet_NaN(); - if (is_assigned("T_shutdown") == true) + double T_shutdown = T_startup; + if (is_assigned("T_shutdown")) { T_shutdown = as_double("T_shutdown"); } - c_trough.m_T_startup = T_startup; + + c_trough.m_T_startup = T_startup; //[C] The required temperature (converted to K in init) of the system before the power block can be switched on c_trough.m_T_shutdown = T_shutdown; //[C] c_trough.m_use_abs_or_rel_mdot_limit = as_integer("use_abs_or_rel_mdot_limit"); // Use mass flow abs (0) or relative (1) limits @@ -1279,15 +1344,17 @@ class cm_trough_physical : public compute_module p_csp_power_cycle->assign(C_pc_Rankine_indirect_224::E_ETA_THERMAL, allocate("eta", n_steps_fixed), n_steps_fixed); } } - - // ******************************** // ******************************** // TES // ******************************** // ******************************** - C_csp_two_tank_tes storage; + C_csp_tes* storage_pointer; + C_csp_two_tank_tes storage_two_tank; + C_csp_NTHeatTrap_tes storage_NT; + C_csp_packedbed_tes storage_packedbed; + if (tes_type == 0) { bool custom_tes_pipe_sizes = as_boolean("custom_tes_pipe_sizes"); @@ -1312,7 +1379,10 @@ class cm_trough_physical : public compute_module tes_diams.assign(tes_diams_val, 1); } - storage = C_csp_two_tank_tes( + double h_tank_in = is_assigned("h_tank_in") == true ? as_double("h_tank_in") : std::numeric_limits::quiet_NaN(); + double d_tank_in = is_assigned("d_tank_in") == true ? as_double("d_tank_in") : std::numeric_limits::quiet_NaN(); + + storage_two_tank = C_csp_two_tank_tes( c_trough.m_Fluid, c_trough.m_field_fl_props, as_integer("store_fluid"), @@ -1320,7 +1390,9 @@ class cm_trough_physical : public compute_module as_double("P_ref") / as_double("eta_ref"), c_trough.m_solar_mult, as_double("P_ref") / as_double("eta_ref") * as_double("tshours"), - as_double("h_tank"), + as_boolean("is_h_tank_fixed"), + h_tank_in, + d_tank_in, as_double("u_tank"), as_integer("tank_pairs"), as_double("hot_tank_Thtr"), @@ -1353,29 +1425,203 @@ class cm_trough_physical : public compute_module as_double("DP_SGS") ); + storage_pointer = &storage_two_tank; + // Set storage outputs - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_Q_DOT_LOSS, allocate("tank_losses", n_steps_fixed), n_steps_fixed); - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_W_DOT_HEATER, allocate("q_tes_heater", n_steps_fixed), n_steps_fixed); - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_TES_T_HOT, allocate("T_tes_hot", n_steps_fixed), n_steps_fixed); - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_TES_T_COLD, allocate("T_tes_cold", n_steps_fixed), n_steps_fixed); - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_M_DOT_TANK_TO_TANK, allocate("m_dot_cold_tank_to_hot_tank", n_steps_fixed), n_steps_fixed); - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_MASS_COLD_TANK, allocate("mass_tes_cold", n_steps_fixed), n_steps_fixed); - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_MASS_HOT_TANK, allocate("mass_tes_hot", n_steps_fixed), n_steps_fixed); - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_W_DOT_HTF_PUMP, allocate("tes_htf_pump_power", n_steps_fixed), n_steps_fixed); - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_HOT_TANK_HTF_PERC_FINAL, allocate("hot_tank_htf_percent_final", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_Q_DOT_LOSS, allocate("tank_losses", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_W_DOT_HEATER, allocate("q_tes_heater", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_TES_T_HOT, allocate("T_tes_hot", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_TES_T_COLD, allocate("T_tes_cold", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_M_DOT_TANK_TO_TANK, allocate("m_dot_cold_tank_to_hot_tank", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_MASS_COLD_TANK, allocate("mass_tes_cold", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_MASS_HOT_TANK, allocate("mass_tes_hot", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_W_DOT_HTF_PUMP, allocate("tes_htf_pump_power", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_VOL_TOT, allocate("vol_tes_tot", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_MASS_TOT, allocate("tes_mass_tot", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_HOT_TANK_HTF_PERC_FINAL, allocate("hot_tank_htf_percent_final", n_steps_fixed), n_steps_fixed); + } + else if (tes_type == 1) + { + // Get number of sub time steps + int nstep = as_integer("tes_n_tsteps"); + + bool custom_tes_pipe_sizes = as_boolean("custom_tes_pipe_sizes"); + util::matrix_t tes_lengths; + if (is_assigned("tes_lengths")) { + tes_lengths = as_matrix("tes_lengths"); //[m] + } + if (!is_assigned("tes_lengths") || tes_lengths.ncells() < 11) { + double vals1[11] = { 0., 90., 100., 120., 0., 30., 90., 80., 80., 120., 80. }; + tes_lengths.assign(vals1, 11); + } + util::matrix_t tes_wallthicks; + if (!is_assigned("tes_wallthicks")) + { + double tes_wallthicks_val[1] = { -1 }; + tes_wallthicks.assign(tes_wallthicks_val, 1); + } + util::matrix_t tes_diams; + if (!is_assigned("tes_diams")) + { + double tes_diams_val[1] = { -1 }; + tes_diams.assign(tes_diams_val, 1); + } + + //double tes_tankthick = as_double("tes_tankthick"); + //double tes_tankcp = 1200; // J/kg K + //double tes_tankdens = 8000; // kg/m3 + + bool tanks_in_parallel = as_boolean("tanks_in_parallel"); + if (tanks_in_parallel == false) + { + throw exec_error("trough_physical", "TES model requires tanks in parallel"); + } + + // Modify wall density to account for insulation mass + double mass_factor = 1.0 + (0.01 * as_double("tes_tank_insul_percent")); + double dens_orig = as_double("tes_tank_dens"); + double dens_w_insulation = dens_orig * mass_factor; + + double h_tank_in = is_assigned("h_tank_in") == true ? as_double("h_tank_in") : std::numeric_limits::quiet_NaN(); + double d_tank_in = is_assigned("d_tank_in") == true ? as_double("d_tank_in") : std::numeric_limits::quiet_NaN(); + + storage_NT = C_csp_NTHeatTrap_tes( + c_trough.m_Fluid, + c_trough.m_field_fl_props, + as_integer("store_fluid"), + as_matrix("store_fl_props"), + as_double("P_ref") / as_double("eta_ref"), + c_trough.m_solar_mult, + as_double("P_ref") / as_double("eta_ref") * as_double("tshours"), + as_boolean("is_h_tank_fixed"), + h_tank_in, + d_tank_in, + as_double("u_tank"), + as_integer("tank_pairs"), + as_double("hot_tank_Thtr"), + as_double("hot_tank_max_heat"), + as_double("cold_tank_Thtr"), + as_double("cold_tank_max_heat"), + as_double("T_loop_in_des"), + as_double("T_loop_out"), + as_double("T_loop_out"), + as_double("T_loop_in_des"), + as_double("h_tank_min"), + as_double("init_hot_htf_percent"), + as_double("pb_pump_coef"), + as_double("tes_tank_cp") * 1000, // convert to J/kgK + dens_w_insulation, + as_double("tes_tank_thick"), + nstep, + as_vector_double("tes_NT_piston_loss_poly"), + as_double("V_tes_des"), + as_boolean("calc_design_pipe_vals"), + as_double("tes_pump_coef"), + as_double("eta_pump"), + as_boolean("has_hot_tank_bypass"), + as_double("T_tank_hot_inlet_min"), + false, + false, + as_matrix("k_tes_loss_coeffs"), + tes_diams, + tes_wallthicks, + tes_lengths, + as_double("HDR_rough"), + as_double("DP_SGS") + ); + + storage_pointer = &storage_NT; + + // Set storage outputs + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_Q_DOT_LOSS, allocate("tank_losses", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_W_DOT_HEATER, allocate("q_tes_heater", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_TES_T_HOT, allocate("T_tes_hot", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_TES_T_COLD, allocate("T_tes_cold", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_M_DOT_TANK_TO_TANK, allocate("m_dot_cold_tank_to_hot_tank", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_MASS_COLD_TANK, allocate("mass_tes_cold", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_MASS_HOT_TANK, allocate("mass_tes_hot", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_W_DOT_HTF_PUMP, allocate("tes_htf_pump_power", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_HOT_TANK_HTF_PERC_FINAL, allocate("hot_tank_htf_percent_final", n_steps_fixed), n_steps_fixed); + + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_VOL_COLD, allocate("vol_tes_cold", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_VOL_HOT, allocate("vol_tes_hot", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_VOL_TOT, allocate("vol_tes_tot", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_PIST_LOC, allocate("tes_piston_loc", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_PIST_FRAC, allocate("tes_piston_frac", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_COLD_FRAC, allocate("tes_cold_vol_frac", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_MASS_TOT, allocate("tes_mass_tot", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_SA_COLD, allocate("tes_SA_cold", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_SA_HOT, allocate("tes_SA_hot", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_SA_TOT, allocate("tes_SA_tot", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_ERROR, allocate("tes_error", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_ERROR_PERCENT, allocate("tes_error_percent", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_LEAK_ERROR, allocate("tes_leak_error", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_WALL_ERROR, allocate("tes_wall_error", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_ERROR_CORRECTED, allocate("tes_error_corrected", n_steps_fixed), n_steps_fixed); + } + else if (tes_type == 2) + { + storage_packedbed = C_csp_packedbed_tes( + as_integer("Fluid"), // [-] field fluid identifier + as_matrix("field_fl_props"), // [-] field fluid properties + as_double("P_ref") / as_double("eta_ref") * as_double("tshours"), // [MWt-hr] design storage capacity + as_integer("is_h_tank_fixed"), // [] Sizing Method (0) use fixed diameter, (1) use fixed height, (2) use preset inputs + as_double("h_tank_in"), // [m] Tank height + as_double("d_tank_in"), // [m] Tank diameter + as_double("tes_pb_f_oversize"), // [] Oversize factor + as_double("T_loop_in_des"), // [C] Cold design temperature + as_double("T_loop_out"), // [C] hot design temperature + as_double("T_loop_out"), // [C] Initial temperature in hot storage tank + as_double("T_loop_in_des"), // [C] Initial temperature in cold storage cold + as_double("init_hot_htf_percent"), // [%] Initial fraction of available volume that is hot + as_integer("tes_pb_n_xsteps"), // number spatial sub steps + as_integer("tes_n_tsteps"), // number subtimesteps + as_double("tes_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop + as_double("tes_pb_k_eff"), // [W/m K] Effective thermal conductivity + as_double("tes_pb_void_frac"), // [] Packed bed void fraction + as_double("tes_pb_dens_solid"), // [kg/m3] solid specific heat + as_double("tes_pb_cp_solid"), // [J/kg K] solid specific heat + as_double("tes_pb_T_hot_delta"), // [C] Max allowable decrease in hot discharge temp + as_double("tes_pb_T_cold_delta"), // [C] Max allowable increase in cold discharge temp + as_double("tes_pb_T_charge_min") // [C] Min allowable charge temperature + ); + + storage_pointer = &storage_packedbed; + + // Set storage outputs + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_Q_DOT_LOSS, allocate("tank_losses", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_W_DOT_HEATER, allocate("q_tes_heater", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_TES_T_HOT, allocate("T_tes_hot", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_TES_T_COLD, allocate("T_tes_cold", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_M_DOT_TANK_TO_TANK, allocate("m_dot_cold_tank_to_hot_tank", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_MASS_COLD_TANK, allocate("mass_tes_cold", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_MASS_HOT_TANK, allocate("mass_tes_hot", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_W_DOT_HTF_PUMP, allocate("tes_htf_pump_power", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_VOL_TOT, allocate("vol_tes_tot", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_MASS_TOT, allocate("tes_mass_tot", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_0, allocate("T_grad_0", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_1, allocate("T_grad_1", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_2, allocate("T_grad_2", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_3, allocate("T_grad_3", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_4, allocate("T_grad_4", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_5, allocate("T_grad_5", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_6, allocate("T_grad_6", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_7, allocate("T_grad_7", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_8, allocate("T_grad_8", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_9, allocate("T_grad_9", n_steps_fixed), n_steps_fixed); + } + + else + { + } - // ************************************************************************* // Schedules // Off-taker schedule C_timeseries_schedule_inputs offtaker_schedule; - bool assigned_is_timestep_fractions = is_assigned("is_timestep_load_fractions"); - bool is_timestep_load_fractions = false; - if (assigned_is_timestep_fractions) { - is_timestep_load_fractions = as_boolean("is_timestep_load_fractions"); - } + bool is_timestep_load_fractions = as_boolean("is_timestep_load_fractions"); if (is_timestep_load_fractions) { auto vec = as_vector_double("timestep_load_fractions"); C_timeseries_schedule_inputs offtaker_series = C_timeseries_schedule_inputs(vec, std::numeric_limits::quiet_NaN()); @@ -1463,6 +1709,8 @@ class cm_trough_physical : public compute_module } else { elec_pricing_schedule = C_timeseries_schedule_inputs(-1.0, std::numeric_limits::quiet_NaN()); + // TMB 2024.01.31 Set en_electricity_rates to 'on' + assign("en_electricity_rates", 1); } } else if (csp_financial_model == 6) { // use 'mp_energy_market_revenue' -> from Merchant Plant model @@ -1629,7 +1877,7 @@ class cm_trough_physical : public compute_module C_csp_solver csp_solver(weather_reader, c_trough, *p_csp_power_cycle, - storage, + *storage_pointer, tou, dispatch, system, @@ -1752,6 +2000,7 @@ class cm_trough_physical : public compute_module // ******************************** // ******************************** double nameplate_des; + double Q_tes = std::numeric_limits::quiet_NaN(); { // System Design { @@ -1818,25 +2067,46 @@ class cm_trough_physical : public compute_module // Thermal Storage { double V_tes_htf_avail_calc /*m3*/, V_tes_htf_total_calc /*m3*/, - d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, + h_tank_calc /*m*/, d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, Q_tes_des_calc /*MWt-hr*/; - storage.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, - d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + double tes_htf_min_temp = 0; + double tes_htf_max_temp = 0; + double vol_min = 0; + + if (tes_type == 0) + { + storage_two_tank.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, + h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + + tes_htf_min_temp = storage_two_tank.get_min_storage_htf_temp() - 273.15; + tes_htf_max_temp = storage_two_tank.get_max_storage_htf_temp() - 273.15; + vol_min = V_tes_htf_total_calc * (storage_two_tank.m_h_tank_min / h_tank_calc); + } + else if (tes_type == 1) + { + storage_NT.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, + h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + } + else if (tes_type == 2) + { + storage_packedbed.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, + h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + } + Q_tes = Q_tes_des_calc; - double vol_min = V_tes_htf_total_calc * (storage.m_h_tank_min / storage.m_h_tank); - double V_tank_hot_ini = (as_double("h_tank_min") / as_double("h_tank")) * V_tes_htf_total_calc; // m3 + double V_tank_hot_ini = (as_double("h_tank_min") / h_tank_calc) * V_tes_htf_total_calc; // m3 double T_avg = (as_double("T_loop_in_des") + as_double("T_loop_out")) / 2.0; // C - double tes_htf_min_temp = storage.get_min_storage_htf_temp() - 273.15; - double tes_htf_max_temp = storage.get_max_storage_htf_temp() - 273.15; + assign("q_tes", Q_tes_des_calc); // MWt-hr assign("tes_avail_vol", V_tes_htf_avail_calc); // m3 assign("vol_tank", V_tes_htf_total_calc); // m3 + assign("csp_pt_tes_tank_height", h_tank_calc); // m assign("csp_pt_tes_tank_diameter", d_tank_calc); // m assign("q_dot_tes_est", q_dot_loss_tes_des_calc); // MWt assign("csp_pt_tes_htf_density", dens_store_htf_at_T_ave_calc); // kg/m3 - assign("is_hx", storage.get_is_hx()); + assign("is_hx", 0); assign("vol_min", vol_min); // m3 assign("V_tank_hot_ini", V_tank_hot_ini); // m3 assign("tes_htf_avg_temp", T_avg); // C @@ -2023,7 +2293,6 @@ class cm_trough_physical : public compute_module double site_improvements_area = c_trough.m_Ap_tot; double solar_field_area = c_trough.m_Ap_tot; double htf_system_area = c_trough.m_Ap_tot; - double Q_tes = q_dot_cycle_des * as_double("tshours"); double P_ref = as_double("P_ref"); // MWe double fossil_backup_mwe = P_ref; // MWe double power_plant_mwe = P_ref; // MWe @@ -2190,10 +2459,10 @@ class cm_trough_physical : public compute_module } // Non-timeseries array outputs - double P_adj = storage.P_in_des; // slightly adjust all field design pressures to account for pressure drop in TES before hot tank - transform(c_trough.m_P_rnr_dsn.begin(), c_trough.m_P_rnr_dsn.end(), c_trough.m_P_rnr_dsn.begin(), [P_adj](double x) {return x + P_adj; }); - transform(c_trough.m_P_hdr_dsn.begin(), c_trough.m_P_hdr_dsn.end(), c_trough.m_P_hdr_dsn.begin(), [P_adj](double x) {return x + P_adj; }); - transform(c_trough.m_P_loop_dsn.begin(), c_trough.m_P_loop_dsn.end(), c_trough.m_P_loop_dsn.begin(), [P_adj](double x) {return x + P_adj; }); + //double P_adj = storage.P_in_des; // slightly adjust all field design pressures to account for pressure drop in TES before hot tank + //transform(c_trough.m_P_rnr_dsn.begin(), c_trough.m_P_rnr_dsn.end(), c_trough.m_P_rnr_dsn.begin(), [P_adj](double x) {return x + P_adj; }); + //transform(c_trough.m_P_hdr_dsn.begin(), c_trough.m_P_hdr_dsn.end(), c_trough.m_P_hdr_dsn.begin(), [P_adj](double x) {return x + P_adj; }); + //transform(c_trough.m_P_loop_dsn.begin(), c_trough.m_P_loop_dsn.end(), c_trough.m_P_loop_dsn.begin(), [P_adj](double x) {return x + P_adj; }); ssc_number_t *p_pipe_runner_diams = allocate("pipe_runner_diams", c_trough.m_D_runner.size()); std::copy(c_trough.m_D_runner.begin(), c_trough.m_D_runner.end(), p_pipe_runner_diams); @@ -2234,20 +2503,25 @@ class cm_trough_physical : public compute_module ssc_number_t *p_pipe_loop_P_dsn = allocate("pipe_loop_P_dsn", c_trough.m_P_loop_dsn.size()); std::copy(c_trough.m_P_loop_dsn.begin(), c_trough.m_P_loop_dsn.end(), p_pipe_loop_P_dsn); - ssc_number_t *p_pipe_tes_diams = allocate("pipe_tes_diams", storage.pipe_diams.ncells()); - std::copy(storage.pipe_diams.data(), storage.pipe_diams.data() + storage.pipe_diams.ncells(), p_pipe_tes_diams); - ssc_number_t *p_pipe_tes_wallthk = allocate("pipe_tes_wallthk", storage.pipe_wall_thk.ncells()); - std::copy(storage.pipe_wall_thk.data(), storage.pipe_wall_thk.data() + storage.pipe_wall_thk.ncells(), p_pipe_tes_wallthk); - ssc_number_t* p_pipe_tes_lengths = allocate("pipe_tes_lengths", storage.pipe_lengths.ncells()); - std::copy(storage.pipe_lengths.data(), storage.pipe_lengths.data() + storage.pipe_lengths.ncells(), p_pipe_tes_lengths); - ssc_number_t *p_pipe_tes_mdot_dsn = allocate("pipe_tes_mdot_dsn", storage.pipe_m_dot_des.ncells()); - std::copy(storage.pipe_m_dot_des.data(), storage.pipe_m_dot_des.data() + storage.pipe_m_dot_des.ncells(), p_pipe_tes_mdot_dsn); - ssc_number_t *p_pipe_tes_vel_dsn = allocate("pipe_tes_vel_dsn", storage.pipe_vel_des.ncells()); - std::copy(storage.pipe_vel_des.data(), storage.pipe_vel_des.data() + storage.pipe_vel_des.ncells(), p_pipe_tes_vel_dsn); - ssc_number_t *p_pipe_tes_T_dsn = allocate("pipe_tes_T_dsn", storage.pipe_T_des.ncells()); - std::copy(storage.pipe_T_des.data(), storage.pipe_T_des.data() + storage.pipe_T_des.ncells(), p_pipe_tes_T_dsn); - ssc_number_t *p_pipe_tes_P_dsn = allocate("pipe_tes_P_dsn", storage.pipe_P_des.ncells()); - std::copy(storage.pipe_P_des.data(), storage.pipe_P_des.data() + storage.pipe_P_des.ncells(), p_pipe_tes_P_dsn); + // Two Tank specific outputs + if (tes_type == 0) + { + ssc_number_t* p_pipe_tes_diams = allocate("pipe_tes_diams", storage_two_tank.pipe_diams.ncells()); + std::copy(storage_two_tank.pipe_diams.data(), storage_two_tank.pipe_diams.data() + storage_two_tank.pipe_diams.ncells(), p_pipe_tes_diams); + ssc_number_t* p_pipe_tes_wallthk = allocate("pipe_tes_wallthk", storage_two_tank.pipe_wall_thk.ncells()); + std::copy(storage_two_tank.pipe_wall_thk.data(), storage_two_tank.pipe_wall_thk.data() + storage_two_tank.pipe_wall_thk.ncells(), p_pipe_tes_wallthk); + ssc_number_t* p_pipe_tes_lengths = allocate("pipe_tes_lengths", storage_two_tank.pipe_lengths.ncells()); + std::copy(storage_two_tank.pipe_lengths.data(), storage_two_tank.pipe_lengths.data() + storage_two_tank.pipe_lengths.ncells(), p_pipe_tes_lengths); + ssc_number_t* p_pipe_tes_mdot_dsn = allocate("pipe_tes_mdot_dsn", storage_two_tank.pipe_m_dot_des.ncells()); + std::copy(storage_two_tank.pipe_m_dot_des.data(), storage_two_tank.pipe_m_dot_des.data() + storage_two_tank.pipe_m_dot_des.ncells(), p_pipe_tes_mdot_dsn); + ssc_number_t* p_pipe_tes_vel_dsn = allocate("pipe_tes_vel_dsn", storage_two_tank.pipe_vel_des.ncells()); + std::copy(storage_two_tank.pipe_vel_des.data(), storage_two_tank.pipe_vel_des.data() + storage_two_tank.pipe_vel_des.ncells(), p_pipe_tes_vel_dsn); + ssc_number_t* p_pipe_tes_T_dsn = allocate("pipe_tes_T_dsn", storage_two_tank.pipe_T_des.ncells()); + std::copy(storage_two_tank.pipe_T_des.data(), storage_two_tank.pipe_T_des.data() + storage_two_tank.pipe_T_des.ncells(), p_pipe_tes_T_dsn); + ssc_number_t* p_pipe_tes_P_dsn = allocate("pipe_tes_P_dsn", storage_two_tank.pipe_P_des.ncells()); + std::copy(storage_two_tank.pipe_P_des.data(), storage_two_tank.pipe_P_des.data() + storage_two_tank.pipe_P_des.ncells(), p_pipe_tes_P_dsn); + } + // Monthly outputs diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 17fbeb0bb..540a80b1b 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -85,7 +85,8 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "Row_Distance", "Spacing between rows (centerline to centerline)", "m", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "T_loop_in_des", "Design loop inlet temperature", "C", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "T_loop_out", "Target loop outlet temperature", "C", "", "solar_field", "*", "", "" }, - //{ SSC_INPUT, SSC_NUMBER, "T_startup", "Required temperature of the system before the power block can be switched on", "C", "", "solar_field", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "T_startup", "Required temperature of the system before the power block can be switched on", "C", "", "solar_field", "?", "", "" }, + { SSC_INPUT, SSC_NUMBER, "T_shutdown", "Temperature when solar field begins recirculating", "C", "", "solar_field", "?", "", "" }, { SSC_INPUT, SSC_NUMBER, "use_abs_or_rel_mdot_limit", "Use mass flow abs (0) or relative (1) limits", "", "", "solar_field", "?=0", "", "" }, @@ -176,7 +177,9 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "store_fluid", "Material number for storage fluid", "-", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_MATRIX, "store_fl_props", "User defined storage fluid property data", "-", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "tshours", "Equivalent full-load thermal storage hours", "hr", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "h_tank", "Total height of tank (height of HTF when tank is full", "m", "", "TES", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "is_h_tank_fixed", "[1] Use fixed height (calculate diameter) [0] Use fixed diameter", "-", "", "TES", "?=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "h_tank_in", "Total height of tank input (height of HTF when tank is full", "m", "", "TES", "is_h_tank_fixed=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "d_tank_in", "Tank diameter input", "m", "", "TES", "is_h_tank_fixed=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "tank_pairs", "Number of equivalent tank pairs", "-", "", "TES", "*", "INTEGER", "" }, { SSC_INPUT, SSC_NUMBER, "hot_tank_Thtr", "Minimum allowable hot tank HTF temp", "C", "", "TES", "*", "", "" }, @@ -381,6 +384,7 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "vol_tank", "Total tank volume", "m3", "", "Thermal Storage","*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "q_tes", "TES design capacity", "MWt-hr", "", "Thermal Storage","*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "csp_pt_tes_tank_diameter", "Tank diameter", "m", "", "Thermal Storage","*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "csp_pt_tes_tank_height", "Tank height", "m", "", "Thermal Storage","*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "q_dot_tes_est", "Estimated TES Heat Loss", "MW", "", "Thermal Storage","*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "csp_pt_tes_htf_density", "Storage htf density", "kg/m3", "", "Thermal Storage","*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "tes_avail_vol", "Available HTF volume", "m3", "", "Thermal Storage","*", "", "" }, @@ -771,18 +775,18 @@ class cm_trough_physical_iph : public compute_module double T_startup_old = max(T_startup_min, 0.67 * T_loop_in_des + 0.33 * T_loop_out_des); //[C] double T_startup = T_startup_old; - if (is_assigned("T_startup") == true) + if (is_assigned("T_startup")) { - T_startup = as_double("T_startup"); //[C] The required temperature (converted to K in init) of the system before the power block can be switched on + T_startup = as_double("T_startup"); } - double T_shutdown = std::numeric_limits::quiet_NaN(); - if (is_assigned("T_shutdown") == true) + double T_shutdown = T_startup; + if (is_assigned("T_shutdown")) { T_shutdown = as_double("T_shutdown"); } - c_trough.m_T_startup = T_startup; + c_trough.m_T_startup = T_startup; //[C] The required temperature (converted to K in init) of the system before the power block can be switched on c_trough.m_T_shutdown = T_shutdown; //[C] c_trough.m_use_abs_or_rel_mdot_limit = as_integer("use_abs_or_rel_mdot_limit"); // Use mass flow abs (0) or relative (1) limits @@ -1091,6 +1095,9 @@ class cm_trough_physical_iph : public compute_module tes_diams.assign(tes_diams_val, 1); } + double h_tank_in = is_assigned("h_tank_in") == true ? as_double("h_tank_in") : std::numeric_limits::quiet_NaN(); + double d_tank_in = is_assigned("d_tank_in") == true ? as_double("d_tank_in") : std::numeric_limits::quiet_NaN(); + storage = C_csp_two_tank_tes( c_trough.m_Fluid, c_trough.m_field_fl_props, @@ -1099,7 +1106,9 @@ class cm_trough_physical_iph : public compute_module q_dot_hs_des, c_trough.m_solar_mult, Q_tes, - as_double("h_tank"), + as_boolean("is_h_tank_fixed"), + h_tank_in, + d_tank_in, as_double("u_tank"), as_integer("tank_pairs"), as_double("hot_tank_Thtr"), @@ -1511,14 +1520,17 @@ class cm_trough_physical_iph : public compute_module // Thermal Storage { double V_tes_htf_avail_calc /*m3*/, V_tes_htf_total_calc /*m3*/, - d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, + h_tank_calc /*m*/, d_tank_calc /*m*/, + q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, Q_tes_des_calc /*MWt-hr*/; storage.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, - d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + h_tank_calc, d_tank_calc, + q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + - double vol_min = V_tes_htf_total_calc * (storage.m_h_tank_min / storage.m_h_tank); - double V_tank_hot_ini = (as_double("h_tank_min") / as_double("h_tank")) * V_tes_htf_total_calc; // m3 + double vol_min = V_tes_htf_total_calc * (storage.m_h_tank_min / h_tank_calc); + double V_tank_hot_ini = (storage.m_h_tank_min / h_tank_calc) * V_tes_htf_total_calc; // m3 double T_avg = (as_double("T_loop_in_des") + as_double("T_loop_out")) / 2.0; // C double tes_htf_min_temp = storage.get_min_storage_htf_temp() - 273.15; double tes_htf_max_temp = storage.get_max_storage_htf_temp() - 273.15; @@ -1526,6 +1538,7 @@ class cm_trough_physical_iph : public compute_module assign("q_tes", Q_tes_des_calc); // MWt-hr assign("tes_avail_vol", V_tes_htf_avail_calc); // m3 assign("vol_tank", V_tes_htf_total_calc); // m3 + assign("csp_pt_tes_tank_height", h_tank_calc); // m assign("csp_pt_tes_tank_diameter", d_tank_calc); // m assign("q_dot_tes_est", q_dot_loss_tes_des_calc); // MWt assign("csp_pt_tes_htf_density", dens_store_htf_at_T_ave_calc); // kg/m3 diff --git a/tcs/CMakeLists.txt b/tcs/CMakeLists.txt index f3d5327a0..a1f28c97d 100644 --- a/tcs/CMakeLists.txt +++ b/tcs/CMakeLists.txt @@ -24,6 +24,8 @@ set(TCS_SRC csp_solver_mspt_collector_receiver.cpp csp_solver_mspt_receiver.cpp csp_solver_mspt_receiver_222.cpp + csp_solver_NTHeatTrap_tes.cpp + csp_solver_packedbed_tes.cpp csp_solver_pc_gen.cpp csp_solver_pc_heat_sink.cpp csp_solver_pc_ptes.cpp @@ -32,6 +34,7 @@ set(TCS_SRC csp_solver_pt_receiver.cpp csp_solver_pt_sf_perf_interp.cpp csp_solver_stratified_tes.cpp + csp_solver_tes_core.cpp csp_solver_tou_block_schedules.cpp csp_solver_trough_collector_receiver.cpp csp_solver_two_tank_tes.cpp @@ -117,6 +120,8 @@ set(TCS_SRC csp_solver_mspt_collector_receiver.h csp_solver_mspt_receiver.h csp_solver_mspt_receiver_222.h + csp_solver_NTHeatTrap_tes.h + csp_solver_packedbed_tes.h csp_solver_pc_gen.h csp_solver_pc_heat_sink.h csp_solver_pc_ptes.h @@ -125,6 +130,7 @@ set(TCS_SRC csp_solver_pt_receiver.h csp_solver_pt_sf_perf_interp.h csp_solver_stratified_tes.h + csp_solver_tes_core.h csp_solver_trough_collector_receiver.h csp_solver_two_tank_tes.h csp_solver_util.h diff --git a/tcs/csp_solver_NTHeatTrap_tes.cpp b/tcs/csp_solver_NTHeatTrap_tes.cpp new file mode 100644 index 000000000..5cb044d08 --- /dev/null +++ b/tcs/csp_solver_NTHeatTrap_tes.cpp @@ -0,0 +1,2094 @@ +/* +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 "csp_solver_NTHeatTrap_tes.h" + +static C_csp_reported_outputs::S_output_info S_output_info[] = +{ + {C_csp_NTHeatTrap_tes::E_Q_DOT_LOSS, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] TES thermal losses + {C_csp_NTHeatTrap_tes::E_W_DOT_HEATER, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWe] TES freeze protection power + {C_csp_NTHeatTrap_tes::E_TES_T_HOT, C_csp_reported_outputs::TS_LAST}, //[C] TES final hot tank temperature + {C_csp_NTHeatTrap_tes::E_TES_T_COLD, C_csp_reported_outputs::TS_LAST}, //[C] TES cold temperature at end of timestep + {C_csp_NTHeatTrap_tes::E_M_DOT_TANK_TO_TANK, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] TES thermal losses + {C_csp_NTHeatTrap_tes::E_MASS_COLD_TANK, C_csp_reported_outputs::TS_LAST}, //[kg] Mass in cold tank at end of timestep + {C_csp_NTHeatTrap_tes::E_MASS_HOT_TANK, C_csp_reported_outputs::TS_LAST}, //[kg] Mass in hot tank at end of timestep + {C_csp_NTHeatTrap_tes::E_HOT_TANK_HTF_PERC_FINAL, C_csp_reported_outputs::TS_LAST}, //[%] Final percent fill of available hot tank mass + {C_csp_NTHeatTrap_tes::E_W_DOT_HTF_PUMP, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWe] + + {C_csp_NTHeatTrap_tes::E_VOL_COLD, C_csp_reported_outputs::TS_LAST}, //[m3] + {C_csp_NTHeatTrap_tes::E_VOL_HOT, C_csp_reported_outputs::TS_LAST}, //[m3] + {C_csp_NTHeatTrap_tes::E_VOL_TOT, C_csp_reported_outputs::TS_LAST}, //[m3] + {C_csp_NTHeatTrap_tes::E_PIST_LOC, C_csp_reported_outputs::TS_LAST}, //[m] + {C_csp_NTHeatTrap_tes::E_PIST_FRAC, C_csp_reported_outputs::TS_LAST}, //[] + {C_csp_NTHeatTrap_tes::E_COLD_FRAC, C_csp_reported_outputs::TS_LAST}, //[] + {C_csp_NTHeatTrap_tes::E_MASS_TOT, C_csp_reported_outputs::TS_LAST}, //[kg] + {C_csp_NTHeatTrap_tes::E_SA_COLD, C_csp_reported_outputs::TS_LAST}, //[kg] + {C_csp_NTHeatTrap_tes::E_SA_HOT, C_csp_reported_outputs::TS_LAST}, //[kg] + {C_csp_NTHeatTrap_tes::E_SA_TOT, C_csp_reported_outputs::TS_LAST}, //[kg] + {C_csp_NTHeatTrap_tes::E_ERROR, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] + {C_csp_NTHeatTrap_tes::E_ERROR_PERCENT, C_csp_reported_outputs::TS_LAST}, //[%] + {C_csp_NTHeatTrap_tes::E_LEAK_ERROR, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] + {C_csp_NTHeatTrap_tes::E_E_HOT, C_csp_reported_outputs::TS_LAST}, //[MJ] + {C_csp_NTHeatTrap_tes::E_E_COLD, C_csp_reported_outputs::TS_LAST}, //[MJ] + {C_csp_NTHeatTrap_tes::E_WALL_ERROR, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MW] + {C_csp_NTHeatTrap_tes::E_ERROR_CORRECTED, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MW] + {C_csp_NTHeatTrap_tes::E_EXP_WALL_MASS, C_csp_reported_outputs::TS_LAST}, //[kg] + {C_csp_NTHeatTrap_tes::E_EXP_LENGTH, C_csp_reported_outputs::TS_LAST}, //[m] + csp_info_invalid +}; + + +C_storage_tank_dynamic_NT::C_storage_tank_dynamic_NT() +{ + m_V_prev = m_T_prev = m_m_prev = m_E_prev = + + m_V_total = m_V_active = m_V_inactive = + + m_T_htr = m_max_q_htr = m_radius = + m_T_design = m_mass_total = m_mass_inactive = m_mass_active = std::numeric_limits::quiet_NaN(); +} + +void C_storage_tank_dynamic_NT::init(HTFProperties htf_class_in, double V_tank /*m3*/, + double h_tank /*m*/, double h_min /*m*/, double u_tank /*W/m2-K*/, + double tank_pairs /*-*/, double T_htr /*K*/, double max_q_htr /*MWt*/, + double V_ini /*m3*/, double T_ini /*K*/, + double T_design /*K*/, + double tank_wall_cp, // [J/kg-K] Tank wall specific heat + double tank_wall_dens, // [kg/m3] Tank wall density + double tank_wall_thick, // [m] Tank wall thickness) + double nstep, // [] Number of time steps for energy balance iteration + std::vector piston_loss_poly //[] Coefficients to piston loss polynomial + ) +{ + mc_htf = htf_class_in; + + double rho_des = mc_htf.dens(T_design, 1.0); //[kg/m^3] Density at average temperature + + m_V_total = V_tank; //[m^3] + + m_mass_total = m_V_total * rho_des; //[kg] + + m_V_inactive = m_V_total * h_min / h_tank; //[m^3] + + m_mass_inactive = m_V_inactive * rho_des; //[kg] + + m_V_active = m_V_total - m_V_inactive; //[m^3] + + m_mass_active = m_mass_total - m_mass_inactive; //[kg] + + double A_cs = m_V_total / (h_tank * tank_pairs); //[m^2] Cross-sectional area of a single tank + + double diameter = pow(A_cs / CSP::pi, 0.5) * 2.0; //[m] Diameter of a single tank + + m_radius = diameter / 2.0; // [m] radius + m_tank_wall_thick = tank_wall_thick; // [m] tank wall thickness + m_tank_wall_dens = tank_wall_dens; //[kg/m3] + m_tank_wall_cp = tank_wall_cp; //[J/kgK] + m_nstep = nstep; //[] + m_piston_loss_poly = piston_loss_poly; //[] + + // Calculate tank conductance + m_u_tank = u_tank; + + m_T_htr = T_htr; + m_max_q_htr = max_q_htr; + + m_V_prev = V_ini; + m_T_prev = T_ini; + m_m_prev = calc_mass_at_prev(); + m_m_wall_prev = calc_mass_wall(m_T_prev, m_m_prev); +} + +double C_storage_tank_dynamic_NT::calc_mass_at_prev() +{ + return m_V_prev * mc_htf.dens(m_T_prev, 1.0); //[kg] +} + +double C_storage_tank_dynamic_NT::get_m_UA() +{ + // Calculate UA value + double SA_prev = calc_SA(m_V_calc); + double UA = m_u_tank * SA_prev; + + return UA; //[W/K] +} + +double C_storage_tank_dynamic_NT::get_m_T_prev() +{ + return m_T_prev; //[K] +} + +double C_storage_tank_dynamic_NT::get_m_T_calc() +{ + return m_T_calc; +} + +double C_storage_tank_dynamic_NT::get_m_m_calc() // Get Current FLUID mass +{ + return m_m_calc; //[kg] +} + +double C_storage_tank_dynamic_NT::get_m_m_prev() // Get Previous FLUID mass +{ + return m_m_prev; //[kg] +} + +double C_storage_tank_dynamic_NT::get_m_E_prev() +{ + return m_E_prev; //[MJ] +} + +double C_storage_tank_dynamic_NT::get_m_E_calc() +{ + return m_E_calc; //[MJ] +} + +double C_storage_tank_dynamic_NT::get_vol_frac() +{ + return (m_V_prev - m_V_inactive) / m_V_active; +} + +double C_storage_tank_dynamic_NT::get_mass_avail() +{ + return std::max(m_m_prev - m_mass_inactive, 0.0); //[kg] +} + +double C_storage_tank_dynamic_NT::get_fluid_vol() +{ + return m_V_calc; +} + +double C_storage_tank_dynamic_NT::get_fluid_vol_prev() +{ + return m_V_prev; +} + +double C_storage_tank_dynamic_NT::get_radius() +{ + return m_radius; +} + +double C_storage_tank_dynamic_NT::get_SA_calc() +{ + return m_SA_calc; +} + +double C_storage_tank_dynamic_NT::calc_mass_wall(double T_fluid, double mass_fluid) +{ + double rho_fluid = mc_htf.dens(T_fluid, 0); // kg/m3 + double V_fluid = mass_fluid / rho_fluid; // m3 + double Ac_fluid = CSP::pi * std::pow(m_radius, 2.0); // m2 + double L_fluid = V_fluid / Ac_fluid; // m + + double Ac_wall = (CSP::pi * std::pow(m_radius + m_tank_wall_thick, 2.0)) - (CSP::pi * std::pow(m_radius, 2.0)); + double V_wall = L_fluid * Ac_wall; + double mass_wall = V_wall * m_tank_wall_dens; + + return mass_wall; +} + +double C_storage_tank_dynamic_NT::get_m_m_wall_prev() +{ + return m_m_wall_prev; +} + +double C_storage_tank_dynamic_NT::get_m_m_wall_calc() +{ + return m_m_wall_calc; +} + +double C_storage_tank_dynamic_NT::get_m_L_calc() +{ + return m_L_calc; +} + +double C_storage_tank_dynamic_NT::m_dot_available(double f_unavail, double timestep) +{ + //double rho = mc_htf.dens(m_T_prev, 1.0); //[kg/m^3] + //double V = m_m_prev / rho; //[m^3] Volume available in tank (one temperature) + //double V_avail = fmax(V - m_V_inactive, 0.0); //[m^3] Volume that is active - need to maintain minimum height (corresponding m_V_inactive) + + double mass_avail = get_mass_avail(); //[kg] + double m_dot_avail = std::max(mass_avail - m_mass_active * f_unavail, 0.0) / timestep; //[kg/s] + + // "Unavailable" fraction now applied to one temperature tank volume, not total tank volume + //double m_dot_avail = fmax(V_avail - m_V_active*f_unavail, 0.0)*rho / timestep; //[kg/s] Max mass flow rate available + + return m_dot_avail; //[kg/s] +} + +void C_storage_tank_dynamic_NT::converged() +{ + // Reset 'previous' timestep values to 'calculated' values + m_V_prev = m_V_calc; //[m^3] + m_T_prev = m_T_calc; //[K] + m_m_prev = m_m_calc; //[kg] + m_SA_prev = m_SA_calc; //[m2] + m_E_prev = m_E_calc; //[MJ] + m_m_wall_prev = m_m_wall_calc; //[kg] +} + +void C_storage_tank_dynamic_NT::energy_balance_core(double timestep /*s*/, double mdot_fluid_in_before_leak /*kg/s*/, double mdot_fluid_out_before_leak /*kg/s*/, + double T_fluid_in /*K*/, double T_amb /*K*/, double mass_fluid_prev_inner /*kg*/, + double T_tank_in /*K*/, double T_prev_inner /*K*/, + double T_leak_in /*K*/, + double& T_ave /*K*/, double& q_heater /*MW*/, double& q_dot_loss /*MW*/, + double& mass_fluid_calc_inner /*kg*/, double& T_calc_inner /*K*/, double& q_dot_out /*MW*/, + double& q_dot_error_inner /*MW*/) +{ + // Get properties from tank state at the end of last time step + double rho_fluid_prev = mc_htf.dens(T_prev_inner, 1.0); //[kg/m^3] + double cp_fluid_prev = mc_htf.Cp(T_prev_inner) * 1000.0; //[J/kg-K] spec heat, convert from kJ/kg-K + + // Calculate Leakage + double mdot_leak_in; + double mdot_leak_out; + { + double leak_frac_in = calc_leakage_fraction(mdot_fluid_out_before_leak); + double leak_frac_out = calc_leakage_fraction(mdot_fluid_in_before_leak); + + mdot_leak_in = leak_frac_in * mdot_fluid_out_before_leak; + mdot_leak_out = leak_frac_out * mdot_fluid_in_before_leak; + } + + // Calculate Net Mass Flows + double mdot_fluid_in_net = mdot_fluid_in_before_leak + mdot_leak_in; + double mdot_fluid_out_net = mdot_fluid_out_before_leak + mdot_leak_out; + + // Get Fluid Beginning volume + double V_prev_inner = mass_fluid_prev_inner / rho_fluid_prev; // [m3] + + // Calculate Fluid ending volume levels + mass_fluid_calc_inner = mass_fluid_prev_inner + timestep * (mdot_fluid_in_net - mdot_fluid_out_net); //[kg] Available mass at the end of this timestep + + double mass_fluid_min, mdot_fluid_out_adj; + bool tank_is_empty = false; + + mass_fluid_min = 0.00; //[kg] minimum tank mass for use in the calculations + if (mass_fluid_calc_inner < mass_fluid_min) { + mass_fluid_calc_inner = mass_fluid_min; + tank_is_empty = true; + mdot_fluid_out_adj = mdot_fluid_in_net - (mass_fluid_min - mass_fluid_prev_inner) / timestep; + } + else { + mdot_fluid_out_adj = mdot_fluid_out_net; + } + m_V_calc = mass_fluid_calc_inner / rho_fluid_prev; //[m^3] Available volume at end of timestep (using initial temperature...) + m_m_calc = mass_fluid_calc_inner; + + // Check for continual empty tank + if (mass_fluid_prev_inner <= 1e-4 && tank_is_empty == true) { + if (mdot_fluid_in_net > 0) { + T_calc_inner = T_ave = T_fluid_in; + } + else { + T_calc_inner = T_ave = T_prev_inner; + } + q_dot_loss = m_V_calc = mass_fluid_calc_inner = q_heater = 0.; + return; + } + + double diff_m_dot = mdot_fluid_in_net - mdot_fluid_out_adj; //[kg/s] + if (diff_m_dot >= 0.0) + { + diff_m_dot = std::max(diff_m_dot, 1.E-5); + } + else + { + diff_m_dot = std::min(diff_m_dot, -1.E-5); + } + + // Calculate Cross Sectional Areas + double Ac_fluid = CSP::pi * std::pow(m_radius, 2.0); + double Ac_wall = (CSP::pi * std::pow(m_radius + m_tank_wall_thick, 2.0)) - (CSP::pi * std::pow(m_radius, 2.0)); + + // Calculate Lengths + double L_prev = V_prev_inner / Ac_fluid; + m_L_calc = m_V_calc / Ac_fluid; + double L_change = (m_V_calc - V_prev_inner) / Ac_fluid; + double L_change_validate = m_L_calc - L_prev; + + // Calculate Beginning and End Wall Volumes + double V_wall_prev = L_prev * Ac_wall; + double V_wall_calc = m_L_calc * Ac_wall; + double V_wall_change = L_change * Ac_wall; + + // Calculate Wall Mass + double mass_wall_prev = m_tank_wall_dens * V_wall_prev; + double mass_wall_calc = m_tank_wall_dens * V_wall_calc; + double mass_wall_change = m_tank_wall_dens * V_wall_change; + double mdot_wall_change = mass_wall_change / timestep; + m_m_wall_calc = mass_wall_calc; + + // Calculate Wall Surface Areas + double SA_wall_prev = L_prev * 2.0 * CSP::pi * (m_radius + m_tank_wall_thick); + m_SA_calc = m_L_calc * 2.0 * CSP::pi * (m_radius + m_tank_wall_thick); + double SA_wall_change = L_change * 2.0 * CSP::pi * (m_radius + m_tank_wall_thick); + + + double mdot_in_wall = 0; // [kg/s] mass gained from expanding + double mdot_out_wall = 0; // [kg/s] mass lost from contracting + + if (mdot_wall_change > 0) + { + mdot_in_wall = mdot_wall_change; + mdot_out_wall = 0; + } + else if (mdot_wall_change < 0) + { + mdot_in_wall = 0; + mdot_out_wall = std::abs(mdot_wall_change); + } + + double T_in_weighted; + double cp_in_weighted; + double cp_out_weighted; + double cp_bulk_weighted_calc; + double mdot_in_total; + double mdot_out_total; + + // If Fluid is coming in (leak going out) + if((mdot_fluid_in_before_leak - mdot_fluid_out_before_leak) > 0) + { + // Fluid In + double cp_fluid_in = mc_htf.Cp(T_fluid_in) * 1000.0; // J/kg K + double mass_fluid_in = mdot_fluid_in_before_leak * timestep; + //double T_fluid_in = T_fluid_in; + + // Wall In + double cp_wall_in = m_tank_wall_cp; + double mass_wall_in = mdot_in_wall * timestep; + double T_wall_in = T_tank_in; + + // Adjust Wall Mass IN (to allow for cp correction) + double cp_wall_in_corrected = cp_fluid_in; // J/kg K + double mass_wall_in_corrected = mass_wall_in * (cp_wall_in / cp_wall_in_corrected); // kg + + // Stagnant Fluid + //double cp_fluid_prev = cp_fluid_prev; + double mass_fluid_stagnant = mass_fluid_prev_inner; + double T_fluid_stagnant = T_prev_inner; + + // Stagnant Wall + double cp_wall_stagnant = m_tank_wall_cp; + double mass_wall_stagnant = mass_wall_prev; + double T_wall_stagnant = T_prev_inner; + + // Adjust Wall Mass STAGNANT (to allow for cp correction) + double cp_wall_stagnant_corrected = cp_fluid_prev; // J/kg K + double mass_wall_stagnant_corrected = mass_wall_stagnant * (cp_wall_stagnant / cp_wall_stagnant_corrected); // kg + + // Total Inlet + double mass_in_total = mass_fluid_in + mass_wall_in_corrected; + mdot_in_total = mass_in_total / timestep; + double mass_cp_in_total = (mass_fluid_in * cp_fluid_in) + (mass_wall_in_corrected * cp_wall_in_corrected); + cp_in_weighted = ((cp_fluid_in * mass_fluid_in) + + (cp_wall_in_corrected * mass_wall_in_corrected)) + / mass_in_total; + + T_in_weighted = ((T_fluid_in * cp_fluid_in * mass_fluid_in) + + (T_wall_in * cp_wall_in_corrected * mass_wall_in_corrected)) + / (mass_cp_in_total); + + // Total Outlet + mdot_out_total = mdot_leak_out; + cp_out_weighted = cp_fluid_prev; + + + // Total Bulk at end of timestep + double mass_total_calc = mass_fluid_in + mass_wall_in_corrected + mass_fluid_stagnant + mass_wall_stagnant_corrected; + cp_bulk_weighted_calc = ((cp_fluid_in * mass_fluid_in) + + (cp_wall_in_corrected * mass_wall_in_corrected) + + (cp_fluid_prev * mass_fluid_stagnant) + + (cp_wall_stagnant_corrected * mass_wall_stagnant_corrected)) + / mass_total_calc; + } + + // If Fluid is leaving (leak coming in) + else + { + // Leak In + double cp_leak_in = mc_htf.Cp(T_leak_in) * 1000.0; // J/kg K + double mass_leak_in = mdot_leak_in * timestep; + + // Stagnant Fluid + double cp_fluid_stagnant = cp_fluid_prev; + double mass_fluid_stagnant = mass_fluid_calc_inner - mass_leak_in; + + // Stagnant Wall + double cp_wall_stagnant = m_tank_wall_cp; + double mass_wall_stagnant = mass_wall_calc; + + // Corrected Stagnant Wall + double cp_wall_stagnant_corrected = cp_fluid_stagnant; + double mass_wall_stagnant_corrected = mass_wall_stagnant * (cp_wall_stagnant / cp_wall_stagnant_corrected); + + // Fluid Out + double mass_fluid_out = mdot_fluid_out_adj * timestep; + double cp_fluid_out = cp_fluid_prev; + + // Wall Out + double mass_wall_out = mdot_out_wall * timestep; + double cp_wall_out = m_tank_wall_cp; + + // Corrected Wall Mass Out + double cp_wall_out_corrected = cp_fluid_out; + double mass_wall_out_corrected = mass_wall_out * (cp_wall_out / cp_wall_out_corrected); + + // Total Inlet + double mass_in_total = mass_leak_in; + mdot_in_total = mass_leak_in / timestep; + cp_in_weighted = cp_leak_in; + T_in_weighted = T_leak_in; + + // Total Outlet + double mass_out_total = mass_fluid_out + mass_wall_out_corrected; + mdot_out_total = mass_out_total / timestep; + cp_out_weighted = ((cp_fluid_out * mass_fluid_out) + + (cp_wall_out_corrected * mass_wall_out_corrected)) + / mass_out_total; + + // Total Bulk at end of timestep + double mass_total_calc = mass_leak_in + mass_fluid_stagnant + mass_wall_stagnant_corrected; + cp_bulk_weighted_calc = ((cp_leak_in * mass_leak_in) + + (cp_fluid_stagnant * mass_fluid_stagnant) + + (cp_wall_stagnant_corrected * mass_wall_stagnant_corrected)) + / mass_total_calc; + } + + // Terms used in final calculation + double diff_m_dot_total = mdot_in_total - mdot_out_total; + double mass_total_prev = mass_fluid_prev_inner + mass_wall_prev; + double UA_calc = m_u_tank * m_SA_calc; + + // Adjust Wall Mass Prev Term + double cp_wall_prev_corrected = cp_fluid_prev; + double mass_wall_prev_corrected = mass_wall_prev * (m_tank_wall_cp / cp_wall_prev_corrected); + + // OVERWRITE mass_total_prev + mass_total_prev = mass_fluid_prev_inner + mass_wall_prev_corrected; + + // Validate Mass Balance + { + double mass_total_prev = mass_wall_prev + mass_fluid_prev_inner; + double mass_in = mdot_in_total * timestep; + double mass_out = mdot_out_total * timestep; + double mass_total_calc = mass_wall_calc + mass_fluid_calc_inner; + + double balance = (mass_total_calc - mass_total_prev) - (mass_in - mass_out); + if (std::abs(balance) >= 1e-5) + { + int x = 0; + } + + } + + // Tank is either expanding or contracting + if (diff_m_dot_total != 0.0) + { + // NEW + // m_dot_in (NOW wall mass in and fluid mass in) + // T_in (NOW wall temp in (from cold or hot side) and fluid Temp in) + // cp (NOW weighted cp_weighted of wall and fluid) + // diff_m_dot (NOW total mass difference from wall and fluid) + // m_m_prev (WAS previous bulk fluid mass, NOW needs to be previous bulk fluid and wall mass) + + double a_coef_old = (mdot_in_total - mdot_in_wall) * T_fluid_in + UA_calc / cp_fluid_prev * T_amb; + double b_coef_old = (mdot_in_total - mdot_in_wall) + UA_calc / cp_fluid_prev; + double c_coef_old = diff_m_dot; + + double a_coef = mdot_in_total * T_in_weighted + (UA_calc / cp_bulk_weighted_calc) * T_amb; + double b_coef = mdot_in_total + UA_calc / cp_bulk_weighted_calc; + double c_coef = diff_m_dot_total; + + double a_coef_beta = ((cp_in_weighted / cp_bulk_weighted_calc) * mdot_in_total * T_in_weighted) + + ((UA_calc / cp_bulk_weighted_calc) * T_amb); + double b_coef_beta = mdot_in_total - mdot_out_total + + ((cp_out_weighted / cp_bulk_weighted_calc) * mdot_out_total) + + (UA_calc / cp_bulk_weighted_calc); + double c_coef_beta = mdot_in_total - mdot_out_total; + + + /*if (std::abs(a_coef - a_coef_beta) > 1e-4) + { + int l = 0; + } + if (std::abs(b_coef - b_coef_beta) > 1e-4) + { + int l = 0; + } + if (std::abs(c_coef - c_coef_beta) > 1e-4) + { + int l = 0; + }*/ + + + T_calc_inner = a_coef / b_coef; + if(mass_total_prev > 0) + T_calc_inner += (T_prev_inner - a_coef / b_coef) * pow(std::max((timestep * c_coef / mass_total_prev + 1), 0.0), -b_coef / c_coef); + + T_ave = a_coef / b_coef; + if(mass_total_prev > 0 && b_coef != c_coef) + T_ave += mass_total_prev * (T_prev_inner - a_coef / b_coef) / ((c_coef - b_coef) * timestep) + * (pow(std::max((timestep * c_coef / mass_total_prev + 1.0), 0.0), 1.0 - b_coef / c_coef) - 1.0); + + if (timestep < 1.e-6) + { + T_ave = a_coef / b_coef; + if (mass_total_prev > 0 && b_coef != c_coef) + T_ave += (T_prev_inner - a_coef / b_coef) * pow(std::max((timestep * c_coef / mass_total_prev + 1.0), 0.0), -b_coef / c_coef); // Limiting expression for small time step + } + + q_dot_loss = UA_calc * (T_ave - T_amb) / 1.E6; //[MW] + + //******* DEBUG + + double T_calc_inner_old = a_coef_old / b_coef_old + (T_prev_inner - a_coef_old / b_coef_old) * pow(std::max((timestep * c_coef_old / mass_fluid_prev_inner + 1), 0.0), -b_coef_old / c_coef_old); + double T_ave_old = a_coef_old / b_coef_old + mass_fluid_prev_inner * (T_prev_inner - a_coef_old / b_coef_old) / ((c_coef_old - b_coef_old) * timestep) * (pow(std::max((timestep * c_coef_old / mass_fluid_prev_inner + 1.0), 0.0), 1.0 - b_coef_old / c_coef_old) - 1.0); + if (timestep < 1.e-6) + T_ave_old = a_coef_old / b_coef_old + (T_prev_inner - a_coef_old / b_coef_old) * pow(std::max((timestep * c_coef_old / mass_fluid_prev_inner + 1.0), 0.0), -b_coef_old / c_coef_old); // Limiting expression for small time step + double q_dot_loss_old = UA_calc * (T_ave_old - T_amb) / 1.E6; //[MW] + + //************ + + if (T_calc_inner < m_T_htr) + { + q_heater = b_coef * ((m_T_htr - T_prev_inner * pow(std::max((timestep * c_coef / mass_total_prev + 1), 0.0), -b_coef / c_coef)) / + (-pow(std::max((timestep * c_coef / mass_total_prev + 1), 0.0), -b_coef / c_coef) + 1)) - a_coef; + + q_heater = q_heater * cp_bulk_weighted_calc; + + q_heater /= 1.E6; + + if (q_heater > m_max_q_htr) + { + q_heater = m_max_q_htr; + } + + a_coef += q_heater * 1.E6 / cp_bulk_weighted_calc; + + T_calc_inner = a_coef / b_coef; + if (mass_total_prev > 0) + T_calc_inner += (T_prev_inner - a_coef / b_coef) * pow(std::max((timestep * c_coef / mass_total_prev + 1), 0.0), -b_coef / c_coef); + + T_ave = a_coef / b_coef; + if (mass_total_prev > 0 && b_coef != c_coef) + T_ave += mass_total_prev * (T_prev_inner - a_coef / b_coef) / ((c_coef - b_coef) * timestep) * (pow(std::max((timestep * c_coef / mass_total_prev + 1.0), 0.0), 1.0 - b_coef / c_coef) - 1.0); + + + if (timestep < 1.e-6) + { + T_ave = a_coef / b_coef; + if(mass_total_prev > 0 && b_coef != c_coef) + T_ave += (T_prev_inner - a_coef / b_coef) * pow(std::max((timestep * c_coef / mass_total_prev + 1.0), 0.0), -b_coef / c_coef); // Limiting expression for small time step + } + + q_dot_loss = UA_calc * (T_ave - T_amb) / 1.E6; //[MW] + } + else + { + q_heater = 0.0; + } + } + + // Tank is stagnant + else // No mass flow rate, tank is idle + { + // NEW + // cp (NOW weighted cp_weighted of wall and fluid) + // m_m_prev (WAS previous bulk fluid mass, NOW needs to be previous bulk fluid and wall mass) + + double b_coef = UA_calc / (cp_bulk_weighted_calc * mass_total_prev); + double c_coef = UA_calc / (cp_bulk_weighted_calc * mass_total_prev) * T_amb; + + T_calc_inner = c_coef / b_coef + (T_prev_inner - c_coef / b_coef) * exp(-b_coef * timestep); + T_ave = c_coef / b_coef - (T_prev_inner - c_coef / b_coef) / (b_coef * timestep) * (exp(-b_coef * timestep) - 1.0); + if (timestep < 1.e-6) + T_ave = c_coef / b_coef + (T_prev_inner - c_coef / b_coef) * exp(-b_coef * timestep); // Limiting expression for small time step + q_dot_loss = UA_calc * (T_ave - T_amb) / 1.E6; + + if (T_calc_inner < m_T_htr) + { + q_heater = (b_coef * (m_T_htr - T_prev_inner * exp(-b_coef * timestep)) / (-exp(-b_coef * timestep) + 1.0) - c_coef) * cp_bulk_weighted_calc * mass_total_prev; + q_heater /= 1.E6; //[MW] + + if (q_heater > m_max_q_htr) + { + q_heater = m_max_q_htr; + } + + c_coef += q_heater * 1.E6 / (cp_bulk_weighted_calc * mass_total_prev); + + T_calc_inner = c_coef / b_coef + (T_prev_inner - c_coef / b_coef) * exp(-b_coef * timestep); + T_ave = c_coef / b_coef - (T_prev_inner - c_coef / b_coef) / (b_coef * timestep) * (exp(-b_coef * timestep) - 1.0); + if (timestep < 1.e-6) + T_ave = c_coef / b_coef + (T_prev_inner - c_coef / b_coef) * exp(-b_coef * timestep); // Limiting expression for small time step + q_dot_loss = UA_calc * (T_ave - T_amb) / 1.E6; //[MW] + } + else + { + q_heater = 0.0; + } + + + } + + // Validate Energy Balance + { + double mass_total_prev = mass_wall_prev + mass_fluid_prev_inner; + double cp_bulk_prev = 0; + if (mass_total_prev > 0) + { + cp_bulk_prev = ((mass_wall_prev * m_tank_wall_cp) + + (mass_fluid_prev_inner * cp_fluid_prev)) + / (mass_wall_prev + mass_fluid_prev_inner); + } + + double E_bulk_prev = mass_total_prev * cp_bulk_prev * T_prev_inner * 1e-9; + double mass_total_calc = mass_wall_calc + mass_fluid_calc_inner; + double E_bulk_calc = mass_total_calc * cp_bulk_weighted_calc * T_calc_inner * 1e-9; + + double T_out_weighted = T_ave; // Change to AVG temperature + + double E_out = 0; + if(mdot_out_total > 1e-4) + E_out = (mdot_out_total * timestep) * cp_out_weighted * T_out_weighted * 1e-9; + double E_loss_out = q_dot_loss * 1e6 * timestep * 1e-9; + double E_out_total = E_out + E_loss_out; + + double E_in = 0; + if(mdot_in_total > 1e-4) + E_in = (mdot_in_total * timestep) * cp_in_weighted * T_in_weighted * 1e-9; + double E_heater_in = q_heater * 1e6 * timestep * 1e-9; + double E_in_total = E_in + E_heater_in; + + q_dot_error_inner = ((E_bulk_calc - E_bulk_prev) - (E_in_total - E_out_total)) / timestep; //[MWt] + + if (std::abs(q_dot_error_inner) >= 1e-4) + { + int x = 0; + } + + if (std::isnan(q_dot_error_inner)) + { + int x = 0; + } + + } + + // Define Q_out + q_dot_out = mdot_fluid_out_before_leak * T_ave * cp_fluid_prev; + + // Define Internal energy + { + double wall_E_calc = m_tank_wall_cp * T_calc_inner * mass_wall_calc * 1e-6; // [MJ] + double fluid_E_calc = mc_htf.Cp(T_calc_inner) * T_calc_inner * mass_fluid_calc_inner * 1e-3; // [MJ] + m_E_calc = wall_E_calc + fluid_E_calc; // [MJ] + } + + // Define calculated variables + m_T_calc = T_calc_inner; + m_m_calc = mass_fluid_calc_inner; + + if (tank_is_empty) { + // set to actual values + m_V_calc = 0.; + mass_fluid_calc_inner = 0.; + } +} + +void C_storage_tank_dynamic_NT::energy_balance_iterated(double timestep /*s*/, double m_dot_in /*kg/s*/, double m_dot_out /*kg/s*/, + double T_in /*K*/, double T_amb /*K*/, + double T_tank_in, /*K*/ + double T_leak_in, /*K*/ + double& T_ave /*K*/, double& q_heater /*MW*/, double& q_dot_loss /*MW*/, double& q_dot_out /*MW*/, double& q_dot_error /*MW*/) +{ + double ministep = timestep / m_nstep; + + double q_heater_summed = 0; + double q_dot_loss_summed = 0; + double T_ave_innerstep = 0; + + double mass_prev_inner = m_m_prev; + double mass_calc_inner = 0; + + double T_prev_inner = m_T_prev; + double T_calc_inner = 0; + double T_ave_weighted = 0; + + double q_dot_out_inner = 0; + double q_dot_out_summed = 0; + double q_dot_error_summed = 0; + + // Run Energy Balance + for (int i = 0; i < m_nstep; i++) + { + double q_heater_innerstep, q_dot_loss_innerstep, q_dot_error_innerstep; + + + energy_balance_core(ministep, m_dot_in, m_dot_out, T_in, T_amb, mass_prev_inner, T_tank_in, T_prev_inner, T_leak_in, + T_ave_innerstep, q_heater_innerstep, q_dot_loss_innerstep, mass_calc_inner, T_calc_inner, q_dot_out_inner, q_dot_error_innerstep); + + q_heater_summed += q_heater_innerstep * (ministep / timestep); + q_dot_loss_summed += q_dot_loss_innerstep * (ministep / timestep); + q_dot_out_summed += q_dot_out_inner; + q_dot_error_summed += q_dot_error_innerstep; + + mass_prev_inner = mass_calc_inner; + T_prev_inner = T_calc_inner; + T_ave_weighted += T_ave_innerstep * (ministep / timestep); + } + + T_ave = T_ave_weighted; + q_heater = q_heater_summed; + q_dot_loss = q_dot_loss_summed; + q_dot_out = q_dot_out_summed; + q_dot_error = q_dot_error_summed; + + + m_m_calc = mass_calc_inner; + m_T_calc = T_calc_inner; + +} + + +double C_storage_tank_dynamic_NT::calc_SA_rate(double mdot_htf /*kg/s*/, double T_htf /*K*/) +{ + // Get Density + double rho = mc_htf.dens(T_htf, 1.0); + + double r = m_radius; + + double SA_rate = (2.0 * mdot_htf) / (r * rho); + + return SA_rate; +} + +double C_storage_tank_dynamic_NT::calc_mdot_expansion(double piston_rate /*m/s*/) +{ + double A_outer = CSP::pi * std::pow(m_radius + m_tank_wall_thick, 2.0); + double A_inner = CSP::pi * std::pow(m_radius, 2.0); + + double A_cross_section = A_outer - A_inner; + double volume_expansion_rate = A_cross_section * piston_rate; + double mdot_rate = volume_expansion_rate * m_tank_wall_dens; + + return mdot_rate; +} + +double C_storage_tank_dynamic_NT::calc_tank_wall_volume(double fluid_mass /*kg*/, double T_htf /*K*/) +{ + double A_outer = CSP::pi * std::pow(m_radius + m_tank_wall_thick, 2.0); + double A_inner = CSP::pi * std::pow(m_radius, 2.0); + double A_cross_section = A_outer - A_inner; + + // Get Density + double rho_htf = mc_htf.dens(T_htf, 1.0); + + double fluid_volume = fluid_mass / rho_htf; + double fluid_L = fluid_volume / (CSP::pi * std::pow(m_radius, 2.0)); + + double tank_wall_vol = A_cross_section * fluid_L; + + return tank_wall_vol; +} + +double C_storage_tank_dynamic_NT::calc_SA(double volume /*m3*/) +{ + return 2.0 * volume / m_radius; +} + +double C_storage_tank_dynamic_NT::calc_leakage_fraction(double mdot) +{ + double N_terms = m_piston_loss_poly.size(); + double frac = 0; + for (int i = 0; i < N_terms; i++) + { + frac += m_piston_loss_poly[i] * std::pow(mdot, i); + } + + frac *= 0.01; // Convert from % to fraction + + return frac; +} + + + +C_csp_NTHeatTrap_tes::C_csp_NTHeatTrap_tes( + int external_fl, // [-] external fluid identifier + util::matrix_t external_fl_props, // [-] external fluid properties + int tes_fl, // [-] tes fluid identifier + util::matrix_t tes_fl_props, // [-] tes fluid properties + double q_dot_design, // [MWt] Design heat rate in and out of tes + double frac_max_q_dot, // [-] the max design heat rate as a fraction of the nominal + double Q_tes_des, // [MWt-hr] design storage capacity + bool is_h_fixed, // [] [true] Height is input, calculate diameter, [false] diameter input, calculate height + double h_tank_in, // [m] tank height input + double d_tank_in, // [m] tank diameter input + double u_tank, // [W/m^2-K] + int tank_pairs, // [-] + double hot_tank_Thtr, // [C] convert to K in init() + double hot_tank_max_heat, // [MW] + double cold_tank_Thtr, // [C] convert to K in init() + double cold_tank_max_heat, // [MW] + double T_cold_des, // [C] convert to K in init() + double T_hot_des, // [C] convert to K in init() + double T_tank_hot_ini, // [C] Initial temperature in hot storage tank + double T_tank_cold_ini, // [C] Initial temperature in cold storage cold + double h_tank_min, // [m] Minimum allowable HTF height in storage tank + double f_V_hot_ini, // [%] Initial fraction of available volume that is hot + double htf_pump_coef, // [kW/kg/s] Pumping power to move 1 kg/s of HTF through sink + double tank_wall_cp, // [J/kg-K] Tank wall specific heat + double tank_wall_dens, // [kg/m3] Tank wall density + double tank_wall_thick, // [m] Tank wall thickness + int nstep, // [] Number of time steps for energy balance iteration + std::vector piston_loss_poly, // [] Coefficients to piston loss polynomial (0*x^0 + 1*x^1 ....) + double V_tes_des, // [m/s] Design-point velocity for sizing the diameters of the TES piping + bool calc_design_pipe_vals, // [-] Should the HTF state be calculated at design conditions + double tes_pump_coef, // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop + double eta_pump, // [-] Pump efficiency, for newer pumping calculations + bool has_hot_tank_bypass, // [-] True if the bypass valve causes the source htf to bypass just the hot tank and enter the cold tank before flowing back to the external system. + double T_tank_hot_inlet_min, // [C] Minimum source htf temperature that may enter the hot tank + bool custom_tes_p_loss, // [-] True if the TES piping losses should be calculated using the TES pipe lengths and minor loss coeffs, false if using the pumping loss parameters + bool custom_tes_pipe_sizes, // [-] True if the TES diameters and wall thicknesses parameters should be used instead of calculating them + util::matrix_t k_tes_loss_coeffs, // [-] Combined minor loss coefficients of the fittings and valves in the collection (including bypass) and generation loops in the TES + util::matrix_t tes_diams, // [m] Imported inner diameters for the TES piping as read from the modified output files + util::matrix_t tes_wallthicks, // [m] Imported wall thicknesses for the TES piping as read from the modified output files + util::matrix_t tes_lengths, // [m] Imported lengths for the TES piping as read from the modified output files + double pipe_rough, // [m] Pipe absolute roughness + double dP_discharge // [bar] Pressure drop on the TES discharge side (e.g., within the steam generator) + ) + : + m_external_fl(external_fl), m_external_fl_props(external_fl_props), m_tes_fl(tes_fl), m_tes_fl_props(tes_fl_props), + m_q_dot_design(q_dot_design), m_frac_max_q_dot(frac_max_q_dot), m_Q_tes_des(Q_tes_des), + m_is_h_fixed(is_h_fixed), m_h_tank_in(h_tank_in), m_d_tank_in(d_tank_in), + m_u_tank(u_tank), m_tank_pairs(tank_pairs), m_hot_tank_Thtr(hot_tank_Thtr), m_hot_tank_max_heat(hot_tank_max_heat), + m_cold_tank_Thtr(cold_tank_Thtr), m_cold_tank_max_heat(cold_tank_max_heat), m_T_cold_des(T_cold_des), + m_T_hot_des(T_hot_des), m_T_tank_hot_ini(T_tank_hot_ini), m_T_tank_cold_ini(T_tank_cold_ini), + m_h_tank_min(h_tank_min), m_f_V_hot_ini(f_V_hot_ini), m_htf_pump_coef(htf_pump_coef), + m_tank_wall_cp(tank_wall_cp), m_tank_wall_dens(tank_wall_dens), m_tank_wall_thick(tank_wall_thick), m_nstep(nstep), + m_piston_loss_poly(piston_loss_poly), + V_tes_des(V_tes_des), calc_design_pipe_vals(calc_design_pipe_vals), m_tes_pump_coef(tes_pump_coef), + eta_pump(eta_pump), has_hot_tank_bypass(has_hot_tank_bypass), T_tank_hot_inlet_min(T_tank_hot_inlet_min), + custom_tes_p_loss(custom_tes_p_loss), custom_tes_pipe_sizes(custom_tes_pipe_sizes), k_tes_loss_coeffs(k_tes_loss_coeffs), + tes_diams(tes_diams), tes_wallthicks(tes_wallthicks), tes_lengths(tes_lengths), + pipe_rough(pipe_rough), dP_discharge(dP_discharge), tanks_in_parallel(true) +{ + + if (tes_lengths.ncells() < 11) { + double lengths[11] = { 0., 90., 100., 120., 0., 30., 90., 80., 80., 120., 80. }; + this->tes_lengths.assign(lengths, 11); + } + + m_vol_tank = m_V_tank_active = m_q_pb_design = m_ts_hours = + m_V_tank_hot_ini = m_mass_total_active = m_h_tank_calc = m_d_tank_calc = m_q_dot_loss_des = + m_cp_external_avg = m_rho_store_avg = m_m_dot_tes_des_over_m_dot_external_des = std::numeric_limits::quiet_NaN(); + + mc_reported_outputs.construct(S_output_info); +} + +C_csp_NTHeatTrap_tes::C_csp_NTHeatTrap_tes() +{ + m_vol_tank = m_V_tank_active = m_q_pb_design = m_Q_tes_des = + m_V_tank_hot_ini = m_mass_total_active = m_h_tank_calc = m_d_tank_calc = m_q_dot_loss_des = + m_cp_external_avg = m_rho_store_avg = m_m_dot_tes_des_over_m_dot_external_des = std::numeric_limits::quiet_NaN(); + + mc_reported_outputs.construct(S_output_info); +} + + +void C_csp_NTHeatTrap_tes::init(const C_csp_tes::S_csp_tes_init_inputs init_inputs) +{ + if (!(m_Q_tes_des > 0.0)) + { + m_is_tes = false; + return; // No storage! + } + + m_is_tes = true; + + // Declare instance of fluid class for EXTERNAL fluid + // Set fluid number and copy over fluid matrix if it makes sense + if (m_external_fl != HTFProperties::User_defined && m_external_fl < HTFProperties::End_Library_Fluids) + { + if (!mc_external_htfProps.SetFluid(m_external_fl)) + { + throw(C_csp_exception("External HTF code is not recognized", "Two Tank TES Initialization")); + } + } + else if (m_external_fl == HTFProperties::User_defined) + { + int n_rows = (int)m_external_fl_props.nrows(); + int n_cols = (int)m_external_fl_props.ncols(); + if (n_rows > 2 && n_cols == 7) + { + if (!mc_external_htfProps.SetUserDefinedFluid(m_external_fl_props)) + { + error_msg = util::format(mc_external_htfProps.UserFluidErrMessage(), n_rows, n_cols); + throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); + } + } + else + { + error_msg = util::format("The user defined external HTF 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, "Two Tank TES Initialization")); + } + } + else + { + throw(C_csp_exception("External HTF code is not recognized", "Two Tank TES Initialization")); + } + + + // Declare instance of fluid class for STORAGE fluid. + // Set fluid number and copy over fluid matrix if it makes sense. + if (m_tes_fl != HTFProperties::User_defined && m_tes_fl < HTFProperties::End_Library_Fluids) + { + if (!mc_store_htfProps.SetFluid(m_tes_fl)) + { + throw(C_csp_exception("Storage HTF code is not recognized", "Two Tank TES Initialization")); + } + } + else if (m_tes_fl == HTFProperties::User_defined) + { + int n_rows = (int)m_tes_fl_props.nrows(); + int n_cols = (int)m_tes_fl_props.ncols(); + if (n_rows > 2 && n_cols == 7) + { + if (!mc_store_htfProps.SetUserDefinedFluid(m_tes_fl_props)) + { + error_msg = util::format(mc_store_htfProps.UserFluidErrMessage(), n_rows, n_cols); + throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); + } + } + else + { + error_msg = util::format("The user defined storage HTF 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, "Two Tank TES Initialization")); + } + } + else + { + throw(C_csp_exception("Storage HTF code is not recognized", "Two Tank TES Initialization")); + } + + bool is_hx_calc = false; + if (m_tes_fl != m_external_fl) + is_hx_calc = true; + else if (m_external_fl != HTFProperties::User_defined) + is_hx_calc = false; + else + { + is_hx_calc = !mc_external_htfProps.equals(&mc_store_htfProps); + } + if (is_hx_calc == true) + { + throw(C_csp_exception("NT Tank must have the same external and internal fluid", "NT TES Initialization")); + } + + if (tanks_in_parallel) { + m_is_cr_to_cold_tank_allowed = false; + } + else { + throw C_csp_exception("Tank model must be in parallel"); + } + + // Calculate thermal power to PC at design + m_q_pb_design = m_q_dot_design * 1.E6; //[Wt] + + // Convert parameter units + m_hot_tank_Thtr += 273.15; //[K] convert from C + m_cold_tank_Thtr += 273.15; //[K] convert from C + m_T_cold_des += 273.15; //[K] convert from C + m_T_hot_des += 273.15; //[K] convert from C + m_T_tank_hot_ini += 273.15; //[K] convert from C + m_T_tank_cold_ini += 273.15; //[K] convert from C + + m_ts_hours = m_Q_tes_des / m_q_dot_design; + + double d_tank_temp = std::numeric_limits::quiet_NaN(); + double q_dot_loss_temp = std::numeric_limits::quiet_NaN(); + double T_tes_hot_des, T_tes_cold_des; + + T_tes_hot_des = m_T_hot_des; + T_tes_cold_des = m_T_cold_des; + + // Size Tank with Fixed Height + if (m_is_h_fixed) + { + heattrap_tes_sizing(mc_store_htfProps, m_Q_tes_des, T_tes_hot_des, T_tes_cold_des, + m_h_tank_min, m_h_tank_in, m_tank_pairs, m_u_tank, + m_V_tank_active, m_vol_tank, m_d_tank_calc, m_q_dot_loss_des); + m_h_tank_calc = m_h_tank_in; + } + // Size Tank with Fixed Diameter + else + { + heattrap_tes_sizing_fixed_diameter(mc_store_htfProps, m_Q_tes_des, T_tes_hot_des, T_tes_cold_des, + m_h_tank_min, m_d_tank_in, m_tank_pairs, m_u_tank, + m_V_tank_active, m_vol_tank, m_h_tank_calc, m_q_dot_loss_des); + m_d_tank_calc = m_d_tank_in; + } + + // 5.13.15, twn: also be sure that hx is sized such that it can supply full load to sink + double duty = m_q_pb_design * std::max(1.0, m_frac_max_q_dot); //[W] Allow all energy from the source to go into storage at any time + + // Calculate initial storage values + + // Initial storage charge based on % mass + double T_tes_ave = 0.5 * (T_tes_hot_des + T_tes_cold_des); + double cp_ave = mc_store_htfProps.Cp_ave(T_tes_cold_des, T_tes_hot_des); //[kJ/kg-K] Specific heat at average temperature + m_rho_store_avg = mc_store_htfProps.dens(T_tes_ave, 1.0); + + //m_mass_total_active = m_Q_tes_des * 3600.0 / (cp_ave / 1000.0 * (T_tes_hot_des - T_tes_cold_des)); //[kg] Total HTF mass at design point inlet/outlet T + m_mass_total_active = m_V_tank_active * mc_store_htfProps.dens(T_tes_cold_des, 1.0); // [kg] total mass is cold fluid that fills tank + + double V_inactive = m_vol_tank - m_V_tank_active; + + + + // UPDATE INITIAL MASS + double rho_hot_des = mc_store_htfProps.dens(T_tes_hot_des, 1.0); + double rho_cold_des = mc_store_htfProps.dens(T_tes_cold_des, 1.0); + double rho_hot = mc_store_htfProps.dens(m_T_tank_hot_ini, 1.0); + double rho_cold = mc_store_htfProps.dens(m_T_tank_cold_ini, 1.0); + double m_hot_ini = m_f_V_hot_ini * 0.01 * m_mass_total_active + V_inactive * rho_hot_des; // Updating intiial storage charge calculation to avoid variation in total mass with specified initial T + double m_cold_ini = (1.0 - m_f_V_hot_ini * 0.01) * m_mass_total_active + V_inactive * rho_cold_des; + double V_hot_ini = m_hot_ini / rho_hot; + double V_cold_ini = m_cold_ini / rho_cold; + + double T_hot_ini = m_T_tank_hot_ini; //[K] + double T_cold_ini = m_T_tank_cold_ini; //[K] + + // TMB 12.15.2023 Calculate Total Length + m_length_total = m_h_tank_calc; + double volume_combined = m_vol_tank; // Total volume is actually volume of one tank (total mass can fill 1 'two tank' tank) + + // Initialize cold and hot tanks + // Hot tank + mc_hot_tank_NT.init(mc_store_htfProps, m_vol_tank, m_h_tank_calc, m_h_tank_min, + m_u_tank, m_tank_pairs, m_hot_tank_Thtr, m_hot_tank_max_heat, + V_hot_ini, T_hot_ini, T_tes_hot_des, m_tank_wall_cp, m_tank_wall_dens, m_tank_wall_thick, m_nstep, m_piston_loss_poly); + // Cold tank + mc_cold_tank_NT.init(mc_store_htfProps, m_vol_tank, m_h_tank_calc, m_h_tank_min, + m_u_tank, m_tank_pairs, m_cold_tank_Thtr, m_cold_tank_max_heat, + V_cold_ini, T_cold_ini, T_tes_cold_des, m_tank_wall_cp, m_tank_wall_dens, m_tank_wall_thick, m_nstep, m_piston_loss_poly); + + double hot_radius = mc_hot_tank_NT.get_radius(); + double cold_radius = mc_cold_tank_NT.get_radius(); + + if (hot_radius != cold_radius) + { + throw C_csp_exception("Tanks have different radius"); + } + + m_radius = hot_radius; + + // Calculate 'nominal' wall mass / volume + double Ac_wall = (CSP::pi * std::pow(m_radius + m_tank_wall_thick, 2.0)) - (CSP::pi * std::pow(m_radius, 2.0)); + m_V_wall_nominal = Ac_wall * m_length_total; //[m3] + m_mass_wall_nominal = m_tank_wall_dens * m_V_wall_nominal; //[kg] + +} + +bool C_csp_NTHeatTrap_tes::does_tes_exist() +{ + return m_is_tes; +} + +bool C_csp_NTHeatTrap_tes::is_cr_to_cold_allowed() +{ + return m_is_cr_to_cold_tank_allowed; +} + +double C_csp_NTHeatTrap_tes::get_hot_temp() +{ + return mc_hot_tank_NT.get_m_T_prev(); //[K] +} + +double C_csp_NTHeatTrap_tes::get_cold_temp() +{ + return mc_cold_tank_NT.get_m_T_prev(); //[K] +} + +double C_csp_NTHeatTrap_tes::get_hot_tank_vol_frac() +{ + return mc_hot_tank_NT.get_vol_frac(); +} + +double C_csp_NTHeatTrap_tes::get_initial_charge_energy() +{ + //MWh + if (std::isnan(m_V_tank_hot_ini)) + { + return m_q_pb_design * m_ts_hours * (m_f_V_hot_ini / 100.0) * 1.e-6; + } + else + { + //TODO: m_V_tank_hot_ini does not get initialized to user value... + return m_q_pb_design * m_ts_hours * m_V_tank_hot_ini / m_vol_tank * 1.e-6; + } +} + +double C_csp_NTHeatTrap_tes::get_min_charge_energy() +{ + //MWh + return 0.; //m_q_pb_design * m_ts_hours * m_h_tank_min / m_h_tank*1.e-6; +} + +double C_csp_NTHeatTrap_tes::get_max_charge_energy() +{ + return m_q_pb_design * m_ts_hours / 1.e6; +} + +double C_csp_NTHeatTrap_tes::get_degradation_rate() +{ + //calculates an approximate "average" tank heat loss rate based on some assumptions. Good for simple optimization performance projections. + double d_tank = sqrt(m_vol_tank / ((double)m_tank_pairs * m_h_tank_calc * 3.14159)); + double e_loss = m_u_tank * 3.14159 * m_tank_pairs * d_tank * (m_T_cold_des + m_T_hot_des - 576.3) * 1.e-6; //MJ/s -- assumes full area for loss, Tamb = 15C + return e_loss / (m_q_pb_design * m_ts_hours * 3600.); //s^-1 -- fraction of heat loss per second based on full charge +} + +void C_csp_NTHeatTrap_tes::reset_storage_to_initial_state() +{ + // Initial storage charge based on % mass + double Q_tes_des = m_q_pb_design / 1.E6 * m_ts_hours; //[MWt-hr] TES thermal capacity at design + double cp_ave = mc_store_htfProps.Cp_ave(m_T_cold_des, m_T_hot_des); //[kJ/kg-K] Specific heat at average temperature + double mtot = Q_tes_des * 3600.0 / (cp_ave / 1000.0 * (m_T_hot_des - m_T_cold_des)); //[kg] Total HTF mass + double rho_hot = mc_store_htfProps.dens(m_T_hot_des, 1.0); + double rho_cold = mc_store_htfProps.dens(m_T_cold_des, 1.0); + + double V_inactive = m_vol_tank - m_V_tank_active; + double V_hot_ini = m_f_V_hot_ini * 0.01 * mtot / rho_hot + V_inactive; //[m^3] + double V_cold_ini = (1.0 - m_f_V_hot_ini * 0.01) * mtot / rho_cold + V_inactive; //[m^3] + + double T_hot_ini = m_T_tank_hot_ini; //[K] + double T_cold_ini = m_T_tank_cold_ini; //[K] + + // Initialize cold and hot tanks + // Hot tank + mc_hot_tank_NT.init(mc_store_htfProps, m_vol_tank, m_h_tank_calc, m_h_tank_min, + m_u_tank, m_tank_pairs, m_hot_tank_Thtr, m_hot_tank_max_heat, + V_hot_ini, T_hot_ini, m_T_hot_des, m_tank_wall_cp, m_tank_wall_dens, m_tank_wall_thick, m_nstep, m_piston_loss_poly); + // Cold tank + mc_cold_tank_NT.init(mc_store_htfProps, m_vol_tank, m_h_tank_calc, m_h_tank_min, + m_u_tank, m_tank_pairs, m_cold_tank_Thtr, m_cold_tank_max_heat, + V_cold_ini, T_cold_ini, m_T_cold_des, m_tank_wall_cp, m_tank_wall_dens, m_tank_wall_thick, m_nstep, m_piston_loss_poly); +} + +void C_csp_NTHeatTrap_tes::discharge_avail_est(double T_cold_K, double step_s, + double& q_dot_dc_est /*MWt*/, double& m_dot_external_est /*kg/s*/, double& T_hot_external_est /*K*/) +{ + double f_storage = 0.0; // for now, hardcode such that storage always completely discharges + + double m_dot_tank_disch_avail = mc_hot_tank_NT.m_dot_available(f_storage, step_s); //[kg/s] + + if (m_dot_tank_disch_avail == 0) { + q_dot_dc_est = 0.; + m_dot_external_est = 0.; + T_hot_external_est = std::numeric_limits::quiet_NaN(); + return; + } + + double T_hot_ini = mc_hot_tank_NT.get_m_T_prev(); //[K] + + double cp_T_avg = mc_store_htfProps.Cp_ave(T_cold_K, T_hot_ini); //[kJ/kg-K] spec heat at average temperature during discharge from hot to cold + q_dot_dc_est = m_dot_tank_disch_avail * cp_T_avg * (T_hot_ini - T_cold_K) * 1.E-3; //[MW] + m_dot_external_est = m_dot_tank_disch_avail; + T_hot_external_est = T_hot_ini; + +} + +void C_csp_NTHeatTrap_tes::charge_avail_est(double T_hot_K, double step_s, + double& q_dot_ch_est /*MWt*/, double& m_dot_external_est /*kg/s*/, double& T_cold_external_est /*K*/) +{ + // Only allow charge up to the 'main' tank volume + double dens_cold = mc_store_htfProps.dens(m_T_cold_des, 1.0); + double dens_hot = mc_store_htfProps.dens(m_T_hot_des, 1.0); + + //double f_ch_storage = 0; // for now, hardcode such that storage always completely charges + double f_ch_storage = 1.0 - dens_hot / dens_cold; + + + // This is amount of cold mass in tank (available for use - i.e., not dead space volume) + double m_dot_tank_charge_avail = mc_cold_tank_NT.m_dot_available(f_ch_storage, step_s); //[kg/s] + + double T_cold_ini = mc_cold_tank_NT.get_m_T_prev(); //[K] + + // for debugging + double T_hot_ini = mc_hot_tank_NT.get_m_T_prev(); //[K] + double cp_T_tanks_avg = mc_store_htfProps.Cp_ave(T_cold_ini, T_hot_ini); // [kJ/kg-K] + double mass_avail_hot_tank = mc_hot_tank_NT.m_dot_available(f_ch_storage, step_s) * step_s; //[kg] + double tes_charge_state = mass_avail_hot_tank * cp_T_tanks_avg * (T_hot_ini - T_cold_ini) * 1.e-3 / 3600.; // [MWht] + + double cp_T_avg = mc_store_htfProps.Cp_ave(T_cold_ini, T_hot_K); //[kJ/kg-K] spec heat at average temperature during charging from cold to hot + q_dot_ch_est = m_dot_tank_charge_avail * cp_T_avg * (T_hot_K - T_cold_ini) * 1.E-3; //[MW] + m_dot_external_est = m_dot_tank_charge_avail; //[kg/s] + T_cold_external_est = T_cold_ini; //[K] + +} + +int C_csp_NTHeatTrap_tes::solve_tes_off_design(double timestep /*s*/, double T_amb /*K*/, + double m_dot_cr_to_cv_hot /*kg/s*/, double m_dot_cv_hot_to_sink /*kg/s*/, double m_dot_cr_to_cv_cold /*kg/s*/, + double T_cr_out_hot /*K*/, double T_sink_out_cold /*K*/, + double& T_sink_htf_in_hot /*K*/, double& T_cr_in_cold /*K*/, + C_csp_tes::S_csp_tes_outputs& s_outputs) //, C_csp_solver_htf_state & s_tes_ch_htf, C_csp_solver_htf_state & s_tes_dc_htf) +{ + // Enthalpy balance on inlet to cold cv + double T_htf_cold_cv_in = T_sink_out_cold; //[K] + double m_dot_total_to_cv_cold = m_dot_cv_hot_to_sink + m_dot_cr_to_cv_cold; //[kg/s] + if (m_dot_total_to_cv_cold > 0.0) { + T_htf_cold_cv_in = (m_dot_cv_hot_to_sink * T_sink_out_cold + m_dot_cr_to_cv_cold * T_cr_out_hot) / (m_dot_total_to_cv_cold); + } + + // Total mass flow leaving cold tank to cr + // One of the RHS should always be 0... + double m_dot_cv_cold_to_cr = m_dot_cr_to_cv_hot + m_dot_cr_to_cv_cold; + + s_outputs = S_csp_tes_outputs(); + + double m_dot_cr_to_tes_hot, m_dot_cr_to_tes_cold, m_dot_tes_hot_out, m_dot_pc_to_tes_cold, m_dot_tes_cold_out, m_dot_tes_cold_in; + m_dot_cr_to_tes_hot = m_dot_cr_to_tes_cold = m_dot_tes_hot_out = m_dot_pc_to_tes_cold = m_dot_tes_cold_out = m_dot_tes_cold_in = std::numeric_limits::quiet_NaN(); + double m_dot_src_to_sink, m_dot_sink_to_src; + m_dot_src_to_sink = m_dot_sink_to_src = std::numeric_limits::quiet_NaN(); + + + if (tanks_in_parallel) + { + // Receiver bypass is possible in a parallel configuration, + // but need to determine if it actually makes sense and how to model it + if (m_dot_cr_to_cv_cold != 0.0) { + throw(C_csp_exception("Receiver output to cold tank not allowed in parallel TES configuration")); + } + m_dot_cr_to_tes_cold = 0.0; + + if (m_dot_cr_to_cv_hot >= m_dot_cv_hot_to_sink) + { + m_dot_cr_to_tes_hot = m_dot_cr_to_cv_hot - m_dot_cv_hot_to_sink; //[kg/s] + m_dot_tes_hot_out = 0.0; //[kg/s] + m_dot_pc_to_tes_cold = 0.0; //[kg/s] + m_dot_tes_cold_out = m_dot_cr_to_tes_hot; //[kg/s] + m_dot_src_to_sink = m_dot_cv_hot_to_sink; //[kg/s] + m_dot_sink_to_src = m_dot_cv_hot_to_sink; //[kg/s] + } + else + { + m_dot_cr_to_tes_hot = 0.0; //[kg/s] + m_dot_tes_hot_out = m_dot_cv_hot_to_sink - m_dot_cr_to_cv_hot; //[kg/s] + m_dot_pc_to_tes_cold = m_dot_tes_hot_out; //[kg/s] + m_dot_tes_cold_out = 0.0; //[kg/s] + m_dot_src_to_sink = m_dot_cr_to_cv_hot; //[kg/s] + m_dot_sink_to_src = m_dot_cr_to_cv_hot; //[kg/s] + } + m_dot_tes_cold_in = m_dot_pc_to_tes_cold; + } + else + { // Serial configuration + throw C_csp_exception("Tank model must be in parallel"); + } + + double q_dot_heater = std::numeric_limits::quiet_NaN(); //[MWe] Heating power required to keep tanks at a minimum temperature + double m_dot_cold_tank_to_hot_tank = std::numeric_limits::quiet_NaN(); //[kg/s] Hot tank mass flow rate, valid for direct and indirect systems + double W_dot_rhtf_pump = std::numeric_limits::quiet_NaN(); //[MWe] Pumping power, just for tank-to-tank in indirect storage + double q_dot_loss = std::numeric_limits::quiet_NaN(); //[MWt] Storage thermal losses + double q_dot_dc_to_htf = std::numeric_limits::quiet_NaN(); //[MWt] Thermal power to the HTF from storage + double q_dot_ch_from_htf = std::numeric_limits::quiet_NaN(); //[MWt] Thermal power from the HTF to storage + double T_hot_ave = std::numeric_limits::quiet_NaN(); //[K] Average hot tank temperature over timestep + double T_cold_ave = std::numeric_limits::quiet_NaN(); //[K] Average cold tank temperature over timestep + double T_hot_final = std::numeric_limits::quiet_NaN(); //[K] Hot tank temperature at end of timestep + double T_cold_final = std::numeric_limits::quiet_NaN(); //[K] Cold tank temperature at end of timestep + double q_dot_out_hot = std::numeric_limits::quiet_NaN(); //[MW] Energy leaving in fluid on hot side + double q_dot_out_cold = std::numeric_limits::quiet_NaN(); //[MW] Energy leaving in fluid on cold side + double q_dot_error_hot = std::numeric_limits::quiet_NaN(); //[MW] Energy Balance Error Hot Side + double q_dot_error_cold = std::numeric_limits::quiet_NaN(); //[MW] Energy Balance Error Cold Side + double q_dot_error_total = std::numeric_limits::quiet_NaN(); //[MW] Energy Balance Total Error + double q_dot_error_leak = std::numeric_limits::quiet_NaN(); //[MW] Energy Balance Error due to Leakage Assumption + double q_dot_error_wall = std::numeric_limits::quiet_NaN(); //[MW] Energy Balance Error due to Wall Assumption + double q_dot_error_corrected = std::numeric_limits::quiet_NaN();//[MW] Energy Balance Error adjusted for leakage and wall assumption + + if (tanks_in_parallel) + { + + if (m_dot_cr_to_cv_hot >= m_dot_cv_hot_to_sink) // Charging + { + T_sink_htf_in_hot = T_cr_out_hot; //[K] + double m_dot_tes_ch = m_dot_cr_to_cv_hot - m_dot_cv_hot_to_sink; //[kg/s] + double T_htf_tes_cold = std::numeric_limits::quiet_NaN(); //[K] + bool ch_solved = charge(timestep, + T_amb, + m_dot_tes_ch, + T_cr_out_hot, + T_htf_tes_cold, + q_dot_heater, m_dot_cold_tank_to_hot_tank, W_dot_rhtf_pump, + q_dot_loss, q_dot_dc_to_htf, q_dot_ch_from_htf, + T_hot_ave, T_cold_ave, T_hot_final, T_cold_final, q_dot_out_cold, q_dot_out_hot, q_dot_error_cold, q_dot_error_hot, + q_dot_error_total, q_dot_error_leak, q_dot_error_wall, q_dot_error_corrected); + + // Check if TES.charge method solved + if (!ch_solved) + { + return -3; + } + + // Enthalpy balance to calculate T_htf_cold to CR + if (m_dot_cr_to_cv_hot == 0.0) + { + T_cr_in_cold = T_htf_tes_cold; //[K] + } + else + { + T_cr_in_cold = (m_dot_tes_ch * T_htf_tes_cold + m_dot_cv_hot_to_sink * T_sink_out_cold) / m_dot_cr_to_cv_hot; //[K] + } + } + else // Discharging + { + T_cr_in_cold = T_sink_out_cold; //[K] + double m_dot_tes_dc = m_dot_cv_hot_to_sink - m_dot_cr_to_cv_hot; //[kg/s] + double T_htf_tes_hot = std::numeric_limits::quiet_NaN(); + bool is_tes_success = discharge(timestep, + T_amb, + m_dot_tes_dc, + T_sink_out_cold, + T_htf_tes_hot, + q_dot_heater, m_dot_cold_tank_to_hot_tank, W_dot_rhtf_pump, + q_dot_loss, q_dot_dc_to_htf, q_dot_ch_from_htf, + T_hot_ave, T_cold_ave, T_hot_final, T_cold_final, q_dot_out_cold, q_dot_out_hot, q_dot_error_cold, q_dot_error_hot, + q_dot_error_total, q_dot_error_leak, q_dot_error_wall, q_dot_error_corrected); + + m_dot_cold_tank_to_hot_tank *= -1.0; + + // Check if discharge method solved + if (!is_tes_success) + { + return -4; + } + + T_sink_htf_in_hot = (m_dot_tes_dc * T_htf_tes_hot + m_dot_cr_to_cv_hot * T_cr_out_hot) / m_dot_cv_hot_to_sink; //[K] + } + + + } + else // Serial tank operation + { + throw C_csp_exception("Tank model must be in parallel"); + } + + // TOTAL Energy Balance for total system here + double energy_balance_error_percent = (q_dot_error_total / m_q_dot_design) * 100.0; // [%] Energy balance power Error / design heat rate + + // Calculate Expansion Tank Size + double expansion_wall_mass = 0.0; + double expansion_wall_L = 0.0; + { + double wall_mass_hot = mc_hot_tank_NT.get_m_m_wall_calc(); + double wall_mass_cold = mc_cold_tank_NT.get_m_m_wall_calc(); + + double L_hot = mc_hot_tank_NT.get_m_L_calc(); + double L_cold = mc_cold_tank_NT.get_m_L_calc(); + + expansion_wall_mass = (wall_mass_hot + wall_mass_cold) - m_mass_wall_nominal; + expansion_wall_L = (L_hot + L_cold) - m_h_tank_calc; // This is length of 'bonus' tank + } + + + // Solve pumping power here + double W_dot_htf_pump = pumping_power(m_dot_cr_to_cv_hot, m_dot_cv_hot_to_sink, std::abs(m_dot_cold_tank_to_hot_tank), + T_cr_in_cold, T_cr_out_hot, T_sink_htf_in_hot, T_sink_out_cold, + false); //[-] C_MEQ__m_dot_tes will not send cr_m_dot to TES if recirculating + + + // Calculate NT Specific Outputs + double mass_cold = mc_cold_tank_NT.get_m_m_calc(); + double mass_hot = mc_hot_tank_NT.get_m_m_calc(); + double vol_cold = mc_cold_tank_NT.get_fluid_vol(); + double vol_hot = mc_hot_tank_NT.get_fluid_vol(); + double vol_tot = vol_cold + vol_hot; + double vol_tot_assigned = CSP::pi * std::pow(m_radius, 2.0) * m_length_total; + double mass_tot = mass_cold + mass_hot; + + double cold_frac = vol_cold / vol_tot; + double piston_loc, piston_frac; + calc_piston_location(piston_loc, piston_frac); + + s_outputs.m_q_heater = q_dot_heater; + s_outputs.m_W_dot_elec_in_tot = W_dot_htf_pump; //[MWe] + s_outputs.m_q_dot_dc_to_htf = q_dot_dc_to_htf; + s_outputs.m_q_dot_ch_from_htf = q_dot_ch_from_htf; + s_outputs.m_m_dot_cr_to_tes_hot = m_dot_cr_to_tes_hot; //[kg/s] + s_outputs.m_m_dot_cr_to_tes_cold = m_dot_cr_to_tes_cold; //[kg/s] + s_outputs.m_m_dot_tes_hot_out = m_dot_tes_hot_out; //[kg/s] + s_outputs.m_m_dot_pc_to_tes_cold = m_dot_pc_to_tes_cold; //[kg/s] + s_outputs.m_m_dot_tes_cold_out = m_dot_tes_cold_out; //[kg/s] + s_outputs.m_m_dot_tes_cold_in = m_dot_tes_cold_in; //[kg/s] + s_outputs.m_m_dot_src_to_sink = m_dot_src_to_sink; //[kg/s] + s_outputs.m_m_dot_sink_to_src = m_dot_sink_to_src; //[kg/s] + + s_outputs.m_T_tes_cold_in = T_htf_cold_cv_in; //[K] + + s_outputs.m_m_dot_cold_tank_to_hot_tank = m_dot_cold_tank_to_hot_tank; + + mc_reported_outputs.value(E_Q_DOT_LOSS, q_dot_loss); //[MWt] + mc_reported_outputs.value(E_W_DOT_HEATER, q_dot_heater); //[MWt] + mc_reported_outputs.value(E_TES_T_HOT, T_hot_final - 273.15); //[C] + mc_reported_outputs.value(E_TES_T_COLD, T_cold_final - 273.15); //[C] + mc_reported_outputs.value(E_M_DOT_TANK_TO_TANK, m_dot_cold_tank_to_hot_tank); //[kg/s] + mc_reported_outputs.value(E_MASS_COLD_TANK, mc_cold_tank_NT.get_m_m_calc()); //[kg] + mc_reported_outputs.value(E_MASS_HOT_TANK, mc_hot_tank_NT.get_m_m_calc()); //[kg] + mc_reported_outputs.value(E_W_DOT_HTF_PUMP, W_dot_htf_pump); //[MWe] + + // Added NT Outputs + mc_reported_outputs.value(E_VOL_COLD, vol_cold); //[m3] + mc_reported_outputs.value(E_VOL_HOT, vol_hot); //[m3] + mc_reported_outputs.value(E_VOL_TOT, vol_tot); //[m3] + mc_reported_outputs.value(E_PIST_LOC, piston_loc); //[m] + mc_reported_outputs.value(E_PIST_FRAC, piston_frac); //[] + mc_reported_outputs.value(E_COLD_FRAC, cold_frac); //[] + mc_reported_outputs.value(E_MASS_TOT, mass_tot); //[kg] + mc_reported_outputs.value(E_SA_COLD, mc_cold_tank_NT.get_SA_calc()); //[m2] + mc_reported_outputs.value(E_SA_HOT, mc_hot_tank_NT.get_SA_calc()); //[m2] + mc_reported_outputs.value(E_SA_TOT, mc_cold_tank_NT.get_SA_calc() + mc_hot_tank_NT.get_SA_calc()); //[m2] + mc_reported_outputs.value(E_ERROR, q_dot_error_total); //[MW] + mc_reported_outputs.value(E_ERROR_PERCENT, energy_balance_error_percent); //[%] + mc_reported_outputs.value(E_LEAK_ERROR, q_dot_error_leak); //[MWt] + mc_reported_outputs.value(E_E_HOT, mc_hot_tank_NT.get_m_E_calc()); //[MJ] + mc_reported_outputs.value(E_E_COLD, mc_cold_tank_NT.get_m_E_calc());//[MJ] + mc_reported_outputs.value(E_E_COLD, mc_cold_tank_NT.get_m_E_calc());//[MJ] + mc_reported_outputs.value(E_WALL_ERROR, q_dot_error_wall); //[MWt] + mc_reported_outputs.value(E_ERROR_CORRECTED, q_dot_error_corrected); //[MW] + mc_reported_outputs.value(E_EXP_WALL_MASS, expansion_wall_mass); //[kg] + mc_reported_outputs.value(E_EXP_LENGTH, expansion_wall_L); //[m] + + return 0; +} + +void C_csp_NTHeatTrap_tes::converged() +{ + mc_cold_tank_NT.converged(); + mc_hot_tank_NT.converged(); + //mc_hx.converged(); + + // Set reported sink converged values + mc_reported_outputs.value(E_HOT_TANK_HTF_PERC_FINAL, mc_hot_tank_NT.get_mass_avail() / m_mass_total_active * 100.0); + + mc_reported_outputs.set_timestep_outputs(); + + // The max charge and discharge flow rates should be set at the beginning of each timestep + // during the q_dot_xx_avail_est calls + // m_m_dot_tes_dc_max = m_m_dot_tes_ch_max = std::numeric_limits::quiet_NaN(); +} + +void C_csp_NTHeatTrap_tes::write_output_intervals(double report_time_start, + const std::vector& v_temp_ts_time_end, double report_time_end) +{ + mc_reported_outputs.send_to_reporting_ts_array(report_time_start, + v_temp_ts_time_end, report_time_end); +} + +void C_csp_NTHeatTrap_tes::assign(int index, double* p_reporting_ts_array, size_t n_reporting_ts_array) +{ + mc_reported_outputs.assign(index, p_reporting_ts_array, n_reporting_ts_array); +} + +double /*MWe*/ C_csp_NTHeatTrap_tes::pumping_power(double m_dot_sf /*kg/s*/, double m_dot_pb /*kg/s*/, double m_dot_tank /*kg/s*/, + double T_sf_in /*K*/, double T_sf_out /*K*/, double T_pb_in /*K*/, double T_pb_out /*K*/, bool recirculating) +{ + // NTHeattrap never uses a hx -> pumping power = 0 (following two tank logic) + // Might want to use tes_pump_coef here? + + double htf_pump_power = 0.0; //[MWe] + + return htf_pump_power; +} + + +void C_csp_NTHeatTrap_tes::get_design_parameters(double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, + double& h_tank_calc /*m*/, double& d_tank_calc /*m*/, + double& q_dot_loss_des /*MWt*/, double& dens_store_htf_at_T_ave /*kg/m3*/, double& Q_tes /*MWt-hr*/) +{ + vol_one_temp_avail = m_V_tank_active; //[m3] + vol_one_temp_total = m_vol_tank; //[m3] + h_tank_calc = m_h_tank_calc; //[m] + d_tank_calc = m_d_tank_calc; //[m] + q_dot_loss_des = m_q_dot_loss_des; //[MWt] + dens_store_htf_at_T_ave = m_rho_store_avg; //[kg/m3] + Q_tes = m_Q_tes_des; //[MWt-hr] +} + +bool C_csp_NTHeatTrap_tes::charge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, + double T_htf_hot_in /*K*/, double& T_htf_cold_out /*K*/, + double& q_dot_heater /*MWe*/, double& m_dot_tank_to_tank /*kg/s*/, double& W_dot_rhtf_pump /*MWe*/, + double& q_dot_loss /*MWt*/, double& q_dot_dc_to_htf /*MWt*/, double& q_dot_ch_from_htf /*MWt*/, + double& T_hot_ave /*K*/, double& T_cold_ave /*K*/, double& T_hot_final /*K*/, double& T_cold_final /*K*/, + double& q_dot_out_cold /*MW*/, double& q_dot_out_hot /*MW*/, double& q_dot_error_cold, double& q_dot_error_hot, + double& q_dot_error_total /*MW*/, double& q_dot_error_leak /*MW*/, double& q_dot_error_wall /*MW*/, + double& q_dot_error_corrected /*MW*/) +{ + // This method calculates the timestep-average cold charge return temperature of the TES system. + // This is out of the external side of the heat exchanger (HX), opposite the tank (or 'TES') side, + // or if no HX (direct storage), this is equal to the cold tank outlet temperature. + + // The method returns FALSE if the system input mass flow rate is greater than the allowable charge + + // Inputs are: + // 1) Mass flow rate of HTF into TES system (equal to that exiting the system) + // 2) Temperature of HTF into TES system. If no heat exchanger, this temperature + // is of the HTF directly entering the hot tank + + // DEBUG + double piston_loc, piston_frac; + calc_piston_location(piston_loc, piston_frac); + + double q_dot_ch_est, m_dot_tes_ch_max, T_cold_to_src_est; + q_dot_ch_est = m_dot_tes_ch_max = T_cold_to_src_est = std::numeric_limits::quiet_NaN(); + charge_avail_est(T_htf_hot_in, timestep, q_dot_ch_est, m_dot_tes_ch_max, T_cold_to_src_est); + + double m_dot_htf_in_net = m_dot_htf_in * (1.0 - mc_hot_tank_NT.calc_leakage_fraction(m_dot_htf_in)); + + if (m_dot_htf_in_net > 1.0001 * m_dot_tes_ch_max && m_dot_htf_in_net > 1.E-6) + { + q_dot_heater = std::numeric_limits::quiet_NaN(); + m_dot_tank_to_tank = std::numeric_limits::quiet_NaN(); + W_dot_rhtf_pump = std::numeric_limits::quiet_NaN(); + q_dot_loss = std::numeric_limits::quiet_NaN(); + q_dot_dc_to_htf = std::numeric_limits::quiet_NaN(); + q_dot_ch_from_htf = std::numeric_limits::quiet_NaN(); + T_hot_ave = std::numeric_limits::quiet_NaN(); + T_cold_ave = std::numeric_limits::quiet_NaN(); + T_hot_final = std::numeric_limits::quiet_NaN(); + T_cold_final = std::numeric_limits::quiet_NaN(); + + return false; + } + + double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, m_dot_src, + T_src_hot_in, T_src_cold_out, m_dot_tank, T_hot_tank_in; + q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_cold_ave = T_hot_ave = m_dot_src = + T_src_hot_in = T_src_cold_out = m_dot_tank = T_hot_tank_in = std::numeric_limits::quiet_NaN(); + + m_dot_src = m_dot_tank = m_dot_htf_in; + T_src_hot_in = T_hot_tank_in = T_htf_hot_in; + + double T_hot_prev = mc_hot_tank_NT.get_m_T_prev(); + double T_cold_prev = mc_cold_tank_NT.get_m_T_prev(); + + solve_tanks_iterative(timestep, m_nstep, m_dot_tank, 0, T_hot_tank_in, 0, T_amb, + T_cold_ave, q_heater_cold, q_dot_loss_cold, q_dot_out_cold, q_dot_error_cold, + T_hot_ave, q_heater_hot, q_dot_loss_hot, q_dot_out_hot, q_dot_error_hot, + q_dot_error_total, q_dot_error_leak, q_dot_error_wall, q_dot_error_corrected); + + q_dot_heater = q_heater_cold + q_heater_hot; //[MWt] + + + m_dot_tank_to_tank = 0.0; + W_dot_rhtf_pump = 0; //[MWe] Just tank-to-tank pumping power + T_htf_cold_out = T_cold_ave; + + q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; //[MWt] + q_dot_dc_to_htf = 0.0; //[MWt] + T_hot_ave = T_hot_ave; //[K] + T_cold_ave = T_cold_ave; //[K] + T_hot_final = mc_hot_tank_NT.get_m_T_calc(); //[K] + T_cold_final = mc_cold_tank_NT.get_m_T_calc(); //[K] + + // Calculate thermal power to HTF + double cp_htf_ave = mc_external_htfProps.Cp_ave(T_htf_cold_out, T_htf_hot_in); //[kJ/kg-K] + q_dot_ch_from_htf = m_dot_htf_in * cp_htf_ave * (T_htf_hot_in - T_htf_cold_out) / 1000.0; //[MWt] + + + return true; + +} + +bool C_csp_NTHeatTrap_tes::discharge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, + double T_htf_cold_in /*K*/, double& T_htf_hot_out /*K*/, + double& q_dot_heater /*MWe*/, double& m_dot_tank_to_tank /*kg/s*/, double& W_dot_rhtf_pump /*MWe*/, + double& q_dot_loss /*MWt*/, double& q_dot_dc_to_htf /*MWt*/, double& q_dot_ch_from_htf /*MWt*/, + double& T_hot_ave /*K*/, double& T_cold_ave /*K*/, double& T_hot_final /*K*/, double& T_cold_final /*K*/, + double& q_dot_out_cold /*MW*/, double& q_dot_out_hot /*MW*/, double& q_dot_error_cold, double& q_dot_error_hot, + double& q_dot_error_total /*MW*/, double& q_dot_error_leak /*MW*/, double& q_dot_error_wall /*MW*/, + double& q_dot_error_corrected /*MW*/) +{ + // This method calculates the timestep-average hot discharge temperature of the TES system. This is out of the external side of the heat exchanger (HX), opposite the tank (or 'TES') side, + // or if no HX (direct storage), this is equal to the hot tank outlet temperature. + + // The method returns FALSE if the system output (same as input) mass flow rate is greater than that available + + // Inputs are: + // 1) Mass flow rate of HTF into TES system (equal to that exiting the system) + // 2) Temperature of HTF into TES system. If no heat exchanger, this temperature + // is of the HTF directly entering the cold tank + + double q_dot_dc_est, m_dot_tes_dc_max, T_hot_to_pc_est; + q_dot_dc_est = m_dot_tes_dc_max = T_hot_to_pc_est = std::numeric_limits::quiet_NaN(); + discharge_avail_est(T_htf_cold_in, timestep, q_dot_dc_est, m_dot_tes_dc_max, T_hot_to_pc_est); + + double m_dot_htf_in_net = m_dot_htf_in * (1.0 - mc_hot_tank_NT.calc_leakage_fraction(m_dot_htf_in)); + + if (m_dot_htf_in_net > 1.0001 * m_dot_tes_dc_max && m_dot_htf_in_net > 1.E-6) // mass flow in = mass flow out + { + q_dot_heater = std::numeric_limits::quiet_NaN(); + m_dot_tank_to_tank = std::numeric_limits::quiet_NaN(); + W_dot_rhtf_pump = std::numeric_limits::quiet_NaN(); + q_dot_loss = std::numeric_limits::quiet_NaN(); + q_dot_dc_to_htf = std::numeric_limits::quiet_NaN(); + q_dot_ch_from_htf = std::numeric_limits::quiet_NaN(); + T_hot_ave = std::numeric_limits::quiet_NaN(); + T_cold_ave = std::numeric_limits::quiet_NaN(); + T_hot_final = std::numeric_limits::quiet_NaN(); + T_cold_final = std::numeric_limits::quiet_NaN(); + + return false; + } + + double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, m_dot_src, + T_src_cold_in, T_src_hot_out, m_dot_tank, T_cold_tank_in; + q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_cold_ave = T_hot_ave = + m_dot_src = T_src_cold_in = T_src_hot_out = m_dot_tank = T_cold_tank_in = std::numeric_limits::quiet_NaN(); + + m_dot_src = m_dot_tank = m_dot_htf_in; + T_src_cold_in = T_cold_tank_in = T_htf_cold_in; + + double T_hot_prev = mc_hot_tank_NT.get_m_T_prev(); + double T_cold_prev = mc_cold_tank_NT.get_m_T_prev(); + + solve_tanks_iterative(timestep, m_nstep, 0, m_dot_tank, 0, T_cold_tank_in, T_amb, + T_cold_ave, q_heater_cold, q_dot_loss_cold, q_dot_out_cold, q_dot_error_cold, + T_hot_ave, q_heater_hot, q_dot_loss_hot, q_dot_out_hot, q_dot_error_hot, + q_dot_error_total, q_dot_error_leak, q_dot_error_wall, q_dot_error_corrected); + + q_dot_heater = q_heater_cold + q_heater_hot; //[MWt] + + m_dot_tank_to_tank = 0.0; + W_dot_rhtf_pump = 0; //[MWe] Just tank-to-tank pumping power + T_htf_hot_out = T_hot_ave; + + q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; //[MWt] + q_dot_ch_from_htf = 0.0; //[MWt] + T_hot_ave = T_hot_ave; //[K] + T_cold_ave = T_cold_ave; //[K] + T_hot_final = mc_hot_tank_NT.get_m_T_calc(); //[K] + T_cold_final = mc_cold_tank_NT.get_m_T_calc(); //[K] + + // Calculate thermal power to HTF + double cp_htf_ave = mc_external_htfProps.Cp_ave(T_htf_cold_in, T_htf_hot_out); //[kJ/kg-K] + q_dot_dc_to_htf = m_dot_htf_in * cp_htf_ave * (T_htf_hot_out - T_htf_cold_in) / 1000.0; //[MWt] + + return true; +} + +void C_csp_NTHeatTrap_tes::solve_tanks_iterative(double timestep /*s*/, double n_substep /**/, double mdot_charge /*kg/s*/, + double mdot_discharge /*kg/s*/, double T_charge /*K*/, double T_discharge /*K*/, double T_amb /*K*/, + double& T_ave_cold /*K*/, double& q_heater_cold /*MW*/, double& q_dot_loss_cold /*MW*/, + double& q_dot_out_cold /*MW*/, double& q_dot_error_cold /*MW*/, + double& T_ave_hot /*K*/, double& q_heater_hot /*MW*/, double& q_dot_loss_hot /*MW*/, + double& q_dot_out_hot /*MW*/, double& q_dot_error_hot /*MW*/, + double& q_dot_error_total /*MW*/, double& q_dot_error_leak /*MW*/, double& q_dot_error_wall /*MW*/, + double& q_dot_error_corrected /*MW*/) +{ + double ministep = timestep / n_substep; + + // Define Cold internal variables + double q_heater_summed_cold = 0; + double q_dot_loss_summed_cold = 0; + double T_ave_innerstep_cold = 0; + double mass_prev_inner_cold = mc_cold_tank_NT.get_m_m_prev(); + double mass_calc_inner_cold = std::numeric_limits::quiet_NaN(); + double T_prev_inner_cold = mc_cold_tank_NT.get_m_T_prev(); + double T_calc_inner_cold = std::numeric_limits::quiet_NaN(); + double T_ave_weighted_cold = 0; + double q_dot_out_summed_cold = 0; + double q_dot_error_summed_cold = 0; + + // Define Hot internal variables + double q_heater_summed_hot = 0; + double q_dot_loss_summed_hot = 0; + double T_ave_innerstep_hot = 0; + double mass_prev_inner_hot = mc_hot_tank_NT.get_m_m_prev(); + double mass_calc_inner_hot = std::numeric_limits::quiet_NaN(); + double T_prev_inner_hot = mc_hot_tank_NT.get_m_T_prev(); + double T_calc_inner_hot = std::numeric_limits::quiet_NaN(); + double T_ave_weighted_hot = 0; + double q_dot_out_summed_hot = 0; + double q_dot_error_summed_hot = 0; + + // Define Energy Balance Error Variables + double error_avg = 0; //[MW] + double error_leak_avg = 0; // [MW] + double error_wall_avg = 0; // [MW] + double error_corrected_avg = 0; + + // Sort out charge/discharge params + double mdot_in_cold = 0; + double mdot_out_cold = 0; + double mdot_in_hot = 0; + double mdot_out_hot = 0; + double T_in_cold = 0; + double T_in_hot = 0; + { + if (mdot_charge != 0 && mdot_discharge != 0) + { + throw(C_csp_exception("NT Tank cannot charge and discharge at once", "NT Tank Energy Balance")); + } + // Charging + if (mdot_charge > 0) + { + mdot_in_cold = 0; + mdot_out_cold = mdot_charge; + T_in_cold = 0; // (no temperature coming in) + mdot_in_hot = mdot_charge; + mdot_out_hot = 0; + T_in_hot = T_charge; + } + // Discharging + else + { + mdot_in_cold = mdot_discharge; + mdot_out_cold = 0; + T_in_cold = T_discharge; + mdot_in_hot = 0; + mdot_out_hot = mdot_discharge; + T_in_hot = 0; // (no temperature coming in) + } + } + + // Run Energy Balance + for (int i = 0; i < n_substep; i++) + { + double q_heater_innerstep_cold, q_dot_loss_innerstep_cold, q_dot_error_innerstep_cold, q_dot_out_inner_cold + = std::numeric_limits::quiet_NaN(); + + double q_heater_innerstep_hot, q_dot_loss_innerstep_hot, q_dot_error_innerstep_hot, q_dot_out_inner_hot + = std::numeric_limits::quiet_NaN(); + + // Simulate Cold Tank + mc_cold_tank_NT.energy_balance_core(ministep, mdot_in_cold, mdot_out_cold, T_in_cold, T_amb, mass_prev_inner_cold, + T_prev_inner_hot, T_prev_inner_cold, T_prev_inner_hot, + T_ave_innerstep_cold, q_heater_innerstep_cold, q_dot_loss_innerstep_cold, mass_calc_inner_cold, T_calc_inner_cold, q_dot_out_inner_cold, + q_dot_error_innerstep_cold); + + // Simulate Hot Tank + mc_hot_tank_NT.energy_balance_core(ministep, mdot_in_hot, mdot_out_hot, T_in_hot, T_amb, mass_prev_inner_hot, + T_prev_inner_cold, T_prev_inner_hot, T_prev_inner_cold, + T_ave_innerstep_hot, q_heater_innerstep_hot, q_dot_loss_innerstep_hot, mass_calc_inner_hot, T_calc_inner_hot, q_dot_out_inner_hot, + q_dot_error_innerstep_hot); + + // TOTAL Energy Balance for TES system here + double energy_balance_error = 0; // [MW] + double energy_error_leakage = 0; // [MW] + double energy_error_wall = 0; // [MW] + double energy_error_corrected = 0; //[MW] + { + // Cold Fluid Energy In (if Discharging) + double Q_cold_in; // MJ + { + double mdot_cold_in = mdot_discharge; // kg/s + double cp_cold_in = mc_external_htfProps.Cp(T_in_cold) * 1e-3; // MJ/kg K + + Q_cold_in = T_in_cold * mdot_cold_in * cp_cold_in * ministep; // MJ + } + + // Hot Fluid Energy In (if Charging) + double Q_hot_in; // MJ + { + double mdot_hot_in = mdot_charge; // kg/s + double cp_hot_in = mc_external_htfProps.Cp(T_in_hot) * 1e-3; // MJ/kg K + + Q_hot_in = T_in_hot * mdot_hot_in * cp_hot_in * ministep; // MJ + } + + // Cold Fluid Energy Out (if Charging) + double Q_cold_out; // MJ + { + double mdot_cold_out = mdot_charge; // kg/s + double cp_cold_out = mc_external_htfProps.Cp(T_ave_innerstep_cold) * 1e-3; // MJ/kg K + + Q_cold_out = T_ave_innerstep_cold * mdot_cold_out * cp_cold_out * ministep; // MJ + } + + // Hot Fluid Energy Out (if Discharging) + double Q_hot_out; // MJ + { + double mdot_hot_out = mdot_discharge; // kg/s + double cp_hot_out = mc_external_htfProps.Cp(T_ave_innerstep_hot) * 1e-3; // MJ/kg K + + Q_hot_out = T_ave_innerstep_hot * mdot_hot_out * cp_hot_out * ministep; // MJ + } + + // Q Loss (to environment) + double q_dot_loss = q_dot_loss_innerstep_cold + q_dot_loss_innerstep_hot; + double Q_loss = q_dot_loss * ministep; // MJ + + // Q Heater (Input energy) + double q_dot_heater = q_heater_innerstep_cold + q_heater_innerstep_hot; + double Q_heater = q_dot_heater * ministep; // MJ + + // Cold Side Energy Change + double dQ_cold; // MJ + double mass_wall_cold_prev; + double mass_wall_cold_final; + double Q_cold_wall_prev; + double Q_cold_wall_final; + { + // Initial Energy + double T_cold_prev = T_prev_inner_cold; + double mass_fluid_cold_prev = mass_prev_inner_cold; + mass_wall_cold_prev = mc_cold_tank_NT.calc_mass_wall(T_cold_prev, mass_fluid_cold_prev); // kg + double cp_cold_fluid_prev = mc_external_htfProps.Cp(T_cold_prev) * 1e-3; // MJ/kg K + + double Q_cold_fluid_prev = T_cold_prev * mass_fluid_cold_prev * cp_cold_fluid_prev; // MJ + Q_cold_wall_prev = T_cold_prev * mass_wall_cold_prev * m_tank_wall_cp * 1e-6; // MJ + + // Final Energy + double mass_fluid_cold_final = mass_calc_inner_cold; + mass_wall_cold_final = mc_cold_tank_NT.get_m_m_wall_calc(); + double cp_cold_fluid_final = mc_external_htfProps.Cp(T_calc_inner_cold) * 1e-3; // MJ/kg K + + double Q_cold_fluid_final = T_calc_inner_cold * mass_fluid_cold_final * cp_cold_fluid_final; // MJ + Q_cold_wall_final = T_calc_inner_cold * mass_wall_cold_final * m_tank_wall_cp * 1e-6; // MJ + + dQ_cold = (Q_cold_fluid_final + Q_cold_wall_final) - (Q_cold_fluid_prev + Q_cold_wall_prev); // MJ + } + + // Hot Side Energy Change + double dQ_hot; // MJ + double mass_wall_hot_prev; + double mass_wall_hot_final; + double Q_hot_wall_final; + double Q_hot_wall_prev; + { + // Initial Energy + double T_hot_prev = T_prev_inner_hot; + double mass_fluid_hot_prev = mass_prev_inner_hot; + mass_wall_hot_prev = mc_hot_tank_NT.calc_mass_wall(T_hot_prev, mass_fluid_hot_prev); // kg + double cp_hot_fluid_prev = mc_external_htfProps.Cp(T_hot_prev) * 1e-3; // MJ/kg K + + double Q_hot_fluid_prev = T_hot_prev * mass_fluid_hot_prev * cp_hot_fluid_prev; // MJ + Q_hot_wall_prev = T_hot_prev * mass_wall_hot_prev * m_tank_wall_cp * 1e-6; // MJ + + // Final Energy + double T_hot = T_calc_inner_hot; + double mass_fluid_hot_final = mass_calc_inner_hot; + mass_wall_hot_final = mc_hot_tank_NT.get_m_m_wall_calc(); + double cp_hot_fluid_final = mc_external_htfProps.Cp(T_hot) * 1e-3; // MJ/kg K + + double Q_hot_fluid_final = T_hot * mass_fluid_hot_final * cp_hot_fluid_final; // MJ + Q_hot_wall_final = T_hot * mass_wall_hot_final * m_tank_wall_cp * 1e-6; // MJ + + dQ_hot = (Q_hot_fluid_final + Q_hot_wall_final) - (Q_hot_fluid_prev + Q_hot_wall_prev); // MJ + } + + // Energy In + double Q_in_sum = Q_cold_in + Q_hot_in + Q_heater; // MJ + + // Energy Out + double Q_out_sum = Q_cold_out + Q_hot_out + Q_loss; // MJ + + // Net Energy + double Q_net = Q_in_sum - Q_out_sum; // MJ + + // Change in Energy + double dQ_total = dQ_cold + dQ_hot; // MJ + + // Balance + energy_balance_error = (dQ_total - Q_net) / ministep; // MW (should be zero) + + // Tank Wall Energy + double mass_wall_prev = mass_wall_cold_prev + mass_wall_hot_prev; + double mass_wall_final = mass_wall_cold_final + mass_wall_hot_final; + double Q_wall_prev = Q_cold_wall_prev + Q_hot_wall_prev; + double Q_wall_final = Q_cold_wall_final + Q_hot_wall_final; + + // Leakage Temperature Discrepancy + { + // Leakage is assumed at a constant temp, while the other side has variable temps + // i.e. During charge, cold side takes on leakage at constant temp, while hot is leaking variable temp + double mdot_leakage; + double mdot_net = mdot_charge + mdot_discharge; + { + double N_terms = m_piston_loss_poly.size(); + double frac = 0; + for (int i = 0; i < N_terms; i++) + { + frac += m_piston_loss_poly[i] * std::pow(mdot_net, i); + } + + frac *= 0.01; // Convert from % to fraction + mdot_leakage = mdot_net * frac; + } + + // Charge + if (mdot_charge > 0) + { + double T_leakage_assumed = T_prev_inner_hot; // [K] Cold tank assumes leakage is constant temp at prev timestep from hot tank + double cp_leakage_assumed = mc_store_htfProps.Cp(T_leakage_assumed) * 1e-3; // [MJ/kg K] + + double T_leakage_actual = T_ave_innerstep_hot; // [K] Actual leakage temp will be hot tank average + double cp_leakage_actual = mc_store_htfProps.Cp(T_leakage_actual) * 1e-3; // [MJ/kg K] + + double Q_dot_leakage_assumed = T_leakage_assumed * cp_leakage_assumed * mdot_leakage; // [MW] + double Q_dot_leakage_actual = T_leakage_actual * cp_leakage_actual * mdot_leakage; // [MW] + + energy_error_leakage = Q_dot_leakage_actual - Q_dot_leakage_assumed; // [MW] + } + // Discharge + else if (mdot_discharge > 0) + { + double T_leakage_assumed = T_prev_inner_cold; // [K] Hot tank assumes leakage is constant temp at prev timestep from cold tank + double cp_leakage_assumed = mc_store_htfProps.Cp(T_leakage_assumed) * 1e-3; // [MJ/kg K] + + double T_leakage_actual = T_ave_innerstep_cold; // [K] Actual leakage temp will be hot tank average + double cp_leakage_actual = mc_store_htfProps.Cp(T_leakage_actual) * 1e-3; // [MJ/kg K] + + double Q_dot_leakage_assumed = T_leakage_assumed * cp_leakage_assumed * mdot_leakage; // [MW] + double Q_dot_leakage_actual = T_leakage_actual * cp_leakage_actual * mdot_leakage; // [MW] + + energy_error_leakage = Q_dot_leakage_actual - Q_dot_leakage_assumed; // [MW] + } + } + + // Wall Temperature Discrepancy + { + // Tank wall mass is assumed at a constant temp, while the other side has variable temp + // i.e. During charge, hot side takes on cold mass at constant temp. Cold side actually has variable temp + + // Charge + if (mdot_charge > 0) + { + // Hot side is expanding, taking on cold wall + double T_wall_assumed = T_prev_inner_cold; //[K] + double T_wall_actual = T_ave_innerstep_cold; //[K] + double mdot_wall = (mc_hot_tank_NT.get_m_m_wall_calc() - mass_wall_hot_prev) / ministep; + + energy_error_wall = mdot_wall * m_tank_wall_cp * (T_wall_actual - T_wall_assumed) * 1e-6; //[MW] + } + // Discharge + else if (mdot_discharge > 0) + { + // Cold side is expanding, taking on hot wall + double T_wall_assumed = T_prev_inner_hot; //[K] + double T_wall_actual = T_ave_innerstep_hot; //[K] + double mdot_wall = (mc_cold_tank_NT.get_m_m_wall_calc() - mass_wall_cold_prev) / ministep; + + energy_error_wall = mdot_wall * m_tank_wall_cp * (T_wall_actual - T_wall_assumed) * 1e-6; //[MW] + } + } + + // Corrected Error (accounting for leakage and wall temp assumptions) + energy_error_corrected = std::abs(energy_balance_error) - std::abs(energy_error_leakage + energy_error_wall); //[MW] + } + + // Collect Cold Results + q_heater_summed_cold += q_heater_innerstep_cold * (ministep / timestep); + q_dot_loss_summed_cold += q_dot_loss_innerstep_cold * (ministep / timestep); + q_dot_out_summed_cold += q_dot_out_inner_cold; + q_dot_error_summed_cold += q_dot_error_innerstep_cold; + mass_prev_inner_cold = mass_calc_inner_cold; + T_prev_inner_cold = T_calc_inner_cold; + T_ave_weighted_cold += T_ave_innerstep_cold * (ministep / timestep); + + // Collect Hot Results + q_heater_summed_hot += q_heater_innerstep_hot * (ministep / timestep); + q_dot_loss_summed_hot += q_dot_loss_innerstep_hot * (ministep / timestep); + q_dot_out_summed_hot += q_dot_out_inner_hot; + q_dot_error_summed_hot += q_dot_error_innerstep_hot; + mass_prev_inner_hot = mass_calc_inner_hot; + T_prev_inner_hot = T_calc_inner_hot; + T_ave_weighted_hot += T_ave_innerstep_hot * (ministep / timestep); + + error_avg += energy_balance_error * (ministep / timestep); //[MW] + error_leak_avg += energy_error_leakage * (ministep / timestep); //[MW] + error_wall_avg += energy_error_wall * (ministep / timestep); //[MW] + error_corrected_avg += energy_error_corrected * (ministep / timestep); //[MW] + } + + T_ave_cold = T_ave_weighted_cold; + q_heater_cold = q_heater_summed_cold; + q_dot_loss_cold = q_dot_loss_summed_cold; + q_dot_out_cold = q_dot_out_summed_cold; + q_dot_error_cold = q_dot_error_summed_cold; + + + T_ave_hot = T_ave_weighted_hot; + q_heater_hot = q_heater_summed_hot; + q_dot_loss_hot = q_dot_loss_summed_hot; + q_dot_out_hot = q_dot_out_summed_hot; + q_dot_error_hot = q_dot_error_summed_hot; + + q_dot_error_total = error_avg; + q_dot_error_leak = error_leak_avg; + q_dot_error_wall = error_wall_avg; + q_dot_error_corrected = error_corrected_avg; + +} + +double C_csp_NTHeatTrap_tes::get_min_storage_htf_temp() +{ + return mc_store_htfProps.min_temp(); +} + +double C_csp_NTHeatTrap_tes::get_max_storage_htf_temp() +{ + return mc_store_htfProps.max_temp(); +} + +double C_csp_NTHeatTrap_tes::get_storage_htf_density() +{ + double avg_temp = (m_T_cold_des + m_T_hot_des) / 2.0; + return mc_store_htfProps.dens(avg_temp, 0); +} + +double C_csp_NTHeatTrap_tes::get_storage_htf_cp() +{ + double avg_temp = (m_T_cold_des + m_T_hot_des) / 2.0; + return mc_store_htfProps.Cp(avg_temp); +} + +void C_csp_NTHeatTrap_tes::calc_piston_location(double &piston_loc, double &piston_frac) +{ + double vol_cold = mc_cold_tank_NT.get_fluid_vol_prev(); + double vol_hot = mc_hot_tank_NT.get_fluid_vol_prev(); + + double length_cold = vol_cold / (CSP::pi * std::pow(m_radius, 2.0)); + double length_hot = vol_hot / (CSP::pi * std::pow(m_radius, 2.0)); + double length_tot = length_cold + length_hot; + //if (length_tot != m_length_total) + //{ + // throw C_csp_exception("Calculated total length does not equal assigned total length"); + //} + + piston_loc = length_cold; // Distance from left (cold) side + piston_frac = length_cold / length_tot; + +} + + diff --git a/tcs/csp_solver_NTHeatTrap_tes.h b/tcs/csp_solver_NTHeatTrap_tes.h new file mode 100644 index 000000000..3287b8c49 --- /dev/null +++ b/tcs/csp_solver_NTHeatTrap_tes.h @@ -0,0 +1,443 @@ +/* +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 __csp_solver_NTHeatTrap_tes_ +#define __csp_solver_NTHeatTrap_tes_ + +#include "csp_solver_core.h" +#include "csp_solver_util.h" +#include "sam_csp_util.h" +#include "csp_solver_tes_core.h" + +class C_storage_tank_dynamic_NT +{ +private: + HTFProperties mc_htf; + + double m_V_total; //[m^3] Total volume for *one temperature* tank + double m_V_active; //[m^3] active volume of *one temperature* tank (either cold or hot) + double m_V_inactive; //[m^3] Inactive volume of *one temperature* tank (either cold or hot) + + double m_T_htr; //[K] Tank heater set point + double m_max_q_htr; //[MWt] Max tank heater capacity + + double m_T_design; //[K] Tank design point temperature + double m_mass_total; //[kg] Mass of storage fluid that would fill tank volume at design temperature + double m_mass_inactive; //[kg] Mass of storage fluid at design Temp that fills inactive volume + double m_mass_active; //[kg] Mass of storage fluid at design Temp that fills active volume + + // Stored values from end of previous timestep + double m_V_prev; //[m^3] Volume of storage fluid in tank + double m_T_prev; //[K] Temperature of storage fluid in tank + double m_m_prev; //[kg] Mass of storage fluid in tank + double m_E_prev; //[MJ] Internal energy (including tank walll) + double m_m_wall_prev; //[kg] Mass of storage tank wall + + // Calculated values for current timestep + double m_V_calc; //[m^3] Volume of storage fluid in tank + double m_T_calc; //[K] Temperature of storage fluid in tank + double m_m_calc; //[kg] Mass of storage fluid in tank + double m_E_calc; //[MJ] Internal energy (including tank wall) + double m_m_wall_calc; //[kg] Mass of storage tank wall + double m_L_calc; //[m] Length of tank at end of timestep + + // Added TMB 12.15.2023 + double m_radius; //[m] + + double m_tank_wall_cp; //[J/kgK] + double m_tank_wall_dens; //[kg/m3] + double m_tank_wall_thick; //[m] + double m_u_tank; //[W/m2-K] + double m_nstep; //[] + std::vector m_piston_loss_poly; //[] Coefficients to piston loss polynomial (0*x^0 + 1*x^1 ....) + + double m_SA_prev; //[m2] Surface area previous timestep + double m_SA_calc; //[m2] Surface area current timestep + + // Added surface area of tank wall as piston moves + double calc_SA_rate(double mdot_htf /*kg/s*/, double T_htf /*K*/); + + // Added mass flow of tank wall as piston moves + double calc_mdot_expansion(double piston_rate /*m/s*/); + + double calc_tank_wall_volume(double fluid_mass /*kg*/, double T_htf /*K*/); + + double calc_SA(double volume /*m3*/); + + + +public: + + C_storage_tank_dynamic_NT(); + + double calc_mass_at_prev(); + + double get_m_UA(); + + double get_m_T_prev(); + + double get_m_T_calc(); + + double get_m_m_calc(); + + double get_m_m_prev(); + + double get_m_E_prev(); + + double get_m_E_calc(); + + double get_vol_frac(); + + double get_mass_avail(); //[kg] + + double get_fluid_vol(); // m3 + + double get_fluid_vol_prev(); // m3 + + double get_radius(); // m + + double get_SA_calc(); //m2 + + double calc_mass_wall(double T_fluid, double mass_fluid); // kg + + double get_m_m_wall_prev(); + + double get_m_m_wall_calc(); + + double get_m_L_calc(); // m + + void init(HTFProperties htf_class_in, double V_tank /*m3*/, + double h_tank /*m*/, double h_min /*m*/, double u_tank /*W/m2-K*/, + double tank_pairs /*-*/, double T_htr /*K*/, double max_q_htr /*MWt*/, + double V_ini /*m3*/, double T_ini /*K*/, + double T_design /*K*/, + double tank_wall_cp, // [J/kg-K] Tank wall specific heat + double tank_wall_dens, // [kg/m3] Tank wall density + double tank_wall_thick, // [m] Tank wall thickness) + double nstep, // [] Number of time steps for energy balance iteration + std::vector piston_loss_poly //[] Coefficients to piston loss polynomial + ); + + double m_dot_available(double f_unavail, double timestep); + + void energy_balance_core(double timestep /*s*/, double m_dot_in /*kg/s*/, double m_dot_out /*kg/s*/, + double T_in /*K*/, double T_amb /*K*/, double mass_prev_inner /*kg*/, + double T_tank_in /*K*/, double T_prev_inner /*K*/, + double T_leak_in /*K*/, + double& T_ave /*K*/, double& q_heater /*MW*/, double& q_dot_loss /*MW*/, + double& mass_calc_inner /*kg*/, double& T_calc_inner /*K*/, double& q_dot_out_inner /*MW*/, + double& q_dot_error_inner /*MW*/); + + void energy_balance_iterated(double timestep /*s*/, double m_dot_in /*kg/s*/, double m_dot_out /*kg/s*/, + double T_in /*K*/, double T_amb /*K*/, + double T_tank_in, /*K*/ + double T_leak_in, /*K*/ + double& T_ave /*K*/, double& q_heater /*MW*/, double& q_dot_loss /*MW*/, double& q_dot_out /*MW*/, + double& q_dot_error /*MW*/); + + void converged(); + + double calc_leakage_fraction(double mdot); + +}; + +class C_csp_NTHeatTrap_tes : public C_csp_tes +{ +private: + + HTFProperties mc_external_htfProps; // Instance of HTFProperties class for external HTF + HTFProperties mc_store_htfProps; // Instance of HTFProperties class for storage HTF + + C_storage_tank_dynamic_NT mc_cold_tank_NT; // Instance of storage tank class for the cold tank + C_storage_tank_dynamic_NT mc_hot_tank_NT; // Instance of storage tank class for the hot tank + + // member string for exception messages + std::string error_msg; + + // Timestep data + // double m_m_dot_tes_dc_max; //[kg/s] TES discharge available from the SYSTEM (external side of HX if there is one) + // double m_m_dot_tes_ch_max; //[kg/s] TES charge that can be sent to the SYSTEM (external side of HX if there is one) + + // Member data + bool m_is_tes; + bool m_is_cr_to_cold_tank_allowed; + double m_vol_tank; //[m3] volume of *one temperature*, i.e. vol_tank = total cold storage = total hot storage + double m_V_tank_active; //[m^3] available volume (considering h_min) of *one temperature* + double m_q_pb_design; //[Wt] thermal power to sink at design + double m_V_tank_hot_ini; //[m^3] Initial volume in hot storage tank + double m_mass_total_active; //[kg] Total HTF mass at design point inlet/outlet T + double m_h_tank_calc; //[m] Actual height (length) of tank + double m_d_tank_calc; //[m] diameter of a single tank + double m_q_dot_loss_des; //[MWt] design tank heat loss + double m_ts_hours; //[hr] hours of storage at design sink operation + + double m_cp_external_avg; //[kJ/kg-K] + double m_rho_store_avg; //[kg/m3] + + double m_m_dot_tes_des_over_m_dot_external_des; //[-] + + // Added for NT + double m_tank_wall_cp; //[J/kg-K] + double m_tank_wall_dens; //[kg/m3] + double m_tank_wall_thick; //[m] + double m_piston_percent; //[%] + double m_piston_location; //[m] Piston distance from left side + double m_nstep; //[] Number of time steps for energy balance iteration + std::vector m_piston_loss_poly; //[] Coefficients to piston loss polynomial (0*x^0 + 1*x^1 ....) + + // Added for NT, calc in init + double m_radius; //[m] radius of tank + double m_length_total; //[m] Total length of tank (two tanks combined) + double m_V_wall_nominal; //[m3] Total wall volume of tank + double m_mass_wall_nominal;//[kg] Total wall mass of tank (wall mass 'changes' as density of fluid changes) + + void solve_tanks_iterative(double timestep /*s*/, double n_substep /**/, double mdot_charge /*kg/s*/, + double mdot_discharge /*kg/s*/, double T_charge /*K*/, double T_discharge /*K*/, double T_amb /*K*/, + double& T_ave_cold /*K*/, double& q_heater_cold /*MW*/, double& q_dot_loss_cold /*MW*/, + double& q_dot_out_cold /*MW*/, double& q_dot_error_cold /*MW*/, + double& T_ave_hot /*K*/, double& q_heater_hot /*MW*/, double& q_dot_loss_hot /*MW*/, + double& q_dot_out_hot /*MW*/, double& q_dot_error_hot /*MW*/, + double& q_dot_error_total /*MW*/, double& q_dot_error_leak /*MW*/, double& q_dot_error_wall /*MW*/, + double& q_dot_error_corrected /*MW*/); + +public: + + enum + { + E_Q_DOT_LOSS, //[MWt] TES thermal losses + E_W_DOT_HEATER, //[MWe] TES freeze protection power + E_TES_T_HOT, //[C] TES final hot tank temperature + E_TES_T_COLD, //[C] TES final cold tank temperature + E_M_DOT_TANK_TO_TANK, //[kg/s] Tank to tank mass flow rate (indirect TES) + E_MASS_COLD_TANK, //[kg] Mass in cold tank at end of timestep + E_MASS_HOT_TANK, //[kg] Mass in hot tank at end of timestep + E_HOT_TANK_HTF_PERC_FINAL, //[%] Final percent fill of available hot tank mass + E_W_DOT_HTF_PUMP, //[MWe] + + // NT Only Outputs + E_VOL_COLD, + E_VOL_HOT, + E_VOL_TOT, + E_PIST_LOC, + E_PIST_FRAC, + E_COLD_FRAC, + E_MASS_TOT, + E_SA_COLD, + E_SA_HOT, + E_SA_TOT, + E_ERROR, + E_ERROR_PERCENT, + E_LEAK_ERROR, + E_E_HOT, + E_E_COLD, + E_WALL_ERROR, + E_ERROR_CORRECTED, + E_EXP_WALL_MASS, + E_EXP_LENGTH + }; + + + int m_external_fl; + util::matrix_t m_external_fl_props; + int m_tes_fl; + util::matrix_t m_tes_fl_props; + double m_q_dot_design; //[MWe] Design heat rate in and out of tes + double m_frac_max_q_dot; //[-] the max design heat rate as a fraction of the nominal + double m_Q_tes_des; //[MWt-hr] design storage capacity + bool m_is_h_fixed; //[true] Height is input, calculate diameter, [false] diameter input, calculate height + double m_h_tank_in; //[m] tank height input + double m_d_tank_in; //[m] tank diameter input + double m_u_tank; //[W/m^2-K] + int m_tank_pairs; //[-] + double m_hot_tank_Thtr; //[C] convert to K in init() + double m_hot_tank_max_heat; //[MW] + double m_cold_tank_Thtr; //[C] convert to K in init() + double m_cold_tank_max_heat; //[MW] + double m_T_cold_des; //[C] convert to K in init() + double m_T_hot_des; //[C] convert to K in init() + double m_T_tank_hot_ini; //[C] Initial temperature in hot storage tank + double m_T_tank_cold_ini; //[C] Initial temperature in cold storage cold + double m_h_tank_min; //[m] Minimum allowable HTF height in storage tank + double m_f_V_hot_ini; //[%] Initial fraction of available volume that is hot + double m_htf_pump_coef; //[kW/kg/s] Pumping power to move 1 kg/s of HTF through sink + double m_tes_pump_coef; //[kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop + double eta_pump; //[-] Pump efficiency, for newer pumping calculations + bool tanks_in_parallel; //[-] Whether the tanks are in series or parallel with the external system. ALWAYS TRUE for NT Heat Trap + bool has_hot_tank_bypass; //[-] True if the bypass valve causes the external htf to bypass just the hot tank and enter the cold tank before flowing back to the external system. + double T_tank_hot_inlet_min; //[C] Minimum external htf temperature that may enter the hot tank + double V_tes_des; //[m/s] Design-point velocity for sizing the diameters of the TES piping + bool custom_tes_p_loss; //[-] True if the TES piping losses should be calculated using the TES pipe lengths and minor loss coeffs, false if using the pumping loss parameters + bool custom_tes_pipe_sizes; //[-] True if the TES diameters and wall thicknesses parameters should be used instead of calculating them + util::matrix_t k_tes_loss_coeffs; //[-] Combined minor loss coefficients of the fittings and valves in the collection (including bypass) and generation loops in the TES + util::matrix_t tes_diams; //[m] Imported inner diameters for the TES piping as read from the modified output files + util::matrix_t tes_wallthicks; //[m] Imported wall thicknesses for the TES piping as read from the modified output files + util::matrix_t tes_lengths; //[m] Imported lengths for the TES piping as read from the modified output files + bool calc_design_pipe_vals; //[-] Should the HTF state be calculated at design conditions + double pipe_rough; //[m] Pipe absolute roughness + double dP_discharge; //[bar] Pressure drop on the TES discharge side (e.g., within the steam generator) + + C_csp_reported_outputs mc_reported_outputs; + + double pipe_vol_tot; //[m^3] + util::matrix_t pipe_v_dot_rel; //[-] + double P_in_des; //[bar] Pressure at the inlet to the TES, at the external system side + + + C_csp_NTHeatTrap_tes( + int external_fl, + util::matrix_t external_fl_props, + int tes_fl, + util::matrix_t tes_fl_props, + double q_dot_design, // [MWt] Design heat rate in and out of tes + double frac_max_q_dot, // [-] the max design heat rate as a fraction of the nominal + double Q_tes_des, // [MWt-hr] design storage capacity + bool is_h_fixed, // [] [true] Height is input, calculate diameter, [false] diameter input, calculate height + double h_tank_in, // [m] tank height input + double d_tank_in, // [m] tank diameter input + double u_tank, // [W/m^2-K] + int tank_pairs, // [-] + double hot_tank_Thtr, // [C] convert to K in init() + double hot_tank_max_heat, // [MW] + double cold_tank_Thtr, // [C] convert to K in init() + double cold_tank_max_heat, // [MW] + double T_cold_des, // [C] convert to K in init() + double T_hot_des, // [C] convert to K in init() + double T_tank_hot_ini, // [C] Initial temperature in hot storage tank + double T_tank_cold_ini, // [C] Initial temperature in cold storage cold + double h_tank_min, // [m] Minimum allowable HTF height in storage tank + double f_V_hot_ini, // [%] Initial fraction of available volume that is hot + double htf_pump_coef, // [kW/kg/s] Pumping power to move 1 kg/s of HTF through sink + double tank_wall_cp, // [J/kg-K] Tank wall specific heat + double tank_wall_dens, // [kg/m3] Tank wall density + double tank_wall_thick = 0, // [m] Tank wall thickness + int nstep = 1, // [] Number of time steps for energy balance iteration + std::vector piston_loss_poly = {}, // [] Coefficients to piston loss polynomial (0*x^0 + 1*x^1 ....) + + double V_tes_des = 1.85, // [m/s] Design-point velocity for sizing the diameters of the TES piping + bool calc_design_pipe_vals = true, // [-] Should the HTF state be calculated at design conditions + double tes_pump_coef = std::numeric_limits::quiet_NaN(), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop + double eta_pump = std::numeric_limits::quiet_NaN(), // [-] Pump efficiency, for newer pumping calculations + bool has_hot_tank_bypass = false, // [-] True if the bypass valve causes the external htf to bypass just the hot tank and enter the cold tank before flowing back to the external system. + double T_tank_hot_inlet_min = std::numeric_limits::quiet_NaN(), // [C] Minimum source htf temperature that may enter the hot tank + bool custom_tes_p_loss = false, // [-] True if the TES piping losses should be calculated using the TES pipe lengths and minor loss coeffs, false if using the pumping loss parameters + bool custom_tes_pipe_sizes = false, // [-] True if the TES diameters and wall thicknesses parameters should be used instead of calculating them + util::matrix_t k_tes_loss_coeffs = util::matrix_t(), // [-] Combined minor loss coefficients of the fittings and valves in the collection (including bypass) and generation loops in the TES + util::matrix_t tes_diams = util::matrix_t(), // [m] Imported inner diameters for the TES piping as read from the modified output files + util::matrix_t tes_wallthicks = util::matrix_t(), // [m] Imported wall thicknesses for the TES piping as read from the modified output files + util::matrix_t tes_lengths = util::matrix_t(), // [m] Imported lengths for the TES piping as read from the modified output files + double pipe_rough = std::numeric_limits::quiet_NaN(), // [m] Pipe absolute roughness + double dP_discharge = std::numeric_limits::quiet_NaN() // [bar] Pressure drop on the TES discharge side (e.g., within the steam generator) + ); + + C_csp_NTHeatTrap_tes(); + + ~C_csp_NTHeatTrap_tes() {}; + + virtual void init(const C_csp_tes::S_csp_tes_init_inputs init_inputs); + + virtual bool does_tes_exist(); + + virtual bool is_cr_to_cold_allowed(); + + virtual double get_hot_temp(); + + virtual double get_cold_temp(); + + virtual double get_hot_tank_vol_frac(); + + virtual double get_initial_charge_energy(); //MWh + + virtual double get_min_charge_energy(); //MWh + + virtual double get_max_charge_energy(); //MWh + + virtual double get_degradation_rate(); // s^-1 + + virtual void reset_storage_to_initial_state(); + + virtual void discharge_avail_est(double T_cold_K, double step_s, + double& q_dot_dc_est /*MWt*/, double& m_dot_external_est /*kg/s*/, double& T_hot_external_est /*K*/); + + virtual void charge_avail_est(double T_hot_K, double step_s, + double& q_dot_ch_est /*MWt*/, double& m_dot_external_est /*kg/s*/, double& T_cold_external_est /*K*/); + + virtual int solve_tes_off_design(double timestep /*s*/, double T_amb /*K*/, + double m_dot_cr_to_cv_hot /*kg/s*/, double m_dot_cv_hot_to_sink /*kg/s*/, double m_dot_cr_to_cv_cold /*kg/s*/, + double T_cr_out_hot /*K*/, double T_sink_out_cold /*K*/, + double& T_sink_htf_in_hot /*K*/, double& T_cr_in_cold /*K*/, + C_csp_tes::S_csp_tes_outputs& outputs); + + virtual void converged(); + + virtual void write_output_intervals(double report_time_start, + const std::vector& v_temp_ts_time_end, double report_time_end); + + virtual void assign(int index, double* p_reporting_ts_array, size_t n_reporting_ts_array); + + virtual /*MWe*/ double pumping_power(double m_dot_sf /*kg/s*/, double m_dot_pb /*kg/s*/, double m_dot_tank /*kg/s*/, + double T_sf_in /*K*/, double T_sf_out /*K*/, double T_pb_in /*K*/, double T_pb_out /*K*/, bool recirculating); + + void get_design_parameters(double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, + double& h_tank /*m*/, double& d_tank /*m*/, + double& q_dot_loss_des /*MWt*/, double& dens_store_htf_at_T_ave /*kg/m3*/, double& Q_tes /*MWt-hr*/); + + bool charge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, + double T_htf_hot_in, double& T_htf_cold_out /*K*/, + double& q_dot_heater /*MWe*/, double& m_dot /*kg/s*/, double& W_dot_rhtf_pump /*MWe*/, + double& q_dot_loss /*MWt*/, double& q_dot_dc_to_htf /*MWt*/, double& q_dot_ch_from_htf /*MWt*/, + double& T_hot_ave /*K*/, double& T_cold_ave /*K*/, double& T_hot_final /*K*/, double& T_cold_final /*K*/, + double& q_dot_out_cold /*MW*/, double& q_dot_out_hot /*MW*/, double& q_dot_error_cold, double& q_dot_error_hot, + double& q_dot_error_total /*MW*/, double& q_dot_error_leak /*MW*/, double& q_dot_error_wall /*MW*/, + double& q_dot_error_corrected /*MW*/); + + bool discharge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, + double T_htf_cold_in, double& T_htf_hot_out /*K*/, + double& q_dot_heater /*MWe*/, double& m_dot /*kg/s*/, double& W_dot_rhtf_pump /*MWe*/, + double& q_dot_loss /*MWt*/, double& q_dot_dc_to_htf /*MWt*/, double& q_dot_ch_from_htf /*MWt*/, + double& T_hot_ave /*K*/, double& T_cold_ave /*K*/, double& T_hot_final /*K*/, double& T_cold_final /*K*/, + double& q_dot_out_cold /*MW*/, double& q_dot_out_hot /*MW*/, double& q_dot_error_cold, double& q_dot_error_hot, + double& q_dot_error_total /*MW*/, double& q_dot_error_leak /*MW*/, double& q_dot_error_wall /*MW*/, + double& q_dot_error_corrected /*MW*/); + + double get_max_storage_htf_temp(); + + double get_min_storage_htf_temp(); + + double get_storage_htf_density(); + + double get_storage_htf_cp(); + + void calc_piston_location(double& piston_loc, double& piston_frac); +}; + + +#endif diff --git a/tcs/csp_solver_packedbed_tes.cpp b/tcs/csp_solver_packedbed_tes.cpp new file mode 100644 index 000000000..2d313b02a --- /dev/null +++ b/tcs/csp_solver_packedbed_tes.cpp @@ -0,0 +1,1004 @@ +/* +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 "csp_solver_packedbed_tes.h" + +static C_csp_reported_outputs::S_output_info S_output_info[] = +{ + {C_csp_packedbed_tes::E_Q_DOT_LOSS, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] TES thermal losses + {C_csp_packedbed_tes::E_W_DOT_HEATER, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWe] TES freeze protection power + {C_csp_packedbed_tes::E_TES_T_HOT, C_csp_reported_outputs::TS_LAST}, //[C] TES final hot tank temperature + {C_csp_packedbed_tes::E_TES_T_COLD, C_csp_reported_outputs::TS_LAST}, //[C] TES cold temperature at end of timestep + {C_csp_packedbed_tes::E_M_DOT_TANK_TO_TANK, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] TES thermal losses + {C_csp_packedbed_tes::E_MASS_COLD_TANK, C_csp_reported_outputs::TS_LAST}, //[kg] Mass in cold tank at end of timestep + {C_csp_packedbed_tes::E_MASS_HOT_TANK, C_csp_reported_outputs::TS_LAST}, //[kg] Mass in hot tank at end of timestep + {C_csp_packedbed_tes::E_HOT_TANK_HTF_PERC_FINAL, C_csp_reported_outputs::TS_LAST}, //[%] Final percent fill of available hot tank mass + {C_csp_packedbed_tes::E_W_DOT_HTF_PUMP, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWe] + {C_csp_packedbed_tes::E_VOL_TOT, C_csp_reported_outputs::TS_LAST}, //[m3] + {C_csp_packedbed_tes::E_MASS_TOT, C_csp_reported_outputs::TS_LAST}, //[kg] + {C_csp_packedbed_tes::E_T_GRAD_0, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[C] + {C_csp_packedbed_tes::E_T_GRAD_1, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[C] + {C_csp_packedbed_tes::E_T_GRAD_2, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[C] + {C_csp_packedbed_tes::E_T_GRAD_3, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[C] + {C_csp_packedbed_tes::E_T_GRAD_4, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[C] + {C_csp_packedbed_tes::E_T_GRAD_5, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[C] + {C_csp_packedbed_tes::E_T_GRAD_6, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[C] + {C_csp_packedbed_tes::E_T_GRAD_7, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[C] + {C_csp_packedbed_tes::E_T_GRAD_8, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[C] + {C_csp_packedbed_tes::E_T_GRAD_9, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[C] + csp_info_invalid +}; + +// Private Methods + +void C_csp_packedbed_tes::size_pb_fixed_height(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double f_oversize, + double void_frac, double dens_solid /*kg/m3*/, double cp_solid /*J/kg K*/, + double T_tes_hot /*K*/, double T_tes_cold /*K*/, double h_tank /*m*/, + double& vol_total /*m3*/, double& d_tank_out /*m*/) +{ + double T_tes_ave = 0.5 * (T_tes_hot + T_tes_cold); //[K] + + double dens_htf_ave = tes_htf_props.dens(T_tes_ave, 1.0); //[kg/m^3] Density at average temperature + double cp_htf_ave = tes_htf_props.Cp_ave(T_tes_cold, T_tes_hot) * 1000.0;//[J/kg-K] Specific heat at average temperature + + // Calculate Total Volume (include void space for HTF) + double dT = (T_tes_hot - T_tes_cold); //[dK] + vol_total = f_oversize * (Q_tes_des * 1e6 * 3600.0) / + ((((1.0 - void_frac) * dens_solid * cp_solid) + (void_frac * dens_htf_ave * cp_htf_ave)) * dT); // [m3] + + // Calculate Diameter + d_tank_out = 2.0 * std::sqrt(vol_total / (h_tank * CSP::pi)); // [m] +} + +void C_csp_packedbed_tes::size_pb_fixed_diameter(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double f_oversize, + double void_frac, double dens_solid /*kg/m3*/, double cp_solid /*J/kg K*/, + double T_tes_hot /*K*/, double T_tes_cold /*K*/, double d_tank /*m*/, + double& vol_total /*m3*/, double& h_tank_out /*m*/) +{ + double T_tes_ave = 0.5 * (T_tes_hot + T_tes_cold); //[K] + + double dens_htf_ave = tes_htf_props.dens(T_tes_ave, 1.0); //[kg/m^3] Density at average temperature + double cp_htf_ave = tes_htf_props.Cp_ave(T_tes_cold, T_tes_hot) * 1000.0;//[J/kg-K] Specific heat at average temperature + + // Calculate Total Volume + double dT = (T_tes_hot - T_tes_cold); //[dK] + vol_total = f_oversize * (Q_tes_des * 1e6 * 3600.0) / + ((((1.0 - void_frac) * dens_solid * cp_solid) + (void_frac * dens_htf_ave * cp_htf_ave)) * dT); // [m3] + + // Calculate Height + h_tank_out = vol_total / (CSP::pi * std::pow(d_tank / 2.0, 2.0)); // m +} + +std::vector C_csp_packedbed_tes::reduce_vector_avg(std::vector vec, int out_vec_size) +{ + // Produce new vector of different size, using averages from 'vec' + std::vector out_vec(out_vec_size, 0); + + for (int i = 0; i < out_vec_size; i++) + { + // Calculate average at this location + double frac = (double)i / (out_vec_size - 1); + double val_avg = this->get_avg_from_vec(vec, frac); + + // Save value + out_vec[i] = val_avg; + } + + return out_vec; +} + +double C_csp_packedbed_tes::get_avg_from_vec(std::vector vec, double frac) +{ + double frac_prev = 0.0; + double frac_next = 0.0; + double val_prev = vec[0]; + double val_next = std::numeric_limits::quiet_NaN(); + double vec_size = vec.size(); + for (int i = 1; i < vec.size(); i++) + { + frac_next = (double)i / (vec_size - 1); + val_next = vec[i]; + if (frac >= frac_prev && frac <= frac_next) + { + break; + } + else + { + frac_prev = frac_next; + val_prev = val_next; + } + } + + double val_avg = val_next + (frac - frac_next) * ((val_prev - val_next) / (frac_prev - frac_next)); + + return val_avg; +} + +// Public Methods + +C_csp_packedbed_tes::C_csp_packedbed_tes( + int external_fl, // [-] external fluid identifier + util::matrix_t external_fl_props, // [-] external fluid properties + double Q_tes_des, // [MWt-hr] design storage capacity + int size_type, // [] Sizing Method (0) use fixed diameter, (1) use fixed height, (2) use preset inputs + double h_tank_in, // [m] tank height input + double d_tank_in, // [m] tank diameter input + double f_oversize, // [] Oversize factor + double T_cold_des_C, // [C] convert to K in constructor() + double T_hot_des_C, // [C] convert to K in constructor() + double T_tank_hot_ini_C, // [C] Initial temperature in hot storage tank + double T_tank_cold_ini_C, // [C] Initial temperature in cold storage cold + double f_V_hot_ini, // [%] Initial fraction of available volume that is hot + int n_xstep, // number spatial sub steps + int n_subtimestep, // number subtimesteps + double tes_pump_coef, // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop + double k_eff, // [W/m K] Effective thermal conductivity + double void_frac, // [] Packed bed void fraction + double dens_solid, // [kg/m3] solid specific heat + double cp_solid, // [J/kg K] solid specific heat + double T_hot_delta, // [C] Max allowable decrease in hot discharge temp + double T_cold_delta, // [C] Max allowable increase in cold discharge temp + double T_charge_min_C // [C] Min allowable charge temperature +) + : + m_external_fl(external_fl), m_external_fl_props(external_fl_props), m_Q_tes_des(Q_tes_des), + m_size_type(size_type), m_h_tank_in(h_tank_in), m_d_tank_in(d_tank_in), m_f_oversize(f_oversize), + m_f_V_hot_ini(f_V_hot_ini), + m_n_xstep(n_xstep), m_n_subtimestep(n_subtimestep), m_tes_pump_coef(tes_pump_coef), + m_k_eff(k_eff), m_void_frac(void_frac), m_dens_solid(dens_solid), m_cp_solid(cp_solid), + m_T_hot_delta(T_hot_delta), m_T_cold_delta(T_cold_delta) +{ + // Convert Temperature Units + m_T_cold_des = T_cold_des_C + 273.15; //[K] + m_T_hot_des = T_hot_des_C + 273.15; //[K] + m_T_tank_hot_ini = T_tank_hot_ini_C + 273.15; //[K] + m_T_tank_cold_ini = T_tank_cold_ini_C + 273.15; //[K] + m_T_charge_min = T_charge_min_C + 273.15; //[K] + + // Set Subtimestep + m_subtimestep_nominal = 3600.0 / m_n_subtimestep; //[s] + + mc_reported_outputs.construct(S_output_info); +} + +C_csp_packedbed_tes::C_csp_packedbed_tes() +{ + mc_reported_outputs.construct(S_output_info); +} + +void C_csp_packedbed_tes::set_T_grad_init(std::vector T_grad_init_C) +{ + for (double T_C : T_grad_init_C) + m_T_prev_vec.push_back(T_C + 273.15); + + m_use_T_grad_init = true; +} + +void C_csp_packedbed_tes::init(const C_csp_tes::S_csp_tes_init_inputs init_inputs) +{ + if (!(m_Q_tes_des > 0.0)) + { + m_is_tes = false; + return; // No storage! + } + + m_is_tes = true; + + // Declare instance of fluid class for EXTERNAL fluid + // Set fluid number and copy over fluid matrix if it makes sense + if (m_external_fl != HTFProperties::User_defined && m_external_fl < HTFProperties::End_Library_Fluids) + { + if (!mc_external_htfProps.SetFluid(m_external_fl)) + { + throw(C_csp_exception("External HTF code is not recognized", "Two Tank TES Initialization")); + } + } + else if (m_external_fl == HTFProperties::User_defined) + { + int n_rows = (int)m_external_fl_props.nrows(); + int n_cols = (int)m_external_fl_props.ncols(); + if (n_rows > 2 && n_cols == 7) + { + if (!mc_external_htfProps.SetUserDefinedFluid(m_external_fl_props)) + { + error_msg = util::format(mc_external_htfProps.UserFluidErrMessage(), n_rows, n_cols); + throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); + } + } + else + { + error_msg = util::format("The user defined external HTF 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, "Two Tank TES Initialization")); + } + } + else + { + throw(C_csp_exception("External HTF code is not recognized", "Two Tank TES Initialization")); + } + + // Size Tank + + // Fixed Diameter + if (m_size_type == 0) + { + double h_tank_out; + size_pb_fixed_diameter(mc_external_htfProps, m_Q_tes_des /*MWt-hr*/, m_f_oversize, + m_void_frac, m_dens_solid /*kg/m3*/, m_cp_solid /*J/kg K*/, + m_T_hot_des /*K*/, m_T_cold_des /*K*/, m_d_tank_in /*m*/, + m_V_tank /*m3*/, h_tank_out /*m*/); + m_h_tank_calc = h_tank_out; + m_d_tank_calc = m_d_tank_in; + } + // Fixed Height + else if (m_size_type == 1) + { + double d_tank_out; + size_pb_fixed_height(mc_external_htfProps, m_Q_tes_des /*MWt-hr*/, m_f_oversize, + m_void_frac, m_dens_solid /*kg/m3*/, m_cp_solid /*J/kg K*/, + m_T_hot_des /*K*/, m_T_cold_des /*K*/, m_h_tank_in /*m*/, + m_V_tank /*m3*/, d_tank_out /*m*/); + m_d_tank_calc = d_tank_out; + m_h_tank_calc = m_h_tank_in; + } + // Diameter and height are defined by user + else if (m_size_type == 2) + { + m_h_tank_calc = m_h_tank_in; //[m] + m_d_tank_calc = m_d_tank_in; //[m] + } + else + { + throw(C_csp_exception("Invalid TES sizing type")); + } + + // Define Cross Sectional Area + m_Ac = M_PI * std::pow(0.5 * m_d_tank_calc, 2.0); + + // Back calculate packed bed mass + m_mass_solid = m_V_tank * (1.0 - m_void_frac) * m_dens_solid; // [kg] + + // Calculate actual storage capacity + m_Q_tes_actual = m_Q_tes_des * m_f_oversize; //[MWt-hr] + + // Define initial temperatures + if (m_use_T_grad_init == false) + { + double dx = m_h_tank_calc / m_n_xstep; // [m] + double x_end = 0; //[m] + double x_mid = 0; //[m] + m_T_prev_vec = std::vector(m_n_xstep + 1); + // Loop through space + for (int i = 0; i <= m_n_xstep; i++) + { + // Update position of center of section + if (i == 0 || i == m_n_xstep) + { + x_end += dx * 0.5; + x_mid = x_end - (dx * 0.5 * 0.5); + } + else + { + x_end += dx; + x_mid = x_end - (dx * 0.5); + } + + double frac_mid = x_mid / m_h_tank_calc; + if(frac_mid < m_f_V_hot_ini * 0.01) + m_T_prev_vec[i] = m_T_tank_hot_ini; + else + m_T_prev_vec[i] = m_T_tank_cold_ini; + } + } + +} + +bool C_csp_packedbed_tes::does_tes_exist() +{ + return m_is_tes; +} + +bool C_csp_packedbed_tes::is_cr_to_cold_allowed() +{ + return false; +} + +double C_csp_packedbed_tes::get_hot_temp() +{ + // Return temperature closest to charge inlet (hot) + return m_T_prev_vec[0]; +} + +double C_csp_packedbed_tes::get_cold_temp() +{ + // Return temperature furthest from charge inlet (cold) + return m_T_prev_vec[m_T_prev_vec.size() - 1]; +} + +double C_csp_packedbed_tes::get_hot_tank_vol_frac() +{ + // Loop through temperatures, starting at hot end, to check temperature threshold + double n_hot_sections = 0; + for (int i = 0; i < m_T_prev_vec.size(); i++) + { + double T_prev_node = m_T_prev_vec[i]; + double weight = 1; + + // First and last node count for half + if (i == 0 || i == m_T_prev_vec.size() - 1) + weight = 0.5; + + if (T_prev_node >= (m_T_hot_des - m_T_hot_delta)) + { + n_hot_sections += weight; + } + } + + // Calculate hot fraction + // Divide by number of "sections" which is one less than number of nodes + double hot_vol_frac = n_hot_sections / m_n_xstep; + + return hot_vol_frac; +} + +double C_csp_packedbed_tes::get_initial_charge_energy() +{ + return std::numeric_limits::quiet_NaN(); +} + +double C_csp_packedbed_tes::get_min_charge_energy() +{ + return std::numeric_limits::quiet_NaN(); +} + +double C_csp_packedbed_tes::get_max_charge_energy() +{ + return std::numeric_limits::quiet_NaN(); +} + +double C_csp_packedbed_tes::get_degradation_rate() +{ + return std::numeric_limits::quiet_NaN(); +} + +void C_csp_packedbed_tes::reset_storage_to_initial_state() +{ + return; +} + +void C_csp_packedbed_tes::discharge_avail_est(double T_cold_K, double step_s, + double& q_dot_dc_est /*MWt*/, double& m_dot_external_est /*kg/s*/, double& T_hot_external_est /*K*/) +{ + // Temperatures are at the node between segments + // if m_n_xstep == 9, there are 10 reported temperatures + // The first and last node represent HALF volume each + + // Calculate HTF properties + double T_tes_ave = 0.5 * (m_T_hot_des + m_T_cold_des); //[K] + double dens_htf_ave = mc_external_htfProps.dens(T_tes_ave, 1.0); //[kg/m^3] Density at average temperature + double cp_htf_ave = mc_external_htfProps.Cp_ave(m_T_cold_des, m_T_hot_des) * 1000.0;//[J/kg-K] Specific heat at average temperature + + // Calculate Available Discharge Energy + double dx = m_h_tank_calc / m_n_xstep; // [m] + double mass_packed_subsection = dx * m_Ac * (1.0 - m_void_frac) * m_dens_solid; // [kg] Packed bed mass in subsection + double mass_htf_subsection = dx * m_Ac * m_void_frac * dens_htf_ave; //[kg] HTF mass in subsection + + // Loop through temperatures, starting at hot end, to check temperature threshold + double Q_dc_avail = 0; // [MJt] + for(int i = 0; i < m_T_prev_vec.size(); i++) + { + double T_prev_node = m_T_prev_vec[i]; + double local_packed_mass = mass_packed_subsection; + double local_htf_mass = mass_htf_subsection; + + // Mass is half if first or last node + if (i == 0 || i == m_T_prev_vec.size() - 1) + { + local_packed_mass = local_packed_mass / 2.0; + local_htf_mass = local_htf_mass / 2.0; + } + + // Check if node is above threshold + if (T_prev_node >= (m_T_hot_des - m_T_hot_delta)) + { + double Q_avail_packed = local_packed_mass * m_cp_solid * (T_prev_node - T_cold_K) * 1e-6; // [MJt] + double Q_avail_htf = local_htf_mass * cp_htf_ave * (T_prev_node - T_cold_K) * 1e-6; // [MJt] + Q_dc_avail += Q_avail_packed; + Q_dc_avail += Q_avail_htf; + } + else + { + //break; + } + } + + // Calculate Q dot avail + double Q_dot_dc_avail = Q_dc_avail / step_s; // [MWt] + + // Calculate Max Mass Flow Rate + double mdot_max = (Q_dc_avail * 1e6) / (step_s * cp_htf_ave * (m_T_hot_des - T_cold_K)); //[kg/s] + + // Set Outputs + q_dot_dc_est = Q_dot_dc_avail; //[MWt] + m_dot_external_est = mdot_max; //[kg/s] + T_hot_external_est = m_T_prev_vec[0]; // [K] Temperature near charge inlet (hottest) + + return; +} + +void C_csp_packedbed_tes::charge_avail_est(double T_hot_K, double step_s, + double& q_dot_ch_est /*MWt*/, double& m_dot_external_est /*kg/s*/, double& T_cold_external_est /*K*/) +{ + // Temperatures are at the node between segments + // if m_n_xstep == 9, there are 10 reported temperatures + // The first and last node represent HALF volume each + + // First check if charge temp is hot enough + if (T_hot_K < m_T_charge_min) + { + q_dot_ch_est = 0; + m_dot_external_est = 0; + T_cold_external_est = 0; + return; + } + + // Calculate HTF properties + double T_tes_ave = 0.5 * (m_T_hot_des + m_T_cold_des); //[K] + double dens_htf_ave = mc_external_htfProps.dens(T_tes_ave, 1.0); //[kg/m^3] Density at average temperature + double cp_htf_ave = mc_external_htfProps.Cp_ave(m_T_cold_des, m_T_hot_des) * 1000.0;//[J/kg-K] Specific heat at average temperature + + // Calculate Available Charge Energy + double dx = m_h_tank_calc / m_n_xstep; // [m] + double mass_packed_subsection = dx * m_Ac * (1.0 - m_void_frac) * m_dens_solid; // [kg] Packed bed mass in subsection + double mass_htf_subsection = dx * m_Ac * m_void_frac * dens_htf_ave; //[kg] HTF mass in subsection + + // Loop through temperatures, starting at cold end, to check temperature threshold + double Q_ch_avail = 0; //[MJt] + double count = 0; + for (int i = m_T_prev_vec.size() - 1; i >= 0; i--) + { + double T_prev_node = m_T_prev_vec[i]; //[K] + double local_packed_mass = mass_packed_subsection; + double local_htf_mass = mass_htf_subsection; + + // Mass is half if first or last node + if (i == 0 || i == m_T_prev_vec.size() - 1) + { + local_packed_mass = local_packed_mass / 2.0; + local_htf_mass = local_htf_mass / 2.0; + } + + if (T_prev_node <= (m_T_cold_des + m_T_cold_delta)) + { + double Q_avail_packed = local_packed_mass * m_cp_solid * (T_hot_K - m_T_cold_des) * 1e-6; //[MJt] + double Q_avail_htf = local_htf_mass * cp_htf_ave * (T_hot_K - m_T_cold_des) * 1e-6; //[MJt] + Q_ch_avail += Q_avail_packed; + Q_ch_avail += Q_avail_htf; + count++; + } + else + { + //break; + } + } + + // Calculate Q dot avail + double Q_dot_ch_avail = Q_ch_avail / step_s; // [MWt] + + // Calculate Max Mass Flow Rate + double mdot_max = (Q_ch_avail * 1e6) / (step_s * cp_htf_ave * (T_hot_K - m_T_cold_des)); //[kg/s] + + // Set Outputs + q_dot_ch_est = Q_dot_ch_avail; //[MWt] + m_dot_external_est = mdot_max; //[kg/s] + T_cold_external_est = m_T_prev_vec[m_T_prev_vec.size() - 1]; // [K] Temperature near charge outlet (coldest) + + return; +} + +int C_csp_packedbed_tes::solve_tes_off_design(double timestep /*s*/, double T_amb /*K*/, + double m_dot_cr_to_cv_hot /*kg/s*/, double m_dot_cv_hot_to_sink /*kg/s*/, double m_dot_cr_to_cv_cold /*kg/s*/, + double T_cr_out_hot /*K*/, double T_sink_out_cold /*K*/, + double& T_sink_htf_in_hot /*K*/, double& T_cr_in_cold /*K*/, + C_csp_tes::S_csp_tes_outputs& s_outputs) //, C_csp_solver_htf_state & s_tes_ch_htf, C_csp_solver_htf_state & s_tes_dc_htf) +{ + // Enthalpy balance on inlet to cold cv + double T_htf_cold_cv_in = T_sink_out_cold; //[K] + double m_dot_total_to_cv_cold = m_dot_cv_hot_to_sink + m_dot_cr_to_cv_cold; //[kg/s] + if (m_dot_total_to_cv_cold > 0.0) { + T_htf_cold_cv_in = (m_dot_cv_hot_to_sink * T_sink_out_cold + m_dot_cr_to_cv_cold * T_cr_out_hot) / (m_dot_total_to_cv_cold); + } + + s_outputs = S_csp_tes_outputs(); + + double m_dot_cr_to_tes_hot, m_dot_cr_to_tes_cold, m_dot_tes_hot_out, m_dot_pc_to_tes_cold, m_dot_tes_cold_out, m_dot_tes_cold_in; + m_dot_cr_to_tes_hot = m_dot_cr_to_tes_cold = m_dot_tes_hot_out = m_dot_pc_to_tes_cold = m_dot_tes_cold_out = m_dot_tes_cold_in = std::numeric_limits::quiet_NaN(); + double m_dot_src_to_sink, m_dot_sink_to_src; + m_dot_src_to_sink = m_dot_sink_to_src = std::numeric_limits::quiet_NaN(); + + // Receiver bypass is possible in a parallel configuration, + // but need to determine if it actually makes sense and how to model it + if (m_dot_cr_to_cv_cold != 0.0) { + throw(C_csp_exception("Receiver output to cold tank not allowed in parallel TES configuration")); + } + m_dot_cr_to_tes_cold = 0.0; + + if (m_dot_cr_to_cv_hot >= m_dot_cv_hot_to_sink) + { + m_dot_cr_to_tes_hot = m_dot_cr_to_cv_hot - m_dot_cv_hot_to_sink; //[kg/s] + m_dot_tes_hot_out = 0.0; //[kg/s] + m_dot_pc_to_tes_cold = 0.0; //[kg/s] + m_dot_tes_cold_out = m_dot_cr_to_tes_hot; //[kg/s] + m_dot_src_to_sink = m_dot_cv_hot_to_sink; //[kg/s] + m_dot_sink_to_src = m_dot_cv_hot_to_sink; //[kg/s] + } + else + { + m_dot_cr_to_tes_hot = 0.0; //[kg/s] + m_dot_tes_hot_out = m_dot_cv_hot_to_sink - m_dot_cr_to_cv_hot; //[kg/s] + m_dot_pc_to_tes_cold = m_dot_tes_hot_out; //[kg/s] + m_dot_tes_cold_out = 0.0; //[kg/s] + m_dot_src_to_sink = m_dot_cr_to_cv_hot; //[kg/s] + m_dot_sink_to_src = m_dot_cr_to_cv_hot; //[kg/s] + } + m_dot_tes_cold_in = m_dot_pc_to_tes_cold; + + double q_dot_heater = std::numeric_limits::quiet_NaN(); //[MWe] Heating power required to keep tanks at a minimum temperature + double m_dot_cold_tank_to_hot_tank = std::numeric_limits::quiet_NaN(); //[kg/s] Hot tank mass flow rate, valid for direct and indirect systems + double W_dot_rhtf_pump = std::numeric_limits::quiet_NaN(); //[MWe] Pumping power, just for tank-to-tank in indirect storage + double q_dot_loss = std::numeric_limits::quiet_NaN(); //[MWt] Storage thermal losses + double q_dot_dc_to_htf = std::numeric_limits::quiet_NaN(); //[MWt] Thermal power to the HTF from storage + double q_dot_ch_from_htf = std::numeric_limits::quiet_NaN(); //[MWt] Thermal power from the HTF to storage + double T_hot_ave = std::numeric_limits::quiet_NaN(); //[K] Average hot tank temperature over timestep + double T_cold_ave = std::numeric_limits::quiet_NaN(); //[K] Average cold tank temperature over timestep + double T_hot_final = std::numeric_limits::quiet_NaN(); //[K] Hot tank temperature at end of timestep + double T_cold_final = std::numeric_limits::quiet_NaN(); //[K] Cold tank temperature at end of timestep + + if (m_dot_cr_to_cv_hot >= m_dot_cv_hot_to_sink) // Charging + { + T_sink_htf_in_hot = T_cr_out_hot; //[K] + double m_dot_tes_ch = m_dot_cr_to_cv_hot - m_dot_cv_hot_to_sink; //[kg/s] + double T_htf_tes_cold = std::numeric_limits::quiet_NaN(); //[K] + bool ch_solved = charge(timestep, + T_amb, + m_dot_tes_ch, + T_cr_out_hot, + T_htf_tes_cold, + q_dot_heater, m_dot_cold_tank_to_hot_tank, W_dot_rhtf_pump, + q_dot_loss, q_dot_dc_to_htf, q_dot_ch_from_htf, + T_hot_ave, T_cold_ave, T_hot_final, T_cold_final); + + // Check if TES.charge method solved + if (!ch_solved) + { + return -3; + } + + // Enthalpy balance to calculate T_htf_cold to CR + if (m_dot_cr_to_cv_hot == 0.0) + { + T_cr_in_cold = T_htf_tes_cold; //[K] + } + else + { + T_cr_in_cold = (m_dot_tes_ch * T_htf_tes_cold + m_dot_cv_hot_to_sink * T_sink_out_cold) / m_dot_cr_to_cv_hot; //[K] + } + } + else + { + T_cr_in_cold = T_sink_out_cold; //[K] + double m_dot_tes_dc = m_dot_cv_hot_to_sink - m_dot_cr_to_cv_hot; //[kg/s] + double T_htf_tes_hot = std::numeric_limits::quiet_NaN(); + bool is_tes_success = discharge(timestep, + T_amb, + m_dot_tes_dc, + T_sink_out_cold, + T_htf_tes_hot, + q_dot_heater, m_dot_cold_tank_to_hot_tank, W_dot_rhtf_pump, + q_dot_loss, q_dot_dc_to_htf, q_dot_ch_from_htf, + T_hot_ave, T_cold_ave, T_hot_final, T_cold_final); + + m_dot_cold_tank_to_hot_tank *= -1.0; + + // Check if discharge method solved + if (!is_tes_success) + { + return -4; + } + + T_sink_htf_in_hot = (m_dot_tes_dc * T_htf_tes_hot + m_dot_cr_to_cv_hot * T_cr_out_hot) / m_dot_cv_hot_to_sink; //[K] + } + + // No need to call pumping_power because pumping power is same as solved in charge/discharge + /*double W_dot_htf_pump = pumping_power(m_dot_cr_to_cv_hot, m_dot_cv_hot_to_sink, std::abs(m_dot_cold_tank_to_hot_tank), + T_cr_in_cold, T_cr_out_hot, T_sink_htf_in_hot, T_sink_out_cold, + false);*/ + + // Set values not part of packed bed model + s_outputs.m_q_heater = q_dot_heater; //[MWe] Heater + s_outputs.m_W_dot_elec_in_tot = W_dot_rhtf_pump; //[MWe] Use this pumping power? + + s_outputs.m_q_dot_dc_to_htf = q_dot_dc_to_htf; //[MWt] Thermal power to the HTF from storage + s_outputs.m_q_dot_ch_from_htf = q_dot_ch_from_htf; //[MWt] Thermal power from the HTF to storage + s_outputs.m_m_dot_cr_to_tes_hot = m_dot_cr_to_tes_hot; //[kg/s] + s_outputs.m_m_dot_cr_to_tes_cold = m_dot_cr_to_tes_cold; //[kg/s] + s_outputs.m_m_dot_tes_hot_out = m_dot_tes_hot_out; //[kg/s] + s_outputs.m_m_dot_pc_to_tes_cold = m_dot_pc_to_tes_cold; //[kg/s] + s_outputs.m_m_dot_tes_cold_out = m_dot_tes_cold_out; //[kg/s] + s_outputs.m_m_dot_tes_cold_in = m_dot_tes_cold_in; //[kg/s] + s_outputs.m_m_dot_src_to_sink = m_dot_src_to_sink; //[kg/s] + s_outputs.m_m_dot_sink_to_src = m_dot_sink_to_src; //[kg/s] + + s_outputs.m_T_tes_cold_in = T_htf_cold_cv_in; //[K] + + s_outputs.m_m_dot_cold_tank_to_hot_tank = m_dot_cold_tank_to_hot_tank; + + mc_reported_outputs.value(E_Q_DOT_LOSS, q_dot_loss); //[MWt] + mc_reported_outputs.value(E_W_DOT_HEATER, q_dot_heater); //[MWt] + mc_reported_outputs.value(E_TES_T_HOT, T_hot_final - 273.15); //[C] + mc_reported_outputs.value(E_TES_T_COLD, T_cold_final - 273.15); //[C] + mc_reported_outputs.value(E_M_DOT_TANK_TO_TANK, m_dot_cold_tank_to_hot_tank); //[kg/s] + //mc_reported_outputs.value(E_MASS_COLD_TANK, mc_cold_tank.get_m_m_calc()); //[kg] + //mc_reported_outputs.value(E_MASS_HOT_TANK, mc_hot_tank.get_m_m_calc()); //[kg] + mc_reported_outputs.value(E_W_DOT_HTF_PUMP, W_dot_rhtf_pump); //[MWe] + //mc_reported_outputs.value(E_VOL_TOT, vol_total); //[m3] + //mc_reported_outputs.value(E_MASS_TOT, mc_cold_tank.get_m_m_calc() + mc_hot_tank.get_m_m_calc()); //[m3] + mc_reported_outputs.value(E_VOL_TOT, m_V_tank); //[m3] + mc_reported_outputs.value(E_MASS_TOT, m_mass_solid); //[m3] + + // Report average thermocline temperatures + std::vector reduced_T_vec = reduce_vector_avg(m_T_calc_vec, 10); //[K] + mc_reported_outputs.value(E_T_GRAD_0, reduced_T_vec[0] - 273.15); //[C] + mc_reported_outputs.value(E_T_GRAD_1, reduced_T_vec[1] - 273.15); //[C] + mc_reported_outputs.value(E_T_GRAD_2, reduced_T_vec[2] - 273.15); //[C] + mc_reported_outputs.value(E_T_GRAD_3, reduced_T_vec[3] - 273.15); //[C] + mc_reported_outputs.value(E_T_GRAD_4, reduced_T_vec[4] - 273.15); //[C] + mc_reported_outputs.value(E_T_GRAD_5, reduced_T_vec[5] - 273.15); //[C] + mc_reported_outputs.value(E_T_GRAD_6, reduced_T_vec[6] - 273.15); //[C] + mc_reported_outputs.value(E_T_GRAD_7, reduced_T_vec[7] - 273.15); //[C] + mc_reported_outputs.value(E_T_GRAD_8, reduced_T_vec[8] - 273.15); //[C] + mc_reported_outputs.value(E_T_GRAD_9, reduced_T_vec[9] - 273.15); //[C] + + return 0; +} + +void C_csp_packedbed_tes::converged() +{ + m_T_prev_vec = m_T_calc_vec; + + mc_reported_outputs.value(E_HOT_TANK_HTF_PERC_FINAL, get_hot_tank_vol_frac() * 100.0); + + mc_reported_outputs.set_timestep_outputs(); + + return; +} + +void C_csp_packedbed_tes::write_output_intervals(double report_time_start, + const std::vector& v_temp_ts_time_end, double report_time_end) +{ + mc_reported_outputs.send_to_reporting_ts_array(report_time_start, + v_temp_ts_time_end, report_time_end); +} + +void C_csp_packedbed_tes::assign(int index, double* p_reporting_ts_array, size_t n_reporting_ts_array) +{ + mc_reported_outputs.assign(index, p_reporting_ts_array, n_reporting_ts_array); +} + +double /*MWe*/ C_csp_packedbed_tes::pumping_power(double m_dot_sf /*kg/s*/, double m_dot_pb /*kg/s*/, double m_dot_tank /*kg/s*/, + double T_sf_in /*K*/, double T_sf_out /*K*/, double T_pb_in /*K*/, double T_pb_out /*K*/, bool recirculating) +{ + double mdot_net = std::abs(m_dot_sf - m_dot_pb); //[kg/s] m_dot_tank is always NaN for packed bed TES + double htf_pump_power = (m_tes_pump_coef * mdot_net) / 1000.0; //[MWe] + + return htf_pump_power; +} + + +void C_csp_packedbed_tes::get_design_parameters(double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, + double& h_tank_calc /*m*/, double& d_tank_calc /*m*/, + double& q_dot_loss_des /*MWt*/, double& dens_store_htf_at_T_ave /*kg/m3*/, double& Q_tes /*MWt-hr*/) +{ + vol_one_temp_avail = m_V_tank; //[m3] + vol_one_temp_total = m_V_tank; //[m3] + h_tank_calc = m_h_tank_calc; //[m] + d_tank_calc = m_d_tank_calc; //[m] + q_dot_loss_des = 0.0; //[MWt] + dens_store_htf_at_T_ave = m_dens_solid; //[kg/m3] This is just the density of the solid media (?) + Q_tes = m_Q_tes_actual; //[MWt-hr] + + return; +} + +bool C_csp_packedbed_tes::charge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, + double T_htf_hot_in /*K*/, double& T_htf_cold_out /*K*/, + double& q_dot_heater /*MWe*/, double& m_dot_tank_to_tank /*kg/s*/, double& W_dot_rhtf_pump /*MWe*/, + double& q_dot_loss /*MWt*/, double& q_dot_dc_to_htf /*MWt*/, double& q_dot_ch_from_htf /*MWt*/, + double& T_hot_ave /*K*/, double& T_cold_ave /*K*/, double& T_hot_final /*K*/, double& T_cold_final /*K*/ +) +{ + + + // Calculate subtimestep + int N_subtimesteps_local = (int)std::ceil(timestep / m_subtimestep_nominal); + double dt = timestep / (double)N_subtimesteps_local; //[s] + + // Define timestep and spatial step + double dx = m_h_tank_calc / m_n_xstep; // [m] + + + + // Initialize Temperature Vectors + std::vector T_calc_vec(m_n_xstep + 1); // [K] Temperature gradient at end of subtimestep + std::vector T_prev_vec_subtime = m_T_prev_vec; // [K] Temperature gradient at beginning of subtimestep + std::vector T_out_vec(N_subtimesteps_local, 0.0); // [K] Temperature at Outlet + std::vector T_hot_vec(N_subtimesteps_local, 0.0); // [K] Temperature closest to inlet + + // Loop through subtimesteps + for (int n = 0; n < N_subtimesteps_local; n++) + { + // Loop through space + for (int i = 0; i <= m_n_xstep; i++) + { + // HTF Properties + double dens_fluid = mc_external_htfProps.dens(T_prev_vec_subtime[i], 1); //[kg/m3] + double cp_fluid = mc_external_htfProps.Cp(T_prev_vec_subtime[i]) * 1.E3; //[J/kg-K] + + // Calculate Coefficients (assume constant for now) + double cp_eff = m_void_frac * dens_fluid * cp_fluid + + (1.0 - m_void_frac) * m_dens_solid * m_cp_solid; // [J/m3 K] + double u0 = (m_dot_htf_in / m_Ac) / dens_fluid; // [kg/s m3] --> [m/s] + double alpha = (dens_fluid * u0 * cp_fluid * dt) / (cp_eff * dx); + double beta = (m_k_eff * dt) / (cp_eff * std::pow(dx, 2.0)); + + // Charge INLET + if (i == 0) + { + T_calc_vec[i] = (T_prev_vec_subtime[0] + (alpha * T_htf_hot_in) + + (beta * (T_prev_vec_subtime[i + 1] - (2.0 * T_prev_vec_subtime[i]) + T_htf_hot_in))) + / (1.0 + alpha); + } + + // Charge OUTLET + else if (i == m_n_xstep) + { + T_calc_vec[i] = (T_prev_vec_subtime[i] + (alpha * T_calc_vec[i - 1]) + + (beta * (T_prev_vec_subtime[i] - 2.0 * T_prev_vec_subtime[i - 1] + T_prev_vec_subtime[i - 2]))) + / (1.0 + alpha); + } + else + { + T_calc_vec[i] = (T_prev_vec_subtime[i] + (alpha * T_calc_vec[i - 1]) + + (beta * (T_prev_vec_subtime[i + 1] - 2.0 * T_prev_vec_subtime[i] + T_prev_vec_subtime[i - 1]))) + / (1.0 + alpha); + } + } + + // Reset Prev Vec + T_prev_vec_subtime = T_calc_vec; + + // Save outlet temp + T_out_vec[n] = T_calc_vec[m_n_xstep]; + + // Save temp closest to inlet + T_hot_vec[n] = T_calc_vec[0]; + } + + m_T_calc_vec = T_calc_vec; + + // Calculate Time Average Outlet Temp + double T_out_avg = std::accumulate(T_out_vec.begin(), T_out_vec.end(), 0.0) / T_out_vec.size(); //[K] + + // Calculate Time Average Closest to Inlet Temp + double T_hot_avg = std::accumulate(T_hot_vec.begin(), T_hot_vec.end(), 0.0) / T_hot_vec.size(); //[K] + + // No Heater (for now) + q_dot_heater = 0.0; //[MWt] + + // No tank to tank mass (only one tank) + m_dot_tank_to_tank = std::numeric_limits::quiet_NaN(); //[kg/s] + + // Pumping Power + W_dot_rhtf_pump = m_dot_htf_in * m_tes_pump_coef / 1.E3; //[MWe] Pumping power through tank + + // Cold out = Average Outlet Temp + T_htf_cold_out = T_out_avg; //[K] + + // Heat Loss + q_dot_loss = 0; //[MWt] + + // Heat transferred from storage to htf + q_dot_dc_to_htf = 0.0; //[MWt] <- should this be calculated? + + // Average Hot Temperature in Tank + T_hot_ave = T_hot_avg; //[K] Average temperature in tank location closest to inlet + + // Average Cold outlet temperature + T_cold_ave = T_out_avg; //[K] + + // Final Hot Temperature + T_hot_final = T_hot_vec[T_hot_vec.size() - 1]; // [K] + + // Final Cold Temperature + T_cold_final = T_out_vec[T_out_vec.size() - 1]; // [K] + + // Charge power from htf to storage + q_dot_ch_from_htf = 0.0; // [MWt] + double cp_fluid_avg = mc_external_htfProps.Cp_ave(m_T_cold_des, m_T_hot_des) * 1.E3; //[J/kg-K] + for (double T_out : T_out_vec) + { + q_dot_ch_from_htf += m_dot_htf_in * cp_fluid_avg * (T_htf_hot_in - T_out) * 1.E-3 * (dt / timestep); // [MWt] + } + + return true; +} + +bool C_csp_packedbed_tes::discharge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, + double T_htf_cold_in /*K*/, double& T_htf_hot_out /*K*/, + double& q_dot_heater /*MWe*/, double& m_dot_tank_to_tank /*kg/s*/, double& W_dot_rhtf_pump /*MWe*/, + double& q_dot_loss /*MWt*/, double& q_dot_dc_to_htf /*MWt*/, double& q_dot_ch_from_htf /*MWt*/, + double& T_hot_ave /*K*/, double& T_cold_ave /*K*/, double& T_hot_final /*K*/, double& T_cold_final /*K*/ +) +{ + // Calculate subtimestep + int N_subtimesteps_local = (int)std::ceil(timestep / m_subtimestep_nominal); + double dt = timestep / (double)N_subtimesteps_local; //[s] + + // Define timestep and spatial step + double dx = m_h_tank_calc / m_n_xstep; // [m] + + // Initialize Temperature Vectors + std::vector T_calc_vec(m_n_xstep + 1); // [K] Temperature gradient at end of subtimestep + std::vector T_prev_vec_subtime = m_T_prev_vec; // [K] Temperature gradient at beginning of subtimestep + std::vector T_out_vec(N_subtimesteps_local, 0.0); // [K] Temperature at Outlet + std::vector T_cold_vec(N_subtimesteps_local, 0.0); // [K] Temperature closest to inlet (cold) + + // Loop through subtimesteps + for (int n = 0; n < N_subtimesteps_local; n++) + { + // Loop through space + for (int i = m_n_xstep; i >= 0; i--) + { + // HTF Properties + double dens_fluid = mc_external_htfProps.dens(T_prev_vec_subtime[i], 1); //[kg/m3] + double cp_fluid = mc_external_htfProps.Cp(T_prev_vec_subtime[i]) * 1.E3; //[J/kg-K] + + // Calculate Coefficients (assume constant for now) + double cp_eff = m_void_frac * dens_fluid * cp_fluid + + (1.0 - m_void_frac) * m_dens_solid * m_cp_solid; // [J/m3 K] + double u0 = (m_dot_htf_in / m_Ac) / dens_fluid; // [kg/s m3] --> [m/s] + double alpha = (dens_fluid * u0 * cp_fluid * dt) / (cp_eff * dx); + double beta = (m_k_eff * dt) / (cp_eff * std::pow(dx, 2.0)); + + // Discharge OUTLET + if (i == 0) + { + T_calc_vec[i] = (T_prev_vec_subtime[i] + (alpha * T_calc_vec[i + 1]) + + (beta * (T_prev_vec_subtime[i] - 2.0 * T_prev_vec_subtime[i + 1] + T_prev_vec_subtime[i + 2]))) + / (1.0 + alpha); + } + + // Discharge INLET + else if (i == m_n_xstep) + { + T_calc_vec[i] = (T_prev_vec_subtime[i] + (alpha * T_htf_cold_in) + + (beta * (T_prev_vec_subtime[i - 1] - (2.0 * T_prev_vec_subtime[i]) + T_htf_cold_in))) + / (1.0 + alpha); + } + + // Middle space + else + { + T_calc_vec[i] = (T_prev_vec_subtime[i] + (alpha * T_calc_vec[i + 1]) + + (beta * (T_prev_vec_subtime[i - 1] - 2.0 * T_prev_vec_subtime[i] + T_prev_vec_subtime[i + 1]))) + / (1.0 + alpha); + } + } + + // Reset Prev Vec + T_prev_vec_subtime = T_calc_vec; + + // Save outlet temp + T_out_vec[n] = T_calc_vec[0]; + + // Save Temperature closest to inlet (cold temp) + T_cold_vec[n] = T_calc_vec[T_calc_vec.size() - 1]; + } + + m_T_calc_vec = T_calc_vec; + + // Calculate Time Average Outlet Temp (hot) + double T_out_avg = std::accumulate(T_out_vec.begin(), T_out_vec.end(), 0.0) / T_out_vec.size(); + + // Calculate Time Average Cold Temp (closest to inlet) + double T_cold_avg = std::accumulate(T_cold_vec.begin(), T_cold_vec.end(), 0.0) / T_cold_vec.size(); + + // No Heater (for now) + q_dot_heater = 0.0; //[MWt] + + // No tank to tank mass (only one tank) + m_dot_tank_to_tank = std::numeric_limits::quiet_NaN(); //[kg/s] + + // Pumping Power + W_dot_rhtf_pump = m_dot_htf_in * m_tes_pump_coef / 1.E3; //[MWe] Pumping power through tank + + // Cold out = Average Outlet Temp + T_htf_hot_out = T_out_avg; //[K] + + // Heat Loss (need to calculate) + q_dot_loss = 0; //[MWt] + + // Heat transferred from storage to htf + q_dot_dc_to_htf = 0.0; //[MWt] + double cp_fluid_avg = mc_external_htfProps.Cp_ave(m_T_cold_des, m_T_hot_des) * 1.E3; //[J/kg-K] + for (double T_out : T_out_vec) + { + q_dot_dc_to_htf += m_dot_htf_in * cp_fluid_avg * (T_out - T_htf_cold_in) * 1.E-3 * (dt / timestep); // [MWt] + } + + // Average Hot Temperature in Tank + T_hot_ave = T_out_avg; //[K] Average temperature in tank location closest to inlet + + // Average Cold outlet temperature + T_cold_ave = T_cold_avg; //[K] + + // Final Hot Temperature + T_hot_final = T_out_vec[T_out_vec.size() - 1]; // [K] + + // Final Cold Temperature + T_cold_final = T_cold_vec[T_cold_vec.size() - 1]; // [K] + + // Charge power from htf to storage + q_dot_ch_from_htf = 0.0; // [MWt] + + return true; +} + +double C_csp_packedbed_tes::get_min_storage_htf_temp() +{ + return std::numeric_limits::quiet_NaN(); +} + +double C_csp_packedbed_tes::get_max_storage_htf_temp() +{ + return std::numeric_limits::quiet_NaN(); +} + +double C_csp_packedbed_tes::get_storage_htf_density() +{ + return std::numeric_limits::quiet_NaN(); +} + +double C_csp_packedbed_tes::get_storage_htf_cp() +{ + return std::numeric_limits::quiet_NaN(); +} diff --git a/tcs/csp_solver_packedbed_tes.h b/tcs/csp_solver_packedbed_tes.h new file mode 100644 index 000000000..b5c053644 --- /dev/null +++ b/tcs/csp_solver_packedbed_tes.h @@ -0,0 +1,239 @@ +/* +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 __csp_solver_packedbed_tes_ +#define __csp_solver_packedbed_tes_ + +#include "csp_solver_core.h" +#include "csp_solver_util.h" +#include "sam_csp_util.h" +#include "csp_solver_tes_core.h" + +class C_csp_packedbed_tes : public C_csp_tes +{ +private: + + // member string for exception messages + std::string error_msg; + + // Constructor Inputs + int m_external_fl; + util::matrix_t m_external_fl_props; + int m_size_type; // [] Sizing Method (0) use fixed diameter, (1) use fixed height, (2) use preset inputs + double m_Q_tes_des; // [MWt-hr] design storage capacity + double m_h_tank_in; // [m] Tank height + double m_d_tank_in; // [m] tank diameter input + double m_f_oversize; // [] Oversize factor + double m_T_cold_des; // [K] Design cold temperature + double m_T_hot_des; // [K] Design hot temperature + double m_T_tank_hot_ini; // [K] Initial hot temperature + double m_T_tank_cold_ini; // [K] Initial cold temperature + double m_f_V_hot_ini; // [] Initial fraction of hot storage + int m_n_xstep; // [] Number spatial steps + int m_n_subtimestep; // [] Number sub timesteps + double m_k_eff; // [W/m K] effective conductivity + double m_void_frac; // [] Void fraction + double m_dens_solid; // [kg/m3] density of particles + double m_cp_solid; // [J/kg K] specific heat of particles + double m_tes_pump_coef; // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop + double m_T_hot_delta; // [dC] Max allowable decrease in hot discharge temp + double m_T_cold_delta; // [dC] Max allowable increase in cold discharge temp + double m_T_charge_min; // [K] Min allowable charge temperature + + // Time step carryover + std::vector m_T_prev_vec; // [K] Temperatures in space, starting at CHARGE inlet (hot) + std::vector m_T_calc_vec; // [K] Temperatures in space, starting at CHARGE inlet (hot) + + // Calculated in init() + double m_h_tank_calc; // [m] Tank height + double m_d_tank_calc; // [m] Tank diameter + double m_Ac; // [m2] Tank cross sectional area + double m_V_tank; // [m3] Tank volume + double m_mass_solid; // [kg] Mass of packed bed media + double m_Q_tes_actual; // [MWt-hr] Storage capacity (including volume oversize factor) + double m_subtimestep_nominal; // [s] + + // Private members + bool m_is_tes; + bool m_use_T_grad_init = false; + HTFProperties mc_external_htfProps; // Instance of HTFProperties class for external HTF + + // Private Methods + void size_pb_fixed_height(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double f_oversize, + double void_frac, double dens_solid /*kg/m3*/, double cp_solid /*J/kg K*/, + double T_tes_hot /*K*/, double T_tes_cold /*K*/, double h_tank /*m*/, + double& vol_total /*m3*/, double& d_tank_out /*m*/); + + void size_pb_fixed_diameter(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double f_oversize, + double void_frac, double dens_solid /*kg/m3*/, double cp_solid /*J/kg K*/, + double T_tes_hot /*K*/, double T_tes_cold /*K*/, double d_tank /*m*/, + double& vol_total /*m3*/, double& h_tank_out /*m*/); + + std::vector reduce_vector_avg(std::vector vec, int out_vec_size); + + double get_avg_from_vec(std::vector vec, double frac); + +public: + + enum + { + E_Q_DOT_LOSS, //[MWt] TES thermal losses + E_W_DOT_HEATER, //[MWe] TES freeze protection power + E_TES_T_HOT, //[C] TES final hot tank temperature + E_TES_T_COLD, //[C] TES final cold tank temperature + E_M_DOT_TANK_TO_TANK, //[kg/s] Tank to tank mass flow rate (indirect TES) + E_MASS_COLD_TANK, //[kg] Mass in cold tank at end of timestep + E_MASS_HOT_TANK, //[kg] Mass in hot tank at end of timestep + E_HOT_TANK_HTF_PERC_FINAL, //[%] Final percent fill of available hot tank mass + E_W_DOT_HTF_PUMP, //[MWe] + E_VOL_TOT, //[m3] Total volume of hot and cold fluid in storage + E_MASS_TOT, //[kg] Total mass of hot and cold fluid in storage + E_T_GRAD_0, + E_T_GRAD_1, + E_T_GRAD_2, + E_T_GRAD_3, + E_T_GRAD_4, + E_T_GRAD_5, + E_T_GRAD_6, + E_T_GRAD_7, + E_T_GRAD_8, + E_T_GRAD_9 + }; + + + C_csp_reported_outputs mc_reported_outputs; + + + C_csp_packedbed_tes( + int external_fl, // [-] external fluid identifier + util::matrix_t external_fl_props, // [-] external fluid properties + double Q_tes_des, // [MWt-hr] design storage capacity + int size_type, // [] Sizing Method (0) use fixed diameter, (1) use fixed height, (2) use preset inputs + double h_tank_in, // [m] tank height input + double d_tank_in, // [m] tank diameter input + double f_oversize, // [] Oversize factor + double T_cold_des_C, // [C] convert to K in constructor() + double T_hot_des_C, // [C] convert to K in constructor() + double T_tank_hot_ini_C, // [C] Initial temperature in hot storage tank + double T_tank_cold_ini_C, // [C] Initial temperature in cold storage cold + double f_V_hot_ini, // [%] Initial fraction of available volume that is hot + int n_xstep, // number spatial sub steps + int n_subtimestep, // number subtimesteps + double tes_pump_coef, // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop + double k_eff_solid, // [W/m K] Solid effective thermal conductivity + double void_frac, // [] Packed bed void fraction + double dens_solid, // [kg/m3] solid specific heat + double cp_solid, // [J/kg K] solid specific heat + double T_hot_delta, // [C] Max allowable decrease in hot discharge temp + double T_cold_delta, // [C] Max allowable increase in cold discharge temp + double T_charge_min // [C] Min allowable charge temperature + ); + + C_csp_packedbed_tes(); + + ~C_csp_packedbed_tes() {}; + + void set_T_grad_init(std::vector T_grad_init_C); + + virtual void init(const C_csp_tes::S_csp_tes_init_inputs init_inputs); + + virtual bool does_tes_exist(); + + virtual bool is_cr_to_cold_allowed(); + + virtual double get_hot_temp(); + + virtual double get_cold_temp(); + + virtual double get_hot_tank_vol_frac(); + + virtual double get_initial_charge_energy(); //MWh + + virtual double get_min_charge_energy(); //MWh + + virtual double get_max_charge_energy(); //MWh + + virtual double get_degradation_rate(); // s^-1 + + virtual void reset_storage_to_initial_state(); + + virtual void discharge_avail_est(double T_cold_K, double step_s, + double& q_dot_dc_est /*MWt*/, double& m_dot_external_est /*kg/s*/, double& T_hot_external_est /*K*/); + + virtual void charge_avail_est(double T_hot_K, double step_s, + double& q_dot_ch_est /*MWt*/, double& m_dot_external_est /*kg/s*/, double& T_cold_external_est /*K*/); + + virtual int solve_tes_off_design(double timestep /*s*/, double T_amb /*K*/, + double m_dot_cr_to_cv_hot /*kg/s*/, double m_dot_cv_hot_to_sink /*kg/s*/, double m_dot_cr_to_cv_cold /*kg/s*/, + double T_cr_out_hot /*K*/, double T_sink_out_cold /*K*/, + double& T_sink_htf_in_hot /*K*/, double& T_cr_in_cold /*K*/, + C_csp_tes::S_csp_tes_outputs& outputs); + + virtual void converged(); + + virtual void write_output_intervals(double report_time_start, + const std::vector& v_temp_ts_time_end, double report_time_end); + + virtual void assign(int index, double* p_reporting_ts_array, size_t n_reporting_ts_array); + + virtual /*MWe*/ double pumping_power(double m_dot_sf /*kg/s*/, double m_dot_pb /*kg/s*/, double m_dot_tank /*kg/s*/, + double T_sf_in /*K*/, double T_sf_out /*K*/, double T_pb_in /*K*/, double T_pb_out /*K*/, bool recirculating); + + void get_design_parameters(double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, + double& h_tank /*m*/, double& d_tank /*m*/, + double& q_dot_loss_des /*MWt*/, double& dens_store_htf_at_T_ave /*kg/m3*/, double& Q_tes /*MWt-hr*/); + + bool charge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, + double T_htf_hot_in, double& T_htf_cold_out /*K*/, + double& q_dot_heater /*MWe*/, double& m_dot /*kg/s*/, double& W_dot_rhtf_pump /*MWe*/, + double& q_dot_loss /*MWt*/, double& q_dot_dc_to_htf /*MWt*/, double& q_dot_ch_from_htf /*MWt*/, + double& T_hot_ave /*K*/, double& T_cold_ave /*K*/, double& T_hot_final /*K*/, double& T_cold_final /*K*/); + + bool discharge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, + double T_htf_cold_in, double& T_htf_hot_out /*K*/, + double& q_dot_heater /*MWe*/, double& m_dot /*kg/s*/, double& W_dot_rhtf_pump /*MWe*/, + double& q_dot_loss /*MWt*/, double& q_dot_dc_to_htf /*MWt*/, double& q_dot_ch_from_htf /*MWt*/, + double& T_hot_ave /*K*/, double& T_cold_ave /*K*/, double& T_hot_final /*K*/, double& T_cold_final /*K*/); + + double get_max_storage_htf_temp(); + + double get_min_storage_htf_temp(); + + double get_storage_htf_density(); + + double get_storage_htf_cp(); + + std::vector get_T_prev_vec() { return m_T_prev_vec; }; +}; + + +#endif diff --git a/tcs/csp_solver_tes_core.cpp b/tcs/csp_solver_tes_core.cpp new file mode 100644 index 000000000..fd8a451b7 --- /dev/null +++ b/tcs/csp_solver_tes_core.cpp @@ -0,0 +1,362 @@ +/* +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 "csp_solver_tes_core.h" + + + +void two_tank_tes_sizing(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, + double T_tes_cold /*K*/, double h_min /*m*/, double h_tank_in /*m*/, int tank_pairs /*-*/, double u_tank /*W/m^2-K*/, + double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, double& d_tank_out /*m*/, + double& q_dot_loss_des /*MWt*/) +{ + double T_tes_ave = 0.5 * (T_tes_hot + T_tes_cold); //[K] + + double rho_ave = tes_htf_props.dens(T_tes_ave, 1.0); //[kg/m^3] Density at average temperature + double cp_ave = tes_htf_props.Cp_ave(T_tes_cold, T_tes_hot);//[kJ/kg-K] Specific heat at average temperature + + // Volume required to supply design hours of thermal energy storage + //[m^3] = [MJ/s-hr] * [sec]/[hr] = [MJ] / (kg/m^3 * MJ/kg-K * K + vol_one_temp_avail = Q_tes_des * 3600.0 / (rho_ave * cp_ave / 1000.0 * (T_tes_hot - T_tes_cold)); + + // Additional volume necessary due to minimum tank limits + vol_one_temp_total = vol_one_temp_avail / (1.0 - h_min / h_tank_in); //[m^3] + + double A_cs = vol_one_temp_total / (h_tank_in * tank_pairs); //[m^2] Cross-sectional area of a single tank + + d_tank_out = pow(A_cs / CSP::pi, 0.5) * 2.0; //[m] Diameter of a single tank + + double UA_tanks_one_temp = u_tank * (A_cs + CSP::pi * d_tank_out * h_tank_in) * tank_pairs; //[W/K] + + double T_amb_des = 15.0 + 273.15; //[K] + double q_dot_loss_cold = UA_tanks_one_temp * (T_tes_cold - T_amb_des) * 1.E-6; //[MWt] + double q_dot_loss_hot = UA_tanks_one_temp * (T_tes_hot - T_amb_des) * 1.E-6; //[MWt] + q_dot_loss_des = q_dot_loss_cold + q_dot_loss_hot; //[MWt] + +} + +void two_tank_tes_sizing_fixed_diameter(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, + double T_tes_cold /*K*/, double h_min /*m*/, double d_tank_in, int tank_pairs /*-*/, double u_tank /*W/m^2-K*/, + double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, double& h_tank_out /*m*/, + double& q_dot_loss_des /*MWt*/) +{ + double T_tes_ave = 0.5 * (T_tes_hot + T_tes_cold); //[K] + + double rho_ave = tes_htf_props.dens(T_tes_ave, 1.0); //[kg/m^3] Density at average temperature + double cp_ave = tes_htf_props.Cp_ave(T_tes_cold, T_tes_hot);//[kJ/kg-K] Specific heat at average temperature + + // Volume required to supply design hours of thermal energy storage + //[m^3] = [MJ/s-hr] * [sec]/[hr] = [MJ] / (kg/m^3 * MJ/kg-K * K + vol_one_temp_avail = Q_tes_des * 3600.0 / (rho_ave * cp_ave / 1000.0 * (T_tes_hot - T_tes_cold)); + + double A_cs = CSP::pi * std::pow(d_tank_in, 2.0) * 0.25 ; //[m^2] Cross-sectional area of a single tank + + // Additional volume necessary due to minimum tank limits + double vol_added_min = A_cs * h_min * tank_pairs; //[m^3] + + // Total Volume + vol_one_temp_total = vol_one_temp_avail + vol_added_min; //[m^3] + + // Tank Height + h_tank_out = vol_one_temp_total / (A_cs * tank_pairs); // [m] + + double UA_tanks_one_temp = u_tank * (A_cs + CSP::pi * d_tank_in * h_tank_out) * tank_pairs; //[W/K] + + double T_amb_des = 15.0 + 273.15; //[K] + double q_dot_loss_cold = UA_tanks_one_temp * (T_tes_cold - T_amb_des) * 1.E-6; //[MWt] + double q_dot_loss_hot = UA_tanks_one_temp * (T_tes_hot - T_amb_des) * 1.E-6; //[MWt] + q_dot_loss_des = q_dot_loss_cold + q_dot_loss_hot; //[MWt] + +} + +void heattrap_tes_sizing(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, + double T_tes_cold /*K*/, double h_min /*m*/, double h_tank_in /*m*/, int tank_pairs /*-*/, double u_tank /*W/m^2-K*/, + double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, double& d_tank_out /*m*/, + double& q_dot_loss_des /*MWt*/) +{ + double T_tes_ave = 0.5 * (T_tes_hot + T_tes_cold); //[K] + + double rho_ave = tes_htf_props.dens(T_tes_ave, 1.0); //[kg/m^3] Density at average temperature + double cp_ave = tes_htf_props.Cp_ave(T_tes_cold, T_tes_hot);//[kJ/kg-K] Specific heat at average temperature + + // Calculate required mass to store design hours of thermal energy storage + double mass_avail = (Q_tes_des * 3600.0) / (cp_ave * 1E-3 * (T_tes_hot - T_tes_cold)); + + // Calculate Main Tank Volume using hot density (so full charge can store hot density) + vol_one_temp_avail = mass_avail / tes_htf_props.dens(T_tes_hot, 1.0); + + + // Volume required to supply design hours of thermal energy storage + //[m^3] = [MJ/s-hr] * [sec]/[hr] = [MJ] / (kg/m^3 * MJ/kg-K * K + double vol_one_temp_avail_orig = Q_tes_des * 3600.0 / (rho_ave * cp_ave / 1000.0 * (T_tes_hot - T_tes_cold)); + + // Additional volume necessary due to minimum tank limits + vol_one_temp_total = vol_one_temp_avail / (1.0 - h_min / h_tank_in); //[m^3] + + double A_cs = vol_one_temp_total / (h_tank_in * tank_pairs); //[m^2] Cross-sectional area of a single tank + + d_tank_out = pow(A_cs / CSP::pi, 0.5) * 2.0; //[m] Diameter of a single tank + + double UA_tanks_one_temp = u_tank * (A_cs + CSP::pi * d_tank_out * h_tank_in) * tank_pairs; //[W/K] + + double T_amb_des = 15.0 + 273.15; //[K] + double q_dot_loss_cold = UA_tanks_one_temp * (T_tes_cold - T_amb_des) * 1.E-6; //[MWt] + double q_dot_loss_hot = UA_tanks_one_temp * (T_tes_hot - T_amb_des) * 1.E-6; //[MWt] + q_dot_loss_des = q_dot_loss_cold + q_dot_loss_hot; //[MWt] + +} + +void heattrap_tes_sizing_fixed_diameter(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, + double T_tes_cold /*K*/, double h_min /*m*/, double d_tank_in, int tank_pairs /*-*/, double u_tank /*W/m^2-K*/, + double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, double& h_tank_out /*m*/, + double& q_dot_loss_des /*MWt*/) +{ + double T_tes_ave = 0.5 * (T_tes_hot + T_tes_cold); //[K] + + double rho_ave = tes_htf_props.dens(T_tes_ave, 1.0); //[kg/m^3] Density at average temperature + double cp_ave = tes_htf_props.Cp_ave(T_tes_cold, T_tes_hot);//[kJ/kg-K] Specific heat at average temperature + + // Calculate required mass to store design hours of thermal energy storage + double mass_avail = (Q_tes_des * 3600.0) / (cp_ave * 1E-3 * (T_tes_hot - T_tes_cold)); + + // Calculate Main Tank Volume using hot density (so full charge can store hot density) + vol_one_temp_avail = mass_avail / tes_htf_props.dens(T_tes_hot, 1.0); + + // Volume required to supply design hours of thermal energy storage + //[m^3] = [MJ/s-hr] * [sec]/[hr] = [MJ] / (kg/m^3 * MJ/kg-K * K + //vol_one_temp_avail = Q_tes_des * 3600.0 / (rho_ave * cp_ave / 1000.0 * (T_tes_hot - T_tes_cold)); + + double A_cs = CSP::pi * std::pow(d_tank_in, 2.0) * 0.25; //[m^2] Cross-sectional area of a single tank + + // Additional volume necessary due to minimum tank limits + double vol_added_min = A_cs * h_min * tank_pairs; //[m^3] + + // Total Volume + vol_one_temp_total = vol_one_temp_avail + vol_added_min; //[m^3] + + // Tank Height + h_tank_out = vol_one_temp_total / (A_cs * tank_pairs); // [m] + + double UA_tanks_one_temp = u_tank * (A_cs + CSP::pi * d_tank_in * h_tank_out) * tank_pairs; //[W/K] + + double T_amb_des = 15.0 + 273.15; //[K] + double q_dot_loss_cold = UA_tanks_one_temp * (T_tes_cold - T_amb_des) * 1.E-6; //[MWt] + double q_dot_loss_hot = UA_tanks_one_temp * (T_tes_hot - T_amb_des) * 1.E-6; //[MWt] + q_dot_loss_des = q_dot_loss_cold + q_dot_loss_hot; //[MWt] + +} + + +int size_tes_piping(double vel_dsn, util::matrix_t L, double rho_avg, double m_dot_pb, double solarm, + bool tanks_in_parallel, double& vol_tot, util::matrix_t& v_dot_rel, util::matrix_t& diams, + util::matrix_t& wall_thk, util::matrix_t& m_dot, util::matrix_t& vel, bool custom_sizes) +{ + const std::size_t bypass_index = 4; + const std::size_t gen_first_index = 5; // first generation section index in combined col. gen. loops + double m_dot_sf; + double v_dot_src, v_dot_sink; // source and sink vol. flow rates + double v_dot_ref; + double v_dot; // volumetric flow rate + double Area; + vol_tot = 0.0; // total volume in SGS piping + std::size_t nPipes = L.ncells(); + v_dot_rel.resize_fill(nPipes, 0.0); // volumetric flow rate relative to the source or sink flow + m_dot.resize_fill(nPipes, 0.0); + vel.resize_fill(nPipes, 0.0); + std::vector sections_no_bypass; + if (!custom_sizes) { + diams.resize_fill(nPipes, 0.0); + wall_thk.resize_fill(nPipes, 0.0); + } + + m_dot_sf = m_dot_pb * solarm; + v_dot_src = m_dot_sf / rho_avg; + v_dot_sink = m_dot_pb / rho_avg; + + //The volumetric flow rate relative to the source for each collection section (v_rel = v_dot / v_dot_sink) + v_dot_rel.at(0) = 1.0 / 2; // 1 - Source pump suction header to individual source pump inlet + // 50% -> "/2.0" . The flow rate (i.e., diameter) is sized here for the case when one pump is down. + v_dot_rel.at(1) = 1.0 / 2; // 2 - Individual SF pump discharge to SF pump discharge header + v_dot_rel.at(2) = 1.0; // 3 - Source pump discharge header to collection source section headers (i.e., runners) + v_dot_rel.at(3) = 1.0; // 4 - Source section outlet headers (i.e., runners) to expansion vessel (indirect storage) or + // hot thermal storage tank (direct storage) + v_dot_rel.at(4) = 1.0; // 5 - Bypass branch - Source section outlet headers (i.e., runners) to pump suction header (indirect) or + // cold thermal storage tank (direct) + +//The volumetric flow rate relative to the power block for each generation section + v_dot_rel.at(5) = 1.0 / 2; // 2 - SGS pump suction header to individual SGS pump inlet (applicable only for storage in series with SF) + // 50% -> "/2.0" . The flow rate (i.e., diameter) is sized here for the case when one pump is down. + v_dot_rel.at(6) = 1.0 / 2; // 3 - Individual SGS pump discharge to SGS pump discharge header (only for series storage) + v_dot_rel.at(7) = 1.0; // 4 - SGS pump discharge header to steam generator supply header (only for series storage) + + v_dot_rel.at(8) = 1.0; // 5 - Steam generator supply header to inter-steam generator piping + v_dot_rel.at(9) = 1.0; // 6 - Inter-steam generator piping to steam generator outlet header + v_dot_rel.at(10) = 1.0; // 7 - Steam generator outlet header to SF pump suction header (indirect) or cold thermal storage tank (direct) + + if (tanks_in_parallel) { + sections_no_bypass = { 0, 1, 2, 3, 8, 9, 10 }; + } + else { // tanks in series + sections_no_bypass = { 0, 1, 2, 3, 5, 6, 7, 8, 9, 10 }; + } + + // Collection loop followed by generation loop + for (std::size_t i = 0; i < nPipes; i++) { + if (L.at(i) > 0) { + i < gen_first_index ? v_dot_ref = v_dot_src : v_dot_ref = v_dot_sink; + v_dot = v_dot_ref * v_dot_rel.at(i); + if (!custom_sizes) { + diams.at(i) = CSP::pipe_sched(sqrt(4.0 * v_dot / (vel_dsn * CSP::pi))); + wall_thk.at(i) = CSP::WallThickness(diams.at(i)); + } + m_dot.at(i) = v_dot * rho_avg; + Area = CSP::pi * pow(diams.at(i), 2) / 4.; + vel.at(i) = v_dot / Area; + + // Calculate total volume, excluding bypass branch + if (std::find(sections_no_bypass.begin(), sections_no_bypass.end(), i) != sections_no_bypass.end()) { + vol_tot += Area * L.at(i); + } + } + } + + return 0; +} + +int size_tes_piping_TandP(HTFProperties& external_htf_props, double T_src_in, double T_src_out, double P_src_in, double dP_discharge, + const util::matrix_t& L, const util::matrix_t& k_tes_loss_coeffs, double pipe_rough, + bool tanks_in_parallel, const util::matrix_t& diams, const util::matrix_t& vel, + util::matrix_t& TES_T_des, util::matrix_t& TES_P_des, double& TES_P_in) +{ + std::size_t nPipes = L.ncells(); + TES_T_des.resize_fill(nPipes, 0.0); + TES_P_des.resize_fill(nPipes, 0.0); + + // Calculate Design Temperatures, in C + TES_T_des.at(0) = T_src_in - 273.15; + TES_T_des.at(1) = T_src_in - 273.15; + TES_T_des.at(2) = T_src_in - 273.15; + TES_T_des.at(3) = T_src_out - 273.15; + TES_T_des.at(4) = T_src_out - 273.15; + if (tanks_in_parallel) { + TES_T_des.at(5) = 0; + TES_T_des.at(6) = 0; + TES_T_des.at(7) = 0; + } + else { + TES_T_des.at(5) = T_src_out - 273.15; + TES_T_des.at(6) = T_src_out - 273.15; + TES_T_des.at(7) = T_src_out - 273.15; + } + TES_T_des.at(8) = T_src_out - 273.15; + TES_T_des.at(9) = T_src_in - 273.15; + TES_T_des.at(10) = T_src_in - 273.15; + + + // Calculate Design Pressures, in Pa + double ff; + double rho_avg = external_htf_props.dens((T_src_in + T_src_out) / 2, 9 / 1.e-5); + const double P_hi = 17 / 1.e-5; // downstream SF pump pressure [Pa] + const double P_lo = 1 / 1.e-5; // atmospheric pressure [Pa] + + // P_10 + ff = CSP::FrictionFactor(pipe_rough / diams.at(10), external_htf_props.Re(TES_T_des.at(10), P_lo, vel.at(10), diams.at(10))); + TES_P_des.at(10) = 0 + + CSP::MajorPressureDrop(vel.at(10), rho_avg, ff, L.at(10), diams.at(10)) + + CSP::MinorPressureDrop(vel.at(10), rho_avg, k_tes_loss_coeffs.at(10)); + + // P_9 + ff = CSP::FrictionFactor(pipe_rough / diams.at(9), external_htf_props.Re(TES_T_des.at(9), P_lo, vel.at(9), diams.at(9))); + TES_P_des.at(9) = TES_P_des.at(10) + + CSP::MajorPressureDrop(vel.at(9), rho_avg, ff, L.at(9), diams.at(9)) + + CSP::MinorPressureDrop(vel.at(9), rho_avg, k_tes_loss_coeffs.at(9)); + + // P_8 + ff = CSP::FrictionFactor(pipe_rough / diams.at(8), external_htf_props.Re(TES_T_des.at(8), P_hi, vel.at(8), diams.at(8))); + TES_P_des.at(8) = TES_P_des.at(9) + dP_discharge + + CSP::MajorPressureDrop(vel.at(8), rho_avg, ff, L.at(8), diams.at(8)) + + CSP::MinorPressureDrop(vel.at(8), rho_avg, k_tes_loss_coeffs.at(8)); + + if (tanks_in_parallel) { + TES_P_des.at(7) = 0; + TES_P_des.at(6) = 0; + TES_P_des.at(5) = 0; + } + else { + // P_7 + ff = CSP::FrictionFactor(pipe_rough / diams.at(7), external_htf_props.Re(TES_T_des.at(7), P_hi, vel.at(7), diams.at(7))); + TES_P_des.at(7) = TES_P_des.at(8) + + CSP::MajorPressureDrop(vel.at(7), rho_avg, ff, L.at(7), diams.at(7)) + + CSP::MinorPressureDrop(vel.at(7), rho_avg, k_tes_loss_coeffs.at(7)); + + // P_6 + ff = CSP::FrictionFactor(pipe_rough / diams.at(6), external_htf_props.Re(TES_T_des.at(6), P_hi, vel.at(6), diams.at(6))); + TES_P_des.at(6) = TES_P_des.at(7) + + CSP::MajorPressureDrop(vel.at(6), rho_avg, ff, L.at(6), diams.at(6)) + + CSP::MinorPressureDrop(vel.at(6), rho_avg, k_tes_loss_coeffs.at(6)); + + // P_5 + TES_P_des.at(5) = 0; + } + + // P_3 + ff = CSP::FrictionFactor(pipe_rough / diams.at(3), external_htf_props.Re(TES_T_des.at(3), P_lo, vel.at(3), diams.at(3))); + TES_P_des.at(3) = 0 + + CSP::MajorPressureDrop(vel.at(3), rho_avg, ff, L.at(3), diams.at(3)) + + CSP::MinorPressureDrop(vel.at(3), rho_avg, k_tes_loss_coeffs.at(3)); + + // P_4 + TES_P_des.at(4) = TES_P_des.at(3); + + // P_2 + ff = CSP::FrictionFactor(pipe_rough / diams.at(2), external_htf_props.Re(TES_T_des.at(2), P_hi, vel.at(2), diams.at(2))); + TES_P_des.at(2) = P_src_in + + CSP::MajorPressureDrop(vel.at(2), rho_avg, ff, L.at(2), diams.at(2)) + + CSP::MinorPressureDrop(vel.at(2), rho_avg, k_tes_loss_coeffs.at(2)); + + // P_1 + ff = CSP::FrictionFactor(pipe_rough / diams.at(1), external_htf_props.Re(TES_T_des.at(1), P_hi, vel.at(1), diams.at(1))); + TES_P_des.at(1) = TES_P_des.at(2) + + CSP::MajorPressureDrop(vel.at(1), rho_avg, ff, L.at(1), diams.at(1)) + + CSP::MinorPressureDrop(vel.at(1), rho_avg, k_tes_loss_coeffs.at(1)); + + // P_0 + TES_P_des.at(0) = 0; + + // Convert Pa to bar + for (int i = 0; i < nPipes; i++) { + TES_P_des.at(i) = TES_P_des.at(i) / 1.e5; + } + TES_P_in = TES_P_des.at(3); // pressure at the inlet to the TES, at the source side + + return 0; +} diff --git a/tcs/csp_solver_tes_core.h b/tcs/csp_solver_tes_core.h new file mode 100644 index 000000000..cde31f2f5 --- /dev/null +++ b/tcs/csp_solver_tes_core.h @@ -0,0 +1,72 @@ +/* +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 __csp_solver_tes_core_ +#define __csp_solver_tes_core_ + +#include "csp_solver_core.h" +#include "csp_solver_util.h" + +#include "sam_csp_util.h" + +void two_tank_tes_sizing(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, + double T_tes_cold /*K*/, double h_min /*m*/, double h_tank_in /*m*/, int tank_pairs /*-*/, double u_tank /*W/m^2-K*/, + double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, double& d_tank_out /*m*/, + double& q_dot_loss_des /*MWt*/); + +void two_tank_tes_sizing_fixed_diameter(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, + double T_tes_cold /*K*/, double h_min /*m*/, double d_tank_in, int tank_pairs /*-*/, double u_tank /*W/m^2-K*/, + double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, double& h_tank_out /*m*/, + double& q_dot_loss_des /*MWt*/); + +void heattrap_tes_sizing(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, + double T_tes_cold /*K*/, double h_min /*m*/, double h_tank_in /*m*/, int tank_pairs /*-*/, double u_tank /*W/m^2-K*/, + double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, double& d_tank_out /*m*/, + double& q_dot_loss_des /*MWt*/); + +void heattrap_tes_sizing_fixed_diameter(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, + double T_tes_cold /*K*/, double h_min /*m*/, double d_tank_in, int tank_pairs /*-*/, double u_tank /*W/m^2-K*/, + double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, double& h_tank_out /*m*/, + double& q_dot_loss_des /*MWt*/); + + + +int size_tes_piping(double vel_dsn, util::matrix_t L, double rho_avg, double m_dot_pb, double solarm, + bool tanks_in_parallel, double& vol_tot, util::matrix_t& v_dot_rel, util::matrix_t& diams, + util::matrix_t& wall_thk, util::matrix_t& m_dot, util::matrix_t& vel, bool custom_sizes = false); + +int size_tes_piping_TandP(HTFProperties& external_htf_props, double T_src_in /*K*/, double T_src_out /*K*/, double P_src_in /*Pa*/, double dP_discharge, + const util::matrix_t& L, const util::matrix_t& k_tes_loss_coeffs, double pipe_rough, + bool tanks_in_parallel, const util::matrix_t& diams, const util::matrix_t& vel, + util::matrix_t& TES_T_des, util::matrix_t& TES_P_des, double& TES_P_in); + +#endif //__csp_solver_tes_core_ diff --git a/tcs/csp_solver_trough_collector_receiver.cpp b/tcs/csp_solver_trough_collector_receiver.cpp index 08af2870b..a543728e3 100644 --- a/tcs/csp_solver_trough_collector_receiver.cpp +++ b/tcs/csp_solver_trough_collector_receiver.cpp @@ -1291,6 +1291,10 @@ int C_csp_trough_collector_receiver::loop_energy_balance_T_t_int(const C_csp_wea m_T_loop[0] = m_T_loop_in; IntcOutputs intc_state = m_interconnects[0].State(m_dot_htf_loop * 2, m_T_loop[0], T_db, P_intc_in); m_T_loop[1] = intc_state.temp_out; + if (m_T_loop[1] < 0 || isnan(m_T_loop[1]) || m_T_loop[1] > 10000) + { + int break_here = 0; + } intc_state = m_interconnects[1].State(m_dot_htf_loop, m_T_loop[1], T_db, intc_state.pressure_out); m_T_htf_in_t_int[0] = intc_state.temp_out; diff --git a/tcs/csp_solver_two_tank_tes.cpp b/tcs/csp_solver_two_tank_tes.cpp index c2bf9796f..83c9ae6ac 100644 --- a/tcs/csp_solver_two_tank_tes.cpp +++ b/tcs/csp_solver_two_tank_tes.cpp @@ -33,382 +33,56 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "csp_solver_two_tank_tes.h" #include "csp_solver_util.h" -C_hx_two_tank_tes::C_hx_two_tank_tes() -{ - m_m_dot_des_ave = m_eff_des = m_UA_des = std::numeric_limits::quiet_NaN(); -} - -void C_hx_two_tank_tes::init(const HTFProperties &fluid_external, const HTFProperties &fluid_store, double q_transfer_des /*W*/, - double dt_des, double T_h_in_des /*K*/, double T_h_out_des /*K*/) -{ - // Counter flow heat exchanger - - mc_external_htfProps = fluid_external; - mc_store_htfProps = fluid_store; - - // Design should provide source/sink side design temperatures - double c_h = mc_external_htfProps.Cp_ave(T_h_out_des, T_h_in_des) * 1000.0; //[J/kg-K] Spec heat of hot side fluid at hot side average temperature, convert from [kJ/kg-K] - double c_c = mc_store_htfProps.Cp_ave(T_h_out_des, T_h_in_des) * 1000.0; //[J/kg-K] Spec heat of cold side fluid at hot side average temperature (estimate, but should be close) - // HX inlet and outlet temperatures - double T_c_out = T_h_in_des - dt_des; - double T_c_in = T_h_out_des - dt_des; - // Mass flow rates - double m_dot_h = q_transfer_des/(c_h*(T_h_in_des - T_h_out_des)); //[kg/s] - double m_dot_c = q_transfer_des/(c_c*(T_c_out - T_c_in)); //[kg/s] - m_m_dot_des_ave = 0.5*(m_dot_h + m_dot_c); //[kg/s] - // Capacitance rates - double c_dot_h = m_dot_h * c_h; //[W/K] - double c_dot_c = m_dot_c * c_c; //[W/K] - double c_dot_max = std::max(c_dot_h, c_dot_c); //[W/K] - double c_dot_min = std::min(c_dot_h, c_dot_c); //[W/K] - double cr = c_dot_min / c_dot_max; //[-] - // Maximum possible energy flow rate - double q_max = c_dot_min * (T_h_in_des - T_c_in); //[W] - // Effectiveness - m_eff_des = q_transfer_des / q_max; - - // Check for realistic conditions - if(cr > 1.0 || cr < 0.0) - { - throw(C_csp_exception("Heat exchanger design calculations failed", "")); - } - - double NTU = std::numeric_limits::quiet_NaN(); - if(cr < 1.0) - NTU = log((1. - m_eff_des*cr) / (1. - m_eff_des)) / (1. - cr); - else - NTU = m_eff_des / (1. - m_eff_des); - - m_UA_des = NTU * c_dot_min; //[W/K] -} - -void C_hx_two_tank_tes::solve(double T_f_htf_hx_in /*K*/, double m_dot_f_htf /*kg/s*/, - double T_s_htf_hx_in /*K*/, double m_dot_s_htf /*kg/s*/, - double & T_f_htf_hx_out /*K*/, double & T_s_htf_hx_out /*K*/, - double & eff /*-*/, double & q_dot_hx /*MWt*/) -{ - if (m_dot_f_htf == 0.0 || m_dot_s_htf == 0.0) - { - T_f_htf_hx_out = T_f_htf_hx_in; - T_s_htf_hx_out = T_s_htf_hx_in; - - eff = 0.0; - q_dot_hx = 0.0; - - return; - } - - // Scale UA - double m_dot_od = 0.5 * (m_dot_f_htf + m_dot_s_htf); //[kg/s] - double UA = m_UA_des * pow(m_dot_od / m_m_dot_des_ave, 0.8); - - double cp_f = mc_external_htfProps.Cp_ave(T_s_htf_hx_in, T_f_htf_hx_in) * 1000.0; //[J/kg-K] - double c_dot_f = cp_f * m_dot_f_htf; - double cp_s = mc_store_htfProps.Cp_ave(T_s_htf_hx_in, T_f_htf_hx_in) * 1000.0; //[J/kg-K] - double c_dot_s = cp_s * m_dot_s_htf; - - double c_dot_min = std::min(c_dot_f, c_dot_s); - - double CR = std::min(c_dot_f, c_dot_s) / std::max(c_dot_s, c_dot_f); - - double NTU = UA / c_dot_min; - - // calculate effectiveness - if (CR > 0.999) - { - eff = NTU / (1.0 + NTU); - } - else - { - eff = (1.0 - std::exp(-NTU*(1.0-CR)))/(1.0 - CR*std::exp(-NTU*(1.0-CR))); //[-] - } - - if (std::isnan(eff) || eff <= 0.0 || eff > 1.0) - { - T_f_htf_hx_out = T_s_htf_hx_out = - eff = q_dot_hx = std::numeric_limits::quiet_NaN(); - throw(C_csp_exception("Off design heat exchanger failed", "")); - } - - double T_hot_in = std::max(T_f_htf_hx_in, T_s_htf_hx_in); //[K] - double T_cold_in = std::min(T_f_htf_hx_in, T_s_htf_hx_in); //[K] - - // Calculate heat transfer in HX - double q_dot_max = c_dot_min * (T_hot_in - T_cold_in); - q_dot_hx = eff * q_dot_max; - - if (T_s_htf_hx_in > T_f_htf_hx_in) - { - T_s_htf_hx_out = T_s_htf_hx_in - q_dot_hx / c_dot_s; - T_f_htf_hx_out = T_f_htf_hx_in + q_dot_hx / c_dot_f; - } - else - { - T_s_htf_hx_out = T_s_htf_hx_in + q_dot_hx / c_dot_s; - T_f_htf_hx_out = T_f_htf_hx_in - q_dot_hx / c_dot_f; - } - - q_dot_hx *= 1.E-6; //[MWt] convert from W -} - -C_hx_cold_tes::C_hx_cold_tes() -{ - m_m_dot_des_ave = m_eff_des = m_UA_des = std::numeric_limits::quiet_NaN(); -} - -void C_hx_cold_tes::init(const HTFProperties& fluid_field, const HTFProperties& fluid_store, double q_transfer_des /*W*/, - double dt_des, double T_h_in_des /*K*/, double T_h_out_des /*K*/) -{ - // Counter flow heat exchanger - - mc_field_htfProps = fluid_field; - mc_store_htfProps = fluid_store; - - // Design should provide field/pc side design temperatures - double c_h = mc_field_htfProps.Cp_ave(T_h_out_des, T_h_in_des) * 1000.0; //[J/kg-K] Spec heat of hot side fluid at hot side average temperature, convert from [kJ/kg-K] - double c_c = mc_store_htfProps.Cp_ave(T_h_out_des, T_h_in_des) * 1000.0; //[J/kg-K] Spec heat of cold side fluid at hot side average temperature (estimate, but should be close) - // HX inlet and outlet temperatures - double T_c_out = T_h_in_des - dt_des; - double T_c_in = T_h_out_des - dt_des; - // Mass flow rates - double m_dot_h = q_transfer_des / (c_h * (T_h_in_des - T_h_out_des)); //[kg/s] - double m_dot_c = q_transfer_des / (c_c * (T_c_out - T_c_in)); //[kg/s] - m_m_dot_des_ave = 0.5 * (m_dot_h + m_dot_c); //[kg/s] - // Capacitance rates - double c_dot_h = m_dot_h * c_h; //[W/K] - double c_dot_c = m_dot_c * c_c; //[W/K] - double c_dot_max = std::max(c_dot_h, c_dot_c); //[W/K] - double c_dot_min = std::min(c_dot_h, c_dot_c); //[W/K] - double cr = c_dot_min / c_dot_max; //[-] - // Maximum possible energy flow rate - double q_max = c_dot_min * (T_h_in_des - T_c_in); //[W] - // Effectiveness - m_eff_des = q_transfer_des / q_max; - - // Check for realistic conditions - if (cr > 1.0 || cr < 0.0) - { - throw(C_csp_exception("Heat exchanger design calculations failed", "")); - } - - double NTU = std::numeric_limits::quiet_NaN(); - if (cr < 1.0) - NTU = log((1. - m_eff_des * cr) / (1. - m_eff_des)) / (1. - cr); - else - NTU = m_eff_des / (1. - m_eff_des); - - m_UA_des = NTU * c_dot_min; //[W/K] - - m_T_hot_field_prev = m_T_hot_field_calc = T_h_in_des; - m_T_cold_field_prev = m_T_cold_field_calc = T_h_out_des; - m_m_dot_field_prev = m_m_dot_field_calc = m_dot_h; - m_T_hot_tes_prev = m_T_hot_tes_calc = T_c_out; - m_T_cold_tes_prev = m_T_cold_tes_calc = T_c_in; - m_m_dot_tes_prev = m_m_dot_tes_calc = m_dot_c; -} - -void C_hx_cold_tes::hx_charge_mdot_tes(double T_cold_tes, double m_dot_tes, double T_hot_field, - double& eff, double& T_hot_tes, double& T_cold_field, double& q_trans, double& m_dot_field) -{ - hx_performance(false, true, T_hot_field, m_dot_tes, T_cold_tes, - eff, T_cold_field, T_hot_tes, q_trans, m_dot_field); -} - -void C_hx_cold_tes::hx_discharge_mdot_tes(double T_hot_tes, double m_dot_tes, double T_cold_field, - double& eff, double& T_cold_tes, double& T_hot_field, double& q_trans, double& m_dot_field) -{ - hx_performance(true, true, T_hot_tes, m_dot_tes, T_cold_field, - eff, T_cold_tes, T_hot_field, q_trans, m_dot_field); -} - -void C_hx_cold_tes::hx_charge_mdot_field(double T_hot_field, double m_dot_field, double T_cold_tes, - double& eff, double& T_cold_field, double& T_hot_tes, double& q_trans, double& m_dot_tes) -{ - hx_performance(true, false, T_hot_field, m_dot_field, T_cold_tes, - eff, T_cold_field, T_hot_tes, q_trans, m_dot_tes); -} - -void C_hx_cold_tes::hx_discharge_mdot_field(double T_cold_field, double m_dot_field, double T_hot_tes, - double& eff, double& T_hot_field, double& T_cold_tes, double& q_trans, double& m_dot_tes) -{ - hx_performance(false, false, T_hot_tes, m_dot_field, T_cold_field, - eff, T_cold_tes, T_hot_field, q_trans, m_dot_tes); -} - -void C_hx_cold_tes::hx_performance(bool is_hot_side_mdot, bool is_storage_side, double T_hot_in, double m_dot_known, double T_cold_in, - double& eff, double& T_hot_out, double& T_cold_out, double& q_trans, double& m_dot_solved) +C_storage_tank::C_storage_tank() { - // UA fixed - - // Subroutine for storage heat exchanger performance - // Pass a flag to determine whether mass flow rate is cold side or hot side. Return mass flow rate will be the other - // Also pass a flag to determine whether storage or field side mass flow rate is known. Return mass flow rate will be the other - // Inputs: hot side mass flow rate [kg/s], hot side inlet temp [K], cold side inlet temp [K] - // Outputs: HX effectiveness [-], hot side outlet temp [K], cold side outlet temp [K], - // Heat transfer between fluids [MWt], cold side mass flow rate [kg/s] - - if (m_dot_known < 0) { - eff = T_hot_out = T_cold_out = q_trans = m_dot_solved = std::numeric_limits::quiet_NaN(); - throw(C_csp_exception("HX provided a negative mass flow", "")); - } - else if (m_dot_known == 0) { - eff = 0.; - T_hot_out = T_hot_in; - T_cold_out = T_cold_in; - q_trans = 0.; - m_dot_solved = 0.; - return; - } - - double m_dot_hot, m_dot_cold, c_hot, c_cold, c_dot; - bool m_field_is_hot; // Is the field side the 'hot' side (e.g., during storage charging)? - - if (is_hot_side_mdot) // know hot side mass flow rate - assuming always know storage side and solving for field - { - if (is_storage_side) - { - m_field_is_hot = false; - c_cold = mc_field_htfProps.Cp_ave(T_cold_in, T_hot_in) * 1000.0; //[J/kg-K] - c_hot = mc_store_htfProps.Cp_ave(T_cold_in, T_hot_in) * 1000.0; //[J/kg-K] - } - else - { - m_field_is_hot = true; - c_hot = mc_field_htfProps.Cp_ave(T_cold_in, T_hot_in) * 1000.0; //[J/kg-K] - c_cold = mc_store_htfProps.Cp_ave(T_cold_in, T_hot_in) * 1000.0; //[J/kg-K] - } - - m_dot_hot = m_dot_known; - // Calculate flow capacitance of hot stream - double c_dot_hot = m_dot_hot * c_hot; //[W/K] - c_dot = c_dot_hot; - // Choose a cold stream mass flow rate that results in c_dot_h = c_dot_c - m_dot_cold = c_dot_hot / c_cold; - m_dot_solved = m_dot_cold; - } - else - { - if (is_storage_side) - { - m_field_is_hot = true; - c_hot = mc_field_htfProps.Cp_ave(T_cold_in, T_hot_in) * 1000.0; //[J/kg-K] - c_cold = mc_store_htfProps.Cp_ave(T_cold_in, T_hot_in) * 1000.0; //[J/kg-K] - } - else - { - m_field_is_hot = false; - c_cold = mc_field_htfProps.Cp_ave(T_cold_in, T_hot_in) * 1000.0; //[J/kg-K] - c_hot = mc_store_htfProps.Cp_ave(T_cold_in, T_hot_in) * 1000.0; //[J/kg-K] - } - - m_dot_cold = m_dot_known; - // Calculate flow capacitance of cold stream - double c_dot_cold = m_dot_cold * c_cold; - c_dot = c_dot_cold; - // Choose a cold stream mass flow rate that results in c_dot_c = c_dot_h - m_dot_hot = c_dot_cold / c_hot; - m_dot_solved = m_dot_hot; - } - - // Scale UA - double m_dot_od = 0.5 * (m_dot_cold + m_dot_hot); - double UA = m_UA_des * pow(m_dot_od / m_m_dot_des_ave, 0.8); - - // Calculate effectiveness - double NTU = UA / c_dot; - eff = NTU / (1.0 + NTU); - - if (std::isnan(eff) || eff <= 0.0 || eff > 1.0) - { - eff = T_hot_out = T_cold_out = q_trans = m_dot_solved = - m_T_hot_field_prev = m_T_cold_field_prev = m_T_hot_tes_prev = m_T_cold_tes_prev = - m_m_dot_field_prev = m_m_dot_tes_prev = std::numeric_limits::quiet_NaN(); - throw(C_csp_exception("Off design heat exchanger failed", "")); - } - - // Calculate heat transfer in HX - double q_dot_max = c_dot * (T_hot_in - T_cold_in); - q_trans = eff * q_dot_max; - - T_hot_out = T_hot_in - q_trans / c_dot; - T_cold_out = T_cold_in + q_trans / c_dot; - - q_trans *= 1.E-6; //[MWt] - - // Set member variables - if (m_field_is_hot == true) { - m_T_hot_field_prev = T_hot_in; - m_T_cold_field_prev = T_hot_out; - m_T_hot_tes_prev = T_cold_out; - m_T_cold_tes_prev = T_cold_in; - } - else { - m_T_hot_field_prev = T_cold_out; - m_T_cold_field_prev = T_cold_in; - m_T_hot_tes_prev = T_hot_in; - m_T_cold_tes_prev = T_hot_out; - } + m_V_prev = m_T_prev = m_m_prev = - if (is_storage_side) { - m_m_dot_field_prev = m_dot_solved; - m_m_dot_tes_prev = m_dot_known; - } - else { - m_m_dot_field_prev = m_dot_known; - m_m_dot_tes_prev = m_dot_solved; - } -} + m_V_total = m_V_active = m_V_inactive = m_UA = -C_storage_tank::C_storage_tank() -{ - m_V_prev = m_T_prev = m_m_prev = - - m_V_total = m_V_active = m_V_inactive = m_UA = - - m_T_htr = m_max_q_htr = - m_T_design = m_mass_total = m_mass_inactive = m_mass_active = std::numeric_limits::quiet_NaN(); + m_T_htr = m_max_q_htr = + m_T_design = m_mass_total = m_mass_inactive = m_mass_active = std::numeric_limits::quiet_NaN(); } void C_storage_tank::init(HTFProperties htf_class_in, double V_tank /*m3*/, - double h_tank /*m*/, double h_min /*m*/, double u_tank /*W/m2-K*/, - double tank_pairs /*-*/, double T_htr /*K*/, double max_q_htr /*MWt*/, - double V_ini /*m3*/, double T_ini /*K*/, - double T_design /*K*/) + double h_tank /*m*/, double h_min /*m*/, double u_tank /*W/m2-K*/, + double tank_pairs /*-*/, double T_htr /*K*/, double max_q_htr /*MWt*/, + double V_ini /*m3*/, double T_ini /*K*/, + double T_design /*K*/) { - mc_htf = htf_class_in; + mc_htf = htf_class_in; - double rho_des = mc_htf.dens(T_design, 1.0); //[kg/m^3] Density at average temperature + double rho_des = mc_htf.dens(T_design, 1.0); //[kg/m^3] Density at average temperature - m_V_total = V_tank; //[m^3] + m_V_total = V_tank; //[m^3] - m_mass_total = m_V_total * rho_des; //[kg] + m_mass_total = m_V_total * rho_des; //[kg] - m_V_inactive = m_V_total*h_min / h_tank; //[m^3] + m_V_inactive = m_V_total * h_min / h_tank; //[m^3] - m_mass_inactive = m_V_inactive * rho_des; //[kg] + m_mass_inactive = m_V_inactive * rho_des; //[kg] - m_V_active = m_V_total - m_V_inactive; //[m^3] + m_V_active = m_V_total - m_V_inactive; //[m^3] - m_mass_active = m_mass_total - m_mass_inactive; //[kg] + m_mass_active = m_mass_total - m_mass_inactive; //[kg] - double A_cs = m_V_total / (h_tank*tank_pairs); //[m^2] Cross-sectional area of a single tank + double A_cs = m_V_total / (h_tank * tank_pairs); //[m^2] Cross-sectional area of a single tank - double diameter = pow(A_cs / CSP::pi, 0.5)*2.0; //[m] Diameter of a single tank + double diameter = pow(A_cs / CSP::pi, 0.5) * 2.0; //[m] Diameter of a single tank - // Calculate tank conductance - m_UA = u_tank*(A_cs + CSP::pi*diameter*h_tank)*tank_pairs; //[W/K] + // Calculate tank conductance + m_UA = u_tank * (A_cs + CSP::pi * diameter * h_tank) * tank_pairs; //[W/K] - m_T_htr = T_htr; - m_max_q_htr = max_q_htr; + m_T_htr = T_htr; + m_max_q_htr = max_q_htr; - m_V_prev = V_ini; - m_T_prev = T_ini; - m_m_prev = calc_mass_at_prev(); + m_V_prev = V_ini; + m_T_prev = T_ini; + m_m_prev = calc_mass_at_prev(); } double C_storage_tank::calc_mass_at_prev() { - return m_V_prev*mc_htf.dens(m_T_prev, 1.0); //[kg] + return m_V_prev * mc_htf.dens(m_T_prev, 1.0); //[kg] } double C_storage_tank::get_m_UA() @@ -418,22 +92,22 @@ double C_storage_tank::get_m_UA() double C_storage_tank::get_m_T_prev() { - return m_T_prev; //[K] + return m_T_prev; //[K] } double C_storage_tank::get_m_T_calc() { - return m_T_calc; + return m_T_calc; } double C_storage_tank::get_m_m_calc() //ARD new getter for current mass { - return m_m_calc; //[kg] + return m_m_calc; //[kg] } double C_storage_tank::get_vol_frac() { - return (m_V_prev - m_V_inactive) / m_V_active; + return (m_V_prev - m_V_inactive) / m_V_active; } double C_storage_tank::get_mass_avail() @@ -441,40 +115,45 @@ double C_storage_tank::get_mass_avail() return std::max(m_m_prev - m_mass_inactive, 0.0); //[kg] } +double C_storage_tank::get_fluid_vol() +{ + return m_V_calc; +} + double C_storage_tank::m_dot_available(double f_unavail, double timestep) { - //double rho = mc_htf.dens(m_T_prev, 1.0); //[kg/m^3] - //double V = m_m_prev / rho; //[m^3] Volume available in tank (one temperature) - //double V_avail = fmax(V - m_V_inactive, 0.0); //[m^3] Volume that is active - need to maintain minimum height (corresponding m_V_inactive) + //double rho = mc_htf.dens(m_T_prev, 1.0); //[kg/m^3] + //double V = m_m_prev / rho; //[m^3] Volume available in tank (one temperature) + //double V_avail = fmax(V - m_V_inactive, 0.0); //[m^3] Volume that is active - need to maintain minimum height (corresponding m_V_inactive) - double mass_avail = get_mass_avail(); //[kg] - double m_dot_avail = std::max(mass_avail - m_mass_active * f_unavail, 0.0) / timestep; //[kg/s] + double mass_avail = get_mass_avail(); //[kg] + double m_dot_avail = std::max(mass_avail - m_mass_active * f_unavail, 0.0) / timestep; //[kg/s] - // "Unavailable" fraction now applied to one temperature tank volume, not total tank volume - //double m_dot_avail = fmax(V_avail - m_V_active*f_unavail, 0.0)*rho / timestep; //[kg/s] Max mass flow rate available + // "Unavailable" fraction now applied to one temperature tank volume, not total tank volume + //double m_dot_avail = fmax(V_avail - m_V_active*f_unavail, 0.0)*rho / timestep; //[kg/s] Max mass flow rate available - return m_dot_avail; //[kg/s] + return m_dot_avail; //[kg/s] } void C_storage_tank::converged() { - // Reset 'previous' timestep values to 'calculated' values - m_V_prev = m_V_calc; //[m^3] - m_T_prev = m_T_calc; //[K] - m_m_prev = m_m_calc; //[kg] + // Reset 'previous' timestep values to 'calculated' values + m_V_prev = m_V_calc; //[m^3] + m_T_prev = m_T_calc; //[K] + m_m_prev = m_m_calc; //[kg] } void C_storage_tank::energy_balance(double timestep /*s*/, double m_dot_in /*kg/s*/, double m_dot_out /*kg/s*/, - double T_in /*K*/, double T_amb /*K*/, - double &T_ave /*K*/, double & q_heater /*MW*/, double & q_dot_loss /*MW*/) + double T_in /*K*/, double T_amb /*K*/, + double& T_ave /*K*/, double& q_heater /*MW*/, double& q_dot_loss /*MW*/) { - // Get properties from tank state at the end of last time step - double rho = mc_htf.dens(m_T_prev, 1.0); //[kg/m^3] - double cp = mc_htf.Cp(m_T_prev)*1000.0; //[J/kg-K] spec heat, convert from kJ/kg-K + // Get properties from tank state at the end of last time step + double rho = mc_htf.dens(m_T_prev, 1.0); //[kg/m^3] + double cp = mc_htf.Cp(m_T_prev) * 1000.0; //[J/kg-K] spec heat, convert from kJ/kg-K //double cp_in = mc_htf.Cp_ave(500+273.15, m_T_prev)*1000.0; - // Calculate ending volume levels - m_m_calc = m_m_prev + timestep*(m_dot_in - m_dot_out); //[kg] Available mass at the end of this timestep + // Calculate ending volume levels + m_m_calc = m_m_prev + timestep * (m_dot_in - m_dot_out); //[kg] Available mass at the end of this timestep double m_min, m_dot_out_adj; bool tank_is_empty = false; m_min = 0.001; //[kg] minimum tank mass for use in the calculations @@ -486,7 +165,7 @@ void C_storage_tank::energy_balance(double timestep /*s*/, double m_dot_in /*kg/ else { m_dot_out_adj = m_dot_out; } - m_V_calc = m_m_calc / rho; //[m^3] Available volume at end of timestep (using initial temperature...) + m_V_calc = m_m_calc / rho; //[m^3] Available volume at end of timestep (using initial temperature...) // Check for continual empty tank if (m_m_prev <= 1e-4 && tank_is_empty == true) { @@ -510,82 +189,82 @@ void C_storage_tank::energy_balance(double timestep /*s*/, double m_dot_in /*kg/ diff_m_dot = std::min(diff_m_dot, -1.E-5); } - if( diff_m_dot != 0.0 ) - { - double a_coef = m_dot_in*T_in + m_UA / cp*T_amb; - double b_coef = m_dot_in + m_UA / cp; + if (diff_m_dot != 0.0) + { + double a_coef = m_dot_in * T_in + m_UA / cp * T_amb; + double b_coef = m_dot_in + m_UA / cp; double c_coef = diff_m_dot; - m_T_calc = a_coef / b_coef + (m_T_prev - a_coef / b_coef)*pow( std::max( (timestep*c_coef / m_m_prev + 1), 0.0), -b_coef / c_coef); - T_ave = a_coef / b_coef + m_m_prev*(m_T_prev - a_coef / b_coef) / ((c_coef - b_coef)*timestep)*(pow( std::max( (timestep*c_coef / m_m_prev + 1.0), 0.0), 1.0 -b_coef/c_coef) - 1.0); - if (timestep < 1.e-6) - T_ave = a_coef / b_coef + (m_T_prev - a_coef / b_coef)*pow( std::max( (timestep*c_coef / m_m_prev + 1.0), 0.0), -b_coef / c_coef); // Limiting expression for small time step - q_dot_loss = m_UA*(T_ave - T_amb)/1.E6; //[MW] + m_T_calc = a_coef / b_coef + (m_T_prev - a_coef / b_coef) * pow(std::max((timestep * c_coef / m_m_prev + 1), 0.0), -b_coef / c_coef); + T_ave = a_coef / b_coef + m_m_prev * (m_T_prev - a_coef / b_coef) / ((c_coef - b_coef) * timestep) * (pow(std::max((timestep * c_coef / m_m_prev + 1.0), 0.0), 1.0 - b_coef / c_coef) - 1.0); + if (timestep < 1.e-6) + T_ave = a_coef / b_coef + (m_T_prev - a_coef / b_coef) * pow(std::max((timestep * c_coef / m_m_prev + 1.0), 0.0), -b_coef / c_coef); // Limiting expression for small time step + q_dot_loss = m_UA * (T_ave - T_amb) / 1.E6; //[MW] - if( m_T_calc < m_T_htr ) - { - q_heater = b_coef*((m_T_htr - m_T_prev*pow( std::max( (timestep*c_coef / m_m_prev + 1), 0.0), -b_coef / c_coef)) / - (-pow( std::max( (timestep*c_coef / m_m_prev + 1), 0.0), -b_coef / c_coef) + 1)) - a_coef; + if (m_T_calc < m_T_htr) + { + q_heater = b_coef * ((m_T_htr - m_T_prev * pow(std::max((timestep * c_coef / m_m_prev + 1), 0.0), -b_coef / c_coef)) / + (-pow(std::max((timestep * c_coef / m_m_prev + 1), 0.0), -b_coef / c_coef) + 1)) - a_coef; - q_heater = q_heater*cp; + q_heater = q_heater * cp; - q_heater /= 1.E6; - } - else - { - q_heater = 0.0; - return; - } + q_heater /= 1.E6; + } + else + { + q_heater = 0.0; + return; + } - if( q_heater > m_max_q_htr ) - { - q_heater = m_max_q_htr; - } + if (q_heater > m_max_q_htr) + { + q_heater = m_max_q_htr; + } - a_coef += q_heater*1.E6 / cp; + a_coef += q_heater * 1.E6 / cp; - m_T_calc = a_coef / b_coef + (m_T_prev - a_coef / b_coef)*pow( std::max( (timestep*c_coef / m_m_prev + 1), 0.0), -b_coef / c_coef); - T_ave = a_coef / b_coef + m_m_prev*(m_T_prev - a_coef / b_coef) / ((c_coef - b_coef)*timestep)*(pow(std::max( (timestep*c_coef / m_m_prev + 1.0), 0.0), 1.0 -b_coef/c_coef) - 1.0); - if (timestep < 1.e-6) - T_ave = a_coef / b_coef + (m_T_prev - a_coef / b_coef)*pow( std::max( (timestep*c_coef / m_m_prev + 1.0), 0.0), -b_coef / c_coef); // Limiting expression for small time step - q_dot_loss = m_UA*(T_ave - T_amb)/1.E6; //[MW] + m_T_calc = a_coef / b_coef + (m_T_prev - a_coef / b_coef) * pow(std::max((timestep * c_coef / m_m_prev + 1), 0.0), -b_coef / c_coef); + T_ave = a_coef / b_coef + m_m_prev * (m_T_prev - a_coef / b_coef) / ((c_coef - b_coef) * timestep) * (pow(std::max((timestep * c_coef / m_m_prev + 1.0), 0.0), 1.0 - b_coef / c_coef) - 1.0); + if (timestep < 1.e-6) + T_ave = a_coef / b_coef + (m_T_prev - a_coef / b_coef) * pow(std::max((timestep * c_coef / m_m_prev + 1.0), 0.0), -b_coef / c_coef); // Limiting expression for small time step + q_dot_loss = m_UA * (T_ave - T_amb) / 1.E6; //[MW] - } - else // No mass flow rate, tank is idle - { - double b_coef = m_UA / (cp*m_m_prev); - double c_coef = m_UA / (cp*m_m_prev) * T_amb; + } + else // No mass flow rate, tank is idle + { + double b_coef = m_UA / (cp * m_m_prev); + double c_coef = m_UA / (cp * m_m_prev) * T_amb; - m_T_calc = c_coef / b_coef + (m_T_prev - c_coef / b_coef)*exp(-b_coef*timestep); - T_ave = c_coef/b_coef - (m_T_prev - c_coef/b_coef)/(b_coef*timestep)*(exp(-b_coef*timestep)-1.0); - if (timestep < 1.e-6) - T_ave = c_coef / b_coef + (m_T_prev - c_coef / b_coef)*exp(-b_coef*timestep); // Limiting expression for small time step - q_dot_loss = m_UA*(T_ave - T_amb)/1.E6; + m_T_calc = c_coef / b_coef + (m_T_prev - c_coef / b_coef) * exp(-b_coef * timestep); + T_ave = c_coef / b_coef - (m_T_prev - c_coef / b_coef) / (b_coef * timestep) * (exp(-b_coef * timestep) - 1.0); + if (timestep < 1.e-6) + T_ave = c_coef / b_coef + (m_T_prev - c_coef / b_coef) * exp(-b_coef * timestep); // Limiting expression for small time step + q_dot_loss = m_UA * (T_ave - T_amb) / 1.E6; - if( m_T_calc < m_T_htr ) - { - q_heater = (b_coef*(m_T_htr - m_T_prev*exp(-b_coef*timestep)) / (-exp(-b_coef*timestep) + 1.0) - c_coef)*cp*m_m_prev; - q_heater /= 1.E6; //[MW] - } - else - { - q_heater = 0.0; - return; - } + if (m_T_calc < m_T_htr) + { + q_heater = (b_coef * (m_T_htr - m_T_prev * exp(-b_coef * timestep)) / (-exp(-b_coef * timestep) + 1.0) - c_coef) * cp * m_m_prev; + q_heater /= 1.E6; //[MW] + } + else + { + q_heater = 0.0; + return; + } - if( q_heater > m_max_q_htr ) - { - q_heater = m_max_q_htr; - } + if (q_heater > m_max_q_htr) + { + q_heater = m_max_q_htr; + } - c_coef += q_heater*1.E6 / (cp*m_m_prev); + c_coef += q_heater * 1.E6 / (cp * m_m_prev); - m_T_calc = c_coef / b_coef + (m_T_prev - c_coef / b_coef)*exp(-b_coef*timestep); - T_ave = c_coef / b_coef - (m_T_prev - c_coef / b_coef) / (b_coef*timestep)*(exp(-b_coef*timestep) - 1.0); - if (timestep < 1.e-6) - T_ave = c_coef / b_coef + (m_T_prev - c_coef / b_coef)*exp(-b_coef*timestep); // Limiting expression for small time step - q_dot_loss = m_UA*(T_ave - T_amb)/1.E6; //[MW] - } + m_T_calc = c_coef / b_coef + (m_T_prev - c_coef / b_coef) * exp(-b_coef * timestep); + T_ave = c_coef / b_coef - (m_T_prev - c_coef / b_coef) / (b_coef * timestep) * (exp(-b_coef * timestep) - 1.0); + if (timestep < 1.e-6) + T_ave = c_coef / b_coef + (m_T_prev - c_coef / b_coef) * exp(-b_coef * timestep); // Limiting expression for small time step + q_dot_loss = m_UA * (T_ave - T_amb) / 1.E6; //[MW] + } if (tank_is_empty) { // set to actual values @@ -595,1430 +274,1155 @@ void C_storage_tank::energy_balance(double timestep /*s*/, double m_dot_in /*kg/ } void C_storage_tank::energy_balance_constant_mass(double timestep /*s*/, double m_dot_in, double T_in /*K*/, double T_amb /*K*/, - double &T_ave /*K*/, double & q_heater /*MW*/, double & q_dot_loss /*MW*/) + double& T_ave /*K*/, double& q_heater /*MW*/, double& q_dot_loss /*MW*/) { - // Get properties from tank state at the end of last time step - double rho = mc_htf.dens(m_T_prev, 1.0); //[kg/m^3] - double cp = mc_htf.Cp(m_T_prev)*1000.0; //[J/kg-K] spec heat, convert from kJ/kg-K + // Get properties from tank state at the end of last time step + double rho = mc_htf.dens(m_T_prev, 1.0); //[kg/m^3] + double cp = mc_htf.Cp(m_T_prev) * 1000.0; //[J/kg-K] spec heat, convert from kJ/kg-K - // Calculate ending volume levels - m_m_calc = m_m_prev; //[kg] Available mass at the end of this timestep, same as previous - m_V_calc = m_m_calc / rho; //[m^3] Available volume at end of timestep (using initial temperature...) + // Calculate ending volume levels + m_m_calc = m_m_prev; //[kg] Available mass at the end of this timestep, same as previous + m_V_calc = m_m_calc / rho; //[m^3] Available volume at end of timestep (using initial temperature...) - //Analytical method - double a_coef = m_dot_in/m_m_calc + m_UA / (m_m_calc*cp); - double b_coef = m_dot_in / m_m_calc*T_in + m_UA / (m_m_calc*cp)*T_amb; + //Analytical method + double a_coef = m_dot_in / m_m_calc + m_UA / (m_m_calc * cp); + double b_coef = m_dot_in / m_m_calc * T_in + m_UA / (m_m_calc * cp) * T_amb; - m_T_calc = b_coef / a_coef - (b_coef/a_coef-m_T_prev)*exp(-a_coef*timestep); - T_ave = b_coef / a_coef - (b_coef / a_coef - m_T_prev)*exp(-a_coef*timestep/2); //estimate of average + m_T_calc = b_coef / a_coef - (b_coef / a_coef - m_T_prev) * exp(-a_coef * timestep); + T_ave = b_coef / a_coef - (b_coef / a_coef - m_T_prev) * exp(-a_coef * timestep / 2); //estimate of average - q_heater = 0.0; - return; + q_heater = 0.0; + return; } -static C_csp_reported_outputs::S_output_info S_output_info[] = -{ - {C_csp_two_tank_tes::E_Q_DOT_LOSS, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] TES thermal losses - {C_csp_two_tank_tes::E_W_DOT_HEATER, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWe] TES freeze protection power - {C_csp_two_tank_tes::E_TES_T_HOT, C_csp_reported_outputs::TS_LAST}, //[C] TES final hot tank temperature - {C_csp_two_tank_tes::E_TES_T_COLD, C_csp_reported_outputs::TS_LAST}, //[C] TES cold temperature at end of timestep - {C_csp_two_tank_tes::E_M_DOT_TANK_TO_TANK, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] TES thermal losses - {C_csp_two_tank_tes::E_MASS_COLD_TANK, C_csp_reported_outputs::TS_LAST}, //[kg] Mass in cold tank at end of timestep - {C_csp_two_tank_tes::E_MASS_HOT_TANK, C_csp_reported_outputs::TS_LAST}, //[kg] Mass in hot tank at end of timestep - {C_csp_two_tank_tes::E_HOT_TANK_HTF_PERC_FINAL, C_csp_reported_outputs::TS_LAST}, //[%] Final percent fill of available hot tank mass - {C_csp_two_tank_tes::E_W_DOT_HTF_PUMP, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWe] - - csp_info_invalid -}; - -C_csp_two_tank_tes::C_csp_two_tank_tes() +C_hx_cold_tes::C_hx_cold_tes() { - m_vol_tank = m_V_tank_active = m_q_pb_design = m_Q_tes_des = - m_V_tank_hot_ini = m_mass_total_active = m_d_tank = m_q_dot_loss_des = - m_cp_external_avg = m_rho_store_avg = m_m_dot_tes_des_over_m_dot_external_des = std::numeric_limits::quiet_NaN(); - - mc_reported_outputs.construct(S_output_info); + m_m_dot_des_ave = m_eff_des = m_UA_des = std::numeric_limits::quiet_NaN(); } -C_csp_two_tank_tes::C_csp_two_tank_tes( - int external_fl, // [-] external fluid identifier - util::matrix_t external_fl_props, // [-] external fluid properties - int tes_fl, // [-] tes fluid identifier - util::matrix_t tes_fl_props, // [-] tes fluid properties - double q_dot_design, // [MWt] Design heat rate in and out of tes - double frac_max_q_dot, // [-] the max design heat rate as a fraction of the nominal - double Q_tes_des, // [MWt-hr] design storage capacity - double h_tank, // [m] tank height - double u_tank, // [W/m^2-K] - int tank_pairs, // [-] - double hot_tank_Thtr, // [C] convert to K in init() - double hot_tank_max_heat, // [MW] - double cold_tank_Thtr, // [C] convert to K in init() - double cold_tank_max_heat, // [MW] - double dt_hot, // [C] Temperature difference across heat exchanger - assume hot and cold deltaTs are equal - double T_cold_des, // [C] convert to K in init() - double T_hot_des, // [C] convert to K in init() - double T_tank_hot_ini, // [C] Initial temperature in hot storage tank - double T_tank_cold_ini, // [C] Initial temperature in cold storage cold - double h_tank_min, // [m] Minimum allowable HTF height in storage tank - double f_V_hot_ini, // [%] Initial fraction of available volume that is hot - double htf_pump_coef, // [kW/kg/s] Pumping power to move 1 kg/s of HTF through sink - bool tanks_in_parallel, // [-] Whether the tanks are in series or parallel with the external system. Series means external htf must go through storage tanks. - double V_tes_des, // [m/s] Design-point velocity for sizing the diameters of the TES piping - bool calc_design_pipe_vals, // [-] Should the HTF state be calculated at design conditions - double tes_pump_coef, // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop - double eta_pump, // [-] Pump efficiency, for newer pumping calculations - bool has_hot_tank_bypass, // [-] True if the bypass valve causes the source htf to bypass just the hot tank and enter the cold tank before flowing back to the external system. - double T_tank_hot_inlet_min, // [C] Minimum source htf temperature that may enter the hot tank - bool custom_tes_p_loss, // [-] True if the TES piping losses should be calculated using the TES pipe lengths and minor loss coeffs, false if using the pumping loss parameters - bool custom_tes_pipe_sizes, // [-] True if the TES diameters and wall thicknesses parameters should be used instead of calculating them - util::matrix_t k_tes_loss_coeffs, // [-] Combined minor loss coefficients of the fittings and valves in the collection (including bypass) and generation loops in the TES - util::matrix_t tes_diams, // [m] Imported inner diameters for the TES piping as read from the modified output files - util::matrix_t tes_wallthicks, // [m] Imported wall thicknesses for the TES piping as read from the modified output files - util::matrix_t tes_lengths, // [m] Imported lengths for the TES piping as read from the modified output files - double pipe_rough, // [m] Pipe absolute roughness - double dP_discharge // [bar] Pressure drop on the TES discharge side (e.g., within the steam generator) - ) - : - m_external_fl(external_fl), m_external_fl_props(external_fl_props), m_tes_fl(tes_fl), m_tes_fl_props(tes_fl_props), - m_q_dot_design(q_dot_design), m_frac_max_q_dot(frac_max_q_dot), m_Q_tes_des(Q_tes_des), m_h_tank(h_tank), - m_u_tank(u_tank), m_tank_pairs(tank_pairs), m_hot_tank_Thtr(hot_tank_Thtr), m_hot_tank_max_heat(hot_tank_max_heat), - m_cold_tank_Thtr(cold_tank_Thtr), m_cold_tank_max_heat(cold_tank_max_heat), m_dt_hot(dt_hot), m_T_cold_des(T_cold_des), - m_T_hot_des(T_hot_des), m_T_tank_hot_ini(T_tank_hot_ini), m_T_tank_cold_ini(T_tank_cold_ini), - m_h_tank_min(h_tank_min), m_f_V_hot_ini(f_V_hot_ini), m_htf_pump_coef(htf_pump_coef), tanks_in_parallel(tanks_in_parallel), - V_tes_des(V_tes_des), calc_design_pipe_vals(calc_design_pipe_vals), m_tes_pump_coef(tes_pump_coef), - eta_pump(eta_pump), has_hot_tank_bypass(has_hot_tank_bypass), T_tank_hot_inlet_min(T_tank_hot_inlet_min), - custom_tes_p_loss(custom_tes_p_loss), custom_tes_pipe_sizes(custom_tes_pipe_sizes), k_tes_loss_coeffs(k_tes_loss_coeffs), - tes_diams(tes_diams), tes_wallthicks(tes_wallthicks), tes_lengths(tes_lengths), - pipe_rough(pipe_rough), dP_discharge(dP_discharge) +void C_hx_cold_tes::init(const HTFProperties& fluid_field, const HTFProperties& fluid_store, double q_transfer_des /*W*/, + double dt_des, double T_h_in_des /*K*/, double T_h_out_des /*K*/) { + // Counter flow heat exchanger - if (tes_lengths.ncells() < 11) { - double lengths[11] = { 0., 90., 100., 120., 0., 30., 90., 80., 80., 120., 80. }; - this->tes_lengths.assign(lengths, 11); + mc_field_htfProps = fluid_field; + mc_store_htfProps = fluid_store; + + // Design should provide field/pc side design temperatures + double c_h = mc_field_htfProps.Cp_ave(T_h_out_des, T_h_in_des) * 1000.0; //[J/kg-K] Spec heat of hot side fluid at hot side average temperature, convert from [kJ/kg-K] + double c_c = mc_store_htfProps.Cp_ave(T_h_out_des, T_h_in_des) * 1000.0; //[J/kg-K] Spec heat of cold side fluid at hot side average temperature (estimate, but should be close) + // HX inlet and outlet temperatures + double T_c_out = T_h_in_des - dt_des; + double T_c_in = T_h_out_des - dt_des; + // Mass flow rates + double m_dot_h = q_transfer_des / (c_h * (T_h_in_des - T_h_out_des)); //[kg/s] + double m_dot_c = q_transfer_des / (c_c * (T_c_out - T_c_in)); //[kg/s] + m_m_dot_des_ave = 0.5 * (m_dot_h + m_dot_c); //[kg/s] + // Capacitance rates + double c_dot_h = m_dot_h * c_h; //[W/K] + double c_dot_c = m_dot_c * c_c; //[W/K] + double c_dot_max = std::max(c_dot_h, c_dot_c); //[W/K] + double c_dot_min = std::min(c_dot_h, c_dot_c); //[W/K] + double cr = c_dot_min / c_dot_max; //[-] + // Maximum possible energy flow rate + double q_max = c_dot_min * (T_h_in_des - T_c_in); //[W] + // Effectiveness + m_eff_des = q_transfer_des / q_max; + + // Check for realistic conditions + if (cr > 1.0 || cr < 0.0) + { + throw(C_csp_exception("Heat exchanger design calculations failed", "")); } - m_vol_tank = m_V_tank_active = m_q_pb_design = m_ts_hours = - m_V_tank_hot_ini = m_mass_total_active = m_d_tank = m_q_dot_loss_des = - m_cp_external_avg = m_rho_store_avg = m_m_dot_tes_des_over_m_dot_external_des = std::numeric_limits::quiet_NaN(); + double NTU = std::numeric_limits::quiet_NaN(); + if (cr < 1.0) + NTU = log((1. - m_eff_des * cr) / (1. - m_eff_des)) / (1. - cr); + else + NTU = m_eff_des / (1. - m_eff_des); - mc_reported_outputs.construct(S_output_info); + m_UA_des = NTU * c_dot_min; //[W/K] + + m_T_hot_field_prev = m_T_hot_field_calc = T_h_in_des; + m_T_cold_field_prev = m_T_cold_field_calc = T_h_out_des; + m_m_dot_field_prev = m_m_dot_field_calc = m_dot_h; + m_T_hot_tes_prev = m_T_hot_tes_calc = T_c_out; + m_T_cold_tes_prev = m_T_cold_tes_calc = T_c_in; + m_m_dot_tes_prev = m_m_dot_tes_calc = m_dot_c; } -void C_csp_two_tank_tes::init(const C_csp_tes::S_csp_tes_init_inputs init_inputs) +void C_hx_cold_tes::hx_charge_mdot_tes(double T_cold_tes, double m_dot_tes, double T_hot_field, + double& eff, double& T_hot_tes, double& T_cold_field, double& q_trans, double& m_dot_field) { - if( !(m_Q_tes_des > 0.0) ) - { - m_is_tes = false; - return; // No storage! - } + hx_performance(false, true, T_hot_field, m_dot_tes, T_cold_tes, + eff, T_cold_field, T_hot_tes, q_trans, m_dot_field); +} - m_is_tes = true; +void C_hx_cold_tes::hx_discharge_mdot_tes(double T_hot_tes, double m_dot_tes, double T_cold_field, + double& eff, double& T_cold_tes, double& T_hot_field, double& q_trans, double& m_dot_field) +{ + hx_performance(true, true, T_hot_tes, m_dot_tes, T_cold_field, + eff, T_cold_tes, T_hot_field, q_trans, m_dot_field); +} - // Declare instance of fluid class for EXTERNAL fluid - // Set fluid number and copy over fluid matrix if it makes sense - if( m_external_fl != HTFProperties::User_defined && m_external_fl < HTFProperties::End_Library_Fluids ) - { - if( !mc_external_htfProps.SetFluid(m_external_fl) ) - { - throw(C_csp_exception("External HTF code is not recognized", "Two Tank TES Initialization")); - } - } - else if( m_external_fl == HTFProperties::User_defined ) - { - int n_rows = (int)m_external_fl_props.nrows(); - int n_cols = (int)m_external_fl_props.ncols(); - if( n_rows > 2 && n_cols == 7 ) - { - if( !mc_external_htfProps.SetUserDefinedFluid(m_external_fl_props) ) - { - error_msg = util::format(mc_external_htfProps.UserFluidErrMessage(), n_rows, n_cols); - throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); - } - } - else - { - error_msg = util::format("The user defined external HTF 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, "Two Tank TES Initialization")); - } - } - else - { - throw(C_csp_exception("External HTF code is not recognized", "Two Tank TES Initialization")); - } +void C_hx_cold_tes::hx_charge_mdot_field(double T_hot_field, double m_dot_field, double T_cold_tes, + double& eff, double& T_cold_field, double& T_hot_tes, double& q_trans, double& m_dot_tes) +{ + hx_performance(true, false, T_hot_field, m_dot_field, T_cold_tes, + eff, T_cold_field, T_hot_tes, q_trans, m_dot_tes); +} +void C_hx_cold_tes::hx_discharge_mdot_field(double T_cold_field, double m_dot_field, double T_hot_tes, + double& eff, double& T_hot_field, double& T_cold_tes, double& q_trans, double& m_dot_tes) +{ + hx_performance(false, false, T_hot_tes, m_dot_field, T_cold_field, + eff, T_cold_tes, T_hot_field, q_trans, m_dot_tes); +} - // Declare instance of fluid class for STORAGE fluid. - // Set fluid number and copy over fluid matrix if it makes sense. - if( m_tes_fl != HTFProperties::User_defined && m_tes_fl < HTFProperties::End_Library_Fluids ) - { - if( !mc_store_htfProps.SetFluid(m_tes_fl) ) - { - throw(C_csp_exception("Storage HTF code is not recognized", "Two Tank TES Initialization")); - } - } - else if( m_tes_fl == HTFProperties::User_defined ) - { - int n_rows = (int)m_tes_fl_props.nrows(); - int n_cols = (int)m_tes_fl_props.ncols(); - if( n_rows > 2 && n_cols == 7 ) - { - if( !mc_store_htfProps.SetUserDefinedFluid(m_tes_fl_props) ) - { - error_msg = util::format(mc_store_htfProps.UserFluidErrMessage(), n_rows, n_cols); - throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); - } - } - else - { - error_msg = util::format("The user defined storage HTF 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, "Two Tank TES Initialization")); - } - } - else - { - throw(C_csp_exception("Storage HTF code is not recognized", "Two Tank TES Initialization")); - } +void C_hx_cold_tes::hx_performance(bool is_hot_side_mdot, bool is_storage_side, double T_hot_in, double m_dot_known, double T_cold_in, + double& eff, double& T_hot_out, double& T_cold_out, double& q_trans, double& m_dot_solved) +{ + // UA fixed - bool is_hx_calc = true; + // Subroutine for storage heat exchanger performance + // Pass a flag to determine whether mass flow rate is cold side or hot side. Return mass flow rate will be the other + // Also pass a flag to determine whether storage or field side mass flow rate is known. Return mass flow rate will be the other + // Inputs: hot side mass flow rate [kg/s], hot side inlet temp [K], cold side inlet temp [K] + // Outputs: HX effectiveness [-], hot side outlet temp [K], cold side outlet temp [K], + // Heat transfer between fluids [MWt], cold side mass flow rate [kg/s] - if( m_tes_fl != m_external_fl ) - is_hx_calc = true; - else if( m_external_fl != HTFProperties::User_defined ) - is_hx_calc = false; - else - { - is_hx_calc = !mc_external_htfProps.equals(&mc_store_htfProps); - } + if (m_dot_known < 0) { + eff = T_hot_out = T_cold_out = q_trans = m_dot_solved = std::numeric_limits::quiet_NaN(); + throw(C_csp_exception("HX provided a negative mass flow", "")); + } + else if (m_dot_known == 0) { + eff = 0.; + T_hot_out = T_hot_in; + T_cold_out = T_cold_in; + q_trans = 0.; + m_dot_solved = 0.; + return; + } - m_is_hx = is_hx_calc; + double m_dot_hot, m_dot_cold, c_hot, c_cold, c_dot; + bool m_field_is_hot; // Is the field side the 'hot' side (e.g., during storage charging)? - // Added by TB 2023-03-03 - // Need to check if tes_pump_coef is defined for storage with hx - if (m_is_hx) + if (is_hot_side_mdot) // know hot side mass flow rate - assuming always know storage side and solving for field { - if(std::isnan(this->m_tes_pump_coef)) - throw(C_csp_exception("TES Pump Coef not provided for system with different field and storage fluids.", "Two Tank TES Initialization")); + if (is_storage_side) + { + m_field_is_hot = false; + c_cold = mc_field_htfProps.Cp_ave(T_cold_in, T_hot_in) * 1000.0; //[J/kg-K] + c_hot = mc_store_htfProps.Cp_ave(T_cold_in, T_hot_in) * 1000.0; //[J/kg-K] + } + else + { + m_field_is_hot = true; + c_hot = mc_field_htfProps.Cp_ave(T_cold_in, T_hot_in) * 1000.0; //[J/kg-K] + c_cold = mc_store_htfProps.Cp_ave(T_cold_in, T_hot_in) * 1000.0; //[J/kg-K] + } + + m_dot_hot = m_dot_known; + // Calculate flow capacitance of hot stream + double c_dot_hot = m_dot_hot * c_hot; //[W/K] + c_dot = c_dot_hot; + // Choose a cold stream mass flow rate that results in c_dot_h = c_dot_c + m_dot_cold = c_dot_hot / c_cold; + m_dot_solved = m_dot_cold; } + else + { + if (is_storage_side) + { + m_field_is_hot = true; + c_hot = mc_field_htfProps.Cp_ave(T_cold_in, T_hot_in) * 1000.0; //[J/kg-K] + c_cold = mc_store_htfProps.Cp_ave(T_cold_in, T_hot_in) * 1000.0; //[J/kg-K] + } + else + { + m_field_is_hot = false; + c_cold = mc_field_htfProps.Cp_ave(T_cold_in, T_hot_in) * 1000.0; //[J/kg-K] + c_hot = mc_store_htfProps.Cp_ave(T_cold_in, T_hot_in) * 1000.0; //[J/kg-K] + } - /* - if( m_is_hx != is_hx_calc ) - { - if( is_hx_calc ) - mc_csp_messages.add_message(C_csp_messages::NOTICE, "Input external and storage fluids are different, but the inputs did not specify an external-to-storage heat exchanger. The system was modeled assuming a heat exchanger."); - else - mc_csp_messages.add_message(C_csp_messages::NOTICE, "Input external and storage fluids are identical, but the inputs specified an external-to-storage heat exchanger. The system was modeled assuming no heat exchanger."); + m_dot_cold = m_dot_known; + // Calculate flow capacitance of cold stream + double c_dot_cold = m_dot_cold * c_cold; + c_dot = c_dot_cold; + // Choose a cold stream mass flow rate that results in c_dot_c = c_dot_h + m_dot_hot = c_dot_cold / c_hot; + m_dot_solved = m_dot_hot; + } - m_is_hx = is_hx_calc; - }*/ + // Scale UA + double m_dot_od = 0.5 * (m_dot_cold + m_dot_hot); + double UA = m_UA_des * pow(m_dot_od / m_m_dot_des_ave, 0.8); - if (m_is_hx && !tanks_in_parallel) + // Calculate effectiveness + double NTU = UA / c_dot; + eff = NTU / (1.0 + NTU); + + if (std::isnan(eff) || eff <= 0.0 || eff > 1.0) { - mc_csp_messages.add_message(C_csp_messages::NOTICE, "The inputs specified serial TES operation, but the external and storage fluids are different." - " The simulation modeled parallel TES operation.\n"); - tanks_in_parallel = true; + eff = T_hot_out = T_cold_out = q_trans = m_dot_solved = + m_T_hot_field_prev = m_T_cold_field_prev = m_T_hot_tes_prev = m_T_cold_tes_prev = + m_m_dot_field_prev = m_m_dot_tes_prev = std::numeric_limits::quiet_NaN(); + throw(C_csp_exception("Off design heat exchanger failed", "")); } - if (tanks_in_parallel) { - m_is_cr_to_cold_tank_allowed = false; + // Calculate heat transfer in HX + double q_dot_max = c_dot * (T_hot_in - T_cold_in); + q_trans = eff * q_dot_max; + + T_hot_out = T_hot_in - q_trans / c_dot; + T_cold_out = T_cold_in + q_trans / c_dot; + + q_trans *= 1.E-6; //[MWt] + + // Set member variables + if (m_field_is_hot == true) { + m_T_hot_field_prev = T_hot_in; + m_T_cold_field_prev = T_hot_out; + m_T_hot_tes_prev = T_cold_out; + m_T_cold_tes_prev = T_cold_in; } else { - m_is_cr_to_cold_tank_allowed = true; + m_T_hot_field_prev = T_cold_out; + m_T_cold_field_prev = T_cold_in; + m_T_hot_tes_prev = T_hot_in; + m_T_cold_tes_prev = T_hot_out; } - // Calculate thermal power to PC at design - m_q_pb_design = m_q_dot_design * 1.E6; //[Wt] + if (is_storage_side) { + m_m_dot_field_prev = m_dot_solved; + m_m_dot_tes_prev = m_dot_known; + } + else { + m_m_dot_field_prev = m_dot_known; + m_m_dot_tes_prev = m_dot_solved; + } +} - // Convert parameter units - m_hot_tank_Thtr += 273.15; //[K] convert from C - m_cold_tank_Thtr += 273.15; //[K] convert from C - m_T_cold_des += 273.15; //[K] convert from C - m_T_hot_des += 273.15; //[K] convert from C - m_T_tank_hot_ini += 273.15; //[K] convert from C - m_T_tank_cold_ini += 273.15; //[K] convert from C +C_csp_cold_tes::C_csp_cold_tes() +{ + m_vol_tank = m_V_tank_active = m_q_pb_design = m_V_tank_hot_ini = std::numeric_limits::quiet_NaN(); - m_ts_hours = m_Q_tes_des / m_q_dot_design; + m_m_dot_tes_dc_max = m_m_dot_tes_ch_max = std::numeric_limits::quiet_NaN(); +} - double d_tank_temp = std::numeric_limits::quiet_NaN(); - double q_dot_loss_temp = std::numeric_limits::quiet_NaN(); - double T_tes_hot_des, T_tes_cold_des; - if (m_is_hx) { - T_tes_hot_des = m_T_hot_des - m_dt_hot; - T_tes_cold_des = m_T_cold_des + m_dt_hot; +void C_csp_cold_tes::init(const C_csp_cold_tes::S_csp_cold_tes_init_inputs init_inputs) +{ + if (!(ms_params.m_ts_hours > 0.0)) + { + m_is_tes = false; + return; // No storage! } - else { - T_tes_hot_des = m_T_hot_des; - T_tes_cold_des = m_T_cold_des; + + m_is_tes = true; + + // Declare instance of fluid class for FIELD fluid + // Set fluid number and copy over fluid matrix if it makes sense + if (ms_params.m_field_fl != HTFProperties::User_defined && ms_params.m_field_fl < HTFProperties::End_Library_Fluids) + { + if (!mc_field_htfProps.SetFluid(ms_params.m_field_fl)) + { + throw(C_csp_exception("Field HTF code is not recognized", "Two Tank TES Initialization")); + } + } + else if (ms_params.m_field_fl == HTFProperties::User_defined) + { + int n_rows = (int)ms_params.m_field_fl_props.nrows(); + int n_cols = (int)ms_params.m_field_fl_props.ncols(); + if (n_rows > 2 && n_cols == 7) + { + if (!mc_field_htfProps.SetUserDefinedFluid(ms_params.m_field_fl_props)) + { + error_msg = util::format(mc_field_htfProps.UserFluidErrMessage(), n_rows, n_cols); + throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); + } + } + else + { + error_msg = util::format("The user defined field HTF 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, "Two Tank TES Initialization")); + } + } + else + { + throw(C_csp_exception("Field HTF code is not recognized", "Two Tank TES Initialization")); } - two_tank_tes_sizing(mc_store_htfProps, m_Q_tes_des, T_tes_hot_des, T_tes_cold_des, - m_h_tank_min, m_h_tank, m_tank_pairs, m_u_tank, - m_V_tank_active, m_vol_tank, m_d_tank, m_q_dot_loss_des); - // 5.13.15, twn: also be sure that hx is sized such that it can supply full load to sink - double duty = m_q_pb_design * std::max(1.0, m_frac_max_q_dot); //[W] Allow all energy from the source to go into storage at any time - if( m_ts_hours > 0.0 ) - { - mc_hx.init(mc_external_htfProps, mc_store_htfProps, duty, m_dt_hot, m_T_hot_des, m_T_cold_des); - } + // Declare instance of fluid class for STORAGE fluid. + // Set fluid number and copy over fluid matrix if it makes sense. + if (ms_params.m_tes_fl != HTFProperties::User_defined && ms_params.m_tes_fl < HTFProperties::End_Library_Fluids) + { + if (!mc_store_htfProps.SetFluid(ms_params.m_tes_fl)) + { + throw(C_csp_exception("Storage HTF code is not recognized", "Two Tank TES Initialization")); + } + } + else if (ms_params.m_tes_fl == HTFProperties::User_defined) + { + int n_rows = (int)ms_params.m_tes_fl_props.nrows(); + int n_cols = (int)ms_params.m_tes_fl_props.ncols(); + if (n_rows > 2 && n_cols == 7) + { + if (!mc_store_htfProps.SetUserDefinedFluid(ms_params.m_tes_fl_props)) + { + error_msg = util::format(mc_store_htfProps.UserFluidErrMessage(), n_rows, n_cols); + throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); + } + } + else + { + error_msg = util::format("The user defined storage HTF 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, "Two Tank TES Initialization")); + } + } + else + { + throw(C_csp_exception("Storage HTF code is not recognized", "Two Tank TES Initialization")); + } - // Do we need to define minimum and maximum thermal powers to/from storage? - // The 'duty' definition should allow the tanks to accept whatever the source and/or sink can provide... + bool is_hx_calc = true; - // Calculate initial storage values + if (ms_params.m_tes_fl != ms_params.m_field_fl) + is_hx_calc = true; + else if (ms_params.m_field_fl != HTFProperties::User_defined) + is_hx_calc = false; + else + { + is_hx_calc = !mc_field_htfProps.equals(&mc_store_htfProps); + } - // Initial storage charge based on % mass - double T_tes_ave = 0.5*(T_tes_hot_des + T_tes_cold_des); - double cp_ave = mc_store_htfProps.Cp_ave(T_tes_cold_des, T_tes_hot_des); //[kJ/kg-K] Specific heat at average temperature - m_rho_store_avg = mc_store_htfProps.dens(T_tes_ave, 1.0); - m_mass_total_active = m_Q_tes_des*3600.0 / (cp_ave / 1000.0 * (T_tes_hot_des - T_tes_cold_des)); //[kg] Total HTF mass at design point inlet/outlet T - double V_inactive = m_vol_tank - m_V_tank_active; + if (ms_params.m_is_hx != is_hx_calc) + { + if (is_hx_calc) + mc_csp_messages.add_message(C_csp_messages::NOTICE, "Input field and storage fluids are different, but the inputs did not specify a field-to-storage heat exchanger. The system was modeled assuming a heat exchanger."); + else + mc_csp_messages.add_message(C_csp_messages::NOTICE, "Input field and storage fluids are identical, but the inputs specified a field-to-storage heat exchanger. The system was modeled assuming no heat exchanger."); - double rho_hot_des = mc_store_htfProps.dens(T_tes_hot_des, 1.0); - double rho_cold_des = mc_store_htfProps.dens(T_tes_cold_des, 1.0); - double rho_hot = mc_store_htfProps.dens(m_T_tank_hot_ini, 1.0); - double rho_cold = mc_store_htfProps.dens(m_T_tank_cold_ini, 1.0); - double m_hot_ini = m_f_V_hot_ini * 0.01 * m_mass_total_active + V_inactive * rho_hot_des; // Updating intiial storage charge calculation to avoid variation in total mass with specified initial T - double m_cold_ini = (1.0 - m_f_V_hot_ini * 0.01) * m_mass_total_active + V_inactive * rho_cold_des; - double V_hot_ini = m_hot_ini / rho_hot; - double V_cold_ini = m_cold_ini / rho_cold; + ms_params.m_is_hx = is_hx_calc; + } - //double rho_hot = mc_store_htfProps.dens(m_T_tank_hot_ini, 1.0); - //double rho_cold = mc_store_htfProps.dens(m_T_tank_cold_ini, 1.0); + // Calculate thermal power to PC at design + m_q_pb_design = ms_params.m_W_dot_pc_design / ms_params.m_eta_pc_factor * 1.E6; //[Wt] - using pc efficiency factor for cold storage ARD - //double V_inactive = m_vol_tank - m_V_tank_active; - //double V_hot_ini = m_f_V_hot_ini*0.01*m_mass_total_active / rho_hot + V_inactive; //[m^3] - //double V_cold_ini = (1.0 - m_f_V_hot_ini*0.01)*m_mass_total_active / rho_cold + V_inactive; //[m^3] + // Convert parameter units + ms_params.m_hot_tank_Thtr += 273.15; //[K] convert from C + ms_params.m_cold_tank_Thtr += 273.15; //[K] convert from C + ms_params.m_T_cold_des += 273.15; //[K] convert from C + ms_params.m_T_hot_des += 273.15; //[K] convert from C + ms_params.m_T_tank_hot_ini += 273.15; //[K] convert from C + ms_params.m_T_tank_cold_ini += 273.15; //[K] convert from C - // Initial storage charge based on % volume - //double V_inactive = m_vol_tank - m_V_tank_active; - //double V_hot_ini = m_f_V_hot_ini*0.01*m_V_tank_active + V_inactive; //[m^3] - //double V_cold_ini = (1.0 - m_f_V_hot_ini*0.01)*m_V_tank_active + V_inactive; //[m^3] - double T_hot_ini = m_T_tank_hot_ini; //[K] - double T_cold_ini = m_T_tank_cold_ini; //[K] + double Q_tes_des = m_q_pb_design / 1.E6 * ms_params.m_ts_hours; //[MWt-hr] TES thermal capacity at design - // Initialize cold and hot tanks - // Hot tank - mc_hot_tank.init(mc_store_htfProps, m_vol_tank, m_h_tank, m_h_tank_min, - m_u_tank, m_tank_pairs, m_hot_tank_Thtr, m_hot_tank_max_heat, - V_hot_ini, T_hot_ini, T_tes_hot_des); - // Cold tank - mc_cold_tank.init(mc_store_htfProps, m_vol_tank, m_h_tank, m_h_tank_min, - m_u_tank, m_tank_pairs, m_cold_tank_Thtr, m_cold_tank_max_heat, - V_cold_ini, T_cold_ini, T_tes_cold_des); + double d_tank_temp = std::numeric_limits::quiet_NaN(); + double q_dot_loss_temp = std::numeric_limits::quiet_NaN(); + two_tank_tes_sizing(mc_store_htfProps, Q_tes_des, ms_params.m_T_hot_des, ms_params.m_T_cold_des, + ms_params.m_h_tank_min, ms_params.m_h_tank, ms_params.m_tank_pairs, ms_params.m_u_tank, + m_V_tank_active, m_vol_tank, d_tank_temp, q_dot_loss_temp); - if (custom_tes_pipe_sizes && - (tes_diams.ncells() != N_tes_pipe_sections || - tes_wallthicks.ncells() != N_tes_pipe_sections)) { - error_msg = "The number of custom TES pipe sections is not correct."; - throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); - } - double rho_avg = mc_external_htfProps.dens((m_T_cold_des + m_T_hot_des) / 2, 9 / 1.e-5); - m_cp_external_avg = mc_external_htfProps.Cp_ave(m_T_cold_des, m_T_hot_des); - double cp_tes_avg = mc_store_htfProps.Cp_ave(T_tes_hot_des, T_tes_cold_des); - m_m_dot_tes_des_over_m_dot_external_des = m_cp_external_avg / cp_tes_avg; //[-] assume hx cr = 1 - double m_dot_pb_design = m_q_dot_design * 1.e3 / // convert MWe to kWe for cp [kJ/kg-K] - (m_cp_external_avg * (m_T_hot_des - m_T_cold_des)); - if (size_tes_piping(V_tes_des, tes_lengths, rho_avg, - m_dot_pb_design, m_frac_max_q_dot, tanks_in_parallel, // Inputs - this->pipe_vol_tot, this->pipe_v_dot_rel, this->pipe_diams, - this->pipe_wall_thk, this->pipe_m_dot_des, this->pipe_vel_des, // Outputs - custom_tes_pipe_sizes)) { + // 5.13.15, twn: also be sure that hx is sized such that it can supply full load to power cycle, in cases of low solar multiples + double duty = m_q_pb_design * std::max(1.0, ms_params.m_solarm); //[W] Allow all energy from the field to go into storage at any time - error_msg = "TES piping sizing failed"; - throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); + if (ms_params.m_ts_hours > 0.0) + { + mc_hx.init(mc_field_htfProps, mc_store_htfProps, duty, ms_params.m_dt_hot, ms_params.m_T_hot_des, ms_params.m_T_cold_des); } - this->pipe_lengths = tes_lengths; - if (calc_design_pipe_vals) { - if (size_tes_piping_TandP(mc_external_htfProps, init_inputs.T_to_cr_at_des, init_inputs.T_from_cr_at_des, - init_inputs.P_to_cr_at_des * 1.e5, dP_discharge * 1.e5, // bar to Pa - tes_lengths, k_tes_loss_coeffs, pipe_rough, tanks_in_parallel, - this->pipe_diams, this->pipe_vel_des, - this->pipe_T_des, this->pipe_P_des, - this->P_in_des)) { // Outputs + // Do we need to define minimum and maximum thermal powers to/from storage? + // The 'duty' definition should allow the tanks to accept whatever the field and/or power cycle can provide... - error_msg = "TES piping design temperature and pressure calculation failed"; - throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); - } - // Adjust first two pressures after source pumps, because the source inlet pressure used above was - // not yet corrected for the section in the TES/PB before the hot tank - double DP_before_hot_tank = this->pipe_P_des.at(3); // first section before hot tank - this->pipe_P_des.at(1) += DP_before_hot_tank; - this->pipe_P_des.at(2) += DP_before_hot_tank; + // Calculate initial storage values + double V_inactive = m_vol_tank - m_V_tank_active; + double V_hot_ini = ms_params.m_f_V_hot_ini * 0.01 * m_V_tank_active + V_inactive; //[m^3] + double V_cold_ini = (1.0 - ms_params.m_f_V_hot_ini * 0.01) * m_V_tank_active + V_inactive; //[m^3] + + double T_hot_ini = ms_params.m_T_tank_hot_ini; //[K] + double T_cold_ini = ms_params.m_T_tank_cold_ini; //[K] + + // Initialize cold and hot tanks + // Hot tank + mc_hot_tank.init(mc_store_htfProps, m_vol_tank, ms_params.m_h_tank, ms_params.m_h_tank_min, + ms_params.m_u_tank, ms_params.m_tank_pairs, ms_params.m_hot_tank_Thtr, ms_params.m_hot_tank_max_heat, + V_hot_ini, T_hot_ini, ms_params.m_T_hot_des); + // Cold tank + mc_cold_tank.init(mc_store_htfProps, m_vol_tank, ms_params.m_h_tank, ms_params.m_h_tank_min, + ms_params.m_u_tank, ms_params.m_tank_pairs, ms_params.m_cold_tank_Thtr, ms_params.m_cold_tank_max_heat, + V_cold_ini, T_cold_ini, ms_params.m_T_cold_des); - //value(O_p_des_sgs_1, DP_before_hot_tank); // for adjusting source design pressures - } } -void C_csp_two_tank_tes::get_design_parameters(double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, double& d_tank /*m*/, - double& q_dot_loss_des /*MWt*/, double& dens_store_htf_at_T_ave /*kg/m3*/, double& Q_tes /*MWt-hr*/) +bool C_csp_cold_tes::does_tes_exist() { - vol_one_temp_avail = m_V_tank_active; //[m3] - vol_one_temp_total = m_vol_tank; //[m3] - d_tank = m_d_tank; //[m] - q_dot_loss_des = m_q_dot_loss_des; //[MWt] - dens_store_htf_at_T_ave = m_rho_store_avg; //[kg/m3] - Q_tes = m_Q_tes_des; //[MWt-hr] + return m_is_tes; } -bool C_csp_two_tank_tes::does_tes_exist() +double C_csp_cold_tes::get_hot_temp() { - return m_is_tes; + return mc_hot_tank.get_m_T_prev(); //[K] } -bool C_csp_two_tank_tes::is_cr_to_cold_allowed() +double C_csp_cold_tes::get_cold_temp() { - return m_is_cr_to_cold_tank_allowed; + return mc_cold_tank.get_m_T_prev(); //[K] } -double C_csp_two_tank_tes::get_hot_temp() + +double C_csp_cold_tes::get_hot_mass() { - return mc_hot_tank.get_m_T_prev(); //[K] + return mc_hot_tank.get_m_m_calc(); // [kg] } -double C_csp_two_tank_tes::get_cold_temp() +double C_csp_cold_tes::get_cold_mass() { - return mc_cold_tank.get_m_T_prev(); //[K] + return mc_cold_tank.get_m_m_calc(); //[kg] } -double C_csp_two_tank_tes::get_hot_tank_vol_frac() +double C_csp_cold_tes::get_hot_mass_prev() { - return mc_hot_tank.get_vol_frac(); + return mc_hot_tank.calc_mass_at_prev(); // [kg] +} + +double C_csp_cold_tes::get_cold_mass_prev() +{ + return mc_cold_tank.calc_mass_at_prev(); //[kg] } +double C_csp_cold_tes::get_physical_volume() +{ + return m_vol_tank; //[m^3] +} -double C_csp_two_tank_tes::get_initial_charge_energy() +double C_csp_cold_tes::get_hot_massflow_avail(double step_s) //[kg/sec] +{ + return mc_hot_tank.m_dot_available(0, step_s); +} + +double C_csp_cold_tes::get_cold_massflow_avail(double step_s) //[kg/sec] +{ + return mc_cold_tank.m_dot_available(0, step_s); +} + + +double C_csp_cold_tes::get_initial_charge_energy() { //MWh if (std::isnan(m_V_tank_hot_ini)) { - return m_q_pb_design * m_ts_hours * (m_f_V_hot_ini / 100.0) * 1.e-6; + return m_q_pb_design * ms_params.m_ts_hours * (ms_params.m_f_V_hot_ini / 100.0) * 1.e-6; } else { - //TODO: m_V_tank_hot_ini does not get initialized to user value... - return m_q_pb_design * m_ts_hours * m_V_tank_hot_ini / m_vol_tank * 1.e-6; + return m_q_pb_design * ms_params.m_ts_hours * m_V_tank_hot_ini / m_vol_tank * 1.e-6; } } -double C_csp_two_tank_tes::get_min_charge_energy() +double C_csp_cold_tes::get_min_charge_energy() { //MWh - return 0.; //m_q_pb_design * m_ts_hours * m_h_tank_min / m_h_tank*1.e-6; + return 0.; //ms_params.m_q_pb_design * ms_params.m_ts_hours * ms_params.m_h_tank_min / ms_params.m_h_tank*1.e-6; } -double C_csp_two_tank_tes::get_max_charge_energy() +double C_csp_cold_tes::get_max_charge_energy() { //MWh - //double cp = mc_store_htfProps.Cp(m_T_hot_des); //[kJ/kg-K] spec heat at average temperature during discharge from hot to cold - // double rho = mc_store_htfProps.dens(m_T_hot_des, 1.); - - // double fadj = (1. - m_h_tank_min / m_h_tank); - - // double vol_avail = m_vol_tank * m_tank_pairs * fadj; - - // double e_max = vol_avail * rho * cp * (m_T_hot_des - m_T_cold_des) / 3.6e6; //MW-hr - - // return e_max; - return m_q_pb_design * m_ts_hours / 1.e6; + return m_q_pb_design * ms_params.m_ts_hours / 1.e6; } -double C_csp_two_tank_tes::get_degradation_rate() +double C_csp_cold_tes::get_degradation_rate() { //calculates an approximate "average" tank heat loss rate based on some assumptions. Good for simple optimization performance projections. - double d_tank = sqrt( m_vol_tank / ( (double)m_tank_pairs * m_h_tank * 3.14159) ); - double e_loss = m_u_tank * 3.14159 * m_tank_pairs * d_tank * ( m_T_cold_des + m_T_hot_des - 576.3 )*1.e-6; //MJ/s -- assumes full area for loss, Tamb = 15C - return e_loss / (m_q_pb_design * m_ts_hours * 3600.); //s^-1 -- fraction of heat loss per second based on full charge + double d_tank = sqrt(m_vol_tank / ((double)ms_params.m_tank_pairs * ms_params.m_h_tank * 3.14159)); + double e_loss = ms_params.m_u_tank * 3.14159 * ms_params.m_tank_pairs * d_tank * (ms_params.m_T_cold_des + ms_params.m_T_hot_des - 576.3) * 1.e-6; //MJ/s -- assumes full area for loss, Tamb = 15C + return e_loss / (m_q_pb_design * ms_params.m_ts_hours * 3600.); //s^-1 -- fraction of heat loss per second based on full charge } -void C_csp_two_tank_tes::reset_storage_to_initial_state() +void C_csp_cold_tes::reset_storage_to_initial_state() {} + +void C_csp_cold_tes::discharge_avail_est(double T_cold_K, double step_s, double& q_dot_dc_est, double& m_dot_field_est, double& T_hot_field_est) { - // Initial storage charge based on % mass - double Q_tes_des = m_q_pb_design / 1.E6 * m_ts_hours; //[MWt-hr] TES thermal capacity at design - double cp_ave = mc_store_htfProps.Cp_ave(m_T_cold_des, m_T_hot_des); //[kJ/kg-K] Specific heat at average temperature - double mtot = Q_tes_des*3600.0 / (cp_ave / 1000.0 * (m_T_hot_des - m_T_cold_des)); //[kg] Total HTF mass - double rho_hot = mc_store_htfProps.dens(m_T_hot_des, 1.0); - double rho_cold = mc_store_htfProps.dens(m_T_cold_des, 1.0); + double f_storage = 0.0; // for now, hardcode such that storage always completely discharges - double V_inactive = m_vol_tank - m_V_tank_active; - double V_hot_ini = m_f_V_hot_ini*0.01*mtot / rho_hot + V_inactive; //[m^3] - double V_cold_ini = (1.0 - m_f_V_hot_ini*0.01)*mtot / rho_cold + V_inactive; //[m^3] + double m_dot_tank_disch_avail = mc_hot_tank.m_dot_available(f_storage, step_s); //[kg/s] - double T_hot_ini = m_T_tank_hot_ini; //[K] - double T_cold_ini = m_T_tank_cold_ini; //[K] + double T_hot_ini = mc_hot_tank.get_m_T_prev(); //[K] - // Initialize cold and hot tanks - // Hot tank - mc_hot_tank.init(mc_store_htfProps, m_vol_tank, m_h_tank, m_h_tank_min, - m_u_tank, m_tank_pairs, m_hot_tank_Thtr, m_hot_tank_max_heat, - V_hot_ini, T_hot_ini, m_T_hot_des); - // Cold tank - mc_cold_tank.init(mc_store_htfProps, m_vol_tank, m_h_tank, m_h_tank_min, - m_u_tank, m_tank_pairs, m_cold_tank_Thtr, m_cold_tank_max_heat, - V_cold_ini, T_cold_ini, m_T_cold_des); -} + if (ms_params.m_is_hx) + { + double eff, T_cold_tes; + eff = T_cold_tes = std::numeric_limits::quiet_NaN(); + mc_hx.hx_discharge_mdot_tes(T_hot_ini, m_dot_tank_disch_avail, T_cold_K, eff, T_cold_tes, T_hot_field_est, q_dot_dc_est, m_dot_field_est); -double C_csp_two_tank_tes::get_tes_m_dot(double m_dot_external /*kg/s*/) -{ - return m_dot_external * m_m_dot_tes_des_over_m_dot_external_des; -} + // If above method fails, it will throw an exception, so if we don't want to break here, need to catch and handle it + } + else + { + double cp_T_avg = mc_store_htfProps.Cp_ave(T_cold_K, T_hot_ini); //[kJ/kg-K] spec heat at average temperature during discharge from hot to cold + q_dot_dc_est = m_dot_tank_disch_avail * cp_T_avg * (T_hot_ini - T_cold_K) * 1.E-3; //[MW] + m_dot_field_est = m_dot_tank_disch_avail; + T_hot_field_est = T_hot_ini; + } -double C_csp_two_tank_tes::get_external_m_dot(double m_dot_tes /*kg/s*/) -{ - return m_dot_tes / m_m_dot_tes_des_over_m_dot_external_des; + m_m_dot_tes_dc_max = m_dot_tank_disch_avail * step_s; //[kg/s] } -void C_csp_two_tank_tes::discharge_avail_est(double T_cold_K, double step_s, - double &q_dot_dc_est /*MWt*/, double &m_dot_external_est /*kg/s*/, double &T_hot_external_est /*K*/) +void C_csp_cold_tes::charge_avail_est(double T_hot_K, double step_s, double& q_dot_ch_est, double& m_dot_field_est, double& T_cold_field_est) { - double f_storage = 0.0; // for now, hardcode such that storage always completely discharges + double f_ch_storage = 0.0; // for now, hardcode such that storage always completely charges - double m_dot_tank_disch_avail = mc_hot_tank.m_dot_available(f_storage, step_s); //[kg/s] + double m_dot_tank_charge_avail = mc_cold_tank.m_dot_available(f_ch_storage, step_s); //[kg/s] - if (m_dot_tank_disch_avail == 0) { - q_dot_dc_est = 0.; - m_dot_external_est = 0.; - T_hot_external_est = std::numeric_limits::quiet_NaN(); - return; + double T_cold_ini = mc_cold_tank.get_m_T_prev(); //[K] + + if (ms_params.m_is_hx) + { + double eff, T_hot_tes; + eff = T_hot_tes = std::numeric_limits::quiet_NaN(); + mc_hx.hx_charge_mdot_tes(T_cold_ini, m_dot_tank_charge_avail, T_hot_K, eff, T_hot_tes, T_cold_field_est, q_dot_ch_est, m_dot_field_est); + + // If above method fails, it will throw an exception, so if we don't want to break here, need to catch and handle it + } + else + { + double cp_T_avg = mc_store_htfProps.Cp_ave(T_cold_ini, T_hot_K); //[kJ/kg-K] spec heat at average temperature during charging from cold to hot + q_dot_ch_est = m_dot_tank_charge_avail * cp_T_avg * (T_hot_K - T_cold_ini) * 1.E-3; //[MW] + m_dot_field_est = m_dot_tank_charge_avail; + T_cold_field_est = T_cold_ini; } - double T_hot_ini = mc_hot_tank.get_m_T_prev(); //[K] + m_m_dot_tes_ch_max = m_dot_tank_charge_avail * step_s; //[kg/s] +} - if(m_is_hx) - { - m_dot_external_est = get_external_m_dot(m_dot_tank_disch_avail); //[kg/s] +void C_csp_cold_tes::discharge_full(double timestep /*s*/, double T_amb /*K*/, double T_htf_cold_in /*K*/, + double& T_htf_hot_out /*K*/, double& m_dot_htf_out /*kg/s*/, S_csp_cold_tes_outputs& outputs) +{ + // This method calculates the hot discharge temperature on the HX side (if applicable) during FULL DISCHARGE. If no heat exchanger (direct storage), + // the discharge temperature is equal to the average (timestep) hot tank outlet temperature - double T_cold_tes, eff; - T_cold_tes = eff = std::numeric_limits::quiet_NaN(); - mc_hx.solve(T_cold_K, m_dot_external_est, - T_hot_ini, m_dot_tank_disch_avail, - T_hot_external_est, T_cold_tes, eff, q_dot_dc_est); + // Inputs are: + // 2) inlet temperature on the HX side (if applicable). If no heat exchanger, the inlet temperature is the temperature + // of HTF directly entering the cold tank. - // If above method fails, it will throw an exception, so if we don't want to break here, need to catch and handle it - } - else - { - double cp_T_avg = mc_store_htfProps.Cp_ave(T_cold_K, T_hot_ini); //[kJ/kg-K] spec heat at average temperature during discharge from hot to cold - q_dot_dc_est = m_dot_tank_disch_avail * cp_T_avg * (T_hot_ini - T_cold_K)*1.E-3; //[MW] - m_dot_external_est = m_dot_tank_disch_avail; - T_hot_external_est = T_hot_ini; - } -} + double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, T_cold_ave; + q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_cold_ave = std::numeric_limits::quiet_NaN(); -void C_csp_two_tank_tes::charge_avail_est(double T_hot_K, double step_s, - double &q_dot_ch_est /*MWt*/, double &m_dot_external_est /*kg/s*/, double &T_cold_external_est /*K*/) -{ - double f_ch_storage = 0.0; // for now, hardcode such that storage always completely charges + // If no heat exchanger, no iteration is required between the heat exchanger and storage tank models + if (!ms_params.m_is_hx) + { + m_dot_htf_out = m_m_dot_tes_dc_max / timestep; //[kg/s] - double m_dot_tank_charge_avail = mc_cold_tank.m_dot_available(f_ch_storage, step_s); //[kg/s] + // Call energy balance on hot tank discharge to get average outlet temperature over timestep + mc_hot_tank.energy_balance(timestep, 0.0, m_dot_htf_out, 0.0, T_amb, T_htf_hot_out, q_heater_hot, q_dot_loss_hot); - double T_cold_ini = mc_cold_tank.get_m_T_prev(); //[K] + // Call energy balance on cold tank charge to track tank mass and temperature + mc_cold_tank.energy_balance(timestep, m_dot_htf_out, 0.0, T_htf_cold_in, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold); + } - // for debugging - double T_hot_ini = mc_hot_tank.get_m_T_prev(); //[K] - double cp_T_tanks_avg = mc_store_htfProps.Cp_ave(T_cold_ini, T_hot_ini); // [kJ/kg-K] - double mass_avail_hot_tank = mc_hot_tank.m_dot_available(f_ch_storage, step_s) * step_s; //[kg] - double tes_charge_state = mass_avail_hot_tank * cp_T_tanks_avg * (T_hot_ini - T_cold_ini) * 1.e-3 / 3600.; // [MWht] + else + { // Iterate between field htf - hx - and storage - if(m_is_hx) - { - m_dot_external_est = get_external_m_dot(m_dot_tank_charge_avail); //[kg/s] + } - double eff, T_hot_tes; - eff = T_hot_tes = std::numeric_limits::quiet_NaN(); - mc_hx.solve(T_hot_K, m_dot_external_est, - T_cold_ini, m_dot_tank_charge_avail, - T_cold_external_est, T_hot_tes, eff, q_dot_ch_est); + outputs.m_q_heater = q_heater_cold + q_heater_hot; + outputs.m_m_dot = m_dot_htf_out; + outputs.m_W_dot_rhtf_pump = m_dot_htf_out * ms_params.m_htf_pump_coef / 1.E3; //[MWe] Pumping power for Receiver HTF, convert from kW/kg/s*kg/s + outputs.m_q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; + + outputs.m_T_hot_ave = T_htf_hot_out; + outputs.m_T_cold_ave = T_cold_ave; + outputs.m_T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] + outputs.m_T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] + + // Calculate thermal power to HTF + double cp_htf_ave = mc_field_htfProps.Cp_ave(T_htf_cold_in, T_htf_hot_out); //[kJ/kg-K] + outputs.m_q_dot_dc_to_htf = m_dot_htf_out * cp_htf_ave * (T_htf_hot_out - T_htf_cold_in) / 1000.0; //[MWt] + outputs.m_q_dot_ch_from_htf = 0.0; //[MWt] - // If above method fails, it will throw an exception, so if we don't want to break here, need to catch and handle it - } - else - { - double cp_T_avg = mc_store_htfProps.Cp_ave(T_cold_ini, T_hot_K); //[kJ/kg-K] spec heat at average temperature during charging from cold to hot - q_dot_ch_est = m_dot_tank_charge_avail * cp_T_avg * (T_hot_K - T_cold_ini) *1.E-3; //[MW] - m_dot_external_est = m_dot_tank_charge_avail; //[kg/s] - T_cold_external_est = T_cold_ini; //[K] - } } -int C_csp_two_tank_tes::solve_tes_off_design(double timestep /*s*/, double T_amb /*K*/, - double m_dot_cr_to_cv_hot /*kg/s*/, double m_dot_cv_hot_to_sink /*kg/s*/, double m_dot_cr_to_cv_cold /*kg/s*/, - double T_cr_out_hot /*K*/, double T_sink_out_cold /*K*/, - double& T_sink_htf_in_hot /*K*/, double& T_cr_in_cold /*K*/, - C_csp_tes::S_csp_tes_outputs& s_outputs) //, C_csp_solver_htf_state & s_tes_ch_htf, C_csp_solver_htf_state & s_tes_dc_htf) +bool C_csp_cold_tes::discharge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, + double T_htf_cold_in /*K*/, double& T_htf_hot_out /*K*/, S_csp_cold_tes_outputs& outputs) { - // Enthalpy balance on inlet to cold cv - double T_htf_cold_cv_in = T_sink_out_cold; //[K] - double m_dot_total_to_cv_cold = m_dot_cv_hot_to_sink + m_dot_cr_to_cv_cold; //[kg/s] - if (m_dot_total_to_cv_cold > 0.0) { - T_htf_cold_cv_in = (m_dot_cv_hot_to_sink*T_sink_out_cold + m_dot_cr_to_cv_cold*T_cr_out_hot) / (m_dot_total_to_cv_cold); - } - - // Total mass flow leaving cold tank to cr - // One of the RHS should always be 0... - double m_dot_cv_cold_to_cr = m_dot_cr_to_cv_hot + m_dot_cr_to_cv_cold; + // This method calculates the hot discharge temperature on the HX side (if applicable). If no heat exchanger (direct storage), + // the discharge temperature is equal to the average (timestep) hot tank outlet temperature. - s_outputs = S_csp_tes_outputs(); + // Inputs are: + // 1) Required hot side mass flow rate on the HX side (if applicable). If no heat exchanger, then the mass flow rate + // is equal to the hot tank exit mass flow rate (and cold tank fill mass flow rate) + // 2) inlet temperature on the HX side (if applicable). If no heat exchanger, the inlet temperature is the temperature + // of HTF directly entering the cold tank. - double m_dot_cr_to_tes_hot, m_dot_cr_to_tes_cold, m_dot_tes_hot_out, m_dot_pc_to_tes_cold, m_dot_tes_cold_out, m_dot_tes_cold_in; - m_dot_cr_to_tes_hot = m_dot_cr_to_tes_cold = m_dot_tes_hot_out = m_dot_pc_to_tes_cold = m_dot_tes_cold_out = m_dot_tes_cold_in = std::numeric_limits::quiet_NaN(); - double m_dot_src_to_sink, m_dot_sink_to_src; - m_dot_src_to_sink = m_dot_sink_to_src = std::numeric_limits::quiet_NaN(); + double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, T_cold_ave; + q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_cold_ave = std::numeric_limits::quiet_NaN(); - if (tanks_in_parallel) + // If no heat exchanger, no iteration is required between the heat exchanger and storage tank models + if (!ms_params.m_is_hx) { - // Receiver bypass is possible in a parallel configuration, - // but need to determine if it actually makes sense and how to model it - if (m_dot_cr_to_cv_cold != 0.0) { - throw(C_csp_exception("Receiver output to cold tank not allowed in parallel TES configuration")); - } - m_dot_cr_to_tes_cold = 0.0; - - if (m_dot_cr_to_cv_hot >= m_dot_cv_hot_to_sink) - { - m_dot_cr_to_tes_hot = m_dot_cr_to_cv_hot - m_dot_cv_hot_to_sink; //[kg/s] - m_dot_tes_hot_out = 0.0; //[kg/s] - m_dot_pc_to_tes_cold = 0.0; //[kg/s] - m_dot_tes_cold_out = m_dot_cr_to_tes_hot; //[kg/s] - m_dot_src_to_sink = m_dot_cv_hot_to_sink; //[kg/s] - m_dot_sink_to_src = m_dot_cv_hot_to_sink; //[kg/s] - } - else + if (m_dot_htf_in > m_m_dot_tes_dc_max / timestep) { - m_dot_cr_to_tes_hot = 0.0; //[kg/s] - m_dot_tes_hot_out = m_dot_cv_hot_to_sink - m_dot_cr_to_cv_hot; //[kg/s] - m_dot_pc_to_tes_cold = m_dot_tes_hot_out; //[kg/s] - m_dot_tes_cold_out = 0.0; //[kg/s] - m_dot_src_to_sink = m_dot_cr_to_cv_hot; //[kg/s] - m_dot_sink_to_src = m_dot_cr_to_cv_hot; //[kg/s] + outputs.m_q_heater = std::numeric_limits::quiet_NaN(); + outputs.m_m_dot = std::numeric_limits::quiet_NaN(); + outputs.m_W_dot_rhtf_pump = std::numeric_limits::quiet_NaN(); + outputs.m_q_dot_loss = std::numeric_limits::quiet_NaN(); + outputs.m_q_dot_dc_to_htf = std::numeric_limits::quiet_NaN(); + outputs.m_q_dot_ch_from_htf = std::numeric_limits::quiet_NaN(); + outputs.m_T_hot_ave = std::numeric_limits::quiet_NaN(); + outputs.m_T_cold_ave = std::numeric_limits::quiet_NaN(); + outputs.m_T_hot_final = std::numeric_limits::quiet_NaN(); + outputs.m_T_cold_final = std::numeric_limits::quiet_NaN(); + + return false; } - m_dot_tes_cold_in = m_dot_pc_to_tes_cold; + + // Call energy balance on hot tank discharge to get average outlet temperature over timestep + mc_hot_tank.energy_balance(timestep, 0.0, m_dot_htf_in, 0.0, T_amb, T_htf_hot_out, q_heater_hot, q_dot_loss_hot); + + // Call energy balance on cold tank charge to track tank mass and temperature + mc_cold_tank.energy_balance(timestep, m_dot_htf_in, 0.0, T_htf_cold_in, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold); } + else - { // Serial configuration - if (m_is_hx) - { - throw(C_csp_exception("Serial operation of C_csp_two_tank_tes not available if there is a storage HX")); - } + { // Iterate between field htf - hx - and storage - m_dot_cr_to_tes_hot = m_dot_cr_to_cv_hot; //[kg/s] - m_dot_cr_to_tes_cold = m_dot_cr_to_cv_cold; //[kg/s] - m_dot_tes_hot_out = m_dot_cv_hot_to_sink; //[kg/s] - m_dot_pc_to_tes_cold = m_dot_cv_hot_to_sink; //[kg/s] - m_dot_tes_cold_out = m_dot_cr_to_cv_hot + m_dot_cr_to_cv_cold; //[kg/s] - m_dot_tes_cold_in = m_dot_total_to_cv_cold; //[kg/s] - m_dot_src_to_sink = 0.0; //[kg/s] - m_dot_sink_to_src = 0.0; //[kg/s] } - double q_dot_heater = std::numeric_limits::quiet_NaN(); //[MWe] Heating power required to keep tanks at a minimum temperature - double m_dot_cold_tank_to_hot_tank = std::numeric_limits::quiet_NaN(); //[kg/s] Hot tank mass flow rate, valid for direct and indirect systems - double W_dot_rhtf_pump = std::numeric_limits::quiet_NaN(); //[MWe] Pumping power, just for tank-to-tank in indirect storage - double q_dot_loss = std::numeric_limits::quiet_NaN(); //[MWt] Storage thermal losses - double q_dot_dc_to_htf = std::numeric_limits::quiet_NaN(); //[MWt] Thermal power to the HTF from storage - double q_dot_ch_from_htf = std::numeric_limits::quiet_NaN(); //[MWt] Thermal power from the HTF to storage - double T_hot_ave = std::numeric_limits::quiet_NaN(); //[K] Average hot tank temperature over timestep - double T_cold_ave = std::numeric_limits::quiet_NaN(); //[K] Average cold tank temperature over timestep - double T_hot_final = std::numeric_limits::quiet_NaN(); //[K] Hot tank temperature at end of timestep - double T_cold_final = std::numeric_limits::quiet_NaN(); //[K] Cold tank temperature at end of timestep + outputs.m_q_heater = q_heater_cold + q_heater_hot; //[MWt] + outputs.m_m_dot = m_dot_htf_in; + outputs.m_W_dot_rhtf_pump = m_dot_htf_in * ms_params.m_htf_pump_coef / 1.E3; //[MWe] Pumping power for Receiver HTF, convert from kW/kg/s*kg/s + outputs.m_q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; //[MWt] - if (tanks_in_parallel) - { + outputs.m_T_hot_ave = T_htf_hot_out; //[K] + outputs.m_T_cold_ave = T_cold_ave; //[K] + outputs.m_T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] + outputs.m_T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] - if (m_dot_cr_to_cv_hot >= m_dot_cv_hot_to_sink) // Charging - { - T_sink_htf_in_hot = T_cr_out_hot; //[K] - double m_dot_tes_ch = m_dot_cr_to_cv_hot - m_dot_cv_hot_to_sink; //[kg/s] - double T_htf_tes_cold = std::numeric_limits::quiet_NaN(); //[K] - bool ch_solved = charge(timestep, - T_amb, - m_dot_tes_ch, - T_cr_out_hot, - T_htf_tes_cold, - q_dot_heater, m_dot_cold_tank_to_hot_tank, W_dot_rhtf_pump, - q_dot_loss, q_dot_dc_to_htf, q_dot_ch_from_htf, - T_hot_ave, T_cold_ave, T_hot_final, T_cold_final); + // Calculate thermal power to HTF + double cp_htf_ave = mc_field_htfProps.Cp_ave(T_htf_cold_in, T_htf_hot_out); //[kJ/kg-K] + outputs.m_q_dot_dc_to_htf = m_dot_htf_in * cp_htf_ave * (T_htf_hot_out - T_htf_cold_in) / 1000.0; //[MWt] + outputs.m_q_dot_ch_from_htf = 0.0; //[MWt] - // Check if TES.charge method solved - if (!ch_solved) - { - return -3; - } + return true; +} - // Enthalpy balance to calculate T_htf_cold to CR - if (m_dot_cr_to_cv_hot == 0.0) - { - T_cr_in_cold = T_htf_tes_cold; //[K] - } - else - { - T_cr_in_cold = (m_dot_tes_ch*T_htf_tes_cold + m_dot_cv_hot_to_sink*T_sink_out_cold) / m_dot_cr_to_cv_hot; //[K] - } - } - else // Discharging - { - T_cr_in_cold = T_sink_out_cold; //[K] - double m_dot_tes_dc = m_dot_cv_hot_to_sink - m_dot_cr_to_cv_hot; //[kg/s] - double T_htf_tes_hot = std::numeric_limits::quiet_NaN(); - bool is_tes_success = discharge(timestep, - T_amb, - m_dot_tes_dc, - T_sink_out_cold, - T_htf_tes_hot, - q_dot_heater, m_dot_cold_tank_to_hot_tank, W_dot_rhtf_pump, - q_dot_loss, q_dot_dc_to_htf, q_dot_ch_from_htf, - T_hot_ave, T_cold_ave, T_hot_final, T_cold_final); +bool C_csp_cold_tes::charge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, + double T_htf_hot_in /*K*/, double& T_htf_cold_out /*K*/, S_csp_cold_tes_outputs& outputs) +{ + // This method calculates the cold charge return temperature on the HX side (if applicable). If no heat exchanger (direct storage), + // the return charge temperature is equal to the average (timestep) cold tank outlet temperature. - m_dot_cold_tank_to_hot_tank *= -1.0; + // The method returns FALSE if the input mass flow rate 'm_dot_htf_in' * timestep is greater than the allowable charge - // Check if discharge method solved - if (!is_tes_success) - { - return -4; - } + // Inputs are: + // 1) Required cold side mass flow rate on the HX side (if applicable). If no heat exchanger, then the mass flow rate + // is equal to the cold tank exit mass flow rate (and hot tank fill mass flow rate) + // 2) Inlet temperature on the HX side (if applicable). If no heat exchanger, the inlet temperature is the temperature + // of HTF directly entering the hot tank - T_sink_htf_in_hot = (m_dot_tes_dc*T_htf_tes_hot + m_dot_cr_to_cv_hot*T_cr_out_hot) / m_dot_cv_hot_to_sink; //[K] - } - } - else // Serial tank operation + double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, T_hot_ave; + q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_hot_ave = std::numeric_limits::quiet_NaN(); + + // If no heat exchanger, no iteration is required between the heat exchanger and storage tank models + if (!ms_params.m_is_hx) { - if (m_is_hx) + if (m_dot_htf_in > m_m_dot_tes_ch_max / timestep) { - throw(C_csp_exception("C_csp_two_tank_tes::discharge_decoupled not available if there is a storage HX")); + outputs.m_q_dot_loss = std::numeric_limits::quiet_NaN(); + outputs.m_m_dot = std::numeric_limits::quiet_NaN(); + outputs.m_q_heater = std::numeric_limits::quiet_NaN(); + outputs.m_T_hot_ave = std::numeric_limits::quiet_NaN(); + outputs.m_T_cold_ave = std::numeric_limits::quiet_NaN(); + outputs.m_T_hot_final = std::numeric_limits::quiet_NaN(); + outputs.m_T_cold_final = std::numeric_limits::quiet_NaN(); + + return false; } - // Inputs are: - // 1) Mass flow rate of HTF from source to hot tank - // 2) Mass flow rate of HTF from source to cold tank - // 3) Mass flow rate of HTF from hot tank to sink - // 4) Temperature of HTF leaving source and entering hot tank - // 5) Temperature of HTF leaving the sink and entering the cold tank + // Call energy balance on cold tank discharge to get average outlet temperature over timestep + mc_cold_tank.energy_balance(timestep, 0.0, m_dot_htf_in, 0.0, T_amb, T_htf_cold_out, q_heater_cold, q_dot_loss_cold); - double q_dot_ch_est, m_dot_tes_ch_max, T_cold_to_src_est; - q_dot_ch_est = m_dot_tes_ch_max = T_cold_to_src_est = std::numeric_limits::quiet_NaN(); - charge_avail_est(T_cr_out_hot, timestep, q_dot_ch_est, m_dot_tes_ch_max, T_cold_to_src_est); + // Call energy balance on hot tank charge to track tank mass and temperature + mc_hot_tank.energy_balance(timestep, m_dot_htf_in, 0.0, T_htf_hot_in, T_amb, T_hot_ave, q_heater_hot, q_dot_loss_hot); + } - if (m_dot_cr_to_cv_hot > m_dot_cv_hot_to_sink && std::max(1.E-4, (m_dot_cr_to_cv_hot - m_dot_cv_hot_to_sink)) > 1.0001 * std::max(1.E-4, m_dot_tes_ch_max)) - { - q_dot_heater = std::numeric_limits::quiet_NaN(); - m_dot_cold_tank_to_hot_tank = std::numeric_limits::quiet_NaN(); - W_dot_rhtf_pump = std::numeric_limits::quiet_NaN(); - q_dot_loss = std::numeric_limits::quiet_NaN(); - q_dot_dc_to_htf = std::numeric_limits::quiet_NaN(); - q_dot_ch_from_htf = std::numeric_limits::quiet_NaN(); - T_hot_ave = std::numeric_limits::quiet_NaN(); - T_cold_ave = std::numeric_limits::quiet_NaN(); - T_hot_final = std::numeric_limits::quiet_NaN(); - T_cold_final = std::numeric_limits::quiet_NaN(); + else + { // Iterate between field htf - hx - and storage - return -1; - } + } - double q_dot_dc_est, m_dot_tes_dc_max, T_hot_to_pc_est; - q_dot_dc_est = m_dot_tes_dc_max = T_hot_to_pc_est = std::numeric_limits::quiet_NaN(); - // Use temperature downstream of sink-out and cr-to-cold-tank mixer - discharge_avail_est(T_htf_cold_cv_in, timestep, q_dot_dc_est, m_dot_tes_dc_max, T_hot_to_pc_est); + outputs.m_q_heater = q_heater_cold + q_heater_hot; //[MW] Storage thermal losses + outputs.m_m_dot = m_dot_htf_in; + outputs.m_W_dot_rhtf_pump = m_dot_htf_in * ms_params.m_htf_pump_coef / 1.E3; //[MWe] Pumping power for Receiver HTF, convert from kW/kg/s*kg/s + outputs.m_q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; //[MW] Heating power required to keep tanks at a minimum temperature - // If mass flow into the cold tank *from the sink* is greater than mass flow going from cold tank to source to hot tank - if (m_dot_cv_hot_to_sink > m_dot_cr_to_cv_hot && std::max(1.E-4, (m_dot_cv_hot_to_sink - m_dot_cr_to_cv_hot)) > 1.0001 * std::max(1.E-4, m_dot_tes_dc_max)) - { - q_dot_heater = std::numeric_limits::quiet_NaN(); - m_dot_cold_tank_to_hot_tank = std::numeric_limits::quiet_NaN(); - W_dot_rhtf_pump = std::numeric_limits::quiet_NaN(); - q_dot_loss = std::numeric_limits::quiet_NaN(); - q_dot_dc_to_htf = std::numeric_limits::quiet_NaN(); - q_dot_ch_from_htf = std::numeric_limits::quiet_NaN(); - T_hot_ave = std::numeric_limits::quiet_NaN(); - T_cold_ave = std::numeric_limits::quiet_NaN(); - T_hot_final = std::numeric_limits::quiet_NaN(); - T_cold_final = std::numeric_limits::quiet_NaN(); - return -2; - } + outputs.m_T_hot_ave = T_hot_ave; //[K] Average hot tank temperature over timestep + outputs.m_T_cold_ave = T_htf_cold_out; //[K] Average cold tank temperature over timestep + outputs.m_T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] Hot temperature at end of timestep + outputs.m_T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] Cold temperature at end of timestep - // serial operation constrained to direct configuration, so HTF leaving TES must pass through another plant component - m_dot_cold_tank_to_hot_tank = 0.0; //[kg/s] + // Calculate thermal power to HTF + double cp_htf_ave = mc_field_htfProps.Cp_ave(T_htf_cold_out, T_htf_hot_in); //[kJ/kg-K] + outputs.m_q_dot_ch_from_htf = m_dot_htf_in * cp_htf_ave * (T_htf_hot_in - T_htf_cold_out) / 1000.0; //[MWt] + outputs.m_q_dot_dc_to_htf = 0.0; //[MWt] - double q_heater_hot, q_dot_loss_hot, q_heater_cold, q_dot_loss_cold; - q_heater_hot = q_dot_loss_hot = q_heater_cold = q_dot_loss_cold = std::numeric_limits::quiet_NaN(); + return true; - // Call energy balance on hot tank discharge to get average outlet temperature over timestep - mc_hot_tank.energy_balance(timestep, m_dot_cr_to_cv_hot, m_dot_cv_hot_to_sink, - T_cr_out_hot, T_amb, - T_sink_htf_in_hot, q_heater_hot, q_dot_loss_hot); +} - // Call energy balance on cold tank charge to track tank mass and temperature - // Use mass flow and temperature downstream of sink-out and cr-to-cold-tank mixer - mc_cold_tank.energy_balance(timestep, m_dot_total_to_cv_cold, m_dot_cv_cold_to_cr, - T_htf_cold_cv_in, T_amb, - T_cr_in_cold, q_heater_cold, q_dot_loss_cold); +bool C_csp_cold_tes::charge_discharge(double timestep /*s*/, double T_amb /*K*/, double m_dot_hot_in /*kg/s*/, + double T_hot_in /*K*/, double m_dot_cold_in /*kg/s*/, double T_cold_in /*K*/, S_csp_cold_tes_outputs& outputs) +{ + // ARD This is for simultaneous charge and discharge. If no heat exchanger (direct storage), + // the return charge temperature is equal to the average (timestep) cold tank outlet temperature. - // Set output structure - q_dot_heater = q_heater_cold + q_heater_hot; //[MWt] + // The method returns FALSE if the input mass flow rate 'm_dot_htf_in' * timestep is greater than the allowable charge - W_dot_rhtf_pump = 0; //[MWe] Tank-to-tank pumping power + // Inputs are: + // 1) (Assumes no heat exchanger) The cold tank exit mass flow rate (and hot tank fill mass flow rate) + // 2) The temperature of HTF directly entering the hot tank. + // 3) The hot tank exit mass flow rate (and cold tank fill mass flow rate) + // 4) The temperature of the HTF directly entering the cold tank. - q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; //[MWt] - q_dot_ch_from_htf = 0.0; //[MWt] - T_hot_ave = T_sink_htf_in_hot; //[K] - T_cold_ave = T_cr_in_cold; //[K] - T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] - T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] - - // Net TES discharge - double cp_field = mc_external_htfProps.Cp_ave(T_cold_ave, T_cr_out_hot); - double cp_cycle = mc_store_htfProps.Cp_ave(T_htf_cold_cv_in, T_hot_ave); - double q_dot_tes_net_discharge = (cp_field * (m_dot_tes_cold_out * T_cold_ave - m_dot_cr_to_tes_hot * T_cr_out_hot) - + cp_cycle * (m_dot_tes_hot_out * T_hot_ave - m_dot_total_to_cv_cold * T_htf_cold_cv_in) ) / 1000.0; //[MWt] - - if (m_dot_cv_hot_to_sink >= m_dot_cr_to_cv_hot) - { - q_dot_ch_from_htf = 0.0; - - q_dot_dc_to_htf = q_dot_tes_net_discharge; //[MWt] - } - else - { - q_dot_dc_to_htf = 0.0; - - q_dot_ch_from_htf = -q_dot_tes_net_discharge; //[MWt] - } - - } - - // Solve pumping power here - double W_dot_htf_pump = pumping_power(m_dot_cr_to_cv_hot, m_dot_cv_hot_to_sink, std::abs(m_dot_cold_tank_to_hot_tank), - T_cr_in_cold, T_cr_out_hot, T_sink_htf_in_hot, T_sink_out_cold, - false); //[-] C_MEQ__m_dot_tes will not send cr_m_dot to TES if recirculating - - s_outputs.m_q_heater = q_dot_heater; - s_outputs.m_W_dot_elec_in_tot = W_dot_htf_pump; //[MWe] - - s_outputs.m_q_dot_dc_to_htf = q_dot_dc_to_htf; - s_outputs.m_q_dot_ch_from_htf = q_dot_ch_from_htf; - s_outputs.m_m_dot_cr_to_tes_hot = m_dot_cr_to_tes_hot; //[kg/s] - s_outputs.m_m_dot_cr_to_tes_cold = m_dot_cr_to_tes_cold; //[kg/s] - s_outputs.m_m_dot_tes_hot_out = m_dot_tes_hot_out; //[kg/s] - s_outputs.m_m_dot_pc_to_tes_cold = m_dot_pc_to_tes_cold; //[kg/s] - s_outputs.m_m_dot_tes_cold_out = m_dot_tes_cold_out; //[kg/s] - s_outputs.m_m_dot_tes_cold_in = m_dot_tes_cold_in; //[kg/s] - s_outputs.m_m_dot_src_to_sink = m_dot_src_to_sink; //[kg/s] - s_outputs.m_m_dot_sink_to_src = m_dot_sink_to_src; //[kg/s] - - s_outputs.m_T_tes_cold_in = T_htf_cold_cv_in; //[K] - - s_outputs.m_m_dot_cold_tank_to_hot_tank = m_dot_cold_tank_to_hot_tank; - - mc_reported_outputs.value(E_Q_DOT_LOSS, q_dot_loss); //[MWt] - mc_reported_outputs.value(E_W_DOT_HEATER, q_dot_heater); //[MWt] - mc_reported_outputs.value(E_TES_T_HOT, T_hot_final - 273.15); //[C] - mc_reported_outputs.value(E_TES_T_COLD, T_cold_final - 273.15); //[C] - mc_reported_outputs.value(E_M_DOT_TANK_TO_TANK, m_dot_cold_tank_to_hot_tank); //[kg/s] - mc_reported_outputs.value(E_MASS_COLD_TANK, mc_cold_tank.get_m_m_calc()); //[kg] - mc_reported_outputs.value(E_MASS_HOT_TANK, mc_hot_tank.get_m_m_calc()); //[kg] - mc_reported_outputs.value(E_W_DOT_HTF_PUMP, W_dot_htf_pump); //[MWe] - - return 0; -} - -bool C_csp_two_tank_tes::discharge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, - double T_htf_cold_in /*K*/, double & T_htf_hot_out /*K*/, - double & q_dot_heater /*MWe*/, double & m_dot_tank_to_tank /*kg/s*/, double & W_dot_rhtf_pump /*MWe*/, - double & q_dot_loss /*MWt*/, double & q_dot_dc_to_htf /*MWt*/, double & q_dot_ch_from_htf /*MWt*/, - double & T_hot_ave /*K*/, double & T_cold_ave /*K*/, double & T_hot_final /*K*/, double & T_cold_final /*K*/) -{ - // This method calculates the timestep-average hot discharge temperature of the TES system. This is out of the external side of the heat exchanger (HX), opposite the tank (or 'TES') side, - // or if no HX (direct storage), this is equal to the hot tank outlet temperature. - - // The method returns FALSE if the system output (same as input) mass flow rate is greater than that available - - // Inputs are: - // 1) Mass flow rate of HTF into TES system (equal to that exiting the system) - // 2) Temperature of HTF into TES system. If no heat exchanger, this temperature - // is of the HTF directly entering the cold tank - - double q_dot_dc_est, m_dot_tes_dc_max, T_hot_to_pc_est; - q_dot_dc_est = m_dot_tes_dc_max = T_hot_to_pc_est = std::numeric_limits::quiet_NaN(); - discharge_avail_est(T_htf_cold_in, timestep, q_dot_dc_est, m_dot_tes_dc_max, T_hot_to_pc_est); - - if (m_dot_htf_in > 1.0001*m_dot_tes_dc_max && m_dot_htf_in > 1.E-6) // mass flow in = mass flow out - { - q_dot_heater = std::numeric_limits::quiet_NaN(); - m_dot_tank_to_tank = std::numeric_limits::quiet_NaN(); - W_dot_rhtf_pump = std::numeric_limits::quiet_NaN(); - q_dot_loss = std::numeric_limits::quiet_NaN(); - q_dot_dc_to_htf = std::numeric_limits::quiet_NaN(); - q_dot_ch_from_htf = std::numeric_limits::quiet_NaN(); - T_hot_ave = std::numeric_limits::quiet_NaN(); - T_cold_ave = std::numeric_limits::quiet_NaN(); - T_hot_final = std::numeric_limits::quiet_NaN(); - T_cold_final = std::numeric_limits::quiet_NaN(); - - return false; - } - - double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, m_dot_src, - T_src_cold_in, T_src_hot_out, m_dot_tank, T_cold_tank_in; - q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_cold_ave = T_hot_ave = - m_dot_src = T_src_cold_in = T_src_hot_out = m_dot_tank = T_cold_tank_in = std::numeric_limits::quiet_NaN(); - - // If no heat exchanger, no iteration is required between the heat exchanger and storage tank models - if(!m_is_hx) - { - m_dot_src = m_dot_tank = m_dot_htf_in; - T_src_cold_in = T_cold_tank_in = T_htf_cold_in; - - // Call energy balance on hot tank discharge to get average outlet temperature over timestep - mc_hot_tank.energy_balance(timestep, 0.0, m_dot_tank, 0.0, T_amb, T_hot_ave, q_heater_hot, q_dot_loss_hot); - - // Call energy balance on cold tank charge to track tank mass and temperature - mc_cold_tank.energy_balance(timestep, m_dot_tank, 0.0, T_cold_tank_in, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold); - } - else - { - T_src_cold_in = T_htf_cold_in; //[K] - - m_dot_src = m_dot_htf_in; //[kg/s] - m_dot_tank = get_tes_m_dot(m_dot_src); - - mc_hot_tank.energy_balance(timestep, 0.0, m_dot_tank, 0.0, T_amb, T_hot_ave, q_heater_hot, q_dot_loss_hot); - - double eff_hx, q_dot_hx; - eff_hx = q_dot_hx = std::numeric_limits::quiet_NaN(); - mc_hx.solve(T_htf_cold_in, m_dot_src, - T_hot_ave, m_dot_tank, - T_src_hot_out, T_cold_tank_in, - eff_hx, q_dot_hx); - - mc_cold_tank.energy_balance(timestep, m_dot_tank, 0.0, T_cold_tank_in, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold); - } - - q_dot_heater = q_heater_cold + q_heater_hot; //[MWt] - - if (!m_is_hx) - { - m_dot_tank_to_tank = 0.0; - W_dot_rhtf_pump = 0; //[MWe] Just tank-to-tank pumping power - T_htf_hot_out = T_hot_ave; - } - else - { - m_dot_tank_to_tank = m_dot_tank; //[kg/s] - W_dot_rhtf_pump = m_dot_tank * m_tes_pump_coef / 1.E3; //[MWe] Just tank-to-tank pumping power, convert from kW/kg/s*kg/s - T_htf_hot_out = T_src_hot_out; - } - - q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; //[MWt] - q_dot_ch_from_htf = 0.0; //[MWt] - T_hot_ave = T_hot_ave; //[K] - T_cold_ave = T_cold_ave; //[K] - T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] - T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] - - // Calculate thermal power to HTF - double cp_htf_ave = mc_external_htfProps.Cp_ave(T_htf_cold_in, T_htf_hot_out); //[kJ/kg-K] - q_dot_dc_to_htf = m_dot_htf_in*cp_htf_ave*(T_htf_hot_out - T_htf_cold_in)/1000.0; //[MWt] - - return true; -} + double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, T_hot_ave, T_cold_ave; + q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_hot_ave = T_cold_ave = std::numeric_limits::quiet_NaN(); -bool C_csp_two_tank_tes::charge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, - double T_htf_hot_in /*K*/, double & T_htf_cold_out /*K*/, - double & q_dot_heater /*MWe*/, double & m_dot_tank_to_tank /*kg/s*/, double & W_dot_rhtf_pump /*MWe*/, - double & q_dot_loss /*MWt*/, double & q_dot_dc_to_htf /*MWt*/, double & q_dot_ch_from_htf /*MWt*/, - double & T_hot_ave /*K*/, double & T_cold_ave /*K*/, double & T_hot_final /*K*/, double & T_cold_final /*K*/) -{ - // This method calculates the timestep-average cold charge return temperature of the TES system. This is out of the external side of the heat exchanger (HX), opposite the tank (or 'TES') side, - // or if no HX (direct storage), this is equal to the cold tank outlet temperature. - - // The method returns FALSE if the system input mass flow rate is greater than the allowable charge - - // Inputs are: - // 1) Mass flow rate of HTF into TES system (equal to that exiting the system) - // 2) Temperature of HTF into TES system. If no heat exchanger, this temperature - // is of the HTF directly entering the hot tank - - double q_dot_ch_est, m_dot_tes_ch_max, T_cold_to_src_est; - q_dot_ch_est = m_dot_tes_ch_max = T_cold_to_src_est = std::numeric_limits::quiet_NaN(); - charge_avail_est(T_htf_hot_in, timestep, q_dot_ch_est, m_dot_tes_ch_max, T_cold_to_src_est); - - if (m_dot_htf_in > 1.0001*m_dot_tes_ch_max && m_dot_htf_in > 1.E-6) - { - q_dot_heater = std::numeric_limits::quiet_NaN(); - m_dot_tank_to_tank = std::numeric_limits::quiet_NaN(); - W_dot_rhtf_pump = std::numeric_limits::quiet_NaN(); - q_dot_loss = std::numeric_limits::quiet_NaN(); - q_dot_dc_to_htf = std::numeric_limits::quiet_NaN(); - q_dot_ch_from_htf = std::numeric_limits::quiet_NaN(); - T_hot_ave = std::numeric_limits::quiet_NaN(); - T_cold_ave = std::numeric_limits::quiet_NaN(); - T_hot_final = std::numeric_limits::quiet_NaN(); - T_cold_final = std::numeric_limits::quiet_NaN(); - - return false; - } - - double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, m_dot_src, - T_src_hot_in, T_src_cold_out, m_dot_tank, T_hot_tank_in; - q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_cold_ave = T_hot_ave = m_dot_src = - T_src_hot_in = T_src_cold_out = m_dot_tank = T_hot_tank_in = std::numeric_limits::quiet_NaN(); - - // If no heat exchanger, no iteration is required between the heat exchanger and storage tank models - if( !m_is_hx ) - { - m_dot_src = m_dot_tank = m_dot_htf_in; - T_src_hot_in = T_hot_tank_in = T_htf_hot_in; - - // Call energy balance on cold tank discharge to get average outlet temperature over timestep - mc_cold_tank.energy_balance(timestep, 0.0, m_dot_tank, 0.0, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold); - - // Call energy balance on hot tank charge to track tank mass and temperature - mc_hot_tank.energy_balance(timestep, m_dot_tank, 0.0, T_hot_tank_in, T_amb, T_hot_ave, q_heater_hot, q_dot_loss_hot); - } - else - { - T_src_hot_in = T_htf_hot_in; //[K] - - m_dot_src = m_dot_htf_in; - m_dot_tank = get_tes_m_dot(m_dot_src); //[kg/s] - - mc_cold_tank.energy_balance(timestep, 0.0, m_dot_tank, 0.0, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold); - - double eff_hx, q_dot_hx; - eff_hx = q_dot_hx = std::numeric_limits::quiet_NaN(); - mc_hx.solve(T_src_hot_in, m_dot_src, - T_cold_ave, m_dot_tank, - T_src_cold_out, T_hot_tank_in, - eff_hx, q_dot_hx); - - mc_hot_tank.energy_balance(timestep, m_dot_tank, 0.0, T_hot_tank_in, T_amb, T_hot_ave, q_heater_hot, q_dot_loss_hot); - } - - q_dot_heater = q_heater_cold + q_heater_hot; //[MWt] - - if (!m_is_hx) - { - m_dot_tank_to_tank = 0.0; - W_dot_rhtf_pump = 0; //[MWe] Just tank-to-tank pumping power - T_htf_cold_out = T_cold_ave; - } - else - { - m_dot_tank_to_tank = m_dot_tank; - W_dot_rhtf_pump = m_dot_tank * m_tes_pump_coef / 1.E3; //[MWe] Just tank-to-tank pumping power, convert from kW/kg/s*kg/s - T_htf_cold_out = T_src_cold_out; - } - q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; //[MWt] - q_dot_dc_to_htf = 0.0; //[MWt] - T_hot_ave = T_hot_ave; //[K] - T_cold_ave = T_cold_ave; //[K] - T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] - T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] - - // Calculate thermal power to HTF - double cp_htf_ave = mc_external_htfProps.Cp_ave(T_htf_cold_out, T_htf_hot_in); //[kJ/kg-K] - q_dot_ch_from_htf = m_dot_htf_in*cp_htf_ave*(T_htf_hot_in - T_htf_cold_out)/1000.0; //[MWt] - - return true; -} - -void C_csp_two_tank_tes::converged() -{ - mc_cold_tank.converged(); - mc_hot_tank.converged(); - //mc_hx.converged(); - - // Set reported sink converged values - mc_reported_outputs.value(E_HOT_TANK_HTF_PERC_FINAL, mc_hot_tank.get_mass_avail() / m_mass_total_active * 100.0); - - mc_reported_outputs.set_timestep_outputs(); - - // The max charge and discharge flow rates should be set at the beginning of each timestep - // during the q_dot_xx_avail_est calls - // m_m_dot_tes_dc_max = m_m_dot_tes_ch_max = std::numeric_limits::quiet_NaN(); -} - -void C_csp_two_tank_tes::get_final_from_converged(double& f_V_hot, double& T_hot_tank /*K*/, double& T_cold_tank /*K*/) -{ - f_V_hot = mc_hot_tank.get_mass_avail() / m_mass_total_active; //[-] - T_hot_tank = mc_hot_tank.get_m_T_prev(); //[K] - T_cold_tank = mc_cold_tank.get_m_T_prev(); //[K] -} - -void C_csp_two_tank_tes::write_output_intervals(double report_time_start, - const std::vector& v_temp_ts_time_end, double report_time_end) -{ - mc_reported_outputs.send_to_reporting_ts_array(report_time_start, - v_temp_ts_time_end, report_time_end); -} - -void C_csp_two_tank_tes::assign(int index, double* p_reporting_ts_array, size_t n_reporting_ts_array) -{ - mc_reported_outputs.assign(index, p_reporting_ts_array, n_reporting_ts_array); -} - -int C_csp_two_tank_tes::pressure_drops(double m_dot_sf, double m_dot_pb, - double T_sf_in, double T_sf_out, double T_pb_in, double T_pb_out, bool recirculating, - double &P_drop_col, double &P_drop_gen) -{ - const std::size_t num_sections = 11; // total number of col. + gen. sections - const std::size_t bypass_section = 4; // bypass section index - const std::size_t gen_first_section = 5; // first generation section index in combined col. gen. loops - const double P_hi = 17 / 1.e-5; // downstream SF pump pressure [Pa] - const double P_lo = 1 / 1.e-5; // atmospheric pressure [Pa] - double P, T, rho, v_dot, vel; // htf properties - double Area; // cross-sectional pipe area - double v_dot_src, v_dot_sink; // source and sink vol. flow rates - double k; // effective minor loss coefficient - double Re, ff; - double v_dot_ref; - double dP_discharge; - std::vector P_drops(num_sections, 0.0); - - util::matrix_t L = this->tes_lengths; - util::matrix_t D = this->pipe_diams; - util::matrix_t k_coeffs = this->k_tes_loss_coeffs; - util::matrix_t v_dot_rel = this->pipe_v_dot_rel; - m_dot_pb > 0 ? dP_discharge = this->dP_discharge * 1.e5 : dP_discharge = 0.; - v_dot_src = m_dot_sf / this->mc_external_htfProps.dens((T_sf_in + T_sf_out) / 2, (P_hi + P_lo) / 2); - v_dot_sink = m_dot_pb / this->mc_external_htfProps.dens((T_pb_in + T_pb_out) / 2, P_lo); + // If no heat exchanger, no iteration is required between the heat exchanger and storage tank models + if (!ms_params.m_is_hx) + { + if (m_dot_hot_in > m_m_dot_tes_ch_max / timestep) + { + outputs.m_q_dot_loss = std::numeric_limits::quiet_NaN(); + outputs.m_m_dot = std::numeric_limits::quiet_NaN(); + outputs.m_q_heater = std::numeric_limits::quiet_NaN(); + outputs.m_T_hot_ave = std::numeric_limits::quiet_NaN(); + outputs.m_T_cold_ave = std::numeric_limits::quiet_NaN(); + outputs.m_T_hot_final = std::numeric_limits::quiet_NaN(); + outputs.m_T_cold_final = std::numeric_limits::quiet_NaN(); - for (std::size_t i = 0; i < num_sections; i++) { - if (L.at(i) > 0 && D.at(i) > 0) { - (i > 0 && i < 3) ? P = P_hi : P = P_lo; - if (i < 3) T = T_sf_in; // 0, 1, 2 - if (i == 3 || i == 4) T = T_sf_out; // 3, 4 - if (i >= gen_first_section && i < gen_first_section + 4) T = T_pb_in; // 5, 6, 7, 8 - if (i == gen_first_section + 4) T = (T_pb_in + T_pb_out) / 2.; // 9 - if (i == gen_first_section + 5) T = T_pb_out; // 10 - i < gen_first_section ? v_dot_ref = v_dot_src : v_dot_ref = v_dot_sink; - v_dot = v_dot_rel.at(i) * v_dot_ref; - Area = CSP::pi * pow(D.at(i), 2) / 4.; - vel = v_dot / Area; - rho = this->mc_external_htfProps.dens(T, P); - Re = this->mc_external_htfProps.Re(T, P, vel, D.at(i)); - ff = CSP::FrictionFactor(this->pipe_rough / D.at(i), Re); - if (i != bypass_section || recirculating) { - P_drops.at(i) += CSP::MajorPressureDrop(vel, rho, ff, L.at(i), D.at(i)); - P_drops.at(i) += CSP::MinorPressureDrop(vel, rho, k_coeffs.at(i)); - } + return false; } + + // Call energy balance on cold tank discharge to get average outlet temperature over timestep + mc_cold_tank.energy_balance(timestep, m_dot_cold_in, m_dot_hot_in, T_cold_in, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold); + + // Call energy balance on hot tank charge to track tank mass and temperature + mc_hot_tank.energy_balance(timestep, m_dot_hot_in, m_dot_cold_in, T_hot_in, T_amb, T_hot_ave, q_heater_hot, q_dot_loss_hot); } - P_drop_col = std::accumulate(P_drops.begin(), P_drops.begin() + gen_first_section, 0.0); - P_drop_gen = dP_discharge + std::accumulate(P_drops.begin() + gen_first_section, P_drops.end(), 0.0); + else + { // Iterate between field htf - hx - and storage + + } + + outputs.m_q_heater = q_heater_cold + q_heater_hot; //[MW] Storage thermal losses + outputs.m_m_dot = m_dot_hot_in; + outputs.m_W_dot_rhtf_pump = m_dot_hot_in * ms_params.m_htf_pump_coef / 1.E3; //[MWe] Pumping power for Receiver HTF, convert from kW/kg/s*kg/s + outputs.m_q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; //[MW] Heating power required to keep tanks at a minimum temperature + + + outputs.m_T_hot_ave = T_hot_ave; //[K] Average hot tank temperature over timestep + outputs.m_T_cold_ave = T_cold_ave; //[K] Average cold tank temperature over timestep + outputs.m_T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] Hot temperature at end of timestep + outputs.m_T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] Cold temperature at end of timestep + + // Calculate thermal power to HTF + double cp_htf_ave = mc_field_htfProps.Cp_ave(T_cold_ave, T_hot_in); //[kJ/kg-K] + outputs.m_q_dot_ch_from_htf = m_dot_hot_in * cp_htf_ave * (T_hot_in - T_cold_ave) / 1000.0; //[MWt] + outputs.m_q_dot_dc_to_htf = 0.0; //[MWt] + + return true; - return 0; } -double /*MWe*/ C_csp_two_tank_tes::pumping_power(double m_dot_sf /*kg/s*/, double m_dot_pb /*kg/s*/, double m_dot_tank /*kg/s*/, - double T_sf_in /*K*/, double T_sf_out /*K*/, double T_pb_in /*K*/, double T_pb_out /*K*/, bool recirculating) +bool C_csp_cold_tes::recirculation(double timestep /*s*/, double T_amb /*K*/, double m_dot_cold_in /*kg/s*/, + double T_cold_in /*K*/, S_csp_cold_tes_outputs& outputs) { - double htf_pump_power = 0.; - double rho_sf, rho_pb; - double DP_col, DP_gen; - double tes_pump_coef = this->m_tes_pump_coef; - double pb_pump_coef = this->m_htf_pump_coef; - double eta_pump = this->eta_pump; - - if (this->custom_tes_p_loss) { - double rho_sf, rho_pb; - double DP_col, DP_gen; - this->pressure_drops(m_dot_sf, m_dot_pb, T_sf_in, T_sf_out, T_pb_in, T_pb_out, recirculating, - DP_col, DP_gen); - rho_sf = this->mc_external_htfProps.dens((T_sf_in + T_sf_out) / 2., 8e5); - rho_pb = this->mc_external_htfProps.dens((T_pb_in + T_pb_out) / 2., 1e5); - if (this->m_is_hx) { - // TODO - replace tes_pump_coef with a pump efficiency. Maybe utilize unused coefficients specified for the - // series configuration, namely the SGS Pump suction header to Individual SGS pump inlet and the additional - // two immediately downstream - htf_pump_power = tes_pump_coef * m_dot_tank / 1000 + - (DP_col * m_dot_sf / (rho_sf * eta_pump) + DP_gen * m_dot_pb / (rho_pb * eta_pump)) / 1e6; //[MW] - } - else { - htf_pump_power = (DP_col * m_dot_sf / (rho_sf * eta_pump) + DP_gen * m_dot_pb / (rho_pb * eta_pump)) / 1e6; //[MW] + // This method calculates the average (timestep) cold tank outlet temperature when recirculating cold fluid for further cooling. + // This warm tank is idle and its state is also determined. + + // The method returns FALSE if the input mass flow rate 'm_dot_htf_in' * timestep is greater than the allowable charge + + // Inputs are: + // 1) The cold tank exit mass flow rate + // 2) The inlet temperature of HTF directly entering the cold tank + + double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, T_hot_ave, T_cold_ave; + q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_hot_ave = T_cold_ave = std::numeric_limits::quiet_NaN(); + + // If no heat exchanger, no iteration is required between the heat exchanger and storage tank models + if (!ms_params.m_is_hx) + { + if (m_dot_cold_in > m_m_dot_tes_ch_max / timestep) //Is this necessary for recirculation mode? ARD + { + outputs.m_q_dot_loss = std::numeric_limits::quiet_NaN(); + outputs.m_m_dot = std::numeric_limits::quiet_NaN(); + outputs.m_q_heater = std::numeric_limits::quiet_NaN(); + outputs.m_T_hot_ave = std::numeric_limits::quiet_NaN(); + outputs.m_T_cold_ave = std::numeric_limits::quiet_NaN(); + outputs.m_T_hot_final = std::numeric_limits::quiet_NaN(); + outputs.m_T_cold_final = std::numeric_limits::quiet_NaN(); + + return false; } + + // Call energy balance on cold tank discharge to get average outlet temperature over timestep + mc_cold_tank.energy_balance_constant_mass(timestep, m_dot_cold_in, T_cold_in, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold); + + // Call energy balance on hot tank charge to track tank mass and temperature while idle + mc_hot_tank.energy_balance(timestep, 0.0, 0.0, 0.0, T_amb, T_hot_ave, q_heater_hot, q_dot_loss_hot); + } - else { // original methods - if (this->m_is_hx) - { - // Also going to be tanks_in_parallel = true if there's a hx between external system and TES HTF - htf_pump_power = (tes_pump_coef * m_dot_tank + tes_pump_coef * std::abs(m_dot_pb - m_dot_sf)) / 1000.0; //[MWe] - //htf_pump_power = (tes_pump_coef*m_dot_tank + pb_pump_coef * (fabs(m_dot_pb - m_dot_sf) + m_dot_pb)) / 1000.0; //[MW] - } - else - { - htf_pump_power = 0.0; //[MWe] - //if (this->tanks_in_parallel) { - // htf_pump_power = pb_pump_coef * (fabs(m_dot_pb - m_dot_sf) + m_dot_pb) / 1000.0; //[MW] - //} - //else { - // htf_pump_power = pb_pump_coef * m_dot_pb / 1000.0; //[MW] - //} - } + + else + { // Iterate between field htf - hx - and storage + } - return htf_pump_power; + outputs.m_q_heater = q_heater_cold + q_heater_hot; //[MW] Storage thermal losses + outputs.m_m_dot = m_dot_cold_in; + outputs.m_W_dot_rhtf_pump = m_dot_cold_in * ms_params.m_htf_pump_coef / 1.E3; //[MWe] Pumping power for Receiver HTF, convert from kW/kg/s*kg/s + outputs.m_q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; //[MW] Heating power required to keep tanks at a minimum temperature + + outputs.m_T_hot_ave = T_hot_ave; //[K] Average hot tank temperature over timestep + outputs.m_T_cold_ave = T_cold_ave; //[K] Average cold tank temperature over timestep + outputs.m_T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] Hot temperature at end of timestep + outputs.m_T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] Cold temperature at end of timestep + + // Calculate thermal power to HTF + double cp_htf_ave = mc_field_htfProps.Cp_ave(T_cold_ave, T_cold_in); //[kJ/kg-K] + outputs.m_q_dot_ch_from_htf = m_dot_cold_in * cp_htf_ave * (T_cold_in - T_cold_ave) / 1000.0; //[MWt] + outputs.m_q_dot_dc_to_htf = 0.0; //[MWt] + + return true; + } -double C_csp_two_tank_tes::get_min_storage_htf_temp() +void C_csp_cold_tes::charge_full(double timestep /*s*/, double T_amb /*K*/, double T_htf_hot_in /*K*/, + double& T_htf_cold_out /*K*/, double& m_dot_htf_out /*kg/s*/, S_csp_cold_tes_outputs& outputs) { - return mc_store_htfProps.min_temp(); + // This method calculates the cold charge return temperature and mass flow rate on the HX side (if applicable) during FULL CHARGE. If no heat exchanger (direct storage), + // the charge return temperature is equal to the average (timestep) cold tank outlet temperature + + // Inputs are: + // 1) inlet temperature on the HX side (if applicable). If no heat exchanger, the inlet temperature is the temperature + // of HTF directly entering the hot tank. + + double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, T_hot_ave; + q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_hot_ave = std::numeric_limits::quiet_NaN(); + + // If no heat exchanger, no iteration is required between the heat exchanger and storage tank models + if (!ms_params.m_is_hx) + { + m_dot_htf_out = m_m_dot_tes_ch_max / timestep; //[kg/s] + + // Call energy balance on hot tank charge to track tank mass and temperature + mc_hot_tank.energy_balance(timestep, m_dot_htf_out, 0.0, T_htf_hot_in, T_amb, T_hot_ave, q_heater_hot, q_dot_loss_hot); + + // Call energy balance on cold tank charge to calculate cold HTF return temperature + mc_cold_tank.energy_balance(timestep, 0.0, m_dot_htf_out, 0.0, T_amb, T_htf_cold_out, q_heater_cold, q_dot_loss_cold); + } + + else + { // Iterate between field htf - hx - and storage + + } + + outputs.m_q_heater = q_heater_cold + q_heater_hot; + outputs.m_m_dot = m_dot_htf_out; + outputs.m_W_dot_rhtf_pump = m_dot_htf_out * ms_params.m_htf_pump_coef / 1.E3; //[MWe] Pumping power for Receiver HTF, convert from kW/kg/s*kg/s + outputs.m_q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; + + outputs.m_T_hot_ave = T_hot_ave; + outputs.m_T_cold_ave = T_htf_cold_out; + outputs.m_T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] + outputs.m_T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] + + // Calculate thermal power to HTF + double cp_htf_ave = mc_field_htfProps.Cp_ave(T_htf_cold_out, T_htf_hot_in); //[kJ/kg-K] + outputs.m_q_dot_ch_from_htf = m_dot_htf_out * cp_htf_ave * (T_htf_hot_in - T_htf_cold_out) / 1000.0; //[MWt] + outputs.m_q_dot_dc_to_htf = 0.0; //[MWt] + } -double C_csp_two_tank_tes::get_max_storage_htf_temp() +void C_csp_cold_tes::idle(double timestep, double T_amb, S_csp_cold_tes_outputs& outputs) { - return mc_store_htfProps.max_temp(); + double T_hot_ave, q_hot_heater, q_dot_hot_loss; + T_hot_ave = q_hot_heater = q_dot_hot_loss = std::numeric_limits::quiet_NaN(); + + mc_hot_tank.energy_balance(timestep, 0.0, 0.0, 0.0, T_amb, T_hot_ave, q_hot_heater, q_dot_hot_loss); + + double T_cold_ave, q_cold_heater, q_dot_cold_loss; + T_cold_ave = q_cold_heater = q_dot_cold_loss = std::numeric_limits::quiet_NaN(); + + mc_cold_tank.energy_balance(timestep, 0.0, 0.0, 0.0, T_amb, T_cold_ave, q_cold_heater, q_dot_cold_loss); + + outputs.m_q_heater = q_cold_heater + q_hot_heater; //[MJ] + outputs.m_m_dot = 0.; + outputs.m_W_dot_rhtf_pump = 0.0; //[MWe] + outputs.m_q_dot_loss = q_dot_cold_loss + q_dot_hot_loss; //[MW] + + outputs.m_T_hot_ave = T_hot_ave; //[K] + outputs.m_T_cold_ave = T_cold_ave; //[K] + outputs.m_T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] + outputs.m_T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] + + outputs.m_q_dot_ch_from_htf = 0.0; //[MWt] + outputs.m_q_dot_dc_to_htf = 0.0; //[MWt] } -double C_csp_two_tank_tes::get_storage_htf_density() +void C_csp_cold_tes::converged() { - double avg_temp = (m_T_cold_des + m_T_hot_des) / 2.0; - return mc_store_htfProps.dens(avg_temp, 0); + mc_cold_tank.converged(); + mc_hot_tank.converged(); + + // The max charge and discharge flow rates should be set at the beginning of each timestep + // during the q_dot_xx_avail_est calls + m_m_dot_tes_dc_max = m_m_dot_tes_ch_max = std::numeric_limits::quiet_NaN(); } -double C_csp_two_tank_tes::get_storage_htf_cp() +int C_csp_cold_tes::pressure_drops(double m_dot_sf, double m_dot_pb, + double T_sf_in, double T_sf_out, double T_pb_in, double T_pb_out, bool recirculating, + double& P_drop_col, double& P_drop_gen) { - double avg_temp = (m_T_cold_des + m_T_hot_des) / 2.0; - return mc_store_htfProps.Cp(avg_temp); + P_drop_col = 0.; + P_drop_gen = 0.; + + return 0; } -bool C_csp_two_tank_tes::get_is_hx() +double C_csp_cold_tes::pumping_power(double m_dot_sf, double m_dot_pb, double m_dot_tank, + double T_sf_in, double T_sf_out, double T_pb_in, double T_pb_out, bool recirculating) { - return m_is_hx; + return m_dot_tank * this->ms_params.m_htf_pump_coef / 1.E3; } -void two_tank_tes_sizing(HTFProperties &tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, - double T_tes_cold /*K*/, double h_min /*m*/, double h_tank /*m*/, int tank_pairs /*-*/, double u_tank /*W/m^2-K*/, - double & vol_one_temp_avail /*m3*/, double & vol_one_temp_total /*m3*/, double & d_tank /*m*/, - double & q_dot_loss_des /*MWt*/) + +C_hx_two_tank_tes::C_hx_two_tank_tes() { - double T_tes_ave = 0.5*(T_tes_hot + T_tes_cold); //[K] - - double rho_ave = tes_htf_props.dens(T_tes_ave, 1.0); //[kg/m^3] Density at average temperature - double cp_ave = tes_htf_props.Cp_ave(T_tes_cold, T_tes_hot);//[kJ/kg-K] Specific heat at average temperature + m_m_dot_des_ave = m_eff_des = m_UA_des = std::numeric_limits::quiet_NaN(); +} - // Volume required to supply design hours of thermal energy storage - //[m^3] = [MJ/s-hr] * [sec]/[hr] = [MJ] / (kg/m^3 * MJ/kg-K * K - vol_one_temp_avail = Q_tes_des*3600.0 / (rho_ave * cp_ave / 1000.0 * (T_tes_hot - T_tes_cold)); +void C_hx_two_tank_tes::init(const HTFProperties &fluid_external, const HTFProperties &fluid_store, double q_transfer_des /*W*/, + double dt_des, double T_h_in_des /*K*/, double T_h_out_des /*K*/) +{ + // Counter flow heat exchanger - // Additional volume necessary due to minimum tank limits - vol_one_temp_total = vol_one_temp_avail / (1.0 - h_min / h_tank); //[m^3] + mc_external_htfProps = fluid_external; + mc_store_htfProps = fluid_store; - double A_cs = vol_one_temp_total / (h_tank*tank_pairs); //[m^2] Cross-sectional area of a single tank + // Design should provide source/sink side design temperatures + double c_h = mc_external_htfProps.Cp_ave(T_h_out_des, T_h_in_des) * 1000.0; //[J/kg-K] Spec heat of hot side fluid at hot side average temperature, convert from [kJ/kg-K] + double c_c = mc_store_htfProps.Cp_ave(T_h_out_des, T_h_in_des) * 1000.0; //[J/kg-K] Spec heat of cold side fluid at hot side average temperature (estimate, but should be close) + // HX inlet and outlet temperatures + double T_c_out = T_h_in_des - dt_des; + double T_c_in = T_h_out_des - dt_des; + // Mass flow rates + double m_dot_h = q_transfer_des/(c_h*(T_h_in_des - T_h_out_des)); //[kg/s] + double m_dot_c = q_transfer_des/(c_c*(T_c_out - T_c_in)); //[kg/s] + m_m_dot_des_ave = 0.5*(m_dot_h + m_dot_c); //[kg/s] + // Capacitance rates + double c_dot_h = m_dot_h * c_h; //[W/K] + double c_dot_c = m_dot_c * c_c; //[W/K] + double c_dot_max = std::max(c_dot_h, c_dot_c); //[W/K] + double c_dot_min = std::min(c_dot_h, c_dot_c); //[W/K] + double cr = c_dot_min / c_dot_max; //[-] + // Maximum possible energy flow rate + double q_max = c_dot_min * (T_h_in_des - T_c_in); //[W] + // Effectiveness + m_eff_des = q_transfer_des / q_max; - d_tank = pow(A_cs / CSP::pi, 0.5)*2.0; //[m] Diameter of a single tank + // Check for realistic conditions + if(cr > 1.0 || cr < 0.0) + { + throw(C_csp_exception("Heat exchanger design calculations failed", "")); + } - double UA_tanks_one_temp = u_tank*(A_cs + CSP::pi*d_tank*h_tank)*tank_pairs; //[W/K] + double NTU = std::numeric_limits::quiet_NaN(); + if(cr < 1.0) + NTU = log((1. - m_eff_des*cr) / (1. - m_eff_des)) / (1. - cr); + else + NTU = m_eff_des / (1. - m_eff_des); - double T_amb_des = 15.0 + 273.15; //[K] - double q_dot_loss_cold = UA_tanks_one_temp*(T_tes_cold - T_amb_des)*1.E-6; //[MWt] - double q_dot_loss_hot = UA_tanks_one_temp*(T_tes_hot - T_amb_des)*1.E-6; //[MWt] - q_dot_loss_des = q_dot_loss_cold + q_dot_loss_hot; //[MWt] - + m_UA_des = NTU * c_dot_min; //[W/K] } -int size_tes_piping(double vel_dsn, util::matrix_t L, double rho_avg, double m_dot_pb, double solarm, - bool tanks_in_parallel, double &vol_tot, util::matrix_t &v_dot_rel, util::matrix_t &diams, - util::matrix_t &wall_thk, util::matrix_t &m_dot, util::matrix_t &vel, bool custom_sizes) +void C_hx_two_tank_tes::solve(double T_f_htf_hx_in /*K*/, double m_dot_f_htf /*kg/s*/, + double T_s_htf_hx_in /*K*/, double m_dot_s_htf /*kg/s*/, + double & T_f_htf_hx_out /*K*/, double & T_s_htf_hx_out /*K*/, + double & eff /*-*/, double & q_dot_hx /*MWt*/) { - const std::size_t bypass_index = 4; - const std::size_t gen_first_index = 5; // first generation section index in combined col. gen. loops - double m_dot_sf; - double v_dot_src, v_dot_sink; // source and sink vol. flow rates - double v_dot_ref; - double v_dot; // volumetric flow rate - double Area; - vol_tot = 0.0; // total volume in SGS piping - std::size_t nPipes = L.ncells(); - v_dot_rel.resize_fill(nPipes, 0.0); // volumetric flow rate relative to the source or sink flow - m_dot.resize_fill(nPipes, 0.0); - vel.resize_fill(nPipes, 0.0); - std::vector sections_no_bypass; - if (!custom_sizes) { - diams.resize_fill(nPipes, 0.0); - wall_thk.resize_fill(nPipes, 0.0); - } - - m_dot_sf = m_dot_pb * solarm; - v_dot_src = m_dot_sf / rho_avg; - v_dot_sink = m_dot_pb / rho_avg; - - //The volumetric flow rate relative to the source for each collection section (v_rel = v_dot / v_dot_sink) - v_dot_rel.at(0) = 1.0 / 2; // 1 - Source pump suction header to individual source pump inlet - // 50% -> "/2.0" . The flow rate (i.e., diameter) is sized here for the case when one pump is down. - v_dot_rel.at(1) = 1.0 / 2; // 2 - Individual SF pump discharge to SF pump discharge header - v_dot_rel.at(2) = 1.0; // 3 - Source pump discharge header to collection source section headers (i.e., runners) - v_dot_rel.at(3) = 1.0; // 4 - Source section outlet headers (i.e., runners) to expansion vessel (indirect storage) or - // hot thermal storage tank (direct storage) - v_dot_rel.at(4) = 1.0; // 5 - Bypass branch - Source section outlet headers (i.e., runners) to pump suction header (indirect) or - // cold thermal storage tank (direct) - - //The volumetric flow rate relative to the power block for each generation section - v_dot_rel.at(5) = 1.0 / 2; // 2 - SGS pump suction header to individual SGS pump inlet (applicable only for storage in series with SF) - // 50% -> "/2.0" . The flow rate (i.e., diameter) is sized here for the case when one pump is down. - v_dot_rel.at(6) = 1.0 / 2; // 3 - Individual SGS pump discharge to SGS pump discharge header (only for series storage) - v_dot_rel.at(7) = 1.0; // 4 - SGS pump discharge header to steam generator supply header (only for series storage) - - v_dot_rel.at(8) = 1.0; // 5 - Steam generator supply header to inter-steam generator piping - v_dot_rel.at(9) = 1.0; // 6 - Inter-steam generator piping to steam generator outlet header - v_dot_rel.at(10) = 1.0; // 7 - Steam generator outlet header to SF pump suction header (indirect) or cold thermal storage tank (direct) + if (m_dot_f_htf == 0.0 || m_dot_s_htf == 0.0) + { + T_f_htf_hx_out = T_f_htf_hx_in; + T_s_htf_hx_out = T_s_htf_hx_in; - if (tanks_in_parallel) { - sections_no_bypass = { 0, 1, 2, 3, 8, 9, 10 }; - } - else { // tanks in series - sections_no_bypass = { 0, 1, 2, 3, 5, 6, 7, 8, 9, 10 }; - } + eff = 0.0; + q_dot_hx = 0.0; - // Collection loop followed by generation loop - for (std::size_t i = 0; i < nPipes; i++) { - if (L.at(i) > 0) { - i < gen_first_index ? v_dot_ref = v_dot_src : v_dot_ref = v_dot_sink; - v_dot = v_dot_ref * v_dot_rel.at(i); - if (!custom_sizes) { - diams.at(i) = CSP::pipe_sched(sqrt(4.0*v_dot / (vel_dsn * CSP::pi))); - wall_thk.at(i) = CSP::WallThickness(diams.at(i)); - } - m_dot.at(i) = v_dot * rho_avg; - Area = CSP::pi * pow(diams.at(i), 2) / 4.; - vel.at(i) = v_dot / Area; + return; + } - // Calculate total volume, excluding bypass branch - if (std::find(sections_no_bypass.begin(), sections_no_bypass.end(), i) != sections_no_bypass.end()) { - vol_tot += Area * L.at(i); - } - } - } + // Scale UA + double m_dot_od = 0.5 * (m_dot_f_htf + m_dot_s_htf); //[kg/s] + double UA = m_UA_des * pow(m_dot_od / m_m_dot_des_ave, 0.8); - return 0; -} + double cp_f = mc_external_htfProps.Cp_ave(T_s_htf_hx_in, T_f_htf_hx_in) * 1000.0; //[J/kg-K] + double c_dot_f = cp_f * m_dot_f_htf; + double cp_s = mc_store_htfProps.Cp_ave(T_s_htf_hx_in, T_f_htf_hx_in) * 1000.0; //[J/kg-K] + double c_dot_s = cp_s * m_dot_s_htf; -int size_tes_piping_TandP(HTFProperties &external_htf_props, double T_src_in, double T_src_out, double P_src_in, double dP_discharge, - const util::matrix_t &L, const util::matrix_t &k_tes_loss_coeffs, double pipe_rough, - bool tanks_in_parallel, const util::matrix_t &diams, const util::matrix_t &vel, - util::matrix_t &TES_T_des, util::matrix_t &TES_P_des, double &TES_P_in) -{ - std::size_t nPipes = L.ncells(); - TES_T_des.resize_fill(nPipes, 0.0); - TES_P_des.resize_fill(nPipes, 0.0); + double c_dot_min = std::min(c_dot_f, c_dot_s); - // Calculate Design Temperatures, in C - TES_T_des.at(0) = T_src_in - 273.15; - TES_T_des.at(1) = T_src_in - 273.15; - TES_T_des.at(2) = T_src_in - 273.15; - TES_T_des.at(3) = T_src_out - 273.15; - TES_T_des.at(4) = T_src_out - 273.15; - if (tanks_in_parallel) { - TES_T_des.at(5) = 0; - TES_T_des.at(6) = 0; - TES_T_des.at(7) = 0; - } - else { - TES_T_des.at(5) = T_src_out - 273.15; - TES_T_des.at(6) = T_src_out - 273.15; - TES_T_des.at(7) = T_src_out - 273.15; - } - TES_T_des.at(8) = T_src_out - 273.15; - TES_T_des.at(9) = T_src_in - 273.15; - TES_T_des.at(10) = T_src_in - 273.15; + double CR = std::min(c_dot_f, c_dot_s) / std::max(c_dot_s, c_dot_f); + double NTU = UA / c_dot_min; - // Calculate Design Pressures, in Pa - double ff; - double rho_avg = external_htf_props.dens((T_src_in + T_src_out) / 2, 9 / 1.e-5); - const double P_hi = 17 / 1.e-5; // downstream SF pump pressure [Pa] - const double P_lo = 1 / 1.e-5; // atmospheric pressure [Pa] + // calculate effectiveness + if (CR > 0.999) + { + eff = NTU / (1.0 + NTU); + } + else + { + eff = (1.0 - std::exp(-NTU*(1.0-CR)))/(1.0 - CR*std::exp(-NTU*(1.0-CR))); //[-] + } + + if (std::isnan(eff) || eff <= 0.0 || eff > 1.0) + { + T_f_htf_hx_out = T_s_htf_hx_out = + eff = q_dot_hx = std::numeric_limits::quiet_NaN(); + throw(C_csp_exception("Off design heat exchanger failed", "")); + } - // P_10 - ff = CSP::FrictionFactor(pipe_rough / diams.at(10), external_htf_props.Re(TES_T_des.at(10), P_lo, vel.at(10), diams.at(10))); - TES_P_des.at(10) = 0 + - CSP::MajorPressureDrop(vel.at(10), rho_avg, ff, L.at(10), diams.at(10)) + - CSP::MinorPressureDrop(vel.at(10), rho_avg, k_tes_loss_coeffs.at(10)); + double T_hot_in = std::max(T_f_htf_hx_in, T_s_htf_hx_in); //[K] + double T_cold_in = std::min(T_f_htf_hx_in, T_s_htf_hx_in); //[K] - // P_9 - ff = CSP::FrictionFactor(pipe_rough / diams.at(9), external_htf_props.Re(TES_T_des.at(9), P_lo, vel.at(9), diams.at(9))); - TES_P_des.at(9) = TES_P_des.at(10) + - CSP::MajorPressureDrop(vel.at(9), rho_avg, ff, L.at(9), diams.at(9)) + - CSP::MinorPressureDrop(vel.at(9), rho_avg, k_tes_loss_coeffs.at(9)); + // Calculate heat transfer in HX + double q_dot_max = c_dot_min * (T_hot_in - T_cold_in); + q_dot_hx = eff * q_dot_max; - // P_8 - ff = CSP::FrictionFactor(pipe_rough / diams.at(8), external_htf_props.Re(TES_T_des.at(8), P_hi, vel.at(8), diams.at(8))); - TES_P_des.at(8) = TES_P_des.at(9) + dP_discharge + - CSP::MajorPressureDrop(vel.at(8), rho_avg, ff, L.at(8), diams.at(8)) + - CSP::MinorPressureDrop(vel.at(8), rho_avg, k_tes_loss_coeffs.at(8)); + if (T_s_htf_hx_in > T_f_htf_hx_in) + { + T_s_htf_hx_out = T_s_htf_hx_in - q_dot_hx / c_dot_s; + T_f_htf_hx_out = T_f_htf_hx_in + q_dot_hx / c_dot_f; + } + else + { + T_s_htf_hx_out = T_s_htf_hx_in + q_dot_hx / c_dot_s; + T_f_htf_hx_out = T_f_htf_hx_in - q_dot_hx / c_dot_f; + } - if (tanks_in_parallel) { - TES_P_des.at(7) = 0; - TES_P_des.at(6) = 0; - TES_P_des.at(5) = 0; - } - else { - // P_7 - ff = CSP::FrictionFactor(pipe_rough / diams.at(7), external_htf_props.Re(TES_T_des.at(7), P_hi, vel.at(7), diams.at(7))); - TES_P_des.at(7) = TES_P_des.at(8) + - CSP::MajorPressureDrop(vel.at(7), rho_avg, ff, L.at(7), diams.at(7)) + - CSP::MinorPressureDrop(vel.at(7), rho_avg, k_tes_loss_coeffs.at(7)); + q_dot_hx *= 1.E-6; //[MWt] convert from W +} - // P_6 - ff = CSP::FrictionFactor(pipe_rough / diams.at(6), external_htf_props.Re(TES_T_des.at(6), P_hi, vel.at(6), diams.at(6))); - TES_P_des.at(6) = TES_P_des.at(7) + - CSP::MajorPressureDrop(vel.at(6), rho_avg, ff, L.at(6), diams.at(6)) + - CSP::MinorPressureDrop(vel.at(6), rho_avg, k_tes_loss_coeffs.at(6)); - // P_5 - TES_P_des.at(5) = 0; - } - // P_3 - ff = CSP::FrictionFactor(pipe_rough / diams.at(3), external_htf_props.Re(TES_T_des.at(3), P_lo, vel.at(3), diams.at(3))); - TES_P_des.at(3) = 0 + - CSP::MajorPressureDrop(vel.at(3), rho_avg, ff, L.at(3), diams.at(3)) + - CSP::MinorPressureDrop(vel.at(3), rho_avg, k_tes_loss_coeffs.at(3)); +static C_csp_reported_outputs::S_output_info S_output_info[] = +{ + {C_csp_two_tank_tes::E_Q_DOT_LOSS, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] TES thermal losses + {C_csp_two_tank_tes::E_W_DOT_HEATER, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWe] TES freeze protection power + {C_csp_two_tank_tes::E_TES_T_HOT, C_csp_reported_outputs::TS_LAST}, //[C] TES final hot tank temperature + {C_csp_two_tank_tes::E_TES_T_COLD, C_csp_reported_outputs::TS_LAST}, //[C] TES cold temperature at end of timestep + {C_csp_two_tank_tes::E_M_DOT_TANK_TO_TANK, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] TES thermal losses + {C_csp_two_tank_tes::E_MASS_COLD_TANK, C_csp_reported_outputs::TS_LAST}, //[kg] Mass in cold tank at end of timestep + {C_csp_two_tank_tes::E_MASS_HOT_TANK, C_csp_reported_outputs::TS_LAST}, //[kg] Mass in hot tank at end of timestep + {C_csp_two_tank_tes::E_HOT_TANK_HTF_PERC_FINAL, C_csp_reported_outputs::TS_LAST}, //[%] Final percent fill of available hot tank mass + {C_csp_two_tank_tes::E_W_DOT_HTF_PUMP, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWe] + {C_csp_two_tank_tes::E_VOL_TOT, C_csp_reported_outputs::TS_LAST}, //[MWe] + {C_csp_two_tank_tes::E_MASS_TOT, C_csp_reported_outputs::TS_LAST}, //[kg] - // P_4 - TES_P_des.at(4) = TES_P_des.at(3); + csp_info_invalid +}; - // P_2 - ff = CSP::FrictionFactor(pipe_rough / diams.at(2), external_htf_props.Re(TES_T_des.at(2), P_hi, vel.at(2), diams.at(2))); - TES_P_des.at(2) = P_src_in + - CSP::MajorPressureDrop(vel.at(2), rho_avg, ff, L.at(2), diams.at(2)) + - CSP::MinorPressureDrop(vel.at(2), rho_avg, k_tes_loss_coeffs.at(2)); +C_csp_two_tank_tes::C_csp_two_tank_tes() +{ + m_vol_tank = m_V_tank_active = m_q_pb_design = m_Q_tes_des = + m_V_tank_hot_ini = m_mass_total_active = m_h_tank_calc = m_d_tank_calc = m_q_dot_loss_des = + m_cp_external_avg = m_rho_store_avg = m_m_dot_tes_des_over_m_dot_external_des = std::numeric_limits::quiet_NaN(); - // P_1 - ff = CSP::FrictionFactor(pipe_rough / diams.at(1), external_htf_props.Re(TES_T_des.at(1), P_hi, vel.at(1), diams.at(1))); - TES_P_des.at(1) = TES_P_des.at(2) + - CSP::MajorPressureDrop(vel.at(1), rho_avg, ff, L.at(1), diams.at(1)) + - CSP::MinorPressureDrop(vel.at(1), rho_avg, k_tes_loss_coeffs.at(1)); + mc_reported_outputs.construct(S_output_info); +} - // P_0 - TES_P_des.at(0) = 0; +C_csp_two_tank_tes::C_csp_two_tank_tes( + int external_fl, // [-] external fluid identifier + util::matrix_t external_fl_props, // [-] external fluid properties + int tes_fl, // [-] tes fluid identifier + util::matrix_t tes_fl_props, // [-] tes fluid properties + double q_dot_design, // [MWt] Design heat rate in and out of tes + double frac_max_q_dot, // [-] the max design heat rate as a fraction of the nominal + double Q_tes_des, // [MWt-hr] design storage capacity + bool is_h_fixed, // [] [true] Height is input, calculate diameter, [false] diameter input, calculate height + double h_tank_in, // [m] tank height input + double d_tank_in, // [m] tank diameter input + double u_tank, // [W/m^2-K] + int tank_pairs, // [-] + double hot_tank_Thtr, // [C] convert to K in init() + double hot_tank_max_heat, // [MW] + double cold_tank_Thtr, // [C] convert to K in init() + double cold_tank_max_heat, // [MW] + double dt_hot, // [C] Temperature difference across heat exchanger - assume hot and cold deltaTs are equal + double T_cold_des, // [C] convert to K in init() + double T_hot_des, // [C] convert to K in init() + double T_tank_hot_ini, // [C] Initial temperature in hot storage tank + double T_tank_cold_ini, // [C] Initial temperature in cold storage cold + double h_tank_min, // [m] Minimum allowable HTF height in storage tank + double f_V_hot_ini, // [%] Initial fraction of available volume that is hot + double htf_pump_coef, // [kW/kg/s] Pumping power to move 1 kg/s of HTF through sink + bool tanks_in_parallel, // [-] Whether the tanks are in series or parallel with the external system. Series means external htf must go through storage tanks. + double V_tes_des, // [m/s] Design-point velocity for sizing the diameters of the TES piping + bool calc_design_pipe_vals, // [-] Should the HTF state be calculated at design conditions + double tes_pump_coef, // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop + double eta_pump, // [-] Pump efficiency, for newer pumping calculations + bool has_hot_tank_bypass, // [-] True if the bypass valve causes the source htf to bypass just the hot tank and enter the cold tank before flowing back to the external system. + double T_tank_hot_inlet_min, // [C] Minimum source htf temperature that may enter the hot tank + bool custom_tes_p_loss, // [-] True if the TES piping losses should be calculated using the TES pipe lengths and minor loss coeffs, false if using the pumping loss parameters + bool custom_tes_pipe_sizes, // [-] True if the TES diameters and wall thicknesses parameters should be used instead of calculating them + util::matrix_t k_tes_loss_coeffs, // [-] Combined minor loss coefficients of the fittings and valves in the collection (including bypass) and generation loops in the TES + util::matrix_t tes_diams, // [m] Imported inner diameters for the TES piping as read from the modified output files + util::matrix_t tes_wallthicks, // [m] Imported wall thicknesses for the TES piping as read from the modified output files + util::matrix_t tes_lengths, // [m] Imported lengths for the TES piping as read from the modified output files + double pipe_rough, // [m] Pipe absolute roughness + double dP_discharge // [bar] Pressure drop on the TES discharge side (e.g., within the steam generator) + ) + : + m_external_fl(external_fl), m_external_fl_props(external_fl_props), m_tes_fl(tes_fl), m_tes_fl_props(tes_fl_props), + m_q_dot_design(q_dot_design), m_frac_max_q_dot(frac_max_q_dot), m_Q_tes_des(Q_tes_des), + m_is_h_fixed(is_h_fixed), m_h_tank_in(h_tank_in), m_d_tank_in(d_tank_in), + m_u_tank(u_tank), m_tank_pairs(tank_pairs), m_hot_tank_Thtr(hot_tank_Thtr), m_hot_tank_max_heat(hot_tank_max_heat), + m_cold_tank_Thtr(cold_tank_Thtr), m_cold_tank_max_heat(cold_tank_max_heat), m_dt_hot(dt_hot), m_T_cold_des(T_cold_des), + m_T_hot_des(T_hot_des), m_T_tank_hot_ini(T_tank_hot_ini), m_T_tank_cold_ini(T_tank_cold_ini), + m_h_tank_min(h_tank_min), m_f_V_hot_ini(f_V_hot_ini), m_htf_pump_coef(htf_pump_coef), tanks_in_parallel(tanks_in_parallel), + V_tes_des(V_tes_des), calc_design_pipe_vals(calc_design_pipe_vals), m_tes_pump_coef(tes_pump_coef), + eta_pump(eta_pump), has_hot_tank_bypass(has_hot_tank_bypass), T_tank_hot_inlet_min(T_tank_hot_inlet_min), + custom_tes_p_loss(custom_tes_p_loss), custom_tes_pipe_sizes(custom_tes_pipe_sizes), k_tes_loss_coeffs(k_tes_loss_coeffs), + tes_diams(tes_diams), tes_wallthicks(tes_wallthicks), tes_lengths(tes_lengths), + pipe_rough(pipe_rough), dP_discharge(dP_discharge) +{ - // Convert Pa to bar - for (int i = 0; i < nPipes; i++) { - TES_P_des.at(i) = TES_P_des.at(i) / 1.e5; + if (tes_lengths.ncells() < 11) { + double lengths[11] = { 0., 90., 100., 120., 0., 30., 90., 80., 80., 120., 80. }; + this->tes_lengths.assign(lengths, 11); } - TES_P_in = TES_P_des.at(3); // pressure at the inlet to the TES, at the source side - - return 0; -} -C_csp_cold_tes::C_csp_cold_tes() -{ - m_vol_tank = m_V_tank_active = m_q_pb_design = m_V_tank_hot_ini = std::numeric_limits::quiet_NaN(); + m_vol_tank = m_V_tank_active = m_q_pb_design = m_ts_hours = + m_V_tank_hot_ini = m_mass_total_active = m_h_tank_calc = m_d_tank_calc = m_q_dot_loss_des = + m_cp_external_avg = m_rho_store_avg = m_m_dot_tes_des_over_m_dot_external_des = std::numeric_limits::quiet_NaN(); - m_m_dot_tes_dc_max = m_m_dot_tes_ch_max = std::numeric_limits::quiet_NaN(); + mc_reported_outputs.construct(S_output_info); } -void C_csp_cold_tes::init(const C_csp_cold_tes::S_csp_cold_tes_init_inputs init_inputs) +void C_csp_two_tank_tes::init(const C_csp_tes::S_csp_tes_init_inputs init_inputs) { - if (!(ms_params.m_ts_hours > 0.0)) + if( !(m_Q_tes_des > 0.0) ) { m_is_tes = false; return; // No storage! @@ -2026,55 +1430,55 @@ void C_csp_cold_tes::init(const C_csp_cold_tes::S_csp_cold_tes_init_inputs init_ m_is_tes = true; - // Declare instance of fluid class for FIELD fluid + // Declare instance of fluid class for EXTERNAL fluid // Set fluid number and copy over fluid matrix if it makes sense - if (ms_params.m_field_fl != HTFProperties::User_defined && ms_params.m_field_fl < HTFProperties::End_Library_Fluids) + if( m_external_fl != HTFProperties::User_defined && m_external_fl < HTFProperties::End_Library_Fluids ) { - if (!mc_field_htfProps.SetFluid(ms_params.m_field_fl)) + if( !mc_external_htfProps.SetFluid(m_external_fl) ) { - throw(C_csp_exception("Field HTF code is not recognized", "Two Tank TES Initialization")); + throw(C_csp_exception("External HTF code is not recognized", "Two Tank TES Initialization")); } } - else if (ms_params.m_field_fl == HTFProperties::User_defined) + else if( m_external_fl == HTFProperties::User_defined ) { - int n_rows = (int)ms_params.m_field_fl_props.nrows(); - int n_cols = (int)ms_params.m_field_fl_props.ncols(); - if (n_rows > 2 && n_cols == 7) + int n_rows = (int)m_external_fl_props.nrows(); + int n_cols = (int)m_external_fl_props.ncols(); + if( n_rows > 2 && n_cols == 7 ) { - if (!mc_field_htfProps.SetUserDefinedFluid(ms_params.m_field_fl_props)) + if( !mc_external_htfProps.SetUserDefinedFluid(m_external_fl_props) ) { - error_msg = util::format(mc_field_htfProps.UserFluidErrMessage(), n_rows, n_cols); + error_msg = util::format(mc_external_htfProps.UserFluidErrMessage(), n_rows, n_cols); throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); } } else { - error_msg = util::format("The user defined field HTF 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); + error_msg = util::format("The user defined external HTF 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, "Two Tank TES Initialization")); } } else { - throw(C_csp_exception("Field HTF code is not recognized", "Two Tank TES Initialization")); + throw(C_csp_exception("External HTF code is not recognized", "Two Tank TES Initialization")); } // Declare instance of fluid class for STORAGE fluid. // Set fluid number and copy over fluid matrix if it makes sense. - if (ms_params.m_tes_fl != HTFProperties::User_defined && ms_params.m_tes_fl < HTFProperties::End_Library_Fluids) + if( m_tes_fl != HTFProperties::User_defined && m_tes_fl < HTFProperties::End_Library_Fluids ) { - if (!mc_store_htfProps.SetFluid(ms_params.m_tes_fl)) + if( !mc_store_htfProps.SetFluid(m_tes_fl) ) { throw(C_csp_exception("Storage HTF code is not recognized", "Two Tank TES Initialization")); - } + } } - else if (ms_params.m_tes_fl == HTFProperties::User_defined) + else if( m_tes_fl == HTFProperties::User_defined ) { - int n_rows = (int)ms_params.m_tes_fl_props.nrows(); - int n_cols = (int)ms_params.m_tes_fl_props.ncols(); - if (n_rows > 2 && n_cols == 7) + int n_rows = (int)m_tes_fl_props.nrows(); + int n_cols = (int)m_tes_fl_props.ncols(); + if( n_rows > 2 && n_cols == 7 ) { - if (!mc_store_htfProps.SetUserDefinedFluid(ms_params.m_tes_fl_props)) + if( !mc_store_htfProps.SetUserDefinedFluid(m_tes_fl_props) ) { error_msg = util::format(mc_store_htfProps.UserFluidErrMessage(), n_rows, n_cols); throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); @@ -2093,177 +1497,338 @@ void C_csp_cold_tes::init(const C_csp_cold_tes::S_csp_cold_tes_init_inputs init_ bool is_hx_calc = true; - if (ms_params.m_tes_fl != ms_params.m_field_fl) + if( m_tes_fl != m_external_fl ) is_hx_calc = true; - else if (ms_params.m_field_fl != HTFProperties::User_defined) + else if( m_external_fl != HTFProperties::User_defined ) is_hx_calc = false; else { - is_hx_calc = !mc_field_htfProps.equals(&mc_store_htfProps); + is_hx_calc = !mc_external_htfProps.equals(&mc_store_htfProps); } - if (ms_params.m_is_hx != is_hx_calc) + m_is_hx = is_hx_calc; + + // Added by TB 2023-03-03 + // Need to check if tes_pump_coef is defined for storage with hx + if (m_is_hx) + { + if(std::isnan(this->m_tes_pump_coef)) + throw(C_csp_exception("TES Pump Coef not provided for system with different field and storage fluids.", "Two Tank TES Initialization")); + } + + /* + if( m_is_hx != is_hx_calc ) { - if (is_hx_calc) - mc_csp_messages.add_message(C_csp_messages::NOTICE, "Input field and storage fluids are different, but the inputs did not specify a field-to-storage heat exchanger. The system was modeled assuming a heat exchanger."); + if( is_hx_calc ) + mc_csp_messages.add_message(C_csp_messages::NOTICE, "Input external and storage fluids are different, but the inputs did not specify an external-to-storage heat exchanger. The system was modeled assuming a heat exchanger."); else - mc_csp_messages.add_message(C_csp_messages::NOTICE, "Input field and storage fluids are identical, but the inputs specified a field-to-storage heat exchanger. The system was modeled assuming no heat exchanger."); + mc_csp_messages.add_message(C_csp_messages::NOTICE, "Input external and storage fluids are identical, but the inputs specified an external-to-storage heat exchanger. The system was modeled assuming no heat exchanger."); - ms_params.m_is_hx = is_hx_calc; - } + m_is_hx = is_hx_calc; + }*/ + + if (m_is_hx && !tanks_in_parallel) + { + mc_csp_messages.add_message(C_csp_messages::NOTICE, "The inputs specified serial TES operation, but the external and storage fluids are different." + " The simulation modeled parallel TES operation.\n"); + tanks_in_parallel = true; + } + + if (tanks_in_parallel) { + m_is_cr_to_cold_tank_allowed = false; + } + else { + m_is_cr_to_cold_tank_allowed = true; + } // Calculate thermal power to PC at design - m_q_pb_design = ms_params.m_W_dot_pc_design / ms_params.m_eta_pc_factor*1.E6; //[Wt] - using pc efficiency factor for cold storage ARD + m_q_pb_design = m_q_dot_design * 1.E6; //[Wt] - // Convert parameter units - ms_params.m_hot_tank_Thtr += 273.15; //[K] convert from C - ms_params.m_cold_tank_Thtr += 273.15; //[K] convert from C - ms_params.m_T_cold_des += 273.15; //[K] convert from C - ms_params.m_T_hot_des += 273.15; //[K] convert from C - ms_params.m_T_tank_hot_ini += 273.15; //[K] convert from C - ms_params.m_T_tank_cold_ini += 273.15; //[K] convert from C + // Convert parameter units + m_hot_tank_Thtr += 273.15; //[K] convert from C + m_cold_tank_Thtr += 273.15; //[K] convert from C + m_T_cold_des += 273.15; //[K] convert from C + m_T_hot_des += 273.15; //[K] convert from C + m_T_tank_hot_ini += 273.15; //[K] convert from C + m_T_tank_cold_ini += 273.15; //[K] convert from C - double Q_tes_des = m_q_pb_design / 1.E6 * ms_params.m_ts_hours; //[MWt-hr] TES thermal capacity at design + m_ts_hours = m_Q_tes_des / m_q_dot_design; double d_tank_temp = std::numeric_limits::quiet_NaN(); double q_dot_loss_temp = std::numeric_limits::quiet_NaN(); - two_tank_tes_sizing(mc_store_htfProps, Q_tes_des, ms_params.m_T_hot_des, ms_params.m_T_cold_des, - ms_params.m_h_tank_min, ms_params.m_h_tank, ms_params.m_tank_pairs, ms_params.m_u_tank, - m_V_tank_active, m_vol_tank, d_tank_temp, q_dot_loss_temp); + double T_tes_hot_des, T_tes_cold_des; + if (m_is_hx) { + T_tes_hot_des = m_T_hot_des - m_dt_hot; + T_tes_cold_des = m_T_cold_des + m_dt_hot; + } + else { + T_tes_hot_des = m_T_hot_des; + T_tes_cold_des = m_T_cold_des; + } + + // Size Tank with Fixed Height + if (m_is_h_fixed) + { + two_tank_tes_sizing(mc_store_htfProps, m_Q_tes_des, T_tes_hot_des, T_tes_cold_des, + m_h_tank_min, m_h_tank_in, m_tank_pairs, m_u_tank, + m_V_tank_active, m_vol_tank, m_d_tank_calc, m_q_dot_loss_des); + m_h_tank_calc = m_h_tank_in; + } + // Size Tank with Fixed Diameter + else + { + two_tank_tes_sizing_fixed_diameter(mc_store_htfProps, m_Q_tes_des, T_tes_hot_des, T_tes_cold_des, + m_h_tank_min, m_d_tank_in, m_tank_pairs, m_u_tank, + m_V_tank_active, m_vol_tank, m_h_tank_calc, m_q_dot_loss_des); + m_d_tank_calc = m_d_tank_in; + } + - // 5.13.15, twn: also be sure that hx is sized such that it can supply full load to power cycle, in cases of low solar multiples - double duty = m_q_pb_design * std::max(1.0, ms_params.m_solarm); //[W] Allow all energy from the field to go into storage at any time + // 5.13.15, twn: also be sure that hx is sized such that it can supply full load to sink + double duty = m_q_pb_design * std::max(1.0, m_frac_max_q_dot); //[W] Allow all energy from the source to go into storage at any time - if (ms_params.m_ts_hours > 0.0) + if( m_ts_hours > 0.0 ) { - mc_hx.init(mc_field_htfProps, mc_store_htfProps, duty, ms_params.m_dt_hot, ms_params.m_T_hot_des, ms_params.m_T_cold_des); + mc_hx.init(mc_external_htfProps, mc_store_htfProps, duty, m_dt_hot, m_T_hot_des, m_T_cold_des); } // Do we need to define minimum and maximum thermal powers to/from storage? - // The 'duty' definition should allow the tanks to accept whatever the field and/or power cycle can provide... + // The 'duty' definition should allow the tanks to accept whatever the source and/or sink can provide... // Calculate initial storage values + + // Initial storage charge based on % mass + double T_tes_ave = 0.5*(T_tes_hot_des + T_tes_cold_des); + double cp_ave = mc_store_htfProps.Cp_ave(T_tes_cold_des, T_tes_hot_des); //[kJ/kg-K] Specific heat at average temperature + m_rho_store_avg = mc_store_htfProps.dens(T_tes_ave, 1.0); + m_mass_total_active = m_Q_tes_des*3600.0 / (cp_ave / 1000.0 * (T_tes_hot_des - T_tes_cold_des)); //[kg] Total HTF mass at design point inlet/outlet T double V_inactive = m_vol_tank - m_V_tank_active; - double V_hot_ini = ms_params.m_f_V_hot_ini*0.01*m_V_tank_active + V_inactive; //[m^3] - double V_cold_ini = (1.0 - ms_params.m_f_V_hot_ini*0.01)*m_V_tank_active + V_inactive; //[m^3] - double T_hot_ini = ms_params.m_T_tank_hot_ini; //[K] - double T_cold_ini = ms_params.m_T_tank_cold_ini; //[K] + double rho_hot_des = mc_store_htfProps.dens(T_tes_hot_des, 1.0); + double rho_cold_des = mc_store_htfProps.dens(T_tes_cold_des, 1.0); + double rho_hot = mc_store_htfProps.dens(m_T_tank_hot_ini, 1.0); + double rho_cold = mc_store_htfProps.dens(m_T_tank_cold_ini, 1.0); + double m_hot_ini = m_f_V_hot_ini * 0.01 * m_mass_total_active + V_inactive * rho_hot_des; // Updating intiial storage charge calculation to avoid variation in total mass with specified initial T + double m_cold_ini = (1.0 - m_f_V_hot_ini * 0.01) * m_mass_total_active + V_inactive * rho_cold_des; + double V_hot_ini = m_hot_ini / rho_hot; + double V_cold_ini = m_cold_ini / rho_cold; + + //double rho_hot = mc_store_htfProps.dens(m_T_tank_hot_ini, 1.0); + //double rho_cold = mc_store_htfProps.dens(m_T_tank_cold_ini, 1.0); - // Initialize cold and hot tanks - // Hot tank - mc_hot_tank.init(mc_store_htfProps, m_vol_tank, ms_params.m_h_tank, ms_params.m_h_tank_min, - ms_params.m_u_tank, ms_params.m_tank_pairs, ms_params.m_hot_tank_Thtr, ms_params.m_hot_tank_max_heat, - V_hot_ini, T_hot_ini, ms_params.m_T_hot_des); - // Cold tank - mc_cold_tank.init(mc_store_htfProps, m_vol_tank, ms_params.m_h_tank, ms_params.m_h_tank_min, - ms_params.m_u_tank, ms_params.m_tank_pairs, ms_params.m_cold_tank_Thtr, ms_params.m_cold_tank_max_heat, - V_cold_ini, T_cold_ini, ms_params.m_T_cold_des); + //double V_inactive = m_vol_tank - m_V_tank_active; + //double V_hot_ini = m_f_V_hot_ini*0.01*m_mass_total_active / rho_hot + V_inactive; //[m^3] + //double V_cold_ini = (1.0 - m_f_V_hot_ini*0.01)*m_mass_total_active / rho_cold + V_inactive; //[m^3] + + // Initial storage charge based on % volume + //double V_inactive = m_vol_tank - m_V_tank_active; + //double V_hot_ini = m_f_V_hot_ini*0.01*m_V_tank_active + V_inactive; //[m^3] + //double V_cold_ini = (1.0 - m_f_V_hot_ini*0.01)*m_V_tank_active + V_inactive; //[m^3] + + double T_hot_ini = m_T_tank_hot_ini; //[K] + double T_cold_ini = m_T_tank_cold_ini; //[K] + + // Initialize cold and hot tanks + // Hot tank + mc_hot_tank.init(mc_store_htfProps, m_vol_tank, m_h_tank_calc, m_h_tank_min, + m_u_tank, m_tank_pairs, m_hot_tank_Thtr, m_hot_tank_max_heat, + V_hot_ini, T_hot_ini, T_tes_hot_des); + // Cold tank + mc_cold_tank.init(mc_store_htfProps, m_vol_tank, m_h_tank_calc, m_h_tank_min, + m_u_tank, m_tank_pairs, m_cold_tank_Thtr, m_cold_tank_max_heat, + V_cold_ini, T_cold_ini, T_tes_cold_des); + + if (custom_tes_pipe_sizes && + (tes_diams.ncells() != N_tes_pipe_sections || + tes_wallthicks.ncells() != N_tes_pipe_sections)) { + error_msg = "The number of custom TES pipe sections is not correct."; + throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); + } + double rho_avg = mc_external_htfProps.dens((m_T_cold_des + m_T_hot_des) / 2, 9 / 1.e-5); + m_cp_external_avg = mc_external_htfProps.Cp_ave(m_T_cold_des, m_T_hot_des); + double cp_tes_avg = mc_store_htfProps.Cp_ave(T_tes_hot_des, T_tes_cold_des); + m_m_dot_tes_des_over_m_dot_external_des = m_cp_external_avg / cp_tes_avg; //[-] assume hx cr = 1 + double m_dot_pb_design = m_q_dot_design * 1.e3 / // convert MWe to kWe for cp [kJ/kg-K] + (m_cp_external_avg * (m_T_hot_des - m_T_cold_des)); + if (size_tes_piping(V_tes_des, tes_lengths, rho_avg, + m_dot_pb_design, m_frac_max_q_dot, tanks_in_parallel, // Inputs + this->pipe_vol_tot, this->pipe_v_dot_rel, this->pipe_diams, + this->pipe_wall_thk, this->pipe_m_dot_des, this->pipe_vel_des, // Outputs + custom_tes_pipe_sizes)) { + + error_msg = "TES piping sizing failed"; + throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); + } + this->pipe_lengths = tes_lengths; + + if (calc_design_pipe_vals) { + if (size_tes_piping_TandP(mc_external_htfProps, init_inputs.T_to_cr_at_des, init_inputs.T_from_cr_at_des, + init_inputs.P_to_cr_at_des * 1.e5, dP_discharge * 1.e5, // bar to Pa + tes_lengths, k_tes_loss_coeffs, pipe_rough, tanks_in_parallel, + this->pipe_diams, this->pipe_vel_des, + this->pipe_T_des, this->pipe_P_des, + this->P_in_des)) { // Outputs + + error_msg = "TES piping design temperature and pressure calculation failed"; + throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); + } + // Adjust first two pressures after source pumps, because the source inlet pressure used above was + // not yet corrected for the section in the TES/PB before the hot tank + double DP_before_hot_tank = this->pipe_P_des.at(3); // first section before hot tank + this->pipe_P_des.at(1) += DP_before_hot_tank; + this->pipe_P_des.at(2) += DP_before_hot_tank; + + //value(O_p_des_sgs_1, DP_before_hot_tank); // for adjusting source design pressures + } +} +void C_csp_two_tank_tes::get_design_parameters(double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, + double& h_tank_calc /*m*/, double& d_tank_calc /*m*/, + double& q_dot_loss_des /*MWt*/, double& dens_store_htf_at_T_ave /*kg/m3*/, double& Q_tes /*MWt-hr*/) +{ + vol_one_temp_avail = m_V_tank_active; //[m3] + vol_one_temp_total = m_vol_tank; //[m3] + h_tank_calc = m_h_tank_calc; //[m] + d_tank_calc = m_d_tank_calc; //[m] + q_dot_loss_des = m_q_dot_loss_des; //[MWt] + dens_store_htf_at_T_ave = m_rho_store_avg; //[kg/m3] + Q_tes = m_Q_tes_des; //[MWt-hr] } -bool C_csp_cold_tes::does_tes_exist() +bool C_csp_two_tank_tes::does_tes_exist() { return m_is_tes; } -double C_csp_cold_tes::get_hot_temp() +bool C_csp_two_tank_tes::is_cr_to_cold_allowed() +{ + return m_is_cr_to_cold_tank_allowed; +} + +double C_csp_two_tank_tes::get_hot_temp() { return mc_hot_tank.get_m_T_prev(); //[K] } -double C_csp_cold_tes::get_cold_temp() +double C_csp_two_tank_tes::get_cold_temp() { return mc_cold_tank.get_m_T_prev(); //[K] } - -double C_csp_cold_tes::get_hot_mass() +double C_csp_two_tank_tes::get_hot_tank_vol_frac() { - return mc_hot_tank.get_m_m_calc(); // [kg] + return mc_hot_tank.get_vol_frac(); } -double C_csp_cold_tes::get_cold_mass() + + +double C_csp_two_tank_tes::get_initial_charge_energy() { - return mc_cold_tank.get_m_m_calc(); //[kg] + //MWh + if (std::isnan(m_V_tank_hot_ini)) + { + return m_q_pb_design * m_ts_hours * (m_f_V_hot_ini / 100.0) * 1.e-6; + } + else + { + //TODO: m_V_tank_hot_ini does not get initialized to user value... + return m_q_pb_design * m_ts_hours * m_V_tank_hot_ini / m_vol_tank * 1.e-6; + } } -double C_csp_cold_tes::get_hot_mass_prev() +double C_csp_two_tank_tes::get_min_charge_energy() { - return mc_hot_tank.calc_mass_at_prev(); // [kg] + //MWh + return 0.; //m_q_pb_design * m_ts_hours * m_h_tank_min / m_h_tank*1.e-6; } -double C_csp_cold_tes::get_cold_mass_prev() +double C_csp_two_tank_tes::get_max_charge_energy() { - return mc_cold_tank.calc_mass_at_prev(); //[kg] -} + //MWh + //double cp = mc_store_htfProps.Cp(m_T_hot_des); //[kJ/kg-K] spec heat at average temperature during discharge from hot to cold + // double rho = mc_store_htfProps.dens(m_T_hot_des, 1.); -double C_csp_cold_tes::get_physical_volume() + // double fadj = (1. - m_h_tank_min / m_h_tank); -{ - return m_vol_tank; //[m^3] + // double vol_avail = m_vol_tank * m_tank_pairs * fadj; + + // double e_max = vol_avail * rho * cp * (m_T_hot_des - m_T_cold_des) / 3.6e6; //MW-hr + + // return e_max; + return m_q_pb_design * m_ts_hours / 1.e6; } -double C_csp_cold_tes::get_hot_massflow_avail(double step_s) //[kg/sec] +double C_csp_two_tank_tes::get_degradation_rate() { - return mc_hot_tank.m_dot_available(0, step_s); + //calculates an approximate "average" tank heat loss rate based on some assumptions. Good for simple optimization performance projections. + double d_tank = sqrt( m_vol_tank / ( (double)m_tank_pairs * m_h_tank_calc * 3.14159) ); + double e_loss = m_u_tank * 3.14159 * m_tank_pairs * d_tank * ( m_T_cold_des + m_T_hot_des - 576.3 )*1.e-6; //MJ/s -- assumes full area for loss, Tamb = 15C + return e_loss / (m_q_pb_design * m_ts_hours * 3600.); //s^-1 -- fraction of heat loss per second based on full charge } -double C_csp_cold_tes::get_cold_massflow_avail(double step_s) //[kg/sec] +void C_csp_two_tank_tes::reset_storage_to_initial_state() { - return mc_cold_tank.m_dot_available(0, step_s); -} + // Initial storage charge based on % mass + double Q_tes_des = m_q_pb_design / 1.E6 * m_ts_hours; //[MWt-hr] TES thermal capacity at design + double cp_ave = mc_store_htfProps.Cp_ave(m_T_cold_des, m_T_hot_des); //[kJ/kg-K] Specific heat at average temperature + double mtot = Q_tes_des*3600.0 / (cp_ave / 1000.0 * (m_T_hot_des - m_T_cold_des)); //[kg] Total HTF mass + double rho_hot = mc_store_htfProps.dens(m_T_hot_des, 1.0); + double rho_cold = mc_store_htfProps.dens(m_T_cold_des, 1.0); + double V_inactive = m_vol_tank - m_V_tank_active; + double V_hot_ini = m_f_V_hot_ini*0.01*mtot / rho_hot + V_inactive; //[m^3] + double V_cold_ini = (1.0 - m_f_V_hot_ini*0.01)*mtot / rho_cold + V_inactive; //[m^3] -double C_csp_cold_tes::get_initial_charge_energy() -{ - //MWh - if (std::isnan(m_V_tank_hot_ini)) - { - return m_q_pb_design * ms_params.m_ts_hours * (ms_params.m_f_V_hot_ini / 100.0) * 1.e-6; - } - else - { - return m_q_pb_design * ms_params.m_ts_hours * m_V_tank_hot_ini / m_vol_tank * 1.e-6; - } -} + double T_hot_ini = m_T_tank_hot_ini; //[K] + double T_cold_ini = m_T_tank_cold_ini; //[K] -double C_csp_cold_tes::get_min_charge_energy() -{ - //MWh - return 0.; //ms_params.m_q_pb_design * ms_params.m_ts_hours * ms_params.m_h_tank_min / ms_params.m_h_tank*1.e-6; + // Initialize cold and hot tanks + // Hot tank + mc_hot_tank.init(mc_store_htfProps, m_vol_tank, m_h_tank_calc, m_h_tank_min, + m_u_tank, m_tank_pairs, m_hot_tank_Thtr, m_hot_tank_max_heat, + V_hot_ini, T_hot_ini, m_T_hot_des); + // Cold tank + mc_cold_tank.init(mc_store_htfProps, m_vol_tank, m_h_tank_calc, m_h_tank_min, + m_u_tank, m_tank_pairs, m_cold_tank_Thtr, m_cold_tank_max_heat, + V_cold_ini, T_cold_ini, m_T_cold_des); } -double C_csp_cold_tes::get_max_charge_energy() +double C_csp_two_tank_tes::get_tes_m_dot(double m_dot_external /*kg/s*/) { - //MWh - return m_q_pb_design * ms_params.m_ts_hours / 1.e6; + return m_dot_external * m_m_dot_tes_des_over_m_dot_external_des; } -double C_csp_cold_tes::get_degradation_rate() +double C_csp_two_tank_tes::get_external_m_dot(double m_dot_tes /*kg/s*/) { - //calculates an approximate "average" tank heat loss rate based on some assumptions. Good for simple optimization performance projections. - double d_tank = sqrt(m_vol_tank / ((double)ms_params.m_tank_pairs * ms_params.m_h_tank * 3.14159)); - double e_loss = ms_params.m_u_tank * 3.14159 * ms_params.m_tank_pairs * d_tank * (ms_params.m_T_cold_des + ms_params.m_T_hot_des - 576.3)*1.e-6; //MJ/s -- assumes full area for loss, Tamb = 15C - return e_loss / (m_q_pb_design * ms_params.m_ts_hours * 3600.); //s^-1 -- fraction of heat loss per second based on full charge + return m_dot_tes / m_m_dot_tes_des_over_m_dot_external_des; } -void C_csp_cold_tes::reset_storage_to_initial_state() {} - -void C_csp_cold_tes::discharge_avail_est(double T_cold_K, double step_s, double &q_dot_dc_est, double &m_dot_field_est, double &T_hot_field_est) +void C_csp_two_tank_tes::discharge_avail_est(double T_cold_K, double step_s, + double &q_dot_dc_est /*MWt*/, double &m_dot_external_est /*kg/s*/, double &T_hot_external_est /*K*/) { double f_storage = 0.0; // for now, hardcode such that storage always completely discharges double m_dot_tank_disch_avail = mc_hot_tank.m_dot_available(f_storage, step_s); //[kg/s] + if (m_dot_tank_disch_avail == 0) { + q_dot_dc_est = 0.; + m_dot_external_est = 0.; + T_hot_external_est = std::numeric_limits::quiet_NaN(); + return; + } + double T_hot_ini = mc_hot_tank.get_m_T_prev(); //[K] - if (ms_params.m_is_hx) + if(m_is_hx) { - double eff, T_cold_tes; - eff = T_cold_tes = std::numeric_limits::quiet_NaN(); - mc_hx.hx_discharge_mdot_tes(T_hot_ini, m_dot_tank_disch_avail, T_cold_K, eff, T_cold_tes, T_hot_field_est, q_dot_dc_est, m_dot_field_est); + m_dot_external_est = get_external_m_dot(m_dot_tank_disch_avail); //[kg/s] + + double T_cold_tes, eff; + T_cold_tes = eff = std::numeric_limits::quiet_NaN(); + mc_hx.solve(T_cold_K, m_dot_external_est, + T_hot_ini, m_dot_tank_disch_avail, + T_hot_external_est, T_cold_tes, eff, q_dot_dc_est); // If above method fails, it will throw an exception, so if we don't want to break here, need to catch and handle it } @@ -2271,14 +1836,13 @@ void C_csp_cold_tes::discharge_avail_est(double T_cold_K, double step_s, double { double cp_T_avg = mc_store_htfProps.Cp_ave(T_cold_K, T_hot_ini); //[kJ/kg-K] spec heat at average temperature during discharge from hot to cold q_dot_dc_est = m_dot_tank_disch_avail * cp_T_avg * (T_hot_ini - T_cold_K)*1.E-3; //[MW] - m_dot_field_est = m_dot_tank_disch_avail; - T_hot_field_est = T_hot_ini; + m_dot_external_est = m_dot_tank_disch_avail; + T_hot_external_est = T_hot_ini; } - - m_m_dot_tes_dc_max = m_dot_tank_disch_avail * step_s; //[kg/s] } -void C_csp_cold_tes::charge_avail_est(double T_hot_K, double step_s, double &q_dot_ch_est, double &m_dot_field_est, double &T_cold_field_est) +void C_csp_two_tank_tes::charge_avail_est(double T_hot_K, double step_s, + double &q_dot_ch_est /*MWt*/, double &m_dot_external_est /*kg/s*/, double &T_cold_external_est /*K*/) { double f_ch_storage = 0.0; // for now, hardcode such that storage always completely charges @@ -2286,11 +1850,21 @@ void C_csp_cold_tes::charge_avail_est(double T_hot_K, double step_s, double &q_d double T_cold_ini = mc_cold_tank.get_m_T_prev(); //[K] - if (ms_params.m_is_hx) + // for debugging + double T_hot_ini = mc_hot_tank.get_m_T_prev(); //[K] + double cp_T_tanks_avg = mc_store_htfProps.Cp_ave(T_cold_ini, T_hot_ini); // [kJ/kg-K] + double mass_avail_hot_tank = mc_hot_tank.m_dot_available(f_ch_storage, step_s) * step_s; //[kg] + double tes_charge_state = mass_avail_hot_tank * cp_T_tanks_avg * (T_hot_ini - T_cold_ini) * 1.e-3 / 3600.; // [MWht] + + if(m_is_hx) { + m_dot_external_est = get_external_m_dot(m_dot_tank_charge_avail); //[kg/s] + double eff, T_hot_tes; eff = T_hot_tes = std::numeric_limits::quiet_NaN(); - mc_hx.hx_charge_mdot_tes(T_cold_ini, m_dot_tank_charge_avail, T_hot_K, eff, T_hot_tes, T_cold_field_est, q_dot_ch_est, m_dot_field_est); + mc_hx.solve(T_hot_K, m_dot_external_est, + T_cold_ini, m_dot_tank_charge_avail, + T_cold_external_est, T_hot_tes, eff, q_dot_ch_est); // If above method fails, it will throw an exception, so if we don't want to break here, need to catch and handle it } @@ -2298,414 +1872,660 @@ void C_csp_cold_tes::charge_avail_est(double T_hot_K, double step_s, double &q_d { double cp_T_avg = mc_store_htfProps.Cp_ave(T_cold_ini, T_hot_K); //[kJ/kg-K] spec heat at average temperature during charging from cold to hot q_dot_ch_est = m_dot_tank_charge_avail * cp_T_avg * (T_hot_K - T_cold_ini) *1.E-3; //[MW] - m_dot_field_est = m_dot_tank_charge_avail; - T_cold_field_est = T_cold_ini; + m_dot_external_est = m_dot_tank_charge_avail; //[kg/s] + T_cold_external_est = T_cold_ini; //[K] } - - m_m_dot_tes_ch_max = m_dot_tank_charge_avail * step_s; //[kg/s] } -void C_csp_cold_tes::discharge_full(double timestep /*s*/, double T_amb /*K*/, double T_htf_cold_in /*K*/, - double & T_htf_hot_out /*K*/, double & m_dot_htf_out /*kg/s*/, S_csp_cold_tes_outputs &outputs) +int C_csp_two_tank_tes::solve_tes_off_design(double timestep /*s*/, double T_amb /*K*/, + double m_dot_cr_to_cv_hot /*kg/s*/, double m_dot_cv_hot_to_sink /*kg/s*/, double m_dot_cr_to_cv_cold /*kg/s*/, + double T_cr_out_hot /*K*/, double T_sink_out_cold /*K*/, + double& T_sink_htf_in_hot /*K*/, double& T_cr_in_cold /*K*/, + C_csp_tes::S_csp_tes_outputs& s_outputs) //, C_csp_solver_htf_state & s_tes_ch_htf, C_csp_solver_htf_state & s_tes_dc_htf) { - // This method calculates the hot discharge temperature on the HX side (if applicable) during FULL DISCHARGE. If no heat exchanger (direct storage), - // the discharge temperature is equal to the average (timestep) hot tank outlet temperature + // Enthalpy balance on inlet to cold cv + double T_htf_cold_cv_in = T_sink_out_cold; //[K] + double m_dot_total_to_cv_cold = m_dot_cv_hot_to_sink + m_dot_cr_to_cv_cold; //[kg/s] + if (m_dot_total_to_cv_cold > 0.0) { + T_htf_cold_cv_in = (m_dot_cv_hot_to_sink*T_sink_out_cold + m_dot_cr_to_cv_cold*T_cr_out_hot) / (m_dot_total_to_cv_cold); + } - // Inputs are: - // 2) inlet temperature on the HX side (if applicable). If no heat exchanger, the inlet temperature is the temperature - // of HTF directly entering the cold tank. + // Total mass flow leaving cold tank to cr + // One of the RHS should always be 0... + double m_dot_cv_cold_to_cr = m_dot_cr_to_cv_hot + m_dot_cr_to_cv_cold; - double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, T_cold_ave; - q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_cold_ave = std::numeric_limits::quiet_NaN(); + s_outputs = S_csp_tes_outputs(); - // If no heat exchanger, no iteration is required between the heat exchanger and storage tank models - if (!ms_params.m_is_hx) - { - m_dot_htf_out = m_m_dot_tes_dc_max / timestep; //[kg/s] + double m_dot_cr_to_tes_hot, m_dot_cr_to_tes_cold, m_dot_tes_hot_out, m_dot_pc_to_tes_cold, m_dot_tes_cold_out, m_dot_tes_cold_in; + m_dot_cr_to_tes_hot = m_dot_cr_to_tes_cold = m_dot_tes_hot_out = m_dot_pc_to_tes_cold = m_dot_tes_cold_out = m_dot_tes_cold_in = std::numeric_limits::quiet_NaN(); + double m_dot_src_to_sink, m_dot_sink_to_src; + m_dot_src_to_sink = m_dot_sink_to_src = std::numeric_limits::quiet_NaN(); - // Call energy balance on hot tank discharge to get average outlet temperature over timestep - mc_hot_tank.energy_balance(timestep, 0.0, m_dot_htf_out, 0.0, T_amb, T_htf_hot_out, q_heater_hot, q_dot_loss_hot); + if (tanks_in_parallel) + { + // Receiver bypass is possible in a parallel configuration, + // but need to determine if it actually makes sense and how to model it + if (m_dot_cr_to_cv_cold != 0.0) { + throw(C_csp_exception("Receiver output to cold tank not allowed in parallel TES configuration")); + } + m_dot_cr_to_tes_cold = 0.0; - // Call energy balance on cold tank charge to track tank mass and temperature - mc_cold_tank.energy_balance(timestep, m_dot_htf_out, 0.0, T_htf_cold_in, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold); - } + if (m_dot_cr_to_cv_hot >= m_dot_cv_hot_to_sink) + { + m_dot_cr_to_tes_hot = m_dot_cr_to_cv_hot - m_dot_cv_hot_to_sink; //[kg/s] + m_dot_tes_hot_out = 0.0; //[kg/s] + m_dot_pc_to_tes_cold = 0.0; //[kg/s] + m_dot_tes_cold_out = m_dot_cr_to_tes_hot; //[kg/s] + m_dot_src_to_sink = m_dot_cv_hot_to_sink; //[kg/s] + m_dot_sink_to_src = m_dot_cv_hot_to_sink; //[kg/s] + } + else + { + m_dot_cr_to_tes_hot = 0.0; //[kg/s] + m_dot_tes_hot_out = m_dot_cv_hot_to_sink - m_dot_cr_to_cv_hot; //[kg/s] + m_dot_pc_to_tes_cold = m_dot_tes_hot_out; //[kg/s] + m_dot_tes_cold_out = 0.0; //[kg/s] + m_dot_src_to_sink = m_dot_cr_to_cv_hot; //[kg/s] + m_dot_sink_to_src = m_dot_cr_to_cv_hot; //[kg/s] + } + m_dot_tes_cold_in = m_dot_pc_to_tes_cold; + } + else + { // Serial configuration + if (m_is_hx) + { + throw(C_csp_exception("Serial operation of C_csp_two_tank_tes not available if there is a storage HX")); + } - else - { // Iterate between field htf - hx - and storage + m_dot_cr_to_tes_hot = m_dot_cr_to_cv_hot; //[kg/s] + m_dot_cr_to_tes_cold = m_dot_cr_to_cv_cold; //[kg/s] + m_dot_tes_hot_out = m_dot_cv_hot_to_sink; //[kg/s] + m_dot_pc_to_tes_cold = m_dot_cv_hot_to_sink; //[kg/s] + m_dot_tes_cold_out = m_dot_cr_to_cv_hot + m_dot_cr_to_cv_cold; //[kg/s] + m_dot_tes_cold_in = m_dot_total_to_cv_cold; //[kg/s] + m_dot_src_to_sink = 0.0; //[kg/s] + m_dot_sink_to_src = 0.0; //[kg/s] + } - } + double q_dot_heater = std::numeric_limits::quiet_NaN(); //[MWe] Heating power required to keep tanks at a minimum temperature + double m_dot_cold_tank_to_hot_tank = std::numeric_limits::quiet_NaN(); //[kg/s] Hot tank mass flow rate, valid for direct and indirect systems + double W_dot_rhtf_pump = std::numeric_limits::quiet_NaN(); //[MWe] Pumping power, just for tank-to-tank in indirect storage + double q_dot_loss = std::numeric_limits::quiet_NaN(); //[MWt] Storage thermal losses + double q_dot_dc_to_htf = std::numeric_limits::quiet_NaN(); //[MWt] Thermal power to the HTF from storage + double q_dot_ch_from_htf = std::numeric_limits::quiet_NaN(); //[MWt] Thermal power from the HTF to storage + double T_hot_ave = std::numeric_limits::quiet_NaN(); //[K] Average hot tank temperature over timestep + double T_cold_ave = std::numeric_limits::quiet_NaN(); //[K] Average cold tank temperature over timestep + double T_hot_final = std::numeric_limits::quiet_NaN(); //[K] Hot tank temperature at end of timestep + double T_cold_final = std::numeric_limits::quiet_NaN(); //[K] Cold tank temperature at end of timestep - outputs.m_q_heater = q_heater_cold + q_heater_hot; - outputs.m_m_dot = m_dot_htf_out; - outputs.m_W_dot_rhtf_pump = m_dot_htf_out * ms_params.m_htf_pump_coef / 1.E3; //[MWe] Pumping power for Receiver HTF, convert from kW/kg/s*kg/s - outputs.m_q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; + if (tanks_in_parallel) + { - outputs.m_T_hot_ave = T_htf_hot_out; - outputs.m_T_cold_ave = T_cold_ave; - outputs.m_T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] - outputs.m_T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] + if (m_dot_cr_to_cv_hot >= m_dot_cv_hot_to_sink) // Charging + { + T_sink_htf_in_hot = T_cr_out_hot; //[K] + double m_dot_tes_ch = m_dot_cr_to_cv_hot - m_dot_cv_hot_to_sink; //[kg/s] + double T_htf_tes_cold = std::numeric_limits::quiet_NaN(); //[K] + bool ch_solved = charge(timestep, + T_amb, + m_dot_tes_ch, + T_cr_out_hot, + T_htf_tes_cold, + q_dot_heater, m_dot_cold_tank_to_hot_tank, W_dot_rhtf_pump, + q_dot_loss, q_dot_dc_to_htf, q_dot_ch_from_htf, + T_hot_ave, T_cold_ave, T_hot_final, T_cold_final); + + // Check if TES.charge method solved + if (!ch_solved) + { + return -3; + } + + // Enthalpy balance to calculate T_htf_cold to CR + if (m_dot_cr_to_cv_hot == 0.0) + { + T_cr_in_cold = T_htf_tes_cold; //[K] + } + else + { + T_cr_in_cold = (m_dot_tes_ch*T_htf_tes_cold + m_dot_cv_hot_to_sink*T_sink_out_cold) / m_dot_cr_to_cv_hot; //[K] + } + } + else // Discharging + { + T_cr_in_cold = T_sink_out_cold; //[K] + double m_dot_tes_dc = m_dot_cv_hot_to_sink - m_dot_cr_to_cv_hot; //[kg/s] + double T_htf_tes_hot = std::numeric_limits::quiet_NaN(); + bool is_tes_success = discharge(timestep, + T_amb, + m_dot_tes_dc, + T_sink_out_cold, + T_htf_tes_hot, + q_dot_heater, m_dot_cold_tank_to_hot_tank, W_dot_rhtf_pump, + q_dot_loss, q_dot_dc_to_htf, q_dot_ch_from_htf, + T_hot_ave, T_cold_ave, T_hot_final, T_cold_final); - // Calculate thermal power to HTF - double cp_htf_ave = mc_field_htfProps.Cp_ave(T_htf_cold_in, T_htf_hot_out); //[kJ/kg-K] - outputs.m_q_dot_dc_to_htf = m_dot_htf_out * cp_htf_ave*(T_htf_hot_out - T_htf_cold_in) / 1000.0; //[MWt] - outputs.m_q_dot_ch_from_htf = 0.0; //[MWt] + m_dot_cold_tank_to_hot_tank *= -1.0; -} + // Check if discharge method solved + if (!is_tes_success) + { + return -4; + } -bool C_csp_cold_tes::discharge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, - double T_htf_cold_in /*K*/, double & T_htf_hot_out /*K*/, S_csp_cold_tes_outputs &outputs) -{ - // This method calculates the hot discharge temperature on the HX side (if applicable). If no heat exchanger (direct storage), - // the discharge temperature is equal to the average (timestep) hot tank outlet temperature. + T_sink_htf_in_hot = (m_dot_tes_dc*T_htf_tes_hot + m_dot_cr_to_cv_hot*T_cr_out_hot) / m_dot_cv_hot_to_sink; //[K] + } + } + else // Serial tank operation + { + if (m_is_hx) + { + throw(C_csp_exception("C_csp_two_tank_tes::discharge_decoupled not available if there is a storage HX")); + } - // Inputs are: - // 1) Required hot side mass flow rate on the HX side (if applicable). If no heat exchanger, then the mass flow rate - // is equal to the hot tank exit mass flow rate (and cold tank fill mass flow rate) - // 2) inlet temperature on the HX side (if applicable). If no heat exchanger, the inlet temperature is the temperature - // of HTF directly entering the cold tank. + // Inputs are: + // 1) Mass flow rate of HTF from source to hot tank + // 2) Mass flow rate of HTF from source to cold tank + // 3) Mass flow rate of HTF from hot tank to sink + // 4) Temperature of HTF leaving source and entering hot tank + // 5) Temperature of HTF leaving the sink and entering the cold tank - double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, T_cold_ave; - q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_cold_ave = std::numeric_limits::quiet_NaN(); + double q_dot_ch_est, m_dot_tes_ch_max, T_cold_to_src_est; + q_dot_ch_est = m_dot_tes_ch_max = T_cold_to_src_est = std::numeric_limits::quiet_NaN(); + charge_avail_est(T_cr_out_hot, timestep, q_dot_ch_est, m_dot_tes_ch_max, T_cold_to_src_est); - // If no heat exchanger, no iteration is required between the heat exchanger and storage tank models - if (!ms_params.m_is_hx) - { - if (m_dot_htf_in > m_m_dot_tes_dc_max / timestep) - { - outputs.m_q_heater = std::numeric_limits::quiet_NaN(); - outputs.m_m_dot = std::numeric_limits::quiet_NaN(); - outputs.m_W_dot_rhtf_pump = std::numeric_limits::quiet_NaN(); - outputs.m_q_dot_loss = std::numeric_limits::quiet_NaN(); - outputs.m_q_dot_dc_to_htf = std::numeric_limits::quiet_NaN(); - outputs.m_q_dot_ch_from_htf = std::numeric_limits::quiet_NaN(); - outputs.m_T_hot_ave = std::numeric_limits::quiet_NaN(); - outputs.m_T_cold_ave = std::numeric_limits::quiet_NaN(); - outputs.m_T_hot_final = std::numeric_limits::quiet_NaN(); - outputs.m_T_cold_final = std::numeric_limits::quiet_NaN(); - - return false; - } + if (m_dot_cr_to_cv_hot > m_dot_cv_hot_to_sink && std::max(1.E-4, (m_dot_cr_to_cv_hot - m_dot_cv_hot_to_sink)) > 1.0001 * std::max(1.E-4, m_dot_tes_ch_max)) + { + q_dot_heater = std::numeric_limits::quiet_NaN(); + m_dot_cold_tank_to_hot_tank = std::numeric_limits::quiet_NaN(); + W_dot_rhtf_pump = std::numeric_limits::quiet_NaN(); + q_dot_loss = std::numeric_limits::quiet_NaN(); + q_dot_dc_to_htf = std::numeric_limits::quiet_NaN(); + q_dot_ch_from_htf = std::numeric_limits::quiet_NaN(); + T_hot_ave = std::numeric_limits::quiet_NaN(); + T_cold_ave = std::numeric_limits::quiet_NaN(); + T_hot_final = std::numeric_limits::quiet_NaN(); + T_cold_final = std::numeric_limits::quiet_NaN(); - // Call energy balance on hot tank discharge to get average outlet temperature over timestep - mc_hot_tank.energy_balance(timestep, 0.0, m_dot_htf_in, 0.0, T_amb, T_htf_hot_out, q_heater_hot, q_dot_loss_hot); + return -1; + } - // Call energy balance on cold tank charge to track tank mass and temperature - mc_cold_tank.energy_balance(timestep, m_dot_htf_in, 0.0, T_htf_cold_in, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold); - } + double q_dot_dc_est, m_dot_tes_dc_max, T_hot_to_pc_est; + q_dot_dc_est = m_dot_tes_dc_max = T_hot_to_pc_est = std::numeric_limits::quiet_NaN(); + // Use temperature downstream of sink-out and cr-to-cold-tank mixer + discharge_avail_est(T_htf_cold_cv_in, timestep, q_dot_dc_est, m_dot_tes_dc_max, T_hot_to_pc_est); - else - { // Iterate between field htf - hx - and storage + // If mass flow into the cold tank *from the sink* is greater than mass flow going from cold tank to source to hot tank + if (m_dot_cv_hot_to_sink > m_dot_cr_to_cv_hot && std::max(1.E-4, (m_dot_cv_hot_to_sink - m_dot_cr_to_cv_hot)) > 1.0001 * std::max(1.E-4, m_dot_tes_dc_max)) + { + q_dot_heater = std::numeric_limits::quiet_NaN(); + m_dot_cold_tank_to_hot_tank = std::numeric_limits::quiet_NaN(); + W_dot_rhtf_pump = std::numeric_limits::quiet_NaN(); + q_dot_loss = std::numeric_limits::quiet_NaN(); + q_dot_dc_to_htf = std::numeric_limits::quiet_NaN(); + q_dot_ch_from_htf = std::numeric_limits::quiet_NaN(); + T_hot_ave = std::numeric_limits::quiet_NaN(); + T_cold_ave = std::numeric_limits::quiet_NaN(); + T_hot_final = std::numeric_limits::quiet_NaN(); + T_cold_final = std::numeric_limits::quiet_NaN(); - } + return -2; + } - outputs.m_q_heater = q_heater_cold + q_heater_hot; //[MWt] - outputs.m_m_dot = m_dot_htf_in; - outputs.m_W_dot_rhtf_pump = m_dot_htf_in * ms_params.m_htf_pump_coef / 1.E3; //[MWe] Pumping power for Receiver HTF, convert from kW/kg/s*kg/s - outputs.m_q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; //[MWt] + // serial operation constrained to direct configuration, so HTF leaving TES must pass through another plant component + m_dot_cold_tank_to_hot_tank = 0.0; //[kg/s] - outputs.m_T_hot_ave = T_htf_hot_out; //[K] - outputs.m_T_cold_ave = T_cold_ave; //[K] - outputs.m_T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] - outputs.m_T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] + double q_heater_hot, q_dot_loss_hot, q_heater_cold, q_dot_loss_cold; + q_heater_hot = q_dot_loss_hot = q_heater_cold = q_dot_loss_cold = std::numeric_limits::quiet_NaN(); - // Calculate thermal power to HTF - double cp_htf_ave = mc_field_htfProps.Cp_ave(T_htf_cold_in, T_htf_hot_out); //[kJ/kg-K] - outputs.m_q_dot_dc_to_htf = m_dot_htf_in * cp_htf_ave*(T_htf_hot_out - T_htf_cold_in) / 1000.0; //[MWt] - outputs.m_q_dot_ch_from_htf = 0.0; //[MWt] + // Call energy balance on hot tank discharge to get average outlet temperature over timestep + mc_hot_tank.energy_balance(timestep, m_dot_cr_to_cv_hot, m_dot_cv_hot_to_sink, + T_cr_out_hot, T_amb, + T_sink_htf_in_hot, q_heater_hot, q_dot_loss_hot); - return true; -} + // Call energy balance on cold tank charge to track tank mass and temperature + // Use mass flow and temperature downstream of sink-out and cr-to-cold-tank mixer + mc_cold_tank.energy_balance(timestep, m_dot_total_to_cv_cold, m_dot_cv_cold_to_cr, + T_htf_cold_cv_in, T_amb, + T_cr_in_cold, q_heater_cold, q_dot_loss_cold); -bool C_csp_cold_tes::charge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, - double T_htf_hot_in /*K*/, double & T_htf_cold_out /*K*/, S_csp_cold_tes_outputs &outputs) -{ - // This method calculates the cold charge return temperature on the HX side (if applicable). If no heat exchanger (direct storage), - // the return charge temperature is equal to the average (timestep) cold tank outlet temperature. + // Set output structure + q_dot_heater = q_heater_cold + q_heater_hot; //[MWt] - // The method returns FALSE if the input mass flow rate 'm_dot_htf_in' * timestep is greater than the allowable charge + W_dot_rhtf_pump = 0; //[MWe] Tank-to-tank pumping power - // Inputs are: - // 1) Required cold side mass flow rate on the HX side (if applicable). If no heat exchanger, then the mass flow rate - // is equal to the cold tank exit mass flow rate (and hot tank fill mass flow rate) - // 2) Inlet temperature on the HX side (if applicable). If no heat exchanger, the inlet temperature is the temperature - // of HTF directly entering the hot tank + q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; //[MWt] + q_dot_ch_from_htf = 0.0; //[MWt] + T_hot_ave = T_sink_htf_in_hot; //[K] + T_cold_ave = T_cr_in_cold; //[K] + T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] + T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] - double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, T_hot_ave; - q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_hot_ave = std::numeric_limits::quiet_NaN(); + // Net TES discharge + double cp_field = mc_external_htfProps.Cp_ave(T_cold_ave, T_cr_out_hot); + double cp_cycle = mc_store_htfProps.Cp_ave(T_htf_cold_cv_in, T_hot_ave); + double q_dot_tes_net_discharge = (cp_field * (m_dot_tes_cold_out * T_cold_ave - m_dot_cr_to_tes_hot * T_cr_out_hot) + + cp_cycle * (m_dot_tes_hot_out * T_hot_ave - m_dot_total_to_cv_cold * T_htf_cold_cv_in) ) / 1000.0; //[MWt] - // If no heat exchanger, no iteration is required between the heat exchanger and storage tank models - if (!ms_params.m_is_hx) - { - if (m_dot_htf_in > m_m_dot_tes_ch_max / timestep) - { - outputs.m_q_dot_loss = std::numeric_limits::quiet_NaN(); - outputs.m_m_dot = std::numeric_limits::quiet_NaN(); - outputs.m_q_heater = std::numeric_limits::quiet_NaN(); - outputs.m_T_hot_ave = std::numeric_limits::quiet_NaN(); - outputs.m_T_cold_ave = std::numeric_limits::quiet_NaN(); - outputs.m_T_hot_final = std::numeric_limits::quiet_NaN(); - outputs.m_T_cold_final = std::numeric_limits::quiet_NaN(); + if (m_dot_cv_hot_to_sink >= m_dot_cr_to_cv_hot) + { + q_dot_ch_from_htf = 0.0; - return false; - } + q_dot_dc_to_htf = q_dot_tes_net_discharge; //[MWt] + } + else + { + q_dot_dc_to_htf = 0.0; - // Call energy balance on cold tank discharge to get average outlet temperature over timestep - mc_cold_tank.energy_balance(timestep, 0.0, m_dot_htf_in, 0.0, T_amb, T_htf_cold_out, q_heater_cold, q_dot_loss_cold); + q_dot_ch_from_htf = -q_dot_tes_net_discharge; //[MWt] + } - // Call energy balance on hot tank charge to track tank mass and temperature - mc_hot_tank.energy_balance(timestep, m_dot_htf_in, 0.0, T_htf_hot_in, T_amb, T_hot_ave, q_heater_hot, q_dot_loss_hot); - } + } - else - { // Iterate between field htf - hx - and storage + // Solve pumping power here + double W_dot_htf_pump = pumping_power(m_dot_cr_to_cv_hot, m_dot_cv_hot_to_sink, std::abs(m_dot_cold_tank_to_hot_tank), + T_cr_in_cold, T_cr_out_hot, T_sink_htf_in_hot, T_sink_out_cold, + false); //[-] C_MEQ__m_dot_tes will not send cr_m_dot to TES if recirculating - } + double vol_total = mc_cold_tank.get_fluid_vol() + mc_hot_tank.get_fluid_vol(); - outputs.m_q_heater = q_heater_cold + q_heater_hot; //[MW] Storage thermal losses - outputs.m_m_dot = m_dot_htf_in; - outputs.m_W_dot_rhtf_pump = m_dot_htf_in * ms_params.m_htf_pump_coef / 1.E3; //[MWe] Pumping power for Receiver HTF, convert from kW/kg/s*kg/s - outputs.m_q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; //[MW] Heating power required to keep tanks at a minimum temperature + s_outputs.m_q_heater = q_dot_heater; + s_outputs.m_W_dot_elec_in_tot = W_dot_htf_pump; //[MWe] + s_outputs.m_q_dot_dc_to_htf = q_dot_dc_to_htf; + s_outputs.m_q_dot_ch_from_htf = q_dot_ch_from_htf; + s_outputs.m_m_dot_cr_to_tes_hot = m_dot_cr_to_tes_hot; //[kg/s] + s_outputs.m_m_dot_cr_to_tes_cold = m_dot_cr_to_tes_cold; //[kg/s] + s_outputs.m_m_dot_tes_hot_out = m_dot_tes_hot_out; //[kg/s] + s_outputs.m_m_dot_pc_to_tes_cold = m_dot_pc_to_tes_cold; //[kg/s] + s_outputs.m_m_dot_tes_cold_out = m_dot_tes_cold_out; //[kg/s] + s_outputs.m_m_dot_tes_cold_in = m_dot_tes_cold_in; //[kg/s] + s_outputs.m_m_dot_src_to_sink = m_dot_src_to_sink; //[kg/s] + s_outputs.m_m_dot_sink_to_src = m_dot_sink_to_src; //[kg/s] - outputs.m_T_hot_ave = T_hot_ave; //[K] Average hot tank temperature over timestep - outputs.m_T_cold_ave = T_htf_cold_out; //[K] Average cold tank temperature over timestep - outputs.m_T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] Hot temperature at end of timestep - outputs.m_T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] Cold temperature at end of timestep + s_outputs.m_T_tes_cold_in = T_htf_cold_cv_in; //[K] - // Calculate thermal power to HTF - double cp_htf_ave = mc_field_htfProps.Cp_ave(T_htf_cold_out, T_htf_hot_in); //[kJ/kg-K] - outputs.m_q_dot_ch_from_htf = m_dot_htf_in * cp_htf_ave*(T_htf_hot_in - T_htf_cold_out) / 1000.0; //[MWt] - outputs.m_q_dot_dc_to_htf = 0.0; //[MWt] + s_outputs.m_m_dot_cold_tank_to_hot_tank = m_dot_cold_tank_to_hot_tank; - return true; + mc_reported_outputs.value(E_Q_DOT_LOSS, q_dot_loss); //[MWt] + mc_reported_outputs.value(E_W_DOT_HEATER, q_dot_heater); //[MWt] + mc_reported_outputs.value(E_TES_T_HOT, T_hot_final - 273.15); //[C] + mc_reported_outputs.value(E_TES_T_COLD, T_cold_final - 273.15); //[C] + mc_reported_outputs.value(E_M_DOT_TANK_TO_TANK, m_dot_cold_tank_to_hot_tank); //[kg/s] + mc_reported_outputs.value(E_MASS_COLD_TANK, mc_cold_tank.get_m_m_calc()); //[kg] + mc_reported_outputs.value(E_MASS_HOT_TANK, mc_hot_tank.get_m_m_calc()); //[kg] + mc_reported_outputs.value(E_W_DOT_HTF_PUMP, W_dot_htf_pump); //[MWe] + mc_reported_outputs.value(E_VOL_TOT, vol_total); //[m3] + mc_reported_outputs.value(E_MASS_TOT, mc_cold_tank.get_m_m_calc() + mc_hot_tank.get_m_m_calc()); //[m3] + return 0; } -bool C_csp_cold_tes::charge_discharge(double timestep /*s*/, double T_amb /*K*/, double m_dot_hot_in /*kg/s*/, - double T_hot_in /*K*/, double m_dot_cold_in /*kg/s*/, double T_cold_in /*K*/, S_csp_cold_tes_outputs &outputs) +bool C_csp_two_tank_tes::discharge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, + double T_htf_cold_in /*K*/, double & T_htf_hot_out /*K*/, + double & q_dot_heater /*MWe*/, double & m_dot_tank_to_tank /*kg/s*/, double & W_dot_rhtf_pump /*MWe*/, + double & q_dot_loss /*MWt*/, double & q_dot_dc_to_htf /*MWt*/, double & q_dot_ch_from_htf /*MWt*/, + double & T_hot_ave /*K*/, double & T_cold_ave /*K*/, double & T_hot_final /*K*/, double & T_cold_final /*K*/) { - // ARD This is for simultaneous charge and discharge. If no heat exchanger (direct storage), - // the return charge temperature is equal to the average (timestep) cold tank outlet temperature. + // This method calculates the timestep-average hot discharge temperature of the TES system. This is out of the external side of the heat exchanger (HX), opposite the tank (or 'TES') side, + // or if no HX (direct storage), this is equal to the hot tank outlet temperature. - // The method returns FALSE if the input mass flow rate 'm_dot_htf_in' * timestep is greater than the allowable charge + // The method returns FALSE if the system output (same as input) mass flow rate is greater than that available // Inputs are: - // 1) (Assumes no heat exchanger) The cold tank exit mass flow rate (and hot tank fill mass flow rate) - // 2) The temperature of HTF directly entering the hot tank. - // 3) The hot tank exit mass flow rate (and cold tank fill mass flow rate) - // 4) The temperature of the HTF directly entering the cold tank. + // 1) Mass flow rate of HTF into TES system (equal to that exiting the system) + // 2) Temperature of HTF into TES system. If no heat exchanger, this temperature + // is of the HTF directly entering the cold tank - double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, T_hot_ave, T_cold_ave; - q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_hot_ave = T_cold_ave=std::numeric_limits::quiet_NaN(); + double q_dot_dc_est, m_dot_tes_dc_max, T_hot_to_pc_est; + q_dot_dc_est = m_dot_tes_dc_max = T_hot_to_pc_est = std::numeric_limits::quiet_NaN(); + discharge_avail_est(T_htf_cold_in, timestep, q_dot_dc_est, m_dot_tes_dc_max, T_hot_to_pc_est); - // If no heat exchanger, no iteration is required between the heat exchanger and storage tank models - if (!ms_params.m_is_hx) - { - if (m_dot_hot_in > m_m_dot_tes_ch_max / timestep) - { - outputs.m_q_dot_loss = std::numeric_limits::quiet_NaN(); - outputs.m_m_dot = std::numeric_limits::quiet_NaN(); - outputs.m_q_heater = std::numeric_limits::quiet_NaN(); - outputs.m_T_hot_ave = std::numeric_limits::quiet_NaN(); - outputs.m_T_cold_ave = std::numeric_limits::quiet_NaN(); - outputs.m_T_hot_final = std::numeric_limits::quiet_NaN(); - outputs.m_T_cold_final = std::numeric_limits::quiet_NaN(); + if (m_dot_htf_in > 1.0001*m_dot_tes_dc_max && m_dot_htf_in > 1.E-6) // mass flow in = mass flow out + { + q_dot_heater = std::numeric_limits::quiet_NaN(); + m_dot_tank_to_tank = std::numeric_limits::quiet_NaN(); + W_dot_rhtf_pump = std::numeric_limits::quiet_NaN(); + q_dot_loss = std::numeric_limits::quiet_NaN(); + q_dot_dc_to_htf = std::numeric_limits::quiet_NaN(); + q_dot_ch_from_htf = std::numeric_limits::quiet_NaN(); + T_hot_ave = std::numeric_limits::quiet_NaN(); + T_cold_ave = std::numeric_limits::quiet_NaN(); + T_hot_final = std::numeric_limits::quiet_NaN(); + T_cold_final = std::numeric_limits::quiet_NaN(); - return false; - } + return false; + } - // Call energy balance on cold tank discharge to get average outlet temperature over timestep - mc_cold_tank.energy_balance(timestep, m_dot_cold_in, m_dot_hot_in, T_cold_in, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold); + double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, m_dot_src, + T_src_cold_in, T_src_hot_out, m_dot_tank, T_cold_tank_in; + q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_cold_ave = T_hot_ave = + m_dot_src = T_src_cold_in = T_src_hot_out = m_dot_tank = T_cold_tank_in = std::numeric_limits::quiet_NaN(); - // Call energy balance on hot tank charge to track tank mass and temperature - mc_hot_tank.energy_balance(timestep, m_dot_hot_in, m_dot_cold_in, T_hot_in, T_amb, T_hot_ave, q_heater_hot, q_dot_loss_hot); - } + // If no heat exchanger, no iteration is required between the heat exchanger and storage tank models + if(!m_is_hx) + { + m_dot_src = m_dot_tank = m_dot_htf_in; + T_src_cold_in = T_cold_tank_in = T_htf_cold_in; - else - { // Iterate between field htf - hx - and storage + // Call energy balance on hot tank discharge to get average outlet temperature over timestep + mc_hot_tank.energy_balance(timestep, 0.0, m_dot_tank, 0.0, T_amb, T_hot_ave, q_heater_hot, q_dot_loss_hot); + // Call energy balance on cold tank charge to track tank mass and temperature + mc_cold_tank.energy_balance(timestep, m_dot_tank, 0.0, T_cold_tank_in, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold); } + else + { + T_src_cold_in = T_htf_cold_in; //[K] + + m_dot_src = m_dot_htf_in; //[kg/s] + m_dot_tank = get_tes_m_dot(m_dot_src); + + mc_hot_tank.energy_balance(timestep, 0.0, m_dot_tank, 0.0, T_amb, T_hot_ave, q_heater_hot, q_dot_loss_hot); - outputs.m_q_heater = q_heater_cold + q_heater_hot; //[MW] Storage thermal losses - outputs.m_m_dot = m_dot_hot_in; - outputs.m_W_dot_rhtf_pump = m_dot_hot_in * ms_params.m_htf_pump_coef / 1.E3; //[MWe] Pumping power for Receiver HTF, convert from kW/kg/s*kg/s - outputs.m_q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; //[MW] Heating power required to keep tanks at a minimum temperature + double eff_hx, q_dot_hx; + eff_hx = q_dot_hx = std::numeric_limits::quiet_NaN(); + mc_hx.solve(T_htf_cold_in, m_dot_src, + T_hot_ave, m_dot_tank, + T_src_hot_out, T_cold_tank_in, + eff_hx, q_dot_hx); + mc_cold_tank.energy_balance(timestep, m_dot_tank, 0.0, T_cold_tank_in, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold); + } - outputs.m_T_hot_ave = T_hot_ave; //[K] Average hot tank temperature over timestep - outputs.m_T_cold_ave = T_cold_ave; //[K] Average cold tank temperature over timestep - outputs.m_T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] Hot temperature at end of timestep - outputs.m_T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] Cold temperature at end of timestep + q_dot_heater = q_heater_cold + q_heater_hot; //[MWt] + + if (!m_is_hx) + { + m_dot_tank_to_tank = 0.0; + W_dot_rhtf_pump = 0; //[MWe] Just tank-to-tank pumping power + T_htf_hot_out = T_hot_ave; + } + else + { + m_dot_tank_to_tank = m_dot_tank; //[kg/s] + W_dot_rhtf_pump = m_dot_tank * m_tes_pump_coef / 1.E3; //[MWe] Just tank-to-tank pumping power, convert from kW/kg/s*kg/s + T_htf_hot_out = T_src_hot_out; + } + + q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; //[MWt] + q_dot_ch_from_htf = 0.0; //[MWt] + T_hot_ave = T_hot_ave; //[K] + T_cold_ave = T_cold_ave; //[K] + T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] + T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] // Calculate thermal power to HTF - double cp_htf_ave = mc_field_htfProps.Cp_ave(T_cold_ave, T_hot_in); //[kJ/kg-K] - outputs.m_q_dot_ch_from_htf = m_dot_hot_in * cp_htf_ave*(T_hot_in - T_cold_ave) / 1000.0; //[MWt] - outputs.m_q_dot_dc_to_htf = 0.0; //[MWt] + double cp_htf_ave = mc_external_htfProps.Cp_ave(T_htf_cold_in, T_htf_hot_out); //[kJ/kg-K] + q_dot_dc_to_htf = m_dot_htf_in*cp_htf_ave*(T_htf_hot_out - T_htf_cold_in)/1000.0; //[MWt] return true; - } -bool C_csp_cold_tes::recirculation(double timestep /*s*/, double T_amb /*K*/, double m_dot_cold_in /*kg/s*/, - double T_cold_in /*K*/, S_csp_cold_tes_outputs &outputs) +bool C_csp_two_tank_tes::charge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, + double T_htf_hot_in /*K*/, double & T_htf_cold_out /*K*/, + double & q_dot_heater /*MWe*/, double & m_dot_tank_to_tank /*kg/s*/, double & W_dot_rhtf_pump /*MWe*/, + double & q_dot_loss /*MWt*/, double & q_dot_dc_to_htf /*MWt*/, double & q_dot_ch_from_htf /*MWt*/, + double & T_hot_ave /*K*/, double & T_cold_ave /*K*/, double & T_hot_final /*K*/, double & T_cold_final /*K*/) { - // This method calculates the average (timestep) cold tank outlet temperature when recirculating cold fluid for further cooling. - // This warm tank is idle and its state is also determined. - - // The method returns FALSE if the input mass flow rate 'm_dot_htf_in' * timestep is greater than the allowable charge + // This method calculates the timestep-average cold charge return temperature of the TES system. This is out of the external side of the heat exchanger (HX), opposite the tank (or 'TES') side, + // or if no HX (direct storage), this is equal to the cold tank outlet temperature. + + // The method returns FALSE if the system input mass flow rate is greater than the allowable charge // Inputs are: - // 1) The cold tank exit mass flow rate - // 2) The inlet temperature of HTF directly entering the cold tank + // 1) Mass flow rate of HTF into TES system (equal to that exiting the system) + // 2) Temperature of HTF into TES system. If no heat exchanger, this temperature + // is of the HTF directly entering the hot tank + + double q_dot_ch_est, m_dot_tes_ch_max, T_cold_to_src_est; + q_dot_ch_est = m_dot_tes_ch_max = T_cold_to_src_est = std::numeric_limits::quiet_NaN(); + charge_avail_est(T_htf_hot_in, timestep, q_dot_ch_est, m_dot_tes_ch_max, T_cold_to_src_est); + + if (m_dot_htf_in > 1.0001*m_dot_tes_ch_max && m_dot_htf_in > 1.E-6) + { + q_dot_heater = std::numeric_limits::quiet_NaN(); + m_dot_tank_to_tank = std::numeric_limits::quiet_NaN(); + W_dot_rhtf_pump = std::numeric_limits::quiet_NaN(); + q_dot_loss = std::numeric_limits::quiet_NaN(); + q_dot_dc_to_htf = std::numeric_limits::quiet_NaN(); + q_dot_ch_from_htf = std::numeric_limits::quiet_NaN(); + T_hot_ave = std::numeric_limits::quiet_NaN(); + T_cold_ave = std::numeric_limits::quiet_NaN(); + T_hot_final = std::numeric_limits::quiet_NaN(); + T_cold_final = std::numeric_limits::quiet_NaN(); + + return false; + } - double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, T_hot_ave, T_cold_ave; - q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_hot_ave =T_cold_ave= std::numeric_limits::quiet_NaN(); + double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, m_dot_src, + T_src_hot_in, T_src_cold_out, m_dot_tank, T_hot_tank_in; + q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_cold_ave = T_hot_ave = m_dot_src = + T_src_hot_in = T_src_cold_out = m_dot_tank = T_hot_tank_in = std::numeric_limits::quiet_NaN(); // If no heat exchanger, no iteration is required between the heat exchanger and storage tank models - if (!ms_params.m_is_hx) + if( !m_is_hx ) { - if (m_dot_cold_in > m_m_dot_tes_ch_max / timestep) //Is this necessary for recirculation mode? ARD - { - outputs.m_q_dot_loss = std::numeric_limits::quiet_NaN(); - outputs.m_m_dot = std::numeric_limits::quiet_NaN(); - outputs.m_q_heater = std::numeric_limits::quiet_NaN(); - outputs.m_T_hot_ave = std::numeric_limits::quiet_NaN(); - outputs.m_T_cold_ave = std::numeric_limits::quiet_NaN(); - outputs.m_T_hot_final = std::numeric_limits::quiet_NaN(); - outputs.m_T_cold_final = std::numeric_limits::quiet_NaN(); - - return false; - } + m_dot_src = m_dot_tank = m_dot_htf_in; + T_src_hot_in = T_hot_tank_in = T_htf_hot_in; // Call energy balance on cold tank discharge to get average outlet temperature over timestep - mc_cold_tank.energy_balance_constant_mass(timestep, m_dot_cold_in, T_cold_in, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold); - - // Call energy balance on hot tank charge to track tank mass and temperature while idle - mc_hot_tank.energy_balance(timestep, 0.0, 0.0, 0.0, T_amb, T_hot_ave, q_heater_hot, q_dot_loss_hot); + mc_cold_tank.energy_balance(timestep, 0.0, m_dot_tank, 0.0, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold); + // Call energy balance on hot tank charge to track tank mass and temperature + mc_hot_tank.energy_balance(timestep, m_dot_tank, 0.0, T_hot_tank_in, T_amb, T_hot_ave, q_heater_hot, q_dot_loss_hot); } - else - { // Iterate between field htf - hx - and storage + { + T_src_hot_in = T_htf_hot_in; //[K] - } + m_dot_src = m_dot_htf_in; + m_dot_tank = get_tes_m_dot(m_dot_src); //[kg/s] - outputs.m_q_heater = q_heater_cold + q_heater_hot; //[MW] Storage thermal losses - outputs.m_m_dot = m_dot_cold_in; - outputs.m_W_dot_rhtf_pump = m_dot_cold_in * ms_params.m_htf_pump_coef / 1.E3; //[MWe] Pumping power for Receiver HTF, convert from kW/kg/s*kg/s - outputs.m_q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; //[MW] Heating power required to keep tanks at a minimum temperature + mc_cold_tank.energy_balance(timestep, 0.0, m_dot_tank, 0.0, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold); + + double eff_hx, q_dot_hx; + eff_hx = q_dot_hx = std::numeric_limits::quiet_NaN(); + mc_hx.solve(T_src_hot_in, m_dot_src, + T_cold_ave, m_dot_tank, + T_src_cold_out, T_hot_tank_in, + eff_hx, q_dot_hx); - outputs.m_T_hot_ave = T_hot_ave; //[K] Average hot tank temperature over timestep - outputs.m_T_cold_ave = T_cold_ave; //[K] Average cold tank temperature over timestep - outputs.m_T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] Hot temperature at end of timestep - outputs.m_T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] Cold temperature at end of timestep + mc_hot_tank.energy_balance(timestep, m_dot_tank, 0.0, T_hot_tank_in, T_amb, T_hot_ave, q_heater_hot, q_dot_loss_hot); + } + + q_dot_heater = q_heater_cold + q_heater_hot; //[MWt] + + if (!m_is_hx) + { + m_dot_tank_to_tank = 0.0; + W_dot_rhtf_pump = 0; //[MWe] Just tank-to-tank pumping power + T_htf_cold_out = T_cold_ave; + } + else + { + m_dot_tank_to_tank = m_dot_tank; + W_dot_rhtf_pump = m_dot_tank * m_tes_pump_coef / 1.E3; //[MWe] Just tank-to-tank pumping power, convert from kW/kg/s*kg/s + T_htf_cold_out = T_src_cold_out; + } + q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; //[MWt] + q_dot_dc_to_htf = 0.0; //[MWt] + T_hot_ave = T_hot_ave; //[K] + T_cold_ave = T_cold_ave; //[K] + T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] + T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] // Calculate thermal power to HTF - double cp_htf_ave = mc_field_htfProps.Cp_ave(T_cold_ave, T_cold_in); //[kJ/kg-K] - outputs.m_q_dot_ch_from_htf = m_dot_cold_in * cp_htf_ave*(T_cold_in - T_cold_ave) / 1000.0; //[MWt] - outputs.m_q_dot_dc_to_htf = 0.0; //[MWt] + double cp_htf_ave = mc_external_htfProps.Cp_ave(T_htf_cold_out, T_htf_hot_in); //[kJ/kg-K] + q_dot_ch_from_htf = m_dot_htf_in*cp_htf_ave*(T_htf_hot_in - T_htf_cold_out)/1000.0; //[MWt] return true; - } - -void C_csp_cold_tes::charge_full(double timestep /*s*/, double T_amb /*K*/, double T_htf_hot_in /*K*/, - double & T_htf_cold_out /*K*/, double & m_dot_htf_out /*kg/s*/, S_csp_cold_tes_outputs &outputs) +void C_csp_two_tank_tes::converged() { - // This method calculates the cold charge return temperature and mass flow rate on the HX side (if applicable) during FULL CHARGE. If no heat exchanger (direct storage), - // the charge return temperature is equal to the average (timestep) cold tank outlet temperature + mc_cold_tank.converged(); + mc_hot_tank.converged(); + //mc_hx.converged(); - // Inputs are: - // 1) inlet temperature on the HX side (if applicable). If no heat exchanger, the inlet temperature is the temperature - // of HTF directly entering the hot tank. + // Set reported sink converged values + mc_reported_outputs.value(E_HOT_TANK_HTF_PERC_FINAL, mc_hot_tank.get_mass_avail() / m_mass_total_active * 100.0); - double q_heater_cold, q_heater_hot, q_dot_loss_cold, q_dot_loss_hot, T_hot_ave; - q_heater_cold = q_heater_hot = q_dot_loss_cold = q_dot_loss_hot = T_hot_ave = std::numeric_limits::quiet_NaN(); + mc_reported_outputs.set_timestep_outputs(); - // If no heat exchanger, no iteration is required between the heat exchanger and storage tank models - if (!ms_params.m_is_hx) - { - m_dot_htf_out = m_m_dot_tes_ch_max / timestep; //[kg/s] + // The max charge and discharge flow rates should be set at the beginning of each timestep + // during the q_dot_xx_avail_est calls + // m_m_dot_tes_dc_max = m_m_dot_tes_ch_max = std::numeric_limits::quiet_NaN(); +} - // Call energy balance on hot tank charge to track tank mass and temperature - mc_hot_tank.energy_balance(timestep, m_dot_htf_out, 0.0, T_htf_hot_in, T_amb, T_hot_ave, q_heater_hot, q_dot_loss_hot); +void C_csp_two_tank_tes::get_final_from_converged(double& f_V_hot, double& T_hot_tank /*K*/, double& T_cold_tank /*K*/) +{ + f_V_hot = mc_hot_tank.get_mass_avail() / m_mass_total_active; //[-] + T_hot_tank = mc_hot_tank.get_m_T_prev(); //[K] + T_cold_tank = mc_cold_tank.get_m_T_prev(); //[K] +} - // Call energy balance on cold tank charge to calculate cold HTF return temperature - mc_cold_tank.energy_balance(timestep, 0.0, m_dot_htf_out, 0.0, T_amb, T_htf_cold_out, q_heater_cold, q_dot_loss_cold); - } +void C_csp_two_tank_tes::write_output_intervals(double report_time_start, + const std::vector& v_temp_ts_time_end, double report_time_end) +{ + mc_reported_outputs.send_to_reporting_ts_array(report_time_start, + v_temp_ts_time_end, report_time_end); +} - else - { // Iterate between field htf - hx - and storage +void C_csp_two_tank_tes::assign(int index, double* p_reporting_ts_array, size_t n_reporting_ts_array) +{ + mc_reported_outputs.assign(index, p_reporting_ts_array, n_reporting_ts_array); +} - } +int C_csp_two_tank_tes::pressure_drops(double m_dot_sf, double m_dot_pb, + double T_sf_in, double T_sf_out, double T_pb_in, double T_pb_out, bool recirculating, + double &P_drop_col, double &P_drop_gen) +{ + const std::size_t num_sections = 11; // total number of col. + gen. sections + const std::size_t bypass_section = 4; // bypass section index + const std::size_t gen_first_section = 5; // first generation section index in combined col. gen. loops + const double P_hi = 17 / 1.e-5; // downstream SF pump pressure [Pa] + const double P_lo = 1 / 1.e-5; // atmospheric pressure [Pa] + double P, T, rho, v_dot, vel; // htf properties + double Area; // cross-sectional pipe area + double v_dot_src, v_dot_sink; // source and sink vol. flow rates + double k; // effective minor loss coefficient + double Re, ff; + double v_dot_ref; + double dP_discharge; + std::vector P_drops(num_sections, 0.0); - outputs.m_q_heater = q_heater_cold + q_heater_hot; - outputs.m_m_dot = m_dot_htf_out; - outputs.m_W_dot_rhtf_pump = m_dot_htf_out * ms_params.m_htf_pump_coef / 1.E3; //[MWe] Pumping power for Receiver HTF, convert from kW/kg/s*kg/s - outputs.m_q_dot_loss = q_dot_loss_cold + q_dot_loss_hot; + util::matrix_t L = this->tes_lengths; + util::matrix_t D = this->pipe_diams; + util::matrix_t k_coeffs = this->k_tes_loss_coeffs; + util::matrix_t v_dot_rel = this->pipe_v_dot_rel; + m_dot_pb > 0 ? dP_discharge = this->dP_discharge * 1.e5 : dP_discharge = 0.; + v_dot_src = m_dot_sf / this->mc_external_htfProps.dens((T_sf_in + T_sf_out) / 2, (P_hi + P_lo) / 2); + v_dot_sink = m_dot_pb / this->mc_external_htfProps.dens((T_pb_in + T_pb_out) / 2, P_lo); - outputs.m_T_hot_ave = T_hot_ave; - outputs.m_T_cold_ave = T_htf_cold_out; - outputs.m_T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] - outputs.m_T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] + for (std::size_t i = 0; i < num_sections; i++) { + if (L.at(i) > 0 && D.at(i) > 0) { + (i > 0 && i < 3) ? P = P_hi : P = P_lo; + if (i < 3) T = T_sf_in; // 0, 1, 2 + if (i == 3 || i == 4) T = T_sf_out; // 3, 4 + if (i >= gen_first_section && i < gen_first_section + 4) T = T_pb_in; // 5, 6, 7, 8 + if (i == gen_first_section + 4) T = (T_pb_in + T_pb_out) / 2.; // 9 + if (i == gen_first_section + 5) T = T_pb_out; // 10 + i < gen_first_section ? v_dot_ref = v_dot_src : v_dot_ref = v_dot_sink; + v_dot = v_dot_rel.at(i) * v_dot_ref; + Area = CSP::pi * pow(D.at(i), 2) / 4.; + vel = v_dot / Area; + rho = this->mc_external_htfProps.dens(T, P); + Re = this->mc_external_htfProps.Re(T, P, vel, D.at(i)); + ff = CSP::FrictionFactor(this->pipe_rough / D.at(i), Re); + if (i != bypass_section || recirculating) { + P_drops.at(i) += CSP::MajorPressureDrop(vel, rho, ff, L.at(i), D.at(i)); + P_drops.at(i) += CSP::MinorPressureDrop(vel, rho, k_coeffs.at(i)); + } + } + } - // Calculate thermal power to HTF - double cp_htf_ave = mc_field_htfProps.Cp_ave(T_htf_cold_out, T_htf_hot_in); //[kJ/kg-K] - outputs.m_q_dot_ch_from_htf = m_dot_htf_out * cp_htf_ave*(T_htf_hot_in - T_htf_cold_out) / 1000.0; //[MWt] - outputs.m_q_dot_dc_to_htf = 0.0; //[MWt] + P_drop_col = std::accumulate(P_drops.begin(), P_drops.begin() + gen_first_section, 0.0); + P_drop_gen = dP_discharge + std::accumulate(P_drops.begin() + gen_first_section, P_drops.end(), 0.0); + return 0; } -void C_csp_cold_tes::idle(double timestep, double T_amb, S_csp_cold_tes_outputs &outputs) +double /*MWe*/ C_csp_two_tank_tes::pumping_power(double m_dot_sf /*kg/s*/, double m_dot_pb /*kg/s*/, double m_dot_tank /*kg/s*/, + double T_sf_in /*K*/, double T_sf_out /*K*/, double T_pb_in /*K*/, double T_pb_out /*K*/, bool recirculating) { - double T_hot_ave, q_hot_heater, q_dot_hot_loss; - T_hot_ave = q_hot_heater = q_dot_hot_loss = std::numeric_limits::quiet_NaN(); - - mc_hot_tank.energy_balance(timestep, 0.0, 0.0, 0.0, T_amb, T_hot_ave, q_hot_heater, q_dot_hot_loss); - - double T_cold_ave, q_cold_heater, q_dot_cold_loss; - T_cold_ave = q_cold_heater = q_dot_cold_loss = std::numeric_limits::quiet_NaN(); - - mc_cold_tank.energy_balance(timestep, 0.0, 0.0, 0.0, T_amb, T_cold_ave, q_cold_heater, q_dot_cold_loss); + double htf_pump_power = 0.; + double rho_sf, rho_pb; + double DP_col, DP_gen; + double tes_pump_coef = this->m_tes_pump_coef; + double pb_pump_coef = this->m_htf_pump_coef; + double eta_pump = this->eta_pump; + + if (this->custom_tes_p_loss) { + double rho_sf, rho_pb; + double DP_col, DP_gen; + this->pressure_drops(m_dot_sf, m_dot_pb, T_sf_in, T_sf_out, T_pb_in, T_pb_out, recirculating, + DP_col, DP_gen); + rho_sf = this->mc_external_htfProps.dens((T_sf_in + T_sf_out) / 2., 8e5); + rho_pb = this->mc_external_htfProps.dens((T_pb_in + T_pb_out) / 2., 1e5); + if (this->m_is_hx) { + // TODO - replace tes_pump_coef with a pump efficiency. Maybe utilize unused coefficients specified for the + // series configuration, namely the SGS Pump suction header to Individual SGS pump inlet and the additional + // two immediately downstream + htf_pump_power = tes_pump_coef * m_dot_tank / 1000 + + (DP_col * m_dot_sf / (rho_sf * eta_pump) + DP_gen * m_dot_pb / (rho_pb * eta_pump)) / 1e6; //[MW] + } + else { + htf_pump_power = (DP_col * m_dot_sf / (rho_sf * eta_pump) + DP_gen * m_dot_pb / (rho_pb * eta_pump)) / 1e6; //[MW] + } + } + else { // original methods + if (this->m_is_hx) + { + // Also going to be tanks_in_parallel = true if there's a hx between external system and TES HTF + htf_pump_power = (tes_pump_coef * m_dot_tank + tes_pump_coef * std::abs(m_dot_pb - m_dot_sf)) / 1000.0; //[MWe] + //htf_pump_power = (tes_pump_coef*m_dot_tank + pb_pump_coef * (fabs(m_dot_pb - m_dot_sf) + m_dot_pb)) / 1000.0; //[MW] + } + else + { + htf_pump_power = 0.0; //[MWe] + //if (this->tanks_in_parallel) { + // htf_pump_power = pb_pump_coef * (fabs(m_dot_pb - m_dot_sf) + m_dot_pb) / 1000.0; //[MW] + //} + //else { + // htf_pump_power = pb_pump_coef * m_dot_pb / 1000.0; //[MW] + //} + } + } - outputs.m_q_heater = q_cold_heater + q_hot_heater; //[MJ] - outputs.m_m_dot = 0.; - outputs.m_W_dot_rhtf_pump = 0.0; //[MWe] - outputs.m_q_dot_loss = q_dot_cold_loss + q_dot_hot_loss; //[MW] + return htf_pump_power; +} - outputs.m_T_hot_ave = T_hot_ave; //[K] - outputs.m_T_cold_ave = T_cold_ave; //[K] - outputs.m_T_hot_final = mc_hot_tank.get_m_T_calc(); //[K] - outputs.m_T_cold_final = mc_cold_tank.get_m_T_calc(); //[K] - outputs.m_q_dot_ch_from_htf = 0.0; //[MWt] - outputs.m_q_dot_dc_to_htf = 0.0; //[MWt] +double C_csp_two_tank_tes::get_min_storage_htf_temp() +{ + return mc_store_htfProps.min_temp(); } -void C_csp_cold_tes::converged() +double C_csp_two_tank_tes::get_max_storage_htf_temp() { - mc_cold_tank.converged(); - mc_hot_tank.converged(); - - // The max charge and discharge flow rates should be set at the beginning of each timestep - // during the q_dot_xx_avail_est calls - m_m_dot_tes_dc_max = m_m_dot_tes_ch_max = std::numeric_limits::quiet_NaN(); + return mc_store_htfProps.max_temp(); } -int C_csp_cold_tes::pressure_drops(double m_dot_sf, double m_dot_pb, - double T_sf_in, double T_sf_out, double T_pb_in, double T_pb_out, bool recirculating, - double &P_drop_col, double &P_drop_gen) +double C_csp_two_tank_tes::get_storage_htf_density() { - P_drop_col = 0.; - P_drop_gen = 0.; + double avg_temp = (m_T_cold_des + m_T_hot_des) / 2.0; + return mc_store_htfProps.dens(avg_temp, 0); +} - return 0; +double C_csp_two_tank_tes::get_storage_htf_cp() +{ + double avg_temp = (m_T_cold_des + m_T_hot_des) / 2.0; + return mc_store_htfProps.Cp(avg_temp); } -double C_csp_cold_tes::pumping_power(double m_dot_sf, double m_dot_pb, double m_dot_tank, - double T_sf_in, double T_sf_out, double T_pb_in, double T_pb_out, bool recirculating) +bool C_csp_two_tank_tes::get_is_hx() { - return m_dot_tank * this->ms_params.m_htf_pump_coef / 1.E3; + return m_is_hx; } diff --git a/tcs/csp_solver_two_tank_tes.h b/tcs/csp_solver_two_tank_tes.h index 89f5714d3..ee004c320 100644 --- a/tcs/csp_solver_two_tank_tes.h +++ b/tcs/csp_solver_two_tank_tes.h @@ -35,97 +35,338 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "csp_solver_core.h" #include "csp_solver_util.h" - +#include "csp_solver_tes_core.h" #include "sam_csp_util.h" const int N_tes_pipe_sections = 11; -class C_hx_two_tank_tes +class C_storage_tank { private: - HTFProperties mc_external_htfProps; - HTFProperties mc_store_htfProps; + HTFProperties mc_htf; - double m_m_dot_des_ave; //[kg/s] Average (external and storage sides) mass flow rate - double m_eff_des; //[-] Heat exchanger effectiveness - double m_UA_des; //[W/K] Heat exchanger conductance + double m_V_total; //[m^3] Total volume for *one temperature* tank + double m_V_active; //[m^3] active volume of *one temperature* tank (either cold or hot) + double m_V_inactive; //[m^3] Inactive volume of *one temperature* tank (either cold or hot) + double m_UA; //[W/K] Tank loss conductance + + double m_T_htr; //[K] Tank heater set point + double m_max_q_htr; //[MWt] Max tank heater capacity + + double m_T_design; //[K] Tank design point temperature + double m_mass_total; //[kg] Mass of storage fluid that would fill tank volume at design temperature + double m_mass_inactive; //[kg] Mass of storage fluid at design Temp that fills inactive volume + double m_mass_active; //[kg] Mass of storage fluid at design Temp that fills active volume + + // Stored values from end of previous timestep + double m_V_prev; //[m^3] Volume of storage fluid in tank + double m_T_prev; //[K] Temperature of storage fluid in tank + double m_m_prev; //[kg] Mass of storage fluid in tank + + // Calculated values for current timestep + double m_V_calc; //[m^3] Volume of storage fluid in tank + double m_T_calc; //[K] Temperature of storage fluid in tank + double m_m_calc; //[kg] Mass of storage fluid in tank public: - C_hx_two_tank_tes(); + C_storage_tank(); - void init(const HTFProperties &fluid_external, const HTFProperties &fluid_store, double q_transfer_des, - double dt_des, double T_h_in_des, double T_h_out_des); + double calc_mass_at_prev(); - void solve(double T_f_htf_hx_in /*K*/, double m_dot_f_htf /*kg/s*/, - double T_s_htf_hx_in /*K*/, double m_dot_s_htf /*kg/s*/, - double & T_f_htf_hx_out /*K*/, double & T_s_htf_hx_out /*K*/, - double & eff /*-*/, double & q_dot_hx /*MWt*/); + double get_m_UA(); + + double get_m_T_prev(); + + double get_m_T_calc(); + + double get_m_m_calc(); + + double get_vol_frac(); + + double get_mass_avail(); //[kg] + + double get_fluid_vol(); //[m3] + + void init(HTFProperties htf_class_in, double V_tank /*m3*/, + double h_tank /*m*/, double h_min /*m*/, double u_tank /*W/m2-K*/, + double tank_pairs /*-*/, double T_htr /*K*/, double max_q_htr /*MWt*/, + double V_ini /*m3*/, double T_ini /*K*/, + double T_design /*K*/); + double m_dot_available(double f_unavail, double timestep); + + void energy_balance(double timestep /*s*/, double m_dot_in /*kg/s*/, double m_dot_out /*kg/s*/, + double T_in /*K*/, double T_amb /*K*/, + double& T_ave /*K*/, double& q_heater /*MW*/, double& q_dot_loss /*MW*/); + + void energy_balance_constant_mass(double timestep /*s*/, double m_dot_in, double T_in /*K*/, double T_amb /*K*/, + double& T_ave /*K*/, double& q_heater /*MW*/, double& q_dot_loss /*MW*/); + + void converged(); }; -class C_storage_tank +class C_hx_cold_tes +{ +private: + HTFProperties mc_field_htfProps; + HTFProperties mc_store_htfProps; + + double m_m_dot_des_ave; //[kg/s] Average (field and storage sides) mass flow rate + double m_eff_des; //[-] Heat exchanger effectiveness + double m_UA_des; //[W/K] Heat exchanger conductance + + // Stored values from previous timestep + double m_T_hot_field_prev; //[K] Hotter temperature on field side (opposite tank side) + double m_T_cold_field_prev; //[K] Colder temperature on field side (opposite tank side) + double m_m_dot_field_prev; //[kg/s] Mass flow rate on field side (opposite tank side) + double m_T_hot_tes_prev; //[K] Hotter temperature on TES side (tank side) + double m_T_cold_tes_prev; //[K] Colder temperature on TES side (tank side) + double m_m_dot_tes_prev; //[kg/s] Mass flow rate on TES side (tank side) + + // Calculated values for current timestep + double m_T_hot_field_calc; //[K] Hotter temperature on field side (opposite tank side) + double m_T_cold_field_calc; //[K] Colder temperature on field side (opposite tank side) + double m_m_dot_field_calc; //[kg/s] Mass flow rate on field side (opposite tank side) + double m_T_hot_tes_calc; //[K] Hotter temperature on TES side (tank side) + double m_T_cold_tes_calc; //[K] Colder temperature on TES side (tank side) + double m_m_dot_tes_calc; //[kg/s] Mass flow rate on TES side (tank side) + + void hx_performance(bool is_hot_side_mdot, bool is_storage_side, + double T_hot_in, double m_dot_known, double T_cold_in, + double& eff, double& T_hot_out, double& T_cold_out, double& q_trans, double& m_dot_solved); + +public: + + C_hx_cold_tes(); + + void init(const HTFProperties& fluid_field, const HTFProperties& fluid_store, double q_transfer_des, + double dt_des, double T_h_in_des, double T_h_out_des); + + void hx_charge_mdot_tes(double T_cold_tes, double m_dot_tes, double T_hot_field, + double& eff, double& T_hot_tes, double& T_cold_field, double& q_trans, double& m_dot_field); + + void hx_discharge_mdot_tes(double T_hot_tes, double m_dot_tes, double T_cold_field, + double& eff, double& T_cold_tes, double& T_hot_field, double& q_trans, double& m_dot_field); + + void hx_charge_mdot_field(double T_hot_field, double m_dot_field, double T_cold_tes, + double& eff, double& T_cold_field, double& T_hot_tes, double& q_trans, double& m_dot_tes); + + void hx_discharge_mdot_field(double T_cold_field, double m_dot_field, double T_hot_tes, + double& eff, double& T_hot_field, double& T_cold_tes, double& q_trans, double& m_dot_tes); +}; + +class C_csp_cold_tes //Class for cold storage based on two tank tes ARD { private: - HTFProperties mc_htf; - double m_V_total; //[m^3] Total volume for *one temperature* tank - double m_V_active; //[m^3] active volume of *one temperature* tank (either cold or hot) - double m_V_inactive; //[m^3] Inactive volume of *one temperature* tank (either cold or hot) - double m_UA; //[W/K] Tank loss conductance + HTFProperties mc_field_htfProps; // Instance of HTFProperties class for field HTF + HTFProperties mc_store_htfProps; // Instance of HTFProperties class for storage HTF - double m_T_htr; //[K] Tank heater set point - double m_max_q_htr; //[MWt] Max tank heater capacity + C_hx_cold_tes mc_hx; - double m_T_design; //[K] Tank design point temperature - double m_mass_total; //[kg] Mass of storage fluid that would fill tank volume at design temperature - double m_mass_inactive; //[kg] Mass of storage fluid at design Temp that fills inactive volume - double m_mass_active; //[kg] Mass of storage fluid at design Temp that fills active volume + //Storage_HX mc_hx_storage; // Instance of Storage_HX class for heat exchanger between storage and field HTFs - // Stored values from end of previous timestep - double m_V_prev; //[m^3] Volume of storage fluid in tank - double m_T_prev; //[K] Temperature of storage fluid in tank - double m_m_prev; //[kg] Mass of storage fluid in tank + C_storage_tank mc_cold_tank; // Instance of storage tank class for the cold tank + C_storage_tank mc_hot_tank; // Instance of storage tank class for the hot tank - // Calculated values for current timestep - double m_V_calc; //[m^3] Volume of storage fluid in tank - double m_T_calc; //[K] Temperature of storage fluid in tank - double m_m_calc; //[kg] Mass of storage fluid in tank + // member string for exception messages + std::string error_msg; + + // Timestep data + double m_m_dot_tes_dc_max; + double m_m_dot_tes_ch_max; + + // Member data + bool m_is_tes; + double m_vol_tank; //[m3] volume of *one temperature*, i.e. vol_tank = total cold storage = total hot storage + double m_V_tank_active; //[m^3] available volume (considering h_min) of *one temperature* + double m_q_pb_design; //[Wt] thermal power to power cycle at design + double m_V_tank_hot_ini; //[m^3] Initial volume in hot storage tank public: - C_storage_tank(); + // Class to save messages for up stream classes + C_csp_messages mc_csp_messages; + + struct S_csp_cold_tes_init_inputs + { + double T_to_cr_at_des; //[K] + double T_from_cr_at_des; //[K] + double P_to_cr_at_des; //[bar] + + S_csp_cold_tes_init_inputs() + { + T_to_cr_at_des = T_from_cr_at_des = P_to_cr_at_des = std::numeric_limits::quiet_NaN(); + } + }; - double calc_mass_at_prev(); + struct S_csp_cold_tes_outputs + { + double m_q_heater; //[MWe] Heating power required to keep tanks at a minimum temperature + double m_m_dot; //[kg/s] Hot tank mass flow rate, valid for direct and indirect systems + double m_W_dot_rhtf_pump; //[MWe] Pumping power, just for tank-to-tank in indirect storage + double m_q_dot_loss; //[MWt] Storage thermal losses + double m_q_dot_dc_to_htf; //[MWt] Thermal power to the HTF from storage + double m_q_dot_ch_from_htf; //[MWt] Thermal power from the HTF to storage + double m_T_hot_ave; //[K] Average hot tank temperature over timestep + double m_T_cold_ave; //[K] Average cold tank temperature over timestep + double m_T_hot_final; //[K] Hot tank temperature at end of timestep + double m_T_cold_final; //[K] Cold tank temperature at end of timestep - double get_m_UA(); + S_csp_cold_tes_outputs() + { + m_q_heater = m_m_dot = m_W_dot_rhtf_pump = m_q_dot_loss = m_q_dot_dc_to_htf = m_q_dot_ch_from_htf = + m_T_hot_ave = m_T_cold_ave = m_T_hot_final = m_T_cold_final = std::numeric_limits::quiet_NaN(); + } + }; - double get_m_T_prev(); + struct S_params + { + int m_field_fl; + util::matrix_t m_field_fl_props; - double get_m_T_calc(); + int m_tes_fl; + util::matrix_t m_tes_fl_props; - double get_m_m_calc(); + bool m_is_hx; - double get_vol_frac(); + double m_W_dot_pc_design; //[MWe] Design point gross power cycle output + double m_eta_pc_factor; //[-] Factor accounting for Design point power cycle thermal efficiency + double m_solarm; //[-] solar multiple + double m_ts_hours; //[hr] hours of storage at design power cycle operation + double m_h_tank; //[m] tank height + double m_u_tank; //[W/m^2-K] + int m_tank_pairs; //[-] + double m_hot_tank_Thtr; //[C] convert to K in init() + double m_hot_tank_max_heat; //[MW] + double m_cold_tank_Thtr; //[C] convert to K in init() + double m_cold_tank_max_heat;//[MW] + double m_dt_hot; //[C] Temperature difference across heat exchanger - assume hot and cold deltaTs are equal + double m_T_cold_des; //[C] convert to K in init() + double m_T_hot_des; //[C] convert to K in init() + double m_T_tank_hot_ini; //[C] Initial temperature in hot storage tank + double m_T_tank_cold_ini; //[C] Initial temperature in cold storage cold + double m_h_tank_min; //[m] Minimum allowable HTF height in storage tank + double m_f_V_hot_ini; //[%] Initial fraction of available volume that is hot - double get_mass_avail(); //[kg] + double m_htf_pump_coef; //[kW/kg/s] Pumping power to move 1 kg/s of HTF through power cycle + + double dT_cw_rad; //[degrees] Temperature change in cooling water for cold storage cooling. + double m_dot_cw_rad; //[kg/sec] Mass flow of cooling water for cold storage cooling at design. + int m_ctes_type; //2= two tank (this model) 3=three node (other model) + double m_dot_cw_cold; //[kg/sec] Mass flow of storage water between cold storage and radiative field HX. + double m_lat; //Latitude [degrees] + + S_params() + { + m_field_fl = m_tes_fl = m_tank_pairs = -1; + m_is_hx = true; + + m_ts_hours = 0.0; //[hr] Default to 0 so that if storage isn't defined, simulation won't crash + m_ctes_type = 0; // Default to <2 so that storage is not assumed in cost calculations. + + m_W_dot_pc_design = m_eta_pc_factor = m_solarm = m_h_tank = m_u_tank = m_hot_tank_Thtr = m_hot_tank_max_heat = m_cold_tank_Thtr = + m_cold_tank_max_heat = m_dt_hot = m_T_cold_des = m_T_hot_des = m_T_tank_hot_ini = + m_T_tank_cold_ini = m_h_tank_min = m_f_V_hot_ini = m_htf_pump_coef = dT_cw_rad = m_dot_cw_rad = m_dot_cw_cold = m_lat = std::numeric_limits::quiet_NaN(); + } + }; + + S_params ms_params; + + C_csp_cold_tes(); - void init(HTFProperties htf_class_in, double V_tank /*m3*/, - double h_tank /*m*/, double h_min /*m*/, double u_tank /*W/m2-K*/, - double tank_pairs /*-*/, double T_htr /*K*/, double max_q_htr /*MWt*/, - double V_ini /*m3*/, double T_ini /*K*/, - double T_design /*K*/); + ~C_csp_cold_tes() {}; - double m_dot_available(double f_unavail, double timestep); + void init(const C_csp_cold_tes::S_csp_cold_tes_init_inputs init_inputs); - void energy_balance(double timestep /*s*/, double m_dot_in /*kg/s*/, double m_dot_out /*kg/s*/, - double T_in /*K*/, double T_amb /*K*/, - double &T_ave /*K*/, double &q_heater /*MW*/, double &q_dot_loss /*MW*/); + bool does_tes_exist(); - void energy_balance_constant_mass(double timestep /*s*/, double m_dot_in, double T_in /*K*/, double T_amb /*K*/, - double &T_ave /*K*/, double &q_heater /*MW*/, double &q_dot_loss /*MW*/); + double get_hot_temp(); + + double get_cold_temp(); + + double get_hot_mass(); + + double get_cold_mass(); + + double get_hot_mass_prev(); + + double get_cold_mass_prev(); + + double get_physical_volume(); //m^3 + + double get_hot_massflow_avail(double step_s); //kg/sec + + double get_cold_massflow_avail(double step_s); //kg/sec + + double get_initial_charge_energy(); //MWh + + double get_min_charge_energy(); //MWh + + double get_max_charge_energy(); //MWh + + double get_degradation_rate(); // s^-1 + + virtual void reset_storage_to_initial_state(); + + void discharge_avail_est(double T_cold_K, double step_s, double& q_dot_dc_est, double& m_dot_field_est, double& T_hot_field_est); + + void charge_avail_est(double T_hot_K, double step_s, double& q_dot_ch_est, double& m_dot_field_est, double& T_cold_field_est); + + // Calculate pumping power...??? + bool discharge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, + double T_htf_cold_in, double& T_htf_hot_out /*K*/, S_csp_cold_tes_outputs& outputs); + + void discharge_full(double timestep /*s*/, double T_amb /*K*/, double T_htf_cold_in, + double& T_htf_hot_out /*K*/, double& m_dot_htf_out /*kg/s*/, S_csp_cold_tes_outputs& outputs); + + bool charge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, + double T_htf_hot_in, double& T_htf_cold_out /*K*/, S_csp_cold_tes_outputs& outputs); + + bool charge_discharge(double timestep /*s*/, double T_amb /*K*/, double m_dot_hot_in /*kg/s*/, + double T_hot_in, double m_dot_cold_in /*kg/s*/, double T_cold_in, S_csp_cold_tes_outputs& outputs); + + bool recirculation(double timestep /*s*/, double T_amb /*K*/, double m_dot_cold_in /*kg/s*/, + double T_cold_in /*K*/, S_csp_cold_tes_outputs& outputs); + + void charge_full(double timestep /*s*/, double T_amb /*K*/, double T_htf_hot_in /*K*/, + double& T_htf_cold_out /*K*/, double& m_dot_htf_out /*kg/s*/, S_csp_cold_tes_outputs& outputs); + + void idle(double timestep, double T_amb, S_csp_cold_tes_outputs& outputs); + + void converged(); + + int pressure_drops(double m_dot_sf, double m_dot_pb, + double T_sf_in, double T_sf_out, double T_pb_in, double T_pb_out, bool recirculating, + double& P_drop_col, double& P_drop_gen); + + double pumping_power(double m_dot_sf, double m_dot_pb, double m_dot_tank, + double T_sf_in, double T_sf_out, double T_pb_in, double T_pb_out, bool recirculating); +}; + +class C_hx_two_tank_tes +{ +private: + HTFProperties mc_external_htfProps; + HTFProperties mc_store_htfProps; + + double m_m_dot_des_ave; //[kg/s] Average (external and storage sides) mass flow rate + double m_eff_des; //[-] Heat exchanger effectiveness + double m_UA_des; //[W/K] Heat exchanger conductance + +public: + + C_hx_two_tank_tes(); + + void init(const HTFProperties &fluid_external, const HTFProperties &fluid_store, double q_transfer_des, + double dt_des, double T_h_in_des, double T_h_out_des); + + void solve(double T_f_htf_hx_in /*K*/, double m_dot_f_htf /*kg/s*/, + double T_s_htf_hx_in /*K*/, double m_dot_s_htf /*kg/s*/, + double & T_f_htf_hx_out /*K*/, double & T_s_htf_hx_out /*K*/, + double & eff /*-*/, double & q_dot_hx /*MWt*/); - void converged(); }; class C_csp_two_tank_tes : public C_csp_tes @@ -155,7 +396,8 @@ class C_csp_two_tank_tes : public C_csp_tes double m_q_pb_design; //[Wt] thermal power to sink at design double m_V_tank_hot_ini; //[m^3] Initial volume in hot storage tank double m_mass_total_active; //[kg] Total HTF mass at design point inlet/outlet T - double m_d_tank; //[m] diameter of a single tank + double m_h_tank_calc; //[m] Actual tank height + double m_d_tank_calc; //[m] Actual tank diameter double m_q_dot_loss_des; //[MWt] design tank heat loss double m_ts_hours; //[hr] hours of storage at design sink operation @@ -164,6 +406,8 @@ class C_csp_two_tank_tes : public C_csp_tes double m_m_dot_tes_des_over_m_dot_external_des; //[-] + + // Used for config with hx double get_tes_m_dot(double m_dot_external /*kg/s*/); //[kg/s] double get_external_m_dot(double m_dot_tes /*kg/s*/); //[kg/s] @@ -182,7 +426,9 @@ class C_csp_two_tank_tes : public C_csp_tes E_MASS_COLD_TANK, //[kg] Mass in cold tank at end of timestep E_MASS_HOT_TANK, //[kg] Mass in hot tank at end of timestep E_HOT_TANK_HTF_PERC_FINAL, //[%] Final percent fill of available hot tank mass - E_W_DOT_HTF_PUMP //[MWe] + E_W_DOT_HTF_PUMP, //[MWe] + E_VOL_TOT, //[m3] Total volume of hot and cold fluid in storage + E_MASS_TOT //[kg] Total mass of hot and cold fluid in storage }; C_csp_two_tank_tes( @@ -193,7 +439,9 @@ class C_csp_two_tank_tes : public C_csp_tes double q_dot_design, // [MWt] Design heat rate in and out of tes double frac_max_q_dot, // [-] the max design heat rate as a fraction of the nominal double Q_tes_des, // [MWt-hr] design storage capacity - double h_tank, // [m] tank height + bool is_h_fixed, // [] [true] Height is input, calculate diameter, [false] diameter input, calculate height + double h_tank_in, // [m] tank height input + double d_tank_in, // [m] tank diameter input double u_tank, // [W/m^2-K] int tank_pairs, // [-] double hot_tank_Thtr, // [C] convert to K in init() @@ -232,7 +480,9 @@ class C_csp_two_tank_tes : public C_csp_tes double m_q_dot_design; //[MWe] Design heat rate in and out of tes double m_frac_max_q_dot; //[-] the max design heat rate as a fraction of the nominal double m_Q_tes_des; //[MWt-hr] design storage capacity - double m_h_tank; //[m] tank height + bool m_is_h_fixed; // [] [true] Height is input, calculate diameter, [false] diameter input, calculate height + double m_h_tank_in; //[m] tank height input + double m_d_tank_in; //[m] tank diameter input double m_u_tank; //[W/m^2-K] int m_tank_pairs; //[-] double m_hot_tank_Thtr; //[C] convert to K in init() @@ -264,6 +514,14 @@ class C_csp_two_tank_tes : public C_csp_tes double pipe_rough; //[m] Pipe absolute roughness double dP_discharge; //[bar] Pressure drop on the TES discharge side (e.g., within the steam generator) + util::matrix_t pipe_diams; //[m^3] + util::matrix_t pipe_wall_thk; //[m] + util::matrix_t pipe_lengths; //[m] + util::matrix_t pipe_m_dot_des; //[kg/s] + util::matrix_t pipe_vel_des; //[m/s] + util::matrix_t pipe_T_des; //[C] + util::matrix_t pipe_P_des; //[bar] + C_csp_reported_outputs mc_reported_outputs; C_csp_two_tank_tes(); @@ -274,13 +532,6 @@ class C_csp_two_tank_tes : public C_csp_tes double pipe_vol_tot; //[m^3] util::matrix_t pipe_v_dot_rel; //[-] - util::matrix_t pipe_diams; //[m^3] - util::matrix_t pipe_wall_thk; //[m] - util::matrix_t pipe_lengths; //[m] - util::matrix_t pipe_m_dot_des; //[kg/s] - util::matrix_t pipe_vel_des; //[m/s] - util::matrix_t pipe_T_des; //[C] - util::matrix_t pipe_P_des; //[bar] double P_in_des; //[bar] Pressure at the inlet to the TES, at the external system side virtual bool does_tes_exist(); @@ -330,6 +581,7 @@ class C_csp_two_tank_tes : public C_csp_tes virtual void converged(); + // Missing (not used?) void get_final_from_converged(double& f_V_hot /*-*/, double& T_hot_tank /*K*/, double& T_cold_tank /*K*/); virtual void write_output_intervals(double report_time_start, @@ -344,10 +596,10 @@ class C_csp_two_tank_tes : public C_csp_tes virtual /*MWe*/ double pumping_power(double m_dot_sf /*kg/s*/, double m_dot_pb /*kg/s*/, double m_dot_tank /*kg/s*/, double T_sf_in /*K*/, double T_sf_out /*K*/, double T_pb_in /*K*/, double T_pb_out /*K*/, bool recirculating); - void get_design_parameters(double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, double& d_tank /*m*/, + void get_design_parameters(double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, + double& h_tank_calc /*m*/, double& d_tank_calc /*m*/, double& q_dot_loss_des /*MWt*/, double& dens_store_htf_at_T_ave /*kg/m3*/, double& Q_tes /*MWt-hr*/); - double get_max_storage_htf_temp(); double get_min_storage_htf_temp(); @@ -356,262 +608,9 @@ class C_csp_two_tank_tes : public C_csp_tes double get_storage_htf_cp(); + // Missing bool get_is_hx(); }; -class C_hx_cold_tes -{ -private: - HTFProperties mc_field_htfProps; - HTFProperties mc_store_htfProps; - - double m_m_dot_des_ave; //[kg/s] Average (field and storage sides) mass flow rate - double m_eff_des; //[-] Heat exchanger effectiveness - double m_UA_des; //[W/K] Heat exchanger conductance - - // Stored values from previous timestep - double m_T_hot_field_prev; //[K] Hotter temperature on field side (opposite tank side) - double m_T_cold_field_prev; //[K] Colder temperature on field side (opposite tank side) - double m_m_dot_field_prev; //[kg/s] Mass flow rate on field side (opposite tank side) - double m_T_hot_tes_prev; //[K] Hotter temperature on TES side (tank side) - double m_T_cold_tes_prev; //[K] Colder temperature on TES side (tank side) - double m_m_dot_tes_prev; //[kg/s] Mass flow rate on TES side (tank side) - - // Calculated values for current timestep - double m_T_hot_field_calc; //[K] Hotter temperature on field side (opposite tank side) - double m_T_cold_field_calc; //[K] Colder temperature on field side (opposite tank side) - double m_m_dot_field_calc; //[kg/s] Mass flow rate on field side (opposite tank side) - double m_T_hot_tes_calc; //[K] Hotter temperature on TES side (tank side) - double m_T_cold_tes_calc; //[K] Colder temperature on TES side (tank side) - double m_m_dot_tes_calc; //[kg/s] Mass flow rate on TES side (tank side) - - void hx_performance(bool is_hot_side_mdot, bool is_storage_side, - double T_hot_in, double m_dot_known, double T_cold_in, - double& eff, double& T_hot_out, double& T_cold_out, double& q_trans, double& m_dot_solved); - -public: - - C_hx_cold_tes(); - - void init(const HTFProperties& fluid_field, const HTFProperties& fluid_store, double q_transfer_des, - double dt_des, double T_h_in_des, double T_h_out_des); - - void hx_charge_mdot_tes(double T_cold_tes, double m_dot_tes, double T_hot_field, - double& eff, double& T_hot_tes, double& T_cold_field, double& q_trans, double& m_dot_field); - - void hx_discharge_mdot_tes(double T_hot_tes, double m_dot_tes, double T_cold_field, - double& eff, double& T_cold_tes, double& T_hot_field, double& q_trans, double& m_dot_field); - - void hx_charge_mdot_field(double T_hot_field, double m_dot_field, double T_cold_tes, - double& eff, double& T_cold_field, double& T_hot_tes, double& q_trans, double& m_dot_tes); - - void hx_discharge_mdot_field(double T_cold_field, double m_dot_field, double T_hot_tes, - double& eff, double& T_hot_field, double& T_cold_tes, double& q_trans, double& m_dot_tes); -}; - -class C_csp_cold_tes //Class for cold storage based on two tank tes ARD -{ -private: - - HTFProperties mc_field_htfProps; // Instance of HTFProperties class for field HTF - HTFProperties mc_store_htfProps; // Instance of HTFProperties class for storage HTF - - C_hx_cold_tes mc_hx; - - //Storage_HX mc_hx_storage; // Instance of Storage_HX class for heat exchanger between storage and field HTFs - - C_storage_tank mc_cold_tank; // Instance of storage tank class for the cold tank - C_storage_tank mc_hot_tank; // Instance of storage tank class for the hot tank - - // member string for exception messages - std::string error_msg; - - // Timestep data - double m_m_dot_tes_dc_max; - double m_m_dot_tes_ch_max; - - // Member data - bool m_is_tes; - double m_vol_tank; //[m3] volume of *one temperature*, i.e. vol_tank = total cold storage = total hot storage - double m_V_tank_active; //[m^3] available volume (considering h_min) of *one temperature* - double m_q_pb_design; //[Wt] thermal power to power cycle at design - double m_V_tank_hot_ini; //[m^3] Initial volume in hot storage tank - -public: - - // Class to save messages for up stream classes - C_csp_messages mc_csp_messages; - - struct S_csp_cold_tes_init_inputs - { - double T_to_cr_at_des; //[K] - double T_from_cr_at_des; //[K] - double P_to_cr_at_des; //[bar] - - S_csp_cold_tes_init_inputs() - { - T_to_cr_at_des = T_from_cr_at_des = P_to_cr_at_des = std::numeric_limits::quiet_NaN(); - } - }; - - struct S_csp_cold_tes_outputs - { - double m_q_heater; //[MWe] Heating power required to keep tanks at a minimum temperature - double m_m_dot; //[kg/s] Hot tank mass flow rate, valid for direct and indirect systems - double m_W_dot_rhtf_pump; //[MWe] Pumping power, just for tank-to-tank in indirect storage - double m_q_dot_loss; //[MWt] Storage thermal losses - double m_q_dot_dc_to_htf; //[MWt] Thermal power to the HTF from storage - double m_q_dot_ch_from_htf; //[MWt] Thermal power from the HTF to storage - double m_T_hot_ave; //[K] Average hot tank temperature over timestep - double m_T_cold_ave; //[K] Average cold tank temperature over timestep - double m_T_hot_final; //[K] Hot tank temperature at end of timestep - double m_T_cold_final; //[K] Cold tank temperature at end of timestep - - S_csp_cold_tes_outputs() - { - m_q_heater = m_m_dot = m_W_dot_rhtf_pump = m_q_dot_loss = m_q_dot_dc_to_htf = m_q_dot_ch_from_htf = - m_T_hot_ave = m_T_cold_ave = m_T_hot_final = m_T_cold_final = std::numeric_limits::quiet_NaN(); - } - }; - - struct S_params - { - int m_field_fl; - util::matrix_t m_field_fl_props; - - int m_tes_fl; - util::matrix_t m_tes_fl_props; - - bool m_is_hx; - - double m_W_dot_pc_design; //[MWe] Design point gross power cycle output - double m_eta_pc_factor; //[-] Factor accounting for Design point power cycle thermal efficiency - double m_solarm; //[-] solar multiple - double m_ts_hours; //[hr] hours of storage at design power cycle operation - double m_h_tank; //[m] tank height - double m_u_tank; //[W/m^2-K] - int m_tank_pairs; //[-] - double m_hot_tank_Thtr; //[C] convert to K in init() - double m_hot_tank_max_heat; //[MW] - double m_cold_tank_Thtr; //[C] convert to K in init() - double m_cold_tank_max_heat;//[MW] - double m_dt_hot; //[C] Temperature difference across heat exchanger - assume hot and cold deltaTs are equal - double m_T_cold_des; //[C] convert to K in init() - double m_T_hot_des; //[C] convert to K in init() - double m_T_tank_hot_ini; //[C] Initial temperature in hot storage tank - double m_T_tank_cold_ini; //[C] Initial temperature in cold storage cold - double m_h_tank_min; //[m] Minimum allowable HTF height in storage tank - double m_f_V_hot_ini; //[%] Initial fraction of available volume that is hot - - double m_htf_pump_coef; //[kW/kg/s] Pumping power to move 1 kg/s of HTF through power cycle - - double dT_cw_rad; //[degrees] Temperature change in cooling water for cold storage cooling. - double m_dot_cw_rad; //[kg/sec] Mass flow of cooling water for cold storage cooling at design. - int m_ctes_type; //2= two tank (this model) 3=three node (other model) - double m_dot_cw_cold; //[kg/sec] Mass flow of storage water between cold storage and radiative field HX. - double m_lat; //Latitude [degrees] - - S_params() - { - m_field_fl = m_tes_fl = m_tank_pairs = -1; - m_is_hx = true; - - m_ts_hours = 0.0; //[hr] Default to 0 so that if storage isn't defined, simulation won't crash - m_ctes_type = 0; // Default to <2 so that storage is not assumed in cost calculations. - - m_W_dot_pc_design = m_eta_pc_factor = m_solarm = m_h_tank = m_u_tank = m_hot_tank_Thtr = m_hot_tank_max_heat = m_cold_tank_Thtr = - m_cold_tank_max_heat = m_dt_hot = m_T_cold_des = m_T_hot_des = m_T_tank_hot_ini = - m_T_tank_cold_ini = m_h_tank_min = m_f_V_hot_ini = m_htf_pump_coef= dT_cw_rad=m_dot_cw_rad=m_dot_cw_cold=m_lat= std::numeric_limits::quiet_NaN(); - } - }; - - S_params ms_params; - - C_csp_cold_tes(); - - ~C_csp_cold_tes() {}; - - void init(const C_csp_cold_tes::S_csp_cold_tes_init_inputs init_inputs); - - bool does_tes_exist(); - - double get_hot_temp(); - - double get_cold_temp(); - - double get_hot_mass(); - - double get_cold_mass(); - - double get_hot_mass_prev(); - - double get_cold_mass_prev(); - - double get_physical_volume(); //m^3 - - double get_hot_massflow_avail(double step_s); //kg/sec - - double get_cold_massflow_avail(double step_s); //kg/sec - - double get_initial_charge_energy(); //MWh - - double get_min_charge_energy(); //MWh - - double get_max_charge_energy(); //MWh - - double get_degradation_rate(); // s^-1 - - virtual void reset_storage_to_initial_state(); - - void discharge_avail_est(double T_cold_K, double step_s, double &q_dot_dc_est, double &m_dot_field_est, double &T_hot_field_est); - - void charge_avail_est(double T_hot_K, double step_s, double &q_dot_ch_est, double &m_dot_field_est, double &T_cold_field_est); - - // Calculate pumping power...??? - bool discharge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, - double T_htf_cold_in, double & T_htf_hot_out /*K*/, S_csp_cold_tes_outputs &outputs); - - void discharge_full(double timestep /*s*/, double T_amb /*K*/, double T_htf_cold_in, - double & T_htf_hot_out /*K*/, double & m_dot_htf_out /*kg/s*/, S_csp_cold_tes_outputs &outputs); - - bool charge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, - double T_htf_hot_in, double & T_htf_cold_out /*K*/, S_csp_cold_tes_outputs &outputs); - - bool charge_discharge(double timestep /*s*/, double T_amb /*K*/, double m_dot_hot_in /*kg/s*/, - double T_hot_in, double m_dot_cold_in /*kg/s*/, double T_cold_in, S_csp_cold_tes_outputs &outputs); - - bool recirculation(double timestep /*s*/, double T_amb /*K*/, double m_dot_cold_in /*kg/s*/, - double T_cold_in /*K*/, S_csp_cold_tes_outputs &outputs); - - void charge_full(double timestep /*s*/, double T_amb /*K*/, double T_htf_hot_in /*K*/, - double & T_htf_cold_out /*K*/, double & m_dot_htf_out /*kg/s*/, S_csp_cold_tes_outputs &outputs); - - void idle(double timestep, double T_amb, S_csp_cold_tes_outputs &outputs); - - void converged(); - - int pressure_drops(double m_dot_sf, double m_dot_pb, - double T_sf_in, double T_sf_out, double T_pb_in, double T_pb_out, bool recirculating, - double &P_drop_col, double &P_drop_gen); - - double pumping_power(double m_dot_sf, double m_dot_pb, double m_dot_tank, - double T_sf_in, double T_sf_out, double T_pb_in, double T_pb_out, bool recirculating); -}; - - -void two_tank_tes_sizing(HTFProperties &tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, - double T_tes_cold /*K*/, double h_min /*m*/, double h_tank /*m*/, int tank_pairs /*-*/, double u_tank /*W/m^2-K*/, - double & vol_one_temp_avail /*m3*/, double & vol_one_temp_total /*m3*/, double & d_tank /*m*/, - double & q_dot_loss_des /*MWt*/ ); - -int size_tes_piping(double vel_dsn, util::matrix_t L, double rho_avg, double m_dot_pb, double solarm, - bool tanks_in_parallel, double &vol_tot, util::matrix_t &v_dot_rel, util::matrix_t &diams, - util::matrix_t &wall_thk, util::matrix_t &m_dot, util::matrix_t &vel, bool custom_sizes = false); - -int size_tes_piping_TandP(HTFProperties &external_htf_props, double T_src_in /*K*/, double T_src_out /*K*/, double P_src_in /*Pa*/, double dP_discharge, - const util::matrix_t &L, const util::matrix_t &k_tes_loss_coeffs, double pipe_rough, - bool tanks_in_parallel, const util::matrix_t &diams, const util::matrix_t &vel, - util::matrix_t &TES_T_des, util::matrix_t &TES_P_des, double &TES_P_in); - #endif //__csp_solver_two_tank_tes_ diff --git a/test/input_cases/trough_physical_defaults.h b/test/input_cases/trough_physical_defaults.h index 38e3268a2..959ff03b6 100644 --- a/test/input_cases/trough_physical_defaults.h +++ b/test/input_cases/trough_physical_defaults.h @@ -236,7 +236,7 @@ ssc_data_t trough_physical_defaults() ssc_data_set_matrix(data, "store_fl_props", p_store_fl_props, 1, 1); ssc_data_set_number(data, "is_hx", 1); ssc_data_set_number(data, "tshours", 6); - ssc_data_set_number(data, "h_tank", 12); + ssc_data_set_number(data, "h_tank_in", 12); ssc_data_set_number(data, "u_tank", 0.40000000000000002); ssc_data_set_number(data, "tank_pairs", 1); ssc_data_set_number(data, "hot_tank_Thtr", 365); diff --git a/test/input_cases/trough_physical_iph_defaults.h b/test/input_cases/trough_physical_iph_defaults.h index dc9805e50..bb7b45fb9 100644 --- a/test/input_cases/trough_physical_iph_defaults.h +++ b/test/input_cases/trough_physical_iph_defaults.h @@ -196,7 +196,7 @@ ssc_data_t trough_physical_iph_defaults() ssc_data_set_array(data, "SCADefocusArray", p_SCADefocusArray, 4); ssc_data_set_number(data, "pb_pump_coef", 0.55000000000000004); ssc_data_set_number(data, "init_hot_htf_percent", 30); - ssc_data_set_number(data, "h_tank", 15); + ssc_data_set_number(data, "h_tank_in", 15); ssc_data_set_number(data, "cold_tank_max_heat", 0.5); ssc_data_set_number(data, "u_tank", 0.29999999999999999); ssc_data_set_number(data, "tank_pairs", 1); diff --git a/test/shared_test/lib_csp_tes_test.cpp b/test/shared_test/lib_csp_tes_test.cpp index 36d9d487f..79f723bbf 100644 --- a/test/shared_test/lib_csp_tes_test.cpp +++ b/test/shared_test/lib_csp_tes_test.cpp @@ -200,7 +200,9 @@ NAMESPACE_TEST(csp_common, TesCspSolver, Default) 311.8, //[MWt] 2, //[-] 311.8 * 6, //[MWht] + true, //[-] 12, //[m] + 0.0, //[m] 0.4, //[W/m^2-K] 1, //[-] 365, //[C] @@ -356,7 +358,7 @@ NAMESPACE_TEST(csp_common, TesSubcomponentCmod, Default) ssc_data_set_number(inputs, "eta_ref", 0.356); ssc_data_set_number(inputs, "solar_mult", 2.); ssc_data_set_number(inputs, "tshours", 6.); - ssc_data_set_number(inputs, "h_tank", 12.); + ssc_data_set_number(inputs, "h_tank_in", 12.); ssc_data_set_number(inputs, "u_tank", 0.4); ssc_data_set_number(inputs, "tank_pairs", 1.); ssc_data_set_number(inputs, "hot_tank_Thtr", 365.); From 28c2f2074cf2a48387cd191b76bd125c839e94c7 Mon Sep 17 00:00:00 2001 From: Taylor Brown Date: Tue, 17 Sep 2024 13:30:20 -0600 Subject: [PATCH 40/85] Squashed commit of the following: commit 943fb726fc76f6d09fc37b67614842983475a1b4 Author: Taylor Brown Date: Mon Sep 16 14:30:57 2024 -0600 Add nt and pb TES models to trough iph. commit d3b209fda4ba29ef8ac2c3b568428ce45d3584af Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Sep 16 11:38:39 2024 -0600 Isolate TES specific variables to correct tes_type. Remove internal fluid input from NT (same as external). commit 56b30568dc7892e9df914d4c065ccfee039f9a2d Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Sep 16 09:13:24 2024 -0600 Update NT variable names. commit 2013681aca423757bc0b6c444275b8f17b38dddc Author: Taylor Brown Date: Sat Sep 14 15:23:24 2024 -0600 Implement packed bed dispatch methods. commit a71d36735397e8e7d0abc5622547ee7c106f439e Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Sep 12 15:31:41 2024 -0600 Fix csp trough tes bug from merge. commit e7217e19aba140385572bba0d96abdb03c663792 Author: Taylor Brown Date: Fri Aug 30 11:10:59 2024 -0600 Combine NT and packed bed subtimestep variable commit 7ccdbf609e224e48f02c6e21f2f6f7b47d12354a Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Sep 4 10:08:56 2024 -0600 Change packed bed cp unit to kJ/kg K. commit e2745551ea5730246b41fcb523949a73db567c21 Author: Taylor Brown Date: Tue Sep 3 14:31:07 2024 -0600 Swap tes_type integers of nt and packed bed commit f980d06bba3d3b5427324cef626fd25af83e4a10 Merge: 2fbda919f e8f8d3eab Author: Taylor Brown Date: Mon Sep 9 20:36:20 2024 -0600 Merge branch 'nt_suntrap_uptodate' into tes_pb_nt commit e8f8d3eab4f31fff0f793a6e88b4f3b6e8ecc00a Author: Taylor Brown Date: Mon Sep 9 13:33:55 2024 -0600 Add T_startup and shutdown to iph trough cmod. commit c5ec78660d0537bb9b1dff00f3fde342e28f9e23 Author: Taylor Brown Date: Wed Sep 4 16:52:44 2024 -0600 Update csp subcomponent test inputs and vartable require_if. commit 2779bf7d7dde00700684ce124fd1882c92a2641d Merge: 21f6cd01f 274560a9e Author: Taylor Brown Date: Wed Sep 4 15:56:52 2024 -0600 Merge branch 'develop' into nt_suntrap_uptodate commit 21f6cd01f20d259f547633d3abb9421af7937a86 Author: Taylor Brown Date: Wed Sep 4 13:28:00 2024 -0600 Fix required vartable variables for ssc tests. commit 2fbda919f981affdae0c930908a9448832a8fede Author: Taylor Brown Date: Fri Aug 30 11:10:59 2024 -0600 Combine NT and packed bed subtimestep variable commit 892d5cd252b83393144a14c9eae720cc2f39886a Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Aug 29 14:55:26 2024 -0600 Add startup/shutdown temps to IPH trough cmod. commit 7c6f40a090113320b168c64812107657b1cd4e37 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Aug 29 13:06:47 2024 -0600 Fix MSPT two tank bug. Fix trough CSP formatting bug from merge. commit 0d71c7fa10e5cf1ad5452bae495ff3bb04a7aad6 Merge: 539564b39 5b0ce64f9 Author: Taylor Brown Date: Thu Aug 29 11:17:26 2024 -0600 Merge branch 'nt_suntrap' into tes_pb_nt commit 5b0ce64f9aa6b06ab412eb07002efd05e2af6236 Author: Taylor Brown Date: Thu Aug 29 11:16:52 2024 -0600 Isolate custom pressure losses to only two tank tes. commit 539564b390e83fb19dfb620d1587df725aa6c895 Merge: 880607ae9 274560a9e Author: Taylor Brown Date: Thu Aug 29 09:13:49 2024 -0600 Merge branch 'develop' into tes_pb_nt commit 880607ae92b8049b69f3a59e373f7dc35f1d5e8b Merge: 7e6ef0fa7 36e7587ee Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 28 16:37:32 2024 -0600 Merge branch 'nt_suntrap' into tes_pb_nt commit 7e6ef0fa78b48f1377b6868b3e0bdb7a496fa1d9 Merge: d09c280ad f2d6aedd3 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 28 11:11:33 2024 -0600 Merge branch 'patch' into tes_pb_nt commit 36e7587eef07b26755556b3458cf3c800544a669 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 28 11:03:53 2024 -0600 Remove unused member variables from NT TES. commit 805763fbc9a5af59ee04e19f12d73368677d1618 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 28 10:23:02 2024 -0600 Remove unused NT TES variables. commit 9a991fdf17888b04c3429ed48871ff4ead4a1da7 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 28 10:00:38 2024 -0600 Remove hot and cold individual error calc outputs from NT TES. commit d09c280ad885131cab431839298d97d22450ad7e Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Aug 26 15:28:45 2024 -0600 Update htf properties during each subtimestep for packed bed tes commit 59f3310febc6a7e67ac60b0f2db25495fca98b31 Author: Taylor Brown Date: Thu Aug 22 11:09:15 2024 -0600 Add pumping power calc to packed bed tes commit e8160d90eba3cb40801178159f4e00252f947dfc Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Aug 19 11:12:42 2024 -0600 Make size of subtimestep consistent, regardless of timestep size. commit 17d2a500d823e19958ebbe9d7af43a1b4f88a568 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Aug 19 09:07:44 2024 -0600 Separate charge/discharge temperature estimate temperature with allowable charge temp. commit 15351cc877fa070c5084fbed4778fe1b3d8db88f Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 14 17:05:09 2024 -0600 Add average temperature gradient results to cmod. Remove full temperature gradient output. commit 67b162d533da55a76db71bca4fc4a1a960b96c66 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 14 11:45:53 2024 -0600 Fix thermal capacity reported output for packed bed. commit cfd202d2c2b9dbe94609509b37037ccf318d7f5d Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 14 11:22:34 2024 -0600 Add htf thermal mass to sizing and energy estimates. commit 3684c31f08d8481d57ee6eeec433b851875c71b1 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue Aug 13 11:48:12 2024 -0600 Add charge inlet temperature check. Adjust charge/discharge energy estimate. Change outlet gradient to K commit 14b5760691fd964a6c79eab3ae6db1f676b391bd Author: Taylor Brown Date: Mon Aug 12 15:23:10 2024 -0600 Add temporary temperature gradient output. commit c9479bc6277fda1071769a428e93c6e5328dd837 Author: Taylor Brown Date: Mon Aug 12 14:32:28 2024 -0600 Fix packed bed result reporting bug. commit 6dc7c373faaecc49caf5b15943c89c0fd8cc0c2d Author: Taylor Brown Date: Sun Aug 11 12:12:42 2024 -0600 Add packed bed outputs. commit 20b8fbb08b0d10b7c9e21679f6ced68ab3b6bb39 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Aug 8 16:57:59 2024 -0600 Add packed bed tes to trough CSP cmod. Update energy estimates and initial temp calculations. commit f0c146e12c47be62c55a778f82eb42d67003badd Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Aug 8 10:23:47 2024 -0600 Rename packed bed cmod inputs. commit a013cd2e99258882a6518af1229fd6fe8f6b7704 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 7 16:56:14 2024 -0600 Include oversize factor in sizing calculation. commit c8ad72d02ca9f9ce9ae29158e7989f1b4d33c0d4 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 7 16:53:30 2024 -0600 Implement packed bed sizing. commit 9672907c3d349e2ab09b672151ca99ccd110a170 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Aug 7 15:05:10 2024 -0600 Remove unused NT methods. commit 4b1596ad3d4f1a9a1da8fc4214c07efc1b2c3535 Author: Taylor Brown Date: Tue Aug 6 14:18:34 2024 -0600 Implement charge/discharge estimate equations. commit fe9e7823577572fe50d0351f29effd40cfd57923 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue Aug 6 08:38:36 2024 -0600 Rename packed bed tes commit b58e7c130343cb573d9ec44b0f9cdf98f3c371f1 Merge: 449407850 d43113ceb Author: Taylor Brown Date: Thu Jul 25 10:56:13 2024 -0600 Merge branch 'nt_suntrap' into nt_suntrap_startup commit d43113cebe457a1f15d76131c84da5f16e500b20 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri Jun 28 16:29:57 2024 -0600 Add helpful outputs to csp subcomponent. commit 0df20f673c3fa1da8dff5d91ace986ab963d39b3 Author: Taylor Brown Date: Thu Jun 27 14:15:35 2024 -0600 Add hot tank mass percent to output. Add tes height to cmod. commit e721c499e353fa2eaa01e01875efe4102a74bceb Merge: 14c0f6aaf 06750a976 Author: Taylor Brown Date: Tue Jun 25 15:41:04 2024 -0600 Merge branch 'csp_hourly_schedule' into nt_suntrap commit 06750a9767a2881cd6060254b5f0838c35907420 Author: Taylor Brown Date: Tue Jun 25 15:19:00 2024 -0600 Add hourly turbine output option to csp MSPT. commit 3a7a18b8417b29036af19b1c019934ffa4802b29 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue Jun 25 11:06:04 2024 -0600 Add hourly timestep fraction option to CSP linear fresnel commit a09f30aa660c8151bed35fa37e299abe7971729b Author: Taylor Brown Date: Mon Jun 24 10:40:25 2024 -0600 Update timestep_load_fractions to follow IPH process commit 41d167afa9303bf80afe977f904f560547973b49 Author: Taylor Brown Date: Mon Jun 24 09:56:25 2024 -0600 Add bool input for hourly timestep load fractions for csp trough. commit 14c0f6aaf9fa0757496f9339281aeebf23d8f9f4 Author: Taylor Brown Date: Wed Jun 19 11:19:41 2024 -0600 Fix merge bugs. commit e5902c4fa9c213f3eedf42d88b63efde6671b897 Merge: b025005ac ec251419d Author: Taylor Brown Date: Tue Jun 18 11:33:33 2024 -0600 Merge branch 'develop' into nt_suntrap commit f76a6a14da33df0ce14039a600a1e07128084e12 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Jun 17 15:50:19 2024 -0600 Add particle properties to cmod. Fix bug with superficial velocity and cp. commit f97a95d5f49476657c790e41ea2f3dc5146c698d Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri Jun 14 13:42:47 2024 -0600 Thread particle properties inputs through cmod commit 83dd21971ecbc49d209f5fec3087179a2c9c807a Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri Jun 14 13:08:40 2024 -0600 Add height as input to particle model commit bfedef1aceaf9188e3f61bc2a92a7b77a30db4f6 Author: Taylor Brown Date: Fri Jun 14 08:16:58 2024 -0600 Thread tes outputs out of particle class. commit fc448bbc6c48d5c8d214ee21ae12469d2d6657f5 Author: Taylor Brown Date: Thu Jun 13 13:09:43 2024 -0600 Add tes_pump_coef from cmod commit c0a8ad8918047f925439b58363e49d8b89f62924 Author: Taylor Brown Date: Thu Jun 13 13:08:06 2024 -0600 Finish adding outputs to Charge and Discharge calc. commit cd1b128cd95a9d688465d6225c681800c9f4d1d2 Author: Taylor Brown Date: Wed Jun 12 08:31:49 2024 -0600 Fix temperature gradient unit bug. commit 5a88a630be91517e499650c34aa42868d7415b45 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Jun 10 15:24:29 2024 -0600 Add initial temperature gradient option commit b025005ac1c4859a2cd04b218b239d7f482163da Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Jun 10 09:14:51 2024 -0600 Calculate 'main tank' volume using hot density. commit 4af2cf3fbe2fac662e0967fa36e90846cae773a2 Author: Taylor Brown Date: Wed Jun 5 09:31:47 2024 -0600 Add temperature gradient to output. commit d03536192ed88168697e8cdd6671e0b162042fb8 Author: Taylor Brown Date: Mon Jun 3 20:17:02 2024 -0600 Remove analytical calculations from isolated TES cmod. commit 5667e30021f827cdf9afcc8e9a216ff449241924 Author: Taylor Brown Date: Mon Jun 3 20:14:17 2024 -0600 Add tank wall density modifier to account for insulation. commit 7ed61aa8242df8925c837261b1669decc98d5602 Author: Taylor Brown Date: Mon Jun 3 19:21:09 2024 -0600 Add simple discharge cycle. commit 0136e7b8d47b61ee3bde92db9d1c87dd9ce037c5 Author: Taylor Brown Date: Mon Jun 3 12:57:02 2024 -0600 Change NT sizing to special function based on cold density. commit f873eda6888e0601a87971d5fa6f806fed6b47aa Author: Taylor Brown Date: Mon Jun 3 12:03:40 2024 -0600 Add expansion tank calculations. commit 9227784ae5088beda72c2d1150bb730c0e498aba Author: Taylor Brown Date: Mon Jun 3 10:26:35 2024 -0600 Update active fluid mass calculation, to fill with cold. Add nominal wall volume and mass members. commit c33407dc93104c9e9bfb54e8cdf8fef93601b0e3 Author: Taylor Brown Date: Sun Jun 2 09:36:42 2024 -0600 Update TES csp subcomponent to allow height or diameter sizing. commit 70b388ec18315b572d88eea9841e56513a3e5700 Author: Taylor Brown Date: Fri May 31 14:32:22 2024 -0600 Remove all thermocline inputs. Get simple charge case running. commit b54b194c7a466a4f6f959f477477f8363a9932ba Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue May 28 15:48:49 2024 -0600 Set TES fluid mass using cold density, rather than avg. commit 11da5de3c7a7c04ed9cbfde1b656f792644bcb54 Author: Taylor Brown Date: Tue May 21 18:28:23 2024 -0600 Adjust core calculation to fix wall cp issue. commit 7c03e71382f5dba840860677e5a8a4d9b5eb7e2f Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue May 21 10:57:39 2024 -0600 Remove unused variables in thermocline constructor. Add new thermocline model to csp subcomponent cmod. commit c50bf16600a17b1a5c4dfa1d4e62e76ff260a381 Author: Taylor Brown Date: Mon May 20 09:13:27 2024 -0600 Begin creating class for particle thermocline TES commit 85c36a46d92e7fecb2a599614610ea9bc6c07377 Merge: 5e4136d75 ec424fb2d Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed May 15 11:12:17 2024 -0600 Merge branch 'nt_suntrap' into tes_particle_therm commit ec424fb2dbdcc6092bceaea765b9fb3b7433dfee Author: Taylor Brown Date: Tue May 14 10:37:05 2024 -0600 Begin investigating wall cp issue. commit 81a7d7ac7ad491fd4293873e64dab8186aa245fb Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon May 13 09:04:08 2024 -0600 Clean code. commit d0c6cb73d1251cc5a7dd7eab788ece278d5d7224 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon May 13 08:49:10 2024 -0600 Move error calculations to inside iteration. commit b3f48ff3e0204c41c243561d4bed2bca63384664 Author: Taylor Brown Date: Sun May 12 16:32:54 2024 -0600 Fix wall error calculation. Add energy balance error adjusted for wall and leakage. commit 0f31db453dae43aea4cd5c233f1853ef2fe53b7b Author: Taylor Brown Date: Sun May 12 11:13:23 2024 -0600 Change energy balance error percent to divide by design charge power. commit 7270baf5516835a5d4177471faedcd0671e75b43 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri May 10 10:58:07 2024 -0600 Add wall temperature assumption error to outputs. commit faa698dccd451d584b8023a540df6bb83f996522 Author: Taylor Brown Date: Wed May 8 15:23:16 2024 -0600 Add initial temps to storage isolated cmod. Add error output accounting for leakage assumption. commit 34b1ec1910b2bd80c63eaf0a3f43d77af9232251 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue May 7 11:11:14 2024 -0600 Add TES energy balance errors. commit d57e4c34b1d2583afc58ffe9d819c620a53385d7 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri May 3 14:55:35 2024 -0600 Add energy balance error to cmod outputs. commit 17fea126b54793d7f51fc8fd41851e7beea48d34 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Apr 8 10:46:57 2024 -0600 Remove debug code. commit 449407850798d7649aec1f68136d79faddd5ab3f Merge: d89e99cc4 ad55aea77 Author: Taylor Brown Date: Thu Mar 28 13:20:48 2024 -0600 Merge branch 'trough_startup_temp' into nt_suntrap_startup commit d89e99cc4744f3eb6a7f4c8d076827df2ba4661f Merge: 284b73d5d 990f35172 Author: Taylor Brown Date: Thu Mar 28 12:55:01 2024 -0600 Merge branch 'patch' into nt_suntrap_startup commit ad55aea77c0d2f9ca2b4e91029afb0859b0192f9 Author: Taylor Brown Date: Tue Mar 26 12:56:50 2024 -0600 Add startup temp and shutdown temp as user inputs. commit 284b73d5de422469302e31e0e05d6422aeae8071 Author: Taylor Brown Date: Tue Mar 26 08:29:58 2024 -0600 Remove serial tanks option for heattrap tes commit d2916633474a88f5aca627d5df0866a892297310 Author: Taylor Brown Date: Sat Mar 16 15:55:58 2024 -0600 Add option to TES to specify diameter, rather than height. commit 851d4a76d002f9fb4946f3e8084471fadfc26b99 Merge: c5eaca6fd 028722445 Author: Taylor Brown Date: Mon Mar 11 11:26:46 2024 -0600 Merge branch 'patch' into nt_suntrap commit c5eaca6fd1e5def708f353555053523f59aab0ff Author: Taylor Brown Date: Tue Feb 20 17:28:04 2024 -0700 Fix Ave Temp bug commit 9d6e8bde072e9344d4564d3828b308cfee25dc37 Author: Taylor Brown Date: Tue Feb 20 16:07:40 2024 -0700 Add TES error percent to outputs. commit 60d75fb4903fc3a2581aa1d30eafeb23c81d513d Author: Taylor Brown Date: Sun Feb 18 11:03:51 2024 -0700 Add energy balance error to annual output commit 6f699125eb46df2bc124ac677de7708ff4f567d3 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Feb 5 15:53:51 2024 -0700 Begin calculating TES energy balance error. commit 1918d0b80cfc1550dfd1b71bd0c86c8efa4f24de Author: Taylor Brown Date: Thu Feb 1 15:16:12 2024 -0700 Add output to TES subcomponent model. Add distributed commercial. commit fb2064e35d2936e2e473633668ec89655fce9976 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue Jan 30 15:27:57 2024 -0700 Add analytical TES balance with leakage to subcomponent model. commit 39b9ab89170512f7885dc17e3e1e5c3545cfdfce Author: Taylor Brown Date: Thu Jan 18 13:48:12 2024 -0700 Add polynomial loss to subcomponent model. Update variable names. commit 0c4e1c9a296c00e43481fdb440d51f9d7ae6b974 Author: Taylor Brown Date: Wed Jan 17 11:50:12 2024 -0700 Finish adding piston leakage losses commit 39bbd5b1ce32b9257cd74bde52624639a02d0135 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Jan 11 13:45:06 2024 -0700 Add comment commit 8b1b48ac44373df14d1468934f68d392efea1e8c Author: Taylor Brown Date: Wed Jan 10 12:37:22 2024 -0700 Begin including piston losses. commit 3b4654048700da4fb1db8ede6bf6e1df7415232a Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue Jan 9 11:27:44 2024 -0700 Expand testing for NT heat trap TES. commit a7af1fe0daae35e4552801900b902f72663a39a8 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Jan 8 14:08:24 2024 -0700 Update subcomponent to test NT tes system commit b6e9bad806a07b477f4122dd46f27a1b6919686f Author: Taylor Brown Date: Fri Jan 5 19:17:19 2024 -0700 Fix dynamic volume calculation. Fix inlet temperature weight calculation. commit d4b8e1c93b178f780de9c0bde1055b4d9f5dd59b Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Jan 4 14:26:04 2024 -0700 Remove unused functions commit d89b2e1fbb185d5871bd77663f79b647bf38a5c1 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Jan 4 13:55:25 2024 -0700 Fix energy balance iterated previous temperature. commit 0d495ebeec376759951ecc7c05fcd4f3882c8591 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Jan 4 13:10:38 2024 -0700 Fix mass flow issue for tank energy balance. Add tank surface area to annual output. commit 206b946a4f32106a3159855e84a4503cd7d0363d Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue Jan 2 15:53:56 2024 -0700 Add NT energy balance time step to UI commit f97944e9d594a7fd2ee6c1cb323ed2c1d76aea55 Author: Taylor Brown Date: Tue Jan 2 09:27:18 2024 -0700 Add NT specific TES outputs commit c47373c74222e4f225c0348bdaa8745f635b8945 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed Dec 20 15:14:37 2023 -0700 Add total volume output for two tank commit c5acfd58eab553a7b2656a2dd10cc85527503e23 Author: Taylor Brown Date: Tue Dec 19 15:13:40 2023 -0700 Add helpful annual outputs to NT tank commit a1269543760bd92db39e78995ac2a8bf8af1a894 Author: Taylor Brown Date: Tue Dec 19 10:54:23 2023 -0700 Add Norwich Tank parameters to cmod commit a90518158615bc47af6133760a3bb8b8c0ec2b53 Author: Taylor Brown Date: Tue Dec 19 09:04:50 2023 -0700 Add NT TES wall thickness to energy balance. commit bf4e4adcf1750f3e35e16fd4ec4832383ec0cdf0 Author: Taylor Brown Date: Sun Dec 17 17:08:05 2023 -0700 Fix bug with energy balance mdot difference. Results are close to original with no wall thickness, however the TES heat loss is too low at certain times. commit 97b047b4af4a578fad7d08399fce1b2861614758 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri Dec 15 16:08:50 2023 -0700 Modify thermal storage to account for storage mass commit e7018360e096afc1be04f7b386319fc6ea18744d Author: Taylor Brown Date: Thu Dec 14 22:07:37 2023 -0700 Move storage classes out of shared solver_tes_core commit 1ff74dc1f9700d9c6a34aea6ff87bda7086e9617 Author: Taylor Brown Date: Thu Dec 14 21:20:40 2023 -0700 Add missing implementations of NT tes functions. commit bb5bb87247d58aa228f954713d5d3a54c5b020a2 Author: Taylor Brown Date: Thu Dec 14 10:57:44 2023 -0700 Move fields from two tank model to parent C_csp_tes class. Add files to CMake commit 87f4807f691bf0115bad64eb7462d721ad82ad68 Author: Taylor Brown Date: Wed Dec 13 18:47:18 2023 -0700 Add 'core' tes class to isolate shared csp tes funcitons. Continue developing NT Heat Trap model commit b4ce8e74716c24f936a02e204a871358966da7f4 Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon Dec 11 11:13:24 2023 -0700 Continue to develop NT storage tank commit e470db0cf09f87e915dfd1bcae0ac22a1e495222 Author: Taylor Brown Date: Tue Dec 5 10:51:53 2023 -0700 Create new HeatTrap TES model skeleton. --- ssc/cmod_csp_subcomponent.cpp | 259 ++++++++++---------- ssc/cmod_trough_physical.cpp | 298 ++++++++++++----------- ssc/cmod_trough_physical_iph.cpp | 391 +++++++++++++++++++++++++----- tcs/csp_solver_NTHeatTrap_tes.cpp | 74 +++--- tcs/csp_solver_NTHeatTrap_tes.h | 8 +- tcs/csp_solver_core.h | 7 + tcs/csp_solver_packedbed_tes.cpp | 83 ++++--- tcs/csp_solver_packedbed_tes.h | 15 +- 8 files changed, 725 insertions(+), 410 deletions(-) diff --git a/ssc/cmod_csp_subcomponent.cpp b/ssc/cmod_csp_subcomponent.cpp index 30e39aec0..bbb3bc759 100644 --- a/ssc/cmod_csp_subcomponent.cpp +++ b/ssc/cmod_csp_subcomponent.cpp @@ -53,32 +53,33 @@ static var_info _cm_vtab_csp_subcomponent[] = { { SSC_INPUT, SSC_NUMBER, "T_tank_hot_ini", "Temperature of fluid in hot tank at beginning of step", "C", "", "TES", "", "", "" }, { SSC_INPUT, SSC_NUMBER, "T_tank_cold_ini", "Temperature of fluid in cold tank at beginning of step", "C", "", "TES", "", "", "" }, - - // TES - { SSC_INPUT, SSC_NUMBER, "Fluid", "Field HTF fluid ID number", "-", "", "solar_field", "*", "", "" }, - { SSC_INPUT, SSC_MATRIX, "field_fl_props", "User defined field fluid property data", "-", "", "solar_field", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "store_fluid", "Material number for storage fluid", "-", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_MATRIX, "store_fl_props", "User defined storage fluid property data", "-", "", "TES", "*", "", "" }, + // General System Parameters { SSC_INPUT, SSC_NUMBER, "P_ref", "Rated plant capacity", "MWe", "", "powerblock", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "eta_ref", "Power cycle efficiency at design", "none", "", "powerblock", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "solar_mult", "Actual solar multiple of system", "-", "", "system", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tshours", "Equivalent full-load thermal storage hours", "hr", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "is_h_tank_fixed", "[1] Use fixed height (calculate diameter) [0] Use fixed diameter", "-", "", "TES", "?=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "h_tank_in", "Total height of tank (height of HTF when tank is full", "m", "", "TES", "is_h_tank_fixed=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "d_tank_in", "Tank diameter input", "m", "", "TES", "is_h_tank_fixed=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tank_pairs", "Number of equivalent tank pairs", "-", "", "TES", "*", "INTEGER", "" }, - { SSC_INPUT, SSC_NUMBER, "hot_tank_Thtr", "Minimum allowable hot tank HTF temp", "C", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "hot_tank_max_heat", "Rated heater capacity for hot tank heating", "MWe", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "cold_tank_Thtr", "Minimum allowable cold tank HTF temp", "C", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "cold_tank_max_heat", "Rated heater capacity for cold tank heating", "MWe", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "dt_hot", "Hot side HX approach temp", "C", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "T_loop_in_des", "Design loop inlet temperature", "C", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "T_loop_out", "Target loop outlet temperature", "C", "", "solar_field", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "init_hot_htf_percent", "Initial fraction of avail. vol that is hot", "%", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "pb_pump_coef", "Pumping power to move 1kg of HTF through PB loop", "kW/kg", "", "powerblock", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tanks_in_parallel", "Tanks are in parallel, not in series, with solar field", "-", "", "controller", "*", "", "" }, + + // General TES Parameters + { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), Packed Bed (1), HeatTrap Single Tank (2)", "-", "", "TES", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "Fluid", "Field HTF fluid ID number", "-", "", "solar_field", "*", "", "" }, + { SSC_INPUT, SSC_MATRIX, "field_fl_props", "User defined field fluid property data", "-", "", "solar_field", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tshours", "Equivalent full-load thermal storage hours", "hr", "", "TES", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "is_h_tank_fixed", "[1] Use fixed height (calculate diameter) [0] Use fixed diameter [2] Use fixed d and h (for packed bed)", "-", "", "TES", "?=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "h_tank_in", "Total height of tank input (height of HTF when tank is full", "m", "", "TES", "is_h_tank_fixed=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "d_tank_in", "Tank diameter input", "m", "", "TES", "is_h_tank_fixed=0|is_h_tank_fixed=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tank_pairs", "Number of equivalent tank pairs", "-", "", "TES", "*", "INTEGER", "" }, + { SSC_INPUT, SSC_NUMBER, "hot_tank_Thtr", "Minimum allowable hot tank HTF temp", "C", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "hot_tank_max_heat", "Rated heater capacity for hot tank heating", "MWe", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "cold_tank_Thtr", "Minimum allowable cold tank HTF temp", "C", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "cold_tank_max_heat", "Rated heater capacity for cold tank heating", "MWe", "", "TES", "tes_type=0|tes_type=2", "", "" }, { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "init_hot_htf_percent", "Initial fraction of avail. vol that is hot", "%", "", "TES", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_n_tsteps", "Number of subtimesteps (for NT and packed bed)", "", "", "TES", "tes_type>0", "", "" }, + + // TES { SSC_INPUT, SSC_NUMBER, "V_tes_des", "Design-point velocity to size the TES pipe diameters", "m/s", "", "controller", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "calc_design_pipe_vals", "Calculate temps and pressures at design conditions for runners and headers", "none", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "tes_pump_coef", "Pumping power to move 1kg of HTF through tes loop", "kW/(kg/s)", "", "controller", "*", "", "" }, @@ -94,30 +95,31 @@ static var_info _cm_vtab_csp_subcomponent[] = { { SSC_INPUT, SSC_NUMBER, "HDR_rough", "Header pipe roughness", "m", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "DP_SGS", "Pressure drop within the steam generator", "bar", "", "controller", "*", "", "" }, - // Added Inputs for NT System - { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), HeatTrap Single Tank (1), Packed Bed (2)", "-", "", "TES", "?=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_tank_thick", "Tank wall thickness (used for Norwich HeatTrap)", "m", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_tank_cp", "Tank wall cp (used for Norwich HeatTrap)", "kJ/kg-K", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_tank_dens", "Tank wall thickness (used for Norwich HeatTrap)", "kg/m3", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_NT_nstep", "Number of time steps for energy balance (used for Norwich HeatTrap)", "", "", "TES", "?=1", "", "" }, - { SSC_INPUT, SSC_ARRAY, "tes_NT_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_tank_insul_percent", "Percent additional wall mass due to insulation", "%", "", "TES", "?=0", "", "" }, - - - // Packed bed Specific Inputs - { SSC_INPUT, SSC_NUMBER, "tes_pb_n_xsteps", "Number of spatial segments", "", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_n_tsteps", "Number of subtimesteps", "", "", "TES", "tes_type=2", "", "" }, + // TES Two Tank Specific + { SSC_INPUT, SSC_NUMBER, "store_fluid", "Material number for storage fluid", "-", "", "TES", "tes_type=0", "", "" }, + { SSC_INPUT, SSC_MATRIX, "store_fl_props", "User defined storage fluid property data", "-", "", "TES", "tes_type=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "dt_hot", "Hot side HX approach temp", "C", "", "TES", "tes_type=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tanks_in_parallel", "Tanks are in parallel, not in series, with solar field", "-", "", "controller", "tes_type=0", "", "" }, + + + // TES Packed Bed + { SSC_INPUT, SSC_NUMBER, "tes_pb_n_xsteps", "Number of spatial segments", "", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_k_eff", "TES packed bed effective conductivity", "W/m K", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_void_frac", "TES packed bed void fraction", "", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_dens_solid", "TES packed bed media density", "kg/m3", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_cp_solid", "TES particle specific heat", "kJ/kg K", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_hot_delta", "Max allowable decrease in hot discharge temp", "C", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_cold_delta", "Max allowable increase in cold discharge temp", "C", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_charge_min", "Min charge temp", "C", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_f_oversize", "Packed bed oversize factor", "", "", "TES", "tes_type=1", "", "" }, { SSC_INPUT, SSC_ARRAY, "tes_pb_T_grad_ini", "TES Temperature gradient at beginning of timestep", "C", "", "TES", "?=[-274]", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_k_eff", "TES packed bed effective conductivity", "W/m K", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_void_frac", "TES particle packed bed void fraction", "", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_dens_solid", "TES particle density", "kg/m3", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_cp_solid", "TES particle specific heat", "J/kg K", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_T_hot_delta", "Max allowable decrease in hot discharge temp", "C", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_T_cold_delta", "Max allowable increase in cold discharge temp", "C", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_T_charge_min", "Min charge temp", "C", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_size_type", "(0) use fixed diameter, (1) use fixed height, (2) use preset inputs", "", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_f_oversize", "Packed bed oversize factor", "", "", "TES", "tes_type=2", "", "" }, + // TES Norwich HeatTrap + { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_thick", "Tank wall thickness (used for Norwich HeatTrap)", "m", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_cp", "Tank wall cp (used for Norwich HeatTrap)", "kJ/kg-K", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_dens", "Tank wall thickness (used for Norwich HeatTrap)", "kg/m3", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_ARRAY, "tes_nt_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_insul_percent", "Percent additional wall mass due to insulation (used for Norwich HeatTrap)", "%", "", "TES", "?=0", "", "" }, // Outputs { SSC_OUTPUT, SSC_ARRAY, "T_src_in", "Temperature to heat source", "C", "", "TES", "*", "", "" }, @@ -135,25 +137,25 @@ static var_info _cm_vtab_csp_subcomponent[] = { - { SSC_OUTPUT, SSC_ARRAY, "tes_error", "TES energy balance error", "MW", "", "TES", "tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_error_percent", "TES energy balance error percent", "%", "", "TES", "tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "piston_loc", "Piston Location (distance from left cold side)", "m", "", "TES", "tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "piston_frac", "Piston Fraction (distance from left cold side)", "", "", "TES", "tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_leak_error", "TES energy balance error due to leakage assumption", "MWt", "", "TES", "tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_E_hot", "TES hot side internal energy", "MJ", "", "TES", "tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_E_cold", "TES cold side internal energy", "MJ", "", "TES", "tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_wall_error", "TES energy balance error due to wall temperature assumption", "MWt", "", "TES", "tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_error_corrected", "TES energy balance error, accounting for wall and temperature assumption error", "MWt", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error", "TES energy balance error", "MW", "", "TES", "tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error_percent", "TES energy balance error percent", "%", "", "TES", "tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "piston_loc", "Piston Location (distance from left cold side)", "m", "", "TES", "tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "piston_frac", "Piston Fraction (distance from left cold side)", "", "", "TES", "tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_leak_error", "TES energy balance error due to leakage assumption", "MWt", "", "TES", "tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_E_hot", "TES hot side internal energy", "MJ", "", "TES", "tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_E_cold", "TES cold side internal energy", "MJ", "", "TES", "tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_wall_error", "TES energy balance error due to wall temperature assumption", "MWt", "", "TES", "tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error_corrected", "TES energy balance error, accounting for wall and temperature assumption error", "MWt", "", "TES", "tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_MATRIX, "T_grad_final", "TES Temperature gradient at end of timestep", "C", "", "TES", "tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_exp_wall_mass", "TES expansion tank effective wall mass", "kg", "", "TES", "tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_exp_length", "TES expansion tank effective length", "m", "", "TES", "tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_mass_cold", "TES cold fluid mass", "kg", "", "TES", "tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_mass_hot", "TES hot fluid mass", "kg", "", "TES", "tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_V_cold", "TES cold fluid volume", "kg", "", "TES", "tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_V_hot", "TES hot fluid volume", "kg", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_exp_wall_mass", "TES expansion tank effective wall mass", "kg", "", "TES", "tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_exp_length", "TES expansion tank effective length", "m", "", "TES", "tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_mass_cold", "TES cold fluid mass", "kg", "", "TES", "tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_mass_hot", "TES hot fluid mass", "kg", "", "TES", "tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_V_cold", "TES cold fluid volume", "kg", "", "TES", "tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_V_hot", "TES hot fluid volume", "kg", "", "TES", "tes_type=2", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "hot_tank_mass_perc", "TES hot tank mass percent of total (end)", "kg", "", "TES", "*", "", "" }, + { SSC_OUTPUT, SSC_MATRIX, "T_grad_final", "TES Temperature gradient at end of timestep", "C", "", "TES", "tes_type=1", "", "" }, var_info_invalid }; @@ -190,7 +192,7 @@ class cm_csp_subcomponent : public compute_module double tshours = as_double("tshours"); // Two Tank - if (tes_type == 0) + if (tes_type == C_csp_tes::csp_tes_types::E_TES_TWO_TANK) { double T_tank_hot_ini = is_assigned("T_tank_hot_ini") == true ? as_double("T_tank_hot_ini") : as_double("T_loop_out"); double T_tank_cold_ini = is_assigned("T_tank_cold_ini") == true ? as_double("T_tank_cold_ini") : as_double("T_loop_in_des"); @@ -242,11 +244,65 @@ class cm_csp_subcomponent : public compute_module storage_pointer = &storage_two_tank; } + // Packed Bed + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_PACKED_BED) + { + if (as_double("tes_pb_cp_solid") > 5) + { + log("Be sure the packed bed solid media cp is in kJ/kg K", SSC_WARNING); + } + + storage_packedbed = C_csp_packedbed_tes( + as_integer("Fluid"), // [-] field fluid identifier + as_matrix("field_fl_props"), // [-] field fluid properties + as_double("P_ref") / as_double("eta_ref"), // [MWt] Design heat rate in and out of tes + as_double("P_ref") / as_double("eta_ref") * as_double("tshours"), // [MWt-hr] design storage capacity + as_integer("is_h_tank_fixed"), // [] Sizing Method (0) use fixed diameter, (1) use fixed height, (2) use preset inputs + as_double("h_tank_in"), // [m] Tank height + as_double("d_tank_in"), // [m] Tank diameter + as_double("tes_pb_f_oversize"), // [] Oversize factor + as_double("T_loop_in_des"), // [C] Cold design temperature + as_double("T_loop_out"), // [C] hot design temperature + // Check initialization variables + (is_assigned("T_tank_hot_ini")) ? as_double("T_tank_hot_ini") : as_double("T_loop_out"), + (is_assigned("T_tank_cold_ini")) ? as_double("T_tank_cold_ini") : as_double("T_loop_in_des"), + as_double("init_hot_htf_percent"), // [%] Initial fraction of available volume that is hot + as_integer("tes_pb_n_xsteps"), // number spatial sub steps + as_integer("tes_n_tsteps"), // number subtimesteps + as_double("tes_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop + as_double("tes_pb_k_eff"), // [W/m K] Effective thermal conductivity + as_double("tes_pb_void_frac"), // [] Packed bed void fraction + as_double("tes_pb_dens_solid"), // [kg/m3] solid specific heat + as_double("tes_pb_cp_solid"), // [kJ/kg K] solid specific heat + as_double("tes_pb_T_hot_delta"), // [C] Max allowable decrease in hot discharge temp + as_double("tes_pb_T_cold_delta"), // [C] Max allowable increase in cold discharge temp + as_double("tes_pb_T_charge_min") // [C] Min allowable charge temperature + ); + + if (is_assigned("tes_pb_T_grad_ini")) + { + vector T_grad_ini = as_vector_double("tes_pb_T_grad_ini"); + if (T_grad_ini.size() > 1 && T_grad_ini[0] != -274) + { + if (T_grad_ini.size() != as_integer("tes_pb_n_xsteps") + 1) + { + throw exec_error("csp_subcomponent", "Initial temperature gradient should be length tes_pb_n_xsteps + 1"); + } + else + { + storage_packedbed.set_T_grad_init(T_grad_ini); + } + } + } + + storage_pointer = &storage_packedbed; + + } // Norwich HeatTrap - else if (tes_type == 1) + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) { - int nstep = as_integer("tes_NT_nstep"); + int nstep = as_integer("tes_n_tsteps"); bool custom_tes_pipe_sizes = as_boolean("custom_tes_pipe_sizes"); util::matrix_t tes_wallthicks; @@ -269,8 +325,8 @@ class cm_csp_subcomponent : public compute_module } // Modify wall density to account for insulation mass - double mass_factor = 1.0 + (0.01 * as_double("tes_tank_insul_percent")); - double dens_orig = as_double("tes_tank_dens"); + double mass_factor = 1.0 + (0.01 * as_double("tes_nt_tank_insul_percent")); + double dens_orig = as_double("tes_nt_tank_dens"); double dens_w_insulation = dens_orig * mass_factor; double T_tank_hot_ini = is_assigned("T_tank_hot_ini") == true ? as_double("T_tank_hot_ini") : as_double("T_loop_out"); @@ -282,8 +338,8 @@ class cm_csp_subcomponent : public compute_module storage_NT = C_csp_NTHeatTrap_tes( as_integer("Fluid"), // [-] field fluid identifier as_matrix("field_fl_props"), // [-] field fluid properties - as_integer("store_fluid"), // [-] tes fluid identifier - as_matrix("store_fl_props"), // [-] tes fluid properties + //as_integer("store_fluid"), // [-] tes fluid identifier + //as_matrix("store_fl_props"), // [-] tes fluid properties as_double("P_ref") / as_double("eta_ref"), // [MWt] Design heat rate in and out of tes as_double("solar_mult"), // [-] the max design heat rate as a fraction of the nominal as_double("P_ref") / as_double("eta_ref") * as_double("tshours"), // [MWt-hr] design storage capacity @@ -303,11 +359,11 @@ class cm_csp_subcomponent : public compute_module as_double("h_tank_min"), // [m] Minimum allowable HTF height in storage tank as_double("init_hot_htf_percent"), // [%] Initial fraction of available volume that is hot as_double("pb_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through power cycle - as_double("tes_tank_cp") * 1000, // convert to J/kgK + as_double("tes_nt_tank_cp") * 1000, // convert to J/kgK dens_w_insulation, // Tank Wall density - as_double("tes_tank_thick"), // Tank wall thickness + as_double("tes_nt_tank_thick"), // Tank wall thickness nstep, // Number subtimesteps - as_vector_double("tes_NT_piston_loss_poly"), // Leakage polynomial (%) + as_vector_double("tes_nt_piston_loss_poly"), // Leakage polynomial (%) as_double("V_tes_des"), // [m/s] Design-point velocity for sizing the diameters of the TES piping as_boolean("calc_design_pipe_vals"), // [-] Should the HTF state be calculated at design conditions as_double("tes_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop @@ -326,59 +382,11 @@ class cm_csp_subcomponent : public compute_module storage_pointer = &storage_NT; } - // Packed Bed - else if (tes_type == 2) - { - - storage_packedbed = C_csp_packedbed_tes( - as_integer("Fluid"), // [-] field fluid identifier - as_matrix("field_fl_props"), // [-] field fluid properties - as_double("P_ref") / as_double("eta_ref") * as_double("tshours"), // [MWt-hr] design storage capacity - as_integer("tes_pb_size_type"), // [] Sizing Method (0) use fixed diameter, (1) use fixed height, (2) use preset inputs - as_double("h_tank_in"), // [m] Tank height - as_double("d_tank_in"), // [m] Tank diameter - as_double("tes_pb_f_oversize"), // [] Oversize factor - as_double("T_loop_in_des"), // [C] Cold design temperature - as_double("T_loop_out"), // [C] hot design temperature - as_double("T_tank_hot_ini"), // [C] Initial temperature in hot storage tank - as_double("T_tank_cold_ini"), // [C] Initial temperature in cold storage cold - as_double("init_hot_htf_percent"), // [%] Initial fraction of available volume that is hot - as_integer("tes_pb_n_xsteps"), // number spatial sub steps - as_integer("tes_pb_n_tsteps"), // number subtimesteps - as_double("tes_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop - as_double("tes_pb_k_eff"), // [W/m K] Effective thermal conductivity - as_double("tes_pb_void_frac"), // [] Packed bed void fraction - as_double("tes_pb_dens_solid"), // [kg/m3] solid specific heat - as_double("tes_pb_cp_solid"), // [J/kg K] solid specific heat - as_double("tes_pb_T_hot_delta"), // [C] Max allowable decrease in hot discharge temp - as_double("tes_pb_T_cold_delta"), // [C] Max allowable increase in cold discharge temp - as_double("tes_pb_T_charge_min") // [C] Min allowable charge temperature - ); - - - vector T_grad_ini = as_vector_double("tes_pb_T_grad_ini"); - if (T_grad_ini.size() > 1 && T_grad_ini[0] != -274) - { - if (T_grad_ini.size() != as_integer("tes_pb_n_xsteps") + 1) - { - throw exec_error("csp_subcomponent", "Initial temperature gradient should be length tes_pb_n_xsteps + 1"); - } - else - { - storage_packedbed.set_T_grad_init(T_grad_ini); - } - } - - storage_pointer = &storage_packedbed; - - } else { throw exec_error("csp_subcomponent", "tes_type must be 0-2"); } - - // Initialization -> this is necessary to fully instantiate the TES C_csp_tes::S_csp_tes_init_inputs init_inputs; init_inputs.T_to_cr_at_des = C_to_K(as_double("T_loop_in_des")); // [K] @@ -409,12 +417,17 @@ class cm_csp_subcomponent : public compute_module h_tank_calc /*m*/, d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, Q_tes_des_calc /*MWt-hr*/; - if (tes_type == 0) + if (tes_type == C_csp_tes::csp_tes_types::E_TES_TWO_TANK) { storage_two_tank.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); } - else if (tes_type == 1) + if (tes_type == C_csp_tes::csp_tes_types::E_TES_PACKED_BED) + { + storage_packedbed.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, + h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + } + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) { storage_NT.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); @@ -454,8 +467,7 @@ class cm_csp_subcomponent : public compute_module vector tes_error_corrected_vec; util::matrix_t tes_T_grad_mat; - - if (tes_type == 2) + if (tes_type == C_csp_tes::csp_tes_types::E_TES_PACKED_BED) { tes_T_grad_mat.resize(n_steps, as_integer("tes_pb_n_xsteps") + 1); } @@ -498,7 +510,7 @@ class cm_csp_subcomponent : public compute_module // Add NT specific outputs - if (tes_type == 1) + if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) { double piston_location, piston_fraction; storage_NT.calc_piston_location(piston_location, piston_fraction); @@ -532,6 +544,7 @@ class cm_csp_subcomponent : public compute_module } // Add packed bed specific outputs + if(tes_type == C_csp_tes::csp_tes_types::E_TES_PACKED_BED) { std::vector T_prev_vec = storage_packedbed.get_T_prev_vec(); for (int j = 0; j < T_prev_vec.size(); j++) @@ -542,7 +555,7 @@ class cm_csp_subcomponent : public compute_module } - if (tes_type == 1) + if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) { set_vector("piston_loc", piston_loc_vec); set_vector("piston_frac", piston_frac_vec); @@ -558,7 +571,7 @@ class cm_csp_subcomponent : public compute_module } - if (tes_type == 2) + if (tes_type == C_csp_tes::csp_tes_types::E_TES_PACKED_BED) { assign("T_grad_final", tes_T_grad_mat); } diff --git a/ssc/cmod_trough_physical.cpp b/ssc/cmod_trough_physical.cpp index 2ba6b85d7..8831c112b 100644 --- a/ssc/cmod_trough_physical.cpp +++ b/ssc/cmod_trough_physical.cpp @@ -59,9 +59,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. static var_info _cm_vtab_trough_physical[] = { - - - /* VARTYPE DATATYPE NAME LABEL UNITS META GROUP REQUIRED_IF CONSTRAINTS UI_HINTS*/ { SSC_INPUT, SSC_NUMBER, "sim_type", "1 (default): timeseries, 2: design only", "", "", "System Control", "?=1", "", "SIMULATION_PARAMETER"}, @@ -204,46 +201,48 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_INPUT, SSC_NUMBER, "ud_m_dot_water_cool_des", "Mass flow rate of water required at user-defined power cycle design point", "kg/s", "", "powerblock", "pc_config=1", "", "" }, { SSC_INPUT, SSC_MATRIX, "ud_ind_od", "Off design user-defined power cycle performance as function of T_htf, m_dot_htf [ND], and T_amb", "", "", "powerblock", "pc_config=1", "", "" }, - // TES - { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), HeatTrap Single Tank (1)", "-", "", "TES", "?=0", "", "" }, - - { SSC_INPUT, SSC_NUMBER, "store_fluid", "Material number for storage fluid", "-", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_MATRIX, "store_fl_props", "User defined storage fluid property data", "-", "", "TES", "*", "", "" }, + // General TES Parameters + { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), Packed Bed (1), HeatTrap Single Tank (2)", "-", "", "TES", "?=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "tshours", "Equivalent full-load thermal storage hours", "hr", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "is_h_tank_fixed", "[1] Use fixed height (calculate diameter) [0] Use fixed diameter", "-", "", "TES", "?=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "is_h_tank_fixed", "[1] Use fixed height (calculate diameter) [0] Use fixed diameter [2] Use fixed d and h (for packed bed)", "-", "", "TES", "?=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "h_tank_in", "Total height of tank input (height of HTF when tank is full", "m", "", "TES", "is_h_tank_fixed=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "d_tank_in", "Tank diameter input", "m", "", "TES", "is_h_tank_fixed=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "TES", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "d_tank_in", "Tank diameter input", "m", "", "TES", "is_h_tank_fixed=0|is_h_tank_fixed=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "TES", "tes_type=0|tes_type=2", "", "" }, { SSC_INPUT, SSC_NUMBER, "tank_pairs", "Number of equivalent tank pairs", "-", "", "TES", "*", "INTEGER", "" }, - { SSC_INPUT, SSC_NUMBER, "hot_tank_Thtr", "Minimum allowable hot tank HTF temp", "C", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "hot_tank_max_heat", "Rated heater capacity for hot tank heating", "MWe", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "cold_tank_Thtr", "Minimum allowable cold tank HTF temp", "C", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "cold_tank_max_heat", "Rated heater capacity for cold tank heating", "MWe", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "dt_hot", "Hot side HX approach temp", "C", "", "TES", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "hot_tank_Thtr", "Minimum allowable hot tank HTF temp", "C", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "hot_tank_max_heat", "Rated heater capacity for hot tank heating", "MWe", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "cold_tank_Thtr", "Minimum allowable cold tank HTF temp", "C", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "cold_tank_max_heat", "Rated heater capacity for cold tank heating", "MWe", "", "TES", "tes_type=0|tes_type=2", "", "" }, //{ SSC_INPUT, SSC_NUMBER, "dt_cold", "Cold side HX approach temp", "C", "", "TES", "*", "", "" }, //{ SSC_INPUT, SSC_NUMBER, "T_tank_hot_ini", "Initial hot tank fluid temperature", "C", "", "TES", "*", "", "" }, //{ SSC_INPUT, SSC_NUMBER, "T_tank_cold_ini", "Initial cold tank fluid temperature", "C", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "tes_type=0|tes_type=2", "", "" }, { SSC_INPUT, SSC_NUMBER, "init_hot_htf_percent", "Initial fraction of avail. vol that is hot", "%", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_n_tsteps", "Number of subtimesteps (for NT and packed bed)", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_n_tsteps", "Number of subtimesteps (for NT and packed bed)", "", "", "TES", "tes_type>0", "", "" }, + + // TES Two Tank Specific + { SSC_INPUT, SSC_NUMBER, "store_fluid", "Material number for storage fluid", "-", "", "TES", "tes_type=0", "", "" }, + { SSC_INPUT, SSC_MATRIX, "store_fl_props", "User defined storage fluid property data", "-", "", "TES", "tes_type=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "dt_hot", "Hot side HX approach temp", "C", "", "TES", "tes_type=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tanks_in_parallel", "Tanks are in parallel, not in series, with solar field", "-", "", "controller", "tes_type=0", "", "" }, // TES Norwich HeatTrap - { SSC_INPUT, SSC_NUMBER, "tes_tank_thick", "Tank wall thickness (used for Norwich HeatTrap)", "m", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_tank_cp", "Tank wall cp (used for Norwich HeatTrap)", "kJ/kg-K", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_tank_dens", "Tank wall thickness (used for Norwich HeatTrap)", "kg/m3", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_ARRAY, "tes_NT_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_tank_insul_percent", "Percent additional wall mass due to insulation", "%", "", "TES", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_thick", "Tank wall thickness (used for Norwich HeatTrap)", "m", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_cp", "Tank wall cp (used for Norwich HeatTrap)", "kJ/kg-K", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_dens", "Tank wall thickness (used for Norwich HeatTrap)", "kg/m3", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_ARRAY, "tes_nt_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_insul_percent", "Percent additional wall mass due to insulation (used for Norwich HeatTrap)", "%", "", "TES", "?=0", "", "" }, // TES Packed Bed - { SSC_INPUT, SSC_NUMBER, "tes_pb_n_xsteps", "Number of spatial segments", "", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_k_eff", "TES packed bed effective conductivity", "W/m K", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_void_frac", "TES particle packed bed void fraction", "", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_dens_solid", "TES particle density", "kg/m3", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_cp_solid", "TES particle specific heat", "J/kg K", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_T_hot_delta", "Max allowable decrease in hot discharge temp", "C", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_T_cold_delta", "Max allowable increase in cold discharge temp", "C", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_T_charge_min", "Min charge temp", "C", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_f_oversize", "Packed bed oversize factor", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_n_xsteps", "Number of spatial segments", "", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_k_eff", "TES packed bed effective conductivity", "W/m K", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_void_frac", "TES packed bed void fraction", "", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_dens_solid", "TES packed bed media density", "kg/m3", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_cp_solid", "TES particle specific heat", "kJ/kg K", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_hot_delta", "Max allowable decrease in hot discharge temp", "C", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_cold_delta", "Max allowable increase in cold discharge temp", "C", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_charge_min", "Min charge temp", "C", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_f_oversize", "Packed bed oversize factor", "", "", "TES", "tes_type=1", "", "" }, // Optional Component Initialization (state at start of first timestep) @@ -347,7 +346,6 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_INPUT, SSC_MATRIX, "sf_hdr_diams", "Custom header diameters", "m", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_MATRIX, "sf_hdr_wallthicks", "Custom header wall thicknesses", "m", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_MATRIX, "sf_hdr_lengths", "Custom header lengths", "m", "", "solar_field", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tanks_in_parallel", "Tanks are in parallel, not in series, with solar field", "-", "", "controller", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "has_hot_tank_bypass", "Bypass valve connects field outlet to cold tank", "-", "", "controller", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "T_tank_hot_inlet_min", "Minimum hot tank htf inlet temperature", "C", "", "controller", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "tes_pump_coef", "Pumping power to move 1kg of HTF through tes loop", "kW/(kg/s)", "", "controller", "*", "", "" }, @@ -630,33 +628,33 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_OUTPUT, SSC_ARRAY, "tes_htf_pump_power", "TES HTF pump power", "MWe", "", "TES", "sim_type=1", "", "" }, // NT TES - { SSC_OUTPUT, SSC_ARRAY, "vol_tes_cold", "TES cold fluid volume", "m3", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "vol_tes_hot", "TES hot fluid volume", "m3" , "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "vol_tes_cold", "TES cold fluid volume", "m3", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "vol_tes_hot", "TES hot fluid volume", "m3" , "", "TES", "sim_type=1&tes_type=2", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "vol_tes_tot", "TES total fluid volume", "m3", "", "TES", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_piston_loc", "TES piston distance from left (cold) side", "m", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_piston_frac", "TES piston fraction of cold distance over total", "", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_cold_vol_frac", "TES volume fraction of cold over total", "", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_piston_loc", "TES piston distance from left (cold) side", "m", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_piston_frac", "TES piston fraction of cold distance over total", "", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_cold_vol_frac", "TES volume fraction of cold over total", "", "", "TES", "sim_type=1&tes_type=2", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "tes_mass_tot", "TES total fluid mass", "kg", "", "TES", "tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_SA_cold", "TES cold side surface area", "m2", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_SA_hot", "TES hot side surface area", "m2", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_SA_tot", "TES total surface area", "m2", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_error", "TES energy balance error", "MWt", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_error_percent", "TES energy balance error percent", "%", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_leak_error", "TES energy balance error due to leakage assumption", "MWt", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_wall_error", "TES energy balance error due to wall temperature assumption", "MWt", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_error_corrected", "TES energy balance error, accounting for wall and temperature assumption error", "MWt", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_SA_cold", "TES cold side surface area", "m2", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_SA_hot", "TES hot side surface area", "m2", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_SA_tot", "TES total surface area", "m2", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error", "TES energy balance error", "MWt", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error_percent", "TES energy balance error percent", "%", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_leak_error", "TES energy balance error due to leakage assumption", "MWt", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_wall_error", "TES energy balance error due to wall temperature assumption", "MWt", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error_corrected", "TES energy balance error, accounting for wall and temperature assumption error", "MWt", "", "TES", "sim_type=1&tes_type=2", "", "" }, // Packed Bed TES - { SSC_OUTPUT, SSC_ARRAY, "T_grad_0", "TES Temperature gradient 0 indice", "C", "", "TES", "", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_1", "TES Temperature gradient 1 indice", "C", "", "TES", "", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_2", "TES Temperature gradient 2 indice", "C", "", "TES", "", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_3", "TES Temperature gradient 3 indice", "C", "", "TES", "", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_4", "TES Temperature gradient 4 indice", "C", "", "TES", "", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_5", "TES Temperature gradient 5 indice", "C", "", "TES", "", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_6", "TES Temperature gradient 6 indice", "C", "", "TES", "", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_7", "TES Temperature gradient 7 indice", "C", "", "TES", "", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_8", "TES Temperature gradient 8 indice", "C", "", "TES", "", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_9", "TES Temperature gradient 9 indice", "C", "", "TES", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_0", "TES Temperature gradient 0 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_1", "TES Temperature gradient 1 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_2", "TES Temperature gradient 2 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_3", "TES Temperature gradient 3 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_4", "TES Temperature gradient 4 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_5", "TES Temperature gradient 5 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_6", "TES Temperature gradient 6 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_7", "TES Temperature gradient 7 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_8", "TES Temperature gradient 8 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_9", "TES Temperature gradient 9 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, //{ SSC_OUTPUT, SSC_ARRAY, "m_dot_tes_dc", "TES discharge mass flow rate", "kg/s", "", "TES", "*", "", "" }, @@ -766,8 +764,6 @@ static var_info _cm_vtab_trough_physical[] = { var_info_invalid }; - - class cm_trough_physical : public compute_module { public: @@ -1354,7 +1350,8 @@ class cm_trough_physical : public compute_module C_csp_two_tank_tes storage_two_tank; C_csp_NTHeatTrap_tes storage_NT; C_csp_packedbed_tes storage_packedbed; - if (tes_type == 0) + // Two Tank + if (tes_type == C_csp_tes::csp_tes_types::E_TES_TWO_TANK) { bool custom_tes_pipe_sizes = as_boolean("custom_tes_pipe_sizes"); @@ -1440,7 +1437,62 @@ class cm_trough_physical : public compute_module storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_MASS_TOT, allocate("tes_mass_tot", n_steps_fixed), n_steps_fixed); storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_HOT_TANK_HTF_PERC_FINAL, allocate("hot_tank_htf_percent_final", n_steps_fixed), n_steps_fixed); } - else if (tes_type == 1) + // Packed Bed + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_PACKED_BED) + { + storage_packedbed = C_csp_packedbed_tes( + as_integer("Fluid"), // [-] field fluid identifier + as_matrix("field_fl_props"), // [-] field fluid properties + as_double("P_ref") / as_double("eta_ref"), // [MWt] Design heat rate in and out of tes + as_double("P_ref") / as_double("eta_ref") * as_double("tshours"), // [MWt-hr] design storage capacity + as_integer("is_h_tank_fixed"), // [] Sizing Method (0) use fixed diameter, (1) use fixed height, (2) use preset inputs + as_double("h_tank_in"), // [m] Tank height + as_double("d_tank_in"), // [m] Tank diameter + as_double("tes_pb_f_oversize"), // [] Oversize factor + as_double("T_loop_in_des"), // [C] Cold design temperature + as_double("T_loop_out"), // [C] hot design temperature + // Check initialization variables + (is_assigned("T_tank_hot_init")) ? as_double("T_tank_hot_init") : as_double("T_loop_out"), + (is_assigned("T_tank_cold_init")) ? as_double("T_tank_cold_init") : as_double("T_loop_in_des"), + as_double("init_hot_htf_percent"), // [%] Initial fraction of available volume that is hot + as_integer("tes_pb_n_xsteps"), // number spatial sub steps + as_integer("tes_n_tsteps"), // number subtimesteps + as_double("tes_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop + as_double("tes_pb_k_eff"), // [W/m K] Effective thermal conductivity + as_double("tes_pb_void_frac"), // [] Packed bed void fraction + as_double("tes_pb_dens_solid"), // [kg/m3] solid specific heat + as_double("tes_pb_cp_solid"), // [kJ/kg K] solid specific heat + as_double("tes_pb_T_hot_delta"), // [C] Max allowable decrease in hot discharge temp + as_double("tes_pb_T_cold_delta"), // [C] Max allowable increase in cold discharge temp + as_double("tes_pb_T_charge_min") // [C] Min allowable charge temperature + ); + + storage_pointer = &storage_packedbed; + + // Set storage outputs + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_Q_DOT_LOSS, allocate("tank_losses", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_W_DOT_HEATER, allocate("q_tes_heater", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_TES_T_HOT, allocate("T_tes_hot", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_TES_T_COLD, allocate("T_tes_cold", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_M_DOT_TANK_TO_TANK, allocate("m_dot_cold_tank_to_hot_tank", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_MASS_COLD_TANK, allocate("mass_tes_cold", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_MASS_HOT_TANK, allocate("mass_tes_hot", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_W_DOT_HTF_PUMP, allocate("tes_htf_pump_power", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_VOL_TOT, allocate("vol_tes_tot", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_MASS_TOT, allocate("tes_mass_tot", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_0, allocate("T_grad_0", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_1, allocate("T_grad_1", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_2, allocate("T_grad_2", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_3, allocate("T_grad_3", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_4, allocate("T_grad_4", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_5, allocate("T_grad_5", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_6, allocate("T_grad_6", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_7, allocate("T_grad_7", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_8, allocate("T_grad_8", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_9, allocate("T_grad_9", n_steps_fixed), n_steps_fixed); + } + // Norwich + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) { // Get number of sub time steps int nstep = as_integer("tes_n_tsteps"); @@ -1467,19 +1519,9 @@ class cm_trough_physical : public compute_module tes_diams.assign(tes_diams_val, 1); } - //double tes_tankthick = as_double("tes_tankthick"); - //double tes_tankcp = 1200; // J/kg K - //double tes_tankdens = 8000; // kg/m3 - - bool tanks_in_parallel = as_boolean("tanks_in_parallel"); - if (tanks_in_parallel == false) - { - throw exec_error("trough_physical", "TES model requires tanks in parallel"); - } - // Modify wall density to account for insulation mass - double mass_factor = 1.0 + (0.01 * as_double("tes_tank_insul_percent")); - double dens_orig = as_double("tes_tank_dens"); + double mass_factor = 1.0 + (0.01 * as_double("tes_nt_tank_insul_percent")); + double dens_orig = as_double("tes_nt_tank_dens"); double dens_w_insulation = dens_orig * mass_factor; double h_tank_in = is_assigned("h_tank_in") == true ? as_double("h_tank_in") : std::numeric_limits::quiet_NaN(); @@ -1488,8 +1530,8 @@ class cm_trough_physical : public compute_module storage_NT = C_csp_NTHeatTrap_tes( c_trough.m_Fluid, c_trough.m_field_fl_props, - as_integer("store_fluid"), - as_matrix("store_fl_props"), + //as_integer("store_fluid"), + //as_matrix("store_fl_props"), as_double("P_ref") / as_double("eta_ref"), c_trough.m_solar_mult, as_double("P_ref") / as_double("eta_ref") * as_double("tshours"), @@ -1504,16 +1546,17 @@ class cm_trough_physical : public compute_module as_double("cold_tank_max_heat"), as_double("T_loop_in_des"), as_double("T_loop_out"), - as_double("T_loop_out"), - as_double("T_loop_in_des"), + // Check initialization variables + (is_assigned("T_tank_hot_init")) ? as_double("T_tank_hot_init") : as_double("T_loop_out"), + (is_assigned("T_tank_cold_init")) ? as_double("T_tank_cold_init") : as_double("T_loop_in_des"), as_double("h_tank_min"), as_double("init_hot_htf_percent"), as_double("pb_pump_coef"), - as_double("tes_tank_cp") * 1000, // convert to J/kgK + as_double("tes_nt_tank_cp") * 1000, // convert to J/kgK dens_w_insulation, - as_double("tes_tank_thick"), + as_double("tes_nt_tank_thick"), nstep, - as_vector_double("tes_NT_piston_loss_poly"), + as_vector_double("tes_nt_piston_loss_poly"), as_double("V_tes_des"), as_boolean("calc_design_pipe_vals"), as_double("tes_pump_coef"), @@ -1559,61 +1602,9 @@ class cm_trough_physical : public compute_module storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_WALL_ERROR, allocate("tes_wall_error", n_steps_fixed), n_steps_fixed); storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_ERROR_CORRECTED, allocate("tes_error_corrected", n_steps_fixed), n_steps_fixed); } - else if (tes_type == 2) - { - storage_packedbed = C_csp_packedbed_tes( - as_integer("Fluid"), // [-] field fluid identifier - as_matrix("field_fl_props"), // [-] field fluid properties - as_double("P_ref") / as_double("eta_ref") * as_double("tshours"), // [MWt-hr] design storage capacity - as_integer("is_h_tank_fixed"), // [] Sizing Method (0) use fixed diameter, (1) use fixed height, (2) use preset inputs - as_double("h_tank_in"), // [m] Tank height - as_double("d_tank_in"), // [m] Tank diameter - as_double("tes_pb_f_oversize"), // [] Oversize factor - as_double("T_loop_in_des"), // [C] Cold design temperature - as_double("T_loop_out"), // [C] hot design temperature - as_double("T_loop_out"), // [C] Initial temperature in hot storage tank - as_double("T_loop_in_des"), // [C] Initial temperature in cold storage cold - as_double("init_hot_htf_percent"), // [%] Initial fraction of available volume that is hot - as_integer("tes_pb_n_xsteps"), // number spatial sub steps - as_integer("tes_n_tsteps"), // number subtimesteps - as_double("tes_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop - as_double("tes_pb_k_eff"), // [W/m K] Effective thermal conductivity - as_double("tes_pb_void_frac"), // [] Packed bed void fraction - as_double("tes_pb_dens_solid"), // [kg/m3] solid specific heat - as_double("tes_pb_cp_solid"), // [J/kg K] solid specific heat - as_double("tes_pb_T_hot_delta"), // [C] Max allowable decrease in hot discharge temp - as_double("tes_pb_T_cold_delta"), // [C] Max allowable increase in cold discharge temp - as_double("tes_pb_T_charge_min") // [C] Min allowable charge temperature - ); - - storage_pointer = &storage_packedbed; - - // Set storage outputs - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_Q_DOT_LOSS, allocate("tank_losses", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_W_DOT_HEATER, allocate("q_tes_heater", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_TES_T_HOT, allocate("T_tes_hot", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_TES_T_COLD, allocate("T_tes_cold", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_M_DOT_TANK_TO_TANK, allocate("m_dot_cold_tank_to_hot_tank", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_MASS_COLD_TANK, allocate("mass_tes_cold", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_MASS_HOT_TANK, allocate("mass_tes_hot", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_W_DOT_HTF_PUMP, allocate("tes_htf_pump_power", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_VOL_TOT, allocate("vol_tes_tot", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_MASS_TOT, allocate("tes_mass_tot", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_0, allocate("T_grad_0", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_1, allocate("T_grad_1", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_2, allocate("T_grad_2", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_3, allocate("T_grad_3", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_4, allocate("T_grad_4", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_5, allocate("T_grad_5", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_6, allocate("T_grad_6", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_7, allocate("T_grad_7", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_8, allocate("T_grad_8", n_steps_fixed), n_steps_fixed); - storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_9, allocate("T_grad_9", n_steps_fixed), n_steps_fixed); - } - else { - + throw exec_error("trough_physical", "tes_type must be 0-2"); } // ************************************************************************* @@ -2073,8 +2064,10 @@ class cm_trough_physical : public compute_module double tes_htf_min_temp = 0; double tes_htf_max_temp = 0; double vol_min = 0; + int is_hx = 0; + double hot_vol_frac = 0; - if (tes_type == 0) + if (tes_type == C_csp_tes::csp_tes_types::E_TES_TWO_TANK) { storage_two_tank.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); @@ -2082,22 +2075,26 @@ class cm_trough_physical : public compute_module tes_htf_min_temp = storage_two_tank.get_min_storage_htf_temp() - 273.15; tes_htf_max_temp = storage_two_tank.get_max_storage_htf_temp() - 273.15; vol_min = V_tes_htf_total_calc * (storage_two_tank.m_h_tank_min / h_tank_calc); + hot_vol_frac = storage_two_tank.get_hot_tank_vol_frac(); + is_hx = storage_two_tank.get_is_hx(); } - else if (tes_type == 1) + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_PACKED_BED) { - storage_NT.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, + storage_packedbed.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + hot_vol_frac = storage_packedbed.get_hot_tank_vol_frac(); } - else if (tes_type == 2) + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) { - storage_packedbed.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, + storage_NT.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + hot_vol_frac = storage_NT.get_hot_tank_vol_frac(); } + Q_tes = Q_tes_des_calc; - double V_tank_hot_ini = (as_double("h_tank_min") / h_tank_calc) * V_tes_htf_total_calc; // m3 + double V_tank_hot_ini = hot_vol_frac * V_tes_htf_total_calc; // m3 double T_avg = (as_double("T_loop_in_des") + as_double("T_loop_out")) / 2.0; // C - assign("q_tes", Q_tes_des_calc); // MWt-hr assign("tes_avail_vol", V_tes_htf_avail_calc); // m3 @@ -2106,7 +2103,7 @@ class cm_trough_physical : public compute_module assign("csp_pt_tes_tank_diameter", d_tank_calc); // m assign("q_dot_tes_est", q_dot_loss_tes_des_calc); // MWt assign("csp_pt_tes_htf_density", dens_store_htf_at_T_ave_calc); // kg/m3 - assign("is_hx", 0); + assign("is_hx", is_hx); assign("vol_min", vol_min); // m3 assign("V_tank_hot_ini", V_tank_hot_ini); // m3 assign("tes_htf_avg_temp", T_avg); // C @@ -2459,10 +2456,17 @@ class cm_trough_physical : public compute_module } // Non-timeseries array outputs - //double P_adj = storage.P_in_des; // slightly adjust all field design pressures to account for pressure drop in TES before hot tank - //transform(c_trough.m_P_rnr_dsn.begin(), c_trough.m_P_rnr_dsn.end(), c_trough.m_P_rnr_dsn.begin(), [P_adj](double x) {return x + P_adj; }); - //transform(c_trough.m_P_hdr_dsn.begin(), c_trough.m_P_hdr_dsn.end(), c_trough.m_P_hdr_dsn.begin(), [P_adj](double x) {return x + P_adj; }); - //transform(c_trough.m_P_loop_dsn.begin(), c_trough.m_P_loop_dsn.end(), c_trough.m_P_loop_dsn.begin(), [P_adj](double x) {return x + P_adj; }); + double P_adj = 0; // slightly adjust all field design pressures to account for pressure drop in TES before hot tank + if (tes_type == C_csp_tes::csp_tes_types::E_TES_TWO_TANK) + P_adj = storage_two_tank.P_in_des; + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) + P_adj = storage_NT.P_in_des; + + transform(c_trough.m_P_rnr_dsn.begin(), c_trough.m_P_rnr_dsn.end(), c_trough.m_P_rnr_dsn.begin(), [P_adj](double x) {return x + P_adj; }); + transform(c_trough.m_P_hdr_dsn.begin(), c_trough.m_P_hdr_dsn.end(), c_trough.m_P_hdr_dsn.begin(), [P_adj](double x) {return x + P_adj; }); + transform(c_trough.m_P_loop_dsn.begin(), c_trough.m_P_loop_dsn.end(), c_trough.m_P_loop_dsn.begin(), [P_adj](double x) {return x + P_adj; }); + + ssc_number_t *p_pipe_runner_diams = allocate("pipe_runner_diams", c_trough.m_D_runner.size()); std::copy(c_trough.m_D_runner.begin(), c_trough.m_D_runner.end(), p_pipe_runner_diams); @@ -2504,7 +2508,7 @@ class cm_trough_physical : public compute_module std::copy(c_trough.m_P_loop_dsn.begin(), c_trough.m_P_loop_dsn.end(), p_pipe_loop_P_dsn); // Two Tank specific outputs - if (tes_type == 0) + if (tes_type == C_csp_tes::csp_tes_types::E_TES_TWO_TANK) { ssc_number_t* p_pipe_tes_diams = allocate("pipe_tes_diams", storage_two_tank.pipe_diams.ncells()); std::copy(storage_two_tank.pipe_diams.data(), storage_two_tank.pipe_diams.data() + storage_two_tank.pipe_diams.ncells(), p_pipe_tes_diams); @@ -2520,9 +2524,7 @@ class cm_trough_physical : public compute_module std::copy(storage_two_tank.pipe_T_des.data(), storage_two_tank.pipe_T_des.data() + storage_two_tank.pipe_T_des.ncells(), p_pipe_tes_T_dsn); ssc_number_t* p_pipe_tes_P_dsn = allocate("pipe_tes_P_dsn", storage_two_tank.pipe_P_des.ncells()); std::copy(storage_two_tank.pipe_P_des.data(), storage_two_tank.pipe_P_des.data() + storage_two_tank.pipe_P_des.ncells(), p_pipe_tes_P_dsn); - } - - + } // Monthly outputs if (!as_boolean("vacuum_arrays")) { diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 540a80b1b..05b1eac66 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -44,6 +44,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "csp_solver_pc_heat_sink.h" #include "csp_solver_two_tank_tes.h" #include "cst_iph_dispatch.h" +#include "csp_solver_NTHeatTrap_tes.h" +#include "csp_solver_packedbed_tes.h" #include "csp_system_costs.h" //#include "cmod_csp_common_eqns.h" @@ -173,22 +175,45 @@ static var_info _cm_vtab_trough_physical_iph[] = { - // TES - { SSC_INPUT, SSC_NUMBER, "store_fluid", "Material number for storage fluid", "-", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_MATRIX, "store_fl_props", "User defined storage fluid property data", "-", "", "TES", "*", "", "" }, + // General TES Parameters + { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), Packed Bed (1), HeatTrap Single Tank (2)", "-", "", "TES", "?=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "tshours", "Equivalent full-load thermal storage hours", "hr", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "is_h_tank_fixed", "[1] Use fixed height (calculate diameter) [0] Use fixed diameter", "-", "", "TES", "?=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "is_h_tank_fixed", "[1] Use fixed height (calculate diameter) [0] Use fixed diameter [2] Use fixed d and h (for packed bed)", "-", "", "TES", "?=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "h_tank_in", "Total height of tank input (height of HTF when tank is full", "m", "", "TES", "is_h_tank_fixed=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "d_tank_in", "Tank diameter input", "m", "", "TES", "is_h_tank_fixed=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "TES", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "d_tank_in", "Tank diameter input", "m", "", "TES", "is_h_tank_fixed=0|is_h_tank_fixed=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "TES", "tes_type=0|tes_type=2", "", "" }, { SSC_INPUT, SSC_NUMBER, "tank_pairs", "Number of equivalent tank pairs", "-", "", "TES", "*", "INTEGER", "" }, - { SSC_INPUT, SSC_NUMBER, "hot_tank_Thtr", "Minimum allowable hot tank HTF temp", "C", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "hot_tank_max_heat", "Rated heater capacity for hot tank heating", "MWe", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "cold_tank_Thtr", "Minimum allowable cold tank HTF temp", "C", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "cold_tank_max_heat", "Rated heater capacity for cold tank heating", "MWe", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "dt_hot", "Hot side HX approach temp", "C", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "hot_tank_Thtr", "Minimum allowable hot tank HTF temp", "C", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "hot_tank_max_heat", "Rated heater capacity for hot tank heating", "MWe", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "cold_tank_Thtr", "Minimum allowable cold tank HTF temp", "C", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "cold_tank_max_heat", "Rated heater capacity for cold tank heating", "MWe", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "tes_type=0|tes_type=2", "", "" }, { SSC_INPUT, SSC_NUMBER, "init_hot_htf_percent", "Initial fraction of avail. vol that is hot", "%", "", "TES", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_n_tsteps", "Number of subtimesteps (for NT and packed bed)", "", "", "TES", "tes_type>0", "", "" }, + + // TES Two Tank Specific + { SSC_INPUT, SSC_NUMBER, "store_fluid", "Material number for storage fluid", "-", "", "TES", "tes_type=0", "", "" }, + { SSC_INPUT, SSC_MATRIX, "store_fl_props", "User defined storage fluid property data", "-", "", "TES", "tes_type=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "dt_hot", "Hot side HX approach temp", "C", "", "TES", "tes_type=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tanks_in_parallel", "Tanks are in parallel, not in series, with solar field", "-", "", "controller", "tes_type=0", "", "" }, + + // TES Norwich HeatTrap + { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_thick", "Tank wall thickness (used for Norwich HeatTrap)", "m", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_cp", "Tank wall cp (used for Norwich HeatTrap)", "kJ/kg-K", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_dens", "Tank wall thickness (used for Norwich HeatTrap)", "kg/m3", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_ARRAY, "tes_nt_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_insul_percent", "Percent additional wall mass due to insulation (used for Norwich HeatTrap)", "%", "", "TES", "?=0", "", "" }, + + // TES Packed Bed + { SSC_INPUT, SSC_NUMBER, "tes_pb_n_xsteps", "Number of spatial segments", "", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_k_eff", "TES packed bed effective conductivity", "W/m K", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_void_frac", "TES packed bed void fraction", "", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_dens_solid", "TES packed bed media density", "kg/m3", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_cp_solid", "TES particle specific heat", "kJ/kg K", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_hot_delta", "Max allowable decrease in hot discharge temp", "C", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_cold_delta", "Max allowable increase in cold discharge temp", "C", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_charge_min", "Min charge temp", "C", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_f_oversize", "Packed bed oversize factor", "", "", "TES", "tes_type=1", "", "" }, // Dispatch optimization @@ -536,7 +561,6 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "T_heat_sink_in", "Heat sink HTF inlet temp", "C", "", "Heat_Sink", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "T_heat_sink_out", "Heat sink HTF outlet temp", "C", "", "Heat_Sink", "sim_type=1", "", "" }, - // TES { SSC_OUTPUT, SSC_ARRAY, "tank_losses", "TES thermal losses", "MWt", "", "TES", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "q_tes_heater", "TES freeze protection power", "MWe", "", "TES", "sim_type=1", "", "" }, @@ -556,6 +580,38 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "m_dot_cold_tank_to_hot_tank", "Mass flow: cold tank to hot tank", "kg/s", "", "TES", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "tes_htf_pump_power", "TES HTF pump power", "MWe", "", "TES", "sim_type=1", "", "" }, + // NT TES + { SSC_OUTPUT, SSC_ARRAY, "vol_tes_cold", "TES cold fluid volume", "m3", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "vol_tes_hot", "TES hot fluid volume", "m3" , "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "vol_tes_tot", "TES total fluid volume", "m3", "", "TES", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_piston_loc", "TES piston distance from left (cold) side", "m", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_piston_frac", "TES piston fraction of cold distance over total", "", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_cold_vol_frac", "TES volume fraction of cold over total", "", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_mass_tot", "TES total fluid mass", "kg", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_SA_cold", "TES cold side surface area", "m2", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_SA_hot", "TES hot side surface area", "m2", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_SA_tot", "TES total surface area", "m2", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error", "TES energy balance error", "MWt", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error_percent", "TES energy balance error percent", "%", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_leak_error", "TES energy balance error due to leakage assumption", "MWt", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_wall_error", "TES energy balance error due to wall temperature assumption", "MWt", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error_corrected", "TES energy balance error, accounting for wall and temperature assumption error", "MWt", "", "TES", "sim_type=1&tes_type=2", "", "" }, + + // Packed Bed TES + { SSC_OUTPUT, SSC_ARRAY, "T_grad_0", "TES Temperature gradient 0 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_1", "TES Temperature gradient 1 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_2", "TES Temperature gradient 2 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_3", "TES Temperature gradient 3 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_4", "TES Temperature gradient 4 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_5", "TES Temperature gradient 5 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_6", "TES Temperature gradient 6 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_7", "TES Temperature gradient 7 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_8", "TES Temperature gradient 8 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_9", "TES Temperature gradient 9 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + + //{ SSC_OUTPUT, SSC_ARRAY, "m_dot_tes_dc", "TES discharge mass flow rate", "kg/s", "", "TES", "*", "", "" }, + //{ SSC_OUTPUT, SSC_ARRAY, "m_dot_tes_ch", "TES charge mass flow rate", "kg/s", "", "TES", "*", "", "" }, + // SYSTEM { SSC_OUTPUT, SSC_ARRAY, "W_dot_parasitic_tot", "System total electrical parasitic", "MWe", "", "system", "sim_type=1", "", "" }, @@ -629,13 +685,13 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "heat_load_capacity_factor", "Percentage of heat load met", "%", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "recirculating", "Field recirculating (bypass valve open)", "-", "", "solar_field", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_diams", "Pipe diameters in TES", "m", "", "TES", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_wallthk", "Pipe wall thickness in TES", "m", "", "TES", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_lengths", "Pipe lengths in TES", "m", "", "TES", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_mdot_dsn", "Mass flow TES pipes at design conditions", "kg/s", "", "TES", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_vel_dsn", "Velocity in TES pipes at design conditions", "m/s", "", "TES", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_T_dsn", "Temperature in TES pipes at design conditions", "C", "", "TES", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_P_dsn", "Pressure in TES pipes at design conditions", "bar", "", "TES", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_diams", "Pipe diameters in TES", "m", "", "TES", "sim_type=1&tes_type=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_wallthk", "Pipe wall thickness in TES", "m", "", "TES", "sim_type=1&tes_type=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_lengths", "Pipe lengths in TES", "m", "", "TES", "sim_type=1&tes_type=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_mdot_dsn", "Mass flow TES pipes at design conditions", "kg/s", "", "TES", "sim_type=1&tes_type=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_vel_dsn", "Velocity in TES pipes at design conditions", "m/s", "", "TES", "sim_type=1&tes_type=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_T_dsn", "Temperature in TES pipes at design conditions", "C", "", "TES", "sim_type=1&tes_type=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_P_dsn", "Pressure in TES pipes at design conditions", "bar", "", "TES", "sim_type=1&tes_type=0", "", "" }, //{ SSC_OUTPUT, SSC_ARRAY, "defocus", "Field optical focus fraction", "", "", "solver", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "electricity_rate", "Electricity price = calculated annual elec cost / total elec energy consusmed", "$/kWe-hr", "", "Post-process", "sim_type=1&csp_financial_model=7", "", "" }, @@ -673,6 +729,8 @@ class cm_trough_physical_iph : public compute_module double q_dot_hs_des = as_double("q_pb_design"); //[MWt] HEAT SINK design thermal power double Q_tes = q_dot_hs_des * tshours; //[MWt-hr] int is_dispatch = as_boolean("is_dispatch"); + + int tes_type = as_integer("tes_type"); // ***************************************************** // System Design Parameters @@ -724,7 +782,6 @@ class cm_trough_physical_iph : public compute_module sim_setup.m_report_step = 3600.0 / (double)steps_per_hour; //[s] } - // ******************************** // ******************************** // Solar field, trough @@ -1033,7 +1090,6 @@ class cm_trough_physical_iph : public compute_module } - // Heat Sink C_pc_heat_sink c_heat_sink; { @@ -1062,15 +1118,18 @@ class cm_trough_physical_iph : public compute_module c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_T_HTF_IN, allocate("T_heat_sink_in", n_steps_fixed), n_steps_fixed); c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_T_HTF_OUT, allocate("T_heat_sink_out", n_steps_fixed), n_steps_fixed); } - - - + // ******************************** // ******************************** // TES // ******************************** // ******************************** - C_csp_two_tank_tes storage; + C_csp_tes* storage_pointer; + C_csp_two_tank_tes storage_two_tank; + C_csp_NTHeatTrap_tes storage_NT; + C_csp_packedbed_tes storage_packedbed; + // Two Tank + if (tes_type == C_csp_tes::csp_tes_types::E_TES_TWO_TANK) { bool custom_tes_pipe_sizes = as_boolean("custom_tes_pipe_sizes"); @@ -1098,7 +1157,7 @@ class cm_trough_physical_iph : public compute_module double h_tank_in = is_assigned("h_tank_in") == true ? as_double("h_tank_in") : std::numeric_limits::quiet_NaN(); double d_tank_in = is_assigned("d_tank_in") == true ? as_double("d_tank_in") : std::numeric_limits::quiet_NaN(); - storage = C_csp_two_tank_tes( + storage_two_tank = C_csp_two_tank_tes( c_trough.m_Fluid, c_trough.m_field_fl_props, as_integer("store_fluid"), @@ -1118,8 +1177,9 @@ class cm_trough_physical_iph : public compute_module as_double("dt_hot"), as_double("T_loop_in_des"), as_double("T_loop_out"), - as_double("T_loop_out"), - as_double("T_loop_in_des"), + // Check initialization variables + (is_assigned("T_tank_hot_init")) ? as_double("T_tank_hot_init") : as_double("T_loop_out"), + (is_assigned("T_tank_cold_init")) ? as_double("T_tank_cold_init") : as_double("T_loop_in_des"), as_double("h_tank_min"), as_double("init_hot_htf_percent"), as_double("pb_pump_coef"), @@ -1139,17 +1199,190 @@ class cm_trough_physical_iph : public compute_module as_double("HDR_rough") ); + storage_pointer = &storage_two_tank; + + // Set storage outputs + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_Q_DOT_LOSS, allocate("tank_losses", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_W_DOT_HEATER, allocate("q_tes_heater", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_TES_T_HOT, allocate("T_tes_hot", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_TES_T_COLD, allocate("T_tes_cold", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_M_DOT_TANK_TO_TANK, allocate("m_dot_cold_tank_to_hot_tank", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_MASS_COLD_TANK, allocate("mass_tes_cold", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_MASS_HOT_TANK, allocate("mass_tes_hot", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_W_DOT_HTF_PUMP, allocate("tes_htf_pump_power", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_VOL_TOT, allocate("vol_tes_tot", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_MASS_TOT, allocate("tes_mass_tot", n_steps_fixed), n_steps_fixed); + storage_two_tank.mc_reported_outputs.assign(C_csp_two_tank_tes::E_HOT_TANK_HTF_PERC_FINAL, allocate("hot_tank_htf_percent_final", n_steps_fixed), n_steps_fixed); + } + // Packed Bed + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_PACKED_BED) + { + storage_packedbed = C_csp_packedbed_tes( + as_integer("Fluid"), // [-] field fluid identifier + as_matrix("field_fl_props"), // [-] field fluid properties + q_dot_pc_des, // [MWt] Design heat rate in and out of tes + Q_tes, // [MWt-hr] design storage capacity + as_integer("is_h_tank_fixed"), // [] Sizing Method (0) use fixed diameter, (1) use fixed height, (2) use preset inputs + as_double("h_tank_in"), // [m] Tank height + as_double("d_tank_in"), // [m] Tank diameter + as_double("tes_pb_f_oversize"), // [] Oversize factor + as_double("T_loop_in_des"), // [C] Cold design temperature + as_double("T_loop_out"), // [C] hot design temperature + // Check initialization variables + (is_assigned("T_tank_hot_init")) ? as_double("T_tank_hot_init") : as_double("T_loop_out"), + (is_assigned("T_tank_cold_init")) ? as_double("T_tank_cold_init") : as_double("T_loop_in_des"), + as_double("init_hot_htf_percent"), // [%] Initial fraction of available volume that is hot + as_integer("tes_pb_n_xsteps"), // number spatial sub steps + as_integer("tes_n_tsteps"), // number subtimesteps + as_double("tes_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop + as_double("tes_pb_k_eff"), // [W/m K] Effective thermal conductivity + as_double("tes_pb_void_frac"), // [] Packed bed void fraction + as_double("tes_pb_dens_solid"), // [kg/m3] solid specific heat + as_double("tes_pb_cp_solid"), // [kJ/kg K] solid specific heat + as_double("tes_pb_T_hot_delta"), // [C] Max allowable decrease in hot discharge temp + as_double("tes_pb_T_cold_delta"), // [C] Max allowable increase in cold discharge temp + as_double("tes_pb_T_charge_min") // [C] Min allowable charge temperature + ); + + storage_pointer = &storage_packedbed; + // Set storage outputs - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_Q_DOT_LOSS, allocate("tank_losses", n_steps_fixed), n_steps_fixed); - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_W_DOT_HEATER, allocate("q_tes_heater", n_steps_fixed), n_steps_fixed); - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_TES_T_HOT, allocate("T_tes_hot", n_steps_fixed), n_steps_fixed); - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_TES_T_COLD, allocate("T_tes_cold", n_steps_fixed), n_steps_fixed); - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_M_DOT_TANK_TO_TANK, allocate("m_dot_cold_tank_to_hot_tank", n_steps_fixed), n_steps_fixed); - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_MASS_COLD_TANK, allocate("mass_tes_cold", n_steps_fixed), n_steps_fixed); - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_MASS_HOT_TANK, allocate("mass_tes_hot", n_steps_fixed), n_steps_fixed); - storage.mc_reported_outputs.assign(C_csp_two_tank_tes::E_W_DOT_HTF_PUMP, allocate("tes_htf_pump_power", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_Q_DOT_LOSS, allocate("tank_losses", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_W_DOT_HEATER, allocate("q_tes_heater", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_TES_T_HOT, allocate("T_tes_hot", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_TES_T_COLD, allocate("T_tes_cold", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_M_DOT_TANK_TO_TANK, allocate("m_dot_cold_tank_to_hot_tank", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_MASS_COLD_TANK, allocate("mass_tes_cold", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_MASS_HOT_TANK, allocate("mass_tes_hot", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_W_DOT_HTF_PUMP, allocate("tes_htf_pump_power", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_VOL_TOT, allocate("vol_tes_tot", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_MASS_TOT, allocate("tes_mass_tot", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_0, allocate("T_grad_0", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_1, allocate("T_grad_1", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_2, allocate("T_grad_2", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_3, allocate("T_grad_3", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_4, allocate("T_grad_4", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_5, allocate("T_grad_5", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_6, allocate("T_grad_6", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_7, allocate("T_grad_7", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_8, allocate("T_grad_8", n_steps_fixed), n_steps_fixed); + storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_9, allocate("T_grad_9", n_steps_fixed), n_steps_fixed); } - + // Norwich + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) + { + // Get number of sub time steps + int nstep = as_integer("tes_n_tsteps"); + + bool custom_tes_pipe_sizes = as_boolean("custom_tes_pipe_sizes"); + util::matrix_t tes_lengths; + if (is_assigned("tes_lengths")) { + tes_lengths = as_matrix("tes_lengths"); //[m] + } + if (!is_assigned("tes_lengths") || tes_lengths.ncells() < 11) { + double vals1[11] = { 0., 90., 100., 120., 0., 30., 90., 80., 80., 120., 80. }; + tes_lengths.assign(vals1, 11); + } + util::matrix_t tes_wallthicks; + if (!is_assigned("tes_wallthicks")) + { + double tes_wallthicks_val[1] = { -1 }; + tes_wallthicks.assign(tes_wallthicks_val, 1); + } + util::matrix_t tes_diams; + if (!is_assigned("tes_diams")) + { + double tes_diams_val[1] = { -1 }; + tes_diams.assign(tes_diams_val, 1); + } + + // Modify wall density to account for insulation mass + double mass_factor = 1.0 + (0.01 * as_double("tes_nt_tank_insul_percent")); + double dens_orig = as_double("tes_nt_tank_dens"); + double dens_w_insulation = dens_orig * mass_factor; + + double h_tank_in = is_assigned("h_tank_in") == true ? as_double("h_tank_in") : std::numeric_limits::quiet_NaN(); + double d_tank_in = is_assigned("d_tank_in") == true ? as_double("d_tank_in") : std::numeric_limits::quiet_NaN(); + + storage_NT = C_csp_NTHeatTrap_tes( + c_trough.m_Fluid, + c_trough.m_field_fl_props, + //as_integer("store_fluid"), + //as_matrix("store_fl_props"), + q_dot_pc_des, + c_trough.m_solar_mult, + Q_tes, + as_boolean("is_h_tank_fixed"), + h_tank_in, + d_tank_in, + as_double("u_tank"), + as_integer("tank_pairs"), + as_double("hot_tank_Thtr"), + as_double("hot_tank_max_heat"), + as_double("cold_tank_Thtr"), + as_double("cold_tank_max_heat"), + as_double("T_loop_in_des"), + as_double("T_loop_out"), + // Check initialization variables + (is_assigned("T_tank_hot_init")) ? as_double("T_tank_hot_init") : as_double("T_loop_out"), + (is_assigned("T_tank_cold_init")) ? as_double("T_tank_cold_init") : as_double("T_loop_in_des"), + as_double("h_tank_min"), + as_double("init_hot_htf_percent"), + as_double("pb_pump_coef"), + as_double("tes_nt_tank_cp") * 1000, // convert to J/kgK + dens_w_insulation, + as_double("tes_nt_tank_thick"), + nstep, + as_vector_double("tes_nt_piston_loss_poly"), + as_double("V_tes_des"), + as_boolean("calc_design_pipe_vals"), + as_double("tes_pump_coef"), + as_double("eta_pump"), + as_boolean("has_hot_tank_bypass"), + as_double("T_tank_hot_inlet_min"), + false, + false, + as_matrix("k_tes_loss_coeffs"), + tes_diams, + tes_wallthicks, + tes_lengths, + as_double("HDR_rough") + ); + + storage_pointer = &storage_NT; + + // Set storage outputs + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_Q_DOT_LOSS, allocate("tank_losses", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_W_DOT_HEATER, allocate("q_tes_heater", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_TES_T_HOT, allocate("T_tes_hot", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_TES_T_COLD, allocate("T_tes_cold", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_M_DOT_TANK_TO_TANK, allocate("m_dot_cold_tank_to_hot_tank", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_MASS_COLD_TANK, allocate("mass_tes_cold", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_MASS_HOT_TANK, allocate("mass_tes_hot", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_W_DOT_HTF_PUMP, allocate("tes_htf_pump_power", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_HOT_TANK_HTF_PERC_FINAL, allocate("hot_tank_htf_percent_final", n_steps_fixed), n_steps_fixed); + + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_VOL_COLD, allocate("vol_tes_cold", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_VOL_HOT, allocate("vol_tes_hot", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_VOL_TOT, allocate("vol_tes_tot", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_PIST_LOC, allocate("tes_piston_loc", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_PIST_FRAC, allocate("tes_piston_frac", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_COLD_FRAC, allocate("tes_cold_vol_frac", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_MASS_TOT, allocate("tes_mass_tot", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_SA_COLD, allocate("tes_SA_cold", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_SA_HOT, allocate("tes_SA_hot", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_SA_TOT, allocate("tes_SA_tot", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_ERROR, allocate("tes_error", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_ERROR_PERCENT, allocate("tes_error_percent", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_LEAK_ERROR, allocate("tes_leak_error", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_WALL_ERROR, allocate("tes_wall_error", n_steps_fixed), n_steps_fixed); + storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_ERROR_CORRECTED, allocate("tes_error_corrected", n_steps_fixed), n_steps_fixed); + } + else + { + throw exec_error("trough_physical", "tes_type must be 0-2"); + } + // ******************************** @@ -1336,7 +1569,7 @@ class cm_trough_physical_iph : public compute_module C_csp_solver csp_solver(weather_reader, c_trough, c_heat_sink, - storage, + *storage_pointer, tou, dispatch, system, @@ -1524,16 +1757,40 @@ class cm_trough_physical_iph : public compute_module q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, Q_tes_des_calc /*MWt-hr*/; - storage.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, - h_tank_calc, d_tank_calc, - q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + double tes_htf_min_temp = 0; + double tes_htf_max_temp = 0; + double vol_min = 0; + int is_hx = 0; + double hot_vol_frac = 0; + if (tes_type == C_csp_tes::csp_tes_types::E_TES_TWO_TANK) + { + storage_two_tank.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, + h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + + tes_htf_min_temp = storage_two_tank.get_min_storage_htf_temp() - 273.15; + tes_htf_max_temp = storage_two_tank.get_max_storage_htf_temp() - 273.15; + vol_min = V_tes_htf_total_calc * (storage_two_tank.m_h_tank_min / h_tank_calc); + hot_vol_frac = storage_two_tank.get_hot_tank_vol_frac(); + is_hx = storage_two_tank.get_is_hx(); + } + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_PACKED_BED) + { + storage_packedbed.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, + h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + hot_vol_frac = storage_packedbed.get_hot_tank_vol_frac(); + } + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) + { + storage_NT.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, + h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); + hot_vol_frac = storage_NT.get_hot_tank_vol_frac(); + } - double vol_min = V_tes_htf_total_calc * (storage.m_h_tank_min / h_tank_calc); - double V_tank_hot_ini = (storage.m_h_tank_min / h_tank_calc) * V_tes_htf_total_calc; // m3 + Q_tes = Q_tes_des_calc; + + double V_tank_hot_ini = hot_vol_frac * V_tes_htf_total_calc; // m3 double T_avg = (as_double("T_loop_in_des") + as_double("T_loop_out")) / 2.0; // C - double tes_htf_min_temp = storage.get_min_storage_htf_temp() - 273.15; - double tes_htf_max_temp = storage.get_max_storage_htf_temp() - 273.15; assign("q_tes", Q_tes_des_calc); // MWt-hr assign("tes_avail_vol", V_tes_htf_avail_calc); // m3 @@ -1542,7 +1799,7 @@ class cm_trough_physical_iph : public compute_module assign("csp_pt_tes_tank_diameter", d_tank_calc); // m assign("q_dot_tes_est", q_dot_loss_tes_des_calc); // MWt assign("csp_pt_tes_htf_density", dens_store_htf_at_T_ave_calc); // kg/m3 - assign("is_hx", storage.get_is_hx()); + assign("is_hx", is_hx); assign("vol_min", vol_min); // m3 assign("V_tank_hot_ini", V_tank_hot_ini); // m3 assign("tes_htf_avg_temp", T_avg); // C @@ -1908,7 +2165,12 @@ class cm_trough_physical_iph : public compute_module ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour); // Non-timeseries array outputs - double P_adj = storage.P_in_des; // slightly adjust all field design pressures to account for pressure drop in TES before hot tank + double P_adj = 0; // slightly adjust all field design pressures to account for pressure drop in TES before hot tank + if (tes_type == C_csp_tes::csp_tes_types::E_TES_TWO_TANK) + P_adj = storage_two_tank.P_in_des; + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) + P_adj = storage_NT.P_in_des; + transform(c_trough.m_P_rnr_dsn.begin(), c_trough.m_P_rnr_dsn.end(), c_trough.m_P_rnr_dsn.begin(), [P_adj](double x) {return x + P_adj; }); transform(c_trough.m_P_hdr_dsn.begin(), c_trough.m_P_hdr_dsn.end(), c_trough.m_P_hdr_dsn.begin(), [P_adj](double x) {return x + P_adj; }); transform(c_trough.m_P_loop_dsn.begin(), c_trough.m_P_loop_dsn.end(), c_trough.m_P_loop_dsn.begin(), [P_adj](double x) {return x + P_adj; }); @@ -1952,21 +2214,24 @@ class cm_trough_physical_iph : public compute_module ssc_number_t *p_pipe_loop_P_dsn = allocate("pipe_loop_P_dsn", c_trough.m_P_loop_dsn.size()); std::copy(c_trough.m_P_loop_dsn.begin(), c_trough.m_P_loop_dsn.end(), p_pipe_loop_P_dsn); - ssc_number_t *p_pipe_tes_diams = allocate("pipe_tes_diams", storage.pipe_diams.ncells()); - std::copy(storage.pipe_diams.data(), storage.pipe_diams.data() + storage.pipe_diams.ncells(), p_pipe_tes_diams); - ssc_number_t *p_pipe_tes_wallthk = allocate("pipe_tes_wallthk", storage.pipe_wall_thk.ncells()); - std::copy(storage.pipe_wall_thk.data(), storage.pipe_wall_thk.data() + storage.pipe_wall_thk.ncells(), p_pipe_tes_wallthk); - ssc_number_t* p_pipe_tes_lengths = allocate("pipe_tes_lengths", storage.pipe_lengths.ncells()); - std::copy(storage.pipe_lengths.data(), storage.pipe_lengths.data() + storage.pipe_lengths.ncells(), p_pipe_tes_lengths); - ssc_number_t *p_pipe_tes_mdot_dsn = allocate("pipe_tes_mdot_dsn", storage.pipe_m_dot_des.ncells()); - std::copy(storage.pipe_m_dot_des.data(), storage.pipe_m_dot_des.data() + storage.pipe_m_dot_des.ncells(), p_pipe_tes_mdot_dsn); - ssc_number_t *p_pipe_tes_vel_dsn = allocate("pipe_tes_vel_dsn", storage.pipe_vel_des.ncells()); - std::copy(storage.pipe_vel_des.data(), storage.pipe_vel_des.data() + storage.pipe_vel_des.ncells(), p_pipe_tes_vel_dsn); - ssc_number_t *p_pipe_tes_T_dsn = allocate("pipe_tes_T_dsn", storage.pipe_T_des.ncells()); - std::copy(storage.pipe_T_des.data(), storage.pipe_T_des.data() + storage.pipe_T_des.ncells(), p_pipe_tes_T_dsn); - ssc_number_t *p_pipe_tes_P_dsn = allocate("pipe_tes_P_dsn", storage.pipe_P_des.ncells()); - std::copy(storage.pipe_P_des.data(), storage.pipe_P_des.data() + storage.pipe_P_des.ncells(), p_pipe_tes_P_dsn); - + // Two Tank specific outputs + if (tes_type == C_csp_tes::csp_tes_types::E_TES_TWO_TANK) + { + ssc_number_t* p_pipe_tes_diams = allocate("pipe_tes_diams", storage_two_tank.pipe_diams.ncells()); + std::copy(storage_two_tank.pipe_diams.data(), storage_two_tank.pipe_diams.data() + storage_two_tank.pipe_diams.ncells(), p_pipe_tes_diams); + ssc_number_t* p_pipe_tes_wallthk = allocate("pipe_tes_wallthk", storage_two_tank.pipe_wall_thk.ncells()); + std::copy(storage_two_tank.pipe_wall_thk.data(), storage_two_tank.pipe_wall_thk.data() + storage_two_tank.pipe_wall_thk.ncells(), p_pipe_tes_wallthk); + ssc_number_t* p_pipe_tes_lengths = allocate("pipe_tes_lengths", storage_two_tank.pipe_lengths.ncells()); + std::copy(storage_two_tank.pipe_lengths.data(), storage_two_tank.pipe_lengths.data() + storage_two_tank.pipe_lengths.ncells(), p_pipe_tes_lengths); + ssc_number_t* p_pipe_tes_mdot_dsn = allocate("pipe_tes_mdot_dsn", storage_two_tank.pipe_m_dot_des.ncells()); + std::copy(storage_two_tank.pipe_m_dot_des.data(), storage_two_tank.pipe_m_dot_des.data() + storage_two_tank.pipe_m_dot_des.ncells(), p_pipe_tes_mdot_dsn); + ssc_number_t* p_pipe_tes_vel_dsn = allocate("pipe_tes_vel_dsn", storage_two_tank.pipe_vel_des.ncells()); + std::copy(storage_two_tank.pipe_vel_des.data(), storage_two_tank.pipe_vel_des.data() + storage_two_tank.pipe_vel_des.ncells(), p_pipe_tes_vel_dsn); + ssc_number_t* p_pipe_tes_T_dsn = allocate("pipe_tes_T_dsn", storage_two_tank.pipe_T_des.ncells()); + std::copy(storage_two_tank.pipe_T_des.data(), storage_two_tank.pipe_T_des.data() + storage_two_tank.pipe_T_des.ncells(), p_pipe_tes_T_dsn); + ssc_number_t* p_pipe_tes_P_dsn = allocate("pipe_tes_P_dsn", storage_two_tank.pipe_P_des.ncells()); + std::copy(storage_two_tank.pipe_P_des.data(), storage_two_tank.pipe_P_des.data() + storage_two_tank.pipe_P_des.ncells(), p_pipe_tes_P_dsn); + } // Monthly outputs accumulate_monthly_for_year("gen", "monthly_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1); diff --git a/tcs/csp_solver_NTHeatTrap_tes.cpp b/tcs/csp_solver_NTHeatTrap_tes.cpp index 5cb044d08..7dcc5e80d 100644 --- a/tcs/csp_solver_NTHeatTrap_tes.cpp +++ b/tcs/csp_solver_NTHeatTrap_tes.cpp @@ -847,8 +847,8 @@ double C_storage_tank_dynamic_NT::calc_leakage_fraction(double mdot) C_csp_NTHeatTrap_tes::C_csp_NTHeatTrap_tes( int external_fl, // [-] external fluid identifier util::matrix_t external_fl_props, // [-] external fluid properties - int tes_fl, // [-] tes fluid identifier - util::matrix_t tes_fl_props, // [-] tes fluid properties + //int tes_fl, // [-] tes fluid identifier + //util::matrix_t tes_fl_props, // [-] tes fluid properties double q_dot_design, // [MWt] Design heat rate in and out of tes double frac_max_q_dot, // [-] the max design heat rate as a fraction of the nominal double Q_tes_des, // [MWt-hr] design storage capacity @@ -889,7 +889,7 @@ C_csp_NTHeatTrap_tes::C_csp_NTHeatTrap_tes( double dP_discharge // [bar] Pressure drop on the TES discharge side (e.g., within the steam generator) ) : - m_external_fl(external_fl), m_external_fl_props(external_fl_props), m_tes_fl(tes_fl), m_tes_fl_props(tes_fl_props), + m_external_fl(external_fl), m_external_fl_props(external_fl_props), //m_tes_fl(tes_fl), m_tes_fl_props(tes_fl_props), m_q_dot_design(q_dot_design), m_frac_max_q_dot(frac_max_q_dot), m_Q_tes_des(Q_tes_des), m_is_h_fixed(is_h_fixed), m_h_tank_in(h_tank_in), m_d_tank_in(d_tank_in), m_u_tank(u_tank), m_tank_pairs(tank_pairs), m_hot_tank_Thtr(hot_tank_Thtr), m_hot_tank_max_heat(hot_tank_max_heat), @@ -969,40 +969,42 @@ void C_csp_NTHeatTrap_tes::init(const C_csp_tes::S_csp_tes_init_inputs init_inpu throw(C_csp_exception("External HTF code is not recognized", "Two Tank TES Initialization")); } + // For NT Heattrap, storage fluid IS external fluid + mc_store_htfProps = mc_external_htfProps; - // Declare instance of fluid class for STORAGE fluid. - // Set fluid number and copy over fluid matrix if it makes sense. - if (m_tes_fl != HTFProperties::User_defined && m_tes_fl < HTFProperties::End_Library_Fluids) - { - if (!mc_store_htfProps.SetFluid(m_tes_fl)) - { - throw(C_csp_exception("Storage HTF code is not recognized", "Two Tank TES Initialization")); - } - } - else if (m_tes_fl == HTFProperties::User_defined) - { - int n_rows = (int)m_tes_fl_props.nrows(); - int n_cols = (int)m_tes_fl_props.ncols(); - if (n_rows > 2 && n_cols == 7) - { - if (!mc_store_htfProps.SetUserDefinedFluid(m_tes_fl_props)) - { - error_msg = util::format(mc_store_htfProps.UserFluidErrMessage(), n_rows, n_cols); - throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); - } - } - else - { - error_msg = util::format("The user defined storage HTF 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, "Two Tank TES Initialization")); - } - } - else - { - throw(C_csp_exception("Storage HTF code is not recognized", "Two Tank TES Initialization")); - } + //// Declare instance of fluid class for STORAGE fluid. + //// Set fluid number and copy over fluid matrix if it makes sense. + //if (m_tes_fl != HTFProperties::User_defined && m_tes_fl < HTFProperties::End_Library_Fluids) + //{ + // if (!mc_store_htfProps.SetFluid(m_tes_fl)) + // { + // throw(C_csp_exception("Storage HTF code is not recognized", "Two Tank TES Initialization")); + // } + //} + //else if (m_tes_fl == HTFProperties::User_defined) + //{ + // int n_rows = (int)m_tes_fl_props.nrows(); + // int n_cols = (int)m_tes_fl_props.ncols(); + // if (n_rows > 2 && n_cols == 7) + // { + // if (!mc_store_htfProps.SetUserDefinedFluid(m_tes_fl_props)) + // { + // error_msg = util::format(mc_store_htfProps.UserFluidErrMessage(), n_rows, n_cols); + // throw(C_csp_exception(error_msg, "Two Tank TES Initialization")); + // } + // } + // else + // { + // error_msg = util::format("The user defined storage HTF 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, "Two Tank TES Initialization")); + // } + //} + //else + //{ + // throw(C_csp_exception("Storage HTF code is not recognized", "Two Tank TES Initialization")); + //} - bool is_hx_calc = false; + /*bool is_hx_calc = false; if (m_tes_fl != m_external_fl) is_hx_calc = true; else if (m_external_fl != HTFProperties::User_defined) @@ -1014,7 +1016,7 @@ void C_csp_NTHeatTrap_tes::init(const C_csp_tes::S_csp_tes_init_inputs init_inpu if (is_hx_calc == true) { throw(C_csp_exception("NT Tank must have the same external and internal fluid", "NT TES Initialization")); - } + }*/ if (tanks_in_parallel) { m_is_cr_to_cold_tank_allowed = false; diff --git a/tcs/csp_solver_NTHeatTrap_tes.h b/tcs/csp_solver_NTHeatTrap_tes.h index 3287b8c49..8c0e7a501 100644 --- a/tcs/csp_solver_NTHeatTrap_tes.h +++ b/tcs/csp_solver_NTHeatTrap_tes.h @@ -268,8 +268,8 @@ class C_csp_NTHeatTrap_tes : public C_csp_tes int m_external_fl; util::matrix_t m_external_fl_props; - int m_tes_fl; - util::matrix_t m_tes_fl_props; + //int m_tes_fl; + //util::matrix_t m_tes_fl_props; double m_q_dot_design; //[MWe] Design heat rate in and out of tes double m_frac_max_q_dot; //[-] the max design heat rate as a fraction of the nominal double m_Q_tes_des; //[MWt-hr] design storage capacity @@ -315,8 +315,8 @@ class C_csp_NTHeatTrap_tes : public C_csp_tes C_csp_NTHeatTrap_tes( int external_fl, util::matrix_t external_fl_props, - int tes_fl, - util::matrix_t tes_fl_props, + //int tes_fl, + //util::matrix_t tes_fl_props, double q_dot_design, // [MWt] Design heat rate in and out of tes double frac_max_q_dot, // [-] the max design heat rate as a fraction of the nominal double Q_tes_des, // [MWt-hr] design storage capacity diff --git a/tcs/csp_solver_core.h b/tcs/csp_solver_core.h index 501a8fe0e..fdfe16afe 100644 --- a/tcs/csp_solver_core.h +++ b/tcs/csp_solver_core.h @@ -728,6 +728,13 @@ class C_csp_tes public: + enum csp_tes_types + { + E_TES_TWO_TANK, + E_TES_PACKED_BED, + E_TES_NT + }; + // Class to save messages for up stream classes C_csp_messages mc_csp_messages; diff --git a/tcs/csp_solver_packedbed_tes.cpp b/tcs/csp_solver_packedbed_tes.cpp index 2d313b02a..fdf6a46bc 100644 --- a/tcs/csp_solver_packedbed_tes.cpp +++ b/tcs/csp_solver_packedbed_tes.cpp @@ -149,6 +149,7 @@ double C_csp_packedbed_tes::get_avg_from_vec(std::vector vec, double fra C_csp_packedbed_tes::C_csp_packedbed_tes( int external_fl, // [-] external fluid identifier util::matrix_t external_fl_props, // [-] external fluid properties + double q_dot_design, // [MWt] Design heat rate in and out of tes double Q_tes_des, // [MWt-hr] design storage capacity int size_type, // [] Sizing Method (0) use fixed diameter, (1) use fixed height, (2) use preset inputs double h_tank_in, // [m] tank height input @@ -165,17 +166,17 @@ C_csp_packedbed_tes::C_csp_packedbed_tes( double k_eff, // [W/m K] Effective thermal conductivity double void_frac, // [] Packed bed void fraction double dens_solid, // [kg/m3] solid specific heat - double cp_solid, // [J/kg K] solid specific heat + double cp_solid, // [kJ/kg K] solid specific heat double T_hot_delta, // [C] Max allowable decrease in hot discharge temp double T_cold_delta, // [C] Max allowable increase in cold discharge temp double T_charge_min_C // [C] Min allowable charge temperature ) : - m_external_fl(external_fl), m_external_fl_props(external_fl_props), m_Q_tes_des(Q_tes_des), + m_external_fl(external_fl), m_external_fl_props(external_fl_props), m_q_dot_design(q_dot_design), m_Q_tes_des(Q_tes_des), m_size_type(size_type), m_h_tank_in(h_tank_in), m_d_tank_in(d_tank_in), m_f_oversize(f_oversize), m_f_V_hot_ini(f_V_hot_ini), m_n_xstep(n_xstep), m_n_subtimestep(n_subtimestep), m_tes_pump_coef(tes_pump_coef), - m_k_eff(k_eff), m_void_frac(void_frac), m_dens_solid(dens_solid), m_cp_solid(cp_solid), + m_k_eff(k_eff), m_void_frac(void_frac), m_dens_solid(dens_solid), m_T_hot_delta(T_hot_delta), m_T_cold_delta(T_cold_delta) { // Convert Temperature Units @@ -185,6 +186,9 @@ C_csp_packedbed_tes::C_csp_packedbed_tes( m_T_tank_cold_ini = T_tank_cold_ini_C + 273.15; //[K] m_T_charge_min = T_charge_min_C + 273.15; //[K] + // Convert Specific heat unit + m_cp_solid = cp_solid * 1e3; //[J/kg K] + // Set Subtimestep m_subtimestep_nominal = 3600.0 / m_n_subtimestep; //[s] @@ -199,7 +203,10 @@ C_csp_packedbed_tes::C_csp_packedbed_tes() void C_csp_packedbed_tes::set_T_grad_init(std::vector T_grad_init_C) { for (double T_C : T_grad_init_C) + { + m_T_grad_init.push_back(T_C + 273.15); m_T_prev_vec.push_back(T_C + 273.15); + } m_use_T_grad_init = true; } @@ -320,6 +327,10 @@ void C_csp_packedbed_tes::init(const C_csp_tes::S_csp_tes_init_inputs init_input } } + // Calculate thermal power to PC at design + m_q_pb_design = m_q_dot_design * 1.E6; //[Wt] + + m_ts_hours = m_Q_tes_des / m_q_dot_design; } bool C_csp_packedbed_tes::does_tes_exist() @@ -372,26 +383,64 @@ double C_csp_packedbed_tes::get_hot_tank_vol_frac() double C_csp_packedbed_tes::get_initial_charge_energy() { - return std::numeric_limits::quiet_NaN(); + //MWh + return m_q_pb_design * m_ts_hours * (m_f_V_hot_ini / 100.0) * 1.e-6; + } double C_csp_packedbed_tes::get_min_charge_energy() { - return std::numeric_limits::quiet_NaN(); + //MWh + return 0.; } double C_csp_packedbed_tes::get_max_charge_energy() { - return std::numeric_limits::quiet_NaN(); + //MWh + return m_q_pb_design * m_ts_hours / 1.e6; } double C_csp_packedbed_tes::get_degradation_rate() { - return std::numeric_limits::quiet_NaN(); + // No heat loss in packed bed model + return 0.0; } void C_csp_packedbed_tes::reset_storage_to_initial_state() { + // Define initial temperatures + if (m_use_T_grad_init == false) + { + double dx = m_h_tank_calc / m_n_xstep; // [m] + double x_end = 0; //[m] + double x_mid = 0; //[m] + m_T_prev_vec = std::vector(m_n_xstep + 1); + // Loop through space + for (int i = 0; i <= m_n_xstep; i++) + { + // Update position of center of section + if (i == 0 || i == m_n_xstep) + { + x_end += dx * 0.5; + x_mid = x_end - (dx * 0.5 * 0.5); + } + else + { + x_end += dx; + x_mid = x_end - (dx * 0.5); + } + + double frac_mid = x_mid / m_h_tank_calc; + if (frac_mid < m_f_V_hot_ini * 0.01) + m_T_prev_vec[i] = m_T_tank_hot_ini; + else + m_T_prev_vec[i] = m_T_tank_cold_ini; + } + } + else + { + m_T_prev_vec = m_T_grad_init; + } return; } @@ -982,23 +1031,3 @@ bool C_csp_packedbed_tes::discharge(double timestep /*s*/, double T_amb /*K*/, d return true; } - -double C_csp_packedbed_tes::get_min_storage_htf_temp() -{ - return std::numeric_limits::quiet_NaN(); -} - -double C_csp_packedbed_tes::get_max_storage_htf_temp() -{ - return std::numeric_limits::quiet_NaN(); -} - -double C_csp_packedbed_tes::get_storage_htf_density() -{ - return std::numeric_limits::quiet_NaN(); -} - -double C_csp_packedbed_tes::get_storage_htf_cp() -{ - return std::numeric_limits::quiet_NaN(); -} diff --git a/tcs/csp_solver_packedbed_tes.h b/tcs/csp_solver_packedbed_tes.h index b5c053644..7798d5f94 100644 --- a/tcs/csp_solver_packedbed_tes.h +++ b/tcs/csp_solver_packedbed_tes.h @@ -49,6 +49,7 @@ class C_csp_packedbed_tes : public C_csp_tes int m_external_fl; util::matrix_t m_external_fl_props; int m_size_type; // [] Sizing Method (0) use fixed diameter, (1) use fixed height, (2) use preset inputs + double m_q_dot_design; //[MWe] Design heat rate in and out of tes double m_Q_tes_des; // [MWt-hr] design storage capacity double m_h_tank_in; // [m] Tank height double m_d_tank_in; // [m] tank diameter input @@ -86,6 +87,9 @@ class C_csp_packedbed_tes : public C_csp_tes bool m_is_tes; bool m_use_T_grad_init = false; HTFProperties mc_external_htfProps; // Instance of HTFProperties class for external HTF + double m_q_pb_design; //[Wt] thermal power to sink at design + double m_ts_hours; //[hr] hours of storage at design sink operation + std::vector m_T_grad_init; //[K] (optional) initial temperature vector // Private Methods void size_pb_fixed_height(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double f_oversize, @@ -136,6 +140,7 @@ class C_csp_packedbed_tes : public C_csp_tes C_csp_packedbed_tes( int external_fl, // [-] external fluid identifier util::matrix_t external_fl_props, // [-] external fluid properties + double q_dot_design, // [MWt] Design heat rate in and out of tes double Q_tes_des, // [MWt-hr] design storage capacity int size_type, // [] Sizing Method (0) use fixed diameter, (1) use fixed height, (2) use preset inputs double h_tank_in, // [m] tank height input @@ -152,7 +157,7 @@ class C_csp_packedbed_tes : public C_csp_tes double k_eff_solid, // [W/m K] Solid effective thermal conductivity double void_frac, // [] Packed bed void fraction double dens_solid, // [kg/m3] solid specific heat - double cp_solid, // [J/kg K] solid specific heat + double cp_solid, // [kJ/kg K] solid specific heat double T_hot_delta, // [C] Max allowable decrease in hot discharge temp double T_cold_delta, // [C] Max allowable increase in cold discharge temp double T_charge_min // [C] Min allowable charge temperature @@ -224,14 +229,6 @@ class C_csp_packedbed_tes : public C_csp_tes double& q_dot_loss /*MWt*/, double& q_dot_dc_to_htf /*MWt*/, double& q_dot_ch_from_htf /*MWt*/, double& T_hot_ave /*K*/, double& T_cold_ave /*K*/, double& T_hot_final /*K*/, double& T_cold_final /*K*/); - double get_max_storage_htf_temp(); - - double get_min_storage_htf_temp(); - - double get_storage_htf_density(); - - double get_storage_htf_cp(); - std::vector get_T_prev_vec() { return m_T_prev_vec; }; }; From d31362aa90e84e05d1c82b97dc37d391cd3af5f9 Mon Sep 17 00:00:00 2001 From: Taylor Brown Date: Tue, 17 Sep 2024 14:02:24 -0600 Subject: [PATCH 41/85] Fix trough iph bug from merge. --- ssc/cmod_trough_physical_iph.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 05b1eac66..0cdefa9c4 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -1220,7 +1220,7 @@ class cm_trough_physical_iph : public compute_module storage_packedbed = C_csp_packedbed_tes( as_integer("Fluid"), // [-] field fluid identifier as_matrix("field_fl_props"), // [-] field fluid properties - q_dot_pc_des, // [MWt] Design heat rate in and out of tes + q_dot_hs_des, // [MWt] Design heat rate in and out of tes Q_tes, // [MWt-hr] design storage capacity as_integer("is_h_tank_fixed"), // [] Sizing Method (0) use fixed diameter, (1) use fixed height, (2) use preset inputs as_double("h_tank_in"), // [m] Tank height @@ -1309,7 +1309,7 @@ class cm_trough_physical_iph : public compute_module c_trough.m_field_fl_props, //as_integer("store_fluid"), //as_matrix("store_fl_props"), - q_dot_pc_des, + q_dot_hs_des, c_trough.m_solar_mult, Q_tes, as_boolean("is_h_tank_fixed"), From 77fb224e83ab3371a6d42ffc99549e27cd7e3901 Mon Sep 17 00:00:00 2001 From: Steven Janzou Date: Tue, 8 Oct 2024 04:30:24 -0600 Subject: [PATCH 42/85] Add cmod_cashloan_heat and cmod_singleowner_heat --- ssc/CMakeLists.txt | 12 +- ssc/cmod_cashloan_heat.cpp | 1747 +++++++++++++ ssc/cmod_singleowner_heat.cpp | 4481 +++++++++++++++++++++++++++++++++ ssc/sscapi.cpp | 12 +- 4 files changed, 6243 insertions(+), 9 deletions(-) create mode 100644 ssc/cmod_cashloan_heat.cpp create mode 100644 ssc/cmod_singleowner_heat.cpp diff --git a/ssc/CMakeLists.txt b/ssc/CMakeLists.txt index c2652da09..819000390 100644 --- a/ssc/CMakeLists.txt +++ b/ssc/CMakeLists.txt @@ -16,13 +16,14 @@ set(SSC_SRC cmod_battery.h cmod_battery_eqns.cpp cmod_battery_eqns.h - cmod_battery_stateful.cpp - cmod_battery_stateful.h + cmod_battery_stateful.cpp + cmod_battery_stateful.h cmod_battwatts.cpp cmod_battwatts.h cmod_belpe.cpp cmod_biomass.cpp cmod_cashloan.cpp + cmod_cashloan_heat.cpp cmod_cb_construction_financing.cpp cmod_cb_empirical_hce_heat_loss.cpp cmod_cb_mspt_system_costs.cpp @@ -30,7 +31,7 @@ set(SSC_SRC cmod_csp_common_eqns.h cmod_csp_dsg_lf_ui.cpp cmod_csp_subcomponent.cpp - cmod_csp_trough_eqns.cpp + cmod_csp_trough_eqns.cpp cmod_csp_trough_eqns.h cmod_etes_electric_resistance.cpp cmod_etes_ptes.cpp @@ -73,8 +74,8 @@ set(SSC_SRC cmod_mhk_tidal.cpp cmod_mhk_wave.cpp cmod_mspt_sf_and_rec_isolated.cpp - cmod_mspt_iph.cpp - cmod_ptes_design_point.cpp + cmod_mspt_iph.cpp + cmod_ptes_design_point.cpp cmod_pv6parmod.cpp cmod_pv_get_shade_loss_mpp.cpp cmod_pvsamv1.cpp @@ -91,6 +92,7 @@ set(SSC_SRC cmod_sco2_csp_ud_pc_tables.cpp cmod_singlediode.cpp cmod_singleowner.cpp + cmod_singleowner_heat.cpp cmod_communitysolar.cpp cmod_snowmodel.cpp cmod_solarpilot.cpp diff --git a/ssc/cmod_cashloan_heat.cpp b/ssc/cmod_cashloan_heat.cpp new file mode 100644 index 000000000..3f6dbfac0 --- /dev/null +++ b/ssc/cmod_cashloan_heat.cpp @@ -0,0 +1,1747 @@ +/* +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 +#include + +#include "core.h" +#include "lib_financial.h" +#include "lib_util.h" +#include "common_financial.h" +using namespace libfin; +#include + +static var_info vtab_cashloan_heat[] = { +/* VARTYPE DATATYPE NAME LABEL UNITS META GROUP REQUIRED_IF CONSTRAINTS UI_HINTS*/ + + { SSC_INPUT, SSC_NUMBER, "market", "Residential or Commercial Market", "0/1", "0=residential,1=comm.", "Financial Parameters", "?=1", "INTEGER,MIN=0,MAX=1", "" }, + { SSC_INPUT, SSC_NUMBER, "mortgage", "Use mortgage style loan (res. only)","0/1", "0=standard loan,1=mortgage","Financial Parameters", "?=0", "INTEGER,MIN=0,MAX=1", "" }, + + { SSC_INPUT, SSC_ARRAY, "utility_bill_w_sys", "Electricity bill for system", "$", "", "Charges by Month", "*", "", "" }, + { SSC_INPUT, SSC_MATRIX, "charge_w_sys_ec_ym", "Energy charge with system", "$", "", "Charges by Month", "", "", "COL_LABEL=MONTHS,FORMAT_SPEC=CURRENCY,GROUP=UR_AM" }, + { SSC_INPUT, SSC_MATRIX, "true_up_credits_ym", "Net annual true-up payments", "$", "", "Charges by Month", "", "", "COL_LABEL=MONTHS,FORMAT_SPEC=CURRENCY,GROUP=UR_AM" }, + { SSC_INPUT, SSC_MATRIX, "nm_dollars_applied_ym", "Net metering credit", "$", "", "Charges by Month", "*", "", "COL_LABEL=MONTHS,FORMAT_SPEC=CURRENCY,GROUP=UR_AM" }, + { SSC_INPUT, SSC_MATRIX, "net_billing_credits_ym", "Net billing credit", "$", "", "Charges by Month", "*", "", "COL_LABEL=MONTHS,FORMAT_SPEC=CURRENCY,GROUP=UR_AM" }, + + { SSC_INPUT, SSC_ARRAY, "batt_capacity_percent", "Battery relative capacity to nameplate", "%", "", "Battery", "", "", "" }, + { SSC_INPUT, SSC_ARRAY, "monthly_grid_to_batt", "Energy to battery from grid", "kWh", "", "Battery", "", "LENGTH=12", "" }, + { SSC_INPUT, SSC_ARRAY, "monthly_batt_to_grid", "Energy to grid from battery", "kWh", "", "Battery", "", "LENGTH=12", "" }, + { SSC_INPUT, SSC_ARRAY, "monthly_grid_to_load", "Energy to load from grid", "kWh", "", "Battery", "", "LENGTH=12", "" }, + { SSC_INPUT, SSC_MATRIX, "charge_w_sys_dc_tou_ym", "Demand charge with system (TOU)", "$", "", "Charges by Month", "*", "", "COL_LABEL=MONTHS,FORMAT_SPEC=CURRENCY,GROUP=UR_AM" }, + { SSC_INPUT, SSC_ARRAY, "year1_hourly_ec_with_system", "Energy charge with system (year 1 hourly)", "$", "", "Time Series", "*", "", "" }, + { SSC_INPUT, SSC_ARRAY, "year1_hourly_dc_with_system", "Demand charge with system (year 1 hourly)", "$", "", "Time Series", "*", "", "" }, + +// { SSC_OUTPUT, SSC_ARRAY, "gen_purchases", "Electricity from grid", "kW", "", "System Output", "", "", "" }, + { SSC_INPUT, SSC_MATRIX, "charge_w_sys_fixed_ym", "Fixed monthly charge with system", "$", "", "Charges by Month", "*", "", "COL_LABEL=MONTHS,FORMAT_SPEC=CURRENCY,GROUP=UR_AM" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_utility_bill", "Electricity purchase", "$", "", "", "", "LENGTH_EQUAL=cf_length", "" }, + { SSC_INPUT, SSC_ARRAY, "year1_hourly_e_fromgrid", "Electricity from grid (year 1 hourly)", "kWh", "", "Time Series", "*", "", "" }, + + { SSC_INPUT, SSC_NUMBER, "total_installed_cost", "Total installed cost", "$", "", "System Costs", "*", "MIN=0", "" }, + { SSC_INPUT, SSC_NUMBER, "salvage_percentage", "Salvage value percentage", "%", "", "Financial Parameters", "?=0.0", "MIN=0,MAX=100", "" }, + //{ SSC_INPUT, SSC_NUMBER, "batt_salvage_percentage", "Net pre-tax cash battery salvage value", "%", "", "Financial Parameters", "?=0", "MIN=0,MAX=100", "" }, + + { SSC_INPUT, SSC_ARRAY, "annual_energy_value", "Energy value", "$", "", "System Output", "*", "", "" }, + { SSC_INPUT, SSC_ARRAY, "annual_thermal_value", "Energy value", "$", "", "System Output", "", "", "" }, + { SSC_INPUT, SSC_ARRAY, "gen", "Power generated by renewable resource", "kW", "", "System Output", "*", "", "" }, + + { SSC_INPUT, SSC_ARRAY, "degradation", "Annual degradation", "%", "", "System Output", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "system_use_lifetime_output","Lifetime hourly system outputs", "0/1", "0=hourly first year,1=hourly lifetime", "Lifetime", "*", "INTEGER,MIN=0", "" }, + + /* financial outputs */ + { SSC_OUTPUT, SSC_NUMBER, "cf_length", "Number of periods in cash flow", "", "", "Cash Flow", "*", "INTEGER", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "lcoe_real", "LCOE Levelized cost of energy real", "cents/kWh", "", "Cash Flow", "*", "", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "lcoe_nom", "LCOE Levelized cost of energy nominal", "cents/kWh", "", "Cash Flow", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "payback", "Payback period", "years", "", "Cash Flow", "*", "", "" }, + // added 9/26/16 for Owen Zinaman Mexico + { SSC_OUTPUT, SSC_NUMBER, "discounted_payback", "Discounted payback period", "years", "", "Cash Flow", "*", "", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "npv", "NPV Net present value", "$", "", "Cash Flow", "*", "", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "present_value_oandm", "Present value of O&M expenses", "$", "", "Financial Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "present_value_oandm_nonfuel", "Present value of non-fuel O&M expenses", "$", "", "Financial Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "present_value_fuel", "Present value of fuel expenses", "$", "", "Financial Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "present_value_insandproptax", "Present value of insurance and property tax", "$", "", "Financial Metrics", "*", "", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "npv_energy_lcos_nom", "Present value of annual stored energy (nominal)", "kWh", "", "LCOE calculations", "", "", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "adjusted_installed_cost", "Net capital cost", "$", "", "Financial Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "loan_amount", "Debt", "$", "", "Financial Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "first_cost", "Equity", "$", "", "Financial Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "total_cost", "Total installed cost", "$", "", "Financial Metrics", "*", "", "" }, + + + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_net", "Electricity net generation", "kWh", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales", "Electricity generation", "kWh", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_purchases", "Electricity from grid to system", "kWh", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_without_battery","Electricity generated without the battery or curtailment", "kWh", "", "Cash Flow Electricity", "", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_value", "Value of electricity savings", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_thermal_value", "Value of thermal savings", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + + // real estate value added 6/24/13 + { SSC_OUTPUT, SSC_ARRAY, "cf_value_added", "Real estate value added", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_om_fixed_expense", "O&M fixed expense", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_production_expense", "O&M production-based expense", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_capacity_expense", "O&M capacity-based expense", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_fixed1_expense", "O&M battery fixed expense", "$", "", "Cash Flow", "", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_production1_expense", "O&M battery production-based expense", "$", "", "Cash Flow", "", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_capacity1_expense", "O&M battery capacity-based expense", "$", "", "Cash Flow", "", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_fixed2_expense", "O&M fuel cell fixed expense", "$", "", "Cash Flow", "", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_production2_expense", "O&M fuel cell production-based expense", "$", "", "Cash Flow", "", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_capacity2_expense", "O&M fuel cell capacity-based expense", "$", "", "Cash Flow", "", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_fuel_expense", "Fuel expense", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_opt_fuel_1_expense", "Feedstock biomass expense", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_opt_fuel_2_expense", "Feedstock coal expense", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + + + { SSC_OUTPUT, SSC_ARRAY, "cf_property_tax_assessed_value","Property tax net assessed value", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_property_tax_expense", "Property tax expense", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_insurance_expense", "Insurance expense", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_net_salvage_value", "Net salvage value", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_operating_expenses", "Total operating expense", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_deductible_expenses", "Deductible expenses", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_debt_balance", "Debt balance", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_debt_payment_interest", "Interest payment", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_debt_payment_principal","Principal payment", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_debt_payment_total", "Total P&I debt payment", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_sta_depr_sched", "State depreciation schedule", "%", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_sta_depreciation", "State depreciation", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_sta_incentive_income_less_deductions", "State incentive income less deductions", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_sta_taxable_income_less_deductions", "State taxable income less deductions", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_sta_tax_savings", "State tax savings", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_sta_taxable_incentive_income", "State taxable incentive income", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_fed_taxable_incentive_income", "Federal taxable incentive income", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_fed_depr_sched", "Federal depreciation schedule", "%", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_fed_depreciation", "Federal depreciation", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_fed_incentive_income_less_deductions", "Federal incentive income less deductions", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_fed_taxable_income_less_deductions", "Federal taxable income less deductions", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_fed_tax_savings", "Federal tax savings", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_sta_and_fed_tax_savings", "Total tax savings (federal and state)", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_after_tax_net_equity_cost_flow", "After-tax annual costs", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_after_tax_cash_flow", "After-tax cash flow", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_discounted_costs", "Discounted costs", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_discounted_savings", "Discounted savings", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_parasitic_cost", "Parasitic load costs", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + + + { SSC_OUTPUT, SSC_ARRAY, "cf_discounted_payback", "Discounted payback", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_discounted_cumulative_payback", "Cumulative discounted payback", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_payback_with_expenses", "Simple payback with expenses", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_cumulative_payback_with_expenses", "Cumulative simple payback with expenses", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_payback_without_expenses", "Simple payback without expenses", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_cumulative_payback_without_expenses", "Cumulative simple payback without expenses", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "lcoptc_fed_real", "Levelized federal PTC real", "cents/kWh", "", "Financial Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "lcoptc_fed_nom", "Levelized federal PTC nominal", "cents/kWh", "", "Financial Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "lcoptc_sta_real", "Levelized state PTC real", "cents/kWh", "", "Financial Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "lcoptc_sta_nom", "Levelized state PTC nominal", "cents/kWh", "", "Financial Metrics", "*", "", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "wacc", "WACC Weighted average cost of capital", "", "", "Financial Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "effective_tax_rate", "Effective tax rate", "%", "", "Financial Metrics", "*", "", "" }, + +// NTE additions 8/10/17 + { SSC_INPUT, SSC_ARRAY, "elec_cost_with_system", "Energy value", "$", "", "ThirdPartyOwnership", "*", "", "" }, + { SSC_INPUT, SSC_ARRAY, "elec_cost_without_system", "Energy value", "$", "", "ThirdPartyOwnership", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_nte", "NTE Not to exceed", "cents/kWh", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_NUMBER, "year1_nte", "NTE Not to exceed Year 1", "cents/kWh", "", "Cash Flow", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "lnte_real", "NTE Not to exceed real", "cents/kWh", "", "Cash Flow", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "lnte_nom", "NTE Not to exceed nominal", "cents/kWh", "", "Cash Flow", "*", "", "" }, + + +var_info_invalid }; + +extern var_info + vtab_standard_financial[], + vtab_standard_loan[], + vtab_oandm[], + vtab_depreciation[], + vtab_battery_replacement_cost[], + vtab_fuelcell_replacement_cost[], + vtab_tax_credits[], + vtab_payment_incentives[], + vtab_lcos_inputs[], + vtab_update_tech_outputs[], + vtab_utility_rate_common[]; + + +enum { + CF_degradation, + CF_energy_net, + CF_energy_value, + CF_thermal_value, + CF_value_added, + + + CF_om_opt_fuel_2_expense, + CF_om_opt_fuel_1_expense, + + CF_federal_tax_frac, + CF_state_tax_frac, + CF_effective_tax_frac, + + CF_property_tax_assessed_value, + CF_property_tax_expense, + CF_insurance_expense, + CF_net_salvage_value, + CF_operating_expenses, + + CF_deductible_expenses, + + CF_debt_balance, + CF_debt_payment_interest, + CF_debt_payment_principal, + CF_debt_payment_total, + + CF_pbi_fed, + CF_pbi_sta, + CF_pbi_uti, + CF_pbi_oth, + CF_pbi_total, + + CF_ptc_fed, + CF_ptc_sta, + + CF_sta_depr_sched, + CF_sta_depreciation, + CF_sta_incentive_income_less_deductions, + CF_sta_taxable_income_less_deductions, + CF_sta_tax_savings, + + CF_sta_taxable_incentive_income, + CF_fed_taxable_incentive_income, + + CF_fed_depr_sched, + CF_fed_depreciation, + CF_fed_incentive_income_less_deductions, + CF_fed_taxable_income_less_deductions, + CF_fed_tax_savings, + + CF_sta_and_fed_tax_savings, + CF_after_tax_net_equity_cost_flow, + CF_after_tax_cash_flow, + + CF_payback_with_expenses, + CF_cumulative_payback_with_expenses, + + // Added for Owen Zinaman for Mexico Rates and analyses 9/26/16 + //- see C:\Projects\SAM\Documentation\Payback\DiscountedPayback_2016.9.26 + CF_discounted_costs, + CF_discounted_savings, + CF_discounted_payback, + CF_discounted_cumulative_payback, + + + CF_payback_without_expenses, + CF_cumulative_payback_without_expenses, + + CF_battery_replacement_cost_schedule, + CF_battery_replacement_cost, + + CF_fuelcell_replacement_cost_schedule, + CF_fuelcell_replacement_cost, + + CF_energy_sales, + CF_energy_purchases, + + CF_energy_without_battery, + + CF_nte, + + CF_om_fixed_expense, + CF_om_production_expense, + CF_om_capacity_expense, + CF_om_fixed1_expense, + CF_om_production1_expense, + CF_om_capacity1_expense, + CF_om_fixed2_expense, + CF_om_production2_expense, + CF_om_capacity2_expense, + CF_om_fuel_expense, + CF_energy_charged_grid, + CF_energy_charged_pv, + CF_energy_discharged, + CF_charging_cost_pv, + CF_charging_cost_grid, + CF_om_cost_lcos, + CF_salvage_cost_lcos, + CF_investment_cost_lcos, + CF_annual_cost_lcos, + CF_util_escal_rate, + + CF_utility_bill, + CF_parasitic_cost, + + // SAM 1038 + CF_itc_fed_amount, + CF_itc_fed_percent_amount, + CF_itc_fed_percent_maxvalue, + CF_itc_fed, + CF_itc_sta_amount, + CF_itc_sta_percent_amount, + CF_itc_sta_percent_maxvalue, + CF_itc_sta, + CF_itc_total, + + CF_max, +}; + + + + +class cm_cashloan_heat : public compute_module +{ +private: + util::matrix_t cf; + util::matrix_t cf_lcos; + double ibi_fed_amount; + double ibi_sta_amount; + double ibi_uti_amount; + double ibi_oth_amount; + double ibi_fed_per; + double ibi_sta_per; + double ibi_uti_per; + double ibi_oth_per; + double cbi_fed_amount; + double cbi_sta_amount; + double cbi_uti_amount; + double cbi_oth_amount; + + hourly_energy_calculation hourly_energy_calcs; + + +public: + cm_cashloan_heat() + { + add_var_info( vtab_standard_financial ); + add_var_info( vtab_standard_loan ); + add_var_info( vtab_oandm ); + add_var_info( vtab_depreciation ); + add_var_info( vtab_tax_credits ); + add_var_info( vtab_payment_incentives ); + add_var_info(vtab_battery_replacement_cost); + add_var_info(vtab_fuelcell_replacement_cost); + add_var_info(vtab_cashloan_heat); + add_var_info(vtab_lcos_inputs); + add_var_info(vtab_update_tech_outputs); + } + + void exec( ) + { + int i; + + bool is_commercial = (as_integer("market")==1); + bool is_mortgage = (as_integer("mortgage")==1); + +// throw exec_error("cmod_cashloan_heat", "mortgage = " + util::to_string(as_integer("mortgage"))); +// if (is_commercial) log("commercial market"); else log("residential market"); +// if (is_mortgage) log("mortgage loan"); else log("standard loan"); + + + int nyears = as_integer("analysis_period"); + + // initialize cashflow matrix + cf.resize_fill( CF_max, nyears+1, 0.0 ); + cf_lcos.resize_fill(CF_max, nyears + 1, 0.0); + + // initialize energy and revenue + size_t count = 0; + ssc_number_t *arrp = 0; + + + double first_year_energy = 0.0; + double first_year_sales = 0.0; + double first_year_purchases = 0.0; + + + // degradation + // degradation starts in year 2 for single value degradation - no degradation in year 1 - degradation =1.0 + // lifetime degradation applied in technology compute modules + if (as_integer("system_use_lifetime_output") == 1) + { + for (i = 1; i <= nyears; i++) cf.at(CF_degradation, i) = 1.0; + } + else + { + size_t count_degrad = 0; + ssc_number_t* degrad = 0; + degrad = as_array("degradation", &count_degrad); + + if (count_degrad == 1) + { + for (i = 1; i <= nyears; i++) cf.at(CF_degradation, i) = pow((1.0 - degrad[0] / 100.0), i - 1); + } + else if (count_degrad > 0) + { + for (i = 0; i < nyears && i < (int)count_degrad; i++) cf.at(CF_degradation, i + 1) = (1.0 - degrad[i] / 100.0); + } + } + + + + hourly_energy_calcs.calculate(this); + + // dispatch + if (as_integer("system_use_lifetime_output") == 1) + { + // hourly_enet includes all curtailment, availability + for (size_t y = 1; y <= (size_t)nyears; y++) + { + for (size_t h = 0; h < 8760; h++) + { + cf.at(CF_energy_net, y) += hourly_energy_calcs.hourly_energy()[(y - 1) * 8760 + h] * cf.at(CF_degradation, y); + cf.at(CF_energy_sales, y) += hourly_energy_calcs.hourly_sales()[(y - 1) * 8760 + h] * cf.at(CF_degradation, y); + cf.at(CF_energy_purchases, y) += hourly_energy_calcs.hourly_purchases()[(y - 1) * 8760 + h] * cf.at(CF_degradation, y); + } + } + } + else + { + for (i = 0; i < 8760; i++) { + first_year_energy += hourly_energy_calcs.hourly_energy()[i]; // sum up hourly kWh to get total annual kWh first year production includes first year curtailment, availability + first_year_sales += hourly_energy_calcs.hourly_sales()[i]; + first_year_purchases += hourly_energy_calcs.hourly_purchases()[i]; + } + cf.at(CF_energy_net, 1) = first_year_energy; + cf.at(CF_energy_sales, 1) = first_year_sales; + cf.at(CF_energy_purchases, 1) = first_year_purchases; + for (i = 1; i <= nyears; i++) { + cf.at(CF_energy_net, i) = first_year_energy * cf.at(CF_degradation, i); + cf.at(CF_energy_sales, i) = first_year_sales * cf.at(CF_degradation, i); + cf.at(CF_energy_purchases, i) = first_year_purchases * cf.at(CF_degradation, i); + } + + } + + if (is_assigned("gen_without_battery") && as_vector_double("gen_without_battery").size() > 0) + { + ssc_number_t first_year_energy_without_battery = 0.0; + if (as_integer("system_use_lifetime_output") == 1) + { + // hourly_enet includes all curtailment, availability + for (size_t y = 1; y <= (size_t)nyears; y++) + { + for (size_t h = 0; h < 8760; h++) + { + cf.at(CF_energy_without_battery, y) += hourly_energy_calcs.hourly_energy_without_battery()[(y - 1) * 8760 + h] * cf.at(CF_degradation, y); + } + } + } + else + { + for (i = 0; i < 8760; i++) { + first_year_energy_without_battery += hourly_energy_calcs.hourly_energy_without_battery()[i]; + } + cf.at(CF_energy_without_battery, 1) = first_year_energy_without_battery; + for (i = 1; i <= nyears; i++) { + cf.at(CF_energy_without_battery, i) = first_year_energy_without_battery * cf.at(CF_degradation, i); + } + + } + } + + if (is_assigned("annual_thermal_value")) + { + arrp = as_array("annual_thermal_value", &count); + i = 0; + while (i < nyears && i < (int)count) + { + cf.at(CF_thermal_value, i + 1) = (double)arrp[i +1]; + i++; + } + } + + + arrp = as_array("annual_energy_value", &count); + i=0; + while ( i < nyears && i < (int)count ) + { + cf.at(CF_energy_value, i+1) = (double) arrp[i+1]; + i++; + } + + double year1_fuel_use = as_double("annual_fuel_usage"); // kWht + std::vector fuel_use; + if ((as_integer("system_use_lifetime_output") == 1) && is_assigned("annual_fuel_usage_lifetime")) { + fuel_use = as_vector_double("annual_fuel_usage_lifetime"); + if (fuel_use.size() != (size_t)(nyears)) { + throw exec_error("cashloan_heat", util::format("fuel usage years (%d) not equal to analysis period years (%d).", (int)fuel_use.size(), nyears)); + } + } + else { + for (size_t y = 0; y < (size_t)(nyears); y++) { + fuel_use.push_back(year1_fuel_use); + } + } + double nameplate = as_double("system_capacity"); // kW + + double inflation_rate = as_double("inflation_rate")*0.01; + double property_tax = as_double("property_tax_rate")*0.01; + double property_tax_decline_percentage = as_double("prop_tax_assessed_decline"); + double insurance_rate = as_double("insurance_rate")*0.01; + double salvage_frac = as_double("salvage_percentage")*0.01; + + //double federal_tax_rate = as_double("federal_tax_rate")*0.01; + //double state_tax_rate = as_double("state_tax_rate")*0.01; + //double effective_tax_rate = state_tax_rate + (1.0-state_tax_rate)*federal_tax_rate; + arrp = as_array("federal_tax_rate", &count); + if (count > 0) + { + if (count == 1) // single value input + { + for (i = 0; i < nyears; i++) + cf.at(CF_federal_tax_frac, i + 1) = arrp[0]*0.01; + } + else // schedule + { + for (i = 0; i < nyears && i < (int)count; i++) + cf.at(CF_federal_tax_frac, i + 1) = arrp[i] * 0.01; + } + } + arrp = as_array("state_tax_rate", &count); + if (count > 0) + { + if (count == 1) // single value input + { + for (i = 0; i < nyears; i++) + cf.at(CF_state_tax_frac, i + 1) = arrp[0] * 0.01; + } + else // schedule + { + for (i = 0; i < nyears && i < (int)count; i++) + cf.at(CF_state_tax_frac, i + 1) = arrp[i] * 0.01; + } + } + for (i = 0; i <= nyears;i++) + cf.at(CF_effective_tax_frac, i) = cf.at(CF_state_tax_frac, i) + + (1.0 - cf.at(CF_state_tax_frac, i))*cf.at(CF_federal_tax_frac, i); + + + + + double real_discount_rate = as_double("real_discount_rate")*0.01; + double nom_discount_rate = (1.0 + real_discount_rate) * (1.0 + inflation_rate) - 1.0; + + +// double hard_cost = as_double("total_hard_cost"); +// double total_sales_tax = as_double("percent_of_cost_subject_sales_tax")*0.01*hard_cost*as_double("sales_tax_rate")*0.01; +// double soft_cost = as_double("total_soft_cost") + total_sales_tax; +// double total_cost = hard_cost + soft_cost; + double total_cost = as_double("total_installed_cost"); + double property_tax_assessed_value = total_cost * as_double("prop_tax_cost_assessed_percent") * 0.01; + + int loan_term = as_integer("loan_term"); + double loan_rate = as_double("loan_rate")*0.01; + double debt_frac = as_double("debt_fraction")*0.01; + + // precompute expenses from annual schedules or value+escalation + escal_or_annual( CF_om_fixed_expense, nyears, "om_fixed", inflation_rate, 1.0, false, as_double("om_fixed_escal")*0.01 ); + escal_or_annual( CF_om_production_expense, nyears, "om_production", inflation_rate, 0.001, false, as_double("om_production_escal")*0.01 ); + escal_or_annual( CF_om_capacity_expense, nyears, "om_capacity", inflation_rate, 1.0, false, as_double("om_capacity_escal")*0.01 ); + escal_or_annual( CF_om_fuel_expense, nyears, "om_fuel_cost", inflation_rate, as_double("system_heat_rate")*0.001, false, as_double("om_fuel_cost_escal")*0.01 ); + + // additional o and m sub types (e.g. batteries and fuel cells) + int add_om_num_types = as_integer("add_om_num_types"); + ssc_number_t nameplate1 = 0; + ssc_number_t nameplate2 = 0; + std::vector battery_discharged(nyears,0); + std::vector fuelcell_discharged(nyears,0); + + if (add_om_num_types > 0) //PV Battery + { + escal_or_annual(CF_om_fixed1_expense, nyears, "om_batt_fixed_cost", inflation_rate, 1.0, false, as_double("om_fixed_escal") * 0.01); + escal_or_annual(CF_om_production1_expense, nyears, "om_batt_variable_cost", inflation_rate, 0.001, false, as_double("om_production_escal") * 0.01); //$/MWh + escal_or_annual(CF_om_capacity1_expense, nyears, "om_batt_capacity_cost", inflation_rate, 1.0, false, as_double("om_capacity_escal") * 0.01); + nameplate1 = as_number("om_batt_nameplate"); + if (as_integer("en_batt") == 1 || as_integer("en_standalone_batt") == 1) + battery_discharged = as_vector_double("batt_annual_discharge_energy"); + } + if (battery_discharged.size() == 1) { // ssc #992 + double first_val = battery_discharged[0]; + battery_discharged.resize(nyears, first_val); + } + if (battery_discharged.size() != nyears) + throw exec_error("cashloan_heat", util::format("battery_discharged size (%d) incorrect",(int)battery_discharged.size())); + + if (add_om_num_types > 1) + { + escal_or_annual(CF_om_fixed2_expense, nyears, "om_fuelcell_fixed_cost", inflation_rate, 1.0, false, as_double("om_fixed_escal")*0.01); + escal_or_annual(CF_om_production2_expense, nyears, "om_fuelcell_variable_cost", inflation_rate, 0.001, false, as_double("om_production_escal")*0.01); + escal_or_annual(CF_om_capacity2_expense, nyears, "om_fuelcell_capacity_cost", inflation_rate, 1.0, false, as_double("om_capacity_escal")*0.01); + nameplate2 = as_number("om_fuelcell_nameplate"); + fuelcell_discharged = as_vector_double("fuelcell_annual_energy_discharged"); + } + if (fuelcell_discharged.size()== 1) { // ssc #992 + double first_val = fuelcell_discharged[0]; + fuelcell_discharged.resize(nyears, first_val); + } + if (fuelcell_discharged.size() != nyears) + throw exec_error("cashloan_heat", util::format("fuelcell_discharged size (%d) incorrect",(int)fuelcell_discharged.size())); + + + // battery cost - replacement from lifetime analysis + if ((as_integer("en_batt") == 1 || as_integer("en_standalone_batt") == 1) && (as_integer("batt_replacement_option") > 0)) + { + ssc_number_t* batt_rep = 0; + std::vector replacement_percent; + + batt_rep = as_array("batt_bank_replacement", &count); // replacements per year calculated + + // replace at capacity percent + if (as_integer("batt_replacement_option") == 1) { + + for (i = 0; i < (int)count; i++) { + replacement_percent.push_back(100); + } + } + else {// user specified + replacement_percent = as_vector_ssc_number_t("batt_replacement_schedule_percent"); + } + double batt_cap = as_double("batt_computed_bank_capacity"); + // updated 10/17/15 per 10/14/15 meeting + escal_or_annual(CF_battery_replacement_cost_schedule, nyears, "om_batt_replacement_cost", inflation_rate, batt_cap, false, as_double("om_replacement_cost_escal") * 0.01); + + for (i = 0; i < nyears && i < (int)count; i++) { + // batt_rep and the cash flow sheets are 1 indexed, replacement_percent is zero indexed + cf.at(CF_battery_replacement_cost, i + 1) = batt_rep[i] * replacement_percent[i] * 0.01 * + cf.at(CF_battery_replacement_cost_schedule, i + 1); + } + } + + // fuelcell cost - replacement from lifetime analysis + if (is_assigned("fuelcell_replacement_option") && (as_integer("fuelcell_replacement_option") > 0)) + { + ssc_number_t *fuelcell_rep = 0; + if (as_integer("fuelcell_replacement_option") == 1) + fuelcell_rep = as_array("fuelcell_replacement", &count); // replacements per year calculated + else // user specified + fuelcell_rep = as_array("fuelcell_replacement_schedule", &count); // replacements per year user-defined + escal_or_annual(CF_fuelcell_replacement_cost_schedule, nyears, "om_fuelcell_replacement_cost", inflation_rate, nameplate2, false, as_double("om_replacement_cost_escal")*0.01); + + for ( i = 0; i < nyears && i < (int)count; i++) { + cf.at(CF_fuelcell_replacement_cost, i + 1) = fuelcell_rep[i] * + cf.at(CF_fuelcell_replacement_cost_schedule, i + 1); + } + } + + + escal_or_annual( CF_om_opt_fuel_1_expense, nyears, "om_opt_fuel_1_cost", inflation_rate, 1.0, false, as_double("om_opt_fuel_1_cost_escal")*0.01 ); + escal_or_annual( CF_om_opt_fuel_2_expense, nyears, "om_opt_fuel_2_cost", inflation_rate, 1.0, false, as_double("om_opt_fuel_2_cost_escal")*0.01 ); + + double om_opt_fuel_1_usage = as_double("om_opt_fuel_1_usage"); + double om_opt_fuel_2_usage = as_double("om_opt_fuel_2_usage"); + + // ibi fixed + ibi_fed_amount = as_double("ibi_fed_amount"); + ibi_sta_amount = as_double("ibi_sta_amount"); + ibi_uti_amount = as_double("ibi_uti_amount"); + ibi_oth_amount = as_double("ibi_oth_amount"); + + // ibi percent + ibi_fed_per = as_double("ibi_fed_percent")*0.01*total_cost; + if (ibi_fed_per > as_double("ibi_fed_percent_maxvalue")) ibi_fed_per = as_double("ibi_fed_percent_maxvalue"); + ibi_sta_per = as_double("ibi_sta_percent")*0.01*total_cost; + if (ibi_sta_per > as_double("ibi_sta_percent_maxvalue")) ibi_sta_per = as_double("ibi_sta_percent_maxvalue"); + ibi_uti_per = as_double("ibi_uti_percent")*0.01*total_cost; + if (ibi_uti_per > as_double("ibi_uti_percent_maxvalue")) ibi_uti_per = as_double("ibi_uti_percent_maxvalue"); + ibi_oth_per = as_double("ibi_oth_percent")*0.01*total_cost; + if (ibi_oth_per > as_double("ibi_oth_percent_maxvalue")) ibi_oth_per = as_double("ibi_oth_percent_maxvalue"); + + // cbi + cbi_fed_amount = 1000.0*nameplate*as_double("cbi_fed_amount"); + if (cbi_fed_amount > as_double("cbi_fed_maxvalue")) cbi_fed_amount = as_double("cbi_fed_maxvalue"); + cbi_sta_amount = 1000.0*nameplate*as_double("cbi_sta_amount"); + if (cbi_sta_amount > as_double("cbi_sta_maxvalue")) cbi_sta_amount = as_double("cbi_sta_maxvalue"); + cbi_uti_amount = 1000.0*nameplate*as_double("cbi_uti_amount"); + if (cbi_uti_amount > as_double("cbi_uti_maxvalue")) cbi_uti_amount = as_double("cbi_uti_maxvalue"); + cbi_oth_amount = 1000.0*nameplate*as_double("cbi_oth_amount"); + if (cbi_oth_amount > as_double("cbi_oth_maxvalue")) cbi_oth_amount = as_double("cbi_oth_maxvalue"); + + // precompute pbi + compute_production_incentive( CF_pbi_fed, nyears, "pbi_fed_amount", "pbi_fed_term", "pbi_fed_escal" ); + compute_production_incentive( CF_pbi_sta, nyears, "pbi_sta_amount", "pbi_sta_term", "pbi_sta_escal" ); + compute_production_incentive( CF_pbi_uti, nyears, "pbi_uti_amount", "pbi_uti_term", "pbi_uti_escal" ); + compute_production_incentive( CF_pbi_oth, nyears, "pbi_oth_amount", "pbi_oth_term", "pbi_oth_escal" ); + + // precompute ptc + compute_production_incentive_IRS_2010_37( CF_ptc_sta, nyears, "ptc_sta_amount", "ptc_sta_term", "ptc_sta_escal" ); + compute_production_incentive_IRS_2010_37( CF_ptc_fed, nyears, "ptc_fed_amount", "ptc_fed_term", "ptc_fed_escal" ); + + // reduce itc bases + double federal_itc_basis = total_cost + - ( as_boolean("ibi_fed_amount_deprbas_fed") ? ibi_fed_amount : 0 ) + - ( as_boolean("ibi_sta_amount_deprbas_fed") ? ibi_sta_amount : 0 ) + - ( as_boolean("ibi_uti_amount_deprbas_fed") ? ibi_uti_amount : 0 ) + - ( as_boolean("ibi_oth_amount_deprbas_fed") ? ibi_oth_amount : 0 ) + - ( as_boolean("ibi_fed_percent_deprbas_fed") ? ibi_fed_per : 0 ) + - ( as_boolean("ibi_sta_percent_deprbas_fed") ? ibi_sta_per : 0 ) + - ( as_boolean("ibi_uti_percent_deprbas_fed") ? ibi_uti_per : 0 ) + - ( as_boolean("ibi_oth_percent_deprbas_fed") ? ibi_oth_per : 0 ) + - ( as_boolean("cbi_fed_deprbas_fed") ? cbi_fed_amount : 0 ) + - ( as_boolean("cbi_sta_deprbas_fed") ? cbi_sta_amount : 0 ) + - ( as_boolean("cbi_uti_deprbas_fed") ? cbi_uti_amount : 0 ) + - ( as_boolean("cbi_oth_deprbas_fed") ? cbi_oth_amount : 0 ); + + + // SAM 1038 + // itc fixed + double itc_fed_amount = 0.0; + double_vec vitc_fed_amount = as_vector_double("itc_fed_amount"); + for (size_t k = 0; k < vitc_fed_amount.size() && k < nyears; k++) { + cf.at(CF_itc_fed_amount, k + 1) = vitc_fed_amount[k]; + itc_fed_amount += vitc_fed_amount[k]; + } + + double itc_sta_amount = 0.0; + double_vec vitc_sta_amount = as_vector_double("itc_sta_amount"); + for (size_t k = 0; k < vitc_sta_amount.size() && k < nyears; k++) { + cf.at(CF_itc_sta_amount, k + 1) = vitc_sta_amount[k]; + itc_sta_amount += vitc_sta_amount[k]; + } + + // itc percent - max value used for comparison to qualifying costs + double_vec vitc_fed_frac = as_vector_double("itc_fed_percent"); + for (size_t k = 0; k < vitc_fed_frac.size(); k++) + cf.at(CF_itc_fed_percent_amount, k + 1) = vitc_fed_frac[k] * 0.01; + double itc_fed_per; + double_vec vitc_sta_frac = as_vector_double("itc_sta_percent"); + for (size_t k = 0; k < vitc_sta_frac.size(); k++) + cf.at(CF_itc_sta_percent_amount, k + 1) = vitc_sta_frac[k] * 0.01; + double itc_sta_per; + + double_vec itc_sta_percent_maxvalue = as_vector_double("itc_sta_percent_maxvalue"); + if (itc_sta_percent_maxvalue.size() == 1) { + for (size_t k = 0; k < nyears; k++) + cf.at(CF_itc_sta_percent_maxvalue, k + 1) = itc_sta_percent_maxvalue[0]; + } + else { + for (size_t k = 0; k < itc_sta_percent_maxvalue.size() && k < nyears; k++) + cf.at(CF_itc_sta_percent_maxvalue, k + 1) = itc_sta_percent_maxvalue[k]; + } + + double_vec itc_fed_percent_maxvalue = as_vector_double("itc_fed_percent_maxvalue"); + if (itc_fed_percent_maxvalue.size() == 1) { + for (size_t k = 0; k < nyears; k++) + cf.at(CF_itc_fed_percent_maxvalue, k + 1) = itc_fed_percent_maxvalue[0]; + } + else { + for (size_t k = 0; k < itc_fed_percent_maxvalue.size() && k < nyears; k++) + cf.at(CF_itc_fed_percent_maxvalue, k + 1) = itc_fed_percent_maxvalue[k]; + } + + + + double state_itc_basis = total_cost + - ( as_boolean("ibi_fed_amount_deprbas_sta") ? ibi_fed_amount : 0 ) + - ( as_boolean("ibi_sta_amount_deprbas_sta") ? ibi_sta_amount : 0 ) + - ( as_boolean("ibi_uti_amount_deprbas_sta") ? ibi_uti_amount : 0 ) + - ( as_boolean("ibi_oth_amount_deprbas_sta") ? ibi_oth_amount : 0 ) + - ( as_boolean("ibi_fed_percent_deprbas_sta") ? ibi_fed_per : 0 ) + - ( as_boolean("ibi_sta_percent_deprbas_sta") ? ibi_sta_per : 0 ) + - ( as_boolean("ibi_uti_percent_deprbas_sta") ? ibi_uti_per : 0 ) + - ( as_boolean("ibi_oth_percent_deprbas_sta") ? ibi_oth_per : 0 ) + - ( as_boolean("cbi_fed_deprbas_sta") ? cbi_fed_amount : 0 ) + - ( as_boolean("cbi_sta_deprbas_sta") ? cbi_sta_amount : 0 ) + - ( as_boolean("cbi_uti_deprbas_sta") ? cbi_uti_amount : 0 ) + - ( as_boolean("cbi_oth_deprbas_sta") ? cbi_oth_amount : 0 ); + + + // SAM 1038 + itc_sta_per = 0.0; + for (size_t k = 0; k <= nyears; k++) { + cf.at(CF_itc_sta_percent_amount, k) = min(cf.at(CF_itc_sta_percent_maxvalue, k), cf.at(CF_itc_sta_percent_amount, k) * state_itc_basis); + itc_sta_per += cf.at(CF_itc_sta_percent_amount, k); + } + + // SAM 1038 + itc_fed_per = 0.0; + for (size_t k = 0; k <= nyears; k++) { + cf.at(CF_itc_fed_percent_amount, k) = min(cf.at(CF_itc_fed_percent_maxvalue, k), cf.at(CF_itc_fed_percent_amount, k) * federal_itc_basis); + itc_fed_per += cf.at(CF_itc_fed_percent_amount, k); + } + + double federal_depr_basis = federal_itc_basis + - ( as_boolean("itc_fed_amount_deprbas_fed") ? 0.5*itc_fed_amount : 0) + - ( as_boolean("itc_fed_percent_deprbas_fed") ? 0.5*itc_fed_per : 0 ) + - ( as_boolean("itc_sta_amount_deprbas_fed") ? 0.5*itc_sta_amount : 0) + - ( as_boolean("itc_sta_percent_deprbas_fed") ? 0.5*itc_sta_per : 0 ); + + double state_depr_basis = state_itc_basis + - ( as_boolean("itc_fed_amount_deprbas_sta") ? 0.5*itc_fed_amount : 0) + - ( as_boolean("itc_fed_percent_deprbas_sta") ? 0.5*itc_fed_per : 0 ) + - ( as_boolean("itc_sta_amount_deprbas_sta") ? 0.5*itc_sta_amount : 0) + - ( as_boolean("itc_sta_percent_deprbas_sta") ? 0.5*itc_sta_per : 0 ); + + if (is_commercial) + { + // only compute depreciation for commercial market + + switch( as_integer("depr_sta_type") ) + { + case 1: depreciation_sched_macrs_half_year( CF_sta_depr_sched, nyears ); break; + case 2: depreciation_sched_straight_line( CF_sta_depr_sched, nyears, as_integer("depr_sta_sl_years") ); break; + case 3: + { + size_t arr_len; + ssc_number_t *arr_cust = as_array( "depr_sta_custom", &arr_len ); + depreciation_sched_custom( CF_sta_depr_sched, nyears, arr_cust, (int)arr_len ); + break; + } + } + + switch( as_integer("depr_fed_type") ) + { + case 1: depreciation_sched_macrs_half_year( CF_fed_depr_sched, nyears ); break; + case 2: depreciation_sched_straight_line( CF_fed_depr_sched, nyears, as_integer("depr_fed_sl_years") ); break; + case 3: + { + size_t arr_len; + ssc_number_t *arr_cust = as_array( "depr_fed_custom", &arr_len ); + depreciation_sched_custom( CF_fed_depr_sched, nyears, arr_cust, (int)arr_len ); + break; + } + } + } + + double state_tax_savings = 0.0; + double federal_tax_savings = 0.0; + + double adjusted_installed_cost = total_cost + - ibi_fed_amount + - ibi_sta_amount + - ibi_uti_amount + - ibi_oth_amount + - ibi_fed_per + - ibi_sta_per + - ibi_uti_per + - ibi_oth_per + - cbi_fed_amount + - cbi_sta_amount + - cbi_uti_amount + - cbi_oth_amount; + + double loan_amount = debt_frac * adjusted_installed_cost; + double first_cost = adjusted_installed_cost - loan_amount; //cpg: What is the difference between adjusted_installed_cost and capital_investment? + double capital_investment = loan_amount + first_cost; + + cf.at(CF_after_tax_net_equity_cost_flow,0) = -first_cost + state_tax_savings + federal_tax_savings; + cf.at(CF_after_tax_cash_flow,0) = cf.at(CF_after_tax_net_equity_cost_flow,0); + + cf.at(CF_payback_with_expenses, 0) = -capital_investment; + cf.at(CF_cumulative_payback_with_expenses, 0) = -capital_investment; + + cf.at(CF_discounted_costs, 0) = capital_investment; + cf.at(CF_discounted_payback, 0) = cf.at(CF_discounted_savings, 0) - cf.at(CF_discounted_costs, 0); + cf.at(CF_discounted_cumulative_payback, 0) = cf.at(CF_discounted_payback, 0); + + cf.at(CF_payback_without_expenses,0) = -capital_investment; + cf.at(CF_cumulative_payback_without_expenses,0) = -capital_investment; + + double ibi_total = ibi_fed_amount + ibi_fed_per + ibi_sta_amount + ibi_sta_per + ibi_uti_amount + ibi_uti_per + ibi_oth_amount + ibi_oth_per; + double cbi_total = cbi_fed_amount + cbi_sta_amount + cbi_uti_amount + cbi_oth_amount; +// double itc_total_fed = itc_fed_amount + itc_fed_per; +// double itc_total_sta = itc_sta_amount + itc_sta_per; + + // SAM 1038 + for (size_t k = 0; k <= nyears; k++) { + cf.at(CF_itc_fed, k) = cf.at(CF_itc_fed_amount, k) + cf.at(CF_itc_fed_percent_amount, k); + cf.at(CF_itc_sta, k) = cf.at(CF_itc_sta_amount, k) + cf.at(CF_itc_sta_percent_amount, k); + cf.at(CF_itc_total, k) = cf.at(CF_itc_fed, k) + cf.at(CF_itc_sta, k); + } + + for (i=1; i<=nyears; i++) + { + // compute expenses + if (is_assigned("gen_without_battery")) + { + // TODO: this does not include curtailment, but CF_energy_net does. Which should be used for VOM? + cf.at(CF_om_production_expense, i) *= cf.at(CF_energy_without_battery, i); + } + else + { + cf.at(CF_om_production_expense, i) *= cf.at(CF_energy_sales, i); + } + cf.at(CF_om_capacity_expense,i) *= nameplate; + + cf.at(CF_om_capacity1_expense, i) *= nameplate1; + cf.at(CF_om_capacity2_expense, i) *= nameplate2; + // TODO additional production o and m here + + + cf.at(CF_om_fuel_expense,i) *= fuel_use[i-1]; + + //Battery Production OM Costs + cf.at(CF_om_production1_expense, i) *= battery_discharged[i - 1]; //$/MWh * 0.001 MWh/kWh * kWh = $ + cf.at(CF_om_production2_expense, i) *= fuelcell_discharged[i-1]; + + cf.at(CF_om_opt_fuel_1_expense,i) *= om_opt_fuel_1_usage; + cf.at(CF_om_opt_fuel_2_expense,i) *= om_opt_fuel_2_usage; + double decline_percent = 100 - (i-1)*property_tax_decline_percentage; + cf.at(CF_property_tax_assessed_value,i) = (decline_percent > 0) ? property_tax_assessed_value * decline_percent * 0.01:0.0; + cf.at(CF_property_tax_expense,i) = cf.at(CF_property_tax_assessed_value,i) * property_tax; + + cf.at(CF_insurance_expense,i) = total_cost * insurance_rate * pow( 1 + inflation_rate, i-1 ); + + cf.at(CF_net_salvage_value, i) = 0.0; + if (i == nyears) /* salvage value handled as negative operating expense in last year */ + cf.at(CF_net_salvage_value,i) = total_cost * salvage_frac; + + cf.at(CF_operating_expenses,i) = + +cf.at(CF_om_fixed_expense, i) + + cf.at(CF_om_production_expense, i) + + cf.at(CF_om_capacity_expense, i) + + cf.at(CF_om_fixed1_expense, i) + + cf.at(CF_om_production1_expense, i) + + cf.at(CF_om_capacity1_expense, i) + + cf.at(CF_om_fixed2_expense, i) + + cf.at(CF_om_production2_expense, i) + + cf.at(CF_om_capacity2_expense, i) + + cf.at(CF_om_fuel_expense,i) + + cf.at(CF_om_opt_fuel_1_expense,i) + + cf.at(CF_om_opt_fuel_2_expense,i) + + cf.at(CF_property_tax_expense,i) + + cf.at(CF_insurance_expense,i) + + cf.at(CF_battery_replacement_cost, i) + + cf.at(CF_fuelcell_replacement_cost, i) + - cf.at(CF_net_salvage_value,i); + + + if (is_commercial) + cf.at(CF_deductible_expenses,i) = -cf.at(CF_operating_expenses,i); // commercial + else + cf.at(CF_deductible_expenses,i) = -cf.at(CF_property_tax_expense,i); // residential + + if (i == 1) + { + cf.at(CF_debt_balance, i-1) = loan_amount; + cf.at(CF_debt_payment_interest, i) = loan_amount * loan_rate; + cf.at(CF_debt_payment_principal,i) = -ppmt( loan_rate, // Rate + i, // Period + loan_term, // Number periods + loan_amount, // Present Value + 0, // future Value + 0 ); // cash flow at end of period + cf.at(CF_debt_balance, i) = cf.at(CF_debt_balance, i - 1) - cf.at(CF_debt_payment_principal, i); + } + else + { + if (i <= loan_term) + { + cf.at(CF_debt_payment_interest, i) = loan_rate * cf.at(CF_debt_balance, i-1); + + if (loan_rate != 0.0) + { + cf.at(CF_debt_payment_principal,i) = loan_rate * loan_amount/(1 - pow((1 + loan_rate),-loan_term)) + - cf.at(CF_debt_payment_interest,i); + } + else + { + cf.at(CF_debt_payment_principal,i) = loan_amount / loan_term - cf.at(CF_debt_payment_interest,i); + } + cf.at(CF_debt_balance, i) = cf.at(CF_debt_balance, i - 1) - cf.at(CF_debt_payment_principal, i); + } + } + + cf.at(CF_debt_payment_total,i) = cf.at(CF_debt_payment_principal,i) + cf.at(CF_debt_payment_interest,i); + + // compute pbi total + cf.at(CF_pbi_total, i) = cf.at(CF_pbi_fed, i) + cf.at(CF_pbi_sta, i) + cf.at(CF_pbi_uti, i) + cf.at(CF_pbi_oth, i); + + // compute depreciation from basis and precalculated schedule + cf.at(CF_sta_depreciation,i) = cf.at(CF_sta_depr_sched,i)*state_depr_basis; + cf.at(CF_fed_depreciation,i) = cf.at(CF_fed_depr_sched,i)*federal_depr_basis; + + + // ************************************************ + // tax effect on equity (state) + + cf.at(CF_sta_incentive_income_less_deductions, i) = + + cf.at(CF_deductible_expenses, i) + + cf.at(CF_pbi_total,i) + - cf.at(CF_sta_depreciation,i); + + if (i==1) cf.at(CF_sta_incentive_income_less_deductions, i) += ibi_total + cbi_total; + +// sales tax is in depreciable bases and is already written off according to depreciation schedule. +// if (is_commercial && i == 1) cf.at(CF_sta_incentive_income_less_deductions, i) -= total_sales_tax; + + + if (is_commercial || is_mortgage) // interest only deductible if residential mortgage or commercial + cf.at(CF_sta_incentive_income_less_deductions, i) -= cf.at(CF_debt_payment_interest,i); + + cf.at(CF_sta_taxable_income_less_deductions, i) = taxable_incentive_income( i, "sta" ) + + cf.at(CF_deductible_expenses,i) + - cf.at(CF_sta_depreciation,i); + + cf.at(CF_sta_taxable_incentive_income, i) = taxable_incentive_income(i, "sta"); + + // sales tax is incf_fed_taxable_incentive_income" depreciable bases and is already written off according to depreciation schedule. +// if (is_commercial && i == 1) cf.at(CF_sta_taxable_income_less_deductions,i) -= total_sales_tax; + + if (is_commercial || is_mortgage) // interest only deductible if residential mortgage or commercial + cf.at(CF_sta_taxable_income_less_deductions, i) -= cf.at(CF_debt_payment_interest,i); + + cf.at(CF_sta_tax_savings, i) = cf.at(CF_ptc_sta,i) - cf.at(CF_state_tax_frac,i)*cf.at(CF_sta_taxable_income_less_deductions,i); +// SAM 1038 if (i==1) cf.at(CF_sta_tax_savings, i) += itc_sta_amount + itc_sta_per; + cf.at(CF_sta_tax_savings, i) += cf.at(CF_itc_sta_amount,i) + cf.at(CF_itc_sta_percent_amount,i); + + // ************************************************ + // tax effect on equity (federal) + + cf.at(CF_fed_incentive_income_less_deductions, i) = + + cf.at(CF_deductible_expenses, i) + + cf.at(CF_pbi_total,i) + - cf.at(CF_fed_depreciation,i) + + cf.at(CF_sta_tax_savings, i); + + if (i==1) cf.at(CF_fed_incentive_income_less_deductions, i) += ibi_total + cbi_total; + +// sales tax is in depreciable bases and is already written off according to depreciation schedule. +// if (is_commercial && i == 1) cf.at(CF_fed_incentive_income_less_deductions, i) -= total_sales_tax; + + if (is_commercial || is_mortgage) // interest only deductible if residential mortgage or commercial + cf.at(CF_fed_incentive_income_less_deductions, i) -= cf.at(CF_debt_payment_interest,i); + + cf.at(CF_fed_taxable_income_less_deductions, i) = taxable_incentive_income( i, "fed" ) + + cf.at(CF_deductible_expenses,i) + - cf.at(CF_fed_depreciation,i) + + cf.at(CF_sta_tax_savings, i); + + cf.at(CF_fed_taxable_incentive_income, i) = taxable_incentive_income(i, "fed"); + +// sales tax is in depreciable bases and is already written off according to depreciation schedule. +// if (is_commercial && i == 1) cf.at(CF_fed_taxable_income_less_deductions, i) -= total_sales_tax; + + if (is_commercial || is_mortgage) // interest only deductible if residential mortgage or commercial + cf.at(CF_fed_taxable_income_less_deductions, i) -= cf.at(CF_debt_payment_interest,i); + + cf.at(CF_fed_tax_savings, i) = cf.at(CF_ptc_fed,i) - cf.at(CF_federal_tax_frac,i)*cf.at(CF_fed_taxable_income_less_deductions,i); +// SAM 1038 if (i == 1) cf.at(CF_fed_tax_savings, i) += itc_fed_amount + itc_fed_per; + cf.at(CF_fed_tax_savings, i) += cf.at(CF_itc_fed_amount,i) + cf.at(CF_itc_fed_percent_amount,i); + + // ************************************************ + // combined tax savings and cost/cash flows + + cf.at(CF_sta_and_fed_tax_savings,i) = cf.at(CF_sta_tax_savings, i)+cf.at(CF_fed_tax_savings, i); + + cf.at(CF_after_tax_net_equity_cost_flow, i) = + + (is_commercial ? cf.at(CF_deductible_expenses, i) : -cf.at(CF_operating_expenses,i) ) + - cf.at(CF_debt_payment_total, i) + + cf.at(CF_pbi_total, i) + + cf.at(CF_sta_and_fed_tax_savings,i); + + /* + Calculate discounted payback period from March 1995 NREL/TP-462-5173 p.58 + CF_discounted_costs, + CF_discounted_savings, + CF_discounted_payback, + CF_discounted_cumulative_payback, + */ + // take costs to be positive in this context + cf.at(CF_discounted_costs, i) = -cf.at(CF_after_tax_net_equity_cost_flow, i) - cf.at(CF_debt_payment_total, i); + // interest already deducted and accounted for in tax savings (so add back here) + if (is_commercial || is_mortgage) + cf.at(CF_discounted_costs, i) += cf.at(CF_debt_payment_interest, i) * cf.at(CF_effective_tax_frac,i); + // discount at nominal discount rate + cf.at(CF_discounted_costs, i) /= pow((1.0 + nom_discount_rate), (i)); + // savings reduced by effective tax rate for commercial since already included in tax savings + cf.at(CF_discounted_savings, i) = ((is_commercial ? (1.0 - cf.at(CF_effective_tax_frac, i)) : 1.0)*cf.at(CF_energy_value, i)) / pow((1.0 + nom_discount_rate), (i)) + + ((is_commercial ? (1.0 - cf.at(CF_effective_tax_frac, i)) : 1.0)*cf.at(CF_thermal_value, i)) / pow((1.0 + nom_discount_rate), (i)); + cf.at(CF_discounted_payback, i) = cf.at(CF_discounted_savings, i) - cf.at(CF_discounted_costs, i); + cf.at(CF_discounted_cumulative_payback, i) = + cf.at(CF_discounted_cumulative_payback, i - 1) + + cf.at(CF_discounted_payback, i); + + + + cf.at(CF_after_tax_cash_flow,i) = + cf.at(CF_after_tax_net_equity_cost_flow, i) + + ((is_commercial ? (1.0 - cf.at(CF_effective_tax_frac, i)) : 1.0)*cf.at(CF_energy_value, i)) + +((is_commercial ? (1.0 - cf.at(CF_effective_tax_frac, i)) : 1.0)*cf.at(CF_thermal_value, i)); + + if ( is_commercial || is_mortgage ) + cf.at(CF_payback_with_expenses,i) = + cf.at(CF_after_tax_cash_flow,i) + + cf.at(CF_debt_payment_interest, i) * (1 - cf.at(CF_effective_tax_frac, i)) + + cf.at(CF_debt_payment_principal,i); + else + cf.at(CF_payback_with_expenses,i) = + cf.at(CF_after_tax_cash_flow,i) + + cf.at(CF_debt_payment_interest,i) + + cf.at(CF_debt_payment_principal,i); + + cf.at(CF_cumulative_payback_with_expenses,i) = + cf.at(CF_cumulative_payback_with_expenses,i-1) + +cf.at(CF_payback_with_expenses,i); + + if ( is_commercial || is_mortgage ) + cf.at(CF_payback_without_expenses,i) = + + cf.at(CF_after_tax_cash_flow,i) + + cf.at(CF_debt_payment_interest, i) * (1.0 - cf.at(CF_effective_tax_frac, i)) + + cf.at(CF_debt_payment_principal,i) + - cf.at(CF_deductible_expenses,i) + + cf.at(CF_deductible_expenses, i) * cf.at(CF_effective_tax_frac, i); + else + cf.at(CF_payback_without_expenses,i) = + + cf.at(CF_after_tax_cash_flow,i) + + cf.at(CF_debt_payment_interest,i) + + cf.at(CF_debt_payment_principal,i) + - cf.at(CF_deductible_expenses,i) + + cf.at(CF_deductible_expenses, i) * cf.at(CF_effective_tax_frac, i); + + + cf.at(CF_cumulative_payback_without_expenses,i) = + + cf.at(CF_cumulative_payback_without_expenses,i-1) + + cf.at(CF_payback_without_expenses,i); + } + + + util::matrix_t monthly_energy_charge; //monthly energy charges at 12 month x nyears matrix ($) + util::matrix_t net_annual_true_up; //net annual true up payments as 12 month x nyears matrix ($) + util::matrix_t net_billing_credit; //net annual true up payments as 12 month x nyears matrix ($) + util::matrix_t net_metering_credit; + net_annual_true_up = as_matrix("true_up_credits_ym"); //Use net annual true up payments regardless of billing mode ($) + net_billing_credit = as_matrix("net_billing_credits_ym"); //Use net annual true up payments regardless of billing mode ($) + net_metering_credit = as_matrix("nm_dollars_applied_ym"); + size_t n_year1_hourly_ec; + size_t n_year1_hourly_dc; + size_t n_gen_purchases; + ssc_number_t* year1_hourly_ec = as_array("year1_hourly_ec_with_system", &n_year1_hourly_ec); + ssc_number_t* year1_hourly_dc = as_array("year1_hourly_dc_with_system", &n_year1_hourly_dc); + ssc_number_t* gen_purchases = as_array("gen_purchases", &n_gen_purchases); + if (is_assigned("rate_escalation")) //Create rate escalation nyears array with inflation and specified rate escalation % + escal_or_annual(CF_util_escal_rate, nyears, "rate_escalation", inflation_rate, 0.01, true, 0); + save_cf(CF_util_escal_rate, nyears, "cf_util_escal_rate"); + cf.at(CF_parasitic_cost, 0) = 0; + double monthly_hourly_purchases = 0; + double monthly_hourly_fromgrid = 0; + int n_steps_per_hour = n_year1_hourly_ec / 8760; + size_t n_e_fromgrid; + ssc_number_t* year1_hourly_e_from_grid = as_array("year1_hourly_e_fromgrid", &n_e_fromgrid); + ssc_number_t* monthly_gen_purchases = allocate("monthly_gen_purchases", 12 * nyears); + ssc_number_t* monthly_e_fromgrid = allocate("monthly_e_from_grid", 12); + for (size_t a = 1; a <= nyears; a++) { + if (as_integer("system_use_lifetime_output") == 1) { + for (size_t m = 1; m <= 12; m++) { + monthly_hourly_purchases = 0; + monthly_hourly_fromgrid = 0; + for (size_t d = 1; d <= util::days_in_month(int(m - 1)); d++) { + for (size_t h = 0; h < 24; h++) { //monthly iteration for each year + for (size_t n = 0; n < n_steps_per_hour; n++) { + monthly_e_fromgrid[m-1] += year1_hourly_e_from_grid[util::hour_of_year(m, d, h) * n_steps_per_hour + n]; + monthly_gen_purchases[(a - 1) * 12 + m-1] += -gen_purchases[(a - 1) * 8760 * n_steps_per_hour + n_steps_per_hour * util::hour_of_year(m, d, h) + n]; + if (year1_hourly_e_from_grid[h] != 0.0) { + cf.at(CF_parasitic_cost, a) += -gen_purchases[(a - 1) * 8760 * n_steps_per_hour + n_steps_per_hour * util::hour_of_year(m, d, h) + n] * cf.at(CF_degradation, a) / year1_hourly_e_from_grid[h] * (year1_hourly_ec[h * n_steps_per_hour + n] + year1_hourly_dc[h * n_steps_per_hour + n]) * cf.at(CF_util_escal_rate, a); //use the electricity rate data by year (also trueup) //* charged_grid[a] / charged_grid[1] * cf.at(CF_util_escal_rate, a); + } + if (d == util::days_in_month(int(m - 1)) && h == 23 && monthly_e_fromgrid[m - 1] > 0) cf.at(CF_parasitic_cost, a) += -monthly_gen_purchases[(a - 1) * 12 + m - 1] / monthly_e_fromgrid[m - 1] * (net_annual_true_up.at(a, m - 1) + net_billing_credit.at(a, m - 1) + net_metering_credit.at(a, m - 1)); + } + } + } + } + } + else { + for (size_t m = 1; m <= 12; m++) { + monthly_hourly_purchases = 0; + monthly_hourly_fromgrid = 0; + for (size_t d = 1; d <= util::days_in_month(int(m - 1)); d++) { + for (size_t h = 0; h < 24; h++) { //monthly iteration for each year + for (size_t n = 0; n < n_steps_per_hour; n++) { + monthly_e_fromgrid[m-1] += year1_hourly_e_from_grid[util::hour_of_year(m, d, h) * n_steps_per_hour + n]; + monthly_gen_purchases[(a - 1) * 12 + m-1] += -gen_purchases[n_steps_per_hour * util::hour_of_year(m, d, h) + n]; + if (year1_hourly_e_from_grid[h] != 0.0) { + cf.at(CF_parasitic_cost, a) += -gen_purchases[n_steps_per_hour * util::hour_of_year(m, d, h) + n] * cf.at(CF_degradation, a) / year1_hourly_e_from_grid[h] * (year1_hourly_ec[h * n_steps_per_hour + n] + year1_hourly_dc[h * n_steps_per_hour + n]) * cf.at(CF_util_escal_rate, a); //use the electricity rate data by year (also trueup) //* charged_grid[a] / charged_grid[1] * cf.at(CF_util_escal_rate, a); + } + if (d == util::days_in_month(int(m - 1)) && h == 23 && monthly_e_fromgrid[m - 1] > 0) cf.at(CF_parasitic_cost, a) += -monthly_gen_purchases[(size_t(a) - 1) * 12 + m - 1] / monthly_e_fromgrid[m - 1] * (net_annual_true_up.at(a, m - 1) + net_billing_credit.at(a, m - 1) + net_metering_credit.at(a, m - 1)); + } + } + } + } + } + } + + save_cf(CF_parasitic_cost, nyears, "cf_parasitic_cost"); + + double npv_energy_real = npv(CF_energy_sales, nyears, real_discount_rate); +// if (npv_energy_real == 0.0) throw general_error("lcoe real failed because energy npv is zero"); +// double lcoe_real = -( cf.at(CF_after_tax_net_equity_cost_flow,0) + npv(CF_after_tax_net_equity_cost_flow, nyears, nom_discount_rate) ) * 100 / npv_energy_real; + double lcoe_real = -( cf.at(CF_after_tax_net_equity_cost_flow,0) + npv(CF_after_tax_net_equity_cost_flow, nyears, nom_discount_rate) - npv(CF_parasitic_cost, nyears, nom_discount_rate) ) * 100; + if (npv_energy_real == 0.0) + { + lcoe_real = std::numeric_limits::quiet_NaN(); + } + else + { + lcoe_real /= npv_energy_real; + } + + double npv_energy_nom = npv( CF_energy_sales, nyears, nom_discount_rate ); +// if (npv_energy_nom == 0.0) throw general_error("lcoe nom failed because energy npv is zero"); +// double lcoe_nom = -( cf.at(CF_after_tax_net_equity_cost_flow,0) + npv(CF_after_tax_net_equity_cost_flow, nyears, nom_discount_rate) ) * 100 / npv_energy_nom; + double lcoe_nom = -( cf.at(CF_after_tax_net_equity_cost_flow,0) + npv(CF_after_tax_net_equity_cost_flow, nyears, nom_discount_rate) - npv(CF_parasitic_cost, nyears, nom_discount_rate)) * 100; + if (npv_energy_nom == 0.0) + { + lcoe_nom = std::numeric_limits::quiet_NaN(); + } + else + { + lcoe_nom /= npv_energy_nom; + } + + double net_present_value = cf.at(CF_after_tax_cash_flow, 0) + npv(CF_after_tax_cash_flow, nyears, nom_discount_rate ); + + double payback = compute_payback(CF_cumulative_payback_with_expenses, CF_payback_with_expenses, nyears); + // Added for Owen Zinaman for Mexico Rates and analyses 9/26/16 + //- see C:\Projects\SAM\Documentation\Payback\DiscountedPayback_2016.9.26 + double discounted_payback = compute_payback(CF_discounted_cumulative_payback, CF_discounted_payback, nyears); + + // save outputs + + + double npv_fed_ptc = npv(CF_ptc_fed,nyears,nom_discount_rate); + double npv_sta_ptc = npv(CF_ptc_sta,nyears,nom_discount_rate); + + // TODO check this +// npv_fed_ptc /= (1.0 - effective_tax_rate); +// npv_sta_ptc /= (1.0 - effective_tax_rate); + npv_fed_ptc /= (1.0 - cf.at(CF_effective_tax_frac,1)); + npv_sta_ptc /= (1.0 - cf.at(CF_effective_tax_frac, 1)); + + double lcoptc_fed_nom=0.0; + if (npv_energy_nom != 0) lcoptc_fed_nom = npv_fed_ptc / npv_energy_nom * 100.0; + double lcoptc_fed_real=0.0; + if (npv_energy_real != 0) lcoptc_fed_real = npv_fed_ptc / npv_energy_real * 100.0; + + double lcoptc_sta_nom=0.0; + if (npv_energy_nom != 0) lcoptc_sta_nom = npv_sta_ptc / npv_energy_nom * 100.0; + double lcoptc_sta_real=0.0; + if (npv_energy_real != 0) lcoptc_sta_real = npv_sta_ptc / npv_energy_real * 100.0; + + assign("lcoptc_fed_nom", var_data((ssc_number_t) lcoptc_fed_nom)); + assign("lcoptc_fed_real", var_data((ssc_number_t) lcoptc_fed_real)); + assign("lcoptc_sta_nom", var_data((ssc_number_t) lcoptc_sta_nom)); + assign("lcoptc_sta_real", var_data((ssc_number_t) lcoptc_sta_real)); + + /////////////////////////////////////////////////////////////////////// +//LCOS Calculations + if (is_assigned("battery_total_cost_lcos") && as_double("battery_total_cost_lcos") != 0) { + for (int y = 0; y <= nyears; y++) { + cf_lcos.at(0, y) = cf.at(CF_battery_replacement_cost, y); + cf_lcos.at(1, y) = cf.at(CF_battery_replacement_cost_schedule, y); + cf_lcos.at(6, y) = cf.at(CF_om_fixed1_expense, y); //Fixed OM Battery cost + cf_lcos.at(7, y) = cf.at(CF_om_production1_expense, y); //Produciton OM Battery cost + cf_lcos.at(8, y) = cf.at(CF_om_capacity1_expense, y); //Capacity OM Battery Cost + } + int grid_charging_cost_version = 0; + lcos_calc(this, cf_lcos, nyears, nom_discount_rate, inflation_rate, lcoe_real, total_cost, real_discount_rate, grid_charging_cost_version); + } + ///////////////////////////////////////////////////////////////////////////////////////// + + if (as_integer("en_batt") == 1 || as_integer("en_standalone_batt") == 1) { + update_battery_outputs(this, nyears); + } + update_fuelcell_outputs(this, nyears); + + double wacc = 0.0; + wacc = (1.0 - debt_frac)*nom_discount_rate + debt_frac*loan_rate*(1.0 - cf.at(CF_effective_tax_frac, 1)); + + wacc *= 100.0; +// effective_tax_rate *= 100.0; + + + assign("wacc", var_data( (ssc_number_t) wacc)); + assign("effective_tax_rate", var_data((ssc_number_t)(cf.at(CF_effective_tax_frac, 1)*100.0))); + + + + // NTE + ssc_number_t *ub_w_sys = 0; + ub_w_sys = as_array("elec_cost_with_system", &count); + if (count != (size_t)(nyears+1)) + throw exec_error("cashloan_heat", util::format("utility bill with system input wrong length (%d) should be (%d)",count, nyears+1)); + ssc_number_t *ub_wo_sys = 0; + ub_wo_sys = as_array("elec_cost_without_system", &count); + if (count != (size_t)(nyears+1)) + throw exec_error("cashloan_heat", util::format("utility bill without system input wrong length (%d) should be (%d)",count, nyears+1)); + + for (i = 0; i < (int)count; i++) + cf.at(CF_nte, i) = (double) (ub_wo_sys[i] - ub_w_sys[i]) *100.0;// $ to cents + double lnte_real = npv( CF_nte, nyears, nom_discount_rate ); + + for (i = 0; i < (int)count; i++) + if (cf.at(CF_energy_net,i) > 0) cf.at(CF_nte,i) /= cf.at(CF_energy_net,i); + + double lnte_nom = lnte_real; + if (npv_energy_real == 0.0) + lnte_real = std::numeric_limits::quiet_NaN(); + else + lnte_real /= npv_energy_real; + if (npv_energy_nom == 0.0) + lnte_nom = std::numeric_limits::quiet_NaN(); + else + lnte_nom /= npv_energy_nom; + + assign( "lnte_real", var_data((ssc_number_t)lnte_real) ); + assign( "lnte_nom", var_data((ssc_number_t)lnte_nom) ); + save_cf(CF_nte, nyears, "cf_nte"); + assign( "year1_nte", var_data((ssc_number_t)cf.at(CF_nte,1)) ); + + assign( "cf_length", var_data( (ssc_number_t) nyears+1 )); + + assign("payback", var_data((ssc_number_t)payback)); + assign("discounted_payback", var_data((ssc_number_t)discounted_payback)); + assign("lcoe_real", var_data((ssc_number_t)lcoe_real)); + assign( "lcoe_nom", var_data((ssc_number_t)lcoe_nom) ); + assign( "npv", var_data((ssc_number_t)net_present_value) ); + +// assign("first_year_energy_net", var_data((ssc_number_t) cf.at(CF_energy_net,1))); + + assign( "depr_basis_fed", var_data((ssc_number_t)federal_depr_basis )); + assign( "depr_basis_sta", var_data((ssc_number_t)state_depr_basis )); + assign( "discount_nominal", var_data((ssc_number_t)(nom_discount_rate*100.0) )); +// assign( "sales_tax_deduction", var_data((ssc_number_t)total_sales_tax )); + assign( "adjusted_installed_cost", var_data((ssc_number_t)adjusted_installed_cost )); + assign( "first_cost", var_data((ssc_number_t)first_cost )); + assign( "total_cost", var_data((ssc_number_t)total_cost )); + assign( "loan_amount", var_data((ssc_number_t)loan_amount )); + + save_cf(CF_energy_value, nyears, "cf_energy_value"); + + save_cf(CF_energy_net, nyears, "cf_energy_net"); + save_cf(CF_energy_sales, nyears, "cf_energy_sales"); + save_cf(CF_energy_purchases, nyears, "cf_energy_purchases"); + + if (is_assigned("gen_without_battery")) + { + save_cf(CF_energy_without_battery, nyears, "cf_energy_without_battery"); + } + + save_cf(CF_thermal_value, nyears, "cf_thermal_value"); + + +// real estate value added 6/24/13 + for ( i=1;i=i;j--) + result = rr * result + cf.at(CF_energy_value, j) + cf.at(CF_thermal_value, j); + cf.at(CF_value_added,i) = result*rr + cf.at(CF_net_salvage_value,i); + } + save_cf( CF_value_added, nyears, "cf_value_added" ); + + save_cf(CF_federal_tax_frac, nyears, "cf_federal_tax_frac"); + save_cf(CF_state_tax_frac, nyears, "cf_state_tax_frac"); + save_cf(CF_effective_tax_frac, nyears, "cf_effective_tax_frac"); + + + save_cf(CF_om_fixed_expense, nyears, "cf_om_fixed_expense"); + save_cf(CF_om_production_expense, nyears, "cf_om_production_expense"); + save_cf(CF_om_capacity_expense, nyears, "cf_om_capacity_expense"); + if (add_om_num_types > 0) { + save_cf(CF_om_fixed1_expense, nyears, "cf_om_fixed1_expense"); + save_cf(CF_om_production1_expense, nyears, "cf_om_production1_expense"); + save_cf(CF_om_capacity1_expense, nyears, "cf_om_capacity1_expense"); + } + if (add_om_num_types > 1) { + save_cf(CF_om_fixed2_expense, nyears, "cf_om_fixed2_expense"); + save_cf(CF_om_production2_expense, nyears, "cf_om_production2_expense"); + save_cf(CF_om_capacity2_expense, nyears, "cf_om_capacity2_expense"); + } + save_cf( CF_om_fuel_expense, nyears, "cf_om_fuel_expense" ); + save_cf( CF_om_opt_fuel_1_expense, nyears, "cf_om_opt_fuel_1_expense" ); + save_cf( CF_om_opt_fuel_2_expense, nyears, "cf_om_opt_fuel_2_expense" ); + save_cf( CF_property_tax_assessed_value, nyears, "cf_property_tax_assessed_value" ); + save_cf( CF_property_tax_expense, nyears, "cf_property_tax_expense" ); + save_cf( CF_insurance_expense, nyears, "cf_insurance_expense" ); + save_cf( CF_net_salvage_value, nyears, "cf_net_salvage_value" ); + if (as_integer("en_batt") == 1 || as_integer("en_standalone_batt") == 1) { + save_cf(CF_battery_replacement_cost, nyears, "cf_battery_replacement_cost"); + save_cf(CF_battery_replacement_cost_schedule, nyears, "cf_battery_replacement_cost_schedule"); + } + if (is_assigned("fuelcell_replacement_option")) { + save_cf(CF_fuelcell_replacement_cost, nyears, "cf_fuelcell_replacement_cost"); + save_cf(CF_fuelcell_replacement_cost_schedule, nyears, "cf_fuelcell_replacement_cost_schedule"); + } + save_cf(CF_operating_expenses, nyears, "cf_operating_expenses"); + + save_cf( CF_deductible_expenses, nyears, "cf_deductible_expenses"); + + save_cf( CF_debt_balance, nyears, "cf_debt_balance" ); + save_cf( CF_debt_payment_interest, nyears, "cf_debt_payment_interest" ); + save_cf( CF_debt_payment_principal, nyears, "cf_debt_payment_principal" ); + save_cf( CF_debt_payment_total, nyears, "cf_debt_payment_total" ); + + assign( "ibi_total_fed", var_data((ssc_number_t) (ibi_fed_amount + ibi_fed_per))); + assign( "ibi_total_sta", var_data((ssc_number_t) (ibi_sta_amount + ibi_sta_per))); + assign( "ibi_total_uti", var_data((ssc_number_t) (ibi_uti_amount + ibi_uti_per))); + assign( "ibi_total_oth", var_data((ssc_number_t) (ibi_oth_amount + ibi_oth_per))); + assign( "ibi_total", var_data((ssc_number_t) ibi_total)); + + assign( "cbi_total_fed", var_data((ssc_number_t) (cbi_fed_amount))); + assign( "cbi_total_sta", var_data((ssc_number_t) (cbi_sta_amount))); + assign( "cbi_total_uti", var_data((ssc_number_t) (cbi_uti_amount))); + assign( "cbi_total_oth", var_data((ssc_number_t) (cbi_oth_amount))); + assign( "cbi_total", var_data((ssc_number_t) cbi_total)); + + + save_cf( CF_pbi_fed, nyears, "cf_pbi_total_fed" ); + save_cf( CF_pbi_sta, nyears, "cf_pbi_total_sta" ); + save_cf( CF_pbi_uti, nyears, "cf_pbi_total_uti" ); + save_cf( CF_pbi_oth, nyears, "cf_pbi_total_oth" ); + save_cf( CF_pbi_total, nyears, "cf_pbi_total" ); + + save_cf( CF_ptc_fed, nyears, "cf_ptc_fed" ); + save_cf( CF_ptc_sta, nyears, "cf_ptc_sta" ); + + + // SAM 1038 + double itc_fed_total = 0.0; + double itc_sta_total = 0.0; + double itc_total = 0.0; + + for (size_t k = 0; k <= nyears; k++) { + itc_fed_total += cf.at(CF_itc_fed, k); + itc_sta_total += cf.at(CF_itc_sta, k); + itc_total += cf.at(CF_itc_total, k); + } + assign("itc_total_fed", var_data((ssc_number_t)itc_fed_total)); + assign("itc_total_sta", var_data((ssc_number_t)itc_sta_total)); + assign("itc_total", var_data((ssc_number_t)itc_total)); + + save_cf( CF_sta_depr_sched, nyears, "cf_sta_depr_sched" ); + save_cf( CF_sta_depreciation, nyears, "cf_sta_depreciation" ); + save_cf( CF_sta_incentive_income_less_deductions, nyears, "cf_sta_incentive_income_less_deductions" ); + save_cf( CF_sta_taxable_income_less_deductions, nyears, "cf_sta_taxable_income_less_deductions" ); + save_cf( CF_sta_tax_savings, nyears, "cf_sta_tax_savings" ); + + save_cf( CF_sta_taxable_incentive_income, nyears, "cf_sta_taxable_incentive_income"); + save_cf( CF_fed_taxable_incentive_income, nyears, "cf_fed_taxable_incentive_income"); + + save_cf( CF_fed_depr_sched, nyears, "cf_fed_depr_sched" ); + save_cf( CF_fed_depreciation, nyears, "cf_fed_depreciation" ); + save_cf( CF_fed_incentive_income_less_deductions, nyears, "cf_fed_incentive_income_less_deductions" ); + save_cf( CF_fed_taxable_income_less_deductions, nyears, "cf_fed_taxable_income_less_deductions" ); + save_cf( CF_fed_tax_savings, nyears, "cf_fed_tax_savings" ); + + save_cf( CF_sta_and_fed_tax_savings, nyears, "cf_sta_and_fed_tax_savings" ); + save_cf( CF_after_tax_net_equity_cost_flow, nyears, "cf_after_tax_net_equity_cost_flow" ); + save_cf( CF_after_tax_cash_flow, nyears, "cf_after_tax_cash_flow" ); + + save_cf( CF_payback_with_expenses, nyears, "cf_payback_with_expenses" ); + save_cf( CF_cumulative_payback_with_expenses, nyears, "cf_cumulative_payback_with_expenses" ); + + // For Owen and discounted payback period + save_cf(CF_discounted_costs, nyears, "cf_discounted_costs"); + save_cf(CF_discounted_savings, nyears, "cf_discounted_savings"); + save_cf(CF_discounted_payback, nyears, "cf_discounted_payback"); + save_cf(CF_discounted_cumulative_payback, nyears, "cf_discounted_cumulative_payback"); + + save_cf( CF_payback_without_expenses, nyears, "cf_payback_without_expenses" ); + save_cf( CF_cumulative_payback_without_expenses, nyears, "cf_cumulative_payback_without_expenses" ); + + // for cost stacked bars + //npv(CF_energy_value, nyears, nom_discount_rate) + // present value of o and m value - note - present value is distributive - sum of pv = pv of sum + double pvAnnualOandM = npv(CF_om_fixed_expense, nyears, nom_discount_rate); + double pvFixedOandM = npv(CF_om_capacity_expense, nyears, nom_discount_rate); + double pvVariableOandM = npv(CF_om_production_expense, nyears, nom_discount_rate); + double pvFuelOandM = npv(CF_om_fuel_expense, nyears, nom_discount_rate); + double pvOptFuel1OandM = npv(CF_om_opt_fuel_1_expense, nyears, nom_discount_rate); + double pvOptFuel2OandM = npv(CF_om_opt_fuel_2_expense, nyears, nom_discount_rate); + // double pvWaterOandM = NetPresentValue(sv[svNominalDiscountRate], cf[cfAnnualWaterCost], analysis_period); + + assign( "present_value_oandm", var_data((ssc_number_t)(pvAnnualOandM + pvFixedOandM + pvVariableOandM + pvFuelOandM))); // + pvWaterOandM); + + assign( "present_value_oandm_nonfuel", var_data((ssc_number_t)(pvAnnualOandM + pvFixedOandM + pvVariableOandM))); + assign( "present_value_fuel", var_data((ssc_number_t)(pvFuelOandM + pvOptFuel1OandM + pvOptFuel2OandM))); + + // present value of insurance and property tax + double pvInsurance = npv(CF_insurance_expense, nyears, nom_discount_rate); + double pvPropertyTax = npv(CF_property_tax_expense, nyears, nom_discount_rate); + + assign( "present_value_insandproptax", var_data((ssc_number_t)(pvInsurance + pvPropertyTax))); + + + // SAM 1038 + save_cf(CF_itc_fed_amount, nyears, "cf_itc_fed_amount"); + save_cf(CF_itc_fed_percent_amount, nyears, "cf_itc_fed_percent_amount"); + save_cf(CF_itc_fed, nyears, "cf_itc_fed"); + save_cf(CF_itc_sta_amount, nyears, "cf_itc_sta_amount"); + save_cf(CF_itc_sta_percent_amount, nyears, "cf_itc_sta_percent_amount"); + save_cf(CF_itc_sta, nyears, "cf_itc_sta"); + save_cf(CF_itc_total, nyears, "cf_itc_total"); + + + } + +/* These functions can be placed in common financial library with matrix and constants passed? */ + + void save_cf(int cf_line, int nyears, const std::string &name) + { + ssc_number_t *arrp = allocate( name, nyears+1 ); + for (int i=0;i<=nyears;i++) + arrp[i] = (ssc_number_t)cf.at(cf_line, i); + } + + double compute_payback( int cf_cpb, int cf_pb, int nyears ) + { + // may need to determine last negative to positive transition for high replacement costs - see C:\Projects\SAM\Documentation\FinancialIssues\Payback_2015.9.8 + double dPayback = std::numeric_limits::quiet_NaN(); // report as > analysis period + bool bolPayback = false; + int iPayback = 0; + int i = 1; + while ((i<=nyears) && (!bolPayback)) + { + if (cf.at(cf_cpb,i) > 0) + { + bolPayback = true; + iPayback = i; + } + i++; + } + + if (bolPayback) + { + dPayback = iPayback; + if (cf.at(cf_pb, iPayback) != 0.0) + dPayback -= cf.at(cf_cpb,iPayback) / cf.at(cf_pb,iPayback); + } + + return dPayback; + } + + + double npv(int cf_line, int nyears, double rate) + { + if (rate <= -1.0) throw general_error("cannot calculate NPV with discount rate less or equal to -1.0"); + + double rr = 1/(1+rate); + double result = 0; + for (int i=nyears;i>0;i--) + result = rr * result + cf.at(cf_line,i); + + return result*rr; + } + + void compute_production_incentive( int cf_line, int nyears, const std::string &s_val, const std::string &s_term, const std::string &s_escal ) + { + size_t len = 0; + ssc_number_t *parr = as_array(s_val, &len); + int term = as_integer(s_term); + double escal = as_double(s_escal)/100.0; + + if (len == 1) + { + for (int i=1;i<=nyears;i++) + cf.at(cf_line, i) = (i <= term) ? parr[0] * cf.at(CF_energy_sales,i) * pow(1 + escal, i-1) : 0.0; + } + else + { + for (int i=1;i<=nyears && i <= (int)len;i++) + cf.at(cf_line, i) = parr[i-1]*cf.at(CF_energy_sales,i); + } + } + + void compute_production_incentive_IRS_2010_37( int cf_line, int nyears, const std::string &s_val, const std::string &s_term, const std::string &s_escal ) + { + // rounding based on IRS document and emails 2/24/2011 + size_t len = 0; + ssc_number_t *parr = as_array(s_val, &len); + int term = as_integer(s_term); + double escal = as_double(s_escal)/100.0; + + if (len == 1) + { + for (int i=1;i<=nyears;i++) + cf.at(cf_line, i) = (i <= term) ? cf.at(CF_energy_sales,i) / 1000.0 * round_irs(1000.0 * parr[0] * pow(1 + escal, i-1)) : 0.0; + } + else + { + for (int i=1;i<=nyears && i <= (int)len;i++) + cf.at(cf_line, i) = parr[i-1]*cf.at(CF_energy_sales,i); + } + } + + + void single_or_schedule( int cf_line, int nyears, double scale, const std::string &name ) + { + size_t len = 0; + ssc_number_t *p = as_array(name, &len); + for (int i=1;i<=(int)len && i <= nyears;i++) + cf.at(cf_line, i) = scale*p[i-1]; + } + + void single_or_schedule_check_max( int cf_line, int nyears, double scale, const std::string &name, const std::string &maxvar ) + { + double max = as_double(maxvar); + size_t len = 0; + ssc_number_t *p = as_array(name, &len); + for (int i=1;i<=(int)len && i <= nyears;i++) + cf.at(cf_line, i) = min( scale*p[i-1], max ); + } + + double taxable_incentive_income(int year, const std::string &fed_or_sta) + { + double ti = 0.0; + if (year==1) + { + if ( as_boolean("ibi_fed_amount_tax_"+fed_or_sta) ) ti += ibi_fed_amount; + if ( as_boolean("ibi_sta_amount_tax_"+fed_or_sta) ) ti += ibi_sta_amount; + if ( as_boolean("ibi_uti_amount_tax_"+fed_or_sta) ) ti += ibi_uti_amount; + if ( as_boolean("ibi_oth_amount_tax_"+fed_or_sta) ) ti += ibi_oth_amount; + + if ( as_boolean("ibi_fed_percent_tax_"+fed_or_sta) ) ti += ibi_fed_per; + if ( as_boolean("ibi_sta_percent_tax_"+fed_or_sta) ) ti += ibi_sta_per; + if ( as_boolean("ibi_uti_percent_tax_"+fed_or_sta) ) ti += ibi_uti_per; + if ( as_boolean("ibi_oth_percent_tax_"+fed_or_sta) ) ti += ibi_oth_per; + + if ( as_boolean("cbi_fed_tax_"+fed_or_sta) ) ti += cbi_fed_amount; + if ( as_boolean("cbi_sta_tax_"+fed_or_sta) ) ti += cbi_sta_amount; + if ( as_boolean("cbi_uti_tax_"+fed_or_sta) ) ti += cbi_uti_amount; + if ( as_boolean("cbi_oth_tax_"+fed_or_sta) ) ti += cbi_oth_amount; + } + + if ( as_boolean("pbi_fed_tax_"+fed_or_sta) ) ti += cf.at( CF_pbi_fed, year ); + if ( as_boolean("pbi_sta_tax_"+fed_or_sta) ) ti += cf.at( CF_pbi_sta, year ); + if ( as_boolean("pbi_uti_tax_"+fed_or_sta) ) ti += cf.at( CF_pbi_uti, year ); + if ( as_boolean("pbi_oth_tax_"+fed_or_sta) ) ti += cf.at( CF_pbi_oth, year ); + + return ti; + } + + void depreciation_sched_macrs_half_year( int cf_line, int nyears ) + { + for (int i=1; i<=nyears; i++) + { + double factor = 0.0; + switch(i) + { + case 1: factor = 0.2000; break; + case 2: factor = 0.3200; break; + case 3: factor = 0.1920; break; + case 4: factor = 0.1152; break; + case 5: factor = 0.1152; break; + case 6: factor = 0.0576; break; + default: factor = 0.0; break; + } + cf.at(cf_line, i) = factor; + } + } + + void depreciation_sched_straight_line( int cf_line, int nyears, int slyears ) + { + double depr_per_year = (slyears!=0) ? 1.0 / ((double)slyears) : 0; + for (int i=1; i<=nyears; i++) + cf.at(cf_line, i) = (i<=slyears) ? depr_per_year : 0.0; + } + + void depreciation_sched_custom( int cf_line, int nyears, ssc_number_t *custp, int custp_len ) + { + // note - allows for greater than or less than 100% depreciation - warning to user in samsim + if (custp_len < 2) + { + cf.at(cf_line, 1) = custp[0]/100.0; + } + else + { + for (int i=1;i<=nyears && i-1 < custp_len;i++) + cf.at(cf_line, i) = custp[i-1]/100.0; + } + } + + void escal_or_annual( int cf_line, int nyears, const std::string &variable, + double inflation_rate, double scale, bool as_rate=true, double escal = 0.0) + { + size_t count; + ssc_number_t *arrp = as_array(variable, &count); + + if (as_rate) + { + if (count == 1) + { + escal = inflation_rate + scale*arrp[0]; + for (int i=0; i < nyears; i++) + cf.at(cf_line, i+1) = pow( 1+escal, i ); + } + else + { + for (int i=0; i < nyears && i < (int)count; i++) + cf.at(cf_line, i+1) = 1 + arrp[i]*scale; + } + } + else + { + if (count == 1) + { + for (int i=0;i b) ? a : b; + } + +}; + +DEFINE_MODULE_ENTRY( cashloan_heat, "Residential/Commerical Finance model for heat.", 1 ); diff --git a/ssc/cmod_singleowner_heat.cpp b/ssc/cmod_singleowner_heat.cpp new file mode 100644 index 000000000..60d1868c1 --- /dev/null +++ b/ssc/cmod_singleowner_heat.cpp @@ -0,0 +1,4481 @@ +/* +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 "common_financial.h" +#include "common.h" +#include "lib_financial.h" +using namespace libfin; +#include + +#ifndef WIN32 +#include +#endif + +static var_info _cm_vtab_singleowner_heat[] = { + +/* VARTYPE DATATYPE NAME LABEL UNITS META GROUP REQUIRED_IF CONSTRAINTS UI_HINTS*/ + +// 3 additional variables for PPA Buy rate +// optional output from battery model + { SSC_INPUT, SSC_NUMBER, "en_batt", "Enable battery storage model", "0/1", "", "BatterySystem", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "en_electricity_rates", "Enable electricity rates for grid purchase", "0/1", "", "Electricity Rates", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "batt_meter_position", "Position of battery relative to electric meter", "", "", "BatterySystem", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "revenue_gen", "Electricity to grid", "kW", "", "System Output", "", "", "" }, + // { SSC_OUTPUT, SSC_ARRAY, "gen_purchases", "Electricity from grid", "kW", "", "System Output", "", "", "" }, + + + { SSC_INPUT, SSC_ARRAY, "gen", "Net power to or from the grid", "kW", "", "System Output", "*", "", "" }, + { SSC_INPUT, SSC_ARRAY, "gen_without_battery", "Electricity to or from the renewable system, without the battery", "kW", "", "System Output", "", "", "" }, + + { SSC_INPUT, SSC_ARRAY, "degradation", "Annual energy degradation", "", "", "System Output", "system_use_lifetime_output=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "system_capacity", "System nameplate capacity", "kW", "", "System Output", "?=0", "", "" }, + + { SSC_INPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption w/ avail derate", "kWe-hr", "", "Heat Model Output", "?=0", "", ""}, + + + /* PPA Buy Rate values */ + { SSC_INPUT, SSC_ARRAY, "utility_bill_w_sys", "Electricity bill with system", "$", "", "Utility Bill", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_utility_bill", "Electricity purchase", "$", "", "", "", "LENGTH_EQUAL=cf_length", "" }, + + + /*loan moratorium from Sara for India Documentation\India\Loan Moratorum + assumptions: + 1) moratorium period begins at beginning of loan term + 2) moratorium affects principal payment and not interest + 3) loan term remains the same + 4) payments increase after moratorium period + */ + { SSC_INPUT, SSC_NUMBER, "loan_moratorium", "Loan moratorium period", "years", "", "Financial Parameters", "?=0", "INTEGER,MIN=0", "" }, + + +/* Recapitalization */ + { SSC_INOUT, SSC_NUMBER, "system_use_recapitalization", "Recapitalization expenses", "0/1", "0=None,1=Recapitalize", "System Costs", "?=0", "INTEGER,MIN=0", "" }, + { SSC_INPUT, SSC_NUMBER, "system_recapitalization_cost", "Recapitalization cost", "$", "", "System Costs", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "system_recapitalization_escalation", "Recapitalization escalation (above inflation)", "%", "", "System Costs", "?=0", "MIN=0,MAX=100", "" }, + { SSC_INOUT, SSC_ARRAY, "system_lifetime_recapitalize", "Recapitalization boolean", "", "", "System Costs", "?=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_recapitalization", "Recapitalization operating expense", "$", "", "Recapitalization", "*", "LENGTH_EQUAL=cf_length", "" }, + +/* Dispatch */ + { SSC_INOUT, SSC_NUMBER, "system_use_lifetime_output", "Lifetime hourly system outputs", "0/1", "0=hourly first year,1=hourly lifetime", "Lifetime", "*", "INTEGER,MIN=0", "" }, + + + + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_jan", "Energy produced by year in January", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_jan", "PPA revenue by year for January", "$", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_feb", "Energy produced by year in February", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_feb", "PPA revenue by year for February", "$", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_mar", "Energy produced by year in March", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_mar", "PPA revenue by year for March", "$", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_apr", "Energy produced by year in April", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_apr", "PPA revenue by year for April", "$", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_may", "Energy produced by year in May", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_may", "PPA revenue by year for May", "$", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_jun", "Energy produced by year in June", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_jun", "PPA revenue by year for June", "$", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_jul", "Energy produced by year in July", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_jul", "PPA revenue by year for July", "$", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_aug", "Energy produced by year in August", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_aug", "PPA revenue by year for August", "$", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_sep", "Energy produced by year in September", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_sep", "PPA revenue by year for September", "$", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_oct", "Energy produced by year in October", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_oct", "PPA revenue by year for October", "$", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_nov", "Energy produced by year in November", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_nov", "PPA revenue by year for November", "$", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_dec", "Energy produced by year in December", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_dec", "PPA revenue by year for December", "$", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_dispatch1", "Energy produced by year in TOD period 1", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_dispatch1", "PPA revenue by year for TOD period 1", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_dispatch2", "Energy produced by year in TOD period 2", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_dispatch2", "PPA revenue by year for TOD period 2", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_dispatch3", "Energy produced by year in TOD period 3", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_dispatch3", "PPA revenue by year for TOD period 3", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_dispatch4", "Energy produced by year in TOD period 4", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_dispatch4", "PPA revenue by year for TOD period 4", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_dispatch5", "Energy produced by year in TOD period 5", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_dispatch5", "PPA revenue by year for TOD period 5", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_dispatch6", "Energy produced by year in TOD period 6", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_dispatch6", "PPA revenue by year for TOD period 6", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_dispatch7", "Energy produced by year in TOD period 7", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_dispatch7", "PPA revenue by year for TOD period 7", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_dispatch8", "Energy produced by year in TOD period 8", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_dispatch8", "PPA revenue by year for TOD period 8", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_dispatch9", "Energy produced by year in TOD period 9", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_dispatch9", "PPA revenue by year for TOD period 9", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "firstyear_revenue_dispatch1", "PPA revenue in Year 1 TOD period 1", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_revenue_dispatch2", "PPA revenue from in Year 1 TOD period 2", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_revenue_dispatch3", "PPA revenue from in Year 1 TOD period 3", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_revenue_dispatch4", "PPA revenue in Year 1 TOD period 4", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_revenue_dispatch5", "PPA revenue in Year 1 TOD period 5", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_revenue_dispatch6", "PPA revenue in Year 1 TOD period 6", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_revenue_dispatch7", "PPA revenue in Year 1 TOD period 7", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_revenue_dispatch8", "PPA revenue in Year 1 TOD period 8", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_revenue_dispatch9", "PPA revenue in Year 1 TOD period 9", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_dispatch1", "Energy produced in Year 1 TOD period 1", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_dispatch2", "Energy produced in Year 1 TOD period 2", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_dispatch3", "Energy produced in Year 1 TOD period 3", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_dispatch4", "Energy produced in Year 1 TOD period 4", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_dispatch5", "Energy produced in Year 1 TOD period 5", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_dispatch6", "Energy produced in Year 1 TOD period 6", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_dispatch7", "Energy produced in Year 1 TOD period 7", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_dispatch8", "Energy produced in Year 1 TOD period 8", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_dispatch9", "Energy produced in Year 1 TOD period 9", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_price1", "Power price in Year 1 TOD period 1", "cents/kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_price2", "Power price in Year 1 TOD period 2", "cents/kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_price3", "Power price in Year 1 TOD period 3", "cents/kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_price4", "Power price in Year 1 TOD period 4", "cents/kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_price5", "Power price in Year 1 TOD period 5", "cents/kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_price6", "Power price in Year 1 TOD period 6", "cents/kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_price7", "Power price in Year 1 TOD period 7", "cents/kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_price8", "Power price in Year 1 TOD period 8", "cents/kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "firstyear_energy_price9", "Power price in Year 1 TOD period 9", "cents/kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + +// first year monthly output for each TOD period +// { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_monthly_firstyear", "PPA revenue in Year 1 by month", "", "", "Cash Flow Revenue by Month and TOD Period", "*", "", "" }, +// { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_monthly_firstyear", "Energy produced in Year 1 by month", "", "", "Cash Flow Revenue by Month and TOD Period", "*", "", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_monthly_firstyear_TOD1", "PPA revenue in Year 1 by month for TOD period 1", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_monthly_firstyear_TOD1", "Energy produced in Year 1 by month for TOD period 1", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_monthly_firstyear_TOD2", "PPA revenue in Year 1 by month for TOD period 2", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_monthly_firstyear_TOD2", "Energy produced in Year 1 by month for TOD period 2", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_monthly_firstyear_TOD3", "PPA revenue in Year 1 by month for TOD period 3", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_monthly_firstyear_TOD3", "Energy produced in Year 1 by month for TOD period 3", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_monthly_firstyear_TOD4", "PPA revenue in Year 1 by month for TOD period 4", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_monthly_firstyear_TOD4", "Energy produced in Year 1 by month for TOD period 4", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_monthly_firstyear_TOD5", "PPA revenue in Year 1 by month for TOD period 5", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_monthly_firstyear_TOD5", "Energy produced in Year 1 by month for TOD period 5", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_monthly_firstyear_TOD6", "PPA revenue in Year 1 by month for TOD period 6", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_monthly_firstyear_TOD6", "Energy produced in Year 1 by month for TOD period 6", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_monthly_firstyear_TOD7", "PPA revenue in Year 1 by month for TOD period 7", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_monthly_firstyear_TOD7", "Energy produced in Year 1 by month for TOD period 7", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_monthly_firstyear_TOD8", "PPA revenue in Year 1 by month for TOD period 8", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_monthly_firstyear_TOD8", "Energy produced in Year 1 by month for TOD period 8", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_monthly_firstyear_TOD9", "PPA revenue in Year 1 by month for TOD period 9", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_monthly_firstyear_TOD9", "Energy produced in Year 1 by month for TOD period 9", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, + +/* inputs in model not currently in M 11/15/10 */ + { SSC_INPUT, SSC_NUMBER, "total_installed_cost", "Installed cost", "$", "", "System Costs", "*", "", "" }, + +/* salvage value */ + { SSC_INPUT, SSC_NUMBER, "salvage_percentage", "Net pre-tax cash salvage value", "%", "", "Financial Parameters", "?=10", "MIN=0,MAX=100", "" }, + //{ SSC_INPUT, SSC_NUMBER, "batt_salvage_percentage", "Net pre-tax cash battery salvage value", "%", "", "Financial Parameters", "?=0", "MIN=0,MAX=100", "" }, + +/* market specific inputs - leveraged partnership flip */ + +/* construction period */ + { SSC_INPUT, SSC_NUMBER, "construction_financing_cost", "Construction financing total", "$", "", "Financial Parameters", "*", "", "" }, + +/* intermediate outputs */ + { SSC_OUTPUT, SSC_NUMBER, "cost_debt_upfront", "Debt up-front fee", "$", "", "Intermediate Costs", "?=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "cost_financing", "Total financing cost", "$", "", "Intermediate Costs", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "cost_prefinancing", "Total installed cost", "$", "", "Intermediate Costs", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "cost_installed", "Net capital cost", "$", "", "Intermediate Costs", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "cost_installedperwatt", "Net capital cost per watt", "$/W", "", "Intermediate Costs", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "nominal_discount_rate", "Nominal discount rate", "%", "", "Intermediate Costs", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "prop_tax_assessed_value", "Assessed value of property for tax purposes","$", "", "Intermediate Costs", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "salvage_value", "Net pre-tax cash salvage value", "$", "", "Intermediate Costs", "*", "", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_none_percent", "Non-depreciable federal and state allocation", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_none", "Non-depreciable federal and state allocation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_total", "Total depreciation federal and state allocation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "pre_depr_alloc_basis", "Depreciable basis prior to allocation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "pre_itc_qual_basis", "ITC basis prior to qualification", "$", "", "Tax Credits", "*", "", "" }, + +// state itc table +/*1*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_macrs_5", "5-yr MACRS state percent of total depreciable basis", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_macrs_5", "5-yr MACRS depreciation federal and state allocation", "$", "", "Depreciation", "*", "", "" }, +/*2*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_ibi_reduc_macrs_5", "5-yr MACRS state IBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*3*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_cbi_reduc_macrs_5", "5-yr MACRS state CBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*4*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_prior_itc_macrs_5", "5-yr MACRS state depreciation basis prior ITC reduction", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_sta_qual_macrs_5", "5-yr MACRS depreciation state ITC adj qualifying costs", "$", "", "Depreciation", "*", "", "" }, +/*5*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_qual_macrs_5", "5-yr MACRS state percent of qualifying costs", "%", "", "Depreciation", "*", "", "" }, +/*6*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_amount_macrs_5", "5-yr MACRS depreciation ITC basis from state percentage", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_sta_percent_macrs_5", "5-yr MACRS depreciation ITC basis disallowance from state percentage", "$", "", "Depreciation", "*", "", "" }, +/*7*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_fixed_amount_macrs_5", "5-yr MACRS depreciation ITC basis from state fixed amount", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_sta_fixed_macrs_5", "5-yr MACRS depreciation ITC basis disallowance from state fixed amount", "$", "", "Depreciation", "*", "", "" }, +/*8*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_itc_sta_reduction_macrs_5", "5-yr MACRS state basis state ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*9*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_itc_fed_reduction_macrs_5", "5-yr MACRS state basis federal ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*10*/{ SSC_OUTPUT, SSC_NUMBER, "depr_stabas_after_itc_macrs_5", "5-yr MACRS state depreciation basis after ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*11*/{ SSC_OUTPUT, SSC_NUMBER, "depr_stabas_first_year_bonus_macrs_5", "5-yr MACRS state first year bonus depreciation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_macrs_5", "5-yr MACRS state depreciation basis", "$", "", "Depreciation", "*", "", "" }, + +/*1*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_macrs_15", "15-yr MACRS state percent of total depreciable basis", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_macrs_15", "15-yr MACRS depreciation federal and state allocation", "$", "", "Depreciation", "*", "", "" }, +/*2*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_ibi_reduc_macrs_15", "15-yr MACRS state IBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*3*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_cbi_reduc_macrs_15", "15-yr MACRS state CBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*4*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_prior_itc_macrs_15", "15-yr MACRS state depreciation basis prior ITC reduction", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_sta_qual_macrs_15", "15-yr MACRS depreciation state ITC adj qualifying costs", "$", "", "Depreciation", "*", "", "" }, +/*5*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_qual_macrs_15", "15-yr MACRS state percent of qualifying costs", "%", "", "Depreciation", "*", "", "" }, +/*6*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_amount_macrs_15", "15-yr MACRS depreciation ITC basis from state percentage", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_sta_percent_macrs_15", "15-yr MACRS depreciation ITC basis disallowance from state percentage", "$", "", "Depreciation", "*", "", "" }, +/*7*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_fixed_amount_macrs_15", "15-yr MACRS depreciation ITC basis from state fixed amount", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_sta_fixed_macrs_15", "15-yr MACRS depreciation ITC basis disallowance from state fixed amount", "$", "", "Depreciation", "*", "", "" }, +/*8*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_itc_sta_reduction_macrs_15", "15-yr MACRS state basis state ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*9*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_itc_fed_reduction_macrs_15", "15-yr MACRS state basis federal ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*10*/{ SSC_OUTPUT, SSC_NUMBER, "depr_stabas_after_itc_macrs_15", "15-yr MACRS state depreciation basis after ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*11*/{ SSC_OUTPUT, SSC_NUMBER, "depr_stabas_first_year_bonus_macrs_15", "15-yr MACRS state first year bonus depreciation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_macrs_15", "15-yr MACRS state depreciation basis", "$", "", "Depreciation", "*", "", "" }, + +/*1*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_sl_5", "5-yr straight line state percent of total depreciable basis", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_sl_5", "5-yr straight line depreciation federal and state allocation", "$", "", "Depreciation", "*", "", "" }, +/*2*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_ibi_reduc_sl_5", "5-yr straight line state IBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*3*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_cbi_reduc_sl_5", "5-yr straight line state CBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*4*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_prior_itc_sl_5", "5-yr straight line state depreciation basis prior ITC reduction", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_sta_qual_sl_5", "5-yr straight line depreciation state ITC adj qualifying costs", "$", "", "Depreciation", "*", "", "" }, +/*5*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_qual_sl_5", "5-yr straight line state percent of qualifying costs", "%", "", "Depreciation", "*", "", "" }, +/*6*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_amount_sl_5", "5-yr straight line depreciation ITC basis from state percentage", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_sta_percent_sl_5", "5-yr straight line depreciation ITC basis disallowance from state percentage", "$", "", "Depreciation", "*", "", "" }, +/*7*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_fixed_amount_sl_5", "5-yr straight line depreciation ITC basis from state fixed amount", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_sta_fixed_sl_5", "5-yr straight line depreciation ITC basis disallowance from state fixed amount", "$", "", "Depreciation", "*", "", "" }, +/*8*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_itc_sta_reduction_sl_5", "5-yr straight line state basis state ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*9*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_itc_fed_reduction_sl_5", "5-yr straight line state basis federal ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*10*/{ SSC_OUTPUT, SSC_NUMBER, "depr_stabas_after_itc_sl_5", "5-yr straight line state depreciation basis after ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*11*/{ SSC_OUTPUT, SSC_NUMBER, "depr_stabas_first_year_bonus_sl_5", "5-yr straight line state first year bonus depreciation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_sl_5", "5-yr straight line state depreciation basis", "$", "", "Depreciation", "*", "", "" }, + +/*1*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_sl_15", "15-yr straight line state percent of total depreciable basis", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_sl_15", "15-yr straight line depreciation federal and state allocation", "$", "", "Depreciation", "*", "", "" }, +/*2*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_ibi_reduc_sl_15", "15-yr straight line state IBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*3*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_cbi_reduc_sl_15", "15-yr straight line state CBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*4*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_prior_itc_sl_15", "15-yr straight line state depreciation basis prior ITC reduction", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_sta_qual_sl_15", "15-yr straight line depreciation state ITC adj qualifying costs", "$", "", "Depreciation", "*", "", "" }, +/*5*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_qual_sl_15", "15-yr straight line state percent of qualifying costs", "%", "", "Depreciation", "*", "", "" }, +/*6*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_amount_sl_15", "15-yr straight line depreciation ITC basis from state percentage", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_sta_percent_sl_15", "15-yr straight line depreciation ITC basis disallowance from state percentage", "$", "", "Depreciation", "*", "", "" }, +/*7*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_fixed_amount_sl_15", "15-yr straight line depreciation ITC basis from state fixed amount", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_sta_fixed_sl_15", "15-yr straight line depreciation ITC basis disallowance from state fixed amount", "$", "", "Depreciation", "*", "", "" }, +/*8*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_itc_sta_reduction_sl_15", "15-yr straight line state basis state ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*9*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_itc_fed_reduction_sl_15", "15-yr straight line state basis federal ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*10*/{ SSC_OUTPUT, SSC_NUMBER, "depr_stabas_after_itc_sl_15", "15-yr straight line state depreciation basis after ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*11*/{ SSC_OUTPUT, SSC_NUMBER, "depr_stabas_first_year_bonus_sl_15", "15-yr straight line state first year bonus depreciation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_sl_15", "15-yr straight line state depreciation basis", "$", "", "Depreciation", "*", "", "" }, + +/*1*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_sl_20", "20-yr straight line state percent of total depreciable basis", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_sl_20", "20-yr straight line depreciation federal and state allocation", "$", "", "Depreciation", "*", "", "" }, +/*2*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_ibi_reduc_sl_20", "20-yr straight line state IBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*3*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_cbi_reduc_sl_20", "20-yr straight line state CBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*4*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_prior_itc_sl_20", "20-yr straight line state depreciation basis prior ITC reduction", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_sta_qual_sl_20", "20-yr straight line depreciation state ITC adj qualifying costs", "$", "", "Depreciation", "*", "", "" }, +/*5*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_qual_sl_20", "20-yr straight line state percent of qualifying costs", "%", "", "Depreciation", "*", "", "" }, +/*6*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_amount_sl_20", "20-yr straight line depreciation ITC basis from state percentage", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_sta_percent_sl_20", "20-yr straight line depreciation ITC basis disallowance from state percentage", "$", "", "Depreciation", "*", "", "" }, +/*7*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_fixed_amount_sl_20", "20-yr straight line depreciation ITC basis from state fixed amount", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_sta_fixed_sl_20", "20-yr straight line depreciation ITC basis disallowance from state fixed amount", "$", "", "Depreciation", "*", "", "" }, +/*8*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_itc_sta_reduction_sl_20", "20-yr straight line state basis state ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*9*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_itc_fed_reduction_sl_20", "20-yr straight line state basis federal ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*10*/{ SSC_OUTPUT, SSC_NUMBER, "depr_stabas_after_itc_sl_20", "20-yr straight line state depreciation basis after ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*11*/{ SSC_OUTPUT, SSC_NUMBER, "depr_stabas_first_year_bonus_sl_20", "20-yr straight line state first year bonus depreciation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_sl_20", "20-yr straight line state depreciation basis", "$", "", "Depreciation", "*", "", "" }, + +/*1*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_sl_39", "39-yr straight line state percent of total depreciable basis", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_sl_39", "39-yr straight line depreciation federal and state allocation", "$", "", "Depreciation", "*", "", "" }, +/*2*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_ibi_reduc_sl_39", "39-yr straight line state IBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*3*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_cbi_reduc_sl_39", "39-yr straight line state CBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*4*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_prior_itc_sl_39", "39-yr straight line state depreciation basis prior ITC reduction", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_sta_qual_sl_39", "39-yr straight line depreciation state ITC adj qualifying costs", "$", "", "Depreciation", "*", "", "" }, +/*5*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_qual_sl_39", "39-yr straight line state percent of qualifying costs", "%", "", "Depreciation", "*", "", "" }, +/*6*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_amount_sl_39", "39-yr straight line depreciation ITC basis from state percentage", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_sta_percent_sl_39", "39-yr straight line depreciation ITC basis disallowance from state percentage", "$", "", "Depreciation", "*", "", "" }, +/*7*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_fixed_amount_sl_39", "39-yr straight line depreciation ITC basis from state fixed amount", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_sta_fixed_sl_39", "39-yr straight line depreciation ITC basis disallowance from state fixed amount", "$", "", "Depreciation", "*", "", "" }, +/*8*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_itc_sta_reduction_sl_39", "39-yr straight line state basis state ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*9*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_itc_fed_reduction_sl_39", "39-yr straight line state basis federal ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*10*/{ SSC_OUTPUT, SSC_NUMBER, "depr_stabas_after_itc_sl_39", "39-yr straight line state depreciation basis after ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*11*/{ SSC_OUTPUT, SSC_NUMBER, "depr_stabas_first_year_bonus_sl_39", "39-yr straight line state first year bonus depreciation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_sl_39", "39-yr straight line state depreciation basis", "$", "", "Depreciation", "*", "", "" }, + +/*1*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_custom", "Custom straight line state percent of total depreciable basis", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_custom", "Custom straight line depreciation federal and state allocation", "$", "", "Depreciation", "*", "", "" }, +/*2*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_ibi_reduc_custom", "Custom straight line state IBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*3*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_cbi_reduc_custom", "Custom straight line state CBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*4*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_prior_itc_custom", "Custom straight line state depreciation basis prior ITC reduction", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_sta_qual_custom", "Custom straight line depreciation state ITC adj qualifying costs", "$", "", "Depreciation", "*", "", "" }, +/*5*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_qual_custom", "Custom straight line state percent of qualifying costs", "%", "", "Depreciation", "*", "", "" }, +/*6*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_amount_custom", "Custom straight line depreciation ITC basis from state percentage", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_sta_percent_custom", "Custom straight line depreciation ITC basis disallowance from state percentage", "$", "", "Depreciation", "*", "", "" }, +/*7*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_fixed_amount_custom", "Custom straight line depreciation ITC basis from state fixed amount", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_sta_fixed_custom", "Custom straight line depreciation ITC basis disallowance from state fixed amount", "$", "", "Depreciation", "*", "", "" }, +/*8*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_itc_sta_reduction_custom", "Custom straight line state basis state ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*9*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_itc_fed_reduction_custom", "Custom straight line state basis federal ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*10*/{ SSC_OUTPUT, SSC_NUMBER, "depr_stabas_after_itc_custom", "Custom straight line state depreciation basis after ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*11*/{ SSC_OUTPUT, SSC_NUMBER, "depr_stabas_first_year_bonus_custom", "Custom straight line state first year bonus depreciation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_custom", "Custom straight line state depreciation basis", "$", "", "Depreciation", "*", "", "" }, + +/*1*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_total", "Total state percent of total depreciable basis", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_total", "Total depreciation federal and state allocation", "$", "", "Depreciation", "*", "", "" }, +/*2*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_ibi_reduc_total", "Total state IBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*3*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_cbi_reduc_total", "Total state CBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*4*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_prior_itc_total", "Total state depreciation basis prior ITC reduction", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_sta_qual_total", "Total state ITC adj qualifying costs", "$", "", "Depreciation", "*", "", "" }, +/*5*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_qual_total", "Total state percent of qualifying costs", "%", "", "Depreciation", "*", "", "" }, +/*6*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_percent_amount_total", "Total depreciation ITC basis from state percentage", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_sta_percent_total", "Total depreciation ITC basis disallowance from state percentage", "$", "", "Depreciation", "*", "", "" }, +/*7*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_fixed_amount_total", "Total depreciation ITC basis from state fixed amount", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_sta_fixed_total", "Total depreciation ITC basis disallowance from state fixed amount", "$", "", "Depreciation", "*", "", "" }, +/*8*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_itc_sta_reduction_total", "Total state basis state ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*9*/ { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_itc_fed_reduction_total", "Total state basis federal ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*10*/{ SSC_OUTPUT, SSC_NUMBER, "depr_stabas_after_itc_total", "Total state depreciation basis after ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*11*/{ SSC_OUTPUT, SSC_NUMBER, "depr_stabas_first_year_bonus_total", "Total state first year bonus depreciation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_total", "Total state depreciation basis", "$", "", "Depreciation", "*", "", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "itc_sta_percent_total", "State ITC percent total", "$", "", "Tax Credits", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_sta_fixed_total", "State ITC fixed total", "$", "", "Tax Credits", "*", "", "" }, + +// federal itc table +/*1*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_macrs_5", "5-yr MACRS federal percent of total depreciable basis", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_macrs_5", "5-yr MACRS depreciation federal and federal allocation", "$", "", "Depreciation", "*", "", "" }, +/*2*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_ibi_reduc_macrs_5", "5-yr MACRS federal IBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*3*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_cbi_reduc_macrs_5", "5-yr MACRS federal CBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*4*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_prior_itc_macrs_5", "5-yr MACRS federal depreciation basis prior ITC reduction", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_fed_qual_macrs_5", "5-yr MACRS depreciation federal ITC adj qualifying costs", "$", "", "Depreciation", "*", "", "" }, +/*5*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_qual_macrs_5", "5-yr MACRS federal percent of qualifying costs", "%", "", "Depreciation", "*", "", "" }, +/*6*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_amount_macrs_5", "5-yr MACRS depreciation ITC basis from federal percentage", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_fed_percent_macrs_5", "5-yr MACRS depreciation ITC basis disallowance from federal percentage", "$", "", "Depreciation", "*", "", "" }, +/*7*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_fixed_amount_macrs_5", "5-yr MACRS depreciation ITC basis from federal fixed amount", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_fed_fixed_macrs_5", "5-yr MACRS depreciation ITC basis disallowance from federal fixed amount", "$", "", "Depreciation", "*", "", "" }, +/*8*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_itc_sta_reduction_macrs_5", "5-yr MACRS federal basis state ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*9*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_itc_fed_reduction_macrs_5", "5-yr MACRS federal basis federal ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*10*/{ SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_after_itc_macrs_5", "5-yr MACRS federal depreciation basis after ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*11*/{ SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_first_year_bonus_macrs_5", "5-yr MACRS federal first year bonus depreciation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_macrs_5", "5-yr MACRS federal depreciation basis", "$", "", "Depreciation", "*", "", "" }, + +/*1*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_macrs_15", "15-yr MACRS federal percent of total depreciable basis", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_macrs_15", "15-yr MACRS depreciation federal and federal allocation", "$", "", "Depreciation", "*", "", "" }, +/*2*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_ibi_reduc_macrs_15", "15-yr MACRS federal IBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*3*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_cbi_reduc_macrs_15", "15-yr MACRS federal CBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*4*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_prior_itc_macrs_15", "15-yr MACRS federal depreciation basis prior ITC reduction", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_fed_qual_macrs_15", "15-yr MACRS depreciation federal ITC adj qualifying costs", "$", "", "Depreciation", "*", "", "" }, +/*5*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_qual_macrs_15", "15-yr MACRS federal percent of qualifying costs", "%", "", "Depreciation", "*", "", "" }, +/*6*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_amount_macrs_15", "15-yr MACRS depreciation ITC basis from federal percentage", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_fed_percent_macrs_15", "15-yr MACRS depreciation ITC basis disallowance from federal percentage", "$", "", "Depreciation", "*", "", "" }, +/*7*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_fixed_amount_macrs_15", "15-yr MACRS depreciation ITC basis from federal fixed amount", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_fed_fixed_macrs_15", "15-yr MACRS depreciation ITC basis disallowance from federal fixed amount", "$", "", "Depreciation", "*", "", "" }, +/*8*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_itc_sta_reduction_macrs_15", "15-yr MACRS federal basis state ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*9*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_itc_fed_reduction_macrs_15", "15-yr MACRS federal basis federal ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*10*/{ SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_after_itc_macrs_15", "15-yr MACRS federal depreciation basis after ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*11*/{ SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_first_year_bonus_macrs_15", "15-yr MACRS federal first year bonus depreciation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_macrs_15", "15-yr MACRS federal depreciation basis", "$", "", "Depreciation", "*", "", "" }, + +/*1*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_sl_5", "5-yr straight line federal percent of total depreciable basis", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_sl_5", "5-yr straight line depreciation federal and federal allocation", "$", "", "Depreciation", "*", "", "" }, +/*2*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_ibi_reduc_sl_5", "5-yr straight line federal IBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*3*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_cbi_reduc_sl_5", "5-yr straight line federal CBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*4*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_prior_itc_sl_5", "5-yr straight line federal depreciation basis prior ITC reduction", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_fed_qual_sl_5", "5-yr straight line depreciation federal ITC adj qualifying costs", "$", "", "Depreciation", "*", "", "" }, +/*5*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_qual_sl_5", "5-yr straight line federal percent of qualifying costs", "%", "", "Depreciation", "*", "", "" }, +/*6*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_amount_sl_5", "5-yr straight line depreciation ITC basis from federal percentage", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_fed_percent_sl_5", "5-yr straight line depreciation ITC basis disallowance from federal percentage", "$", "", "Depreciation", "*", "", "" }, +/*7*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_fixed_amount_sl_5", "5-yr straight line depreciation ITC basis from federal fixed amount", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_fed_fixed_sl_5", "5-yr straight line depreciation ITC basis disallowance from federal fixed amount", "$", "", "Depreciation", "*", "", "" }, +/*8*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_itc_sta_reduction_sl_5", "5-yr straight line federal basis state ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*9*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_itc_fed_reduction_sl_5", "5-yr straight line federal basis federal ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*10*/{ SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_after_itc_sl_5", "5-yr straight line federal depreciation basis after ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*11*/{ SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_first_year_bonus_sl_5", "5-yr straight line federal first year bonus depreciation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_sl_5", "5-yr straight line federal depreciation basis", "$", "", "Depreciation", "*", "", "" }, + +/*1*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_sl_15", "15-yr straight line federal percent of total depreciable basis", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_sl_15", "15-yr straight line depreciation federal and federal allocation", "$", "", "Depreciation", "*", "", "" }, +/*2*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_ibi_reduc_sl_15", "15-yr straight line federal IBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*3*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_cbi_reduc_sl_15", "15-yr straight line federal CBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*4*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_prior_itc_sl_15", "15-yr straight line federal depreciation basis prior ITC reduction", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_fed_qual_sl_15", "15-yr straight line depreciation federal ITC adj qualifying costs", "$", "", "Depreciation", "*", "", "" }, +/*5*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_qual_sl_15", "15-yr straight line federal percent of qualifying costs", "%", "", "Depreciation", "*", "", "" }, +/*6*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_amount_sl_15", "15-yr straight line depreciation ITC basis from federal percentage", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_fed_percent_sl_15", "15-yr straight line depreciation ITC basis disallowance from federal percentage", "$", "", "Depreciation", "*", "", "" }, +/*7*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_fixed_amount_sl_15", "15-yr straight line depreciation ITC basis from federal fixed amount", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_fed_fixed_sl_15", "15-yr straight line depreciation ITC basis disallowance from federal fixed amount", "$", "", "Depreciation", "*", "", "" }, +/*8*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_itc_sta_reduction_sl_15", "15-yr straight line federal basis state ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*9*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_itc_fed_reduction_sl_15", "15-yr straight line federal basis federal ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*10*/{ SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_after_itc_sl_15", "15-yr straight line federal depreciation basis after ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*11*/{ SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_first_year_bonus_sl_15", "15-yr straight line federal first year bonus depreciation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_sl_15", "15-yr straight line federal depreciation basis", "$", "", "Depreciation", "*", "", "" }, + +/*1*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_sl_20", "20-yr straight line federal percent of total depreciable basis", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_sl_20", "20-yr straight line depreciation federal and federal allocation", "$", "", "Depreciation", "*", "", "" }, +/*2*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_ibi_reduc_sl_20", "20-yr straight line federal IBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*3*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_cbi_reduc_sl_20", "20-yr straight line federal CBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*4*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_prior_itc_sl_20", "20-yr straight line federal depreciation basis prior ITC reduction", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_fed_qual_sl_20", "20-yr straight line depreciation federal ITC adj qualifying costs", "$", "", "Depreciation", "*", "", "" }, +/*5*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_qual_sl_20", "20-yr straight line federal percent of qualifying costs", "%", "", "Depreciation", "*", "", "" }, +/*6*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_amount_sl_20", "20-yr straight line depreciation ITC basis from federal percentage", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_fed_percent_sl_20", "20-yr straight line depreciation ITC basis disallowance from federal percentage", "$", "", "Depreciation", "*", "", "" }, +/*7*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_fixed_amount_sl_20", "20-yr straight line depreciation ITC basis from federal fixed amount", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_fed_fixed_sl_20", "20-yr straight line depreciation ITC basis disallowance from federal fixed amount", "$", "", "Depreciation", "*", "", "" }, +/*8*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_itc_sta_reduction_sl_20", "20-yr straight line federal basis state ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*9*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_itc_fed_reduction_sl_20", "20-yr straight line federal basis federal ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*10*/{ SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_after_itc_sl_20", "20-yr straight line federal depreciation basis after ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*11*/{ SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_first_year_bonus_sl_20", "20-yr straight line federal first year bonus depreciation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_sl_20", "20-yr straight line federal depreciation basis", "$", "", "Depreciation", "*", "", "" }, + +/*1*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_sl_39", "39-yr straight line federal percent of total depreciable basis", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_sl_39", "39-yr straight line depreciation federal and federal allocation", "$", "", "Depreciation", "*", "", "" }, +/*2*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_ibi_reduc_sl_39", "39-yr straight line federal IBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*3*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_cbi_reduc_sl_39", "39-yr straight line federal CBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*4*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_prior_itc_sl_39", "39-yr straight line federal depreciation basis prior ITC reduction", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_fed_qual_sl_39", "39-yr straight line depreciation federal ITC adj qualifying costs", "$", "", "Depreciation", "*", "", "" }, +/*5*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_qual_sl_39", "39-yr straight line federal percent of qualifying costs", "%", "", "Depreciation", "*", "", "" }, +/*6*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_amount_sl_39", "39-yr straight line depreciation ITC basis from federal percentage", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_fed_percent_sl_39", "39-yr straight line depreciation ITC basis disallowance from federal percentage", "$", "", "Depreciation", "*", "", "" }, +/*7*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_fixed_amount_sl_39", "39-yr straight line depreciation ITC basis from federal fixed amount", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_fed_fixed_sl_39", "39-yr straight line depreciation ITC basis disallowance from federal fixed amount", "$", "", "Depreciation", "*", "", "" }, +/*8*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_itc_sta_reduction_sl_39", "39-yr straight line federal basis state ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*9*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_itc_fed_reduction_sl_39", "39-yr straight line federal basis federal ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*10*/{ SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_after_itc_sl_39", "39-yr straight line federal depreciation basis after ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*11*/{ SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_first_year_bonus_sl_39", "39-yr straight line federal first year bonus depreciation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_sl_39", "39-yr straight line federal depreciation basis", "$", "", "Depreciation", "*", "", "" }, + +/*1*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_custom", "Custom straight line federal percent of total depreciable basis", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_custom", "Custom straight line depreciation federal and federal allocation", "$", "", "Depreciation", "*", "", "" }, +/*2*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_ibi_reduc_custom", "Custom straight line federal IBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*3*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_cbi_reduc_custom", "Custom straight line federal CBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*4*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_prior_itc_custom", "Custom straight line federal depreciation basis prior ITC reduction", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_fed_qual_custom", "Custom straight line depreciation federal ITC adj qualifying costs", "$", "", "Depreciation", "*", "", "" }, +/*5*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_qual_custom", "Custom straight line federal percent of qualifying costs", "%", "", "Depreciation", "*", "", "" }, +/*6*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_amount_custom", "Custom straight line depreciation ITC basis from federal percentage", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_fed_percent_custom", "Custom straight line depreciation ITC basis disallowance from federal percentage", "$", "", "Depreciation", "*", "", "" }, +/*7*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_fixed_amount_custom", "Custom straight line depreciation ITC basis from federal fixed amount", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_fed_fixed_custom", "Custom straight line depreciation ITC basis disallowance from federal fixed amount", "$", "", "Depreciation", "*", "", "" }, +/*8*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_itc_sta_reduction_custom", "Custom straight line federal basis state ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*9*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_itc_fed_reduction_custom", "Custom straight line federal basis federal ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*10*/{ SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_after_itc_custom", "Custom straight line federal depreciation basis after ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*11*/{ SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_first_year_bonus_custom", "Custom straight line federal first year bonus depreciation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_custom", "Custom straight line federal depreciation basis", "$", "", "Depreciation", "*", "", "" }, + +/*1*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_total", "Total federal percent of total depreciable basis", "%", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_alloc_total", "Total depreciation federal and federal allocation", "$", "", "Depreciation", "*", "", "" }, +/*2*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_ibi_reduc_total", "Total federal IBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*3*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_cbi_reduc_total", "Total federal CBI reduction", "$", "", "Depreciation", "*", "", "" }, +/*4*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_prior_itc_total", "Total federal depreciation basis prior ITC reduction", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_fed_qual_total", "Total federal ITC adj qualifying costs", "$", "", "Depreciation", "*", "", "" }, +/*5*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_qual_total", "Total federal percent of qualifying costs", "%", "", "Depreciation", "*", "", "" }, +/*6*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_percent_amount_total", "Total depreciation ITC basis from federal percentage", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_fed_percent_total", "Total depreciation ITC basis disallowance from federal percentage", "$", "", "Depreciation", "*", "", "" }, +/*7*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_fixed_amount_total", "Total depreciation ITC basis from federal fixed amount", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_disallow_fed_fixed_total", "Total depreciation ITC basis disallowance from federal fixed amount", "$", "", "Depreciation", "*", "", "" }, +/*8*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_itc_sta_reduction_total", "Total federal basis state ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*9*/ { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_itc_fed_reduction_total", "Total federal basis federal ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*10*/{ SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_after_itc_total", "Total federal depreciation basis after ITC reduction", "$", "", "Depreciation", "*", "", "" }, +/*11*/{ SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_first_year_bonus_total", "Total federal first year bonus depreciation", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_total", "Total federal depreciation basis", "$", "", "Depreciation", "*", "", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "itc_fed_percent_total", "Federal ITC percent total", "$", "", "Tax Credits", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "itc_fed_fixed_total", "Federal ITC fixed total", "$", "", "Tax Credits", "*", "", "" }, + +/* depreciation bases method - added with version 4.1 0=5-yrMacrs, 1=proportional */ + { SSC_INPUT, SSC_NUMBER, "depr_stabas_method", "Method of state depreciation reduction", "", "0=5yr MACRS,1=Proportional", "Depreciation", "?=0", "INTEGER,MIN=0,MAX=1", "" }, + { SSC_INPUT, SSC_NUMBER, "depr_fedbas_method", "Method of federal depreciation reduction", "", "0=5yr MACRS,1=Proportional", "Depreciation", "?=0", "INTEGER,MIN=0,MAX=1", "" }, + +/* State depreciation table */ + { SSC_OUTPUT, SSC_NUMBER, "depr_stabas_total", "Total state depreciation basis", "$", "", "Depreciation", "*", "", "" }, + +/* Federal depreciation table */ + { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_macrs_5", "5-yr MACRS federal depreciation basis", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_macrs_15", "15-yr MACRS federal depreciation basis", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_sl_5", "5-yr straight line federal depreciation basis", "$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_sl_15", "15-yr straight line federal depreciation basis","$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_sl_20", "20-yr straight line federal depreciation basis","$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_sl_39", "39-yr straight line federal depreciation basis","$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_custom", "Custom federal depreciation basis","$", "", "Depreciation", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "depr_fedbas_total", "Total federal depreciation basis", "$", "", "Depreciation", "*", "", "" }, + +/* State taxes */ + /* intermediate outputs for validation */ + { SSC_OUTPUT, SSC_NUMBER, "cash_for_debt_service", "Cash available for debt service (CAFDS)", "$", "", "Debt Sizing", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "pv_cafds", "Present value of CAFDS","$", "", "Debt Sizing", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "size_of_debt", "Size of debt", "$", "", "Debt Sizing", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "size_of_equity", "Equity", "$", "", "Debt Sizing", "*", "", "" }, + +/* model outputs */ + { SSC_OUTPUT, SSC_NUMBER, "cf_length", "Number of periods in cashflow", "", "", "Metrics", "*", "INTEGER", "" }, + { SSC_OUTPUT, SSC_NUMBER, "ppa_price", "PPA price in first year", "cents/kWh", "", "Metrics", "*", "", "" }, +/* Production - input as energy_net above */ + +/* Partial Income Statement: Project */ + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_net", "Electricity to grid net", "kWh", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales", "Electricity to grid", "kWh", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_purchases", "Electricity from grid", "kWh", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_without_battery", "Electricity generated without storage", "kWh", "", "Cash Flow Electricity", "", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_ppa_price", "PPA price", "cents/kWh", "", "Cash Flow Revenues", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_value", "PPA revenue", "$", "", "Cash Flow Revenues", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_thermal_value", "Thermal revenue", "$", "", "Cash Flow Revenues", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_om_fixed_expense", "O&M fixed expense", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_production_expense", "O&M production-based expense", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_capacity_expense", "O&M capacity-based expense", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_fixed1_expense", "O&M battery fixed expense", "$", "", "Cash Flow Expenses", "", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_production1_expense", "O&M battery production-based expense", "$", "", "Cash Flow Expenses", "", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_capacity1_expense", "O&M battery capacity-based expense", "$", "", "Cash Flow Expenses", "", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_fixed2_expense", "O&M fuel cell fixed expense", "$", "", "Cash Flow Expenses", "", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_production2_expense", "O&M fuel cell production-based expense", "$", "", "Cash Flow Expenses", "", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_capacity2_expense", "O&M fuel cell capacity-based expense", "$", "", "Cash Flow Expenses", "", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_fuel_expense", "Fuel expense", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_om_elec_price_for_heat_techs", "Electricity expense in heat models", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_om_opt_fuel_1_expense", "Feedstock biomass expense", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_opt_fuel_2_expense", "Feedstock coal expense", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_property_tax_assessed_value", "Property tax net assessed value", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_property_tax_expense", "Property tax expense", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_insurance_expense", "Insurance expense", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_operating_expenses", "Total operating expenses", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_net_salvage_value", "Salvage value", "$", "", "Cash Flow Revenues", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_total_revenue", "Total revenue", "$", "", "Cash Flow Revenues", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_ebitda", "EBITDA", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_reserve_debtservice", "Reserves debt service balance", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_reserve_om", "Reserves working capital balance ", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_reserve_receivables", "Reserves receivables balance", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_reserve_equip1", "Reserves major equipment 1 balance", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_reserve_equip2", "Reserves major equipment 2 balance", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_reserve_equip3", "Reserves major equipment 3 balance", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_reserve_total", "Reserves total reserves balance", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_reserve_interest", "Interest earned on reserves", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_funding_debtservice", "Reserves debt service funding", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_funding_om", "Reserves working capital funding", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_funding_receivables", "Reserves receivables funding", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_funding_equip1", "Reserves major equipment 1 funding", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_funding_equip2", "Reserves major equipment 2 funding", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_funding_equip3", "Reserves major equipment 3 funding", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_disbursement_debtservice", "Reserves debt service disbursement ", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_disbursement_om", "Reserves working capital disbursement", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_disbursement_receivables", "Reserves receivables disbursement", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_disbursement_equip1", "Reserves major equipment 1 disbursement", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_disbursement_equip2", "Reserves major equipment 2 disbursement", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_disbursement_equip3", "Reserves major equipment 3 disbursement", "$", "", "Cash Flow Reserves", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_cash_for_ds", "Cash available for debt service (CAFDS)", "$", "", "Cash Flow Debt Sizing", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_pv_interest_factor", "Present value interest factor for CAFDS", "", "", "Cash Flow Debt Repayment", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_pv_cash_for_ds", "Present value of CAFDS", "$", "", "Cash Flow Debt Sizing", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_debt_size", "Size of debt", "$", "", "Cash Flow Debt Sizing", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_debt_balance", "Debt balance", "$", "", "Cash Flow Debt Repayment", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_debt_payment_interest", "Debt interest payment", "$", "", "Cash Flow Debt Repayment", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_debt_payment_principal", "Debt principal payment", "$", "", "Cash Flow Debt Repayment", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_debt_payment_total", "Debt total payment", "$", "", "Cash Flow Debt Repayment", "*", "LENGTH_EQUAL=cf_length", "" }, + + // Project cash flow + + { SSC_OUTPUT, SSC_ARRAY, "cf_project_operating_activities", "Cash flow from operating activities", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "purchase_of_property", "Purchase of property", "$", "", "Cash Flow Total and Returns", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_dsra", "Reserve (increase)/decrease debt service ", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_wcra", "Reserve (increase)/decrease working capital", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_receivablesra", "Reserve (increase)/decrease receivables", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_me1ra", "Reserve (increase)/decrease major equipment 1", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_me2ra", "Reserve (increase)/decrease major equipment 2", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_me3ra", "Reserve (increase)/decrease major equipment 3", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_ra", "Reserve (increase)/decrease total reserve account", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_me1cs", "Reserve capital spending major equipment 1", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_me2cs", "Reserve capital spending major equipment 2", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_me3cs", "Reserve capital spending major equipment 3", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_mecs", "Reserve capital spending major equipment total", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_investing_activities", "Cash flow from investing activities", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "issuance_of_equity", "Issuance of equity", "$", "", "Cash Flow Total and Returns", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_financing_activities", "Cash flow from financing activities", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_pretax_cashflow", "Total pre-tax cash flow", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + +// Project returns + { SSC_OUTPUT, SSC_ARRAY, "cf_project_return_pretax", "Total pre-tax returns", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_return_pretax_irr", "Pre-tax cumulative IRR", "%", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_return_pretax_npv", "Pre-tax cumulative NPV", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_project_return_aftertax_cash", "Total after-tax cash returns", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_return_aftertax", "Total after-tax returns", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_return_aftertax_irr", "After-tax cumulative IRR", "%", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_return_aftertax_max_irr", "After-tax project maximum IRR", "%", "", "Cash Flow After Tax", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_project_return_aftertax_npv", "After-tax cumulative NPV", "$", "", "Cash Flow Total and Returns", "*", "LENGTH_EQUAL=cf_length", "" }, + + // metrics table + { SSC_OUTPUT, SSC_NUMBER, "project_return_aftertax_irr", "IRR Internal rate of return", "%", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "project_return_aftertax_npv", "NPV Net present value", "$", "", "Metrics", "*", "", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_annual_costs", "Annual costs", "$", "", "LCOE calculations", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "npv_annual_costs", "Present value of annual costs", "$", "", "LCOE calculations", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "adjusted_installed_cost", "Initial cost less cash incentives", "$", "", "", "*", "", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "min_dscr", "Minimum DSCR", "", "", "DSCR", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_pretax_dscr", "DSCR (pre-tax)", "", "", "DSCR", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_INPUT, SSC_ARRAY, "system_pre_curtailment_kwac", "System power before grid curtailment", "kW", "System generation" "", "System Output", "", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_curtailed", "Electricity curtailed", "kWh", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_curtailment_value", "Curtailment payment revenue", "$", "", "Cash Flow Revenues", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_capacity_payment", "Capacity payment revenue", "$", "", "Cash Flow Revenues", "*", "LENGTH_EQUAL=cf_length", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "npv_curtailment_revenue", "Present value of curtailment payment revenue", "$", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "npv_capacity_revenue", "Present value of capacity payment revenue", "$", "", "Metrics", "*", "", "" }, + // only count toward revenue if user selected + { SSC_OUTPUT, SSC_NUMBER, "npv_fed_pbi_income", "Present value of federal PBI income", "$", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "npv_sta_pbi_income", "Present value of state PBI income", "$", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "npv_uti_pbi_income", "Present value of utility PBI income", "$", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "npv_oth_pbi_income", "Present value of other PBI income", "$", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "npv_salvage_value", "Present value of salvage value", "$", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "npv_thermal_value", "Present value of thermal value", "$", "", "Metrics", "*", "", "" }, + +var_info_invalid }; + +extern var_info + vtab_update_tech_outputs[], + vtab_ppa_inout[], + vtab_standard_financial[], + vtab_oandm[], + vtab_equip_reserve[], + vtab_tax_credits[], + vtab_depreciation_inputs[], + vtab_depreciation_outputs[], + vtab_payment_incentives[], + vtab_debt[], + vtab_financial_metrics[], + vtab_financial_capacity_payments[], + vtab_financial_grid[], + vtab_fuelcell_replacement_cost[], + vtab_lcos_inputs[], + vtab_battery_replacement_cost[], + vtab_tod_dispatch_periods[]; + +enum { + CF_energy_net, + CF_energy_curtailed, + CF_energy_value, + CF_thermal_value, + CF_curtailment_value, + CF_capacity_payment, + CF_ppa_price, + + CF_om_fixed_expense, + CF_om_production_expense, + CF_om_capacity_expense, + CF_om_fixed1_expense, + CF_om_production1_expense, + CF_om_capacity1_expense, + CF_om_fixed2_expense, + CF_om_production2_expense, + CF_om_capacity2_expense, + CF_om_fuel_expense, + CF_om_elec_price_for_heat_techs, + + CF_om_opt_fuel_2_expense, + CF_om_opt_fuel_1_expense, + CF_om_hybrid_sum, + + CF_land_lease_expense, + + CF_federal_tax_frac, + CF_state_tax_frac, + CF_effective_tax_frac, + + CF_property_tax_assessed_value, + CF_property_tax_expense, + CF_insurance_expense, + CF_operating_expenses, + CF_net_salvage_value, + CF_total_revenue, + CF_ebitda, + + CF_reserve_debtservice, + CF_funding_debtservice, + CF_disbursement_debtservice, + CF_reserve_om, + CF_funding_om, + CF_disbursement_om, + CF_reserve_receivables, + CF_funding_receivables, + CF_disbursement_receivables, + CF_reserve_equip1, + CF_funding_equip1, + CF_disbursement_equip1, + CF_reserve_equip2, + CF_funding_equip2, + CF_disbursement_equip2, + CF_reserve_equip3, + CF_funding_equip3, + CF_disbursement_equip3, + CF_reserve_total, + CF_reserve_interest, + + + // Project cash flow + CF_project_operating_activities, + CF_project_dsra, + CF_project_wcra, + CF_project_receivablesra, + CF_project_me1ra, + CF_project_me2ra, + CF_project_me3ra, + CF_project_ra, + CF_project_me1cs, + CF_project_me2cs, + CF_project_me3cs, + CF_project_mecs, + CF_project_investing_activities, + CF_project_financing_activities, + CF_pretax_cashflow, + + + // Project returns + CF_project_return_pretax, + CF_project_return_pretax_irr, + CF_project_return_pretax_npv, + CF_project_return_aftertax_cash, + CF_project_return_aftertax_itc, + CF_project_return_aftertax_ptc, + CF_project_return_aftertax_tax, + CF_project_return_aftertax, + CF_project_return_aftertax_irr, + CF_project_return_aftertax_max_irr, + CF_project_return_aftertax_npv, + + + CF_pv_interest_factor, + CF_cash_for_ds, + CF_pv_cash_for_ds, + CF_debt_size, + + CF_debt_balance, + CF_debt_payment_interest, + CF_debt_payment_principal, + CF_debt_payment_total, + + CF_pbi_fed, + CF_pbi_sta, + CF_pbi_uti, + CF_pbi_oth, + CF_pbi_total, + CF_pbi_statax_total, + CF_pbi_fedtax_total, + + CF_ptc_fed, + CF_ptc_sta, + CF_aftertax_ptc, + + CF_macrs_5_frac, + CF_macrs_15_frac, + CF_sl_5_frac, + CF_sl_15_frac, + CF_sl_20_frac, + CF_sl_39_frac, + CF_custom_frac, + + CF_stadepr_macrs_5, + CF_stadepr_macrs_15, + CF_stadepr_sl_5, + CF_stadepr_sl_15, + CF_stadepr_sl_20, + CF_stadepr_sl_39, + CF_stadepr_custom, + CF_stadepr_me1, + CF_stadepr_me2, + CF_stadepr_me3, + CF_stadepr_total, + CF_statax_income_prior_incentives, + CF_statax_taxable_incentives, + CF_statax_income_with_incentives, + CF_statax, + + CF_feddepr_macrs_5, + CF_feddepr_macrs_15, + CF_feddepr_sl_5, + CF_feddepr_sl_15, + CF_feddepr_sl_20, + CF_feddepr_sl_39, + CF_feddepr_custom, + CF_feddepr_me1, + CF_feddepr_me2, + CF_feddepr_me3, + CF_feddepr_total, + CF_fedtax_income_prior_incentives, + CF_fedtax_taxable_incentives, + CF_fedtax_income_with_incentives, + CF_fedtax, + + CF_me1depr_total, + CF_me2depr_total, + CF_me3depr_total, + + CF_sta_depr_sched, + CF_sta_depreciation, + CF_sta_incentive_income_less_deductions, + CF_sta_taxable_income_less_deductions, +// CF_sta_tax_savings, + + CF_fed_depr_sched, + CF_fed_depreciation, + CF_fed_incentive_income_less_deductions, + CF_fed_taxable_income_less_deductions, +// CF_fed_tax_savings, + + CF_degradation, + + CF_Recapitalization, + CF_Recapitalization_boolean, + + CF_Annual_Costs, + CF_pretax_dscr, + + CF_battery_replacement_cost_schedule, + CF_battery_replacement_cost, + + CF_fuelcell_replacement_cost_schedule, + CF_fuelcell_replacement_cost, + + CF_utility_bill, + + CF_energy_sales, + CF_energy_purchases, + + CF_elec_purchases_for_heat_techs, + + CF_energy_without_battery, + + CF_battery_discharged, + CF_fuelcell_discharged, + + CF_energy_charged_grid, + CF_energy_charged_pv, + CF_energy_discharged, + CF_charging_cost_pv, + CF_charging_cost_grid, + CF_charging_cost_grid_month, + CF_om_cost_lcos, + CF_salvage_cost_lcos, + CF_investment_cost_lcos, + CF_annual_cost_lcos, + CF_util_escal_rate, + + + // SAM 1038 + CF_itc_fed_amount, + CF_itc_fed_percent_fraction, + CF_itc_fed_percent_amount, + CF_itc_fed_percent_maxvalue, + CF_itc_fed, + CF_itc_sta_amount, + CF_itc_sta_percent_fraction, + CF_itc_sta_percent_amount, + CF_itc_sta_percent_maxvalue, + CF_itc_sta, + CF_itc_total, + + + CF_max, + }; + + + +class cm_singleowner_heat : public compute_module +{ +private: + util::matrix_t cf; + util::matrix_t cf_lcos; + dispatch_calculations m_disp_calcs; + hourly_energy_calculation hourly_energy_calcs; + + +public: + cm_singleowner_heat() + { + add_var_info(vtab_ppa_inout ); + add_var_info( vtab_standard_financial ); + add_var_info( vtab_oandm ); + add_var_info( vtab_equip_reserve ); + add_var_info( vtab_tax_credits ); + add_var_info(vtab_depreciation_inputs ); + add_var_info(vtab_depreciation_outputs ); + add_var_info( vtab_payment_incentives ); + add_var_info( vtab_debt ); + add_var_info( vtab_financial_metrics ); + add_var_info( _cm_vtab_singleowner_heat ); + add_var_info(vtab_battery_replacement_cost); + add_var_info(vtab_fuelcell_replacement_cost); + add_var_info(vtab_financial_capacity_payments); + add_var_info(vtab_financial_grid); + add_var_info(vtab_lcos_inputs); + add_var_info(vtab_update_tech_outputs); + add_var_info(vtab_tod_dispatch_periods); + add_var_info(vtab_utility_rate_common); + add_var_info(vtab_hybrid_fin_om); + add_var_info(vtab_update_tech_outputs); + } + + void exec( ) + { + int i = 0; + + if (is_assigned("en_electricity_rates") && as_number("en_electricity_rates") == 0 && as_number("ppa_soln_mode") == 0) + throw exec_error("singleowner_heat", "PPA price from which to calculate parasitic load costs is not specified. Check inputs for Revenue and Electricity Purchases."); + + + // cash flow initialization + int nyears = as_integer("analysis_period"); + cf.resize_fill(CF_max, nyears + 1, 0.0); + cf_lcos.resize_fill(CF_max, nyears + 1, 0.0); + // assign inputs + double inflation_rate = as_double("inflation_rate")*0.01; + double ppa_escalation = as_double("ppa_escalation")*0.01; + double disc_real = as_double("real_discount_rate")*0.01; +// double federal_tax_rate = as_double("federal_tax_rate")*0.01; +// double state_tax_rate = as_double("state_tax_rate")*0.01; + size_t count; + ssc_number_t* arrp; + arrp = as_array("federal_tax_rate", &count); + if (count > 0) + { + if (count == 1) // single value input + { + for (i = 0; i < nyears; i++) + cf.at(CF_federal_tax_frac, i + 1) = arrp[0] * 0.01; + } + else // schedule + { + for (i = 0; i < nyears && i < (int)count; i++) + cf.at(CF_federal_tax_frac, i + 1) = arrp[i] * 0.01; + } + } + arrp = as_array("state_tax_rate", &count); + if (count > 0) + { + if (count == 1) // single value input + { + for (i = 0; i < nyears; i++) + cf.at(CF_state_tax_frac, i + 1) = arrp[0] * 0.01; + } + else // schedule + { + for (i = 0; i < nyears && i < (int)count; i++) + cf.at(CF_state_tax_frac, i + 1) = arrp[i] * 0.01; + } + } + for (i = 0; i <= nyears; i++) + cf.at(CF_effective_tax_frac, i) = cf.at(CF_state_tax_frac, i) + + (1.0 - cf.at(CF_state_tax_frac, i))*cf.at(CF_federal_tax_frac, i); + + if (is_assigned("annual_thermal_value")) + { + arrp = as_array("annual_thermal_value", &count); + i = 0; + while (i < nyears && i < (int)(count-1)) + { + cf.at(CF_thermal_value, i + 1) = (double)arrp[i+1]; + i++; + } + } + + if (is_assigned("cf_hybrid_om_sum")) { + arrp = as_array("cf_hybrid_om_sum", &count); + for (i = 0; i < count && i <= nyears; i++) + cf.at(CF_om_hybrid_sum, i) = arrp[i]; + } + + + double nom_discount_rate = (1+inflation_rate)*(1+disc_real)-1; + + // In conjunction with SAM - take installed costs and salestax costs (for deducting if necessary) + double cost_prefinancing = as_double("total_installed_cost"); + + double annual_electricity_consumption_heat_model = as_double("annual_electricity_consumption"); + + // use named range names for variables whenever possible + double nameplate = as_double("system_capacity"); + double year1_fuel_use = as_double("annual_fuel_usage"); // kWht + std::vector fuel_use; + if ((as_integer("system_use_lifetime_output") == 1) && is_assigned("annual_fuel_usage_lifetime")) { + fuel_use = as_vector_double("annual_fuel_usage_lifetime"); + if (fuel_use.size() != (size_t)(nyears )) { + throw exec_error("singleowner_heat", util::format("fuel usage years (%d) not equal to analysis period years (%d).", (int)fuel_use.size(), nyears)); + } + } + else { + for (size_t y = 0; y < (size_t)(nyears); y++) { + fuel_use.push_back(year1_fuel_use); + } + } + + + double assessed_frac = as_double("prop_tax_cost_assessed_percent")*0.01; + double salvage_value_frac = as_double("salvage_percentage")*0.01; + double salvage_value = salvage_value_frac * cost_prefinancing; + + double cost_debt_closing = as_double("cost_debt_closing"); + double cost_debt_fee_frac = as_double("cost_debt_fee")*0.01; + double cost_other_financing = as_double("cost_other_financing"); + double cost_debt_upfront; + + + double constr_total_financing = as_double("construction_financing_cost"); + + int ppa_mode = as_integer("ppa_soln_mode"); + + bool constant_dscr_mode = (as_integer("debt_option")==1); + bool constant_principal = (as_integer("payment_option") == 1);; + // log(util::format("debt option=%d and constant dscr mode=%s.", +// as_integer("debt_option"), (constant_dscr_mode ? "true":"false")), +// SSC_WARNING); + + + // general financial expenses and incentives - stdlib? + // precompute expenses from annual schedules or value+escalation + escal_or_annual( CF_om_fixed_expense, nyears, "om_fixed", inflation_rate, 1.0, false, as_double("om_fixed_escal")*0.01 ); + escal_or_annual( CF_om_production_expense, nyears, "om_production", inflation_rate, 0.001, false, as_double("om_production_escal")*0.01 ); + escal_or_annual( CF_om_capacity_expense, nyears, "om_capacity", inflation_rate, 1.0, false, as_double("om_capacity_escal")*0.01 ); + escal_or_annual( CF_om_fuel_expense, nyears, "om_fuel_cost", inflation_rate, as_double("system_heat_rate")*0.001, false, as_double("om_fuel_cost_escal")*0.01 ); + + escal_or_annual(CF_om_elec_price_for_heat_techs, nyears, "om_elec_price_for_heat_techs", inflation_rate, 1.0, false, as_double("om_elec_price_for_heat_techs_escal")*0.01 ); + + escal_or_annual( CF_om_opt_fuel_1_expense, nyears, "om_opt_fuel_1_cost", inflation_rate, 1.0, false, as_double("om_opt_fuel_1_cost_escal")*0.01 ); + escal_or_annual( CF_om_opt_fuel_2_expense, nyears, "om_opt_fuel_2_cost", inflation_rate, 1.0, false, as_double("om_opt_fuel_2_cost_escal")*0.01 ); + + ssc_number_t total_land_area = as_double("land_area"); + escal_or_annual(CF_land_lease_expense, nyears, "om_land_lease", inflation_rate, total_land_area, false, as_double("om_land_lease_escal") * 0.01); + + double om_opt_fuel_1_usage = as_double("om_opt_fuel_1_usage"); + double om_opt_fuel_2_usage = as_double("om_opt_fuel_2_usage"); + + // additional o and m sub types (e.g. batteries and fuel cells) + int add_om_num_types = as_integer("add_om_num_types"); + ssc_number_t nameplate1 = 0; + ssc_number_t nameplate2 = 0; + std::vector battery_discharged(nyears,0); + std::vector fuelcell_discharged(nyears,0); + + + if (is_assigned("is_hybrid") && as_integer("is_hybrid") == 1) { + // already added in additional o and m - this is only necessary if dispatch and replacments at top level as when fuel cell and battery dispatch combined + } + else { + if (add_om_num_types > 0) //PV Battery + { + escal_or_annual(CF_om_fixed1_expense, nyears, "om_batt_fixed_cost", inflation_rate, 1.0, false, as_double("om_fixed_escal") * 0.01); + escal_or_annual(CF_om_production1_expense, nyears, "om_batt_variable_cost", inflation_rate, 0.001, false, as_double("om_production_escal") * 0.01); //$/MWh + escal_or_annual(CF_om_capacity1_expense, nyears, "om_batt_capacity_cost", inflation_rate, 1.0, false, as_double("om_capacity_escal") * 0.01); + nameplate1 = as_number("om_batt_nameplate"); + if (as_integer("en_batt") == 1 || as_integer("en_standalone_batt") == 1) + battery_discharged = as_vector_double("batt_annual_discharge_energy"); + } + if (battery_discharged.size() == 1) { // ssc #992 + double first_val = battery_discharged[0]; + battery_discharged.resize(nyears, first_val); + } + if (battery_discharged.size() != nyears) + throw exec_error("singleowner_heat", util::format("battery_discharged size (%d) incorrect", (int)battery_discharged.size())); + + if (add_om_num_types > 1) + { + escal_or_annual(CF_om_fixed2_expense, nyears, "om_fuelcell_fixed_cost", inflation_rate, 1.0, false, as_double("om_fixed_escal") * 0.01); + escal_or_annual(CF_om_production2_expense, nyears, "om_fuelcell_variable_cost", inflation_rate, 0.001, false, as_double("om_production_escal") * 0.01); + escal_or_annual(CF_om_capacity2_expense, nyears, "om_fuelcell_capacity_cost", inflation_rate, 1.0, false, as_double("om_capacity_escal") * 0.01); + nameplate2 = as_number("om_fuelcell_nameplate"); + fuelcell_discharged = as_vector_double("fuelcell_annual_energy_discharged"); + } + if (fuelcell_discharged.size() == 1) { // ssc #992 + double first_val = fuelcell_discharged[0]; + fuelcell_discharged.resize(nyears, first_val); + } + if (fuelcell_discharged.size() != nyears) + throw exec_error("singleowner_heat", util::format("fuelcell_discharged size (%d) incorrect", (int)fuelcell_discharged.size())); + + // battery cost - replacement from lifetime analysis + if (as_integer("en_batt") == 1 || as_integer("en_standalone_batt") == 1 || as_integer("en_wave_batt") == 1) { + if (as_integer("batt_replacement_option") > 0) { + ssc_number_t* batt_rep = 0; + std::vector replacement_percent; + + batt_rep = as_array("batt_bank_replacement", &count); // replacements per year calculated + + // replace at capacity percent + if (as_integer("batt_replacement_option") == 1) { + + for (i = 0; i < (int)count; i++) { + replacement_percent.push_back(100); + } + } + else {// user specified + replacement_percent = as_vector_ssc_number_t("batt_replacement_schedule_percent"); + } + double batt_cap = as_double("batt_computed_bank_capacity"); + // updated 10/17/15 per 10/14/15 meeting + escal_or_annual(CF_battery_replacement_cost_schedule, nyears, "om_batt_replacement_cost", inflation_rate, batt_cap, false, as_double("om_replacement_cost_escal") * 0.01); + + for (i = 0; i < nyears && i < (int)count; i++) { + // the cash flow sheets are 1 indexed, batt_rep and replacement_percent is zero indexed + cf.at(CF_battery_replacement_cost, i + 1) = batt_rep[i] * replacement_percent[i] * 0.01 * + cf.at(CF_battery_replacement_cost_schedule, i + 1); + } + } + else + { + double batt_cap = as_double("batt_computed_bank_capacity"); + // updated 10/17/15 per 10/14/15 meeting + escal_or_annual(CF_battery_replacement_cost_schedule, nyears, "om_batt_replacement_cost", inflation_rate, batt_cap, false, as_double("om_replacement_cost_escal") * 0.01); + } + } + + // fuelcell cost - replacement from lifetime analysis + if (is_assigned("fuelcell_replacement_option") && (as_integer("fuelcell_replacement_option") > 0)) + { + ssc_number_t* fuelcell_rep = 0; + if (as_integer("fuelcell_replacement_option") == 1) + fuelcell_rep = as_array("fuelcell_replacement", &count); // replacements per year calculated + else // user specified + fuelcell_rep = as_array("fuelcell_replacement_schedule", &count); // replacements per year user-defined + escal_or_annual(CF_fuelcell_replacement_cost_schedule, nyears, "om_fuelcell_replacement_cost", inflation_rate, nameplate2, false, as_double("om_replacement_cost_escal") * 0.01); + + for (i = 0; i < nyears && i < (int)count; i++) { + cf.at(CF_fuelcell_replacement_cost, i + 1) = fuelcell_rep[i] * + cf.at(CF_fuelcell_replacement_cost_schedule, i + 1); + } + } + } + + + + + + if (is_assigned("utility_bill_w_sys")) + { + size_t ub_count; + ssc_number_t* ub_arr; + ub_arr = as_array("utility_bill_w_sys", &ub_count); + if (ub_count != (size_t)(nyears+1)) + throw exec_error("singleowner_heat", util::format("utility bill years (%d) not equal to analysis period years (%d).", (int)ub_count, nyears)); + + for ( i = 0; i <= nyears; i++) + cf.at(CF_utility_bill, i) = ub_arr[i]; + } + else + { + for (i = 0; i <= nyears; i++) + cf.at(CF_utility_bill, i) = 0; + } + + + + + // initialize energy and revenue + // initialize energy + // differs from samsim - accumulate hourly energy + //double first_year_energy = as_double("energy_net"); + double first_year_energy = 0.0; + double first_year_sales = 0.0; + double first_year_purchases = 0.0; + double first_year_elec_purchases_for_heat_techs = as_double("annual_electricity_consumption"); //[kWe-hr] + + + // degradation + // degradation starts in year 2 for single value degradation - no degradation in year 1 - degradation =1.0 + // lifetime degradation applied in technology compute modules + if (as_integer("system_use_lifetime_output") == 1) + { + for (i = 1; i <= nyears; i++) cf.at(CF_degradation, i) = 1.0; + } + else + { + size_t count_degrad = 0; + ssc_number_t *degrad = 0; + degrad = as_array("degradation", &count_degrad); + + if (count_degrad == 1) + { + for (i = 1; i <= nyears; i++) cf.at(CF_degradation, i) = pow((1.0 - degrad[0] / 100.0), i - 1); + } + else if (count_degrad > 0) + { + for (i = 0; i < nyears && i < (int)count_degrad; i++) cf.at(CF_degradation, i + 1) = (1.0 - degrad[i] / 100.0); + } + } + + hourly_energy_calcs.calculate(this); + + // dispatch + if (as_integer("system_use_lifetime_output") == 1) + { + if (first_year_elec_purchases_for_heat_techs > 0.0) + throw exec_error("singleowner_heat", "system_use_lifetime_output must be 0 if using electricity purchases for heat technologies option"); + + // hourly_enet includes all curtailment, availability + for (size_t y = 1; y <= (size_t)nyears; y++) + { + for (size_t h = 0; h<8760; h++) + { + cf.at(CF_energy_net, y) += hourly_energy_calcs.hourly_energy()[(y - 1) * 8760 + h] * cf.at(CF_degradation, y); + cf.at(CF_energy_sales, y) += hourly_energy_calcs.hourly_sales()[(y - 1) * 8760 + h] * cf.at(CF_degradation, y); + cf.at(CF_energy_purchases, y) += hourly_energy_calcs.hourly_purchases()[(y - 1) * 8760 + h] * cf.at(CF_degradation, y); + } + } + } + else + { + for (i = 0; i < 8760; i++) { + first_year_energy += hourly_energy_calcs.hourly_energy()[i]; // sum up hourly kWh to get total annual kWh first year production includes first year curtailment, availability + first_year_sales += hourly_energy_calcs.hourly_sales()[i]; + first_year_purchases += hourly_energy_calcs.hourly_purchases()[i]; + } + cf.at(CF_energy_net, 1) = first_year_energy; + cf.at(CF_energy_sales, 1) = first_year_sales; + cf.at(CF_energy_purchases, 1) = first_year_purchases; + cf.at(CF_elec_purchases_for_heat_techs, 1) = first_year_elec_purchases_for_heat_techs; + for (i = 1; i <= nyears; i++) { + cf.at(CF_energy_net, i) = first_year_energy * cf.at(CF_degradation, i); + cf.at(CF_energy_sales, i) = first_year_sales * cf.at(CF_degradation, i); + cf.at(CF_energy_purchases, i) = first_year_purchases * cf.at(CF_degradation, i); + cf.at(CF_elec_purchases_for_heat_techs, i) = first_year_elec_purchases_for_heat_techs * cf.at(CF_degradation, i); + } + + } + + if (is_assigned("gen_without_battery") && as_vector_double("gen_without_battery").size() > 0) + { + ssc_number_t first_year_energy_without_battery = 0.0; + if (as_integer("system_use_lifetime_output") == 1) + { + // hourly_enet includes all curtailment, availability + for (size_t y = 1; y <= (size_t)nyears; y++) + { + for (size_t h = 0; h < 8760; h++) + { + cf.at(CF_energy_without_battery, y) += hourly_energy_calcs.hourly_energy_without_battery()[(y - 1) * 8760 + h] * cf.at(CF_degradation, y); + } + } + } + else + { + for (i = 0; i < 8760; i++) { + first_year_energy_without_battery += hourly_energy_calcs.hourly_energy_without_battery()[i]; + } + cf.at(CF_energy_without_battery, 1) = first_year_energy_without_battery; + for (i = 1; i <= nyears; i++) { + cf.at(CF_energy_without_battery, i) = first_year_energy_without_battery * cf.at(CF_degradation, i); + } + + } + } + + // curtailed energy and revenue + ssc_number_t pre_curtailement_year1_energy = as_number("annual_energy_pre_curtailment_ac"); + size_t count_curtailment_price; + ssc_number_t *grid_curtailment_price = as_array("grid_curtailment_price", &count_curtailment_price); + ssc_number_t grid_curtailment_price_esc = as_number("grid_curtailment_price_esc") * 0.01; + // does not work with degraded energy production escal_or_annual(CF_curtailment_value, nyears, "grid_curtailment_price", 0.0, pre_curtailement_year1_energy, false, as_double("grid_curtailment_price_esc")*0.01); + // use "system_pre_curtailment_kwac" input to determine energy curtailed for lifetime output + if (as_integer("system_use_lifetime_output") == 1) + { + size_t count_pre_curtailment_kwac; + ssc_number_t *system_pre_curtailment_kwac = as_array("system_pre_curtailment_kwac", &count_pre_curtailment_kwac); + size_t num_rec_pre_curtailment_kwac_per_year = count_pre_curtailment_kwac / nyears; + // hourly_enet includes all curtailment, availability + for (size_t y = 1; y <= (size_t)nyears; y++) + { + cf.at(CF_energy_curtailed, y) = 0.0; + for (size_t h = 0; h < num_rec_pre_curtailment_kwac_per_year; h++) + { + // add steps per hour + cf.at(CF_energy_curtailed, y) += system_pre_curtailment_kwac[h + (y - 1)*num_rec_pre_curtailment_kwac_per_year]*(8760.0/(ssc_number_t)num_rec_pre_curtailment_kwac_per_year); + } + cf.at(CF_energy_curtailed, y) -= cf.at(CF_energy_net, y); + } + } + else + { + for (size_t y = 1; y <= (size_t)nyears; y++) + cf.at(CF_energy_curtailed, y) = pre_curtailement_year1_energy * cf.at(CF_degradation, y) - cf.at(CF_energy_net, y); + } + + for (size_t y = 1; y <= (size_t)nyears; y++) + { + // for fom calculations - gen is reduced by batt from grid in compute module but updated in hourly_energy_calcs.calculate(this); above + if (cf.at(CF_energy_curtailed, y) < 0) cf.at(CF_energy_curtailed, y) = 0; // TODO - address upstream possibly + if (count_curtailment_price == 1) + cf.at(CF_curtailment_value, y) = cf.at(CF_energy_curtailed, y) * grid_curtailment_price[0] * pow(1 + grid_curtailment_price_esc, y - 1); + else if (y <= count_curtailment_price)// schedule + cf.at(CF_curtailment_value, y) = cf.at(CF_energy_curtailed, y) * grid_curtailment_price[y - 1]; + else + cf.at(CF_curtailment_value, y) = 0.0; + } + + + // capacity payment + int cp_payment_type = as_integer("cp_capacity_payment_type"); + if (cp_payment_type < 0 || cp_payment_type > 1) + throw exec_error("singleowner_heat", util::format("Invalid capacity payment type (%d).", cp_payment_type)); + + size_t count_cp_payment_amount; + ssc_number_t *cp_payment_amount = as_array("cp_capacity_payment_amount", &count_cp_payment_amount); + ssc_number_t cp_payment_esc = as_number("cp_capacity_payment_esc") *0.01; + if (count_cp_payment_amount == 1) + { + for (size_t y = 1; y <= (size_t)nyears; y++) + cf.at(CF_capacity_payment, y) = cp_payment_amount[0] * pow(1 + cp_payment_esc, y - 1); + } + else + { + for (size_t y = 1; y <= (size_t)nyears; y++) + { + if (y <= count_cp_payment_amount) + cf.at(CF_capacity_payment, y) = cp_payment_amount[y - 1]; + else + cf.at(CF_capacity_payment, y) = 0.0; + } + } + if (cp_payment_type == 0) // capacity based payment ($/MW) + { + // use system nameplate + ssc_number_t cp_nameplate = as_number("cp_system_nameplate"); + size_t count_cp_capacity_credit_percent = 0; + ssc_number_t *cp_capacity_credit_percent = as_array("cp_capacity_credit_percent", &count_cp_capacity_credit_percent); + if (count_cp_capacity_credit_percent == 1) + { + for (size_t y = 1; y <= (size_t)nyears; y++) + cf.at(CF_capacity_payment, y) *= cp_capacity_credit_percent[0]*0.01 * cp_nameplate; + } + else + { + for (size_t y = 1; y <= (size_t)nyears; y++) + { + if (y <= count_cp_capacity_credit_percent) + cf.at(CF_capacity_payment, y) *= cp_capacity_credit_percent[y - 1] * 0.01 * cp_nameplate; + else + cf.at(CF_capacity_payment, y) = 0.0; + } + } + } + + + + + + + + + first_year_energy = cf.at(CF_energy_net, 1); + + + std::vector degrade_cf; + for (i = 0; i <= nyears; i++) + { + degrade_cf.push_back(cf.at(CF_degradation, i)); + } + m_disp_calcs.init(this, degrade_cf, hourly_energy_calcs.hourly_sales()); + // end of energy and dispatch initialization + + + + for (i=1;i<=nyears;i++) + { + if (is_assigned("gen_without_battery")) { + // TODO: this does not include curtailment, but CF_energy_net does. Which should be used for VOM? + cf.at(CF_om_production_expense, i) *= cf.at(CF_energy_without_battery, i); + } + else { + cf.at(CF_om_production_expense, i) *= cf.at(CF_energy_sales, i); + } + cf.at(CF_om_capacity_expense, i) *= nameplate; + cf.at(CF_om_capacity1_expense, i) *= nameplate1; + cf.at(CF_om_capacity2_expense, i) *= nameplate2; + cf.at(CF_om_fuel_expense,i) *= fuel_use[i-1]; + + cf.at(CF_om_elec_price_for_heat_techs, i) *= cf.at(CF_elec_purchases_for_heat_techs, i); + + //Battery Production OM Costs + cf.at(CF_om_production1_expense, i) *= battery_discharged[i - 1]; //$/MWh * 0.001 MWh/kWh * kWh = $ + cf.at(CF_om_production2_expense, i) *= fuelcell_discharged[i-1]; + + cf.at(CF_om_opt_fuel_1_expense,i) *= om_opt_fuel_1_usage; + cf.at(CF_om_opt_fuel_2_expense,i) *= om_opt_fuel_2_usage; + } + + //Commenting out to test forced retail rate cost calculations + size_t count_ppa_price_input; + ssc_number_t* ppa_price_input = as_array("ppa_price_input", &count_ppa_price_input); + double ppa = 0; + if (count_ppa_price_input > 0) ppa = ppa_price_input[0] * 100.0; +// double ppa = as_double("ppa_price_input")*100.0; // either initial guess for ppa_mode=1 or final ppa for ppa_mode=0 + if (ppa_mode == 0) ppa = 0; // initial guess for target irr mode + + // Use PPA values to calculate revenue from purchases and sales + size_t n_multipliers; + + ssc_number_t* ppa_multipliers = as_array("ppa_multipliers", &n_multipliers); + bool ppa_purchases = !(is_assigned("en_electricity_rates") && as_number("en_electricity_rates") == 1); + if (as_integer("system_use_lifetime_output") == 1) + { + // hourly_enet includes all curtailment, availability + for (size_t i = 1; i <= nyears; i++) { + + // Project partial income statement + // energy_value = DHF Total PPA Revenue (cents/kWh) + if ((ppa_mode == 1) && (count_ppa_price_input > 1)) + { + if (i <= (int)count_ppa_price_input) + cf.at(CF_ppa_price, i) = ppa_price_input[i - 1] * 100.0; // $/kWh to cents/kWh + else + cf.at(CF_ppa_price, i) = 0; + } + else + cf.at(CF_ppa_price, i) = ppa * pow(1 + ppa_escalation, i - 1); // ppa_mode==0 or single value + double ppa_value = cf.at(CF_ppa_price, i); + for (size_t h = 0; h < 8760; h++) { + if (ppa_purchases) { + cf.at(CF_utility_bill, i) += -hourly_energy_calcs.hourly_purchases()[(i - 1) * 8760 + h] * cf.at(CF_degradation, i) * ppa_value / 100.0 * ppa_multipliers[h]; + } + } + } + } + else + { + for (size_t i = 1; i <= nyears; i++) { + if ((ppa_mode == 1) && (count_ppa_price_input > 1)) + { + if (i <= (int)count_ppa_price_input) + cf.at(CF_ppa_price, i) = ppa_price_input[i - 1] * 100.0; // $/kWh to cents/kWh + else + cf.at(CF_ppa_price, i) = 0; + } + else + cf.at(CF_ppa_price, i) = ppa * pow(1 + ppa_escalation, i - 1); // ppa_mode==0 or single value + double ppa_value = cf.at(CF_ppa_price, i); + for (size_t h = 0; h < 8760; h++) { + if (ppa_purchases) { + cf.at(CF_utility_bill, i) += -hourly_energy_calcs.hourly_purchases()[h] * cf.at(CF_degradation, i) * ppa_value / 100.0 * ppa_multipliers[h]; + } + } + } + } + save_cf(CF_utility_bill, nyears, "cf_utility_bill"); + + double property_tax_assessed_value = cost_prefinancing * as_double("prop_tax_cost_assessed_percent") * 0.01; + double property_tax_decline_percentage = as_double("prop_tax_assessed_decline"); + double property_tax_rate = as_double("property_tax_rate")*0.01; + double insurance_rate = as_double("insurance_rate")*0.01; + double months_working_reserve_frac = as_double("months_working_reserve") / 12.0; + double months_receivables_reserve_frac = as_double("months_receivables_reserve") / 12.0; + double equip1_reserve_cost = as_double("equip1_reserve_cost"); + int equip1_reserve_freq = as_integer("equip1_reserve_freq"); + double equip2_reserve_cost = as_double("equip2_reserve_cost"); + int equip2_reserve_freq = as_integer("equip2_reserve_freq"); + double equip3_reserve_cost = as_double("equip3_reserve_cost"); + int equip3_reserve_freq = as_integer("equip3_reserve_freq"); + + // calculate debt for constant dscr mode + int term_tenor = as_integer("term_tenor"); + int loan_moratorium = as_integer("loan_moratorium"); + double term_int_rate = as_double("term_int_rate")*0.01; + double dscr_input = as_double("dscr"); + bool dscr_limit_debt_fraction = as_boolean("dscr_limit_debt_fraction"); + double dscr_maximum_debt_fraction = as_double("dscr_maximum_debt_fraction") * 0.01; + double dscr_reserve_months = as_double("dscr_reserve_months"); + double cash_for_debt_service=0; + double pv_cafds=0; + double size_of_debt=0; + + + // pre calculate reserves + int i_equip1=1; + int i_equip2=1; + int i_equip3=1; + + for (i=1; i<=nyears; i++) + { + // reserves calculations + // major equipment 1 reserve + if ( (i <= (i_equip1 * equip1_reserve_freq)) && ((i_equip1 * equip1_reserve_freq) <= nyears) ) // note will not enter if equip_reequip1_reserve_freq=0 + { + cf.at(CF_funding_equip1,i) = equip1_reserve_cost * nameplate*1000 * pow( 1 + inflation_rate, i_equip1 * equip1_reserve_freq-1 ) / equip1_reserve_freq; + } + if (i == (i_equip1 * equip1_reserve_freq)) + { + cf.at(CF_disbursement_equip1,i) = -equip1_reserve_cost * nameplate*1000 * pow( 1 + inflation_rate, i-1 ); + i_equip1++; + } + cf.at(CF_reserve_equip1,i) = cf.at(CF_funding_equip1,i) + cf.at(CF_disbursement_equip1,i) + cf.at(CF_reserve_equip1,i-1); + // major equipment 2 reserve + if ( (i <= (i_equip2 * equip2_reserve_freq)) && ((i_equip2 * equip2_reserve_freq) <= nyears) ) // note will not enter if equip_reequip2_reserve_freq=0 + { + cf.at(CF_funding_equip2,i) = equip2_reserve_cost * nameplate*1000 * pow( 1 + inflation_rate, i_equip2 * equip2_reserve_freq-1 ) / equip2_reserve_freq; + } + if (i == (i_equip2 * equip2_reserve_freq)) + { + cf.at(CF_disbursement_equip2,i) = -equip2_reserve_cost * nameplate*1000 * pow( 1 + inflation_rate, i-1 ); + i_equip2++; + } + cf.at(CF_reserve_equip2,i) = cf.at(CF_funding_equip2,i) + cf.at(CF_disbursement_equip2,i) + cf.at(CF_reserve_equip2,i-1);; + // major equipment 3 reserve + if ( (i <= (i_equip3 * equip3_reserve_freq)) && ((i_equip3 * equip3_reserve_freq) <= nyears) ) // note will not enter if equip_reequip3_reserve_freq=0 + { + cf.at(CF_funding_equip3,i) = equip3_reserve_cost * nameplate*1000 * pow( 1 + inflation_rate, i_equip3 * equip3_reserve_freq-1 ) / equip3_reserve_freq; + } + if (i == (i_equip3 * equip3_reserve_freq)) + { + cf.at(CF_disbursement_equip3,i) = -equip3_reserve_cost * nameplate*1000 * pow( 1 + inflation_rate, i-1 ); + i_equip3++; + } + cf.at(CF_reserve_equip3,i) = cf.at(CF_funding_equip3,i) + cf.at(CF_disbursement_equip3,i) + cf.at(CF_reserve_equip3,i-1); + } + + depreciation_sched_5_year_macrs_half_year(CF_macrs_5_frac,nyears); + depreciation_sched_15_year_macrs_half_year(CF_macrs_15_frac,nyears); + depreciation_sched_5_year_straight_line_half_year(CF_sl_5_frac,nyears); + depreciation_sched_15_year_straight_line_half_year(CF_sl_15_frac,nyears); + depreciation_sched_20_year_straight_line_half_year(CF_sl_20_frac,nyears); + depreciation_sched_39_year_straight_line_half_year(CF_sl_39_frac,nyears); + depreciation_sched_custom(CF_custom_frac,nyears,"depr_custom_schedule"); + + int feddepr_me1=CF_macrs_5_frac + as_integer("equip_reserve_depr_fed"); + int feddepr_me2=CF_macrs_5_frac + as_integer("equip_reserve_depr_fed"); + int feddepr_me3=CF_macrs_5_frac + as_integer("equip_reserve_depr_fed"); + + int stadepr_me1=CF_macrs_5_frac + as_integer("equip_reserve_depr_sta"); + int stadepr_me2=CF_macrs_5_frac + as_integer("equip_reserve_depr_sta"); + int stadepr_me3=CF_macrs_5_frac + as_integer("equip_reserve_depr_sta"); + + + for (i=1;i<=nyears;i++) + { + if ((equip1_reserve_freq != 0) && (i%equip1_reserve_freq == 0)) + { + major_equipment_depreciation(CF_disbursement_equip1,feddepr_me1,i,nyears,CF_feddepr_me1); + major_equipment_depreciation(CF_disbursement_equip1,stadepr_me1,i,nyears,CF_stadepr_me1); + } + if ((equip2_reserve_freq != 0) && (i%equip2_reserve_freq == 0)) + { + major_equipment_depreciation(CF_disbursement_equip2,feddepr_me2,i,nyears,CF_feddepr_me2); + major_equipment_depreciation(CF_disbursement_equip2,stadepr_me2,i,nyears,CF_stadepr_me2); + } + if ((equip3_reserve_freq != 0) && (i%equip3_reserve_freq == 0)) + { + major_equipment_depreciation(CF_disbursement_equip3,feddepr_me3,i,nyears,CF_feddepr_me3); + major_equipment_depreciation(CF_disbursement_equip3,stadepr_me3,i,nyears,CF_stadepr_me3); + } + } + + double recapitalization_cost = as_double("system_recapitalization_cost"); + double recapitalization_escalation = 0.01*as_double("system_recapitalization_escalation"); + if (as_integer("system_use_recapitalization")) + { + size_t recap_boolean_count; + ssc_number_t *recap_boolean = 0; + recap_boolean = as_array("system_lifetime_recapitalize", &recap_boolean_count); + + if (recap_boolean_count > 0) + { + for (i=0;i 0) ? property_tax_assessed_value * decline_percent * 0.01:0.0; + cf.at(CF_property_tax_expense,i) = cf.at(CF_property_tax_assessed_value,i) * property_tax_rate; + cf.at(CF_insurance_expense,i) = cost_prefinancing * insurance_rate * pow( 1 + inflation_rate, i-1 ); + + if (as_integer("system_use_recapitalization")) + { + cf.at(CF_Recapitalization,i) = cf.at(CF_Recapitalization_boolean,i) * recapitalization_cost + * pow((1 + inflation_rate + recapitalization_escalation ), i-1 ); + } + + cf.at(CF_operating_expenses, i) = + +cf.at(CF_om_fixed_expense, i) + + cf.at(CF_om_production_expense, i) + + cf.at(CF_om_capacity_expense, i) + + cf.at(CF_om_fixed1_expense, i) + + cf.at(CF_om_production1_expense, i) + + cf.at(CF_om_capacity1_expense, i) + + cf.at(CF_om_fixed2_expense, i) + + cf.at(CF_om_production2_expense, i) + + cf.at(CF_om_capacity2_expense, i) + + cf.at(CF_om_fuel_expense, i) + + cf.at(CF_om_elec_price_for_heat_techs, i) + + cf.at(CF_om_opt_fuel_1_expense, i) + + cf.at(CF_om_opt_fuel_2_expense, i) + + cf.at(CF_land_lease_expense, i) + + cf.at(CF_property_tax_expense, i) + + cf.at(CF_insurance_expense, i) + + cf.at(CF_battery_replacement_cost, i) + + cf.at(CF_fuelcell_replacement_cost, i) + + cf.at(CF_om_hybrid_sum, i) + + cf.at(CF_utility_bill, i) + + cf.at(CF_Recapitalization,i); + } + + // salvage value + cf.at(CF_net_salvage_value,nyears) = salvage_value; + + // o and m reserve + if (nyears>0) + { + cf.at(CF_reserve_om,0) = months_working_reserve_frac * cf.at(CF_operating_expenses,1) ; + cf.at(CF_funding_om,0) = cf.at(CF_reserve_om,0); + for (i=1; i as_double("ibi_fed_percent_maxvalue")) ibi_fed_per = as_double("ibi_fed_percent_maxvalue"); + double ibi_sta_per = as_double("ibi_sta_percent")*0.01*cost_prefinancing; + if (ibi_sta_per > as_double("ibi_sta_percent_maxvalue")) ibi_sta_per = as_double("ibi_sta_percent_maxvalue"); + double ibi_uti_per = as_double("ibi_uti_percent")*0.01*cost_prefinancing; + if (ibi_uti_per > as_double("ibi_uti_percent_maxvalue")) ibi_uti_per = as_double("ibi_uti_percent_maxvalue"); + double ibi_oth_per = as_double("ibi_oth_percent")*0.01*cost_prefinancing; + if (ibi_oth_per > as_double("ibi_oth_percent_maxvalue")) ibi_oth_per = as_double("ibi_oth_percent_maxvalue"); + + // SAM 1038 + // itc fixed + double itc_fed_amount = 0.0; + double_vec vitc_fed_amount = as_vector_double("itc_fed_amount"); + for (size_t k = 0; k < vitc_fed_amount.size() && k < nyears; k++) { + cf.at(CF_itc_fed_amount, k + 1) = vitc_fed_amount[k]; + itc_fed_amount += vitc_fed_amount[k]; + } + + double itc_sta_amount = 0.0; + double_vec vitc_sta_amount = as_vector_double("itc_sta_amount"); + for (size_t k = 0; k < vitc_sta_amount.size() && k < nyears; k++) { + cf.at(CF_itc_sta_amount, k + 1) = vitc_sta_amount[k]; + itc_sta_amount += vitc_sta_amount[k]; + } + + // itc percent - max value used for comparison to qualifying costs + double_vec vitc_fed_frac = as_vector_double("itc_fed_percent"); + for (size_t k = 0; k < vitc_fed_frac.size(); k++) + cf.at(CF_itc_fed_percent_fraction, k + 1) = vitc_fed_frac[k] * 0.01; + double itc_fed_per; + double_vec vitc_sta_frac = as_vector_double("itc_sta_percent"); + for (size_t k = 0; k < vitc_sta_frac.size(); k++) + cf.at(CF_itc_sta_percent_fraction, k + 1) = vitc_sta_frac[k] * 0.01; + double itc_sta_per; + + double_vec itc_sta_percent_maxvalue = as_vector_double("itc_sta_percent_maxvalue"); + if (itc_sta_percent_maxvalue.size() == 1) { + for (size_t k = 0; k < nyears; k++) + cf.at(CF_itc_sta_percent_maxvalue, k + 1) = itc_sta_percent_maxvalue[0]; + } + else { + for (size_t k = 0; k < itc_sta_percent_maxvalue.size() && k < nyears; k++) + cf.at(CF_itc_sta_percent_maxvalue, k + 1) = itc_sta_percent_maxvalue[k]; + } + + double_vec itc_fed_percent_maxvalue = as_vector_double("itc_fed_percent_maxvalue"); + if (itc_fed_percent_maxvalue.size() == 1) { + for (size_t k = 0; k < nyears; k++) + cf.at(CF_itc_fed_percent_maxvalue, k + 1) = itc_fed_percent_maxvalue[0]; + } + else { + for (size_t k = 0; k < itc_fed_percent_maxvalue.size() && k < nyears; k++) + cf.at(CF_itc_fed_percent_maxvalue, k + 1) = itc_fed_percent_maxvalue[k]; + } + + + // cbi + double cbi_fed_amount = 1000.0*nameplate*as_double("cbi_fed_amount"); + if (cbi_fed_amount > as_double("cbi_fed_maxvalue")) cbi_fed_amount = as_double("cbi_fed_maxvalue"); + double cbi_sta_amount = 1000.0*nameplate*as_double("cbi_sta_amount"); + if (cbi_sta_amount > as_double("cbi_sta_maxvalue")) cbi_sta_amount = as_double("cbi_sta_maxvalue"); + double cbi_uti_amount = 1000.0*nameplate*as_double("cbi_uti_amount"); + if (cbi_uti_amount > as_double("cbi_uti_maxvalue")) cbi_uti_amount = as_double("cbi_uti_maxvalue"); + double cbi_oth_amount = 1000.0*nameplate*as_double("cbi_oth_amount"); + if (cbi_oth_amount > as_double("cbi_oth_maxvalue")) cbi_oth_amount = as_double("cbi_oth_maxvalue"); + + // precompute pbi + compute_production_incentive( CF_pbi_fed, nyears, "pbi_fed_amount", "pbi_fed_term", "pbi_fed_escal" ); + compute_production_incentive( CF_pbi_sta, nyears, "pbi_sta_amount", "pbi_sta_term", "pbi_sta_escal" ); + compute_production_incentive( CF_pbi_uti, nyears, "pbi_uti_amount", "pbi_uti_term", "pbi_uti_escal" ); + compute_production_incentive( CF_pbi_oth, nyears, "pbi_oth_amount", "pbi_oth_term", "pbi_oth_escal" ); + + // precompute ptc + compute_production_incentive_IRS_2010_37( CF_ptc_sta, nyears, "ptc_sta_amount", "ptc_sta_term", "ptc_sta_escal" ); + compute_production_incentive_IRS_2010_37( CF_ptc_fed, nyears, "ptc_fed_amount", "ptc_fed_term", "ptc_fed_escal" ); + + for (i=0;i<=nyears; i++) + { + cf.at(CF_pbi_total,i) = cf.at(CF_pbi_fed,i) + cf.at(CF_pbi_sta,i) + cf.at(CF_pbi_uti,i) + cf.at(CF_pbi_oth,i); + cf.at(CF_aftertax_ptc,i) = cf.at(CF_ptc_fed,i) + cf.at(CF_ptc_sta,i); + } + + double cbi_total = cbi_fed_amount + cbi_sta_amount +cbi_uti_amount + cbi_oth_amount; + double ibi_total = ibi_fed_amount + ibi_sta_amount +ibi_uti_amount + ibi_oth_amount + ibi_fed_per + ibi_sta_per +ibi_uti_per + ibi_oth_per; + + double cbi_statax_total = + ( as_boolean("cbi_fed_tax_sta") ? cbi_fed_amount : 0 ) + + ( as_boolean("cbi_sta_tax_sta") ? cbi_sta_amount : 0 ) + + ( as_boolean("cbi_uti_tax_sta") ? cbi_uti_amount : 0 ) + + ( as_boolean("cbi_oth_tax_sta") ? cbi_oth_amount : 0 ); + double ibi_statax_total = + ( as_boolean("ibi_fed_amount_tax_sta") ? ibi_fed_amount : 0 ) + + ( as_boolean("ibi_fed_percent_tax_sta") ? ibi_fed_per : 0 ) + + ( as_boolean("ibi_sta_amount_tax_sta") ? ibi_sta_amount : 0 ) + + ( as_boolean("ibi_sta_percent_tax_sta") ? ibi_sta_per : 0 ) + + ( as_boolean("ibi_uti_amount_tax_sta") ? ibi_uti_amount : 0 ) + + ( as_boolean("ibi_uti_percent_tax_sta") ? ibi_uti_per : 0 ) + + ( as_boolean("ibi_oth_amount_tax_sta") ? ibi_oth_amount : 0 ) + + ( as_boolean("ibi_oth_percent_tax_sta") ? ibi_oth_per : 0 ); + + double cbi_fedtax_total = + ( as_boolean("cbi_fed_tax_fed") ? cbi_fed_amount : 0 ) + + ( as_boolean("cbi_sta_tax_fed") ? cbi_sta_amount : 0 ) + + ( as_boolean("cbi_uti_tax_fed") ? cbi_uti_amount : 0 ) + + ( as_boolean("cbi_oth_tax_fed") ? cbi_oth_amount : 0 ); + double ibi_fedtax_total = + ( as_boolean("ibi_fed_amount_tax_fed") ? ibi_fed_amount : 0 ) + + ( as_boolean("ibi_fed_percent_tax_fed") ? ibi_fed_per : 0 ) + + ( as_boolean("ibi_sta_amount_tax_fed") ? ibi_sta_amount : 0 ) + + ( as_boolean("ibi_sta_percent_tax_fed") ? ibi_sta_per : 0 ) + + ( as_boolean("ibi_uti_amount_tax_fed") ? ibi_uti_amount : 0 ) + + ( as_boolean("ibi_uti_percent_tax_fed") ? ibi_uti_per : 0 ) + + ( as_boolean("ibi_oth_amount_tax_fed") ? ibi_oth_amount : 0 ) + + ( as_boolean("ibi_oth_percent_tax_fed") ? ibi_oth_per : 0 ); + + + for (i=1;i<=nyears;i++) + { + cf.at(CF_pbi_statax_total,i) = + (( as_boolean("pbi_fed_tax_sta") && (!as_boolean("pbi_fed_for_ds"))) ? cf.at(CF_pbi_fed,i) : 0 ) + + (( as_boolean("pbi_sta_tax_sta") && (!as_boolean("pbi_sta_for_ds"))) ? cf.at(CF_pbi_sta,i) : 0 ) + + (( as_boolean("pbi_uti_tax_sta") && (!as_boolean("pbi_uti_for_ds"))) ? cf.at(CF_pbi_uti,i) : 0 ) + + (( as_boolean("pbi_oth_tax_sta") && (!as_boolean("pbi_oth_for_ds"))) ? cf.at(CF_pbi_oth,i) : 0 ) ; + + cf.at(CF_pbi_fedtax_total,i) = + (( as_boolean("pbi_fed_tax_fed") && (!as_boolean("pbi_fed_for_ds"))) ? cf.at(CF_pbi_fed,i) : 0 ) + + (( as_boolean("pbi_sta_tax_fed") && (!as_boolean("pbi_sta_for_ds"))) ? cf.at(CF_pbi_sta,i) : 0 ) + + (( as_boolean("pbi_uti_tax_fed") && (!as_boolean("pbi_uti_for_ds"))) ? cf.at(CF_pbi_uti,i) : 0 ) + + (( as_boolean("pbi_oth_tax_fed") && (!as_boolean("pbi_oth_for_ds"))) ? cf.at(CF_pbi_oth,i) : 0 ) ; + } + // 5/1/11 + for (i=1;i<=nyears;i++) + { + cf.at(CF_statax_taxable_incentives,i) = cf.at(CF_pbi_statax_total,i); + cf.at(CF_fedtax_taxable_incentives,i) = cf.at(CF_pbi_fedtax_total,i); + } + cf.at(CF_statax_taxable_incentives,1) += cbi_statax_total + ibi_statax_total; + cf.at(CF_fedtax_taxable_incentives,1) += cbi_fedtax_total + ibi_fedtax_total; + + double cost_financing; + + double cost_installed; + + double pre_depr_alloc_basis; // Total costs that could qualify for depreciation before allocations + double pre_itc_qual_basis; // Total costs that could qualify for ITC before allocations + + double depr_alloc_macrs_5_frac = as_double("depr_alloc_macrs_5_percent") * 0.01; + double depr_alloc_macrs_15_frac = as_double("depr_alloc_macrs_15_percent") * 0.01; + double depr_alloc_sl_5_frac = as_double("depr_alloc_sl_5_percent") * 0.01; + double depr_alloc_sl_15_frac = as_double("depr_alloc_sl_15_percent") * 0.01; + double depr_alloc_sl_20_frac = as_double("depr_alloc_sl_20_percent") * 0.01; + double depr_alloc_sl_39_frac = as_double("depr_alloc_sl_39_percent") * 0.01; + double depr_alloc_custom_frac = as_double("depr_alloc_custom_percent") * 0.01; + double depr_alloc_total_frac = depr_alloc_macrs_5_frac + depr_alloc_macrs_15_frac + + depr_alloc_sl_5_frac + depr_alloc_sl_15_frac + depr_alloc_sl_20_frac + depr_alloc_sl_39_frac + depr_alloc_custom_frac; + // TODO - place check that depr_alloc_total_frac <= 1 and <>0 + double depr_alloc_none_frac = 1.0 - depr_alloc_total_frac; + // TODO - place check that depr_alloc_none_frac >= 0 + + + // redistribute fractions to only depreciable allocations + if (depr_alloc_total_frac > 0) // and <=1 + { + depr_alloc_macrs_5_frac /= depr_alloc_total_frac; + depr_alloc_macrs_15_frac /= depr_alloc_total_frac; + depr_alloc_sl_5_frac /= depr_alloc_total_frac; + depr_alloc_sl_15_frac /= depr_alloc_total_frac; + depr_alloc_sl_20_frac /= depr_alloc_total_frac; + depr_alloc_sl_39_frac /= depr_alloc_total_frac; + depr_alloc_custom_frac /= depr_alloc_total_frac; + } + + double depr_stabas_macrs_5_frac; + double depr_stabas_macrs_15_frac; + double depr_stabas_sl_5_frac; + double depr_stabas_sl_15_frac; + double depr_stabas_sl_20_frac; + double depr_stabas_sl_39_frac; + double depr_stabas_custom_frac; + + if (as_integer("depr_stabas_method")==0) + { + depr_stabas_macrs_5_frac = 1.0; + depr_stabas_macrs_15_frac = 0.0; + depr_stabas_sl_5_frac = 0.0; + depr_stabas_sl_15_frac = 0.0; + depr_stabas_sl_20_frac = 0.0; + depr_stabas_sl_39_frac = 0.0; + depr_stabas_custom_frac = 0.0; + } + else + { + depr_stabas_macrs_5_frac = depr_alloc_macrs_5_frac; + depr_stabas_macrs_15_frac = depr_alloc_macrs_15_frac; + depr_stabas_sl_5_frac = depr_alloc_sl_5_frac; + depr_stabas_sl_15_frac = depr_alloc_sl_15_frac; + depr_stabas_sl_20_frac = depr_alloc_sl_20_frac; + depr_stabas_sl_39_frac = depr_alloc_sl_39_frac; + depr_stabas_custom_frac = depr_alloc_custom_frac; + } + + double depr_fedbas_macrs_5_frac; + double depr_fedbas_macrs_15_frac; + double depr_fedbas_sl_5_frac; + double depr_fedbas_sl_15_frac; + double depr_fedbas_sl_20_frac; + double depr_fedbas_sl_39_frac; + double depr_fedbas_custom_frac; + + if (as_integer("depr_fedbas_method")==0) + { + depr_fedbas_macrs_5_frac = 1.0; + depr_fedbas_macrs_15_frac = 0.0; + depr_fedbas_sl_5_frac = 0.0; + depr_fedbas_sl_15_frac = 0.0; + depr_fedbas_sl_20_frac = 0.0; + depr_fedbas_sl_39_frac = 0.0; + depr_fedbas_custom_frac = 0.0; + } + else + { + depr_fedbas_macrs_5_frac = depr_alloc_macrs_5_frac; + depr_fedbas_macrs_15_frac = depr_alloc_macrs_15_frac; + depr_fedbas_sl_5_frac = depr_alloc_sl_5_frac; + depr_fedbas_sl_15_frac = depr_alloc_sl_15_frac; + depr_fedbas_sl_20_frac = depr_alloc_sl_20_frac; + depr_fedbas_sl_39_frac = depr_alloc_sl_39_frac; + depr_fedbas_custom_frac = depr_alloc_custom_frac; + } + + double depr_alloc_macrs_5; + double depr_alloc_macrs_15; + double depr_alloc_sl_5; + double depr_alloc_sl_15; + double depr_alloc_sl_20; + double depr_alloc_sl_39; + double depr_alloc_custom; + double depr_alloc_none; + double depr_alloc_total; + + double itc_sta_qual_macrs_5_frac = ( as_boolean("depr_itc_sta_macrs_5") ? 1: 0 ) ; + double itc_sta_qual_macrs_15_frac = ( as_boolean("depr_itc_sta_macrs_15") ? 1: 0 ) ; + double itc_sta_qual_sl_5_frac = ( as_boolean("depr_itc_sta_sl_5") ? 1: 0 ) ; + double itc_sta_qual_sl_15_frac = ( as_boolean("depr_itc_sta_sl_15") ? 1: 0 ) ; + double itc_sta_qual_sl_20_frac = ( as_boolean("depr_itc_sta_sl_20") ? 1: 0 ) ; + double itc_sta_qual_sl_39_frac = ( as_boolean("depr_itc_sta_sl_39") ? 1: 0 ) ; + double itc_sta_qual_custom_frac = ( as_boolean("depr_itc_sta_custom") ? 1: 0 ) ; + + double itc_sta_qual_total; + + double itc_sta_qual_macrs_5; + double itc_sta_qual_macrs_15; + double itc_sta_qual_sl_5; + double itc_sta_qual_sl_15; + double itc_sta_qual_sl_20; + double itc_sta_qual_sl_39; + double itc_sta_qual_custom; + + double itc_sta_disallow_factor = 0.5; + + + double itc_disallow_sta_percent_macrs_5; + double itc_disallow_sta_percent_macrs_15; + double itc_disallow_sta_percent_sl_5; + double itc_disallow_sta_percent_sl_15; + double itc_disallow_sta_percent_sl_20; + double itc_disallow_sta_percent_sl_39; + double itc_disallow_sta_percent_custom; + + + double itc_disallow_sta_fixed_macrs_5 = (itc_sta_disallow_factor*itc_sta_qual_macrs_5_frac * itc_sta_amount); + double itc_disallow_sta_fixed_macrs_15 = (itc_sta_disallow_factor*itc_sta_qual_macrs_15_frac * itc_sta_amount); + double itc_disallow_sta_fixed_sl_5 = (itc_sta_disallow_factor*itc_sta_qual_sl_5_frac * itc_sta_amount); + double itc_disallow_sta_fixed_sl_15 = (itc_sta_disallow_factor*itc_sta_qual_sl_15_frac * itc_sta_amount); + double itc_disallow_sta_fixed_sl_20 = (itc_sta_disallow_factor*itc_sta_qual_sl_20_frac * itc_sta_amount); + double itc_disallow_sta_fixed_sl_39 = (itc_sta_disallow_factor*itc_sta_qual_sl_39_frac * itc_sta_amount); + double itc_disallow_sta_fixed_custom = (itc_sta_disallow_factor*itc_sta_qual_custom_frac * itc_sta_amount); + + double itc_fed_qual_macrs_5_frac = ( as_boolean("depr_itc_fed_macrs_5") ?1: 0 ) ; + double itc_fed_qual_macrs_15_frac = ( as_boolean("depr_itc_fed_macrs_15") ? 1: 0 ) ; + double itc_fed_qual_sl_5_frac = ( as_boolean("depr_itc_fed_sl_5") ? 1: 0 ) ; + double itc_fed_qual_sl_15_frac = ( as_boolean("depr_itc_fed_sl_15") ? 1: 0 ) ; + double itc_fed_qual_sl_20_frac = ( as_boolean("depr_itc_fed_sl_20") ? 1: 0 ) ; + double itc_fed_qual_sl_39_frac = ( as_boolean("depr_itc_fed_sl_39") ? 1: 0 ) ; + double itc_fed_qual_custom_frac = ( as_boolean("depr_itc_fed_custom") ? 1: 0 ) ; + + double itc_fed_qual_total; + + double itc_fed_qual_macrs_5; + double itc_fed_qual_macrs_15; + double itc_fed_qual_sl_5; + double itc_fed_qual_sl_15; + double itc_fed_qual_sl_20; + double itc_fed_qual_sl_39; + double itc_fed_qual_custom; + + double itc_fed_disallow_factor = 0.5; + + + double itc_disallow_fed_percent_macrs_5; + double itc_disallow_fed_percent_macrs_15; + double itc_disallow_fed_percent_sl_5; + double itc_disallow_fed_percent_sl_15; + double itc_disallow_fed_percent_sl_20; + double itc_disallow_fed_percent_sl_39; + double itc_disallow_fed_percent_custom; + + double itc_disallow_fed_fixed_macrs_5 = (itc_fed_disallow_factor*itc_fed_qual_macrs_5_frac * itc_fed_amount); + double itc_disallow_fed_fixed_macrs_15 = (itc_fed_disallow_factor*itc_fed_qual_macrs_15_frac * itc_fed_amount); + double itc_disallow_fed_fixed_sl_5 = (itc_fed_disallow_factor*itc_fed_qual_sl_5_frac * itc_fed_amount); + double itc_disallow_fed_fixed_sl_15 = (itc_fed_disallow_factor*itc_fed_qual_sl_15_frac * itc_fed_amount); + double itc_disallow_fed_fixed_sl_20 = (itc_fed_disallow_factor*itc_fed_qual_sl_20_frac * itc_fed_amount); + double itc_disallow_fed_fixed_sl_39 = (itc_fed_disallow_factor*itc_fed_qual_sl_39_frac * itc_fed_amount); + double itc_disallow_fed_fixed_custom = (itc_fed_disallow_factor*itc_fed_qual_custom_frac * itc_fed_amount); + + +// Depreciation +// State depreciation + double depr_sta_reduction_ibi = ( + ( as_boolean("ibi_fed_amount_deprbas_sta") ? ibi_fed_amount : 0 ) + + ( as_boolean("ibi_fed_percent_deprbas_sta") ? ibi_fed_per : 0 ) + + ( as_boolean("ibi_sta_amount_deprbas_sta") ? ibi_sta_amount : 0 ) + + ( as_boolean("ibi_sta_percent_deprbas_sta") ? ibi_sta_per : 0 ) + + ( as_boolean("ibi_uti_amount_deprbas_sta") ? ibi_uti_amount : 0 ) + + ( as_boolean("ibi_uti_percent_deprbas_sta") ? ibi_uti_per : 0 ) + + ( as_boolean("ibi_oth_amount_deprbas_sta") ? ibi_oth_amount : 0 ) + + ( as_boolean("ibi_oth_percent_deprbas_sta") ? ibi_oth_per : 0 ) + ); + + double depr_sta_reduction_cbi = ( + ( as_boolean("cbi_fed_deprbas_sta") ? cbi_fed_amount : 0 ) + + ( as_boolean("cbi_sta_deprbas_sta") ? cbi_sta_amount : 0 ) + + ( as_boolean("cbi_uti_deprbas_sta") ? cbi_uti_amount : 0 ) + + ( as_boolean("cbi_oth_deprbas_sta") ? cbi_oth_amount : 0 ) + ); + + double depr_sta_reduction = depr_sta_reduction_ibi + depr_sta_reduction_cbi; + + double depr_stabas_macrs_5; + double depr_stabas_macrs_15; + double depr_stabas_sl_5; + double depr_stabas_sl_15; + double depr_stabas_sl_20; + double depr_stabas_sl_39; + double depr_stabas_custom; + + // ITC reduction + double itc_fed_percent_deprbas_sta = as_boolean("itc_fed_percent_deprbas_sta") ? 1.0 : 0.0; + double itc_fed_amount_deprbas_sta = as_boolean("itc_fed_amount_deprbas_sta") ? 1.0 : 0.0; + double itc_sta_percent_deprbas_sta = as_boolean("itc_sta_percent_deprbas_sta") ? 1.0 : 0.0; + double itc_sta_amount_deprbas_sta = as_boolean("itc_sta_amount_deprbas_sta") ? 1.0 : 0.0; + + + // Bonus depreciation + double depr_stabas_macrs_5_bonus_frac = ( as_boolean("depr_bonus_sta_macrs_5") ? as_double("depr_bonus_sta")*0.01 : 0 ); + double depr_stabas_macrs_15_bonus_frac = ( as_boolean("depr_bonus_sta_macrs_15") ? as_double("depr_bonus_sta")*0.01 : 0 ); + double depr_stabas_sl_5_bonus_frac = ( as_boolean("depr_bonus_sta_sl_5") ? as_double("depr_bonus_sta")*0.01 : 0 ); + double depr_stabas_sl_15_bonus_frac = ( as_boolean("depr_bonus_sta_sl_15") ? as_double("depr_bonus_sta")*0.01 : 0 ); + double depr_stabas_sl_20_bonus_frac = ( as_boolean("depr_bonus_sta_sl_20") ? as_double("depr_bonus_sta")*0.01 : 0 ); + double depr_stabas_sl_39_bonus_frac = ( as_boolean("depr_bonus_sta_sl_39") ? as_double("depr_bonus_sta")*0.01 : 0 ); + double depr_stabas_custom_bonus_frac = ( as_boolean("depr_bonus_sta_custom") ? as_double("depr_bonus_sta")*0.01 : 0 ); + + double depr_stabas_macrs_5_bonus; + double depr_stabas_macrs_15_bonus; + double depr_stabas_sl_5_bonus; + double depr_stabas_sl_15_bonus; + double depr_stabas_sl_20_bonus; + double depr_stabas_sl_39_bonus; + double depr_stabas_custom_bonus; + + double depr_stabas_total; + + // Federal depreciation + double depr_fed_reduction_ibi = ( + ( as_boolean("ibi_fed_amount_deprbas_fed") ? ibi_fed_amount : 0 ) + + ( as_boolean("ibi_fed_percent_deprbas_fed") ? ibi_fed_per : 0 ) + + ( as_boolean("ibi_sta_amount_deprbas_fed") ? ibi_sta_amount : 0 ) + + ( as_boolean("ibi_sta_percent_deprbas_fed") ? ibi_sta_per : 0 ) + + ( as_boolean("ibi_uti_amount_deprbas_fed") ? ibi_uti_amount : 0 ) + + ( as_boolean("ibi_uti_percent_deprbas_fed") ? ibi_uti_per : 0 ) + + ( as_boolean("ibi_oth_amount_deprbas_fed") ? ibi_oth_amount : 0 ) + + ( as_boolean("ibi_oth_percent_deprbas_fed") ? ibi_oth_per : 0 ) + ); + + double depr_fed_reduction_cbi = ( + ( as_boolean("cbi_fed_deprbas_fed") ? cbi_fed_amount : 0 ) + + ( as_boolean("cbi_sta_deprbas_fed") ? cbi_sta_amount : 0 ) + + ( as_boolean("cbi_uti_deprbas_fed") ? cbi_uti_amount : 0 ) + + ( as_boolean("cbi_oth_deprbas_fed") ? cbi_oth_amount : 0 ) + ); + + double depr_fed_reduction = depr_fed_reduction_ibi + depr_fed_reduction_cbi; + + double depr_fedbas_macrs_5; + double depr_fedbas_macrs_15; + double depr_fedbas_sl_5; + double depr_fedbas_sl_15; + double depr_fedbas_sl_20; + double depr_fedbas_sl_39; + double depr_fedbas_custom; + + // ITC reduction + double itc_fed_percent_deprbas_fed = as_boolean("itc_fed_percent_deprbas_fed") ? 1.0 : 0.0; + double itc_fed_amount_deprbas_fed = as_boolean("itc_fed_amount_deprbas_fed") ? 1.0 : 0.0; + double itc_sta_percent_deprbas_fed = as_boolean("itc_sta_percent_deprbas_fed") ? 1.0 : 0.0; + double itc_sta_amount_deprbas_fed = as_boolean("itc_sta_amount_deprbas_fed") ? 1.0 : 0.0; + + // Bonus depreciation + double depr_fedbas_macrs_5_bonus_frac = ( as_boolean("depr_bonus_fed_macrs_5") ? as_double("depr_bonus_fed")*0.01 : 0 ); + double depr_fedbas_macrs_15_bonus_frac = ( as_boolean("depr_bonus_fed_macrs_15") ? as_double("depr_bonus_fed")*0.01 : 0 ); + double depr_fedbas_sl_5_bonus_frac = ( as_boolean("depr_bonus_fed_sl_5") ? as_double("depr_bonus_fed")*0.01 : 0 ); + double depr_fedbas_sl_15_bonus_frac = ( as_boolean("depr_bonus_fed_sl_15") ? as_double("depr_bonus_fed")*0.01 : 0 ); + double depr_fedbas_sl_20_bonus_frac = ( as_boolean("depr_bonus_fed_sl_20") ? as_double("depr_bonus_fed")*0.01 : 0 ); + double depr_fedbas_sl_39_bonus_frac = ( as_boolean("depr_bonus_fed_sl_39") ? as_double("depr_bonus_fed")*0.01 : 0 ); + double depr_fedbas_custom_bonus_frac = ( as_boolean("depr_bonus_fed_custom") ? as_double("depr_bonus_fed")*0.01 : 0 ); + + double depr_fedbas_macrs_5_bonus; + double depr_fedbas_macrs_15_bonus; + double depr_fedbas_sl_5_bonus; + double depr_fedbas_sl_15_bonus; + double depr_fedbas_sl_20_bonus; + double depr_fedbas_sl_39_bonus; + double depr_fedbas_custom_bonus; + + double depr_fedbas_total; + + + double pbi_fed_for_ds_frac = as_boolean("pbi_fed_for_ds") ? 1.0 : 0.0; + double pbi_sta_for_ds_frac = as_boolean("pbi_sta_for_ds") ? 1.0 : 0.0; + double pbi_uti_for_ds_frac = as_boolean("pbi_uti_for_ds") ? 1.0 : 0.0; + double pbi_oth_for_ds_frac = as_boolean("pbi_oth_for_ds") ? 1.0 : 0.0; + + + // if (ppa_mode == 0) // iterate to meet flip target by varying ppa price + double ppa_soln_tolerance = as_double("ppa_soln_tolerance"); + int ppa_soln_max_iteations = as_integer("ppa_soln_max_iterations"); + double flip_target_percent = as_double("flip_target_percent") ; + int flip_target_year = as_integer("flip_target_year"); + // check for accessing off of the end of cashflow matrix + if (flip_target_year > nyears) flip_target_year = nyears; + int flip_year=-1; + double purchase_of_property; + bool solved=true; + double ppa_min=as_double("ppa_soln_min"); + double ppa_max=as_double("ppa_soln_max"); + int its=0; + double irr_weighting_factor = DBL_MAX; + bool irr_is_minimally_met = false; + bool irr_greater_than_target = false; + double w0=1.0; + double w1=1.0; + double x0=ppa_min; + double x1=ppa_max; + double ppa_coarse_interval=10; // 10 cents/kWh + bool ppa_interval_found=false; + bool ppa_too_large=false; + bool ppa_interval_reset=true; + // 12/14/12 - address issue from Eric Lantz - ppa solution when target mode and ppa < 0 + double ppa_old=ppa; + + + + if (constant_dscr_mode) { + // initial installed_cost estimate + cost_financing = + cost_debt_closing + + cost_debt_fee_frac * cost_prefinancing + //estimate until final size of debt known + cost_other_financing + + // cf.at(CF_reserve_debtservice, 0) + // estimate until debt size for each year is known + constr_total_financing + + cf.at(CF_reserve_om, 0) + + cf.at(CF_reserve_receivables, 0); + + cost_installed = cost_prefinancing + cost_financing + - ibi_fed_amount + - ibi_sta_amount + - ibi_uti_amount + - ibi_oth_amount + - ibi_fed_per + - ibi_sta_per + - ibi_uti_per + - ibi_oth_per + - cbi_fed_amount + - cbi_sta_amount + - cbi_uti_amount + - cbi_oth_amount; + + } + else + { // debt fraction input + double debt_frac = as_double("debt_percent")*0.01; + + cost_installed = + cost_prefinancing + + constr_total_financing + + cost_debt_closing + + cost_other_financing + + cf.at(CF_reserve_debtservice, 0) // initially zero - based on p&i + + cf.at(CF_reserve_om, 0) + - ibi_fed_amount + - ibi_sta_amount + - ibi_uti_amount + - ibi_oth_amount + - ibi_fed_per + - ibi_sta_per + - ibi_uti_per + - ibi_oth_per + - cbi_fed_amount + - cbi_sta_amount + - cbi_uti_amount + - cbi_oth_amount; + cost_installed += debt_frac *cost_installed*cost_debt_fee_frac; // approximate up front fee + double loan_amount = debt_frac * cost_installed; + + int i_repeat = 0; + double old_ds_reserve = 0, new_ds_reserve = 0; + + // first year principal payment based on loan moratorium + ssc_number_t first_principal_payment = 0; + ssc_number_t first_principal_payment_batt = 0; + do + { + // first iteration - calculate debt reserve account based on initial installed cost + old_ds_reserve = new_ds_reserve; + // debt service reserve + if (loan_moratorium < 1) + { + if (constant_principal) + { + if ((term_tenor - loan_moratorium) > 0) + first_principal_payment = (ssc_number_t)loan_amount / (ssc_number_t)(term_tenor - loan_moratorium); + } + else + { + first_principal_payment = (ssc_number_t)-ppmt(term_int_rate, // Rate + 1, // Period + (term_tenor - loan_moratorium), // Number periods + loan_amount, // Present Value + 0, // future Value + 0); // cash flow at end of period + } + } + else + first_principal_payment = 0; + cf.at(CF_debt_payment_principal, 1) = first_principal_payment; + cf.at(CF_debt_payment_interest, 1) = loan_amount * term_int_rate; + cf.at(CF_reserve_debtservice, 0) = dscr_reserve_months / 12.0 * (cf.at(CF_debt_payment_principal, 1) + cf.at(CF_debt_payment_interest, 1)); + cf.at(CF_funding_debtservice, 0) = cf.at(CF_reserve_debtservice, 0); + new_ds_reserve = cf.at(CF_reserve_debtservice, 0); + + // update installed cost with approximate debt reserve account for year 0 + cost_installed = + cost_prefinancing + + constr_total_financing + + cost_debt_closing + + cost_other_financing + + cf.at(CF_reserve_debtservice, 0) // initially zero - based on p&i + + cf.at(CF_reserve_om, 0) + - ibi_fed_amount + - ibi_sta_amount + - ibi_uti_amount + - ibi_oth_amount + - ibi_fed_per + - ibi_sta_per + - ibi_uti_per + - ibi_oth_per + - cbi_fed_amount + - cbi_sta_amount + - cbi_uti_amount + - cbi_oth_amount; + cost_debt_upfront = debt_frac * cost_installed * cost_debt_fee_frac; // for cash flow output + cost_installed += debt_frac *cost_installed*cost_debt_fee_frac; + loan_amount = debt_frac * cost_installed; + i_repeat++; + } while ((std::abs(new_ds_reserve - old_ds_reserve) > 1e-3) && (i_repeat < 10)); + + if (term_tenor == 0) loan_amount = 0; +// log(util::format("loan amount =%lg, debt fraction=%lg, adj installed cost=%lg", loan_amount, debt_frac, adjusted_installed_cost), SSC_WARNING); + for ( i = 1; i <= nyears; i++) + { + if (i == 1) + { + first_principal_payment = 0; + cf.at(CF_debt_balance, i - 1) = loan_amount; + cf.at(CF_debt_payment_interest, i) = loan_amount * term_int_rate; + if (i > loan_moratorium) + { + if (constant_principal) + { + if ((term_tenor - loan_moratorium) > 0) + first_principal_payment = (ssc_number_t)loan_amount / (ssc_number_t)(term_tenor - loan_moratorium); + } + else + { + first_principal_payment = (ssc_number_t)-ppmt(term_int_rate, // Rate + i, // Period + (term_tenor - loan_moratorium), // Number periods + loan_amount, // Present Value + 0, // future Value + 0); // cash flow at end of period + } + } + cf.at(CF_debt_payment_principal, 1) = first_principal_payment; + cf.at(CF_debt_balance, i) = cf.at(CF_debt_balance, i - 1) - cf.at(CF_debt_payment_principal, i); +// update reserve account + cf.at(CF_debt_payment_interest, i) = loan_amount * term_int_rate; + cf.at(CF_reserve_debtservice, i-1) = dscr_reserve_months / 12.0 * (cf.at(CF_debt_payment_principal, i) + cf.at(CF_debt_payment_interest, i)); + cf.at(CF_funding_debtservice, i-1) = cf.at(CF_reserve_debtservice, i-1); + } + else // i > 1 + { + if (i <= term_tenor) + { + cf.at(CF_debt_payment_interest, i) = term_int_rate * cf.at(CF_debt_balance, i - 1); + if (i > loan_moratorium) + { + if (constant_principal) + { + if ((term_tenor - loan_moratorium) > 0) + cf.at(CF_debt_payment_principal, i) = loan_amount / (term_tenor - loan_moratorium); + } + else + { + if (term_int_rate != 0.0) + { + cf.at(CF_debt_payment_principal, i) = term_int_rate * loan_amount / (1 - pow((1 + term_int_rate), -(term_tenor - loan_moratorium))) + - cf.at(CF_debt_payment_interest, i); + } + else + { + cf.at(CF_debt_payment_principal, i) = loan_amount / (term_tenor - loan_moratorium) - cf.at(CF_debt_payment_interest, i); + } + } + } + else + { + cf.at(CF_debt_payment_principal, i) = 0; + } + + // debt service reserve + cf.at(CF_reserve_debtservice, i - 1) = dscr_reserve_months / 12.0 * (cf.at(CF_debt_payment_principal, i) + cf.at(CF_debt_payment_interest, i)); + cf.at(CF_funding_debtservice, i - 1) = cf.at(CF_reserve_debtservice, i - 1); + cf.at(CF_funding_debtservice, i - 1) -= cf.at(CF_reserve_debtservice, i - 2); + if (i == term_tenor) cf.at(CF_disbursement_debtservice, i) = 0 - cf.at (CF_reserve_debtservice, i - 1); + + } + cf.at(CF_debt_balance, i) = cf.at(CF_debt_balance, i - 1) - cf.at(CF_debt_payment_principal, i); + + } + + cf.at(CF_debt_payment_total, i) = cf.at(CF_debt_payment_principal, i) + cf.at(CF_debt_payment_interest, i); + cf.at(CF_debt_size, i) = cf.at(CF_debt_payment_principal, i); +// log(util::format("year=%d, debt balance =%lg, debt interest=%lg, debt principal=%lg, total debt payment=%lg, debt size=%lg", i, cf.at(CF_debt_balance, i), cf.at(CF_debt_payment_interest, i), cf.at(CF_debt_payment_principal, i), cf.at(CF_debt_payment_total, i), cf.at(CF_debt_size, i)), SSC_WARNING); + + size_of_debt += cf.at(CF_debt_size, i); + } + cf.at(CF_debt_balance, 0) = loan_amount; +// log(util::format("size of debt=%lg.", size_of_debt), SSC_WARNING); + + } + + +// log(util::format("before loop - size of debt =%lg .", size_of_debt), SSC_WARNING); + + + double dscr = dscr_input; // reset to input and limit to max debt fraction if necessary line 2298 and Github issue 550 + +/***************** begin iterative solution *********************************************************************/ + + do + { + + flip_year=-1; + cash_for_debt_service=0; + pv_cafds=0; + if (constant_dscr_mode) { + size_of_debt = 0; + dscr = dscr_input; + } + if (ppa_interval_found) ppa = (w0*x1+w1*x0)/(w0 + w1); + + // debt pre calculation + for (i=1; i<=nyears; i++) + { + // Project partial income statement + // energy_value = DHF Total PPA Revenue (cents/kWh) + if ((ppa_mode == 1) && (count_ppa_price_input > 1)) + { + if (i <= (int)count_ppa_price_input) + cf.at(CF_ppa_price, i) = ppa_price_input[i - 1] * 100.0; // $/kWh to cents/kWh + else + cf.at(CF_ppa_price, i) = 0; + } + else + cf.at(CF_ppa_price, i) = ppa * pow(1 + ppa_escalation, i - 1); // ppa_mode==0 or single value +// cf.at(CF_energy_value,i) = cf.at(CF_energy_net,i) * cf.at(CF_ppa_price,i) /100.0; + // dispatch + cf.at(CF_energy_value, i) = cf.at(CF_ppa_price, i) / 100.0 *( + m_disp_calcs.tod_energy_value(i)); + +// log(util::format("year %d : energy value =%lg", i, m_disp_calcs.tod_energy_value(i)), SSC_WARNING); + // total revenue + cf.at(CF_total_revenue,i) = cf.at(CF_energy_value,i) + + cf.at(CF_thermal_value,i) + + cf.at(CF_curtailment_value, i) + + cf.at(CF_capacity_payment, i) + + pbi_fed_for_ds_frac * cf.at(CF_pbi_fed,i) + + pbi_sta_for_ds_frac * cf.at(CF_pbi_sta,i) + + pbi_uti_for_ds_frac * cf.at(CF_pbi_uti,i) + + pbi_oth_for_ds_frac * cf.at(CF_pbi_oth,i) + + cf.at(CF_net_salvage_value,i); + + cf.at(CF_ebitda,i) = cf.at(CF_total_revenue,i) - cf.at(CF_operating_expenses,i); + + + } // end of debt precalculation. + + // receivables precalculation need future energy value so outside previous loop + if (nyears>0) + { + cf.at(CF_reserve_receivables, 0) = months_receivables_reserve_frac * (cf.at(CF_energy_value, 1) + cf.at(CF_thermal_value, 1) + cf.at(CF_curtailment_value, 1) + cf.at(CF_capacity_payment, 1)); + cf.at(CF_funding_receivables, 0) = cf.at(CF_reserve_receivables, 0); + for (i = 1; i 0)*/) { + // TODO - determine if we are going to allow negative DSCR values for coverage when PPA fixed price is too low to cover expenses + if ((std::abs(size_of_debt) > (cost_installed * dscr_maximum_debt_fraction)) || (size_of_debt <0)) { + if (/*(size_of_debt > 0) &&*/ (cost_installed > 0) && (dscr_maximum_debt_fraction > 0)) { +// dscr = fabs(size_of_debt) / (cost_installed * dscr_maximum_debt_fraction) * dscr_input; + dscr = size_of_debt / (cost_installed * dscr_maximum_debt_fraction) * dscr_input; + // recalculate debt size with constrained dscr + size_of_debt = 0.0; + for (i = 0; i <= nyears; i++) { + if (dscr > 0) + cf.at(CF_debt_size, i) = cf.at(CF_pv_cash_for_ds, i) / dscr; + else + cf.at(CF_debt_size, i) = 0.0; // default behavior of initialization of cash flow line items + size_of_debt += cf.at(CF_debt_size, i); + } + + } + } + } + /* + // DSCR calculations + for (i = 0; i <= nyears; i++) + { + if (cf.at(CF_debt_payment_total, i) == 0.0) cf.at(CF_pretax_dscr, i) = 0; //cf.at(CF_pretax_dscr, i) = std::numeric_limits::quiet_NaN(); + else cf.at(CF_pretax_dscr, i) = cf.at(CF_cash_for_ds, i) / cf.at(CF_debt_payment_total, i); + } + + */ + if (constant_dscr_mode) + { + cf.at(CF_debt_balance, 0) = size_of_debt; + + for (i = 1; ((i <= nyears) && (i <= term_tenor)); i++) + { + cf.at(CF_debt_payment_interest, i) = cf.at(CF_debt_balance, i - 1) * term_int_rate; + if (dscr != 0) + cf.at(CF_debt_payment_total, i) = cf.at(CF_cash_for_ds, i) / dscr; + else + cf.at(CF_debt_payment_total, i) = cf.at(CF_debt_payment_interest, i); + cf.at(CF_debt_payment_principal, i) = cf.at(CF_debt_payment_total, i) - cf.at(CF_debt_payment_interest, i); + cf.at(CF_debt_balance, i) = cf.at(CF_debt_balance, i - 1) - cf.at(CF_debt_payment_principal, i); + } + + + // debt service reserve + for (i = 1; ((i <= nyears) && (i <= term_tenor)); i++) + { + cf.at(CF_reserve_debtservice, i - 1) = dscr_reserve_months / 12.0 * (cf.at(CF_debt_payment_principal, i) + cf.at(CF_debt_payment_interest, i)); + cf.at(CF_funding_debtservice, i - 1) = cf.at(CF_reserve_debtservice, i - 1); + if (i > 1) cf.at(CF_funding_debtservice, i - 1) -= cf.at(CF_reserve_debtservice, i - 2); + if (i == term_tenor) cf.at(CF_disbursement_debtservice, i) = 0 - cf.at(CF_reserve_debtservice, i - 1); + } + } + + // total reserves + for (i=0; i<=nyears; i++) + cf.at(CF_reserve_total,i) = + cf.at(CF_reserve_debtservice,i) + + cf.at(CF_reserve_om, i) + + cf.at(CF_reserve_receivables, i) + + cf.at(CF_reserve_equip1, i) + + cf.at(CF_reserve_equip2,i) + + cf.at(CF_reserve_equip3,i); + for (i=1; i<=nyears; i++) + cf.at(CF_reserve_interest,i) = reserves_interest * cf.at(CF_reserve_total,i-1); + + + cost_financing = + cost_debt_closing + + cost_debt_fee_frac * size_of_debt + + cost_other_financing + + cf.at(CF_reserve_debtservice, 0) + + constr_total_financing + + cf.at(CF_reserve_om, 0) + + cf.at(CF_reserve_receivables, 0); + + cost_debt_upfront = cost_debt_fee_frac * size_of_debt; // cpg added this to make cash flow consistent with single_owner.xlsx + + cost_installed = cost_prefinancing + cost_financing + - ibi_fed_amount + - ibi_sta_amount + - ibi_uti_amount + - ibi_oth_amount + - ibi_fed_per + - ibi_sta_per + - ibi_uti_per + - ibi_oth_per + - cbi_fed_amount + - cbi_sta_amount + - cbi_uti_amount + - cbi_oth_amount; + + // Installed costs and construction costs, developer fees, and legal fees can be claimed in the basis, but reserves and financing fees cannot + // See https://github.com/NREL/SAM/issues/1803 and linked issues for more details + pre_depr_alloc_basis = cost_prefinancing + constr_total_financing + cost_other_financing; + + // Basis reductions are handled in depr_fed_reduction and depr_sta_reduction + + // Under 2024 law these are understood to be the same, keep seperate variables for reporting out + pre_itc_qual_basis = pre_depr_alloc_basis; + + + depr_alloc_total = depr_alloc_total_frac * pre_depr_alloc_basis; + depr_alloc_macrs_5 = depr_alloc_macrs_5_frac * depr_alloc_total; + depr_alloc_macrs_15 = depr_alloc_macrs_15_frac * depr_alloc_total; + depr_alloc_sl_5 = depr_alloc_sl_5_frac * depr_alloc_total; + depr_alloc_sl_15 = depr_alloc_sl_15_frac * depr_alloc_total; + depr_alloc_sl_20 = depr_alloc_sl_20_frac * depr_alloc_total; + depr_alloc_sl_39 = depr_alloc_sl_39_frac * depr_alloc_total; + depr_alloc_custom = depr_alloc_custom_frac * depr_alloc_total; + depr_alloc_none = depr_alloc_none_frac * depr_alloc_total; + + itc_sta_qual_macrs_5 = itc_sta_qual_macrs_5_frac * ( depr_alloc_macrs_5 - depr_stabas_macrs_5_frac * depr_sta_reduction); + itc_sta_qual_macrs_15 = itc_sta_qual_macrs_15_frac * ( depr_alloc_macrs_15 - depr_stabas_macrs_15_frac * depr_sta_reduction); + itc_sta_qual_sl_5 = itc_sta_qual_sl_5_frac * ( depr_alloc_sl_5 - depr_stabas_sl_5_frac * depr_sta_reduction); + itc_sta_qual_sl_15 = itc_sta_qual_sl_15_frac * ( depr_alloc_sl_15 - depr_stabas_sl_15_frac * depr_sta_reduction); + itc_sta_qual_sl_20 = itc_sta_qual_sl_20_frac * ( depr_alloc_sl_20 - depr_stabas_sl_20_frac * depr_sta_reduction); + itc_sta_qual_sl_39 = itc_sta_qual_sl_39_frac * ( depr_alloc_sl_39 - depr_stabas_sl_39_frac * depr_sta_reduction); + itc_sta_qual_custom = itc_sta_qual_custom_frac * ( depr_alloc_custom - depr_stabas_custom_frac * depr_sta_reduction); + + itc_sta_qual_total = itc_sta_qual_macrs_5 + itc_sta_qual_macrs_15 + itc_sta_qual_sl_5 +itc_sta_qual_sl_15 +itc_sta_qual_sl_20 + itc_sta_qual_sl_39 + itc_sta_qual_custom; + + // SAM 1038 + itc_sta_per = 0.0; + for (size_t k = 0; k <= nyears; k++) { + cf.at(CF_itc_sta_percent_amount, k) = min(cf.at(CF_itc_sta_percent_maxvalue, k), cf.at(CF_itc_sta_percent_fraction, k) * itc_sta_qual_total); + itc_sta_per += cf.at(CF_itc_sta_percent_amount, k); + } + + if (itc_sta_qual_total > 0) + { + itc_disallow_sta_percent_macrs_5 = itc_sta_qual_macrs_5_frac * (itc_sta_disallow_factor * itc_sta_qual_macrs_5 / itc_sta_qual_total * itc_sta_per); + itc_disallow_sta_percent_macrs_15 = itc_sta_qual_macrs_15_frac * (itc_sta_disallow_factor * itc_sta_qual_macrs_15 / itc_sta_qual_total * itc_sta_per); + itc_disallow_sta_percent_sl_5 = itc_sta_qual_sl_5_frac * (itc_sta_disallow_factor * itc_sta_qual_sl_5 / itc_sta_qual_total * itc_sta_per); + itc_disallow_sta_percent_sl_15 = itc_sta_qual_sl_15_frac * (itc_sta_disallow_factor * itc_sta_qual_sl_15 / itc_sta_qual_total * itc_sta_per); + itc_disallow_sta_percent_sl_20 = itc_sta_qual_sl_20_frac * (itc_sta_disallow_factor * itc_sta_qual_sl_20 / itc_sta_qual_total * itc_sta_per); + itc_disallow_sta_percent_sl_39 = itc_sta_qual_sl_39_frac * (itc_sta_disallow_factor * itc_sta_qual_sl_39 / itc_sta_qual_total * itc_sta_per); + itc_disallow_sta_percent_custom = itc_sta_qual_custom_frac * (itc_sta_disallow_factor * itc_sta_qual_custom / itc_sta_qual_total * itc_sta_per); + + itc_disallow_sta_fixed_macrs_5 = itc_sta_qual_macrs_5_frac * (itc_sta_disallow_factor * itc_sta_qual_macrs_5 / itc_sta_qual_total * itc_sta_amount); + itc_disallow_sta_fixed_macrs_15 = itc_sta_qual_macrs_15_frac * (itc_sta_disallow_factor * itc_sta_qual_macrs_15 / itc_sta_qual_total * itc_sta_amount); + itc_disallow_sta_fixed_sl_5 = itc_sta_qual_sl_5_frac * (itc_sta_disallow_factor * itc_sta_qual_sl_5 / itc_sta_qual_total * itc_sta_amount); + itc_disallow_sta_fixed_sl_15 = itc_sta_qual_sl_15_frac * (itc_sta_disallow_factor * itc_sta_qual_sl_15 / itc_sta_qual_total * itc_sta_amount); + itc_disallow_sta_fixed_sl_20 = itc_sta_qual_sl_20_frac * (itc_sta_disallow_factor * itc_sta_qual_sl_20 / itc_sta_qual_total * itc_sta_amount); + itc_disallow_sta_fixed_sl_39 = itc_sta_qual_sl_39_frac * (itc_sta_disallow_factor * itc_sta_qual_sl_39 / itc_sta_qual_total * itc_sta_amount); + itc_disallow_sta_fixed_custom = itc_sta_qual_custom_frac * (itc_sta_disallow_factor * itc_sta_qual_custom / itc_sta_qual_total * itc_sta_amount); + } + else + { + itc_disallow_sta_percent_macrs_5 = 0; + itc_disallow_sta_percent_macrs_15 = 0; + itc_disallow_sta_percent_sl_5 = 0; + itc_disallow_sta_percent_sl_15 = 0; + itc_disallow_sta_percent_sl_20 = 0; + itc_disallow_sta_percent_sl_39 = 0; + itc_disallow_sta_percent_custom = 0; + + itc_disallow_sta_fixed_macrs_5 = 0; + itc_disallow_sta_fixed_macrs_15 = 0; + itc_disallow_sta_fixed_sl_5 = 0; + itc_disallow_sta_fixed_sl_15 = 0; + itc_disallow_sta_fixed_sl_20 = 0; + itc_disallow_sta_fixed_sl_39 = 0; + itc_disallow_sta_fixed_custom = 0; + } + + itc_fed_qual_macrs_5 = itc_fed_qual_macrs_5_frac * ( depr_alloc_macrs_5 - depr_fedbas_macrs_5_frac * depr_fed_reduction); + itc_fed_qual_macrs_15 = itc_fed_qual_macrs_15_frac * ( depr_alloc_macrs_15 - depr_fedbas_macrs_15_frac * depr_fed_reduction); + itc_fed_qual_sl_5 = itc_fed_qual_sl_5_frac * ( depr_alloc_sl_5 - depr_fedbas_sl_5_frac * depr_fed_reduction); + itc_fed_qual_sl_15 = itc_fed_qual_sl_15_frac * ( depr_alloc_sl_15 - depr_fedbas_sl_15_frac * depr_fed_reduction); + itc_fed_qual_sl_20 = itc_fed_qual_sl_20_frac * ( depr_alloc_sl_20 - depr_fedbas_sl_20_frac * depr_fed_reduction); + itc_fed_qual_sl_39 = itc_fed_qual_sl_39_frac * ( depr_alloc_sl_39 - depr_fedbas_sl_39_frac * depr_fed_reduction); + itc_fed_qual_custom = itc_fed_qual_custom_frac * ( depr_alloc_custom - depr_fedbas_custom_frac * depr_fed_reduction); + + itc_fed_qual_total = itc_fed_qual_macrs_5 + itc_fed_qual_macrs_15 + itc_fed_qual_sl_5 +itc_fed_qual_sl_15 +itc_fed_qual_sl_20 + itc_fed_qual_sl_39 + itc_fed_qual_custom; + + // SAM 1038 + itc_fed_per = 0.0; + for (size_t k = 0; k <= nyears; k++) { + cf.at(CF_itc_fed_percent_amount, k) = min(cf.at(CF_itc_fed_percent_maxvalue, k), cf.at(CF_itc_fed_percent_fraction, k) * itc_fed_qual_total); + itc_fed_per += cf.at(CF_itc_fed_percent_amount, k); + } + + if (itc_fed_qual_total > 0) + { + itc_disallow_fed_percent_macrs_5 = itc_fed_qual_macrs_5_frac * (itc_fed_disallow_factor * itc_fed_qual_macrs_5 / itc_fed_qual_total * itc_fed_per); + itc_disallow_fed_percent_macrs_15 = itc_fed_qual_macrs_15_frac * (itc_fed_disallow_factor * itc_fed_qual_macrs_15 / itc_fed_qual_total * itc_fed_per); + itc_disallow_fed_percent_sl_5 = itc_fed_qual_sl_5_frac * (itc_fed_disallow_factor * itc_fed_qual_sl_5 / itc_fed_qual_total * itc_fed_per); + itc_disallow_fed_percent_sl_15 = itc_fed_qual_sl_15_frac * (itc_fed_disallow_factor * itc_fed_qual_sl_15 / itc_fed_qual_total * itc_fed_per); + itc_disallow_fed_percent_sl_20 = itc_fed_qual_sl_20_frac * (itc_fed_disallow_factor * itc_fed_qual_sl_20 / itc_fed_qual_total * itc_fed_per); + itc_disallow_fed_percent_sl_39 = itc_fed_qual_sl_39_frac * (itc_fed_disallow_factor * itc_fed_qual_sl_39 / itc_fed_qual_total * itc_fed_per); + itc_disallow_fed_percent_custom = itc_fed_qual_custom_frac * (itc_fed_disallow_factor * itc_fed_qual_custom / itc_fed_qual_total * itc_fed_per); + + itc_disallow_fed_fixed_macrs_5 = itc_fed_qual_macrs_5_frac * (itc_fed_disallow_factor * itc_fed_qual_macrs_5 / itc_fed_qual_total * itc_fed_amount); + itc_disallow_fed_fixed_macrs_15 = itc_fed_qual_macrs_15_frac * (itc_fed_disallow_factor * itc_fed_qual_macrs_15 / itc_fed_qual_total * itc_fed_amount); + itc_disallow_fed_fixed_sl_5 = itc_fed_qual_sl_5_frac * (itc_fed_disallow_factor * itc_fed_qual_sl_5 / itc_fed_qual_total * itc_fed_amount); + itc_disallow_fed_fixed_sl_15 = itc_fed_qual_sl_15_frac * (itc_fed_disallow_factor * itc_fed_qual_sl_15 / itc_fed_qual_total * itc_fed_amount); + itc_disallow_fed_fixed_sl_20 = itc_fed_qual_sl_20_frac * (itc_fed_disallow_factor * itc_fed_qual_sl_20 / itc_fed_qual_total * itc_fed_amount); + itc_disallow_fed_fixed_sl_39 = itc_fed_qual_sl_39_frac * (itc_fed_disallow_factor * itc_fed_qual_sl_39 / itc_fed_qual_total * itc_fed_amount); + itc_disallow_fed_fixed_custom = itc_fed_qual_custom_frac * (itc_fed_disallow_factor * itc_fed_qual_custom / itc_fed_qual_total * itc_fed_amount); + } + else + { + itc_disallow_fed_percent_macrs_5 = 0; + itc_disallow_fed_percent_macrs_15 = 0; + itc_disallow_fed_percent_sl_5 = 0; + itc_disallow_fed_percent_sl_15 = 0; + itc_disallow_fed_percent_sl_20 = 0; + itc_disallow_fed_percent_sl_39 = 0; + itc_disallow_fed_percent_custom = 0; + + itc_disallow_fed_fixed_macrs_5 = 0; + itc_disallow_fed_fixed_macrs_15 = 0; + itc_disallow_fed_fixed_sl_5 = 0; + itc_disallow_fed_fixed_sl_15 = 0; + itc_disallow_fed_fixed_sl_20 = 0; + itc_disallow_fed_fixed_sl_39 = 0; + itc_disallow_fed_fixed_custom = 0; + } + +// SAM 1038 + for (size_t k = 0; k <= nyears; k++) { + cf.at(CF_itc_fed, k) = cf.at(CF_itc_fed_amount, k) + cf.at(CF_itc_fed_percent_amount, k); + cf.at(CF_itc_sta, k) = cf.at(CF_itc_sta_amount, k) + cf.at(CF_itc_sta_percent_amount, k); + cf.at(CF_itc_total, k) = cf.at(CF_itc_fed, k) + cf.at(CF_itc_sta, k); + } +// Depreciation +// State depreciation + depr_stabas_macrs_5 = depr_alloc_macrs_5 - depr_stabas_macrs_5_frac * depr_sta_reduction; + depr_stabas_macrs_15 = depr_alloc_macrs_15 - depr_stabas_macrs_15_frac * depr_sta_reduction; + depr_stabas_sl_5 = depr_alloc_sl_5 - depr_stabas_sl_5_frac * depr_sta_reduction; + depr_stabas_sl_15 = depr_alloc_sl_15 - depr_stabas_sl_15_frac * depr_sta_reduction; + depr_stabas_sl_20 = depr_alloc_sl_20 - depr_stabas_sl_20_frac * depr_sta_reduction; + depr_stabas_sl_39 = depr_alloc_sl_39 - depr_stabas_sl_39_frac * depr_sta_reduction; + depr_stabas_custom = depr_alloc_custom - depr_stabas_custom_frac * depr_sta_reduction; + + // ITC reduction + depr_stabas_macrs_5 -= (itc_fed_percent_deprbas_sta * itc_disallow_fed_percent_macrs_5 + + itc_fed_amount_deprbas_sta * itc_disallow_fed_fixed_macrs_5 + + itc_sta_percent_deprbas_sta * itc_disallow_sta_percent_macrs_5 + + itc_sta_amount_deprbas_sta * itc_disallow_sta_fixed_macrs_5 ); + + depr_stabas_macrs_15 -= (itc_fed_percent_deprbas_sta * itc_disallow_fed_percent_macrs_15 + + itc_fed_amount_deprbas_sta * itc_disallow_fed_fixed_macrs_15 + + itc_sta_percent_deprbas_sta * itc_disallow_sta_percent_macrs_15 + + itc_sta_amount_deprbas_sta * itc_disallow_sta_fixed_macrs_15 ); + + depr_stabas_sl_5 -= (itc_fed_percent_deprbas_sta * itc_disallow_fed_percent_sl_5 + + itc_fed_amount_deprbas_sta * itc_disallow_fed_fixed_sl_5 + + itc_sta_percent_deprbas_sta * itc_disallow_sta_percent_sl_5 + + itc_sta_amount_deprbas_sta * itc_disallow_sta_fixed_sl_5 ); + + depr_stabas_sl_15 -= (itc_fed_percent_deprbas_sta * itc_disallow_fed_percent_sl_15 + + itc_fed_amount_deprbas_sta * itc_disallow_fed_fixed_sl_15 + + itc_sta_percent_deprbas_sta * itc_disallow_sta_percent_sl_15 + + itc_sta_amount_deprbas_sta * itc_disallow_sta_fixed_sl_15 ); + + depr_stabas_sl_20 -= (itc_fed_percent_deprbas_sta * itc_disallow_fed_percent_sl_20 + + itc_fed_amount_deprbas_sta * itc_disallow_fed_fixed_sl_20 + + itc_sta_percent_deprbas_sta * itc_disallow_sta_percent_sl_20 + + itc_sta_amount_deprbas_sta * itc_disallow_sta_fixed_sl_20 ); + + depr_stabas_sl_39 -= (itc_fed_percent_deprbas_sta * itc_disallow_fed_percent_sl_39 + + itc_fed_amount_deprbas_sta * itc_disallow_fed_fixed_sl_39 + + itc_sta_percent_deprbas_sta * itc_disallow_sta_percent_sl_39 + + itc_sta_amount_deprbas_sta * itc_disallow_sta_fixed_sl_39 ); + + depr_stabas_custom -= (itc_fed_percent_deprbas_sta * itc_disallow_fed_percent_custom + + itc_fed_amount_deprbas_sta * itc_disallow_fed_fixed_custom + + itc_sta_percent_deprbas_sta * itc_disallow_sta_percent_custom + + itc_sta_amount_deprbas_sta * itc_disallow_sta_fixed_custom ); + + // Bonus depreciation + depr_stabas_macrs_5_bonus = depr_stabas_macrs_5_bonus_frac * depr_stabas_macrs_5; + depr_stabas_macrs_15_bonus = depr_stabas_macrs_15_bonus_frac * depr_stabas_macrs_15; + depr_stabas_sl_5_bonus = depr_stabas_sl_5_bonus_frac * depr_stabas_sl_5; + depr_stabas_sl_15_bonus = depr_stabas_sl_15_bonus_frac * depr_stabas_sl_15; + depr_stabas_sl_20_bonus = depr_stabas_sl_20_bonus_frac * depr_stabas_sl_20; + depr_stabas_sl_39_bonus = depr_stabas_sl_39_bonus_frac * depr_stabas_sl_39; + depr_stabas_custom_bonus = depr_stabas_custom_bonus_frac * depr_stabas_custom; + + depr_stabas_macrs_5 -= depr_stabas_macrs_5_bonus; + depr_stabas_macrs_15 -= depr_stabas_macrs_15_bonus; + depr_stabas_sl_5 -= depr_stabas_sl_5_bonus; + depr_stabas_sl_15 -= depr_stabas_sl_15_bonus; + depr_stabas_sl_20 -= depr_stabas_sl_20_bonus; + depr_stabas_sl_39 -= depr_stabas_sl_39_bonus; + depr_stabas_custom -= depr_stabas_custom_bonus; + + depr_stabas_total = depr_stabas_macrs_5 + depr_stabas_macrs_15 + depr_stabas_sl_5 + depr_stabas_sl_15 + depr_stabas_sl_20 + depr_stabas_sl_39 + depr_stabas_custom; + + // Federal depreciation + depr_fedbas_macrs_5 = depr_alloc_macrs_5 - depr_fedbas_macrs_5_frac * depr_fed_reduction; + depr_fedbas_macrs_15 = depr_alloc_macrs_15 - depr_fedbas_macrs_15_frac * depr_fed_reduction; + depr_fedbas_sl_5 = depr_alloc_sl_5 - depr_fedbas_sl_5_frac * depr_fed_reduction; + depr_fedbas_sl_15 = depr_alloc_sl_15 - depr_fedbas_sl_15_frac * depr_fed_reduction; + depr_fedbas_sl_20 = depr_alloc_sl_20 - depr_fedbas_sl_20_frac * depr_fed_reduction; + depr_fedbas_sl_39 = depr_alloc_sl_39 - depr_fedbas_sl_39_frac * depr_fed_reduction; + depr_fedbas_custom = depr_alloc_custom - depr_fedbas_custom_frac * depr_fed_reduction; + + // ITC reduction + depr_fedbas_macrs_5 -= (itc_fed_percent_deprbas_fed * itc_disallow_fed_percent_macrs_5 + + itc_fed_amount_deprbas_fed * itc_disallow_fed_fixed_macrs_5 + + itc_sta_percent_deprbas_fed * itc_disallow_sta_percent_macrs_5 + + itc_sta_amount_deprbas_fed * itc_disallow_sta_fixed_macrs_5 ); + + depr_fedbas_macrs_15 -= (itc_fed_percent_deprbas_fed * itc_disallow_fed_percent_macrs_15 + + itc_fed_amount_deprbas_fed * itc_disallow_fed_fixed_macrs_15 + + itc_sta_percent_deprbas_fed * itc_disallow_sta_percent_macrs_15 + + itc_sta_amount_deprbas_fed * itc_disallow_sta_fixed_macrs_15 ); + + depr_fedbas_sl_5 -= (itc_fed_percent_deprbas_fed * itc_disallow_fed_percent_sl_5 + + itc_fed_amount_deprbas_fed * itc_disallow_fed_fixed_sl_5 + + itc_sta_percent_deprbas_fed * itc_disallow_sta_percent_sl_5 + + itc_sta_amount_deprbas_fed * itc_disallow_sta_fixed_sl_5 ); + + depr_fedbas_sl_15 -= (itc_fed_percent_deprbas_fed * itc_disallow_fed_percent_sl_15 + + itc_fed_amount_deprbas_fed * itc_disallow_fed_fixed_sl_15 + + itc_sta_percent_deprbas_fed * itc_disallow_sta_percent_sl_15 + + itc_sta_amount_deprbas_fed * itc_disallow_sta_fixed_sl_15 ); + + depr_fedbas_sl_20 -= (itc_fed_percent_deprbas_fed * itc_disallow_fed_percent_sl_20 + + itc_fed_amount_deprbas_fed * itc_disallow_fed_fixed_sl_20 + + itc_sta_percent_deprbas_fed * itc_disallow_sta_percent_sl_20 + + itc_sta_amount_deprbas_fed * itc_disallow_sta_fixed_sl_20 ); + + depr_fedbas_sl_39 -= (itc_fed_percent_deprbas_fed * itc_disallow_fed_percent_sl_39 + + itc_fed_amount_deprbas_fed * itc_disallow_fed_fixed_sl_39 + + itc_sta_percent_deprbas_fed * itc_disallow_sta_percent_sl_39 + + itc_sta_amount_deprbas_fed * itc_disallow_sta_fixed_sl_39 ); + + depr_fedbas_custom -= (itc_fed_percent_deprbas_fed * itc_disallow_fed_percent_custom + + itc_fed_amount_deprbas_fed * itc_disallow_fed_fixed_custom + + itc_sta_percent_deprbas_fed * itc_disallow_sta_percent_custom + + itc_sta_amount_deprbas_fed * itc_disallow_sta_fixed_custom ); + + // Bonus depreciation + depr_fedbas_macrs_5_bonus = depr_fedbas_macrs_5_bonus_frac * depr_fedbas_macrs_5; + depr_fedbas_macrs_15_bonus = depr_fedbas_macrs_15_bonus_frac * depr_fedbas_macrs_15; + depr_fedbas_sl_5_bonus = depr_fedbas_sl_5_bonus_frac * depr_fedbas_sl_5; + depr_fedbas_sl_15_bonus = depr_fedbas_sl_15_bonus_frac * depr_fedbas_sl_15; + depr_fedbas_sl_20_bonus = depr_fedbas_sl_20_bonus_frac * depr_fedbas_sl_20; + depr_fedbas_sl_39_bonus = depr_fedbas_sl_39_bonus_frac * depr_fedbas_sl_39; + depr_fedbas_custom_bonus = depr_fedbas_custom_bonus_frac * depr_fedbas_custom; + + depr_fedbas_macrs_5 -= depr_fedbas_macrs_5_bonus; + depr_fedbas_macrs_15 -= depr_fedbas_macrs_15_bonus; + depr_fedbas_sl_5 -= depr_fedbas_sl_5_bonus; + depr_fedbas_sl_15 -= depr_fedbas_sl_15_bonus; + depr_fedbas_sl_20 -= depr_fedbas_sl_20_bonus; + depr_fedbas_sl_39 -= depr_fedbas_sl_39_bonus; + depr_fedbas_custom -= depr_fedbas_custom_bonus; + + depr_fedbas_total = depr_fedbas_macrs_5 + depr_fedbas_macrs_15 + depr_fedbas_sl_5 + depr_fedbas_sl_15 + depr_fedbas_sl_20 + depr_fedbas_sl_39 + depr_fedbas_custom; + + purchase_of_property = -cost_installed + cf.at(CF_reserve_debtservice, 0) + cf.at(CF_reserve_om, 0) + cf.at(CF_reserve_receivables, 0); +// issuance_of_equity = cost_installed - (size_of_debt + ibi_total + cbi_total); + issuance_of_equity = cost_installed - size_of_debt; + + for (i=0; i<=nyears; i++) + { + // cf.at(CF_project_operating_activities,i) = cf.at(CF_ebitda,i) + cf.at(CF_pbi_total,i) + cf.at(CF_reserve_interest,i) - cf.at(CF_debt_payment_interest,i); + cf.at(CF_project_operating_activities,i) = cf.at(CF_ebitda,i) + cf.at(CF_reserve_interest,i) - cf.at(CF_debt_payment_interest,i) + + (1.0 - pbi_fed_for_ds_frac) * cf.at(CF_pbi_fed,i) + + (1-0 - pbi_sta_for_ds_frac) * cf.at(CF_pbi_sta,i) + + (1-0 - pbi_uti_for_ds_frac) * cf.at(CF_pbi_uti,i) + + (1-0 - pbi_oth_for_ds_frac) * cf.at(CF_pbi_oth,i); + cf.at(CF_project_dsra,i) = -cf.at(CF_funding_debtservice,i) - cf.at(CF_disbursement_debtservice,i); + cf.at(CF_project_ra,i) = + cf.at(CF_project_dsra,i) + + cf.at(CF_project_wcra, i) + + cf.at(CF_project_receivablesra, i) + + cf.at(CF_project_me1ra, i) + + cf.at(CF_project_me2ra,i) + + cf.at(CF_project_me3ra,i); + cf.at(CF_project_me1cs,i) = cf.at(CF_disbursement_equip1,i); + cf.at(CF_project_me2cs,i) = cf.at(CF_disbursement_equip2,i); + cf.at(CF_project_me3cs,i) = cf.at(CF_disbursement_equip3,i); + cf.at(CF_project_mecs,i) = + cf.at(CF_project_me1cs,i) + + cf.at(CF_project_me2cs,i) + + cf.at(CF_project_me3cs,i); + cf.at(CF_project_investing_activities,i) = cf.at(CF_project_ra,i) + cf.at(CF_project_mecs,i); + if (i==0) cf.at(CF_project_investing_activities,i) += purchase_of_property; + + cf.at(CF_project_financing_activities,i) = -cf.at(CF_debt_payment_principal,i); + if (i == 0) cf.at(CF_project_financing_activities, i) += issuance_of_equity + size_of_debt; + + cf.at(CF_pretax_cashflow,i) = cf.at(CF_project_operating_activities,i) + cf.at(CF_project_investing_activities,i) + cf.at(CF_project_financing_activities,i); + + cf.at(CF_project_return_pretax,i) = cf.at(CF_pretax_cashflow,i); + if (i==0) cf.at(CF_project_return_pretax,i) -= (issuance_of_equity); + + cf.at(CF_project_return_pretax_irr,i) = irr(CF_project_return_pretax,i)*100.0; + cf.at(CF_project_return_pretax_npv,i) = npv(CF_project_return_pretax,i,nom_discount_rate) + cf.at(CF_project_return_pretax,0) ; + + cf.at(CF_project_return_aftertax_cash,i) = cf.at(CF_project_return_pretax,i); + } + + + cf.at(CF_project_return_aftertax,0) = cf.at(CF_project_return_aftertax_cash,0); + cf.at(CF_project_return_aftertax_irr,0) = irr(CF_project_return_aftertax_tax,0)*100.0; + cf.at(CF_project_return_aftertax_max_irr,0) = cf.at(CF_project_return_aftertax_irr,0); + cf.at(CF_project_return_aftertax_npv,0) = cf.at(CF_project_return_aftertax,0) ; + + + for (i=1;i<=nyears;i++) + { + cf.at(CF_stadepr_macrs_5,i) = cf.at(CF_macrs_5_frac,i) * depr_stabas_macrs_5; + cf.at(CF_stadepr_macrs_15,i) = cf.at(CF_macrs_15_frac,i) * depr_stabas_macrs_15; + cf.at(CF_stadepr_sl_5,i) = cf.at(CF_sl_5_frac,i) * depr_stabas_sl_5; + cf.at(CF_stadepr_sl_15,i) = cf.at(CF_sl_15_frac,i) * depr_stabas_sl_15; + cf.at(CF_stadepr_sl_20,i) = cf.at(CF_sl_20_frac,i) * depr_stabas_sl_20; + cf.at(CF_stadepr_sl_39,i) = cf.at(CF_sl_39_frac,i) * depr_stabas_sl_39; + cf.at(CF_stadepr_custom,i) = cf.at(CF_custom_frac,i) * depr_stabas_custom; + + + cf.at(CF_stadepr_total,i)= + cf.at(CF_stadepr_macrs_5,i)+ + cf.at(CF_stadepr_macrs_15,i)+ + cf.at(CF_stadepr_sl_5,i)+ + cf.at(CF_stadepr_sl_15,i)+ + cf.at(CF_stadepr_sl_20,i)+ + cf.at(CF_stadepr_sl_39,i)+ + cf.at(CF_stadepr_custom,i)+ + cf.at(CF_stadepr_me1,i)+ + cf.at(CF_stadepr_me2,i)+ + cf.at(CF_stadepr_me3,i); + + if (i==1) cf.at(CF_stadepr_total,i) += ( depr_stabas_macrs_5_bonus +depr_stabas_macrs_15_bonus + depr_stabas_sl_5_bonus + depr_stabas_sl_15_bonus + depr_stabas_sl_20_bonus + depr_stabas_sl_39_bonus + depr_stabas_custom_bonus); + cf.at(CF_statax_income_prior_incentives,i)= + cf.at(CF_ebitda,i) + + cf.at(CF_reserve_interest,i) - + cf.at(CF_debt_payment_interest,i) - + cf.at(CF_stadepr_total,i); + + + + // pbi in ebitda - so remove if non-taxable + // 5/1/11 + cf.at(CF_statax_income_with_incentives,i) = cf.at(CF_statax_income_prior_incentives,i) + cf.at(CF_statax_taxable_incentives,i); + cf.at(CF_statax, i) = -cf.at(CF_state_tax_frac, i) * cf.at(CF_statax_income_with_incentives, i); + +// federal + cf.at(CF_feddepr_macrs_5,i) = cf.at(CF_macrs_5_frac,i) * depr_fedbas_macrs_5; + cf.at(CF_feddepr_macrs_15,i) = cf.at(CF_macrs_15_frac,i) * depr_fedbas_macrs_15; + cf.at(CF_feddepr_sl_5,i) = cf.at(CF_sl_5_frac,i) * depr_fedbas_sl_5; + cf.at(CF_feddepr_sl_15,i) = cf.at(CF_sl_15_frac,i) * depr_fedbas_sl_15; + cf.at(CF_feddepr_sl_20,i) = cf.at(CF_sl_20_frac,i) * depr_fedbas_sl_20; + cf.at(CF_feddepr_sl_39,i) = cf.at(CF_sl_39_frac,i) * depr_fedbas_sl_39; + cf.at(CF_feddepr_custom,i) = cf.at(CF_custom_frac,i) * depr_fedbas_custom; + cf.at(CF_feddepr_total,i)= + cf.at(CF_feddepr_macrs_5,i)+ + cf.at(CF_feddepr_macrs_15,i)+ + cf.at(CF_feddepr_sl_5,i)+ + cf.at(CF_feddepr_sl_15,i)+ + cf.at(CF_feddepr_sl_20,i)+ + cf.at(CF_feddepr_sl_39,i)+ + cf.at(CF_feddepr_custom,i)+ + cf.at(CF_feddepr_me1,i)+ + cf.at(CF_feddepr_me2,i)+ + cf.at(CF_feddepr_me3,i); + if (i==1) cf.at(CF_feddepr_total,i) += ( depr_fedbas_macrs_5_bonus +depr_fedbas_macrs_15_bonus + depr_fedbas_sl_5_bonus + depr_fedbas_sl_15_bonus + depr_fedbas_sl_20_bonus + depr_fedbas_sl_39_bonus + depr_fedbas_custom_bonus); + cf.at(CF_fedtax_income_prior_incentives,i)= + cf.at(CF_ebitda,i) + + cf.at(CF_reserve_interest,i) - + cf.at(CF_debt_payment_interest,i) - + cf.at(CF_feddepr_total,i) + + cf.at(CF_statax,i) + + cf.at(CF_ptc_sta,i) + + cf.at(CF_itc_sta, i); +// SAM 1038 if (i==1) cf.at(CF_fedtax_income_prior_incentives,i) += itc_sta_total; + + + // pbi in ebitda - so remove if non-taxable + // 5/1/11 + cf.at(CF_fedtax_income_with_incentives,i) = cf.at(CF_fedtax_income_prior_incentives,i) + cf.at(CF_fedtax_taxable_incentives,i); + cf.at(CF_fedtax, i) = -cf.at(CF_federal_tax_frac, i) * cf.at(CF_fedtax_income_with_incentives, i); + + cf.at(CF_project_return_aftertax,i) = + cf.at(CF_project_return_aftertax_cash,i) + + cf.at(CF_ptc_fed,i) + cf.at(CF_ptc_sta,i) + + cf.at(CF_statax,i) + cf.at(CF_fedtax,i) + cf.at(CF_itc_total, i); +// SAM 1038 if (i==1) cf.at(CF_project_return_aftertax,i) += itc_total; + + cf.at(CF_project_return_aftertax_irr,i) = irr(CF_project_return_aftertax,i)*100.0; + cf.at(CF_project_return_aftertax_max_irr,i) = max(cf.at(CF_project_return_aftertax_max_irr,i-1),cf.at(CF_project_return_aftertax_irr,i)); + cf.at(CF_project_return_aftertax_npv,i) = npv(CF_project_return_aftertax,i,nom_discount_rate) + cf.at(CF_project_return_aftertax,0) ; + + if (flip_year <=0) + { + double residual = std::abs(cf.at(CF_project_return_aftertax_irr, i) - flip_target_percent) / 100.0; // solver checks fractions and not percentages + if ( ( cf.at(CF_project_return_aftertax_max_irr,i-1) < flip_target_percent ) && ( residual < ppa_soln_tolerance ) ) + { + flip_year = i; + cf.at(CF_project_return_aftertax_max_irr,i)=flip_target_percent; //within tolerance so pre-flip and post-flip percentages applied correctly + } + else if ((cf.at(CF_project_return_aftertax_max_irr, i - 1) < flip_target_percent) && (cf.at(CF_project_return_aftertax_max_irr, i) >= flip_target_percent)) flip_year = i; + } + + + } + cf.at(CF_project_return_aftertax_npv,0) = cf.at(CF_project_return_aftertax,0) ; + + // 12/14/12 - address issue from Eric Lantz - ppa solution when target mode and ppa < 0 + ppa_old = ppa; + + if (ppa_mode == 0) + { + // 12/14/12 - address issue from Eric Lantz - ppa solution when target mode and ppa < 0 + double resid_denom = max(flip_target_percent,1); + // 12/14/12 - address issue from Eric Lantz - ppa solution when target mode and ppa < 0 + double ppa_denom = max(x0, x1); + if (ppa_denom <= ppa_soln_tolerance) ppa_denom = 1; + double residual = cf.at(CF_project_return_aftertax_irr, flip_target_year) - flip_target_percent; + solved = ((std::abs( residual )/resid_denom < ppa_soln_tolerance ) || (std::abs(x0-x1)/ppa_denom < ppa_soln_tolerance) ); +// solved = (( fabs( residual ) < ppa_soln_tolerance ) ); + double flip_frac = flip_target_percent/100.0; + double itnpv_target = npv(CF_project_return_aftertax,flip_target_year,flip_frac) + cf.at(CF_project_return_aftertax,0) ; +// double itnpv_target_delta = npv(CF_project_return_aftertax,flip_target_year,flip_frac+0.001) + cf.at(CF_project_return_aftertax,0) ; + // double itnpv_actual = npv(CF_project_return_aftertax,flip_target_year,cf.at(CF_project_return_aftertax_irr, flip_target_year)) + cf.at(CF_project_return_aftertax,0) ; + // double itnpv_actual_delta = npv(CF_project_return_aftertax,flip_target_year,cf.at(CF_project_return_aftertax_irr, flip_target_year)+0.001) + cf.at(CF_project_return_aftertax,0) ; + if (!solved) + { +// double flip_frac = flip_target_percent/100.0; +// double itnpv_target = npv(CF_project_return_aftertax,flip_target_year,flip_frac) + cf.at(CF_project_return_aftertax,0) ; + irr_weighting_factor = std::abs(itnpv_target); + irr_is_minimally_met = ((irr_weighting_factor < ppa_soln_tolerance)); + irr_greater_than_target = (( itnpv_target >= 0.0) || irr_is_minimally_met ); + if (ppa_interval_found) + {// reset interval + + if (irr_greater_than_target) // too large + { + // set endpoint of weighted interval x0::quiet_NaN(); + if (flip_year > -1) + { + actual_flip_irr = cf.at(CF_project_return_aftertax_irr, flip_target_year); + assign("flip_actual_year", var_data((ssc_number_t)flip_year)); + } + else + { + assign("flip_actual_year", var_data((ssc_number_t)actual_flip_irr)); + } + assign("flip_actual_irr", var_data((ssc_number_t)actual_flip_irr)); + + // NPV of revenue components for stacked bar chart + /* + { SSC_OUTPUT, SSC_NUMBER, "npv_curtailment_revenue", "Present value of curtailment payment revenue", "$", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "npv_capacity_revenue", "Present value of capacity payment revenue", "$", "", "Metrics", "*", "", "" }, + // only count toward revenue if user selected + { SSC_OUTPUT, SSC_NUMBER, "npv_fed_pbi_income", "Present value of federal PBI income", "$", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "npv_sta_pbi_income", "Present value of state PBI income", "$", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "npv_uti_pbi_income", "Present value of utility PBI income", "$", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "npv_oth_pbi_income", "Present value of other PBI income", "$", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "npv_salvage_value", "Present value of salvage value", "$", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "npv_thermal_value", "Present value of thermal value", "$", "", "Metrics", "*", "", "" }, + + */ + assign("npv_curtailment_revenue", var_data((ssc_number_t)npv(CF_curtailment_value, nyears, nom_discount_rate))); + assign("npv_capacity_revenue", var_data((ssc_number_t)npv(CF_capacity_payment, nyears, nom_discount_rate))); + assign("npv_fed_pbi_income", var_data((ssc_number_t)npv(CF_pbi_fed, nyears, nom_discount_rate))); + assign("npv_sta_pbi_income", var_data((ssc_number_t)npv(CF_pbi_sta, nyears, nom_discount_rate))); + assign("npv_uti_pbi_income", var_data((ssc_number_t)npv(CF_pbi_uti, nyears, nom_discount_rate))); + assign("npv_oth_pbi_income", var_data((ssc_number_t)npv(CF_pbi_oth, nyears, nom_discount_rate))); + assign("npv_salvage_value", var_data((ssc_number_t)npv(CF_net_salvage_value, nyears, nom_discount_rate))); + assign("npv_thermal_value", var_data((ssc_number_t)npv(CF_thermal_value, nyears, nom_discount_rate))); + + // LPPA - change form total revenue to PPA revenue 7/19/15 consistent with DHF v4.4 + // fixed price PPA - LPPA independent of salvage value per 7/16/15 meeting + // Thermal value not included in LPPA calculation but in total revenue. + double npv_ppa_revenue = npv(CF_energy_value, nyears, nom_discount_rate); +// double npv_ppa_revenue = npv(CF_total_revenue, nyears, nom_discount_rate); + double npv_energy_nom = npv(CF_energy_sales, nyears, nom_discount_rate); + double lppa_nom = 0; + if (npv_energy_nom != 0) lppa_nom = npv_ppa_revenue / npv_energy_nom * 100.0; + double lppa_real = 0; + double npv_energy_real = npv(CF_energy_sales,nyears,disc_real); + if (npv_energy_real != 0) lppa_real = npv_ppa_revenue / npv_energy_real * 100.0; + + // update LCOE calculations + double lcoe_nom = lppa_nom; + double lcoe_real = lppa_real; + + // from single_owner.xlsm + cf.at(CF_Annual_Costs, 0) = -issuance_of_equity; + for (i = 1; i <= nyears; i++) + { + cf.at(CF_Annual_Costs, i) = + cf.at(CF_pbi_total, i) + + cf.at(CF_statax, i) + + cf.at(CF_fedtax, i) + - cf.at(CF_debt_payment_interest, i) + - cf.at(CF_debt_payment_principal, i) + - cf.at(CF_operating_expenses, i) + // incentives (cbi and ibi in installed cost and itc in year 1 below + // TODO - check PBI + + cf.at(CF_ptc_fed, i) + + cf.at(CF_ptc_sta, i) + // reserve accounts + - cf.at(CF_funding_equip1, i) + - cf.at(CF_funding_equip2, i) + - cf.at(CF_funding_equip3, i) + - cf.at(CF_funding_om, i) + - cf.at(CF_funding_receivables, i) + - cf.at(CF_funding_debtservice, i) + + cf.at(CF_reserve_interest, i) + - cf.at(CF_disbursement_debtservice, i) // note sign is negative for positive disbursement + - cf.at(CF_disbursement_om, i) // note sign is negative for positive disbursement + + cf.at(CF_net_salvage_value, i) // benefit to cost reduction so that project revenue based on PPA revenue and not total revenue per 7/16/15 meeting + + cf.at(CF_itc_total, i); // SAM 1038 + } + // year 1 add total ITC (net benefit) so that project return = project revenue - project cost + //if (nyears >= 1) cf.at(CF_Annual_Costs, 1) += itc_total; + + double npv_annual_costs = -(npv(CF_Annual_Costs, nyears, nom_discount_rate) + + cf.at(CF_Annual_Costs, 0)); + if (npv_energy_nom != 0) lcoe_nom = npv_annual_costs / npv_energy_nom * 100.0; + if (npv_energy_real != 0) lcoe_real = npv_annual_costs / npv_energy_real * 100.0; + + assign("npv_annual_costs", var_data((ssc_number_t)npv_annual_costs)); + save_cf(CF_Annual_Costs, nyears, "cf_annual_costs"); + + /////////////////////////////////////////////////////////////////////// + //LCOS Calculations + if (is_assigned("battery_total_cost_lcos") && as_double("battery_total_cost_lcos") != 0) { + for (int y = 0; y <= nyears; y++) { + cf_lcos.at(0, y) = cf.at(CF_battery_replacement_cost, y); + cf_lcos.at(1, y) = cf.at(CF_battery_replacement_cost_schedule, y); + cf_lcos.at(2, y) = cf.at(CF_ppa_price, y); + cf_lcos.at(6, y) = cf.at(CF_om_fixed1_expense, y); //Fixed OM Battery cost + cf_lcos.at(7, y) = cf.at(CF_om_production1_expense, y); //Production OM Battery cost + cf_lcos.at(8, y) = cf.at(CF_om_capacity1_expense, y); //Capacity OM Battery Cost + } + int grid_charging_cost_version = 1; + ssc_number_t* tod_multipliers; + size_t* n_tod_multipliers = 0; + lcos_calc(this, cf_lcos, nyears, nom_discount_rate, inflation_rate, lcoe_real, cost_prefinancing, disc_real, grid_charging_cost_version); + } + ///////////////////////////////////////////////////////////////////////////////////////// + + if (as_integer("en_batt") == 1 || as_integer("en_standalone_batt") == 1 || as_integer("en_wave_batt") == 1 || as_integer("is_hybrid") == 1) { + update_battery_outputs(this, nyears); + } + update_fuelcell_outputs(this, nyears); + + + // DSCR calculations + for (i = 0; i <= nyears; i++) + { + if (cf.at(CF_debt_payment_total, i) == 0.0) cf.at(CF_pretax_dscr, i) = 0; //cf.at(CF_pretax_dscr, i) = std::numeric_limits::quiet_NaN(); + else cf.at(CF_pretax_dscr, i) = cf.at(CF_cash_for_ds, i) / cf.at(CF_debt_payment_total, i); + } + double min_dscr = min_cashflow_value(CF_pretax_dscr, nyears); + assign("min_dscr", var_data((ssc_number_t)min_dscr)); + save_cf(CF_pretax_dscr, nyears, "cf_pretax_dscr"); + + + + double npv_fed_ptc = npv(CF_ptc_fed,nyears,nom_discount_rate); + double npv_sta_ptc = npv(CF_ptc_sta,nyears,nom_discount_rate); + +// double effective_tax_rate = state_tax_rate + (1.0-state_tax_rate)*federal_tax_rate; + npv_fed_ptc /= (1.0 - cf.at(CF_effective_tax_frac, 1)); + npv_sta_ptc /= (1.0 - cf.at(CF_effective_tax_frac, 1)); + + + double lcoptc_fed_nom=0.0; + if (npv_energy_nom != 0) lcoptc_fed_nom = npv_fed_ptc / npv_energy_nom * 100.0; + double lcoptc_fed_real=0.0; + if (npv_energy_real != 0) lcoptc_fed_real = npv_fed_ptc / npv_energy_real * 100.0; + + double lcoptc_sta_nom=0.0; + if (npv_energy_nom != 0) lcoptc_sta_nom = npv_sta_ptc / npv_energy_nom * 100.0; + double lcoptc_sta_real=0.0; + if (npv_energy_real != 0) lcoptc_sta_real = npv_sta_ptc / npv_energy_real * 100.0; + + assign("lcoptc_fed_nom", var_data((ssc_number_t) lcoptc_fed_nom)); + assign("lcoptc_fed_real", var_data((ssc_number_t) lcoptc_fed_real)); + assign("lcoptc_sta_nom", var_data((ssc_number_t) lcoptc_sta_nom)); + assign("lcoptc_sta_real", var_data((ssc_number_t) lcoptc_sta_real)); + + double analysis_period_irr = 0.0; + analysis_period_irr = cf.at(CF_project_return_aftertax_irr, nyears)/100.0; //fraction for calculations + + double debt_fraction = 0.0; +// double size_of_equity = cost_installed - ibi_total - cbi_total - size_of_debt; + double size_of_equity = cost_installed - size_of_debt; + //cpg same as issuance_of_equity + // if (cost_installed > 0) debt_fraction = size_of_debt / cost_installed; + if ((size_of_debt + size_of_equity) > 0) + debt_fraction = size_of_debt / (size_of_debt + size_of_equity); + + double wacc = 0.0; + wacc = (1.0 - debt_fraction)*analysis_period_irr + debt_fraction*term_int_rate*(1.0 - cf.at(CF_effective_tax_frac, 1)); + + // percentages + debt_fraction *= 100.0; + wacc *= 100.0; +// effective_tax_rate *= 100.0; + analysis_period_irr *= 100.0; + + + assign("debt_fraction", var_data((ssc_number_t) debt_fraction )); + assign("wacc", var_data( (ssc_number_t) wacc)); + assign("effective_tax_rate", var_data((ssc_number_t)(cf.at(CF_effective_tax_frac, 1)*100.0))); + assign("analysis_period_irr", var_data( (ssc_number_t) analysis_period_irr)); + + + + assign("npv_ppa_revenue", var_data( (ssc_number_t) npv_ppa_revenue)); + assign("npv_energy_nom", var_data( (ssc_number_t) npv_energy_nom)); + assign("npv_energy_real", var_data( (ssc_number_t) npv_energy_real)); + + assign( "cf_length", var_data( (ssc_number_t) nyears+1 )); + + assign( "salvage_value", var_data((ssc_number_t)salvage_value)); + + assign( "prop_tax_assessed_value", var_data((ssc_number_t)( assessed_frac * cost_prefinancing ))); + + assign("adjusted_installed_cost", var_data((ssc_number_t)(cost_installed - cbi_total - ibi_total))); + assign("cost_installed", var_data((ssc_number_t)cost_installed)); + + assign( "cost_prefinancing", var_data((ssc_number_t) cost_prefinancing ) ); + //assign( "cost_prefinancingperwatt", var_data((ssc_number_t)( cost_prefinancing / nameplate / 1000.0 ) )); + + assign( "nominal_discount_rate", var_data((ssc_number_t)nom_discount_rate ) ); + + + assign( "depr_fedbas_macrs_5", var_data((ssc_number_t) depr_fedbas_macrs_5 ) ); + assign( "depr_fedbas_macrs_15", var_data((ssc_number_t) depr_fedbas_macrs_15 ) ); + assign( "depr_fedbas_sl_5", var_data((ssc_number_t) depr_fedbas_sl_5 ) ); + assign( "depr_fedbas_sl_15", var_data((ssc_number_t) depr_fedbas_sl_15 ) ); + assign( "depr_fedbas_sl_20", var_data((ssc_number_t) depr_fedbas_sl_20 ) ); + assign( "depr_fedbas_sl_39", var_data((ssc_number_t) depr_fedbas_sl_39 ) ); + assign( "depr_fedbas_custom", var_data((ssc_number_t) depr_fedbas_custom ) ); + assign( "depr_fedbas_total", var_data((ssc_number_t) depr_fedbas_total ) ); + + + assign("cost_financing", var_data((ssc_number_t) cost_financing)); + assign("cost_debt_upfront", var_data((ssc_number_t) cost_debt_upfront)); + + + assign( "size_of_equity", var_data((ssc_number_t) size_of_equity) ); + assign( "cost_installedperwatt", var_data((ssc_number_t)( cost_installed / nameplate / 1000.0 ) )); + + // metric costs + //advanced_financing_cost adv(this); + //adv.compute_cost(cost_installed, size_of_equity, size_of_debt, cbi_total, ibi_total); + + + + assign( "itc_fed_qual_macrs_5", var_data((ssc_number_t) itc_fed_qual_macrs_5 ) ); + assign( "itc_fed_qual_macrs_15", var_data((ssc_number_t) itc_fed_qual_macrs_15 ) ); + assign( "itc_fed_qual_sl_5", var_data((ssc_number_t) itc_fed_qual_sl_5 ) ); + assign( "itc_fed_qual_sl_15", var_data((ssc_number_t) itc_fed_qual_sl_15 ) ); + assign( "itc_fed_qual_sl_20", var_data((ssc_number_t) itc_fed_qual_sl_20 ) ); + assign( "itc_fed_qual_sl_39", var_data((ssc_number_t) itc_fed_qual_sl_39 ) ); + assign( "itc_fed_qual_custom", var_data((ssc_number_t) itc_fed_qual_custom ) ); + + assign( "itc_disallow_fed_percent_macrs_5", var_data((ssc_number_t) itc_disallow_fed_percent_macrs_5 ) ); + assign( "itc_disallow_fed_percent_macrs_15", var_data((ssc_number_t) itc_disallow_fed_percent_macrs_15 ) ); + assign( "itc_disallow_fed_percent_sl_5", var_data((ssc_number_t) itc_disallow_fed_percent_sl_5 ) ); + assign( "itc_disallow_fed_percent_sl_15", var_data((ssc_number_t) itc_disallow_fed_percent_sl_15 ) ); + assign( "itc_disallow_fed_percent_sl_20", var_data((ssc_number_t) itc_disallow_fed_percent_sl_20 ) ); + assign( "itc_disallow_fed_percent_sl_39", var_data((ssc_number_t) itc_disallow_fed_percent_sl_39 ) ); + assign( "itc_disallow_fed_percent_custom", var_data((ssc_number_t) itc_disallow_fed_percent_custom ) ); + + assign( "itc_disallow_fed_fixed_macrs_5", var_data((ssc_number_t) itc_disallow_fed_fixed_macrs_5 ) ); + assign( "itc_disallow_fed_fixed_macrs_15", var_data((ssc_number_t) itc_disallow_fed_fixed_macrs_15 ) ); + assign( "itc_disallow_fed_fixed_sl_5", var_data((ssc_number_t) itc_disallow_fed_fixed_sl_5 ) ); + assign( "itc_disallow_fed_fixed_sl_15", var_data((ssc_number_t) itc_disallow_fed_fixed_sl_15 ) ); + assign( "itc_disallow_fed_fixed_sl_20", var_data((ssc_number_t) itc_disallow_fed_fixed_sl_20 ) ); + assign( "itc_disallow_fed_fixed_sl_39", var_data((ssc_number_t) itc_disallow_fed_fixed_sl_39 ) ); + assign( "itc_disallow_fed_fixed_custom", var_data((ssc_number_t) itc_disallow_fed_fixed_custom ) ); + + assign( "itc_fed_qual_total", var_data((ssc_number_t) itc_fed_qual_total ) ); + assign( "itc_fed_percent_total", var_data((ssc_number_t) itc_fed_per ) ); + assign( "itc_fed_fixed_total", var_data((ssc_number_t) itc_fed_amount)); + + + + // output variable and cashflow line item assignments + + assign("issuance_of_equity", var_data((ssc_number_t) issuance_of_equity)); + assign("purchase_of_property", var_data((ssc_number_t) purchase_of_property)); + assign("cash_for_debt_service", var_data((ssc_number_t) cash_for_debt_service)); + assign("pv_cafds", var_data((ssc_number_t) pv_cafds)); + assign("size_of_debt", var_data((ssc_number_t) size_of_debt)); + + assign("ppa_price", var_data((ssc_number_t) ppa)); + assign("target_return_flip_year", var_data((ssc_number_t) flip_year)); + + + assign("ibi_total_fed", var_data((ssc_number_t) (ibi_fed_amount+ibi_fed_per))); + assign("ibi_total_sta", var_data((ssc_number_t) (ibi_sta_amount+ibi_sta_per))); + assign("ibi_total_oth", var_data((ssc_number_t) (ibi_oth_amount+ibi_oth_per))); + assign("ibi_total_uti", var_data((ssc_number_t) (ibi_uti_amount+ibi_uti_per))); + assign("ibi_total", var_data((ssc_number_t) ibi_total)); + assign("ibi_fedtax_total", var_data((ssc_number_t) ibi_fedtax_total)); + assign("ibi_statax_total", var_data((ssc_number_t) ibi_statax_total)); + assign("cbi_total", var_data((ssc_number_t) cbi_total)); + assign("cbi_fedtax_total", var_data((ssc_number_t) cbi_fedtax_total)); + assign("cbi_statax_total", var_data((ssc_number_t) cbi_statax_total)); + assign("cbi_total_fed", var_data((ssc_number_t) cbi_fed_amount)); + assign("cbi_total_sta", var_data((ssc_number_t) cbi_sta_amount)); + assign("cbi_total_oth", var_data((ssc_number_t) cbi_oth_amount)); + assign("cbi_total_uti", var_data((ssc_number_t) cbi_uti_amount)); + + // SAM 1038 + double itc_fed_total = 0.0; + double itc_sta_total = 0.0; + double itc_total = 0.0; + + for (size_t k = 0; k <= nyears; k++) { + itc_fed_total += cf.at(CF_itc_fed, k); + itc_sta_total += cf.at(CF_itc_sta, k); + itc_total += cf.at(CF_itc_total, k); + } + assign("itc_total_fed", var_data((ssc_number_t) itc_fed_total)); + assign("itc_total_sta", var_data((ssc_number_t) itc_sta_total)); + assign("itc_total", var_data((ssc_number_t) itc_total)); + +// assign("first_year_energy_net", var_data((ssc_number_t) cf.at(CF_energy_net,1))); + + assign("lcoe_nom", var_data((ssc_number_t)lcoe_nom)); + assign("lcoe_real", var_data((ssc_number_t)lcoe_real)); + assign("lppa_nom", var_data((ssc_number_t)lppa_nom)); + assign("lppa_real", var_data((ssc_number_t)lppa_real)); + assign("ppa_price", var_data((ssc_number_t)ppa)); + assign("ppa_escalation", var_data((ssc_number_t) (ppa_escalation *100.0) )); + assign("ppa", var_data((ssc_number_t) ppa)); + + + assign("issuance_of_equity", var_data((ssc_number_t) issuance_of_equity)); + + assign("project_return_aftertax_irr", var_data((ssc_number_t) (irr(CF_project_return_aftertax,nyears)*100.0))); + assign("project_return_aftertax_npv", var_data((ssc_number_t) (npv(CF_project_return_aftertax,nyears,nom_discount_rate) + cf.at(CF_project_return_aftertax,0)) )); + + + // cash flow line items + save_cf(CF_federal_tax_frac, nyears, "cf_federal_tax_frac"); + save_cf(CF_state_tax_frac, nyears, "cf_state_tax_frac"); + save_cf(CF_effective_tax_frac, nyears, "cf_effective_tax_frac"); + + + save_cf( CF_statax_taxable_incentives, nyears, "cf_statax_taxable_incentives" ); + save_cf( CF_statax_income_with_incentives, nyears, "cf_statax_income_with_incentives" ); + save_cf( CF_statax, nyears, "cf_statax" ); + save_cf( CF_fedtax_taxable_incentives, nyears, "cf_fedtax_taxable_incentives" ); + save_cf( CF_fedtax_income_with_incentives, nyears, "cf_fedtax_income_with_incentives" ); + save_cf( CF_fedtax, nyears, "cf_fedtax" ); + + save_cf( CF_stadepr_macrs_5, nyears, "cf_stadepr_macrs_5" ); + save_cf( CF_stadepr_macrs_15, nyears, "cf_stadepr_macrs_15" ); + save_cf( CF_stadepr_sl_5, nyears, "cf_stadepr_sl_5" ); + save_cf( CF_stadepr_sl_15, nyears, "cf_stadepr_sl_15" ); + save_cf( CF_stadepr_sl_20, nyears, "cf_stadepr_sl_20" ); + save_cf( CF_stadepr_sl_39, nyears, "cf_stadepr_sl_39" ); + save_cf( CF_stadepr_custom, nyears, "cf_stadepr_custom" ); + save_cf( CF_stadepr_me1, nyears, "cf_stadepr_me1" ); + save_cf( CF_stadepr_me2, nyears, "cf_stadepr_me2" ); + save_cf( CF_stadepr_me3, nyears, "cf_stadepr_me3" ); + save_cf( CF_stadepr_total, nyears, "cf_stadepr_total" ); + save_cf( CF_statax_income_prior_incentives, nyears, "cf_statax_income_prior_incentives" ); + + save_cf( CF_feddepr_macrs_5, nyears, "cf_feddepr_macrs_5" ); + save_cf( CF_feddepr_macrs_15, nyears, "cf_feddepr_macrs_15" ); + save_cf( CF_feddepr_sl_5, nyears, "cf_feddepr_sl_5" ); + save_cf( CF_feddepr_sl_15, nyears, "cf_feddepr_sl_15" ); + save_cf( CF_feddepr_sl_20, nyears, "cf_feddepr_sl_20" ); + save_cf( CF_feddepr_sl_39, nyears, "cf_feddepr_sl_39" ); + save_cf( CF_feddepr_custom, nyears, "cf_feddepr_custom" ); + save_cf( CF_feddepr_me1, nyears, "cf_feddepr_me1" ); + save_cf( CF_feddepr_me2, nyears, "cf_feddepr_me2" ); + save_cf( CF_feddepr_me3, nyears, "cf_feddepr_me3" ); + save_cf( CF_feddepr_total, nyears, "cf_feddepr_total" ); + save_cf( CF_fedtax_income_prior_incentives, nyears, "cf_fedtax_income_prior_incentives" ); + + save_cf( CF_pbi_fed, nyears, "cf_pbi_total_fed"); + save_cf( CF_pbi_sta, nyears, "cf_pbi_total_sta"); + save_cf( CF_pbi_oth, nyears, "cf_pbi_total_oth"); + save_cf( CF_pbi_uti, nyears, "cf_pbi_total_uti"); + save_cf( CF_pbi_total, nyears, "cf_pbi_total" ); + save_cf( CF_pbi_statax_total, nyears, "cf_pbi_statax_total" ); + save_cf( CF_pbi_fedtax_total, nyears, "cf_pbi_fedtax_total" ); + + save_cf( CF_ptc_fed, nyears, "cf_ptc_fed" ); + save_cf( CF_ptc_sta, nyears, "cf_ptc_sta" ); + + save_cf( CF_project_return_aftertax_cash, nyears, "cf_project_return_aftertax_cash" ); + save_cf( CF_project_return_aftertax, nyears, "cf_project_return_aftertax" ); + save_cf( CF_project_return_aftertax_irr, nyears, "cf_project_return_aftertax_irr" ); + save_cf( CF_project_return_aftertax_max_irr, nyears, "cf_project_return_aftertax_max_irr" ); + save_cf( CF_project_return_aftertax_npv, nyears, "cf_project_return_aftertax_npv" ); + save_cf( CF_project_return_pretax, nyears, "cf_project_return_pretax" ); + save_cf( CF_project_return_pretax_irr, nyears, "cf_project_return_pretax_irr" ); + save_cf( CF_project_return_pretax_npv, nyears, "cf_project_return_pretax_npv" ); + + save_cf( CF_project_financing_activities, nyears, "cf_project_financing_activities" ); + save_cf( CF_pretax_cashflow, nyears, "cf_pretax_cashflow" ); + + save_cf( CF_project_dsra, nyears, "cf_project_dsra" ); + save_cf(CF_project_wcra, nyears, "cf_project_wcra"); + save_cf(CF_project_receivablesra, nyears, "cf_project_receivablesra"); + save_cf(CF_project_me1ra, nyears, "cf_project_me1ra"); + save_cf( CF_project_me2ra, nyears, "cf_project_me2ra" ); + save_cf( CF_project_me3ra, nyears, "cf_project_me3ra" ); + save_cf( CF_project_ra, nyears, "cf_project_ra" ); + save_cf( CF_project_me1cs, nyears, "cf_project_me1cs" ); + save_cf( CF_project_me2cs, nyears, "cf_project_me2cs" ); + save_cf( CF_project_me3cs, nyears, "cf_project_me3cs" ); + save_cf( CF_project_mecs, nyears, "cf_project_mecs" ); + save_cf( CF_project_investing_activities, nyears, "cf_project_investing_activities" ); + + save_cf( CF_pv_interest_factor, nyears, "cf_pv_interest_factor" ); + save_cf( CF_cash_for_ds, nyears, "cf_cash_for_ds" ); + save_cf( CF_pv_cash_for_ds, nyears, "cf_pv_cash_for_ds" ); + save_cf( CF_debt_size, nyears, "cf_debt_size" ); + save_cf( CF_project_operating_activities, nyears, "cf_project_operating_activities" ); + + save_cf( CF_debt_payment_total, nyears, "cf_debt_payment_total" ); + save_cf( CF_debt_payment_interest, nyears, "cf_debt_payment_interest" ); + save_cf( CF_debt_payment_principal, nyears, "cf_debt_payment_principal" ); + save_cf( CF_debt_balance, nyears, "cf_debt_balance" ); + + + save_cf(CF_energy_value, nyears, "cf_energy_value"); + save_cf(CF_thermal_value, nyears, "cf_thermal_value"); + save_cf(CF_curtailment_value, nyears, "cf_curtailment_value"); + save_cf(CF_capacity_payment, nyears, "cf_capacity_payment"); + save_cf(CF_energy_curtailed, nyears, "cf_energy_curtailed"); + save_cf( CF_ppa_price, nyears, "cf_ppa_price" ); + save_cf( CF_om_fixed_expense, nyears, "cf_om_fixed_expense" ); + save_cf( CF_om_production_expense, nyears, "cf_om_production_expense" ); + save_cf( CF_om_capacity_expense, nyears, "cf_om_capacity_expense" ); + + if (add_om_num_types > 0) { + save_cf(CF_om_fixed1_expense, nyears, "cf_om_fixed1_expense"); + save_cf(CF_om_production1_expense, nyears, "cf_om_production1_expense"); + save_cf(CF_om_capacity1_expense, nyears, "cf_om_capacity1_expense"); + } + if (add_om_num_types > 1) { + save_cf(CF_om_fixed2_expense, nyears, "cf_om_fixed2_expense"); + save_cf(CF_om_production2_expense, nyears, "cf_om_production2_expense"); + save_cf(CF_om_capacity2_expense, nyears, "cf_om_capacity2_expense"); + } + + if (as_integer("en_batt") == 1 || as_integer("en_standalone_batt") == 1 || as_integer("en_wave_batt") == 1) { + save_cf(CF_battery_replacement_cost, nyears, "cf_battery_replacement_cost"); + save_cf(CF_battery_replacement_cost_schedule, nyears, "cf_battery_replacement_cost_schedule"); + } + if (is_assigned("fuelcell_replacement_option")) { + save_cf(CF_fuelcell_replacement_cost, nyears, "cf_fuelcell_replacement_cost"); + save_cf(CF_fuelcell_replacement_cost_schedule, nyears, "cf_fuelcell_replacement_cost_schedule"); + } + + save_cf( CF_om_fuel_expense, nyears, "cf_om_fuel_expense" ); + save_cf( CF_om_elec_price_for_heat_techs, nyears, "cf_om_elec_price_for_heat_techs"); + save_cf( CF_om_opt_fuel_1_expense, nyears, "cf_om_opt_fuel_1_expense" ); + save_cf( CF_om_opt_fuel_2_expense, nyears, "cf_om_opt_fuel_2_expense" ); + save_cf(CF_land_lease_expense, nyears, "cf_land_lease_expense"); + save_cf( CF_property_tax_assessed_value, nyears, "cf_property_tax_assessed_value" ); + save_cf( CF_property_tax_expense, nyears, "cf_property_tax_expense" ); + save_cf( CF_insurance_expense, nyears, "cf_insurance_expense" ); + + save_cf( CF_operating_expenses, nyears, "cf_operating_expenses" ); + save_cf( CF_ebitda, nyears, "cf_ebitda" ); + save_cf( CF_net_salvage_value, nyears, "cf_net_salvage_value" ); + save_cf( CF_total_revenue, nyears, "cf_total_revenue" ); + + save_cf( CF_energy_net, nyears, "cf_energy_net" ); + save_cf( CF_energy_sales, nyears, "cf_energy_sales" ); + save_cf( CF_energy_purchases, nyears, "cf_energy_purchases" ); + if (is_assigned("gen_without_battery")) { + save_cf(CF_energy_without_battery, nyears, "cf_energy_without_battery"); + } + + save_cf( CF_reserve_debtservice, nyears, "cf_reserve_debtservice" ); + save_cf(CF_reserve_om, nyears, "cf_reserve_om"); + save_cf(CF_reserve_receivables, nyears, "cf_reserve_receivables"); + save_cf(CF_reserve_equip1, nyears, "cf_reserve_equip1"); + save_cf( CF_reserve_equip2, nyears, "cf_reserve_equip2" ); + save_cf( CF_reserve_equip3, nyears, "cf_reserve_equip3" ); + + save_cf( CF_funding_debtservice, nyears, "cf_funding_debtservice" ); + save_cf(CF_funding_om, nyears, "cf_funding_om"); + save_cf(CF_funding_receivables, nyears, "cf_funding_receivables"); + save_cf(CF_funding_equip1, nyears, "cf_funding_equip1"); + save_cf( CF_funding_equip2, nyears, "cf_funding_equip2" ); + save_cf( CF_funding_equip3, nyears, "cf_funding_equip3" ); + + save_cf( CF_disbursement_debtservice, nyears, "cf_disbursement_debtservice" ); + save_cf(CF_disbursement_om, nyears, "cf_disbursement_om"); + save_cf(CF_disbursement_receivables, nyears, "cf_disbursement_receivables"); + save_cf(CF_disbursement_equip1, nyears, "cf_disbursement_equip1"); + save_cf( CF_disbursement_equip2, nyears, "cf_disbursement_equip2" ); + save_cf( CF_disbursement_equip3, nyears, "cf_disbursement_equip3" ); + + save_cf( CF_reserve_total, nyears, "cf_reserve_total" ); + save_cf( CF_reserve_interest, nyears, "cf_reserve_interest" ); + + save_cf(CF_Recapitalization, nyears, "cf_recapitalization"); + + + // dispatch + std::vector ppa_cf; + for (i = 0; i <= nyears; i++) + { + ppa_cf.push_back(cf.at(CF_ppa_price, i)); + } + m_disp_calcs.compute_outputs(ppa_cf); + + // Intermediate tax credit/depreciation variables + assign("pre_depr_alloc_basis", var_data((ssc_number_t)pre_depr_alloc_basis)); + assign("pre_itc_qual_basis", var_data((ssc_number_t)pre_itc_qual_basis)); + + // State ITC/depreciation table + assign("depr_stabas_percent_macrs_5", var_data((ssc_number_t) (depr_stabas_macrs_5_frac*100.0))); + assign( "depr_alloc_macrs_5", var_data((ssc_number_t) depr_alloc_macrs_5 ) ); + double depr_stabas_ibi_reduc_macrs_5 = depr_stabas_macrs_5_frac * depr_sta_reduction_ibi; + double depr_stabas_cbi_reduc_macrs_5 = depr_stabas_macrs_5_frac * depr_sta_reduction_cbi; + assign( "depr_stabas_ibi_reduc_macrs_5", var_data((ssc_number_t) depr_stabas_ibi_reduc_macrs_5 ) ); + assign( "depr_stabas_cbi_reduc_macrs_5", var_data((ssc_number_t) depr_stabas_cbi_reduc_macrs_5 ) ); + assign( "depr_stabas_prior_itc_macrs_5", var_data((ssc_number_t) ( depr_alloc_macrs_5 - depr_stabas_ibi_reduc_macrs_5 - depr_stabas_cbi_reduc_macrs_5)) ); + assign( "itc_sta_qual_macrs_5", var_data((ssc_number_t) itc_sta_qual_macrs_5 ) ); + double depr_stabas_percent_qual_macrs_5 = (itc_sta_qual_total > 0)? 100.0 * itc_sta_qual_macrs_5 / itc_sta_qual_total:0.0; + assign( "depr_stabas_percent_qual_macrs_5", var_data((ssc_number_t) depr_stabas_percent_qual_macrs_5) ); + assign( "depr_stabas_percent_amount_macrs_5", var_data((ssc_number_t) (depr_stabas_percent_qual_macrs_5/100.0 * itc_sta_per)) ); + assign( "itc_disallow_sta_percent_macrs_5", var_data((ssc_number_t) itc_disallow_sta_percent_macrs_5 ) ); + assign( "depr_stabas_fixed_amount_macrs_5", var_data((ssc_number_t) (depr_stabas_percent_qual_macrs_5/100.0 * itc_sta_amount))); + assign( "itc_disallow_sta_fixed_macrs_5", var_data((ssc_number_t) itc_disallow_sta_fixed_macrs_5 ) ); + double depr_stabas_itc_sta_reduction_macrs_5 = itc_sta_percent_deprbas_sta * itc_disallow_sta_percent_macrs_5 + itc_sta_amount_deprbas_sta * itc_disallow_sta_fixed_macrs_5; + double depr_stabas_itc_fed_reduction_macrs_5 = itc_fed_percent_deprbas_sta * itc_disallow_fed_percent_macrs_5 + itc_fed_amount_deprbas_sta * itc_disallow_fed_fixed_macrs_5; + assign( "depr_stabas_itc_sta_reduction_macrs_5", var_data((ssc_number_t) depr_stabas_itc_sta_reduction_macrs_5 ) ); + assign( "depr_stabas_itc_fed_reduction_macrs_5", var_data((ssc_number_t) depr_stabas_itc_fed_reduction_macrs_5 ) ); + assign( "depr_stabas_after_itc_macrs_5", var_data((ssc_number_t) (depr_stabas_macrs_5 + depr_stabas_macrs_5_bonus) ) ); + assign( "depr_stabas_first_year_bonus_macrs_5", var_data((ssc_number_t) depr_stabas_macrs_5_bonus ) ); + assign( "depr_stabas_macrs_5", var_data((ssc_number_t) depr_stabas_macrs_5 ) ); + + assign("depr_stabas_percent_macrs_15", var_data((ssc_number_t) (depr_stabas_macrs_15_frac*100.0))); + assign( "depr_alloc_macrs_15", var_data((ssc_number_t) depr_alloc_macrs_15 ) ); + double depr_stabas_ibi_reduc_macrs_15 = depr_stabas_macrs_15_frac * depr_sta_reduction_ibi; + double depr_stabas_cbi_reduc_macrs_15 = depr_stabas_macrs_15_frac * depr_sta_reduction_cbi; + assign( "depr_stabas_ibi_reduc_macrs_15", var_data((ssc_number_t) depr_stabas_ibi_reduc_macrs_15 ) ); + assign( "depr_stabas_cbi_reduc_macrs_15", var_data((ssc_number_t) depr_stabas_cbi_reduc_macrs_15 ) ); + assign( "depr_stabas_prior_itc_macrs_15", var_data((ssc_number_t) ( depr_alloc_macrs_15 - depr_stabas_ibi_reduc_macrs_15 - depr_stabas_cbi_reduc_macrs_15)) ); + assign( "itc_sta_qual_macrs_15", var_data((ssc_number_t) itc_sta_qual_macrs_15 ) ); + double depr_stabas_percent_qual_macrs_15 = (itc_sta_qual_total > 0)? 100.0 * itc_sta_qual_macrs_15 / itc_sta_qual_total:0.0; + assign( "depr_stabas_percent_qual_macrs_15", var_data((ssc_number_t) depr_stabas_percent_qual_macrs_15) ); + assign( "depr_stabas_percent_amount_macrs_15", var_data((ssc_number_t) (depr_stabas_percent_qual_macrs_15/100.0 * itc_sta_per)) ); + assign( "itc_disallow_sta_percent_macrs_15", var_data((ssc_number_t) itc_disallow_sta_percent_macrs_15 ) ); + assign( "depr_stabas_fixed_amount_macrs_15", var_data((ssc_number_t) (depr_stabas_percent_qual_macrs_15/100.0 * itc_sta_amount))); + assign( "itc_disallow_sta_fixed_macrs_15", var_data((ssc_number_t) itc_disallow_sta_fixed_macrs_15 ) ); + double depr_stabas_itc_sta_reduction_macrs_15 = itc_sta_percent_deprbas_sta * itc_disallow_sta_percent_macrs_15 + itc_sta_amount_deprbas_sta * itc_disallow_sta_fixed_macrs_15; + double depr_stabas_itc_fed_reduction_macrs_15 = itc_fed_percent_deprbas_sta * itc_disallow_fed_percent_macrs_15 + itc_fed_amount_deprbas_sta * itc_disallow_fed_fixed_macrs_15; + assign( "depr_stabas_itc_sta_reduction_macrs_15", var_data((ssc_number_t) depr_stabas_itc_sta_reduction_macrs_15 ) ); + assign( "depr_stabas_itc_fed_reduction_macrs_15", var_data((ssc_number_t) depr_stabas_itc_fed_reduction_macrs_15 ) ); + assign( "depr_stabas_after_itc_macrs_15", var_data((ssc_number_t) (depr_stabas_macrs_15 + depr_stabas_macrs_15_bonus) ) ); + assign( "depr_stabas_first_year_bonus_macrs_15", var_data((ssc_number_t) depr_stabas_macrs_15_bonus ) ); + assign( "depr_stabas_macrs_15", var_data((ssc_number_t) depr_stabas_macrs_15 ) ); + + assign("depr_stabas_percent_sl_5", var_data((ssc_number_t) (depr_stabas_sl_5_frac*100.0))); + assign( "depr_alloc_sl_5", var_data((ssc_number_t) depr_alloc_sl_5 ) ); + double depr_stabas_ibi_reduc_sl_5 = depr_stabas_sl_5_frac * depr_sta_reduction_ibi; + double depr_stabas_cbi_reduc_sl_5 = depr_stabas_sl_5_frac * depr_sta_reduction_cbi; + assign( "depr_stabas_ibi_reduc_sl_5", var_data((ssc_number_t) depr_stabas_ibi_reduc_sl_5 ) ); + assign( "depr_stabas_cbi_reduc_sl_5", var_data((ssc_number_t) depr_stabas_cbi_reduc_sl_5 ) ); + assign( "depr_stabas_prior_itc_sl_5", var_data((ssc_number_t) ( depr_alloc_sl_5 - depr_stabas_ibi_reduc_sl_5 - depr_stabas_cbi_reduc_sl_5)) ); + assign( "itc_sta_qual_sl_5", var_data((ssc_number_t) itc_sta_qual_sl_5 ) ); + double depr_stabas_percent_qual_sl_5 = (itc_sta_qual_total > 0)? 100.0 * itc_sta_qual_sl_5 / itc_sta_qual_total:0.0; + assign( "depr_stabas_percent_qual_sl_5", var_data((ssc_number_t) depr_stabas_percent_qual_sl_5) ); + assign( "depr_stabas_percent_amount_sl_5", var_data((ssc_number_t) (depr_stabas_percent_qual_sl_5/100.0 * itc_sta_per)) ); + assign( "itc_disallow_sta_percent_sl_5", var_data((ssc_number_t) itc_disallow_sta_percent_sl_5 ) ); + assign( "depr_stabas_fixed_amount_sl_5", var_data((ssc_number_t) (depr_stabas_percent_qual_sl_5/100.0 * itc_sta_amount))); + assign( "itc_disallow_sta_fixed_sl_5", var_data((ssc_number_t) itc_disallow_sta_fixed_sl_5 ) ); + double depr_stabas_itc_sta_reduction_sl_5 = itc_sta_percent_deprbas_sta * itc_disallow_sta_percent_sl_5 + itc_sta_amount_deprbas_sta * itc_disallow_sta_fixed_sl_5; + double depr_stabas_itc_fed_reduction_sl_5 = itc_fed_percent_deprbas_sta * itc_disallow_fed_percent_sl_5 + itc_fed_amount_deprbas_sta * itc_disallow_fed_fixed_sl_5; + assign( "depr_stabas_itc_sta_reduction_sl_5", var_data((ssc_number_t) depr_stabas_itc_sta_reduction_sl_5 ) ); + assign( "depr_stabas_itc_fed_reduction_sl_5", var_data((ssc_number_t) depr_stabas_itc_fed_reduction_sl_5 ) ); + assign( "depr_stabas_after_itc_sl_5", var_data((ssc_number_t) (depr_stabas_sl_5 + depr_stabas_sl_5_bonus) ) ); + assign( "depr_stabas_first_year_bonus_sl_5", var_data((ssc_number_t) depr_stabas_sl_5_bonus ) ); + assign( "depr_stabas_sl_5", var_data((ssc_number_t) depr_stabas_sl_5 ) ); + + assign("depr_stabas_percent_sl_15", var_data((ssc_number_t) (depr_stabas_sl_15_frac*100.0))); + assign( "depr_alloc_sl_15", var_data((ssc_number_t) depr_alloc_sl_15 ) ); + double depr_stabas_ibi_reduc_sl_15 = depr_stabas_sl_15_frac * depr_sta_reduction_ibi; + double depr_stabas_cbi_reduc_sl_15 = depr_stabas_sl_15_frac * depr_sta_reduction_cbi; + assign( "depr_stabas_ibi_reduc_sl_15", var_data((ssc_number_t) depr_stabas_ibi_reduc_sl_15 ) ); + assign( "depr_stabas_cbi_reduc_sl_15", var_data((ssc_number_t) depr_stabas_cbi_reduc_sl_15 ) ); + assign( "depr_stabas_prior_itc_sl_15", var_data((ssc_number_t) ( depr_alloc_sl_15 - depr_stabas_ibi_reduc_sl_15 - depr_stabas_cbi_reduc_sl_15)) ); + assign( "itc_sta_qual_sl_15", var_data((ssc_number_t) itc_sta_qual_sl_15 ) ); + double depr_stabas_percent_qual_sl_15 = (itc_sta_qual_total > 0)? 100.0 * itc_sta_qual_sl_15 / itc_sta_qual_total:0.0; + assign( "depr_stabas_percent_qual_sl_15", var_data((ssc_number_t) depr_stabas_percent_qual_sl_15) ); + assign( "depr_stabas_percent_amount_sl_15", var_data((ssc_number_t) (depr_stabas_percent_qual_sl_15/100.0 * itc_sta_per)) ); + assign( "itc_disallow_sta_percent_sl_15", var_data((ssc_number_t) itc_disallow_sta_percent_sl_15 ) ); + assign( "depr_stabas_fixed_amount_sl_15", var_data((ssc_number_t) (depr_stabas_percent_qual_sl_15/100.0 * itc_sta_amount))); + assign( "itc_disallow_sta_fixed_sl_15", var_data((ssc_number_t) itc_disallow_sta_fixed_sl_15 ) ); + double depr_stabas_itc_sta_reduction_sl_15 = itc_sta_percent_deprbas_sta * itc_disallow_sta_percent_sl_15 + itc_sta_amount_deprbas_sta * itc_disallow_sta_fixed_sl_15; + double depr_stabas_itc_fed_reduction_sl_15 = itc_fed_percent_deprbas_sta * itc_disallow_fed_percent_sl_15 + itc_fed_amount_deprbas_sta * itc_disallow_fed_fixed_sl_15; + assign( "depr_stabas_itc_sta_reduction_sl_15", var_data((ssc_number_t) depr_stabas_itc_sta_reduction_sl_15 ) ); + assign( "depr_stabas_itc_fed_reduction_sl_15", var_data((ssc_number_t) depr_stabas_itc_fed_reduction_sl_15 ) ); + assign( "depr_stabas_after_itc_sl_15", var_data((ssc_number_t) (depr_stabas_sl_15 + depr_stabas_sl_15_bonus) ) ); + assign( "depr_stabas_first_year_bonus_sl_15", var_data((ssc_number_t) depr_stabas_sl_15_bonus ) ); + assign( "depr_stabas_sl_15", var_data((ssc_number_t) depr_stabas_sl_15 ) ); + + assign("depr_stabas_percent_sl_20", var_data((ssc_number_t) (depr_stabas_sl_20_frac*100.0))); + assign( "depr_alloc_sl_20", var_data((ssc_number_t) depr_alloc_sl_20 ) ); + double depr_stabas_ibi_reduc_sl_20 = depr_stabas_sl_20_frac * depr_sta_reduction_ibi; + double depr_stabas_cbi_reduc_sl_20 = depr_stabas_sl_20_frac * depr_sta_reduction_cbi; + assign( "depr_stabas_ibi_reduc_sl_20", var_data((ssc_number_t) depr_stabas_ibi_reduc_sl_20 ) ); + assign( "depr_stabas_cbi_reduc_sl_20", var_data((ssc_number_t) depr_stabas_cbi_reduc_sl_20 ) ); + assign( "depr_stabas_prior_itc_sl_20", var_data((ssc_number_t) ( depr_alloc_sl_20 - depr_stabas_ibi_reduc_sl_20 - depr_stabas_cbi_reduc_sl_20)) ); + assign( "itc_sta_qual_sl_20", var_data((ssc_number_t) itc_sta_qual_sl_20 ) ); + double depr_stabas_percent_qual_sl_20 = (itc_sta_qual_total > 0)? 100.0 * itc_sta_qual_sl_20 / itc_sta_qual_total:0.0; + assign( "depr_stabas_percent_qual_sl_20", var_data((ssc_number_t) depr_stabas_percent_qual_sl_20) ); + assign( "depr_stabas_percent_amount_sl_20", var_data((ssc_number_t) (depr_stabas_percent_qual_sl_20/100.0 * itc_sta_per)) ); + assign( "itc_disallow_sta_percent_sl_20", var_data((ssc_number_t) itc_disallow_sta_percent_sl_20 ) ); + assign( "depr_stabas_fixed_amount_sl_20", var_data((ssc_number_t) (depr_stabas_percent_qual_sl_20/100.0 * itc_sta_amount))); + assign( "itc_disallow_sta_fixed_sl_20", var_data((ssc_number_t) itc_disallow_sta_fixed_sl_20 ) ); + double depr_stabas_itc_sta_reduction_sl_20 = itc_sta_percent_deprbas_sta * itc_disallow_sta_percent_sl_20 + itc_sta_amount_deprbas_sta * itc_disallow_sta_fixed_sl_20; + double depr_stabas_itc_fed_reduction_sl_20 = itc_fed_percent_deprbas_sta * itc_disallow_fed_percent_sl_20 + itc_fed_amount_deprbas_sta * itc_disallow_fed_fixed_sl_20; + assign( "depr_stabas_itc_sta_reduction_sl_20", var_data((ssc_number_t) depr_stabas_itc_sta_reduction_sl_20 ) ); + assign( "depr_stabas_itc_fed_reduction_sl_20", var_data((ssc_number_t) depr_stabas_itc_fed_reduction_sl_20 ) ); + assign( "depr_stabas_after_itc_sl_20", var_data((ssc_number_t) (depr_stabas_sl_20 + depr_stabas_sl_20_bonus) ) ); + assign( "depr_stabas_first_year_bonus_sl_20", var_data((ssc_number_t) depr_stabas_sl_20_bonus ) ); + assign( "depr_stabas_sl_20", var_data((ssc_number_t) depr_stabas_sl_20 ) ); + + assign("depr_stabas_percent_sl_39", var_data((ssc_number_t) (depr_stabas_sl_39_frac*100.0))); + assign( "depr_alloc_sl_39", var_data((ssc_number_t) depr_alloc_sl_39 ) ); + double depr_stabas_ibi_reduc_sl_39 = depr_stabas_sl_39_frac * depr_sta_reduction_ibi; + double depr_stabas_cbi_reduc_sl_39 = depr_stabas_sl_39_frac * depr_sta_reduction_cbi; + assign( "depr_stabas_ibi_reduc_sl_39", var_data((ssc_number_t) depr_stabas_ibi_reduc_sl_39 ) ); + assign( "depr_stabas_cbi_reduc_sl_39", var_data((ssc_number_t) depr_stabas_cbi_reduc_sl_39 ) ); + assign( "depr_stabas_prior_itc_sl_39", var_data((ssc_number_t) ( depr_alloc_sl_39 - depr_stabas_ibi_reduc_sl_39 - depr_stabas_cbi_reduc_sl_39)) ); + assign( "itc_sta_qual_sl_39", var_data((ssc_number_t) itc_sta_qual_sl_39 ) ); + double depr_stabas_percent_qual_sl_39 = (itc_sta_qual_total > 0)? 100.0 * itc_sta_qual_sl_39 / itc_sta_qual_total:0.0; + assign( "depr_stabas_percent_qual_sl_39", var_data((ssc_number_t) depr_stabas_percent_qual_sl_39) ); + assign( "depr_stabas_percent_amount_sl_39", var_data((ssc_number_t) (depr_stabas_percent_qual_sl_39/100.0 * itc_sta_per)) ); + assign( "itc_disallow_sta_percent_sl_39", var_data((ssc_number_t) itc_disallow_sta_percent_sl_39 ) ); + assign( "depr_stabas_fixed_amount_sl_39", var_data((ssc_number_t) (depr_stabas_percent_qual_sl_39/100.0 * itc_sta_amount))); + assign( "itc_disallow_sta_fixed_sl_39", var_data((ssc_number_t) itc_disallow_sta_fixed_sl_39 ) ); + double depr_stabas_itc_sta_reduction_sl_39 = itc_sta_percent_deprbas_sta * itc_disallow_sta_percent_sl_39 + itc_sta_amount_deprbas_sta * itc_disallow_sta_fixed_sl_39; + double depr_stabas_itc_fed_reduction_sl_39 = itc_fed_percent_deprbas_sta * itc_disallow_fed_percent_sl_39 + itc_fed_amount_deprbas_sta * itc_disallow_fed_fixed_sl_39; + assign( "depr_stabas_itc_sta_reduction_sl_39", var_data((ssc_number_t) depr_stabas_itc_sta_reduction_sl_39 ) ); + assign( "depr_stabas_itc_fed_reduction_sl_39", var_data((ssc_number_t) depr_stabas_itc_fed_reduction_sl_39 ) ); + assign( "depr_stabas_after_itc_sl_39", var_data((ssc_number_t) (depr_stabas_sl_39 + depr_stabas_sl_39_bonus) ) ); + assign( "depr_stabas_first_year_bonus_sl_39", var_data((ssc_number_t) depr_stabas_sl_39_bonus ) ); + assign( "depr_stabas_sl_39", var_data((ssc_number_t) depr_stabas_sl_39 ) ); + + assign("depr_stabas_percent_custom", var_data((ssc_number_t) (depr_stabas_custom_frac*100.0))); + assign( "depr_alloc_custom", var_data((ssc_number_t) depr_alloc_custom ) ); + double depr_stabas_ibi_reduc_custom = depr_stabas_custom_frac * depr_sta_reduction_ibi; + double depr_stabas_cbi_reduc_custom = depr_stabas_custom_frac * depr_sta_reduction_cbi; + assign( "depr_stabas_ibi_reduc_custom", var_data((ssc_number_t) depr_stabas_ibi_reduc_custom ) ); + assign( "depr_stabas_cbi_reduc_custom", var_data((ssc_number_t) depr_stabas_cbi_reduc_custom ) ); + assign( "depr_stabas_prior_itc_custom", var_data((ssc_number_t) ( depr_alloc_custom - depr_stabas_ibi_reduc_custom - depr_stabas_cbi_reduc_custom)) ); + assign( "itc_sta_qual_custom", var_data((ssc_number_t) itc_sta_qual_custom ) ); + double depr_stabas_percent_qual_custom = (itc_sta_qual_total > 0)? 100.0 * itc_sta_qual_custom / itc_sta_qual_total:0.0; + assign( "depr_stabas_percent_qual_custom", var_data((ssc_number_t) depr_stabas_percent_qual_custom) ); + assign( "depr_stabas_percent_amount_custom", var_data((ssc_number_t) (depr_stabas_percent_qual_custom/100.0 * itc_sta_per)) ); + assign( "itc_disallow_sta_percent_custom", var_data((ssc_number_t) itc_disallow_sta_percent_custom ) ); + assign( "depr_stabas_fixed_amount_custom", var_data((ssc_number_t) (depr_stabas_percent_qual_custom/100.0 * itc_sta_amount))); + assign( "itc_disallow_sta_fixed_custom", var_data((ssc_number_t) itc_disallow_sta_fixed_custom ) ); + double depr_stabas_itc_sta_reduction_custom = itc_sta_percent_deprbas_sta * itc_disallow_sta_percent_custom + itc_sta_amount_deprbas_sta * itc_disallow_sta_fixed_custom; + double depr_stabas_itc_fed_reduction_custom = itc_fed_percent_deprbas_sta * itc_disallow_fed_percent_custom + itc_fed_amount_deprbas_sta * itc_disallow_fed_fixed_custom; + assign( "depr_stabas_itc_sta_reduction_custom", var_data((ssc_number_t) depr_stabas_itc_sta_reduction_custom ) ); + assign( "depr_stabas_itc_fed_reduction_custom", var_data((ssc_number_t) depr_stabas_itc_fed_reduction_custom ) ); + assign( "depr_stabas_after_itc_custom", var_data((ssc_number_t) (depr_stabas_custom + depr_stabas_custom_bonus) ) ); + assign( "depr_stabas_first_year_bonus_custom", var_data((ssc_number_t) depr_stabas_custom_bonus ) ); + assign( "depr_stabas_custom", var_data((ssc_number_t) depr_stabas_custom ) ); + + assign("depr_stabas_percent_total", var_data((ssc_number_t) (100.0*(depr_stabas_macrs_5_frac+depr_stabas_macrs_15_frac+depr_stabas_sl_5_frac+depr_stabas_sl_15_frac+depr_stabas_sl_20_frac+depr_stabas_sl_39_frac+depr_stabas_custom_frac)))); + assign( "depr_alloc_total", var_data((ssc_number_t) depr_alloc_total ) ); + assign( "depr_stabas_ibi_reduc_total", var_data((ssc_number_t) depr_sta_reduction_ibi ) ); + assign( "depr_stabas_cbi_reduc_total", var_data((ssc_number_t) depr_sta_reduction_cbi ) ); + assign( "depr_stabas_prior_itc_total", var_data((ssc_number_t) ( depr_alloc_total - depr_sta_reduction_ibi - depr_sta_reduction_cbi)) ); + assign( "itc_sta_qual_total", var_data((ssc_number_t) itc_sta_qual_total ) ); + assign( "depr_stabas_percent_qual_total", var_data((ssc_number_t) 100.0) ); + assign( "depr_stabas_percent_amount_total", var_data((ssc_number_t) itc_sta_per) ); + assign( "itc_disallow_sta_percent_total", var_data((ssc_number_t) (itc_disallow_sta_percent_macrs_5 + itc_disallow_sta_percent_macrs_15 + itc_disallow_sta_percent_sl_5 + itc_disallow_sta_percent_sl_15 + itc_disallow_sta_percent_sl_20 + itc_disallow_sta_percent_sl_39 + itc_disallow_sta_percent_custom) ) ); + assign( "depr_stabas_fixed_amount_total", var_data((ssc_number_t) itc_sta_amount)); + assign( "itc_disallow_sta_fixed_total", var_data((ssc_number_t) (itc_disallow_sta_fixed_macrs_5 + itc_disallow_sta_fixed_macrs_15 + itc_disallow_sta_fixed_sl_5 + itc_disallow_sta_fixed_sl_15 + itc_disallow_sta_fixed_sl_20 + itc_disallow_sta_fixed_sl_39 + itc_disallow_sta_fixed_custom) ) ); + double depr_stabas_itc_sta_reduction_total = depr_stabas_itc_sta_reduction_macrs_5 + depr_stabas_itc_sta_reduction_macrs_15 + depr_stabas_itc_sta_reduction_sl_5 + depr_stabas_itc_sta_reduction_sl_15 + depr_stabas_itc_sta_reduction_sl_20 + depr_stabas_itc_sta_reduction_sl_39 + depr_stabas_itc_sta_reduction_custom; + assign( "depr_stabas_itc_sta_reduction_total", var_data((ssc_number_t) depr_stabas_itc_sta_reduction_total ) ); + double depr_stabas_itc_fed_reduction_total = depr_stabas_itc_fed_reduction_macrs_5 + depr_stabas_itc_fed_reduction_macrs_15 + depr_stabas_itc_fed_reduction_sl_5 + depr_stabas_itc_fed_reduction_sl_15 + depr_stabas_itc_fed_reduction_sl_20 + depr_stabas_itc_fed_reduction_sl_39 + depr_stabas_itc_fed_reduction_custom; + assign( "depr_stabas_itc_fed_reduction_total", var_data((ssc_number_t) depr_stabas_itc_fed_reduction_total ) ); + double depr_stabas_first_year_bonus_total = depr_stabas_macrs_5_bonus+depr_stabas_macrs_15_bonus+depr_stabas_sl_5_bonus+depr_stabas_sl_15_bonus+depr_stabas_sl_20_bonus+depr_stabas_sl_39_bonus+depr_stabas_custom_bonus; + assign( "depr_stabas_after_itc_total", var_data((ssc_number_t) (depr_stabas_total + depr_stabas_first_year_bonus_total) ) ); + assign( "depr_stabas_first_year_bonus_total", var_data((ssc_number_t) depr_stabas_first_year_bonus_total ) ); + assign( "depr_stabas_total", var_data((ssc_number_t) depr_stabas_total ) ); + + + assign( "itc_sta_percent_total", var_data((ssc_number_t) itc_sta_per ) ); + assign( "itc_sta_fixed_total", var_data((ssc_number_t) itc_sta_amount)); + + + + // Federal ITC/depreciation table + assign("depr_fedbas_percent_macrs_5", var_data((ssc_number_t) (depr_fedbas_macrs_5_frac*100.0))); + assign( "depr_alloc_macrs_5", var_data((ssc_number_t) depr_alloc_macrs_5 ) ); + double depr_fedbas_ibi_reduc_macrs_5 = depr_fedbas_macrs_5_frac * depr_fed_reduction_ibi; + double depr_fedbas_cbi_reduc_macrs_5 = depr_fedbas_macrs_5_frac * depr_fed_reduction_cbi; + assign( "depr_fedbas_ibi_reduc_macrs_5", var_data((ssc_number_t) depr_fedbas_ibi_reduc_macrs_5 ) ); + assign( "depr_fedbas_cbi_reduc_macrs_5", var_data((ssc_number_t) depr_fedbas_cbi_reduc_macrs_5 ) ); + assign( "depr_fedbas_prior_itc_macrs_5", var_data((ssc_number_t) ( depr_alloc_macrs_5 - depr_fedbas_ibi_reduc_macrs_5 - depr_fedbas_cbi_reduc_macrs_5)) ); + assign( "itc_fed_qual_macrs_5", var_data((ssc_number_t) itc_fed_qual_macrs_5 ) ); + double depr_fedbas_percent_qual_macrs_5 = (itc_fed_qual_total > 0)? 100.0 * itc_fed_qual_macrs_5 / itc_fed_qual_total:0.0; + assign( "depr_fedbas_percent_qual_macrs_5", var_data((ssc_number_t) depr_fedbas_percent_qual_macrs_5) ); + assign( "depr_fedbas_percent_amount_macrs_5", var_data((ssc_number_t) (depr_fedbas_percent_qual_macrs_5/100.0 * itc_fed_per)) ); + assign( "itc_disallow_fed_percent_macrs_5", var_data((ssc_number_t) itc_disallow_fed_percent_macrs_5 ) ); + assign( "depr_fedbas_fixed_amount_macrs_5", var_data((ssc_number_t) (depr_fedbas_percent_qual_macrs_5/100.0 * itc_fed_amount))); + assign( "itc_disallow_fed_fixed_macrs_5", var_data((ssc_number_t) itc_disallow_fed_fixed_macrs_5 ) ); + double depr_fedbas_itc_sta_reduction_macrs_5 = itc_sta_percent_deprbas_fed * itc_disallow_sta_percent_macrs_5 + itc_sta_amount_deprbas_fed * itc_disallow_sta_fixed_macrs_5; + double depr_fedbas_itc_fed_reduction_macrs_5 = itc_fed_percent_deprbas_fed * itc_disallow_fed_percent_macrs_5 + itc_fed_amount_deprbas_fed * itc_disallow_fed_fixed_macrs_5; + assign( "depr_fedbas_itc_sta_reduction_macrs_5", var_data((ssc_number_t) depr_fedbas_itc_sta_reduction_macrs_5 ) ); + assign( "depr_fedbas_itc_fed_reduction_macrs_5", var_data((ssc_number_t) depr_fedbas_itc_fed_reduction_macrs_5 ) ); + assign( "depr_fedbas_after_itc_macrs_5", var_data((ssc_number_t) (depr_fedbas_macrs_5 + depr_fedbas_macrs_5_bonus) ) ); + assign( "depr_fedbas_first_year_bonus_macrs_5", var_data((ssc_number_t) depr_fedbas_macrs_5_bonus ) ); + assign( "depr_fedbas_macrs_5", var_data((ssc_number_t) depr_fedbas_macrs_5 ) ); + + assign("depr_fedbas_percent_macrs_15", var_data((ssc_number_t) (depr_fedbas_macrs_15_frac*100.0))); + assign( "depr_alloc_macrs_15", var_data((ssc_number_t) depr_alloc_macrs_15 ) ); + double depr_fedbas_ibi_reduc_macrs_15 = depr_fedbas_macrs_15_frac * depr_fed_reduction_ibi; + double depr_fedbas_cbi_reduc_macrs_15 = depr_fedbas_macrs_15_frac * depr_fed_reduction_cbi; + assign( "depr_fedbas_ibi_reduc_macrs_15", var_data((ssc_number_t) depr_fedbas_ibi_reduc_macrs_15 ) ); + assign( "depr_fedbas_cbi_reduc_macrs_15", var_data((ssc_number_t) depr_fedbas_cbi_reduc_macrs_15 ) ); + assign( "depr_fedbas_prior_itc_macrs_15", var_data((ssc_number_t) ( depr_alloc_macrs_15 - depr_fedbas_ibi_reduc_macrs_15 - depr_fedbas_cbi_reduc_macrs_15)) ); + assign( "itc_fed_qual_macrs_15", var_data((ssc_number_t) itc_fed_qual_macrs_15 ) ); + double depr_fedbas_percent_qual_macrs_15 = (itc_fed_qual_total > 0)? 100.0 * itc_fed_qual_macrs_15 / itc_fed_qual_total:0.0; + assign( "depr_fedbas_percent_qual_macrs_15", var_data((ssc_number_t) depr_fedbas_percent_qual_macrs_15) ); + assign( "depr_fedbas_percent_amount_macrs_15", var_data((ssc_number_t) (depr_fedbas_percent_qual_macrs_15/100.0 * itc_fed_per)) ); + assign( "itc_disallow_fed_percent_macrs_15", var_data((ssc_number_t) itc_disallow_fed_percent_macrs_15 ) ); + assign( "depr_fedbas_fixed_amount_macrs_15", var_data((ssc_number_t) (depr_fedbas_percent_qual_macrs_15/100.0 * itc_fed_amount))); + assign( "itc_disallow_fed_fixed_macrs_15", var_data((ssc_number_t) itc_disallow_fed_fixed_macrs_15 ) ); + double depr_fedbas_itc_sta_reduction_macrs_15 = itc_sta_percent_deprbas_fed * itc_disallow_sta_percent_macrs_15 + itc_sta_amount_deprbas_fed * itc_disallow_sta_fixed_macrs_15; + double depr_fedbas_itc_fed_reduction_macrs_15 = itc_fed_percent_deprbas_fed * itc_disallow_fed_percent_macrs_15 + itc_fed_amount_deprbas_fed * itc_disallow_fed_fixed_macrs_15; + assign( "depr_fedbas_itc_sta_reduction_macrs_15", var_data((ssc_number_t) depr_fedbas_itc_sta_reduction_macrs_15 ) ); + assign( "depr_fedbas_itc_fed_reduction_macrs_15", var_data((ssc_number_t) depr_fedbas_itc_fed_reduction_macrs_15 ) ); + assign( "depr_fedbas_after_itc_macrs_15", var_data((ssc_number_t) (depr_fedbas_macrs_15 + depr_fedbas_macrs_15_bonus) ) ); + assign( "depr_fedbas_first_year_bonus_macrs_15", var_data((ssc_number_t) depr_fedbas_macrs_15_bonus ) ); + assign( "depr_fedbas_macrs_15", var_data((ssc_number_t) depr_fedbas_macrs_15 ) ); + + assign("depr_fedbas_percent_sl_5", var_data((ssc_number_t) (depr_fedbas_sl_5_frac*100.0))); + assign( "depr_alloc_sl_5", var_data((ssc_number_t) depr_alloc_sl_5 ) ); + double depr_fedbas_ibi_reduc_sl_5 = depr_fedbas_sl_5_frac * depr_fed_reduction_ibi; + double depr_fedbas_cbi_reduc_sl_5 = depr_fedbas_sl_5_frac * depr_fed_reduction_cbi; + assign( "depr_fedbas_ibi_reduc_sl_5", var_data((ssc_number_t) depr_fedbas_ibi_reduc_sl_5 ) ); + assign( "depr_fedbas_cbi_reduc_sl_5", var_data((ssc_number_t) depr_fedbas_cbi_reduc_sl_5 ) ); + assign( "depr_fedbas_prior_itc_sl_5", var_data((ssc_number_t) ( depr_alloc_sl_5 - depr_fedbas_ibi_reduc_sl_5 - depr_fedbas_cbi_reduc_sl_5)) ); + assign( "itc_fed_qual_sl_5", var_data((ssc_number_t) itc_fed_qual_sl_5 ) ); + double depr_fedbas_percent_qual_sl_5 = (itc_fed_qual_total > 0)? 100.0 * itc_fed_qual_sl_5 / itc_fed_qual_total:0.0; + assign( "depr_fedbas_percent_qual_sl_5", var_data((ssc_number_t) depr_fedbas_percent_qual_sl_5) ); + assign( "depr_fedbas_percent_amount_sl_5", var_data((ssc_number_t) (depr_fedbas_percent_qual_sl_5/100.0 * itc_fed_per)) ); + assign( "itc_disallow_fed_percent_sl_5", var_data((ssc_number_t) itc_disallow_fed_percent_sl_5 ) ); + assign( "depr_fedbas_fixed_amount_sl_5", var_data((ssc_number_t) (depr_fedbas_percent_qual_sl_5/100.0 * itc_fed_amount))); + assign( "itc_disallow_fed_fixed_sl_5", var_data((ssc_number_t) itc_disallow_fed_fixed_sl_5 ) ); + double depr_fedbas_itc_sta_reduction_sl_5 = itc_sta_percent_deprbas_fed * itc_disallow_sta_percent_sl_5 + itc_sta_amount_deprbas_fed * itc_disallow_sta_fixed_sl_5; + double depr_fedbas_itc_fed_reduction_sl_5 = itc_fed_percent_deprbas_fed * itc_disallow_fed_percent_sl_5 + itc_fed_amount_deprbas_fed * itc_disallow_fed_fixed_sl_5; + assign( "depr_fedbas_itc_sta_reduction_sl_5", var_data((ssc_number_t) depr_fedbas_itc_sta_reduction_sl_5 ) ); + assign( "depr_fedbas_itc_fed_reduction_sl_5", var_data((ssc_number_t) depr_fedbas_itc_fed_reduction_sl_5 ) ); + assign( "depr_fedbas_after_itc_sl_5", var_data((ssc_number_t) (depr_fedbas_sl_5 + depr_fedbas_sl_5_bonus) ) ); + assign( "depr_fedbas_first_year_bonus_sl_5", var_data((ssc_number_t) depr_fedbas_sl_5_bonus ) ); + assign( "depr_fedbas_sl_5", var_data((ssc_number_t) depr_fedbas_sl_5 ) ); + + assign("depr_fedbas_percent_sl_15", var_data((ssc_number_t) (depr_fedbas_sl_15_frac*100.0))); + assign( "depr_alloc_sl_15", var_data((ssc_number_t) depr_alloc_sl_15 ) ); + double depr_fedbas_ibi_reduc_sl_15 = depr_fedbas_sl_15_frac * depr_fed_reduction_ibi; + double depr_fedbas_cbi_reduc_sl_15 = depr_fedbas_sl_15_frac * depr_fed_reduction_cbi; + assign( "depr_fedbas_ibi_reduc_sl_15", var_data((ssc_number_t) depr_fedbas_ibi_reduc_sl_15 ) ); + assign( "depr_fedbas_cbi_reduc_sl_15", var_data((ssc_number_t) depr_fedbas_cbi_reduc_sl_15 ) ); + assign( "depr_fedbas_prior_itc_sl_15", var_data((ssc_number_t) ( depr_alloc_sl_15 - depr_fedbas_ibi_reduc_sl_15 - depr_fedbas_cbi_reduc_sl_15)) ); + assign( "itc_fed_qual_sl_15", var_data((ssc_number_t) itc_fed_qual_sl_15 ) ); + double depr_fedbas_percent_qual_sl_15 = (itc_fed_qual_total > 0)? 100.0 * itc_fed_qual_sl_15 / itc_fed_qual_total:0.0; + assign( "depr_fedbas_percent_qual_sl_15", var_data((ssc_number_t) depr_fedbas_percent_qual_sl_15) ); + assign( "depr_fedbas_percent_amount_sl_15", var_data((ssc_number_t) (depr_fedbas_percent_qual_sl_15/100.0 * itc_fed_per)) ); + assign( "itc_disallow_fed_percent_sl_15", var_data((ssc_number_t) itc_disallow_fed_percent_sl_15 ) ); + assign( "depr_fedbas_fixed_amount_sl_15", var_data((ssc_number_t) (depr_fedbas_percent_qual_sl_15/100.0 * itc_fed_amount))); + assign( "itc_disallow_fed_fixed_sl_15", var_data((ssc_number_t) itc_disallow_fed_fixed_sl_15 ) ); + double depr_fedbas_itc_sta_reduction_sl_15 = itc_sta_percent_deprbas_fed * itc_disallow_sta_percent_sl_15 + itc_sta_amount_deprbas_fed * itc_disallow_sta_fixed_sl_15; + double depr_fedbas_itc_fed_reduction_sl_15 = itc_fed_percent_deprbas_fed * itc_disallow_fed_percent_sl_15 + itc_fed_amount_deprbas_fed * itc_disallow_fed_fixed_sl_15; + assign( "depr_fedbas_itc_sta_reduction_sl_15", var_data((ssc_number_t) depr_fedbas_itc_sta_reduction_sl_15 ) ); + assign( "depr_fedbas_itc_fed_reduction_sl_15", var_data((ssc_number_t) depr_fedbas_itc_fed_reduction_sl_15 ) ); + assign( "depr_fedbas_after_itc_sl_15", var_data((ssc_number_t) (depr_fedbas_sl_15 + depr_fedbas_sl_15_bonus) ) ); + assign( "depr_fedbas_first_year_bonus_sl_15", var_data((ssc_number_t) depr_fedbas_sl_15_bonus ) ); + assign( "depr_fedbas_sl_15", var_data((ssc_number_t) depr_fedbas_sl_15 ) ); + + assign("depr_fedbas_percent_sl_20", var_data((ssc_number_t) (depr_fedbas_sl_20_frac*100.0))); + assign( "depr_alloc_sl_20", var_data((ssc_number_t) depr_alloc_sl_20 ) ); + double depr_fedbas_ibi_reduc_sl_20 = depr_fedbas_sl_20_frac * depr_fed_reduction_ibi; + double depr_fedbas_cbi_reduc_sl_20 = depr_fedbas_sl_20_frac * depr_fed_reduction_cbi; + assign( "depr_fedbas_ibi_reduc_sl_20", var_data((ssc_number_t) depr_fedbas_ibi_reduc_sl_20 ) ); + assign( "depr_fedbas_cbi_reduc_sl_20", var_data((ssc_number_t) depr_fedbas_cbi_reduc_sl_20 ) ); + assign( "depr_fedbas_prior_itc_sl_20", var_data((ssc_number_t) ( depr_alloc_sl_20 - depr_fedbas_ibi_reduc_sl_20 - depr_fedbas_cbi_reduc_sl_20)) ); + assign( "itc_fed_qual_sl_20", var_data((ssc_number_t) itc_fed_qual_sl_20 ) ); + double depr_fedbas_percent_qual_sl_20 = (itc_fed_qual_total > 0)? 100.0 * itc_fed_qual_sl_20 / itc_fed_qual_total:0.0; + assign( "depr_fedbas_percent_qual_sl_20", var_data((ssc_number_t) depr_fedbas_percent_qual_sl_20) ); + assign( "depr_fedbas_percent_amount_sl_20", var_data((ssc_number_t) (depr_fedbas_percent_qual_sl_20/100.0 * itc_fed_per)) ); + assign( "itc_disallow_fed_percent_sl_20", var_data((ssc_number_t) itc_disallow_fed_percent_sl_20 ) ); + assign( "depr_fedbas_fixed_amount_sl_20", var_data((ssc_number_t) (depr_fedbas_percent_qual_sl_20/100.0 * itc_fed_amount))); + assign( "itc_disallow_fed_fixed_sl_20", var_data((ssc_number_t) itc_disallow_fed_fixed_sl_20 ) ); + double depr_fedbas_itc_sta_reduction_sl_20 = itc_sta_percent_deprbas_fed * itc_disallow_sta_percent_sl_20 + itc_sta_amount_deprbas_fed * itc_disallow_sta_fixed_sl_20; + double depr_fedbas_itc_fed_reduction_sl_20 = itc_fed_percent_deprbas_fed * itc_disallow_fed_percent_sl_20 + itc_fed_amount_deprbas_fed * itc_disallow_fed_fixed_sl_20; + assign( "depr_fedbas_itc_sta_reduction_sl_20", var_data((ssc_number_t) depr_fedbas_itc_sta_reduction_sl_20 ) ); + assign( "depr_fedbas_itc_fed_reduction_sl_20", var_data((ssc_number_t) depr_fedbas_itc_fed_reduction_sl_20 ) ); + assign( "depr_fedbas_after_itc_sl_20", var_data((ssc_number_t) (depr_fedbas_sl_20 + depr_fedbas_sl_20_bonus) ) ); + assign( "depr_fedbas_first_year_bonus_sl_20", var_data((ssc_number_t) depr_fedbas_sl_20_bonus ) ); + assign( "depr_fedbas_sl_20", var_data((ssc_number_t) depr_fedbas_sl_20 ) ); + + assign("depr_fedbas_percent_sl_39", var_data((ssc_number_t) (depr_fedbas_sl_39_frac*100.0))); + assign( "depr_alloc_sl_39", var_data((ssc_number_t) depr_alloc_sl_39 ) ); + double depr_fedbas_ibi_reduc_sl_39 = depr_fedbas_sl_39_frac * depr_fed_reduction_ibi; + double depr_fedbas_cbi_reduc_sl_39 = depr_fedbas_sl_39_frac * depr_fed_reduction_cbi; + assign( "depr_fedbas_ibi_reduc_sl_39", var_data((ssc_number_t) depr_fedbas_ibi_reduc_sl_39 ) ); + assign( "depr_fedbas_cbi_reduc_sl_39", var_data((ssc_number_t) depr_fedbas_cbi_reduc_sl_39 ) ); + assign( "depr_fedbas_prior_itc_sl_39", var_data((ssc_number_t) ( depr_alloc_sl_39 - depr_fedbas_ibi_reduc_sl_39 - depr_fedbas_cbi_reduc_sl_39)) ); + assign( "itc_fed_qual_sl_39", var_data((ssc_number_t) itc_fed_qual_sl_39 ) ); + double depr_fedbas_percent_qual_sl_39 = (itc_fed_qual_total > 0)? 100.0 * itc_fed_qual_sl_39 / itc_fed_qual_total:0.0; + assign( "depr_fedbas_percent_qual_sl_39", var_data((ssc_number_t) depr_fedbas_percent_qual_sl_39) ); + assign( "depr_fedbas_percent_amount_sl_39", var_data((ssc_number_t) (depr_fedbas_percent_qual_sl_39/100.0 * itc_fed_per)) ); + assign( "itc_disallow_fed_percent_sl_39", var_data((ssc_number_t) itc_disallow_fed_percent_sl_39 ) ); + assign( "depr_fedbas_fixed_amount_sl_39", var_data((ssc_number_t) (depr_fedbas_percent_qual_sl_39/100.0 * itc_fed_amount))); + assign( "itc_disallow_fed_fixed_sl_39", var_data((ssc_number_t) itc_disallow_fed_fixed_sl_39 ) ); + double depr_fedbas_itc_sta_reduction_sl_39 = itc_sta_percent_deprbas_fed * itc_disallow_sta_percent_sl_39 + itc_sta_amount_deprbas_fed * itc_disallow_sta_fixed_sl_39; + double depr_fedbas_itc_fed_reduction_sl_39 = itc_fed_percent_deprbas_fed * itc_disallow_fed_percent_sl_39 + itc_fed_amount_deprbas_fed * itc_disallow_fed_fixed_sl_39; + assign( "depr_fedbas_itc_sta_reduction_sl_39", var_data((ssc_number_t) depr_fedbas_itc_sta_reduction_sl_39 ) ); + assign( "depr_fedbas_itc_fed_reduction_sl_39", var_data((ssc_number_t) depr_fedbas_itc_fed_reduction_sl_39 ) ); + assign( "depr_fedbas_after_itc_sl_39", var_data((ssc_number_t) (depr_fedbas_sl_39 + depr_fedbas_sl_39_bonus) ) ); + assign( "depr_fedbas_first_year_bonus_sl_39", var_data((ssc_number_t) depr_fedbas_sl_39_bonus ) ); + assign( "depr_fedbas_sl_39", var_data((ssc_number_t) depr_fedbas_sl_39 ) ); + + assign("depr_fedbas_percent_custom", var_data((ssc_number_t) (depr_fedbas_custom_frac*100.0))); + assign( "depr_alloc_custom", var_data((ssc_number_t) depr_alloc_custom ) ); + double depr_fedbas_ibi_reduc_custom = depr_fedbas_custom_frac * depr_fed_reduction_ibi; + double depr_fedbas_cbi_reduc_custom = depr_fedbas_custom_frac * depr_fed_reduction_cbi; + assign( "depr_fedbas_ibi_reduc_custom", var_data((ssc_number_t) depr_fedbas_ibi_reduc_custom ) ); + assign( "depr_fedbas_cbi_reduc_custom", var_data((ssc_number_t) depr_fedbas_cbi_reduc_custom ) ); + assign( "depr_fedbas_prior_itc_custom", var_data((ssc_number_t) ( depr_alloc_custom - depr_fedbas_ibi_reduc_custom - depr_fedbas_cbi_reduc_custom)) ); + assign( "itc_fed_qual_custom", var_data((ssc_number_t) itc_fed_qual_custom ) ); + double depr_fedbas_percent_qual_custom = (itc_fed_qual_total > 0)? 100.0 * itc_fed_qual_custom / itc_fed_qual_total:0.0; + assign( "depr_fedbas_percent_qual_custom", var_data((ssc_number_t) depr_fedbas_percent_qual_custom) ); + assign( "depr_fedbas_percent_amount_custom", var_data((ssc_number_t) (depr_fedbas_percent_qual_custom/100.0 * itc_fed_per)) ); + assign( "itc_disallow_fed_percent_custom", var_data((ssc_number_t) itc_disallow_fed_percent_custom ) ); + assign( "depr_fedbas_fixed_amount_custom", var_data((ssc_number_t) (depr_fedbas_percent_qual_custom/100.0 * itc_fed_amount))); + assign( "itc_disallow_fed_fixed_custom", var_data((ssc_number_t) itc_disallow_fed_fixed_custom ) ); + double depr_fedbas_itc_sta_reduction_custom = itc_sta_percent_deprbas_fed * itc_disallow_sta_percent_custom + itc_sta_amount_deprbas_fed * itc_disallow_sta_fixed_custom; + double depr_fedbas_itc_fed_reduction_custom = itc_fed_percent_deprbas_fed * itc_disallow_fed_percent_custom + itc_fed_amount_deprbas_fed * itc_disallow_fed_fixed_custom; + assign( "depr_fedbas_itc_sta_reduction_custom", var_data((ssc_number_t) depr_fedbas_itc_sta_reduction_custom ) ); + assign( "depr_fedbas_itc_fed_reduction_custom", var_data((ssc_number_t) depr_fedbas_itc_fed_reduction_custom ) ); + assign( "depr_fedbas_after_itc_custom", var_data((ssc_number_t) (depr_fedbas_custom + depr_fedbas_custom_bonus) ) ); + assign( "depr_fedbas_first_year_bonus_custom", var_data((ssc_number_t) depr_fedbas_custom_bonus ) ); + assign( "depr_fedbas_custom", var_data((ssc_number_t) depr_fedbas_custom ) ); + + + assign("depr_fedbas_percent_total", var_data((ssc_number_t) (100.0*(depr_fedbas_macrs_5_frac+depr_fedbas_macrs_15_frac+depr_fedbas_sl_5_frac+depr_fedbas_sl_15_frac+depr_fedbas_sl_20_frac+depr_fedbas_sl_39_frac+depr_fedbas_custom_frac)))); + assign( "depr_alloc_total", var_data((ssc_number_t) depr_alloc_total ) ); + assign("depr_fedbas_ibi_reduc_total", var_data((ssc_number_t) depr_fed_reduction_ibi)); + assign( "depr_fedbas_cbi_reduc_total", var_data((ssc_number_t) depr_fed_reduction_cbi ) ); + assign( "depr_fedbas_prior_itc_total", var_data((ssc_number_t) ( depr_alloc_total - depr_fed_reduction_ibi - depr_fed_reduction_cbi)) ); + assign( "itc_sta_qual_total", var_data((ssc_number_t) itc_sta_qual_total ) ); + assign( "depr_fedbas_percent_qual_total", var_data((ssc_number_t) 100.0) ); + assign( "depr_fedbas_percent_amount_total", var_data((ssc_number_t) itc_fed_per) ); + assign( "itc_disallow_fed_percent_total", var_data((ssc_number_t) (itc_disallow_fed_percent_macrs_5 + itc_disallow_fed_percent_macrs_15 + itc_disallow_fed_percent_sl_5 + itc_disallow_fed_percent_sl_15 + itc_disallow_fed_percent_sl_20 + itc_disallow_fed_percent_sl_39 + itc_disallow_fed_percent_custom) ) ); + assign( "depr_fedbas_fixed_amount_total", var_data((ssc_number_t) itc_fed_amount)); + assign( "itc_disallow_fed_fixed_total", var_data((ssc_number_t) (itc_disallow_fed_fixed_macrs_5 + itc_disallow_fed_fixed_macrs_15 + itc_disallow_fed_fixed_sl_5 + itc_disallow_fed_fixed_sl_15 + itc_disallow_fed_fixed_sl_20 + itc_disallow_fed_fixed_sl_39 + itc_disallow_fed_fixed_custom) ) ); + double depr_fedbas_itc_sta_reduction_total = depr_fedbas_itc_sta_reduction_macrs_5 + depr_fedbas_itc_sta_reduction_macrs_15 + depr_fedbas_itc_sta_reduction_sl_5 + depr_fedbas_itc_sta_reduction_sl_15 + depr_fedbas_itc_sta_reduction_sl_20 + depr_fedbas_itc_sta_reduction_sl_39 + depr_fedbas_itc_sta_reduction_custom; + assign( "depr_fedbas_itc_sta_reduction_total", var_data((ssc_number_t) depr_fedbas_itc_sta_reduction_total ) ); + double depr_fedbas_itc_fed_reduction_total = depr_fedbas_itc_fed_reduction_macrs_5 + depr_fedbas_itc_fed_reduction_macrs_15 + depr_fedbas_itc_fed_reduction_sl_5 + depr_fedbas_itc_fed_reduction_sl_15 + depr_fedbas_itc_fed_reduction_sl_20 + depr_fedbas_itc_fed_reduction_sl_39 + depr_fedbas_itc_fed_reduction_custom; + assign( "depr_fedbas_itc_fed_reduction_total", var_data((ssc_number_t) depr_fedbas_itc_fed_reduction_total ) ); + double depr_fedbas_first_year_bonus_total = depr_fedbas_macrs_5_bonus+depr_fedbas_macrs_15_bonus+depr_fedbas_sl_5_bonus+depr_fedbas_sl_15_bonus+depr_fedbas_sl_20_bonus+depr_fedbas_sl_39_bonus+depr_fedbas_custom_bonus; + assign( "depr_fedbas_after_itc_total", var_data((ssc_number_t) (depr_fedbas_total + depr_fedbas_first_year_bonus_total) ) ); + assign( "depr_fedbas_first_year_bonus_total", var_data((ssc_number_t) depr_fedbas_first_year_bonus_total ) ); + assign( "depr_fedbas_total", var_data((ssc_number_t) depr_fedbas_total ) ); + + assign( "depr_alloc_none_percent", var_data((ssc_number_t) (depr_alloc_none_frac*100.0) ) ); + assign( "depr_alloc_none", var_data((ssc_number_t) depr_alloc_none ) ); + assign( "depr_alloc_total", var_data((ssc_number_t) depr_alloc_total ) ); + // Project cash flow + + // for cost stacked bars + //npv(CF_energy_value, nyears, nom_discount_rate) + // present value of o and m value - note - present value is distributive - sum of pv = pv of sum + double pvAnnualOandM = npv(CF_om_fixed_expense, nyears, nom_discount_rate); + double pvFixedOandM = npv(CF_om_capacity_expense, nyears, nom_discount_rate); + double pvVariableOandM = npv(CF_om_production_expense, nyears, nom_discount_rate); + double pvFuelOandM = npv(CF_om_fuel_expense, nyears, nom_discount_rate); + double pvElec_price_for_heat_techs = npv(CF_om_elec_price_for_heat_techs, nyears, nom_discount_rate); + double pvOptFuel1OandM = npv(CF_om_opt_fuel_1_expense, nyears, nom_discount_rate); + double pvOptFuel2OandM = npv(CF_om_opt_fuel_2_expense, nyears, nom_discount_rate); + // double pvWaterOandM = NetPresentValue(sv[svNominalDiscountRate], cf[cfAnnualWaterCost], analysis_period); + + assign( "present_value_oandm", var_data((ssc_number_t)(pvAnnualOandM + pvFixedOandM + pvVariableOandM + pvFuelOandM + pvElec_price_for_heat_techs))); // + pvWaterOandM); + + assign( "present_value_oandm_nonfuel", var_data((ssc_number_t)(pvAnnualOandM + pvFixedOandM + pvVariableOandM))); + assign( "present_value_fuel", var_data((ssc_number_t)(pvFuelOandM + pvOptFuel1OandM + pvOptFuel2OandM))); + + // present value of insurance and property tax + double pvInsurance = npv(CF_insurance_expense, nyears, nom_discount_rate); + double pvPropertyTax = npv(CF_property_tax_expense, nyears, nom_discount_rate); + + assign( "present_value_insandproptax", var_data((ssc_number_t)(pvInsurance + pvPropertyTax))); + + + // check financial metric outputs per SAM issue 551 + ssc_number_t irr_metric_end = irr(CF_project_return_aftertax, nyears) * 100.0; + ssc_number_t irr_metric_flip_year = cf.at(CF_project_return_aftertax_irr, flip_target_year); + ssc_number_t npv_metric = npv(CF_project_return_aftertax, nyears, nom_discount_rate) + cf.at(CF_project_return_aftertax, 0); + + check_financial_metrics cfm; + cfm.check_irr(this, irr_metric_end); + cfm.check_irr_flip(this, irr_metric_flip_year); + cfm.check_npv(this, npv_metric); + cfm.check_debt_percentage(this, debt_fraction); + +// SAM 1038 + save_cf(CF_itc_fed_amount, nyears, "cf_itc_fed_amount"); + save_cf(CF_itc_fed_percent_amount, nyears, "cf_itc_fed_percent_amount"); + save_cf(CF_itc_fed, nyears, "cf_itc_fed"); + save_cf(CF_itc_sta_amount, nyears, "cf_itc_sta_amount"); + save_cf(CF_itc_sta_percent_amount, nyears, "cf_itc_sta_percent_amount"); + save_cf(CF_itc_sta, nyears, "cf_itc_sta"); + save_cf(CF_itc_total, nyears, "cf_itc_total"); + + } + + + // std lib + void major_equipment_depreciation( int cf_equipment_expenditure, int cf_depr_sched, int expenditure_year, int analysis_period, int cf_equipment_depreciation ) + { + // depreciate equipment cost in expenditure_year according to depr_sched schedule subject to cutoff by analysis_period + if ( (expenditure_year > 0 ) && (expenditure_year <= analysis_period)) + { + // sign convention from v3 model + double depreciable_basis = -cf.at(cf_equipment_expenditure, expenditure_year); + for (int i=expenditure_year; i<=analysis_period; i++) + { + cf.at(cf_equipment_depreciation,i) += depreciable_basis * cf.at(cf_depr_sched,i-expenditure_year+1); + } + + } + } + + // std lib + void depreciation_sched_5_year_macrs_half_year( int cf_line, int nyears ) + { + for (int i=1; i<=nyears; i++) + { + double factor = 0.0; + switch(i) + { + case 1: factor = 0.2000; break; + case 2: factor = 0.3200; break; + case 3: factor = 0.1920; break; + case 4: factor = 0.1152; break; + case 5: factor = 0.1152; break; + case 6: factor = 0.0576; break; + default: factor = 0.0; break; + } + cf.at(cf_line, i) = factor; + } + } + // std lib + void depreciation_sched_15_year_macrs_half_year( int cf_line, int nyears ) + { + for (int i=1; i<=nyears; i++) + { + double factor = 0.0; + switch(i) + { + case 1: factor = 0.0500; break; + case 2: factor = 0.0950; break; + case 3: factor = 0.0855; break; + case 4: factor = 0.0770; break; + case 5: factor = 0.0693; break; + case 6: factor = 0.0623; break; + case 7: factor = 0.0590; break; + case 8: factor = 0.0590; break; + case 9: factor = 0.0591; break; + case 10: factor = 0.0590; break; + case 11: factor = 0.0591; break; + case 12: factor = 0.0590; break; + case 13: factor = 0.0591; break; + case 14: factor = 0.0590; break; + case 15: factor = 0.0591; break; + case 16: factor = 0.0295; break; + default: factor = 0.0; break; + } + cf.at(cf_line, i) = factor; + } + } + // std lib + void depreciation_sched_5_year_straight_line_half_year( int cf_line, int nyears ) + { + for (int i=1; i<=nyears; i++) + { + double factor = 0.0; + switch(i) + { + case 1: factor = 0.1000; break; + case 2: factor = 0.2000; break; + case 3: factor = 0.2000; break; + case 4: factor = 0.2000; break; + case 5: factor = 0.2000; break; + case 6: factor = 0.1000; break; + default: factor = 0.0; break; + } + cf.at(cf_line, i) = factor; + } + } + // std lib + void depreciation_sched_15_year_straight_line_half_year( int cf_line, int nyears ) + { + for (int i=1; i<=nyears; i++) + { + double factor = 0.0; + switch(i) + { + case 1: factor = 0.0333; break; + case 2: factor = 0.0667; break; + case 3: factor = 0.0667; break; + case 4: factor = 0.0667; break; + case 5: factor = 0.0667; break; + case 6: factor = 0.0667; break; + case 7: factor = 0.0667; break; + case 8: factor = 0.0666; break; + case 9: factor = 0.0667; break; + case 10: factor = 0.0666; break; + case 11: factor = 0.0667; break; + case 12: factor = 0.0666; break; + case 13: factor = 0.0667; break; + case 14: factor = 0.0666; break; + case 15: factor = 0.0667; break; + case 16: factor = 0.0333; break; + + default: factor = 0.0; break; + } + cf.at(cf_line, i) = factor; + } + } + // std lib + void depreciation_sched_20_year_straight_line_half_year( int cf_line, int nyears ) + { + for (int i=1; i<=nyears; i++) + { + double factor = 0.0; + switch(i) + { + case 1: factor = 0.0250; break; + case 2: factor = 0.0500; break; + case 3: factor = 0.0500; break; + case 4: factor = 0.0500; break; + case 5: factor = 0.0500; break; + case 6: factor = 0.0500; break; + case 7: factor = 0.0500; break; + case 8: factor = 0.0500; break; + case 9: factor = 0.0500; break; + case 10: factor = 0.0500; break; + case 11: factor = 0.0500; break; + case 12: factor = 0.0500; break; + case 13: factor = 0.0500; break; + case 14: factor = 0.0500; break; + case 15: factor = 0.0500; break; + case 16: factor = 0.0500; break; + case 17: factor = 0.0500; break; + case 18: factor = 0.0500; break; + case 19: factor = 0.0500; break; + case 20: factor = 0.0500; break; + case 21: factor = 0.0250; break; + default: factor = 0.0; break; + } + cf.at(cf_line, i) = factor; + } + } + // std lib + void depreciation_sched_39_year_straight_line_half_year( int cf_line, int nyears ) + { + for (int i=1; i<=nyears; i++) + { + double factor = 0.0; + double base=2.56410256410256e-2; + switch(i) + { + case 1: factor = 0.5*base; break; + case 2: factor = base; break; + case 3: factor = base; break; + case 4: factor = base; break; + case 5: factor = base; break; + case 6: factor = base; break; + case 7: factor = base; break; + case 8: factor = base; break; + case 9: factor = base; break; + case 10: factor = base; break; + case 11: factor = base; break; + case 12: factor = base; break; + case 13: factor = base; break; + case 14: factor = base; break; + case 15: factor = base; break; + case 16: factor = base; break; + case 17: factor = base; break; + case 18: factor = base; break; + case 19: factor = base; break; + case 20: factor = base; break; + case 21: factor = base; break; + case 22: factor = base; break; + case 23: factor = base; break; + case 24: factor = base; break; + case 25: factor = base; break; + case 26: factor = base; break; + case 27: factor = base; break; + case 28: factor = base; break; + case 29: factor = base; break; + case 30: factor = base; break; + case 31: factor = base; break; + case 32: factor = base; break; + case 33: factor = base; break; + case 34: factor = base; break; + case 35: factor = base; break; + case 36: factor = base; break; + case 37: factor = base; break; + case 38: factor = base; break; + case 39: factor = base; break; + case 40: factor = 0.5*base; break; + default: factor = 0.0; break; + } + cf.at(cf_line, i) = factor; + } + } + + + void depreciation_sched_custom(int cf_line, int nyears, const std::string &custom) + { + // computes custom percentage schedule 100% + // if customValue is array then annual schedule of percent + // if customValue is a single value then that value is used up to 100% + int i; + size_t count = 0; + ssc_number_t *parr = as_array(custom, &count); + for (i = 1; i<=nyears; i++) + { + cf.at(cf_line,i) = 0; + } + + if (count ==1) // single value + { + cf.at(cf_line,1) = parr[0]/100.0; + } + else // annual schedule + {// note schedules begin at year 1 (index 0) + int scheduleDuration = ((int)count > nyears)? nyears : (int)count; + for (i = 1; i<=scheduleDuration; i++) + { + cf.at(cf_line,i) = parr[i-1] / 100.0; // percentage to factor + } + } + } + + + + // std lib + void save_cf(int cf_line, int nyears, const std::string &name) + { + ssc_number_t *arrp = allocate( name, nyears+1 ); + for (int i=0;i<=nyears;i++) + arrp[i] = (ssc_number_t)cf.at(cf_line, i); + } + + void escal_or_annual( int cf_line, int nyears, const std::string &variable, + double inflation_rate, double scale, bool as_rate=true, double escal = 0.0) + { + size_t count; + ssc_number_t *arrp = as_array(variable, &count); + + if (as_rate) + { + if (count == 1) + { + escal = inflation_rate + scale*arrp[0]; + for (int i=0; i < nyears; i++) + cf.at(cf_line, i+1) = pow( 1+escal, i ); + } + else + { + for (int i=0; i < nyears && i < (int)count; i++) + cf.at(cf_line, i+1) = 1 + arrp[i]*scale; + } + } + else + { + if (count == 1) + { + for (int i=0;i0;i--) + result = rr * result + cf.at(cf_line,i); + + return result*rr; + } + +/* ported from http://code.google.com/p/irr-newtonraphson-calculator/ */ + bool is_valid_iter_bound(double estimated_return_rate) + { + return estimated_return_rate != -1 && (estimated_return_rate < std::numeric_limits::max()) && (estimated_return_rate > std::numeric_limits::min()); + } + + double irr_poly_sum(double estimated_return_rate, int cf_line, int count) + { + double sum_of_polynomial = 0; + if (is_valid_iter_bound(estimated_return_rate)) + { + for (int j = 0; j <= count ; j++) + { + double val = (pow((1 + estimated_return_rate), j)); + if (val != 0.0) + sum_of_polynomial += cf.at(cf_line,j)/val; + else + break; + } + } + return sum_of_polynomial; + } + + double irr_derivative_sum(double estimated_return_rate,int cf_line, int count) + { + double sum_of_derivative = 0; + if (is_valid_iter_bound(estimated_return_rate)) + for (int i = 1; i <= count ; i++) + { + sum_of_derivative += cf.at(cf_line,i)*(i)/pow((1 + estimated_return_rate), i+1); + } + return sum_of_derivative*-1; + } + + double irr_scale_factor( int cf_unscaled, int count) + { + // scale to max value for better irr convergence + if (count<1) return 1.0; + int i=0; + double max= std::abs(cf.at(cf_unscaled,0)); + for (i=0;i<=count;i++) + if (std::abs(cf.at(cf_unscaled,i))> max) max = std::abs(cf.at(cf_unscaled,i)); + return (max>0 ? max:1); + } + + bool is_valid_irr( int cf_line, int count, double residual, double tolerance, int number_of_iterations, int max_iterations, double calculated_irr, double scale_factor ) + { + double npv_of_irr = npv(cf_line,count,calculated_irr)+cf.at(cf_line,0); + double npv_of_irr_plus_delta = npv(cf_line,count,calculated_irr+0.001)+cf.at(cf_line,0); + bool is_valid = ( (number_of_iterationsnpv_of_irr_plus_delta) && (std::abs(npv_of_irr/scale_factor) Date: Wed, 9 Oct 2024 02:56:33 -0600 Subject: [PATCH 43/85] update single owner from latest develop version --- ssc/cmod_singleowner.cpp | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/ssc/cmod_singleowner.cpp b/ssc/cmod_singleowner.cpp index 84c197e82..bc39c946e 100644 --- a/ssc/cmod_singleowner.cpp +++ b/ssc/cmod_singleowner.cpp @@ -55,13 +55,10 @@ static var_info _cm_vtab_singleowner[] = { { SSC_INPUT, SSC_ARRAY, "gen", "Net power to or from the grid", "kW", "", "System Output", "*", "", "" }, - { SSC_INPUT, SSC_ARRAY, "gen_without_battery", "Electricity to or from the renewable system, without the battery", "kW", "", "System Output", "", "", "" }, - - { SSC_INPUT, SSC_ARRAY, "degradation", "Annual energy degradation", "", "", "System Output", "system_use_lifetime_output=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "system_capacity", "System nameplate capacity", "kW", "", "System Output", "?=0", "", "" }, - - { SSC_INPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption w/ avail derate", "kWe-hr", "", "Heat Model Output", "?=0", "", ""}, + { SSC_INPUT, SSC_ARRAY, "gen_without_battery", "Electricity to or from the renewable system, without the battery", "kW", "", "System Output", "", "", "" }, + { SSC_INPUT, SSC_ARRAY, "degradation", "Annual energy degradation", "", "", "System Output", "system_use_lifetime_output=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "system_capacity", "System nameplate capacity", "kW", "", "System Output", "?=0", "", "" }, /* PPA Buy Rate values */ { SSC_INPUT, SSC_ARRAY, "utility_bill_w_sys", "Electricity bill with system", "$", "", "Utility Bill", "", "", "" }, @@ -544,10 +541,7 @@ static var_info _cm_vtab_singleowner[] = { { SSC_OUTPUT, SSC_ARRAY, "cf_om_production2_expense", "O&M fuel cell production-based expense", "$", "", "Cash Flow Expenses", "", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_om_capacity2_expense", "O&M fuel cell capacity-based expense", "$", "", "Cash Flow Expenses", "", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_om_fuel_expense", "Fuel expense", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, - - { SSC_OUTPUT, SSC_ARRAY, "cf_om_elec_price_for_heat_techs", "Electricity expense in heat models", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, - - { SSC_OUTPUT, SSC_ARRAY, "cf_om_opt_fuel_1_expense", "Feedstock biomass expense", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_opt_fuel_1_expense", "Feedstock biomass expense", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_om_opt_fuel_2_expense", "Feedstock coal expense", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_property_tax_assessed_value", "Property tax net assessed value", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, @@ -694,7 +688,6 @@ enum { CF_om_production2_expense, CF_om_capacity2_expense, CF_om_fuel_expense, - CF_om_elec_price_for_heat_techs, CF_om_opt_fuel_2_expense, CF_om_opt_fuel_1_expense, @@ -865,8 +858,6 @@ enum { CF_energy_sales, CF_energy_purchases, - CF_elec_purchases_for_heat_techs, - CF_energy_without_battery, CF_battery_discharged, @@ -1014,8 +1005,6 @@ class cm_singleowner : public compute_module // In conjunction with SAM - take installed costs and salestax costs (for deducting if necessary) double cost_prefinancing = as_double("total_installed_cost"); - double annual_electricity_consumption_heat_model = as_double("annual_electricity_consumption"); - // use named range names for variables whenever possible double nameplate = as_double("system_capacity"); double year1_fuel_use = as_double("annual_fuel_usage"); // kWht @@ -1061,8 +1050,6 @@ class cm_singleowner : public compute_module escal_or_annual( CF_om_capacity_expense, nyears, "om_capacity", inflation_rate, 1.0, false, as_double("om_capacity_escal")*0.01 ); escal_or_annual( CF_om_fuel_expense, nyears, "om_fuel_cost", inflation_rate, as_double("system_heat_rate")*0.001, false, as_double("om_fuel_cost_escal")*0.01 ); - escal_or_annual(CF_om_elec_price_for_heat_techs, nyears, "om_elec_price_for_heat_techs", inflation_rate, 1.0, false, as_double("om_elec_price_for_heat_techs_escal")*0.01 ); - escal_or_annual( CF_om_opt_fuel_1_expense, nyears, "om_opt_fuel_1_cost", inflation_rate, 1.0, false, as_double("om_opt_fuel_1_cost_escal")*0.01 ); escal_or_annual( CF_om_opt_fuel_2_expense, nyears, "om_opt_fuel_2_cost", inflation_rate, 1.0, false, as_double("om_opt_fuel_2_cost_escal")*0.01 ); @@ -1199,7 +1186,6 @@ class cm_singleowner : public compute_module double first_year_energy = 0.0; double first_year_sales = 0.0; double first_year_purchases = 0.0; - double first_year_elec_purchases_for_heat_techs = as_double("annual_electricity_consumption"); //[kWe-hr] // degradation @@ -1230,9 +1216,6 @@ class cm_singleowner : public compute_module // dispatch if (as_integer("system_use_lifetime_output") == 1) { - if (first_year_elec_purchases_for_heat_techs > 0.0) - throw exec_error("singleowner", "system_use_lifetime_output must be 0 if using electricity purchases for heat technologies option"); - // hourly_enet includes all curtailment, availability for (size_t y = 1; y <= (size_t)nyears; y++) { @@ -1254,12 +1237,10 @@ class cm_singleowner : public compute_module cf.at(CF_energy_net, 1) = first_year_energy; cf.at(CF_energy_sales, 1) = first_year_sales; cf.at(CF_energy_purchases, 1) = first_year_purchases; - cf.at(CF_elec_purchases_for_heat_techs, 1) = first_year_elec_purchases_for_heat_techs; for (i = 1; i <= nyears; i++) { cf.at(CF_energy_net, i) = first_year_energy * cf.at(CF_degradation, i); cf.at(CF_energy_sales, i) = first_year_sales * cf.at(CF_degradation, i); cf.at(CF_energy_purchases, i) = first_year_purchases * cf.at(CF_degradation, i); - cf.at(CF_elec_purchases_for_heat_techs, i) = first_year_elec_purchases_for_heat_techs * cf.at(CF_degradation, i); } } @@ -1414,8 +1395,6 @@ class cm_singleowner : public compute_module cf.at(CF_om_capacity2_expense, i) *= nameplate2; cf.at(CF_om_fuel_expense,i) *= fuel_use[i-1]; - cf.at(CF_om_elec_price_for_heat_techs, i) *= cf.at(CF_elec_purchases_for_heat_techs, i); - //Battery Production OM Costs cf.at(CF_om_production1_expense, i) *= battery_discharged[i - 1]; //$/MWh * 0.001 MWh/kWh * kWh = $ cf.at(CF_om_production2_expense, i) *= fuelcell_discharged[i-1]; @@ -1629,7 +1608,6 @@ class cm_singleowner : public compute_module + cf.at(CF_om_production2_expense, i) + cf.at(CF_om_capacity2_expense, i) + cf.at(CF_om_fuel_expense, i) - + cf.at(CF_om_elec_price_for_heat_techs, i) + cf.at(CF_om_opt_fuel_1_expense, i) + cf.at(CF_om_opt_fuel_2_expense, i) + cf.at(CF_land_lease_expense, i) @@ -3470,7 +3448,6 @@ class cm_singleowner : public compute_module } save_cf( CF_om_fuel_expense, nyears, "cf_om_fuel_expense" ); - save_cf( CF_om_elec_price_for_heat_techs, nyears, "cf_om_elec_price_for_heat_techs"); save_cf( CF_om_opt_fuel_1_expense, nyears, "cf_om_opt_fuel_1_expense" ); save_cf( CF_om_opt_fuel_2_expense, nyears, "cf_om_opt_fuel_2_expense" ); save_cf(CF_land_lease_expense, nyears, "cf_land_lease_expense"); @@ -3898,12 +3875,11 @@ class cm_singleowner : public compute_module double pvFixedOandM = npv(CF_om_capacity_expense, nyears, nom_discount_rate); double pvVariableOandM = npv(CF_om_production_expense, nyears, nom_discount_rate); double pvFuelOandM = npv(CF_om_fuel_expense, nyears, nom_discount_rate); - double pvElec_price_for_heat_techs = npv(CF_om_elec_price_for_heat_techs, nyears, nom_discount_rate); double pvOptFuel1OandM = npv(CF_om_opt_fuel_1_expense, nyears, nom_discount_rate); double pvOptFuel2OandM = npv(CF_om_opt_fuel_2_expense, nyears, nom_discount_rate); // double pvWaterOandM = NetPresentValue(sv[svNominalDiscountRate], cf[cfAnnualWaterCost], analysis_period); - assign( "present_value_oandm", var_data((ssc_number_t)(pvAnnualOandM + pvFixedOandM + pvVariableOandM + pvFuelOandM + pvElec_price_for_heat_techs))); // + pvWaterOandM); + assign( "present_value_oandm", var_data((ssc_number_t)(pvAnnualOandM + pvFixedOandM + pvVariableOandM + pvFuelOandM))); // + pvWaterOandM); assign( "present_value_oandm_nonfuel", var_data((ssc_number_t)(pvAnnualOandM + pvFixedOandM + pvVariableOandM))); assign( "present_value_fuel", var_data((ssc_number_t)(pvFuelOandM + pvOptFuel1OandM + pvOptFuel2OandM))); From d39923e389e1e8d5b729338442a7fb76272eb658 Mon Sep 17 00:00:00 2001 From: Steven Janzou Date: Tue, 15 Oct 2024 04:16:43 -0600 Subject: [PATCH 44/85] Update IPH MSPT to output "load" to call cmod_utilityrate5 --- ssc/cmod_mspt_iph.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index fa5845798..45ad5f2ec 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -2288,6 +2288,7 @@ class cm_mspt_iph : public compute_module ssc_number_t* p_gen = allocate("gen", count); ssc_number_t* p_W_dot_parasitic_tot = as_array("W_dot_parasitic_tot", &count); ssc_number_t* p_W_dot_par_tot_haf = allocate("W_dot_par_tot_haf", n_steps_fixed); + ssc_number_t* p_load = allocate("load", n_steps_fixed); // testing using cmod_utilityrate5 for electricity rates p_load = p_W_dot_par_tot_haf ssc_number_t* p_time_final_hr = as_array("time_hr", &count); @@ -2298,7 +2299,7 @@ class cm_mspt_iph : public compute_module p_gen[i] = (ssc_number_t)(p_q_dot_heat_sink[i] * 1.E3 * haf(hour)); //[kWt] p_W_dot_parasitic_tot[i] *= -1.0; //[MWe] Label is total parasitics, so change to a positive value p_W_dot_par_tot_haf[i] = (ssc_number_t)(p_W_dot_parasitic_tot[i] * haf(hour) * 1.E3); //[kWe] apply availability derate and convert from MWe - + p_load[i] = p_W_dot_par_tot_haf[i]; } ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour); From 3ce0bbf3e3cdc6b1b749f8a4dc476b48417a7cce Mon Sep 17 00:00:00 2001 From: Steven Janzou Date: Wed, 16 Oct 2024 03:39:53 -0600 Subject: [PATCH 45/85] Update cf_om_elec_price__for_heat_techs to utility_bill_wo_sys Now, IPH MSPT using cmod_utilityrate5 results with p_W_dot_par_tot_haf (parasitic electric load from CSP model) as load --- ssc/cmod_singleowner_heat.cpp | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/ssc/cmod_singleowner_heat.cpp b/ssc/cmod_singleowner_heat.cpp index 60d1868c1..3d8192078 100644 --- a/ssc/cmod_singleowner_heat.cpp +++ b/ssc/cmod_singleowner_heat.cpp @@ -64,8 +64,10 @@ static var_info _cm_vtab_singleowner_heat[] = { /* PPA Buy Rate values */ - { SSC_INPUT, SSC_ARRAY, "utility_bill_w_sys", "Electricity bill with system", "$", "", "Utility Bill", "", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "cf_utility_bill", "Electricity purchase", "$", "", "", "", "LENGTH_EQUAL=cf_length", "" }, + { SSC_INPUT, SSC_ARRAY, "utility_bill_w_sys", "Electricity bill with system", "$", "", "Utility Bill", "", "", "" }, + // parasitic electricity cost from cmod_utilityrate5 + { SSC_INPUT, SSC_ARRAY, "utility_bill_wo_sys", "Electricity bill without system", "$", "", "Utility Bill", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_utility_bill", "Electricity purchase", "$", "", "", "", "LENGTH_EQUAL=cf_length", "" }, /*loan moratorium from Sara for India Documentation\India\Loan Moratorum @@ -865,7 +867,7 @@ enum { CF_energy_sales, CF_energy_purchases, - CF_elec_purchases_for_heat_techs, +// CF_elec_purchases_for_heat_techs, CF_energy_without_battery, @@ -1061,7 +1063,13 @@ class cm_singleowner_heat : public compute_module escal_or_annual( CF_om_capacity_expense, nyears, "om_capacity", inflation_rate, 1.0, false, as_double("om_capacity_escal")*0.01 ); escal_or_annual( CF_om_fuel_expense, nyears, "om_fuel_cost", inflation_rate, as_double("system_heat_rate")*0.001, false, as_double("om_fuel_cost_escal")*0.01 ); - escal_or_annual(CF_om_elec_price_for_heat_techs, nyears, "om_elec_price_for_heat_techs", inflation_rate, 1.0, false, as_double("om_elec_price_for_heat_techs_escal")*0.01 ); +// escal_or_annual(CF_om_elec_price_for_heat_techs, nyears, "om_elec_price_for_heat_techs", inflation_rate, 1.0, false, as_double("om_elec_price_for_heat_techs_escal")*0.01 ); + arrp = as_array("utility_bill_wo_sys", &count); + for (i = 0; i < count && i <= nyears; i++) + cf.at(CF_om_elec_price_for_heat_techs, i) = arrp[i]; + + + escal_or_annual( CF_om_opt_fuel_1_expense, nyears, "om_opt_fuel_1_cost", inflation_rate, 1.0, false, as_double("om_opt_fuel_1_cost_escal")*0.01 ); escal_or_annual( CF_om_opt_fuel_2_expense, nyears, "om_opt_fuel_2_cost", inflation_rate, 1.0, false, as_double("om_opt_fuel_2_cost_escal")*0.01 ); @@ -1254,12 +1262,12 @@ class cm_singleowner_heat : public compute_module cf.at(CF_energy_net, 1) = first_year_energy; cf.at(CF_energy_sales, 1) = first_year_sales; cf.at(CF_energy_purchases, 1) = first_year_purchases; - cf.at(CF_elec_purchases_for_heat_techs, 1) = first_year_elec_purchases_for_heat_techs; +// cf.at(CF_elec_purchases_for_heat_techs, 1) = first_year_elec_purchases_for_heat_techs; for (i = 1; i <= nyears; i++) { cf.at(CF_energy_net, i) = first_year_energy * cf.at(CF_degradation, i); cf.at(CF_energy_sales, i) = first_year_sales * cf.at(CF_degradation, i); cf.at(CF_energy_purchases, i) = first_year_purchases * cf.at(CF_degradation, i); - cf.at(CF_elec_purchases_for_heat_techs, i) = first_year_elec_purchases_for_heat_techs * cf.at(CF_degradation, i); + // cf.at(CF_elec_purchases_for_heat_techs, i) = first_year_elec_purchases_for_heat_techs * cf.at(CF_degradation, i); } } @@ -1414,7 +1422,7 @@ class cm_singleowner_heat : public compute_module cf.at(CF_om_capacity2_expense, i) *= nameplate2; cf.at(CF_om_fuel_expense,i) *= fuel_use[i-1]; - cf.at(CF_om_elec_price_for_heat_techs, i) *= cf.at(CF_elec_purchases_for_heat_techs, i); + // cf.at(CF_om_elec_price_for_heat_techs, i) *= cf.at(CF_elec_purchases_for_heat_techs, i); //Battery Production OM Costs cf.at(CF_om_production1_expense, i) *= battery_discharged[i - 1]; //$/MWh * 0.001 MWh/kWh * kWh = $ From 1192d735a8be095f379a80aeec6eb8c04a3c7a8a Mon Sep 17 00:00:00 2001 From: qualand <34353104+qualand@users.noreply.github.com> Date: Thu, 17 Oct 2024 15:45:12 -0600 Subject: [PATCH 46/85] adding electric heater into trough IPH model - enabled in dispatch too - solver needs to be debugged --- ssc/cmod_fresnel_physical.cpp | 6 +- ssc/cmod_fresnel_physical_iph.cpp | 6 +- ssc/cmod_tcsmolten_salt.cpp | 10 --- ssc/cmod_trough_physical.cpp | 6 +- ssc/cmod_trough_physical_iph.cpp | 115 ++++++++++++++++++++++++++---- tcs/csp_solver_core.cpp | 2 +- tcs/csp_system_costs.cpp | 33 +++++---- tcs/csp_system_costs.h | 8 ++- tcs/cst_iph_dispatch.cpp | 6 +- 9 files changed, 142 insertions(+), 50 deletions(-) diff --git a/ssc/cmod_fresnel_physical.cpp b/ssc/cmod_fresnel_physical.cpp index fce04356a..d8e5f8f32 100644 --- a/ssc/cmod_fresnel_physical.cpp +++ b/ssc/cmod_fresnel_physical.cpp @@ -1595,15 +1595,15 @@ class cm_fresnel_physical : public compute_module double sales_tax_rate = as_double("sales_tax_rate"); // Define outputs - double power_plant_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, htf_system_cost_out, fossil_backup_cost_out, contingency_cost_out, + double power_plant_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, heater_cost_out, htf_system_cost_out, fossil_backup_cost_out, contingency_cost_out, total_direct_cost_out, epc_total_cost_out, plm_total_cost_out, total_indirect_cost_out, sales_tax_total_out, total_installed_cost_out, installed_per_capacity_out; // Calculate Costs - N_mspt::calculate_mslf_costs(site_improvements_area, site_improvements_spec_cost, solar_field_area, solar_field_spec_cost, htf_system_area, htf_system_spec_cost, Q_tes, storage_spec_cost, fossil_backup_mwe, + N_mspt::calculate_mslf_costs(site_improvements_area, site_improvements_spec_cost, solar_field_area, solar_field_spec_cost, 0.0, 0.0, htf_system_area, htf_system_spec_cost, Q_tes, storage_spec_cost, fossil_backup_mwe, fossil_spec_cost, power_plant_mwe, power_plant_spec_cost, bop_mwe, bop_spec_cost, contingency_percent, total_land_area, nameplate, epc_cost_per_acre, epc_cost_percent_direct, epc_cost_per_watt, epc_cost_fixed, plm_cost_per_acre, plm_cost_percent_direct, plm_cost_per_watt, plm_cost_fixed, sales_tax_rate, sales_tax_percent, - power_plant_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, htf_system_cost_out, fossil_backup_cost_out, contingency_cost_out, + power_plant_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, heater_cost_out, htf_system_cost_out, fossil_backup_cost_out, contingency_cost_out, total_direct_cost_out, epc_total_cost_out, plm_total_cost_out, total_indirect_cost_out, sales_tax_total_out, total_installed_cost_out, installed_per_capacity_out); // Assign Outputs diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index fdc452a48..6f2d2ce39 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -1381,16 +1381,16 @@ class cm_fresnel_physical_iph : public compute_module double sales_tax_rate = as_double("sales_tax_rate"); // Define outputs - double heat_sink_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, htf_system_cost_out, contingency_cost_out, + double heat_sink_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, heater_cost_out, htf_system_cost_out, contingency_cost_out, total_direct_cost_out, epc_total_cost_out, plm_total_cost_out, total_indirect_cost_out, sales_tax_total_out, total_installed_cost_out, installed_per_capacity_out; double dummy; // Calculate Costs - N_mspt::calculate_mslf_costs(site_improvements_area, site_improvements_spec_cost, solar_field_area, solar_field_spec_cost, htf_system_area, htf_system_spec_cost, Q_tes, storage_spec_cost,0,0, + N_mspt::calculate_mslf_costs(site_improvements_area, site_improvements_spec_cost, solar_field_area, solar_field_spec_cost, 0.0, 0.0, htf_system_area, htf_system_spec_cost, Q_tes, storage_spec_cost,0,0, heat_sink_mwt, heat_sink_spec_cost, bop_mwt, bop_spec_cost, contingency_percent, total_land_area, nameplate, epc_cost_per_acre, epc_cost_percent_direct, epc_cost_per_watt, epc_cost_fixed, plm_cost_per_acre, plm_cost_percent_direct, plm_cost_per_watt, plm_cost_fixed, sales_tax_rate, sales_tax_percent, - heat_sink_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, htf_system_cost_out, dummy, contingency_cost_out, + heat_sink_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, heater_cost_out, htf_system_cost_out, dummy, contingency_cost_out, total_direct_cost_out, epc_total_cost_out, plm_total_cost_out, total_indirect_cost_out, sales_tax_total_out, total_installed_cost_out, installed_per_capacity_out); // Assign Outputs diff --git a/ssc/cmod_tcsmolten_salt.cpp b/ssc/cmod_tcsmolten_salt.cpp index f49fccf09..527a642e8 100644 --- a/ssc/cmod_tcsmolten_salt.cpp +++ b/ssc/cmod_tcsmolten_salt.cpp @@ -91,16 +91,6 @@ static var_info _cm_vtab_tcsmolten_salt[] = { { SSC_INPUT, SSC_NUMBER, "is_calc_sm", "False (default): use input solarm, True: calc solar multiple to achieve q_dot_rec_des_target", "", "", "Tower and Receiver", "?=0", "", "SIMULATION_PARAMETER"}, { SSC_INPUT, SSC_NUMBER, "q_dot_rec_des_target", "Target design receiver thermal power", "MWe", "", "Tower and Receiver", "is_calc_sm=1", "", "SIMULATION_PARAMETER" }, - // System Design - { SSC_INPUT, SSC_NUMBER, "is_parallel_htr", "Does plant include a HTF heater parallel to solar field?", "", "", "System Control", "?=0", "", ""}, - { SSC_INPUT, SSC_NUMBER, "T_htf_cold_des", "Cold HTF inlet temperature at design conditions", "C", "", "System Design", "*", "", ""}, - { SSC_INPUT, SSC_NUMBER, "T_htf_hot_des", "Hot HTF outlet temperature at design conditions", "C", "", "System Design", "*", "", ""}, - { SSC_INPUT, SSC_NUMBER, "P_ref", "Reference output electric power at design condition", "MW", "", "System Design", "*", "", ""}, - { SSC_INPUT, SSC_NUMBER, "design_eff", "Power cycle efficiency at design", "none", "", "System Design", "*", "", ""}, - { SSC_INPUT, SSC_NUMBER, "tshours", "Equivalent full-load thermal storage hours", "hr", "", "System Design", "*", "", ""}, - { SSC_INPUT, SSC_NUMBER, "solarm", "Solar multiple", "-", "", "System Design", "*", "", ""}, - { SSC_INPUT, SSC_NUMBER, "dni_des", "Design-point DNI", "W/m2", "", "System Design", "*", "", ""}, - // Solar field { SSC_INPUT, SSC_NUMBER, "field_model_type", "0=design field and tower/receiver geometry, 1=design field, 2=user specified field, 3=user flux and eta map, pass heliostat_positions to SolarPILOT for layout, 4=user flux and eta maps, no SolarPILOT, input A_sf_in, total_land_area_before_rad_cooling_in, and N_hel", "", "", "Heliostat Field", "*", "", ""}, { SSC_INPUT, SSC_NUMBER, "helio_width", "Heliostat width", "m", "", "Heliostat Field", "field_model_type<4", "", ""}, diff --git a/ssc/cmod_trough_physical.cpp b/ssc/cmod_trough_physical.cpp index 32bba42a4..a93fec7ef 100644 --- a/ssc/cmod_trough_physical.cpp +++ b/ssc/cmod_trough_physical.cpp @@ -2301,15 +2301,15 @@ class cm_trough_physical : public compute_module // Define outputs - double power_plant_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, htf_system_cost_out, fossil_backup_cost_out, contingency_cost_out, + double power_plant_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, heater_cost_out, htf_system_cost_out, fossil_backup_cost_out, contingency_cost_out, total_direct_cost_out, epc_total_cost_out, plm_total_cost_out, total_indirect_cost_out, sales_tax_total_out, total_installed_cost_out, installed_per_capacity_out; // Calculate Costs - N_mspt::calculate_mslf_costs(site_improvements_area, site_improvements_spec_cost, solar_field_area, solar_field_spec_cost, htf_system_area, htf_system_spec_cost, Q_tes, storage_spec_cost, fossil_backup_mwe, + N_mspt::calculate_mslf_costs(site_improvements_area, site_improvements_spec_cost, solar_field_area, solar_field_spec_cost, 0.0, 0.0, htf_system_area, htf_system_spec_cost, Q_tes, storage_spec_cost, fossil_backup_mwe, fossil_spec_cost, power_plant_mwe, power_plant_spec_cost, bop_mwe, bop_spec_cost, contingency_percent, c_trough.m_total_land_area, nameplate_des, epc_cost_per_acre, epc_cost_percent_direct, epc_cost_per_watt, epc_cost_fixed, plm_cost_per_acre, plm_cost_percent_direct, plm_cost_per_watt, plm_cost_fixed, sales_tax_rate, sales_tax_percent, - power_plant_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, htf_system_cost_out, fossil_backup_cost_out, contingency_cost_out, + power_plant_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, heater_cost_out, htf_system_cost_out, fossil_backup_cost_out, contingency_cost_out, total_direct_cost_out, epc_total_cost_out, plm_total_cost_out, total_indirect_cost_out, sales_tax_total_out, total_installed_cost_out, installed_per_capacity_out); double direct_subtotal = site_improvements_cost_out + solar_field_cost_out + htf_system_cost_out + ts_cost_out + fossil_backup_cost_out + power_plant_cost_out + bop_cost_out; diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 0cdefa9c4..7094596de 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -49,6 +49,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "csp_system_costs.h" //#include "cmod_csp_common_eqns.h" +#include "csp_solver_cr_electric_resistance.h" + #include #include #include @@ -65,6 +67,7 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "is_dispatch", "Allow dispatch optimization?", "", "", "System Control", "?=0", "", ""}, { SSC_INPUT, SSC_NUMBER, "sim_type", "1 (default): timeseries, 2: design only", "", "", "System Control", "?=1", "", "SIMULATION_PARAMETER"}, + { SSC_INPUT, SSC_NUMBER, "is_parallel_htr", "Does plant include a HTF heater parallel to solar field?", "", "", "System Control", "?=0", "", ""}, // Weather Reader { SSC_INPUT, SSC_STRING, "file_name", "Local weather file with path", "none", "", "weather", "?", "LOCAL_FILE", "" }, @@ -170,10 +173,16 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "p_start", "Collector startup energy, per SCA", "kWe-hr", "", "solar_field", "*", "", "" }, // Heat Sink + { SSC_INPUT, SSC_NUMBER, "pb_pump_coef", "Pumping power to move 1kg of HTF through PB loop", "kW/kg", "", "Heat Sink", "*", "", "" }, - /*Heat Sink*/{ SSC_INPUT, SSC_NUMBER, "pb_pump_coef", "Pumping power to move 1kg of HTF through PB loop", "kW/kg", "", "Heat Sink", "*", "", "" }, - - + // Parallel heater parameters + { SSC_INPUT, SSC_NUMBER, "heater_mult", "Heater multiple relative to design cycle thermal power", "-", "", "Parallel Heater", "is_parallel_htr=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "heater_efficiency", "Heater electric to thermal efficiency", "%", "", "Parallel Heater", "is_parallel_htr=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "f_q_dot_des_allowable_su", "Fraction of design power allowed during startup", "-", "", "Parallel Heater", "is_parallel_htr=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "hrs_startup_at_max_rate", "Duration of startup at max startup power", "hr", "", "Parallel Heater", "is_parallel_htr=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "f_q_dot_heater_min", "Minimum allowable heater output as fraction of design", "", "", "Parallel Heater", "is_parallel_htr=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "heater_spec_cost", "Heater specific cost", "$/kWht", "", "System Costs", "is_parallel_htr=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "allow_heater_no_dispatch_opt","Allow heater with no dispatch optimization? SAM UI relies on cmod default", "", "", "System Costs", "?=0", "", "SIMULATION_PARAMETER" }, // General TES Parameters { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), Packed Bed (1), HeatTrap Single Tank (2)", "-", "", "TES", "?=0", "", "" }, @@ -404,6 +413,10 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "dP_sf_SS", "Steady State field pressure drop", "bar", "", "Solar Field", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "W_dot_pump_SS", "Steady State pumping power", "MWe", "", "Solar Field", "*", "", "" }, + // Heater + { SSC_OUTPUT, SSC_NUMBER, "q_dot_heater_des", "Heater design thermal power", "MWt", "", "Heater", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "W_dot_heater_des", "Heater electricity consumption at design", "MWe", "", "Heater", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "E_heater_su_des", "Heater startup energy", "MWt-hr", "", "Heater", "*", "", "" }, // Thermal Storage { SSC_OUTPUT, SSC_NUMBER, "vol_tank", "Total tank volume", "m3", "", "Thermal Storage","*", "", "" }, @@ -419,7 +432,8 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "tes_htf_avg_temp", "HTF Average Temperature at Design", "C", "", "Thermal Storage","*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "tes_htf_min_temp", "Minimum storage htf temp", "C", "", "Power Cycle", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "tes_htf_max_temp", "Maximum storage htf temp", "C", "", "Power Cycle", "*", "", "" }, - + { SSC_OUTPUT, SSC_NUMBER, "tshours_field", "TES duration at field design output", "hr", "", "TES Design Calc","*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "tshours_heater", "TES duration at heater design output", "hr", "", "TES Design Calc","*", "", "" }, // Collector { SSC_OUTPUT, SSC_MATRIX, "csp_dtr_sca_ap_lengths", "Length of single module", "m", "", "Collector", "?=0", "", "" }, @@ -647,6 +661,10 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "is_rec_su_allowed", "is receiver startup allowed", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "is_pc_su_allowed", "is power cycle startup allowed", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "is_pc_sb_allowed", "is power cycle standby allowed", "", "", "solver", "sim_type=1", "", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "is_PAR_HTR_allowed", "Is parallel electric heater operation allowed", "", "", "solver", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "q_dot_elec_to_PAR_HTR", "Electric heater thermal power target", "MWt", "", "solver", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "q_dot_est_cr_su", "Estimate rec. startup thermal power", "MWt", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "q_dot_est_cr_on", "Estimate rec. thermal power TO HTF", "MWt", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "q_dot_est_tes_dc", "Estimate max TES discharge thermal power", "MWt", "", "solver", "sim_type=1", "", "" }, @@ -655,7 +673,16 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "operating_modes_a", "First 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "operating_modes_b", "Next 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "operating_modes_c", "Final 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, - + + // Heater outputs is_parallel_htr + { SSC_OUTPUT, SSC_ARRAY, "W_dot_heater", "Parallel heater electricity consumption", "MWe", "", "Parallel Heater","sim_type=1&is_parallel_htr=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "q_dot_heater_to_htf", "Parallel heater thermal power to HTF", "MWt", "", "Parallel Heater","sim_type=1&is_parallel_htr=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "q_dot_heater_startup", "Parallel heater thermal power consumed during startup", "MWt", "", "Parallel Heater","sim_type=1&is_parallel_htr=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "m_dot_htf_heater", "Parallel heater HTF mass flow rate", "kg/s", "", "Parallel Heater","sim_type=1&is_parallel_htr=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_htf_heater_in", "Parallel heater HTF inlet temperature", "C", "", "Parallel Heater","sim_type=1&is_parallel_htr=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_htf_heater_out", "Parallel heater HTF outlet temperature", "C", "", "Parallel Heater","sim_type=1&is_parallel_htr=1", "", "" }, + + // Dispatch { SSC_OUTPUT, SSC_ARRAY, "disp_rel_mip_gap", "Dispatch relative MIP gap", "", "", "tou", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "disp_solve_state", "Dispatch solver state", "", "", "tou", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "disp_subopt_flag", "Dispatch suboptimal solution flag", "", "", "tou", "sim_type=1", "", "" }, @@ -1089,7 +1116,44 @@ class cm_trough_physical_iph : public compute_module } } - + + // Check if system configuration includes a heater parallel to primary collector receiver + C_csp_collector_receiver* p_heater; + C_csp_cr_electric_resistance* p_electric_resistance = NULL; + bool is_parallel_heater = as_boolean("is_parallel_htr"); // defaults to false + double q_dot_heater_des = 0.0; //[MWt] + double heater_spec_cost = 0.0; + if (is_parallel_heater) { + if (!is_dispatch && sim_type == 1) { + if (!as_boolean("allow_heater_no_dispatch_opt")) { + throw exec_error("trough_physical", "When the IPH physical trough case has an electric HTF charger, dispatch optimization must be selected"); + } + } + + double heater_mult = as_double("heater_mult"); //[-] + heater_spec_cost = as_double("heater_spec_cost"); //[$/kWt] + + q_dot_heater_des = q_dot_hs_des * heater_mult; //[MWt] + + double heater_efficiency = as_double("heater_efficiency") / 100.0; //[-] convert from % input + double f_q_dot_des_allowable_su = as_double("f_q_dot_des_allowable_su"); //[-] fraction of design power allowed during startup + double hrs_startup_at_max_rate = as_double("hrs_startup_at_max_rate"); //[hr] duration of startup at max startup power + double f_heater_min = as_double("f_q_dot_heater_min"); //[-] minimum allowable heater output as fraction of design + + p_electric_resistance = new C_csp_cr_electric_resistance(T_htf_cold_des, T_htf_hot_des, + q_dot_heater_des, heater_efficiency, f_heater_min, + f_q_dot_des_allowable_su, hrs_startup_at_max_rate, + as_integer("Fluid"), as_matrix("field_fl_props"), C_csp_cr_electric_resistance::E_elec_resist_startup_mode::INSTANTANEOUS_NO_MAX_ELEC_IN); + + p_electric_resistance->mc_reported_outputs.assign(C_csp_cr_electric_resistance::E_W_DOT_HEATER, allocate("W_dot_heater", n_steps_fixed), n_steps_fixed); + p_electric_resistance->mc_reported_outputs.assign(C_csp_cr_electric_resistance::E_Q_DOT_HTF, allocate("q_dot_heater_to_htf", n_steps_fixed), n_steps_fixed); + p_electric_resistance->mc_reported_outputs.assign(C_csp_cr_electric_resistance::E_Q_DOT_STARTUP, allocate("q_dot_heater_startup", n_steps_fixed), n_steps_fixed); + p_electric_resistance->mc_reported_outputs.assign(C_csp_cr_electric_resistance::E_M_DOT_HTF, allocate("m_dot_htf_heater", n_steps_fixed), n_steps_fixed); + p_electric_resistance->mc_reported_outputs.assign(C_csp_cr_electric_resistance::E_T_HTF_IN, allocate("T_htf_heater_in", n_steps_fixed), n_steps_fixed); + p_electric_resistance->mc_reported_outputs.assign(C_csp_cr_electric_resistance::E_T_HTF_OUT, allocate("T_htf_heater_out", n_steps_fixed), n_steps_fixed); + } + p_heater = p_electric_resistance; + // Heat Sink C_pc_heat_sink c_heat_sink; { @@ -1555,9 +1619,6 @@ class cm_trough_physical_iph : public compute_module if (is_dispatch && sim_type == 1) { - double heater_startup_cost = 0.0; // TODO: Should we add an heater to this model? - double q_dot_rec_des = q_dot_hs_des * c_trough.m_solar_mult; //[MWt] - dispatch.solver_params.set_user_inputs(as_integer("disp_steps_per_hour"), as_integer("disp_frequency"), as_integer("disp_horizon"), as_integer("disp_max_iter"), as_double("disp_mip_gap"), as_double("disp_timeout"), as_integer("disp_spec_presolve"), as_integer("disp_spec_bb"), as_integer("disp_spec_scaling"), as_integer("disp_reporting")); @@ -1573,7 +1634,7 @@ class cm_trough_physical_iph : public compute_module tou, dispatch, system, - NULL, + p_heater, nullptr, ssc_cmod_update, (void*)(this)); @@ -1633,6 +1694,9 @@ class cm_trough_physical_iph : public compute_module csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::EST_Q_DOT_DC, allocate("q_dot_est_tes_dc", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::EST_Q_DOT_CH, allocate("q_dot_est_tes_ch", n_steps_fixed), n_steps_fixed); + csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::CTRL_IS_PAR_HTR_SU, allocate("is_PAR_HTR_allowed", n_steps_fixed), n_steps_fixed); + csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::PAR_HTR_Q_DOT_TARGET, allocate("q_dot_elec_to_PAR_HTR", n_steps_fixed), n_steps_fixed); + csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::CTRL_OP_MODE_SEQ_A, allocate("operating_modes_a", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::CTRL_OP_MODE_SEQ_B, allocate("operating_modes_b", n_steps_fixed), n_steps_fixed); csp_solver.mc_reported_outputs.assign(C_csp_solver::C_solver_outputs::CTRL_OP_MODE_SEQ_C, allocate("operating_modes_c", n_steps_fixed), n_steps_fixed); @@ -1750,6 +1814,20 @@ class cm_trough_physical_iph : public compute_module assign("W_dot_pump_SS", c_trough.m_W_dot_pump_SS); //[MWe] } + // ************************* + // Heater + { + assign("q_dot_heater_des", q_dot_heater_des); //[MWt] + double W_dot_heater_des_calc = 0.0; //[MWe] + double E_heater_su_des = 0.0; //[MWt-hr] + if (is_parallel_heater) { + p_electric_resistance->get_design_parameters(E_heater_su_des, W_dot_heater_des_calc); + } + assign("W_dot_heater_des", (ssc_number_t)W_dot_heater_des_calc); //[MWe] + assign("E_heater_su_des", (ssc_number_t)E_heater_su_des); //[MWt-hr] + } + + // Thermal Storage { double V_tes_htf_avail_calc /*m3*/, V_tes_htf_total_calc /*m3*/, @@ -1805,6 +1883,14 @@ class cm_trough_physical_iph : public compute_module assign("tes_htf_avg_temp", T_avg); // C assign("tes_htf_min_temp", tes_htf_min_temp); assign("tes_htf_max_temp", tes_htf_max_temp); + double q_dot_field_des = c_trough.m_solar_mult * q_dot_hs_des; + assign("tshours_field", Q_tes_des_calc / q_dot_field_des); //[hr] + + double tshours_heater = 0.0; + if (q_dot_heater_des > 0.0) { + tshours_heater = Q_tes_des_calc / q_dot_heater_des; //[hr] + } + assign("tshours_heater", tshours_heater); } // Collector @@ -1963,23 +2049,24 @@ class cm_trough_physical_iph : public compute_module // Define outputs - double heat_sink_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, htf_system_cost_out, dummy_cost_out, contingency_cost_out, + double heat_sink_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, heater_cost_out, htf_system_cost_out, dummy_cost_out, contingency_cost_out, total_direct_cost_out, epc_total_cost_out, plm_total_cost_out, total_indirect_cost_out, sales_tax_total_out, total_installed_cost_out, installed_per_capacity_out; // Calculate Costs - N_mspt::calculate_mslf_costs(site_improvements_area, site_improvements_spec_cost, solar_field_area, solar_field_spec_cost, htf_system_area, htf_system_spec_cost, Q_tes, storage_spec_cost, 0, + N_mspt::calculate_mslf_costs(site_improvements_area, site_improvements_spec_cost, solar_field_area, solar_field_spec_cost, q_dot_heater_des /*[MWt]*/, heater_spec_cost, htf_system_area, htf_system_spec_cost, Q_tes, storage_spec_cost, 0, 0, heat_sink_mwe, heat_sink_spec_cost, bop_mwe, bop_spec_cost, contingency_percent, c_trough.m_total_land_area, nameplate, epc_cost_per_acre, epc_cost_percent_direct, epc_cost_per_watt, epc_cost_fixed, plm_cost_per_acre, plm_cost_percent_direct, plm_cost_per_watt, plm_cost_fixed, sales_tax_rate, sales_tax_percent, - heat_sink_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, htf_system_cost_out, dummy_cost_out, contingency_cost_out, + heat_sink_cost_out, ts_cost_out, site_improvements_cost_out, bop_cost_out, solar_field_cost_out, heater_cost_out, htf_system_cost_out, dummy_cost_out, contingency_cost_out, total_direct_cost_out, epc_total_cost_out, plm_total_cost_out, total_indirect_cost_out, sales_tax_total_out, total_installed_cost_out, installed_per_capacity_out); - double direct_subtotal = site_improvements_cost_out + solar_field_cost_out + htf_system_cost_out + ts_cost_out + heat_sink_cost_out + bop_cost_out; + double direct_subtotal = site_improvements_cost_out + solar_field_cost_out + heater_cost_out + htf_system_cost_out + ts_cost_out + heat_sink_cost_out + bop_cost_out; // Assign Outputs { assign("csp.dtr.cost.site_improvements", site_improvements_cost_out); assign("csp.dtr.cost.solar_field", solar_field_cost_out); + assign("heater_cost", heater_cost_out); assign("csp.dtr.cost.htf_system", htf_system_cost_out); assign("csp.dtr.cost.storage", ts_cost_out); assign("csp.dtr.cost.heat_sink", heat_sink_cost_out); diff --git a/tcs/csp_solver_core.cpp b/tcs/csp_solver_core.cpp index 55b4454ce..ce321c32f 100644 --- a/tcs/csp_solver_core.cpp +++ b/tcs/csp_solver_core.cpp @@ -1482,7 +1482,7 @@ void C_csp_solver::calc_timestep_plant_control_and_targets( send_callback((float)calc_frac_current * 100.f); ss.flush(); - // Update horizon parameter values and inital conition parameters + // Update horizon parameter values and inital condition parameters if (!mc_dispatch.update_horizon_parameters(mc_tou)) { throw(C_csp_exception("Dispatch failed to update horizon parameter values")); } diff --git a/tcs/csp_system_costs.cpp b/tcs/csp_system_costs.cpp index 9cc7d2c96..268255b05 100644 --- a/tcs/csp_system_costs.cpp +++ b/tcs/csp_system_costs.cpp @@ -725,6 +725,11 @@ void N_mspt::calculate_mslf_costs( double site_improvement_cost_per_m2, // csp.mslf.cost.site_improvements.cost_per_m2 double sf_area, // csp.mslf.cost.solar_field.area double sf_cost_per_m2, // csp.mslf.cost.solar_field.cost_per_m2 + + // Heater + double q_dot_heater_design, //[MWt] Heater design thermal power + double heater_spec_cost, //[$/kWe] Heater specific cost + double htf_area, // csp.mslf.cost.htf_system.area double htf_cost_per_m2, // csp.mslf.cost.htf_system.cost_per_m2 double ts_mwht, // csp.mslf.cost.ts_mwht @@ -759,6 +764,7 @@ void N_mspt::calculate_mslf_costs( double& site_improvements_cost_out, // csp.mslf.cost.site_improvements double& bop_out, // csp.mslf.cost.bop double& solar_field_cost_out, // csp.mslf.cost.solar_field + double& heater_cost_out, double& htf_system_cost_out, // csp.mslf.cost.htf_system double& fossil_backup_cost_out, // csp.mslf.cost.fossil_backup double& contingency_cost_out, // csp.mslf.cost.contingency @@ -774,40 +780,42 @@ void N_mspt::calculate_mslf_costs( ) { - double power_plant = power_cycle_cost(power_plant_mwe, power_plant_cost_per_kwe); + double power_plant = N_mspt::power_cycle_cost(power_plant_mwe, power_plant_cost_per_kwe); - double ts = tes_cost(ts_mwht, ts_per_kwht); + double ts = N_mspt::tes_cost(ts_mwht, ts_per_kwht); - double site_improvements = site_improvement_cost(site_improvement_area, site_improvement_cost_per_m2); + double site_improvements = N_mspt::site_improvement_cost(site_improvement_area, site_improvement_cost_per_m2); - double bop = bop_cost(bop_mwe, bop_per_kwe); + double bop = N_mspt::bop_cost(bop_mwe, bop_per_kwe); double solar_field = sf_area * sf_cost_per_m2; + double heater = N_mspt::heater_cost(q_dot_heater_design, heater_spec_cost); + double htf_system = htf_area * htf_cost_per_m2; - double fossil_backup = fossil_backup_cost(fossil_mwe, fossil_cost_per_kwe); + double fossil_backup = N_mspt::fossil_backup_cost(fossil_mwe, fossil_cost_per_kwe); - double direct_capital_precontingency_cost = site_improvements + solar_field + htf_system + fossil_backup + double direct_capital_precontingency_cost = site_improvements + solar_field + heater + htf_system + fossil_backup + power_plant + bop + ts; - double contingency = contingency_cost(contigency_percent, direct_capital_precontingency_cost); + double contingency = N_mspt::contingency_cost(contigency_percent, direct_capital_precontingency_cost); - double total_direct = total_direct_cost(direct_capital_precontingency_cost, contingency); + double total_direct = N_mspt::total_direct_cost(direct_capital_precontingency_cost, contingency); - double epc_total = epc_and_owner_cost(total_land_area, total_direct, nameplate_MWe, epc_per_acre, + double epc_total = N_mspt::epc_and_owner_cost(total_land_area, total_direct, nameplate_MWe, epc_per_acre, epc_percent, epc_per_watt, epc_fixed); - double plm_total = total_land_cost(total_land_area, total_direct, nameplate_MWe, plm_per_acre, plm_percent, + double plm_total = N_mspt::total_land_cost(total_land_area, total_direct, nameplate_MWe, plm_per_acre, plm_percent, plm_per_watt, plm_fixed); double total_indirect = epc_total + plm_total; - double sales_tax_total = sales_tax_cost(total_direct, sales_tax_value, sales_tax_percent); + double sales_tax_total = N_mspt::sales_tax_cost(total_direct, sales_tax_value, sales_tax_percent); double total_installed = total_direct + total_indirect + sales_tax_total; - double installed_per_cap = estimated_installed_cost_per_cap(total_installed, nameplate_MWe); + double installed_per_cap = N_mspt::estimated_installed_cost_per_cap(total_installed, nameplate_MWe); // Set Outputs { @@ -816,6 +824,7 @@ void N_mspt::calculate_mslf_costs( site_improvements_cost_out = site_improvements; bop_out = bop; solar_field_cost_out = solar_field; + heater_cost_out = heater; htf_system_cost_out = htf_system; fossil_backup_cost_out = fossil_backup; contingency_cost_out = contingency; diff --git a/tcs/csp_system_costs.h b/tcs/csp_system_costs.h index 649b7a0f6..0ffbba787 100644 --- a/tcs/csp_system_costs.h +++ b/tcs/csp_system_costs.h @@ -342,6 +342,11 @@ namespace N_mspt double site_improvement_cost_per_m2, // csp.mslf.cost.site_improvements.cost_per_m2 double sf_area, // csp.mslf.cost.solar_field.area double sf_cost_per_m2, // csp.mslf.cost.solar_field.cost_per_m2 + + // Heater + double q_dot_heater_design, //[MWt] Heater design thermal power + double heater_spec_cost, //[$/kWe] Heater specific cost + double htf_area, // csp.mslf.cost.htf_system.area double htf_cost_per_m2, // csp.mslf.cost.htf_system.cost_per_m2 double ts_mwht, // csp.mslf.cost.ts_mwht @@ -355,7 +360,7 @@ namespace N_mspt double contigency_percent, // csp.mslf.cost.contingency_percent double total_land_area, // csp.mslf.cost.total_land_area - double nameplate_MWe, // csp.mslf.cost.nameplate + double nameplate_MWe, // csp.mslf.cost.nameplate double epc_per_acre, // csp.mslf.cost.epc.per_acre double epc_percent, // csp.mslf.cost.epc.percent @@ -376,6 +381,7 @@ namespace N_mspt double& site_improvements_cost_out, // csp.mslf.cost.site_improvements double& bop_out, // csp.mslf.cost.bop double& solar_field_cost_out, // csp.mslf.cost.solar_field + double& heater_cost_out, double& htf_system_cost_out, // csp.mslf.cost.htf_system double& fossil_backup_cost_out, // csp.mslf.cost.fossil_backup double& contingency_cost_out, // csp.mslf.cost.contingency diff --git a/tcs/cst_iph_dispatch.cpp b/tcs/cst_iph_dispatch.cpp index 3d9a02e35..20ffc71da 100644 --- a/tcs/cst_iph_dispatch.cpp +++ b/tcs/cst_iph_dispatch.cpp @@ -157,13 +157,13 @@ bool cst_iph_dispatch_opt::update_horizon_parameters(C_csp_tou& mc_tou) params.heat_load.resize(num_steps, 1.e99); double sec_per_step = 3600. / (double)solver_params.steps_per_hour; - double W_dot_max = params.q_hs_max * params.eta_hs_des; //[kWe] TODO: Change this to heat only (remove efficiency) + double q_dot_max = params.q_hs_max * params.eta_hs_des; //[kWe] TODO: Change this to heat only (remove efficiency) for (int t = 0; t < num_steps; t++) { C_csp_tou::S_csp_tou_outputs tou_outputs; mc_tou.call(pointers.siminfo->ms_ts.m_time + t * sec_per_step, tou_outputs); params.elec_price.at(t) = tou_outputs.m_elec_price * 1000.0; // $/kWhe -> $/MWhe params.heat_cost.at(t) = tou_outputs.m_heat_price * 1000.0; // $/kWht -> $/MWht - params.heat_load.at(t) = tou_outputs.m_wlim_dispatch * W_dot_max; + params.heat_load.at(t) = tou_outputs.m_wlim_dispatch * q_dot_max; } return true; } @@ -432,7 +432,7 @@ bool cst_iph_dispatch_opt::optimize() // Electric heater charging if (params.is_parallel_heater) { row[t + nt * (i)] = P["delta"] * params.elec_price.at(t) * (1 / tadj) * (1 / P["eta_eh"]); - col[t + nt * (i)] = O.column("qeh", t); + col[t + nt * (i++)] = O.column("qeh", t); } tadj *= P["disp_time_weighting"]; From 776e287727aa4457cd203ff85e7bf689fbc57959 Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri, 18 Oct 2024 10:08:16 -0600 Subject: [PATCH 47/85] Add load output to IPH trough and fresnel. Change gen to gen_heat. Change ppa_price input units. --- ssc/cmod_fresnel_physical_iph.cpp | 39 +++++++++++++++++++++++++------ ssc/cmod_mspt_iph.cpp | 29 +++++++++++++++++++---- ssc/cmod_trough_physical_iph.cpp | 33 ++++++++++++++++++++++---- 3 files changed, 85 insertions(+), 16 deletions(-) diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 6f2d2ce39..8c978b618 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -215,8 +215,9 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { { SSC_INPUT, SSC_ARRAY, "dispatch_factors_ts", "Dispatch payment factor array", "", "", "tou", "ppa_multiplier_model=1&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekday", "PPA pricing weekday schedule, 12x24", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekend", "PPA pricing weekend schedule, 12x24", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "dispatch_tod_factors", "TOD factors for periods 1 through 9", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "ppa_price_input", "PPA solution mode (0=Specify IRR target, 1=Specify PPA price)", "", "", "Financial Solution Mode", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "dispatch_tod_factors", "TOD factors for periods 1 through 9", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "ppa_price_input_heatBtu", "PPA prices - yearly", "$/MMBtu", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1", "", "SIMULATION_PARAMETER" }, + // System Control @@ -404,6 +405,7 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "const_per_principal_total", "Total principal, all loans", "$", "", "Financial Parameters", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "const_per_interest_total", "Total interest costs, all loans", "$", "", "Financial Parameters", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "construction_financing_cost", "Total construction financing cost", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1", "", "" }, // **************************************************************************************************************************************** // Timeseries Simulation Outputs here (sim_type = 1): @@ -560,7 +562,7 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "operating_modes_a", "First 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "operating_modes_b", "Next 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "operating_modes_c", "Final 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen", "Total thermal power to heat sink with available derate", "kWe", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "Total thermal power to heat sink with available derate", "kWe", "", "system", "sim_type=1", "", "" }, // Monthly Outputs { SSC_OUTPUT, SSC_ARRAY, "monthly_energy", "Monthly Energy", "kWh", "", "Post-process", "sim_type=1", "LENGTH=12", "" }, @@ -609,6 +611,26 @@ class cm_fresnel_physical_iph : public compute_module double q_dot_pc_des = as_double("q_pb_design"); //[MWt] HEAT SINK design thermal power double Q_tes = q_dot_pc_des * tshours; //[MWt-hr] + // Convert IPH Input Units + { + const double MMBTU_TO_KWh = 293.07107; // 1 + if (is_assigned("ppa_price_input_heatBtu")) + { + size_t count_ppa_price_MMBTU_input; + ssc_number_t* ppa_price_MMBTU_input_array = as_array("ppa_price_input_heatBtu", &count_ppa_price_MMBTU_input); + std::vector ppa_price_input_vec; + for (int i = 0; i < count_ppa_price_MMBTU_input; i++) + { + ppa_price_input_vec.push_back(ppa_price_MMBTU_input_array[i] / MMBTU_TO_KWh); + } + + int size = ppa_price_input_vec.size(); + ssc_number_t* alloc_vals = allocate("ppa_price_input", size); + for (int i = 0; i < size; i++) + alloc_vals[i] = ppa_price_input_vec[i]; // [] + } + } + // Weather reader C_csp_weatherreader weather_reader; C_csp_solver::S_sim_setup sim_setup; @@ -1530,7 +1552,7 @@ class cm_fresnel_physical_iph : public compute_module if (!haf.setup(n_steps_fixed)) throw exec_error("fresnel_physical", "failed to setup adjustment factors: " + haf.error()); - ssc_number_t* p_gen = allocate("gen", n_steps_fixed); + ssc_number_t* p_gen = allocate("gen_heat", n_steps_fixed); ssc_number_t* p_q_dot_defocus_est = allocate("q_dot_defocus_est", n_steps_fixed); ssc_number_t* p_SCAs_def = as_array("SCAs_def", &count); if ((int)count != n_steps_fixed) @@ -1538,6 +1560,8 @@ class cm_fresnel_physical_iph : public compute_module ssc_number_t* p_W_dot_parasitic_tot = as_array("W_dot_parasitic_tot", &count); ssc_number_t* p_W_dot_par_tot_haf = allocate("W_dot_par_tot_haf", n_steps_fixed); + ssc_number_t* p_load = allocate("load", n_steps_fixed); // testing using cmod_utilityrate5 for electricity rates p_load = p_W_dot_par_tot_haf + ssc_number_t* p_q_dot_htf_sf_out = as_array("q_dot_htf_sf_out", &count); if ((int)count != n_steps_fixed) @@ -1548,14 +1572,15 @@ class cm_fresnel_physical_iph : public compute_module p_gen[i] = (ssc_number_t)(p_q_dot_heat_sink[i] * haf(hour) * 1.E3); //[kWe] p_q_dot_defocus_est[i] = (ssc_number_t)(1.0 - p_SCAs_def[i]) * p_q_dot_htf_sf_out[i]; //[MWt] p_W_dot_parasitic_tot[i] *= -1.0; //[MWe] Label is total parasitics, so change to a positive value - p_W_dot_par_tot_haf[i] = (ssc_number_t)(p_W_dot_parasitic_tot[i] * haf(hour) * 1.E3); //[kWe] apply availability derate and convert from MWe + p_W_dot_par_tot_haf[i] = (ssc_number_t)(p_W_dot_parasitic_tot[i] * haf(hour) * 1.E3); //[kWe] apply availability derate and convert from MWe + p_load[i] = p_W_dot_par_tot_haf[i]; } // Monthly outputs - accumulate_monthly_for_year("gen", "monthly_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1); + accumulate_monthly_for_year("gen_heat", "monthly_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1); // Annual outputs - accumulate_annual_for_year("gen", "annual_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); + accumulate_annual_for_year("gen_heat", "annual_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); // This term currently includes TES freeze protection accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWe-hr] diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index 45ad5f2ec..e31adb7dd 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -274,7 +274,8 @@ static var_info _cm_vtab_mspt_iph[] = { { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekday", "PPA pricing weekday schedule, 12x24", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekend", "PPA pricing weekend schedule, 12x24", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_ARRAY, "dispatch_tod_factors", "TOD factors for periods 1 through 9", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, -{ SSC_INPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_ARRAY, "ppa_price_input_heatBtu", "PPA prices - yearly", "$/MMBtu", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, + // Costs { SSC_INPUT, SSC_NUMBER, "tower_fixed_cost", "Tower fixed cost", "$", "", "System Costs", "*", "", "" }, @@ -452,6 +453,7 @@ static var_info _cm_vtab_mspt_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "const_per_principal_total", "Total principal, all loans", "$", "", "Financial Parameters", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "const_per_interest_total", "Total interest costs, all loans", "$", "", "Financial Parameters", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "construction_financing_cost", "Total construction financing cost", "$", "", "Financial Parameters", "*", "", "" }, +{ SSC_OUTPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, @@ -598,7 +600,7 @@ static var_info _cm_vtab_mspt_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "operating_modes_b", "Next 3 operating modes tried", "", "", "", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_ARRAY, "operating_modes_c", "Final 3 operating modes tried", "", "", "", "sim_type=1", "", ""}, -{ SSC_OUTPUT, SSC_ARRAY, "gen", "Total thermal power to heat sink with available derate", "kWt", "", "", "sim_type=1", "", ""}, +{ SSC_OUTPUT, SSC_ARRAY, "gen_heat", "Total thermal power to heat sink with available derate", "kWt", "", "", "sim_type=1", "", ""}, // Annual single-value outputs { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Thermal Energy to Heat Sink w/ avail derate", "kWt-hr", "", "Post-process", "sim_type=1", "", ""}, @@ -654,6 +656,25 @@ class cm_mspt_iph : public compute_module // ***************************************************** // ***************************************************** + // Convert IPH Input Units + { + const double MMBTU_TO_KWh = 293.07107; // 1 + if (is_assigned("ppa_price_input_heatBtu")) + { + size_t count_ppa_price_MMBTU_input; + ssc_number_t* ppa_price_MMBTU_input_array = as_array("ppa_price_input_heatBtu", &count_ppa_price_MMBTU_input); + std::vector ppa_price_input_vec; + for (int i = 0; i < count_ppa_price_MMBTU_input; i++) + { + ppa_price_input_vec.push_back(ppa_price_MMBTU_input_array[i] / MMBTU_TO_KWh); + } + + int size = ppa_price_input_vec.size(); + ssc_number_t* alloc_vals = allocate("ppa_price_input", size); + for (int i = 0; i < size; i++) + alloc_vals[i] = ppa_price_input_vec[i]; // [] + } + } // ***************************************************** // System Design Parameters @@ -2285,7 +2306,7 @@ class cm_mspt_iph : public compute_module if (!haf.setup(count)) throw exec_error("mspt_iph", "failed to setup adjustment factors: " + haf.error()); - ssc_number_t* p_gen = allocate("gen", count); + ssc_number_t* p_gen = allocate("gen_heat", count); ssc_number_t* p_W_dot_parasitic_tot = as_array("W_dot_parasitic_tot", &count); ssc_number_t* p_W_dot_par_tot_haf = allocate("W_dot_par_tot_haf", n_steps_fixed); ssc_number_t* p_load = allocate("load", n_steps_fixed); // testing using cmod_utilityrate5 for electricity rates p_load = p_W_dot_par_tot_haf @@ -2304,7 +2325,7 @@ class cm_mspt_iph : public compute_module ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour); - accumulate_annual_for_year("gen", "annual_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[kWt-hr] + accumulate_annual_for_year("gen_heat", "annual_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[kWt-hr] // This term currently includes TES freeze protection accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWe-hr] diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 7094596de..023a29cd4 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -244,7 +244,7 @@ static var_info _cm_vtab_trough_physical_iph[] = { // Prices for *electricity* purchases { SSC_INPUT, SSC_NUMBER, "ppa_multiplier_model", "PPA multiplier model 0: dispatch factors dispatch_factorX, 1: hourly multipliers dispatch_factors_ts", "0/1", "0=diurnal,1=timestep", "tou", "?=0", /*need a default so this var works in required_if*/ "INTEGER,MIN=0", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "ppa_soln_mode", "PPA solution mode (0=Specify IRR target, 1=Specify PPA price)", "", "", "Financial Solution Mode", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "ppa_price_input_heatBtu", "PPA prices - yearly", "$/MMBtu", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, // *Electricity* hourly price multipliers from Block Schedule { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekday", "12x24 PPA pricing Weekday schedule", "", "", "tou", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekend", "12x24 PPA pricing Weekend schedule", "", "", "tou", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, @@ -496,6 +496,8 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "const_per_principal_total", "Total principal, all loans", "$", "", "Financial Parameters", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "const_per_interest_total", "Total interest costs, all loans", "$", "", "Financial Parameters", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "construction_financing_cost", "Total construction financing cost", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "" }, + // Simulation Kernel { SSC_OUTPUT, SSC_ARRAY, "time_hr", "Time at end of timestep", "hr", "", "solver", "sim_type=1", "", "" }, @@ -702,7 +704,7 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "P_fixed", "Parasitic power fixed load", "MWe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "P_plant_balance_tot", "Parasitic power generation-dependent load", "MWe", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen", "Total thermal power to grid w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "Total thermal power to grid w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, //{ SSC_OUTPUT, SSC_NUMBER, "conversion_factor", "Gross to Net Conversion Factor", "%", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "kwh_per_kw", "First year kWh/kW", "kWh/kW", "", "system", "sim_type=1", "", "" }, @@ -758,6 +760,24 @@ class cm_trough_physical_iph : public compute_module int is_dispatch = as_boolean("is_dispatch"); int tes_type = as_integer("tes_type"); + const double MMBTU_TO_KWh = 293.07107; // 1 + + // Convert IPH Input Units + { + if (is_assigned("ppa_price_input_heatBtu")) + { + size_t count_ppa_price_MMBTU_input; + ssc_number_t* ppa_price_MMBTU_input_array = as_array("ppa_price_input_heatBtu", &count_ppa_price_MMBTU_input); + std::vector ppa_price_input_vec; + for (int i = 0; i < count_ppa_price_MMBTU_input; i++) + { + ppa_price_input_vec.push_back(ppa_price_MMBTU_input_array[i] / MMBTU_TO_KWh); + } + set_vector("ppa_price_input", ppa_price_input_vec); + } + } + + // ***************************************************** // System Design Parameters @@ -2202,9 +2222,11 @@ class cm_trough_physical_iph : public compute_module if( !haf.setup(n_steps_fixed) ) throw exec_error("trough_physical", "failed to setup adjustment factors: " + haf.error()); - ssc_number_t *p_gen = allocate("gen", n_steps_fixed); + ssc_number_t *p_gen = allocate("gen_heat", n_steps_fixed); ssc_number_t *p_W_dot_par_tot_haf = allocate("W_dot_par_tot_haf", n_steps_fixed); ssc_number_t *p_q_dot_defocus_est = allocate("q_dot_defocus_est", n_steps_fixed); + ssc_number_t* p_load = allocate("load", n_steps_fixed); // testing using cmod_utilityrate5 for electricity rates p_load = p_W_dot_par_tot_haf + ssc_number_t *p_W_dot_parasitic_tot = as_array("W_dot_parasitic_tot", &count); if (count != n_steps_fixed) @@ -2243,6 +2265,7 @@ class cm_trough_physical_iph : public compute_module p_q_dot_defocus_est[i] = (ssc_number_t)(1.0 - p_SCAs_def[i])*p_q_dot_htf_sf_out[i]; //[MWt] //p_m_dot_tes_dc[i] = (ssc_number_t)(p_m_dot_tes_dc[i] / 3600.0); //[kg/s] convert from kg/hr //p_m_dot_tes_ch[i] = (ssc_number_t)(p_m_dot_tes_ch[i] / 3600.0); //[kg/s] convert from kg/hr + p_load[i] = p_W_dot_par_tot_haf[i]; i_elec_cost = p_elec_price_out[i] * p_W_dot_par_tot_haf[i]; //[$] p_elec_purchase_cost[i] = i_elec_cost; //[$] @@ -2321,11 +2344,11 @@ class cm_trough_physical_iph : public compute_module } // Monthly outputs - accumulate_monthly_for_year("gen", "monthly_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1); + accumulate_monthly_for_year("gen_heat", "monthly_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1); // Annual outputs - accumulate_annual_for_year("gen", "annual_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); + accumulate_annual_for_year("gen_heat", "annual_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //accumulate_annual_for_year("disp_objective", "disp_objective_ann", 1000.0*sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //accumulate_annual_for_year("disp_solve_iter", "disp_iter_ann", 1000.0*sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //accumulate_annual_for_year("disp_presolve_nconstr", "disp_presolve_nconstr_ann", sim_setup.m_report_step / 3600.0 / as_double("disp_frequency"), steps_per_hour, 1, n_steps_fixed / steps_per_hour); From ce19e5f2880334ebb1986fbda37a391d66bb9a73 Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:56:10 -0600 Subject: [PATCH 48/85] Add thermalrate_iph cmod and configure gen and gen_heat for IPH models. - Change iph units to _heat_btu - Assign gen and gen_heat properly in iph tech models - Make thermalrate_iph cmod for IPH thermal rates with BTU units --- ssc/CMakeLists.txt | 1 + ssc/cmod_fresnel_physical_iph.cpp | 16 +- ssc/cmod_mspt_iph.cpp | 14 +- ssc/cmod_thermalrate_iph.cpp | 686 ++++++++++++++++++++++++++++++ ssc/cmod_trough_physical_iph.cpp | 21 +- ssc/sscapi.cpp | 2 + 6 files changed, 720 insertions(+), 20 deletions(-) create mode 100644 ssc/cmod_thermalrate_iph.cpp diff --git a/ssc/CMakeLists.txt b/ssc/CMakeLists.txt index 819000390..20493316d 100644 --- a/ssc/CMakeLists.txt +++ b/ssc/CMakeLists.txt @@ -106,6 +106,7 @@ set(SSC_SRC cmod_tcstrough_physical.cpp cmod_test_ud_power_cycle.cpp cmod_thermalrate.cpp + cmod_thermalrate_iph.cpp cmod_thirdpartyownership.cpp cmod_timeseq.cpp cmod_trough_physical.cpp diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 8c978b618..1a5243c20 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -216,7 +216,7 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekday", "PPA pricing weekday schedule, 12x24", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekend", "PPA pricing weekend schedule, 12x24", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_ARRAY, "dispatch_tod_factors", "TOD factors for periods 1 through 9", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "ppa_price_input_heatBtu", "PPA prices - yearly", "$/MMBtu", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "ppa_price_input_heat_btu", "PPA prices - yearly", "$/MMBtu", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1", "", "SIMULATION_PARAMETER" }, @@ -563,6 +563,8 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "operating_modes_b", "Next 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "operating_modes_c", "Final 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "Total thermal power to heat sink with available derate", "kWe", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen", "Total electric power to grid w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, + // Monthly Outputs { SSC_OUTPUT, SSC_ARRAY, "monthly_energy", "Monthly Energy", "kWh", "", "Post-process", "sim_type=1", "LENGTH=12", "" }, @@ -613,11 +615,11 @@ class cm_fresnel_physical_iph : public compute_module // Convert IPH Input Units { - const double MMBTU_TO_KWh = 293.07107; // 1 - if (is_assigned("ppa_price_input_heatBtu")) + const double MMBTU_TO_KWh = 293.07107; // 1 MMBtu = 293.07107 kWh + if (is_assigned("ppa_price_input_heat_btu")) { size_t count_ppa_price_MMBTU_input; - ssc_number_t* ppa_price_MMBTU_input_array = as_array("ppa_price_input_heatBtu", &count_ppa_price_MMBTU_input); + ssc_number_t* ppa_price_MMBTU_input_array = as_array("ppa_price_input_heat_btu", &count_ppa_price_MMBTU_input); std::vector ppa_price_input_vec; for (int i = 0; i < count_ppa_price_MMBTU_input; i++) { @@ -1552,7 +1554,8 @@ class cm_fresnel_physical_iph : public compute_module if (!haf.setup(n_steps_fixed)) throw exec_error("fresnel_physical", "failed to setup adjustment factors: " + haf.error()); - ssc_number_t* p_gen = allocate("gen_heat", n_steps_fixed); + ssc_number_t* p_gen_heat = allocate("gen_heat", n_steps_fixed); + ssc_number_t* p_gen = allocate("gen", n_steps_fixed); ssc_number_t* p_q_dot_defocus_est = allocate("q_dot_defocus_est", n_steps_fixed); ssc_number_t* p_SCAs_def = as_array("SCAs_def", &count); if ((int)count != n_steps_fixed) @@ -1569,7 +1572,8 @@ class cm_fresnel_physical_iph : public compute_module for (int i = 0; i < n_steps_fixed; i++) { size_t hour = (size_t)ceil(p_time_final_hr[i]); - p_gen[i] = (ssc_number_t)(p_q_dot_heat_sink[i] * haf(hour) * 1.E3); //[kWe] + p_gen_heat[i] = (ssc_number_t)(p_q_dot_heat_sink[i] * haf(hour) * 1.E3); //[kWe] + p_gen[i] = (ssc_number_t)0.0; //[kWt] (no electrical generation for IPH mslf) p_q_dot_defocus_est[i] = (ssc_number_t)(1.0 - p_SCAs_def[i]) * p_q_dot_htf_sf_out[i]; //[MWt] p_W_dot_parasitic_tot[i] *= -1.0; //[MWe] Label is total parasitics, so change to a positive value p_W_dot_par_tot_haf[i] = (ssc_number_t)(p_W_dot_parasitic_tot[i] * haf(hour) * 1.E3); //[kWe] apply availability derate and convert from MWe diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index e31adb7dd..6769aabeb 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -274,7 +274,7 @@ static var_info _cm_vtab_mspt_iph[] = { { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekday", "PPA pricing weekday schedule, 12x24", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekend", "PPA pricing weekend schedule, 12x24", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_ARRAY, "dispatch_tod_factors", "TOD factors for periods 1 through 9", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, -{ SSC_INPUT, SSC_ARRAY, "ppa_price_input_heatBtu", "PPA prices - yearly", "$/MMBtu", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, +{ SSC_INPUT, SSC_ARRAY, "ppa_price_input_heat_btu", "PPA prices - yearly", "$/MMBtu", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, // Costs @@ -601,6 +601,8 @@ static var_info _cm_vtab_mspt_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "operating_modes_c", "Final 3 operating modes tried", "", "", "", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "Total thermal power to heat sink with available derate", "kWt", "", "", "sim_type=1", "", ""}, +{ SSC_OUTPUT, SSC_ARRAY, "gen", "Total electric power to grid w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, + // Annual single-value outputs { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Thermal Energy to Heat Sink w/ avail derate", "kWt-hr", "", "Post-process", "sim_type=1", "", ""}, @@ -659,10 +661,10 @@ class cm_mspt_iph : public compute_module // Convert IPH Input Units { const double MMBTU_TO_KWh = 293.07107; // 1 - if (is_assigned("ppa_price_input_heatBtu")) + if (is_assigned("ppa_price_input_heat_btu")) { size_t count_ppa_price_MMBTU_input; - ssc_number_t* ppa_price_MMBTU_input_array = as_array("ppa_price_input_heatBtu", &count_ppa_price_MMBTU_input); + ssc_number_t* ppa_price_MMBTU_input_array = as_array("ppa_price_input_heat_btu", &count_ppa_price_MMBTU_input); std::vector ppa_price_input_vec; for (int i = 0; i < count_ppa_price_MMBTU_input; i++) { @@ -2306,7 +2308,8 @@ class cm_mspt_iph : public compute_module if (!haf.setup(count)) throw exec_error("mspt_iph", "failed to setup adjustment factors: " + haf.error()); - ssc_number_t* p_gen = allocate("gen_heat", count); + ssc_number_t* p_gen_heat = allocate("gen_heat", count); + ssc_number_t* p_gen = allocate("gen", n_steps_fixed); ssc_number_t* p_W_dot_parasitic_tot = as_array("W_dot_parasitic_tot", &count); ssc_number_t* p_W_dot_par_tot_haf = allocate("W_dot_par_tot_haf", n_steps_fixed); ssc_number_t* p_load = allocate("load", n_steps_fixed); // testing using cmod_utilityrate5 for electricity rates p_load = p_W_dot_par_tot_haf @@ -2317,7 +2320,8 @@ class cm_mspt_iph : public compute_module for (size_t i = 0; i < count; i++) { size_t hour = (size_t)ceil(p_time_final_hr[i]); - p_gen[i] = (ssc_number_t)(p_q_dot_heat_sink[i] * 1.E3 * haf(hour)); //[kWt] + p_gen_heat[i] = (ssc_number_t)(p_q_dot_heat_sink[i] * 1.E3 * haf(hour)); //[kWt] + p_gen[i] = (ssc_number_t)0.0; //[kWt] (no electrical generation for IPH tower) p_W_dot_parasitic_tot[i] *= -1.0; //[MWe] Label is total parasitics, so change to a positive value p_W_dot_par_tot_haf[i] = (ssc_number_t)(p_W_dot_parasitic_tot[i] * haf(hour) * 1.E3); //[kWe] apply availability derate and convert from MWe p_load[i] = p_W_dot_par_tot_haf[i]; diff --git a/ssc/cmod_thermalrate_iph.cpp b/ssc/cmod_thermalrate_iph.cpp new file mode 100644 index 000000000..7d53a2fb4 --- /dev/null +++ b/ssc/cmod_thermalrate_iph.cpp @@ -0,0 +1,686 @@ +/* +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 "core.h" +#include +#include + + + +static var_info vtab_thermal_rate_iph[] = { + +/* VARTYPE DATATYPE NAME LABEL UNITS META GROUP REQUIRED_IF CONSTRAINTS UI_HINTS*/ + { SSC_INPUT, SSC_NUMBER, "en_thermal_rates", "Optionally enable/disable thermal_rate", "years", "", "Thermal Rate", "", "INTEGER,MIN=0,MAX=1", "" }, + { SSC_INPUT, SSC_NUMBER, "analysis_period", "Number of years in analysis", "years", "", "Lifetime", "*", "INTEGER,POSITIVE", "" }, + + { SSC_INPUT, SSC_NUMBER, "system_use_lifetime_output", "Lifetime hourly system outputs", "0/1", "0=hourly first year,1=hourly lifetime", "Lifetime", "*", "INTEGER,MIN=0,MAX=1", "" }, + + // First year or lifetime hourly or subhourly + // load and gen expected to be > 0 + { SSC_INPUT, SSC_ARRAY, "gen_heat", "Thermal power generated", "kW-t", "", "Thermal Rate", "*", "", "" }, + + // input from user as MMBtu/hr and output as MMBtu/hr + { SSC_INOUT, SSC_ARRAY, "thermal_load_heat_btu", "Thermal load (year 1)", "MMBtu/hr", "", "Thermal Rate", "", "", "" }, + + { SSC_INPUT, SSC_NUMBER, "inflation_rate", "Inflation rate", "%", "", "Lifetime", "*", "MIN=-99", "" }, + + { SSC_INPUT, SSC_ARRAY, "thermal_degradation", "Annual energy degradation", "%", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "thermal_load_escalation", "Annual load escalation", "%/year", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "thermal_rate_escalation", "Annual thermal rate escalation", "%/year", "", "Thermal Rate", "?=0", "", "" }, + + { SSC_INPUT, SSC_NUMBER, "thermal_buy_rate_option", "Thermal buy rate option", "0/1", "0=flat,1=timestep", "Thermal Rate", "?=0", "INTEGER,MIN=0,MAX=1", "" }, + { SSC_INPUT, SSC_ARRAY, "thermal_buy_rate_heat_btu", "Thermal buy rate", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "thermal_buy_rate_flat_heat_btu", "Thermal buy rate flat", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, + + { SSC_INPUT, SSC_NUMBER, "thermal_sell_rate_option", "Thermal sell rate option", "0/1", "0=flat,1=timestep", "Thermal Rate", "?=0", "INTEGER,MIN=0,MAX=1", "" }, + { SSC_INPUT, SSC_ARRAY, "thermal_sell_rate_heat_btu", "Thermal sell rate", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "thermal_sell_rate_flat_heat_btu", "Thermal sell rate flat", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, + + + { SSC_OUTPUT, SSC_ARRAY, "annual_thermal_value", "Thermal value", "$", "", "Annual", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "thermal_revenue_with_system", "Thermal revenue with system", "$", "", "Time Series", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "thermal_revenue_without_system", "Thermal revenue without system", "$", "", "Time Series", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "thermal_load_year1", "Thermal load (year 1)", "$", "", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "thermal_savings_year1", "Thermal savings (year 1)", "$", "", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "thermal_cost_with_system_year1", "Thermal cost with sytem (year 1)", "$", "", "", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "thermal_cost_without_system_year1", "Thermal cost without system (year 1)", "$", "", "", "*", "", "" }, + + + + + var_info_invalid }; + +class tr_month +{ +public: + // period numbers + // net energy use per month + ssc_number_t thermal_net; + ssc_number_t thermal_load; + ssc_number_t thermal_gen; + // hours per period per month + int hours_per_month; + ssc_number_t thermal_peak; + int thermal_peak_hour; + ssc_number_t thermal_buy; + ssc_number_t thermal_sell; +}; + +class cm_thermalrate_iph : public compute_module +{ +private: + size_t m_num_rec_yearly; + std::vector m_month; + + +public: + cm_thermalrate_iph() + { + add_var_info( vtab_thermal_rate_iph ); + } + + void exec( ) + { + // if not assigned, we assume thermal rates are enabled + if (is_assigned("en_thermal_rates")) { + if (!as_boolean("en_thermal_rates")) { + remove_var_info(vtab_thermal_rate_iph); + return; + } + } + + // Convert Generation to MMBtu + const double MMBTU_TO_KWh = 293.07107; // 1 MMBtu = 293.07107 kWh (also 1 MMBtu/hr == 293.07107 kW) + std::vector gen_heat_kW = as_vector_double("gen_heat"); // [kW] + std::vector gen_heat_MMBtu_per_hr; + for (double gen_kW : gen_heat_kW) + { + gen_heat_MMBtu_per_hr.push_back(gen_kW / MMBTU_TO_KWh); + } + + + ssc_number_t *parr = 0; + size_t count, i, j; + + size_t nyears = (size_t)as_integer("analysis_period"); + double inflation_rate = as_double("inflation_rate")*0.01; + + // compute annual system output degradation multipliers + std::vector sys_scale(nyears); + + // degradation + // degradation starts in year 2 for single value degradation - no degradation in year 1 - degradation =1.0 + // lifetime degradation applied in technology compute modules + if (as_integer("system_use_lifetime_output") == 1) + { + for (i = 0; i load_scale(nyears); + parr = as_array("thermal_load_escalation", &count); + if (count == 1) + { + for (i=0;i rate_scale(nyears); + parr = as_array("thermal_rate_escalation", &count); + if (count == 1) + { + for (i=0;i 60 || step_per_hour_gen * 8760 != nrec_gen_per_year) + throw exec_error("thermalrate", util::format("invalid number of thermal records (%d): must be an integer multiple of 8760", (int)nrec_gen_per_year)); + ssc_number_t ts_hour_gen = 1.0f / step_per_hour_gen; + m_num_rec_yearly = nrec_gen_per_year; + + if (is_assigned("thermal_load_heat_btu")) + { // hourly or sub hourly loads for single year + + // Convert thermal load units + std::vector thermal_load_MMBtu_per_hr = as_vector_double("thermal_load_heat_btu"); //[MMBtu/hr] + bload = true; + pload = &thermal_load_MMBtu_per_hr[0]; + nrec_load = thermal_load_MMBtu_per_hr.size(); + + + step_per_hour_load = nrec_load / 8760; + if (step_per_hour_load < 1 || step_per_hour_load > 60 || step_per_hour_load * 8760 != nrec_load) + throw exec_error("thermalrate", util::format("invalid number of load records (%d): must be an integer multiple of 8760", (int)nrec_load)); + if ((nrec_load != m_num_rec_yearly) && (nrec_load != 8760)) + throw exec_error("thermalrate", util::format("number of load records (%d) must be equal to number of gen records (%d) or 8760 for each year", (int)nrec_load, (int)m_num_rec_yearly)); + } +// ssc_number_t ts_hour_load = 1.0f / step_per_hour_load; + + + + // prepare timestep arrays for load and grid values + std::vector + e_sys_cy(m_num_rec_yearly), p_sys_cy(m_num_rec_yearly), + p_load(m_num_rec_yearly), // to handle no load, or num load != num gen + p_buyrate(m_num_rec_yearly), + p_sellrate(m_num_rec_yearly), + e_grid_cy(m_num_rec_yearly), p_grid_cy(m_num_rec_yearly), + e_load_cy(m_num_rec_yearly), p_load_cy(m_num_rec_yearly); // current year load (accounts for escal) + + + + + size_t idx = 0; + + if (as_integer("thermal_buy_rate_option") == 1) + { + size_t nbuyrate,step_per_hour_br; + ssc_number_t br; + pbuyrate = as_array("thermal_buy_rate_heat_btu", &nbuyrate); + step_per_hour_br = nbuyrate / 8760; + if (step_per_hour_br < 1 || step_per_hour_br > 60 || step_per_hour_br * 8760 != nbuyrate) + throw exec_error("thermalrate", util::format("invalid number of buy rate records (%d): must be an integer multiple of 8760", (int)nbuyrate)); + if ((nbuyrate != m_num_rec_yearly) && (nbuyrate != 8760)) + throw exec_error("thermalrate", util::format("number of buy rate records (%d) must be equal to number of gen records (%d) or 8760 for each year", (int)nbuyrate, (int)m_num_rec_yearly)); + for (i = 0; i < 8760; i++) + { + for (size_t ii = 0; ii < step_per_hour_gen; ii++) + { + size_t ndx = i * step_per_hour_gen + ii; + br = ((idx < nbuyrate) ? pbuyrate[idx] : 0); + p_buyrate[ndx] = br; + if (step_per_hour_gen == step_per_hour_br) + idx++; + else if (ii == (step_per_hour_gen - 1)) + idx++; + } + } + } + else // flat rate + { + ssc_number_t br = as_number("thermal_buy_rate_flat_heat_btu"); + for (i = 0; i < m_num_rec_yearly; i++) + p_buyrate[i] = br; + } + + if (as_integer("thermal_sell_rate_option") == 1) + { + size_t nsellrate, step_per_hour_br; + ssc_number_t br; + psellrate = as_array("thermal_sell_rate_heat_btu", &nsellrate); + step_per_hour_br = nsellrate / 8760; + if (step_per_hour_br < 1 || step_per_hour_br > 60 || step_per_hour_br * 8760 != nsellrate) + throw exec_error("thermalrate", util::format("invalid number of sell rate records (%d): must be an integer multiple of 8760", (int)nsellrate)); + if ((nsellrate != m_num_rec_yearly) && (nsellrate != 8760)) + throw exec_error("thermalrate", util::format("number of sell rate records (%d) must be equal to number of gen records (%d) or 8760 for each year", (int)nsellrate, (int)m_num_rec_yearly)); + for (i = 0; i < 8760; i++) + { + for (size_t ii = 0; ii < step_per_hour_gen; ii++) + { + size_t ndx = i * step_per_hour_gen + ii; + br = ((idx < nsellrate) ? psellrate[idx] : 0); + p_sellrate[ndx] = br; + if (step_per_hour_gen == step_per_hour_br) + idx++; + else if (ii == (step_per_hour_gen - 1)) + idx++; + } + } + } + else // flat rate + { + ssc_number_t br = as_number("thermal_sell_rate_flat_heat_btu"); + for (i = 0; i < m_num_rec_yearly; i++) + p_sellrate[i] = br; + } + + + + // assign timestep values for utility rate calculations + ssc_number_t ts_load = 0; + ssc_number_t year1_thermal_load = 0; + + //load - fill out to number of generation records per year + // handle cases + // 1. if no load + // 2. if load has 8760 and gen has more records + // 3. if number records same for load and gen + idx = 0; + for (i = 0; i < 8760; i++) + { + for (size_t ii = 0; ii < step_per_hour_gen; ii++) + { + size_t ndx = i*step_per_hour_gen + ii; + ts_load = (bload ? ((idx < nrec_load) ? pload[idx] : 0) : 0); + year1_thermal_load += ts_load; + // sign correction for utility rate calculations + p_load[ndx] = -ts_load; + if (step_per_hour_gen == step_per_hour_load) + idx++; + else if (ii == (step_per_hour_gen - 1)) + idx++; + } + } + + assign("thermal_load_year1", year1_thermal_load* ts_hour_gen); + + + /* allocate intermediate data arrays */ + std::vector revenue_w_sys(m_num_rec_yearly), revenue_wo_sys(m_num_rec_yearly), + payment(m_num_rec_yearly), income(m_num_rec_yearly), + thermal_charge_w_sys(m_num_rec_yearly), thermal_charge_wo_sys(m_num_rec_yearly), + load(m_num_rec_yearly), salespurchases(m_num_rec_yearly); + std::vector monthly_revenue_w_sys(12), monthly_revenue_wo_sys(12), + monthly_thermal_charges(12), + monthly_ec_rates(12), + monthly_salespurchases(12), + monthly_load(12), monthly_system_generation(12), monthly_bill(12), monthly_peak(12), monthly_test(12); + + /* allocate outputs */ + ssc_number_t *annual_net_revenue = allocate("annual_thermal_value", nyears+1); + ssc_number_t *annual_thermal_load = allocate("annual_thermal_load", nyears+1); + ssc_number_t *thermal_net = allocate("scaled_annual_thermal_energy", nyears+1); + ssc_number_t *annual_revenue_w_sys = allocate("annual_thermal_revenue_with_system", nyears+1); + ssc_number_t *annual_revenue_wo_sys = allocate("annual_thermal_revenue_without_system", nyears+1); + ssc_number_t *annual_thermal_cost_w_sys = allocate("thermal_cost_with_system", nyears+1); + ssc_number_t *annual_thermal_cost_wo_sys = allocate("thermal_cost_without_system", nyears+1); + + + // matrices + //ssc_number_t *thermal_bill_w_sys_ym = allocate("thermal_bill_w_sys_ym", nyears + 1, 12); + //ssc_number_t *thermal_bill_wo_sys_ym = allocate("thermal_bill_wo_sys_ym", nyears + 1, 12); + + + // annual sums + //ssc_number_t *thermal_bill_w_sys = allocate("thermal_bill_w_sys", nyears + 1); + //ssc_number_t *utility_bill_wo_sys = allocate("thermal_bill_wo_sys", nyears + 1); + + + // lifetime hourly load + ssc_number_t *lifetime_load = allocate("lifetime_thermal_load", nrec_gen); + + + idx = 0; + for (i=0;i p_load[i]) ? ts_load : p_load[i]); + idx++; + } + lifetime_hourly_load[i*8760 + j] = e_load[i]; + // sign correction for utility rate calculations + e_load[i] = -e_load[i]; + p_load[i] = -p_load[i]; + } + */ + // apply load escalation appropriate for current year +// e_load_cy[j] = e_load[j] * load_scale[i]; +// p_load_cy[j] = p_load[j] * load_scale[i]; + e_load_cy[j] = p_load[j] * load_scale[i] * ts_hour_gen; + p_load_cy[j] = p_load[j] * load_scale[i]; + + + // update e_sys per year if lifetime output + if ((as_integer("system_use_lifetime_output") == 1) && ( idx < nrec_gen )) + { +// e_sys[j] = p_sys[j] = 0.0; +// ts_power = (idx < nrec_gen) ? pgen[idx] : 0; +// e_sys[j] = ts_power * ts_hour_gen; +// p_sys[j] = ((ts_power > p_sys[j]) ? ts_power : p_sys[j]); + e_sys_cy[j] = pgen[idx] * ts_hour_gen; + p_sys_cy[j] = pgen[idx]; + // until lifetime load fully implemented + lifetime_load[idx] = -e_load_cy[j]/ts_hour_gen; + idx++; + } + else + { + e_sys_cy[j] = pgen[j] * ts_hour_gen; + p_sys_cy[j] = pgen[j]; + } + e_sys_cy[j] *= sys_scale[i]; + p_sys_cy[j] *= sys_scale[i]; + // calculate e_grid value (e_sys + e_load) +// e_sys_cy[j] = e_sys[j] * sys_scale[i]; +// p_sys_cy[j] = p_sys[j] * sys_scale[i]; + // note: load is assumed to have negative sign + e_grid_cy[j] = e_sys_cy[j] + e_load_cy[j]; + p_grid_cy[j] = p_sys_cy[j] + p_load_cy[j]; + } + + // without system + tr_calc_timestep(&e_load_cy[0], &p_load_cy[0], &p_buyrate[0], &p_sellrate[0], + &revenue_wo_sys[0], &payment[0], &income[0], &thermal_charge_wo_sys[0], rate_scale[i]); + + + if (i==0) + { + assign("year1_hourly_charge_without_system", var_data(&thermal_charge_wo_sys[0], m_num_rec_yearly)); + + // sign reversal based on 9/5/13 meeting, reverse again 9/6/13 + for (int ii = 0; ii<(int)m_num_rec_yearly; ii++) + { + salespurchases[ii] = revenue_wo_sys[ii]; + } + + int c = 0; + + for (int m=0;m<12;m++) + { + monthly_salespurchases[m] = 0; + for (size_t d=0;d output(m_num_rec_yearly), tdemand(m_num_rec_yearly), pdemand(m_num_rec_yearly), e_sys_to_grid(m_num_rec_yearly), e_sys_to_load(m_num_rec_yearly), p_sys_to_load(m_num_rec_yearly); + for (j = 0; j 0 ? sys_e_net : (ssc_number_t)0.0; + e_sys_to_load[j] = sys_e_net > 0 ? -e_load_cy[j] : output[j]; + +// ssc_number_t sys_p_net = output[j] + p_load[j];// loads are assumed negative +// p_sys_to_load[j] = sys_p_net > 0 ? -p_load[j] : output[j]; + ssc_number_t sys_p_net = output[j] + p_load_cy[j];// loads are assumed negative + p_sys_to_load[j] = sys_p_net > 0 ? -p_load_cy[j] : output[j]; + } + + assign("year1_hourly_system_output", var_data(&output[0], (int)m_num_rec_yearly)); + assign("year1_hourly_t_demand", var_data(&tdemand[0], (int)m_num_rec_yearly)); + assign("year1_hourly_p_demand", var_data(&pdemand[0], (int)m_num_rec_yearly)); + + assign("year1_hourly_system_to_load", var_data(&e_sys_to_load[0], (int)m_num_rec_yearly)); + assign("year1_hourly_p_system_to_load", var_data(&p_sys_to_load[0], (int)m_num_rec_yearly)); + + + } + + // determine net-revenue benefit due to thermal for year 'i' + + annual_net_revenue[i+1] = 0.0; + annual_thermal_load[i + 1] = 0.0; + thermal_net[i + 1] = 0.0; + annual_revenue_w_sys[i + 1] = 0.0; + annual_revenue_wo_sys[i + 1] = 0.0; + + for (j = 0; j 0) + monthly_thermal_needed_from_grid[m] = monthly_thermal_to_grid[m]; + else + monthly_thermal_needed_from_grid[m]=0; + } + } + + + void tr_calc_timestep(ssc_number_t *e_in, ssc_number_t *p_in, ssc_number_t *br_in, ssc_number_t *sr_in, + ssc_number_t *revenue, ssc_number_t *payment, ssc_number_t *income, + ssc_number_t *thermal_charge, + ssc_number_t rate_esc, bool = true, bool = true, bool = false) + + { + int i; + for (i = 0; i<(int)m_num_rec_yearly; i++) + revenue[i] = payment[i] = income[i] = thermal_charge[i] = 0.0; + + + + size_t steps_per_hour = m_num_rec_yearly / 8760; + + + // calculate the monthly net energy and monthly hours + int m, d, h, s; + size_t c = 0; + for (m = 0; m < (int)m_month.size(); m++) + { + m_month[m].thermal_net = 0; + m_month[m].hours_per_month = 0; + m_month[m].thermal_peak = 0; + m_month[m].thermal_peak_hour = 0; + for (d = 0; d < (int)util::nday[m]; d++) + { + for (h = 0; h < 24; h++) + { + for (s = 0; s < (int)steps_per_hour && (int)c < (int)m_num_rec_yearly; s++) + { + // net energy use per month + m_month[m].thermal_net += e_in[c]; // -load and +gen + // hours per period per month + m_month[m].hours_per_month++; + // peak + if (p_in[c] < 0 && p_in[c] < -m_month[m].thermal_peak) + { + m_month[m].thermal_peak = -p_in[c]; + m_month[m].thermal_peak_hour = (int)c; + } + c++; + } + } + } + } + + +// main loop + c = 0; // hourly count + // process one timestep at a time + for (m = 0; m < 12; m++) + { + for (d = 0; d<(int)util::nday[m]; d++) + { + //daily_net_energy = 0; + for (h = 0; h<24; h++) + { + for (s = 0; s < (int)steps_per_hour && (int)c < (int)m_num_rec_yearly; s++) + { + + if (e_in[c] >= 0.0) + { // calculate income or credit + + // cumulative energy used to determine tier for credit of entire surplus amount + ssc_number_t credit_amt = 0; + ssc_number_t thermal_surplus = e_in[c]; + credit_amt = thermal_surplus * sr_in[c] * rate_esc; + // accumulate monthly charge and therms + income[c] = (ssc_number_t)credit_amt; + } + else + { // calculate payment or charge + ssc_number_t charge_amt = 0; + ssc_number_t thermal_deficit = -e_in[c]; + charge_amt = thermal_deficit * br_in[c] * rate_esc; + // accumulate monthly charge and therms + payment[c] = (ssc_number_t)charge_amt; + //monthly_charges[m] += (ssc_number_t)charge_amt; + + } + revenue[c] = income[c] - payment[c]; + + c++; + } // steps per hour loop + } // h loop + } // d loop + + // Calculate monthly bill + + } // end of month m (m loop) + + + } + + + +}; + +DEFINE_MODULE_ENTRY( thermalrate_iph, "Thermal flat rate structure net revenue calculator", 1 ); + + diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 023a29cd4..9340040c7 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -244,7 +244,7 @@ static var_info _cm_vtab_trough_physical_iph[] = { // Prices for *electricity* purchases { SSC_INPUT, SSC_NUMBER, "ppa_multiplier_model", "PPA multiplier model 0: dispatch factors dispatch_factorX, 1: hourly multipliers dispatch_factors_ts", "0/1", "0=diurnal,1=timestep", "tou", "?=0", /*need a default so this var works in required_if*/ "INTEGER,MIN=0", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "ppa_soln_mode", "PPA solution mode (0=Specify IRR target, 1=Specify PPA price)", "", "", "Financial Solution Mode", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "ppa_price_input_heatBtu", "PPA prices - yearly", "$/MMBtu", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_ARRAY, "ppa_price_input_heat_btu", "PPA prices - yearly", "$/MMBtu", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, // *Electricity* hourly price multipliers from Block Schedule { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekday", "12x24 PPA pricing Weekday schedule", "", "", "tou", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_MATRIX, "dispatch_sched_weekend", "12x24 PPA pricing Weekend schedule", "", "", "tou", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, @@ -362,7 +362,7 @@ static var_info _cm_vtab_trough_physical_iph[] = { // Design Point Outputs { SSC_OUTPUT, SSC_NUMBER, "solar_mult", "Actual solar multiple", "", "", "System Design Calc","*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "nameplate", "Nameplate capacity", "MWe", "", "System Design Calc","*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "nameplate", "Nameplate capacity", "MWt", "", "System Design Calc","*", "", "" }, // System capacity required by downstream financial model { SSC_OUTPUT, SSC_NUMBER, "system_capacity", "System capacity", "kWt", "", "System Design", "*", "", "" }, @@ -704,7 +704,9 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "P_fixed", "Parasitic power fixed load", "MWe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "P_plant_balance_tot", "Parasitic power generation-dependent load", "MWe", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "Total thermal power to grid w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "Total thermal power to grid w/ avail. derate", "kWt", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen", "Total electric power to grid w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, + //{ SSC_OUTPUT, SSC_NUMBER, "conversion_factor", "Gross to Net Conversion Factor", "%", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "kwh_per_kw", "First year kWh/kW", "kWh/kW", "", "system", "sim_type=1", "", "" }, @@ -738,7 +740,7 @@ class cm_trough_physical_iph : public compute_module { add_var_info( _cm_vtab_trough_physical_iph ); add_var_info( vtab_adjustment_factors ); - add_var_info(vtab_technology_outputs); + //add_var_info(vtab_technology_outputs); add_var_info(vtab_utility_rate_common); // Required for dispatch w/ utility rates } @@ -764,10 +766,10 @@ class cm_trough_physical_iph : public compute_module // Convert IPH Input Units { - if (is_assigned("ppa_price_input_heatBtu")) + if (is_assigned("ppa_price_input_heat_btu")) { size_t count_ppa_price_MMBTU_input; - ssc_number_t* ppa_price_MMBTU_input_array = as_array("ppa_price_input_heatBtu", &count_ppa_price_MMBTU_input); + ssc_number_t* ppa_price_MMBTU_input_array = as_array("ppa_price_input_heat_btu", &count_ppa_price_MMBTU_input); std::vector ppa_price_input_vec; for (int i = 0; i < count_ppa_price_MMBTU_input; i++) { @@ -2222,7 +2224,8 @@ class cm_trough_physical_iph : public compute_module if( !haf.setup(n_steps_fixed) ) throw exec_error("trough_physical", "failed to setup adjustment factors: " + haf.error()); - ssc_number_t *p_gen = allocate("gen_heat", n_steps_fixed); + ssc_number_t *p_gen_heat = allocate("gen_heat", n_steps_fixed); + ssc_number_t* p_gen = allocate("gen", n_steps_fixed); ssc_number_t *p_W_dot_par_tot_haf = allocate("W_dot_par_tot_haf", n_steps_fixed); ssc_number_t *p_q_dot_defocus_est = allocate("q_dot_defocus_est", n_steps_fixed); ssc_number_t* p_load = allocate("load", n_steps_fixed); // testing using cmod_utilityrate5 for electricity rates p_load = p_W_dot_par_tot_haf @@ -2259,7 +2262,8 @@ class cm_trough_physical_iph : public compute_module for(int i = 0; i < n_steps_fixed; i++) { size_t hour = (size_t)ceil(p_time_final_hr[i]); - p_gen[i] = (ssc_number_t)(p_q_dot_heat_sink[i] * haf(hour) * 1.E3); //[kWe] + p_gen_heat[i] = (ssc_number_t)(p_q_dot_heat_sink[i] * haf(hour) * 1.E3); //[kWt] + p_gen[i] = (ssc_number_t)0.0; //[kWt] (no electrical generation for IPH trough) p_W_dot_parasitic_tot[i] *= -1.0; //[kWe] Label is total parasitics, so change to a positive value p_W_dot_par_tot_haf[i] = (ssc_number_t)(p_W_dot_parasitic_tot[i] * haf(hour) * 1.E3); //[kWe] p_q_dot_defocus_est[i] = (ssc_number_t)(1.0 - p_SCAs_def[i])*p_q_dot_htf_sf_out[i]; //[MWt] @@ -2272,7 +2276,6 @@ class cm_trough_physical_iph : public compute_module annual_elec_cost += i_elec_cost; //[$] } - ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour); // Non-timeseries array outputs double P_adj = 0; // slightly adjust all field design pressures to account for pressure drop in TES before hot tank diff --git a/ssc/sscapi.cpp b/ssc/sscapi.cpp index 350774c07..6e1951cee 100644 --- a/ssc/sscapi.cpp +++ b/ssc/sscapi.cpp @@ -155,6 +155,7 @@ extern module_entry_info cm_entry_pv_get_shade_loss_mpp, cm_entry_inv_cec_cg, cm_entry_thermalrate, + cm_entry_thermalrate_iph, cm_entry_mhk_tidal, cm_entry_mhk_wave, cm_entry_mhk_costs, @@ -257,6 +258,7 @@ static module_entry_info *module_table[] = { &cm_entry_pv_get_shade_loss_mpp, &cm_entry_inv_cec_cg, &cm_entry_thermalrate, + &cm_entry_thermalrate_iph, &cm_entry_mhk_tidal, &cm_entry_mhk_wave, &cm_entry_mhk_costs, From 034af65c0ec414f9c4991836f784d10984a564b4 Mon Sep 17 00:00:00 2001 From: Steven Janzou Date: Sun, 27 Oct 2024 04:42:17 -0600 Subject: [PATCH 49/85] Remove curtailment and capacity inputs and revenue streams for heat --- ssc/cmod_singleowner_heat.cpp | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/ssc/cmod_singleowner_heat.cpp b/ssc/cmod_singleowner_heat.cpp index 3d8192078..4facd85a1 100644 --- a/ssc/cmod_singleowner_heat.cpp +++ b/ssc/cmod_singleowner_heat.cpp @@ -639,7 +639,7 @@ static var_info _cm_vtab_singleowner_heat[] = { { SSC_OUTPUT, SSC_NUMBER, "min_dscr", "Minimum DSCR", "", "", "DSCR", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_pretax_dscr", "DSCR (pre-tax)", "", "", "DSCR", "*", "LENGTH_EQUAL=cf_length", "" }, - + /* Not for heat { SSC_INPUT, SSC_ARRAY, "system_pre_curtailment_kwac", "System power before grid curtailment", "kW", "System generation" "", "System Output", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_energy_curtailed", "Electricity curtailed", "kWh", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, @@ -648,6 +648,7 @@ static var_info _cm_vtab_singleowner_heat[] = { { SSC_OUTPUT, SSC_NUMBER, "npv_curtailment_revenue", "Present value of curtailment payment revenue", "$", "", "Metrics", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "npv_capacity_revenue", "Present value of capacity payment revenue", "$", "", "Metrics", "*", "", "" }, + */ // only count toward revenue if user selected { SSC_OUTPUT, SSC_NUMBER, "npv_fed_pbi_income", "Present value of federal PBI income", "$", "", "Metrics", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "npv_sta_pbi_income", "Present value of state PBI income", "$", "", "Metrics", "*", "", "" }, @@ -671,7 +672,7 @@ extern var_info vtab_debt[], vtab_financial_metrics[], vtab_financial_capacity_payments[], - vtab_financial_grid[], +// vtab_financial_grid[], vtab_fuelcell_replacement_cost[], vtab_lcos_inputs[], vtab_battery_replacement_cost[], @@ -679,11 +680,11 @@ extern var_info enum { CF_energy_net, - CF_energy_curtailed, +// CF_energy_curtailed, CF_energy_value, CF_thermal_value, - CF_curtailment_value, - CF_capacity_payment, +// CF_curtailment_value, +// CF_capacity_payment, CF_ppa_price, CF_om_fixed_expense, @@ -932,7 +933,7 @@ class cm_singleowner_heat : public compute_module add_var_info(vtab_battery_replacement_cost); add_var_info(vtab_fuelcell_replacement_cost); add_var_info(vtab_financial_capacity_payments); - add_var_info(vtab_financial_grid); +// add_var_info(vtab_financial_grid); add_var_info(vtab_lcos_inputs); add_var_info(vtab_update_tech_outputs); add_var_info(vtab_tod_dispatch_periods); @@ -1298,7 +1299,7 @@ class cm_singleowner_heat : public compute_module } } - + /* // curtailed energy and revenue ssc_number_t pre_curtailement_year1_energy = as_number("annual_energy_pre_curtailment_ac"); size_t count_curtailment_price; @@ -1387,7 +1388,7 @@ class cm_singleowner_heat : public compute_module } } } - + */ @@ -2390,8 +2391,8 @@ class cm_singleowner_heat : public compute_module // total revenue cf.at(CF_total_revenue,i) = cf.at(CF_energy_value,i) + cf.at(CF_thermal_value,i) + - cf.at(CF_curtailment_value, i) + - cf.at(CF_capacity_payment, i) + +// cf.at(CF_curtailment_value, i) + +// cf.at(CF_capacity_payment, i) + pbi_fed_for_ds_frac * cf.at(CF_pbi_fed,i) + pbi_sta_for_ds_frac * cf.at(CF_pbi_sta,i) + pbi_uti_for_ds_frac * cf.at(CF_pbi_uti,i) + @@ -2406,11 +2407,11 @@ class cm_singleowner_heat : public compute_module // receivables precalculation need future energy value so outside previous loop if (nyears>0) { - cf.at(CF_reserve_receivables, 0) = months_receivables_reserve_frac * (cf.at(CF_energy_value, 1) + cf.at(CF_thermal_value, 1) + cf.at(CF_curtailment_value, 1) + cf.at(CF_capacity_payment, 1)); + cf.at(CF_reserve_receivables, 0) = months_receivables_reserve_frac * (cf.at(CF_energy_value, 1) + cf.at(CF_thermal_value, 1));// +cf.at(CF_curtailment_value, 1) + cf.at(CF_capacity_payment, 1)); cf.at(CF_funding_receivables, 0) = cf.at(CF_reserve_receivables, 0); for (i = 1; i Date: Sun, 27 Oct 2024 04:48:40 -0600 Subject: [PATCH 50/85] Remove capacity payment input dependency for cmod_singleowner_heat --- ssc/cmod_singleowner_heat.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ssc/cmod_singleowner_heat.cpp b/ssc/cmod_singleowner_heat.cpp index 4facd85a1..20f968b01 100644 --- a/ssc/cmod_singleowner_heat.cpp +++ b/ssc/cmod_singleowner_heat.cpp @@ -671,7 +671,7 @@ extern var_info vtab_payment_incentives[], vtab_debt[], vtab_financial_metrics[], - vtab_financial_capacity_payments[], +// vtab_financial_capacity_payments[], // vtab_financial_grid[], vtab_fuelcell_replacement_cost[], vtab_lcos_inputs[], @@ -932,7 +932,7 @@ class cm_singleowner_heat : public compute_module add_var_info( _cm_vtab_singleowner_heat ); add_var_info(vtab_battery_replacement_cost); add_var_info(vtab_fuelcell_replacement_cost); - add_var_info(vtab_financial_capacity_payments); +// add_var_info(vtab_financial_capacity_payments); // add_var_info(vtab_financial_grid); add_var_info(vtab_lcos_inputs); add_var_info(vtab_update_tech_outputs); From 700751aa4eca9c37b3fabc79e6a67fe904c450a5 Mon Sep 17 00:00:00 2001 From: Steven Janzou Date: Mon, 28 Oct 2024 04:56:23 -0600 Subject: [PATCH 51/85] Update hourly_calculations class to handle heat values --- ssc/cmod_singleowner_heat.cpp | 62 +++++++++++++++++++---------------- ssc/common_financial.cpp | 7 ++-- ssc/common_financial.h | 2 +- 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/ssc/cmod_singleowner_heat.cpp b/ssc/cmod_singleowner_heat.cpp index 20f968b01..776d85184 100644 --- a/ssc/cmod_singleowner_heat.cpp +++ b/ssc/cmod_singleowner_heat.cpp @@ -53,6 +53,7 @@ static var_info _cm_vtab_singleowner_heat[] = { { SSC_OUTPUT, SSC_ARRAY, "revenue_gen", "Electricity to grid", "kW", "", "System Output", "", "", "" }, // { SSC_OUTPUT, SSC_ARRAY, "gen_purchases", "Electricity from grid", "kW", "", "System Output", "", "", "" }, + { SSC_INPUT, SSC_ARRAY, "gen_heat", "Thermal power", "kWt", "", "System Output", "*", "", "" }, { SSC_INPUT, SSC_ARRAY, "gen", "Net power to or from the grid", "kW", "", "System Output", "*", "", "" }, { SSC_INPUT, SSC_ARRAY, "gen_without_battery", "Electricity to or from the renewable system, without the battery", "kW", "", "System Output", "", "", "" }, @@ -91,7 +92,7 @@ static var_info _cm_vtab_singleowner_heat[] = { { SSC_INOUT, SSC_NUMBER, "system_use_lifetime_output", "Lifetime hourly system outputs", "0/1", "0=hourly first year,1=hourly lifetime", "Lifetime", "*", "INTEGER,MIN=0", "" }, - +/* { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_jan", "Energy produced by year in January", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_jan", "PPA revenue by year for January", "$", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_feb", "Energy produced by year in February", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "*", "LENGTH_EQUAL=cf_length", "" }, @@ -188,7 +189,7 @@ static var_info _cm_vtab_singleowner_heat[] = { { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_monthly_firstyear_TOD8", "Energy produced in Year 1 by month for TOD period 8", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_revenue_monthly_firstyear_TOD9", "PPA revenue in Year 1 by month for TOD period 9", "$", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales_monthly_firstyear_TOD9", "Energy produced in Year 1 by month for TOD period 9", "kWh", "", "Cash Flow Revenue by Month and TOD Period", "ppa_multiplier_model=0", "", "" }, - +*/ /* inputs in model not currently in M 11/15/10 */ { SSC_INPUT, SSC_NUMBER, "total_installed_cost", "Installed cost", "$", "", "System Costs", "*", "", "" }, @@ -660,23 +661,23 @@ static var_info _cm_vtab_singleowner_heat[] = { var_info_invalid }; extern var_info - vtab_update_tech_outputs[], - vtab_ppa_inout[], - vtab_standard_financial[], - vtab_oandm[], - vtab_equip_reserve[], - vtab_tax_credits[], - vtab_depreciation_inputs[], - vtab_depreciation_outputs[], - vtab_payment_incentives[], - vtab_debt[], - vtab_financial_metrics[], +vtab_update_tech_outputs[], +vtab_ppa_inout[], +vtab_standard_financial[], +vtab_oandm[], +vtab_equip_reserve[], +vtab_tax_credits[], +vtab_depreciation_inputs[], +vtab_depreciation_outputs[], +vtab_payment_incentives[], +vtab_debt[], +vtab_financial_metrics[], // vtab_financial_capacity_payments[], // vtab_financial_grid[], - vtab_fuelcell_replacement_cost[], - vtab_lcos_inputs[], - vtab_battery_replacement_cost[], - vtab_tod_dispatch_periods[]; +vtab_fuelcell_replacement_cost[], +vtab_lcos_inputs[], +vtab_battery_replacement_cost[]; +// vtab_tod_dispatch_periods[]; enum { CF_energy_net, @@ -912,7 +913,7 @@ class cm_singleowner_heat : public compute_module private: util::matrix_t cf; util::matrix_t cf_lcos; - dispatch_calculations m_disp_calcs; +// dispatch_calculations m_disp_calcs; hourly_energy_calculation hourly_energy_calcs; @@ -936,7 +937,7 @@ class cm_singleowner_heat : public compute_module // add_var_info(vtab_financial_grid); add_var_info(vtab_lcos_inputs); add_var_info(vtab_update_tech_outputs); - add_var_info(vtab_tod_dispatch_periods); +// add_var_info(vtab_tod_dispatch_periods); add_var_info(vtab_utility_rate_common); add_var_info(vtab_hybrid_fin_om); add_var_info(vtab_update_tech_outputs); @@ -1234,7 +1235,7 @@ class cm_singleowner_heat : public compute_module } } - hourly_energy_calcs.calculate(this); + hourly_energy_calcs.calculate(this, true); // dispatch if (as_integer("system_use_lifetime_output") == 1) @@ -1404,7 +1405,7 @@ class cm_singleowner_heat : public compute_module { degrade_cf.push_back(cf.at(CF_degradation, i)); } - m_disp_calcs.init(this, degrade_cf, hourly_energy_calcs.hourly_sales()); + //m_disp_calcs.init(this, degrade_cf, hourly_energy_calcs.hourly_sales()); // end of energy and dispatch initialization @@ -1441,10 +1442,12 @@ class cm_singleowner_heat : public compute_module // double ppa = as_double("ppa_price_input")*100.0; // either initial guess for ppa_mode=1 or final ppa for ppa_mode=0 if (ppa_mode == 0) ppa = 0; // initial guess for target irr mode + // convert to $/kWht done in iph models + // Use PPA values to calculate revenue from purchases and sales - size_t n_multipliers; + //size_t n_multipliers; - ssc_number_t* ppa_multipliers = as_array("ppa_multipliers", &n_multipliers); + //ssc_number_t* ppa_multipliers = as_array("ppa_multipliers", &n_multipliers); bool ppa_purchases = !(is_assigned("en_electricity_rates") && as_number("en_electricity_rates") == 1); if (as_integer("system_use_lifetime_output") == 1) { @@ -1463,9 +1466,10 @@ class cm_singleowner_heat : public compute_module else cf.at(CF_ppa_price, i) = ppa * pow(1 + ppa_escalation, i - 1); // ppa_mode==0 or single value double ppa_value = cf.at(CF_ppa_price, i); + // TODO - verify n_multipliers == 8760 for (size_t h = 0; h < 8760; h++) { if (ppa_purchases) { - cf.at(CF_utility_bill, i) += -hourly_energy_calcs.hourly_purchases()[(i - 1) * 8760 + h] * cf.at(CF_degradation, i) * ppa_value / 100.0 * ppa_multipliers[h]; + cf.at(CF_utility_bill, i) += -hourly_energy_calcs.hourly_purchases()[(i - 1) * 8760 + h] * cf.at(CF_degradation, i) * ppa_value / 100.0;// *ppa_multipliers[h]; } } } @@ -1485,7 +1489,7 @@ class cm_singleowner_heat : public compute_module double ppa_value = cf.at(CF_ppa_price, i); for (size_t h = 0; h < 8760; h++) { if (ppa_purchases) { - cf.at(CF_utility_bill, i) += -hourly_energy_calcs.hourly_purchases()[h] * cf.at(CF_degradation, i) * ppa_value / 100.0 * ppa_multipliers[h]; + cf.at(CF_utility_bill, i) += -hourly_energy_calcs.hourly_purchases()[h] * cf.at(CF_degradation, i) * ppa_value / 100.0;// *ppa_multipliers[h]; } } } @@ -2382,10 +2386,10 @@ class cm_singleowner_heat : public compute_module } else cf.at(CF_ppa_price, i) = ppa * pow(1 + ppa_escalation, i - 1); // ppa_mode==0 or single value -// cf.at(CF_energy_value,i) = cf.at(CF_energy_net,i) * cf.at(CF_ppa_price,i) /100.0; + cf.at(CF_energy_value,i) = cf.at(CF_energy_net,i) * cf.at(CF_ppa_price,i) /100.0; // dispatch - cf.at(CF_energy_value, i) = cf.at(CF_ppa_price, i) / 100.0 *( - m_disp_calcs.tod_energy_value(i)); +// cf.at(CF_energy_value, i) = cf.at(CF_ppa_price, i) / 100.0 *( +// m_disp_calcs.tod_energy_value(i)); // log(util::format("year %d : energy value =%lg", i, m_disp_calcs.tod_energy_value(i)), SSC_WARNING); // total revenue @@ -3532,7 +3536,7 @@ class cm_singleowner_heat : public compute_module { ppa_cf.push_back(cf.at(CF_ppa_price, i)); } - m_disp_calcs.compute_outputs(ppa_cf); + //m_disp_calcs.compute_outputs(ppa_cf); // Intermediate tax credit/depreciation variables assign("pre_depr_alloc_basis", var_data((ssc_number_t)pre_depr_alloc_basis)); diff --git a/ssc/common_financial.cpp b/ssc/common_financial.cpp index bd271f032..814fb1a27 100644 --- a/ssc/common_financial.cpp +++ b/ssc/common_financial.cpp @@ -3165,7 +3165,7 @@ bool advanced_financing_cost::compute_cost(double cost_installed, double equity, } */ -bool hourly_energy_calculation::calculate(compute_module *cm) +bool hourly_energy_calculation::calculate(compute_module *cm, bool heat) { if (!cm) return false; @@ -3177,7 +3177,10 @@ bool hourly_energy_calculation::calculate(compute_module *cm) ssc_number_t *pgen; size_t nrec_gen = 0; m_step_per_hour_gen = 1; - pgen = m_cm->as_array("gen", &nrec_gen); + if (heat) + pgen = m_cm->as_array("gen_heat", &nrec_gen); // kWht + else + pgen = m_cm->as_array("gen", &nrec_gen); // in front of meter - account for charging and size_t i; diff --git a/ssc/common_financial.h b/ssc/common_financial.h index 7029c4dfe..fe41ed95a 100644 --- a/ssc/common_financial.h +++ b/ssc/common_financial.h @@ -113,7 +113,7 @@ class hourly_energy_calculation size_t m_step_per_hour_gen; public: - bool calculate(compute_module *cm); + bool calculate(compute_module *cm, bool heat = false); std::vector& hourly_energy() { return m_hourly_energy; } From 861034c5b43f78a2dc7289b077851a452dc56eb6 Mon Sep 17 00:00:00 2001 From: Steven Janzou Date: Tue, 29 Oct 2024 03:42:55 -0600 Subject: [PATCH 52/85] Update o and m and cash flow for cmod_singleowner_heat --- ssc/cmod_singleowner_heat.cpp | 18 +++++------ ssc/common.cpp | 60 ++++++++++++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/ssc/cmod_singleowner_heat.cpp b/ssc/cmod_singleowner_heat.cpp index 776d85184..13b3b0167 100644 --- a/ssc/cmod_singleowner_heat.cpp +++ b/ssc/cmod_singleowner_heat.cpp @@ -50,7 +50,7 @@ static var_info _cm_vtab_singleowner_heat[] = { { SSC_INPUT, SSC_NUMBER, "en_batt", "Enable battery storage model", "0/1", "", "BatterySystem", "?=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "en_electricity_rates", "Enable electricity rates for grid purchase", "0/1", "", "Electricity Rates", "?=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "batt_meter_position", "Position of battery relative to electric meter", "", "", "BatterySystem", "", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "revenue_gen", "Electricity to grid", "kW", "", "System Output", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "revenue_gen", "Thermal energy", "kWht", "", "System Output", "", "", "" }, // { SSC_OUTPUT, SSC_ARRAY, "gen_purchases", "Electricity from grid", "kW", "", "System Output", "", "", "" }, { SSC_INPUT, SSC_ARRAY, "gen_heat", "Thermal power", "kWt", "", "System Output", "*", "", "" }, @@ -528,10 +528,10 @@ static var_info _cm_vtab_singleowner_heat[] = { /* Production - input as energy_net above */ /* Partial Income Statement: Project */ - { SSC_OUTPUT, SSC_ARRAY, "cf_energy_net", "Electricity to grid net", "kWh", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, - { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales", "Electricity to grid", "kWh", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, - { SSC_OUTPUT, SSC_ARRAY, "cf_energy_purchases", "Electricity from grid", "kWh", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, - { SSC_OUTPUT, SSC_ARRAY, "cf_energy_without_battery", "Electricity generated without storage", "kWh", "", "Cash Flow Electricity", "", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_net", "Thermal energy", "kWht", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales", "Thermal energy to grid", "kWht", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_purchases", "Thermal energy from grid", "kWht", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_without_battery", "Thermal energy generated without storage", "kWht", "", "Cash Flow Electricity", "", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_ppa_price", "PPA price", "cents/kWh", "", "Cash Flow Revenues", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_energy_value", "PPA revenue", "$", "", "Cash Flow Revenues", "*", "LENGTH_EQUAL=cf_length", "" }, @@ -664,7 +664,7 @@ extern var_info vtab_update_tech_outputs[], vtab_ppa_inout[], vtab_standard_financial[], -vtab_oandm[], +vtab_oandm_heat[], vtab_equip_reserve[], vtab_tax_credits[], vtab_depreciation_inputs[], @@ -922,7 +922,7 @@ class cm_singleowner_heat : public compute_module { add_var_info(vtab_ppa_inout ); add_var_info( vtab_standard_financial ); - add_var_info( vtab_oandm ); + add_var_info( vtab_oandm_heat ); add_var_info( vtab_equip_reserve ); add_var_info( vtab_tax_credits ); add_var_info(vtab_depreciation_inputs ); @@ -1061,8 +1061,8 @@ class cm_singleowner_heat : public compute_module // general financial expenses and incentives - stdlib? // precompute expenses from annual schedules or value+escalation escal_or_annual( CF_om_fixed_expense, nyears, "om_fixed", inflation_rate, 1.0, false, as_double("om_fixed_escal")*0.01 ); - escal_or_annual( CF_om_production_expense, nyears, "om_production", inflation_rate, 0.001, false, as_double("om_production_escal")*0.01 ); - escal_or_annual( CF_om_capacity_expense, nyears, "om_capacity", inflation_rate, 1.0, false, as_double("om_capacity_escal")*0.01 ); + escal_or_annual( CF_om_production_expense, nyears, "om_production_heat", inflation_rate, 0.001, false, as_double("om_production_escal")*0.01 ); + escal_or_annual( CF_om_capacity_expense, nyears, "om_capacity_heat", inflation_rate, 1.0, false, as_double("om_capacity_escal")*0.01 ); escal_or_annual( CF_om_fuel_expense, nyears, "om_fuel_cost", inflation_rate, as_double("system_heat_rate")*0.001, false, as_double("om_fuel_cost_escal")*0.01 ); // escal_or_annual(CF_om_elec_price_for_heat_techs, nyears, "om_elec_price_for_heat_techs", inflation_rate, 1.0, false, as_double("om_elec_price_for_heat_techs_escal")*0.01 ); diff --git a/ssc/common.cpp b/ssc/common.cpp index 6b5171cba..2cd726697 100644 --- a/ssc/common.cpp +++ b/ssc/common.cpp @@ -99,6 +99,62 @@ var_info vtab_standard_loan[] = { { SSC_INPUT,SSC_NUMBER , "debt_fraction" , "Debt percentage" , "%" , "" , "Financial Parameters" , "?=0" , "MIN=0,MAX=100" , ""}, var_info_invalid }; + +var_info vtab_oandm_heat[] = { + /* VARTYPE DATATYPE NAME LABEL UNITS META GROUP REQUIRED_IF CONSTRAINTS UI_HINTS*/ + + { SSC_INPUT, SSC_ARRAY, "om_fixed", "Fixed O&M annual amount", "$/year", "", "System Costs", "?=0.0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "om_fixed_escal", "Fixed O&M escalation", "%/year", "", "System Costs", "?=0.0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "om_production_heat", "Production-based O&M amount", "$/MWht", "", "System Costs", "?=0.0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "om_production_escal", "Production-based O&M escalation", "%/year", "", "System Costs", "?=0.0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "om_capacity_heat", "Capacity-based O&M amount", "$/kWt-yr", "", "System Costs", "?=0.0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "om_capacity_escal", "Capacity-based O&M escalation", "%/year", "", "System Costs", "?=0.0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "om_fuel_cost", "Fuel cost", "$/MMBtu", "generic_system,fuelcell,tcslinearfresnel,tcstroughempirical,tcsgenericsolar,fresnelphysical", "System Costs", "?=0.0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "om_fuel_cost_escal", "Fuel cost escalation", "%/year", "generic_system,fuelcell,tcslinearfresnel,tcstroughempirical,tcsgenericsolar,fresnelphysical", "System Costs", "?=0.0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "annual_fuel_usage", "Fuel usage (yr 1)", "kWht", "generic_system,fuelcell,tcslinearfresnel,tcstroughempirical,tcsgenericsolar,fresnelphysical", "System Costs", "?=0", "MIN=0", "" }, + { SSC_INPUT, SSC_ARRAY, "annual_fuel_usage_lifetime", "Fuel usage (lifetime)", "kWht", "generic_system,fuelcell,tcslinearfresnel,tcstroughempirical,tcsgenericsolar,fresnelphysical", "System Costs", "", "", "" }, + + { SSC_INPUT, SSC_ARRAY, "om_elec_price_for_heat_techs", "Electricity price for purchases in heat model", "$/kWh", "", "System Costs", "?=0.0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "om_elec_price_for_heat_techs_escal", "Escalation for electricity price for purchases in heat model", "%/year", "", "System Costs", "?=0.0", "", "" }, + + + // replacements + { SSC_INPUT,SSC_ARRAY , "om_batt_replacement_cost" , "Replacement cost 1" , "$/kWh" , "battery" , "System Costs" , "?=0.0" , "" , ""}, + { SSC_INPUT,SSC_ARRAY , "om_fuelcell_replacement_cost" , "Replacement cost 2" , "$/kW", "fuelcell" , "System Costs" , "?=0.0" , "" , ""}, + { SSC_INPUT,SSC_NUMBER , "om_replacement_cost_escal" , "Replacement cost escalation" , "%/year", "battery,fuelcell" , "System Costs" , "?=0.0" , "" , ""}, + + // optional fuel o and m for Biopower - usage can be in any unit and cost is in $ per usage unit + { SSC_INPUT,SSC_NUMBER , "om_opt_fuel_1_usage" , "Biomass feedstock usage" , "unit" , "biomass" , "System Costs" , "?=0.0" , "" , ""}, + { SSC_INPUT,SSC_ARRAY , "om_opt_fuel_1_cost" , "Biomass feedstock cost" , "$/unit" , "biomass" , "System Costs" , "?=0.0" , "" , ""}, + { SSC_INPUT,SSC_NUMBER , "om_opt_fuel_1_cost_escal" , "Biomass feedstock cost escalation" , "%/year" , "biomass" , "System Costs" , "?=0.0" , "" , ""}, + { SSC_INPUT,SSC_NUMBER , "om_opt_fuel_2_usage" , "Coal feedstock usage" , "unit" , "biomass" , "System Costs" , "?=0.0" , "" , ""}, + { SSC_INPUT,SSC_ARRAY , "om_opt_fuel_2_cost" , "Coal feedstock cost" , "$/unit" , "biomass" , "System Costs" , "?=0.0" , "" , ""}, + { SSC_INPUT,SSC_NUMBER , "om_opt_fuel_2_cost_escal" , "Coal feedstock cost escalation" , "%/year" , "biomass" , "System Costs" , "?=0.0" , "" , ""}, + + // optional additional base o and m types + { SSC_INPUT,SSC_NUMBER , "add_om_num_types" , "Number of O and M types" , "" , "battery,fuelcell" , "System Costs" , "?=0" , "INTEGER,MIN=0,MAX=2" , ""}, + { SSC_INPUT,SSC_NUMBER , "om_batt_nameplate" , "Battery capacity for System Costs values" , "kW", "battery" , "System Costs" , "?=0" , "" , ""}, + { SSC_INPUT,SSC_ARRAY , "om_production1_values" , "Battery production for System Costs values" , "kWh", "battery" , "System Costs" , "?=0" , "" , ""}, + + { SSC_INPUT,SSC_ARRAY , "om_batt_fixed_cost" , "Battery fixed System Costs annual amount" , "$/year", "battery", "System Costs" , "?=0.0" , "" , ""}, + { SSC_INPUT,SSC_ARRAY , "om_batt_variable_cost" , "Battery production-based System Costs amount" , "$/MWh", "battery" , "System Costs" , "?=0.0" , "" , ""}, + { SSC_INPUT,SSC_ARRAY , "om_batt_capacity_cost" , "Battery capacity-based System Costs amount" , "$/kWcap", "battery" , "System Costs" , "?=0.0" , "" , ""}, + + { SSC_INPUT,SSC_NUMBER , "om_fuelcell_nameplate" , "Fuel cell capacity for System Costs values" , "kW", "fuelcell" , "System Costs" , "?=0" , "" , ""}, + { SSC_INPUT,SSC_ARRAY , "om_production2_values" , "Fuel cell production for System Costs values" , "kWh", "fuelcell" , "System Costs" , "?=0" , "" , ""}, + + { SSC_INPUT,SSC_ARRAY , "om_fuelcell_fixed_cost" , "Fuel cell fixed System Costs annual amount" , "$/year", "fuelcell" , "System Costs" , "?=0.0" , "" , ""}, + { SSC_INPUT,SSC_ARRAY , "om_fuelcell_variable_cost" , "Fuel cell production-based System Costs amount" , "$/MWh", "fuelcell" , "System Costs" , "?=0.0" , "" , ""}, + { SSC_INPUT,SSC_ARRAY , "om_fuelcell_capacity_cost" , "Fuel cell capacity-based System Costs amount" , "$/kWcap", "fuelcell" , "System Costs" , "?=0.0" , "" , ""}, + + // optional land lease + { SSC_INPUT, SSC_NUMBER, "land_area", "Total land area", "acres", "", "Land Lease", "?=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "om_land_lease", "Land lease cost", "$/acre", "", "Land Lease", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "om_land_lease_escal", "Land lease cost escalation", "%/yr", "", "Land Lease", "?=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_land_lease_expense", "Land lease expense", "$", "", "Land Lease", "", "LENGTH_EQUAL=cf_length", "" }, + +var_info_invalid }; + // meta should be either a blocklist (!gen,!gen) or an allowlist. Cannot do both blocked and allowed gens var_info vtab_oandm[] = { /* VARTYPE DATATYPE NAME LABEL UNITS META GROUP REQUIRED_IF CONSTRAINTS UI_HINTS*/ @@ -114,10 +170,6 @@ var_info vtab_oandm[] = { { SSC_INPUT, SSC_NUMBER, "annual_fuel_usage", "Fuel usage (yr 1)", "kWht", "generic_system,fuelcell,tcslinearfresnel,tcstroughempirical,tcsgenericsolar,fresnelphysical", "System Costs", "?=0", "MIN=0", "" }, { SSC_INPUT, SSC_ARRAY, "annual_fuel_usage_lifetime", "Fuel usage (lifetime)", "kWht", "generic_system,fuelcell,tcslinearfresnel,tcstroughempirical,tcsgenericsolar,fresnelphysical", "System Costs", "", "", "" }, -{ SSC_INPUT, SSC_ARRAY, "om_elec_price_for_heat_techs", "Electricity price for purchases in heat model", "$/kWh", "", "System Costs", "?=0.0", "", "" }, -{ SSC_INPUT, SSC_NUMBER, "om_elec_price_for_heat_techs_escal", "Escalation for electricity price for purchases in heat model", "%/year", "", "System Costs", "?=0.0", "", "" }, - - // replacements { SSC_INPUT,SSC_ARRAY , "om_batt_replacement_cost" , "Replacement cost 1" , "$/kWh" , "battery" , "System Costs" , "?=0.0" , "" , ""}, { SSC_INPUT,SSC_ARRAY , "om_fuelcell_replacement_cost" , "Replacement cost 2" , "$/kW", "fuelcell" , "System Costs" , "?=0.0" , "" , ""}, From c7c9eb053b8307d46b8996c800a831c8a0896775 Mon Sep 17 00:00:00 2001 From: Steven Janzou Date: Wed, 30 Oct 2024 04:48:08 -0600 Subject: [PATCH 53/85] Add additional heat outputs --- ssc/cmod_singleowner_heat.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ssc/cmod_singleowner_heat.cpp b/ssc/cmod_singleowner_heat.cpp index 13b3b0167..5e3bdd190 100644 --- a/ssc/cmod_singleowner_heat.cpp +++ b/ssc/cmod_singleowner_heat.cpp @@ -533,7 +533,8 @@ static var_info _cm_vtab_singleowner_heat[] = { { SSC_OUTPUT, SSC_ARRAY, "cf_energy_purchases", "Thermal energy from grid", "kWht", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_energy_without_battery", "Thermal energy generated without storage", "kWht", "", "Cash Flow Electricity", "", "LENGTH_EQUAL=cf_length", "" }, - { SSC_OUTPUT, SSC_ARRAY, "cf_ppa_price", "PPA price", "cents/kWh", "", "Cash Flow Revenues", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_ppa_price", "PPA price", "cents/kWht", "", "Cash Flow Revenues", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_ppa_price_heat_btu", "PPA price", "cents/MMBtu", "", "Cash Flow Revenues", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_energy_value", "PPA revenue", "$", "", "Cash Flow Revenues", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_thermal_value", "Thermal revenue", "$", "", "Cash Flow Revenues", "*", "LENGTH_EQUAL=cf_length", "" }, @@ -686,7 +687,8 @@ enum { CF_thermal_value, // CF_curtailment_value, // CF_capacity_payment, - CF_ppa_price, + CF_ppa_price, + CF_ppa_price_heat_btu, CF_om_fixed_expense, CF_om_production_expense, @@ -3457,7 +3459,11 @@ class cm_singleowner_heat : public compute_module // save_cf(CF_curtailment_value, nyears, "cf_curtailment_value"); // save_cf(CF_capacity_payment, nyears, "cf_capacity_payment"); // save_cf(CF_energy_curtailed, nyears, "cf_energy_curtailed"); - save_cf( CF_ppa_price, nyears, "cf_ppa_price" ); + const double MMBTU_TO_KWh = 293.07107; // 1 + save_cf(CF_ppa_price, nyears, "cf_ppa_price"); + for (size_t i = 0; i < nyears; i++) + cf.at(CF_ppa_price_heat_btu, i) = cf.at(CF_ppa_price, i) * MMBTU_TO_KWh; + save_cf( CF_ppa_price_heat_btu, nyears, "cf_ppa_price_heat_btu" ); save_cf( CF_om_fixed_expense, nyears, "cf_om_fixed_expense" ); save_cf( CF_om_production_expense, nyears, "cf_om_production_expense" ); save_cf( CF_om_capacity_expense, nyears, "cf_om_capacity_expense" ); From 80abf6ad9e7937a6b96055cec17cb2cc1d5e1c6d Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:40:55 -0600 Subject: [PATCH 54/85] Add monthly thermal rate options. --- ssc/cmod_thermalrate_iph.cpp | 60 +++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/ssc/cmod_thermalrate_iph.cpp b/ssc/cmod_thermalrate_iph.cpp index 7d53a2fb4..1b6e624aa 100644 --- a/ssc/cmod_thermalrate_iph.cpp +++ b/ssc/cmod_thermalrate_iph.cpp @@ -57,13 +57,15 @@ static var_info vtab_thermal_rate_iph[] = { { SSC_INPUT, SSC_ARRAY, "thermal_load_escalation", "Annual load escalation", "%/year", "", "Thermal Rate", "?=0", "", "" }, { SSC_INPUT, SSC_ARRAY, "thermal_rate_escalation", "Annual thermal rate escalation", "%/year", "", "Thermal Rate", "?=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "thermal_buy_rate_option", "Thermal buy rate option", "0/1", "0=flat,1=timestep", "Thermal Rate", "?=0", "INTEGER,MIN=0,MAX=1", "" }, - { SSC_INPUT, SSC_ARRAY, "thermal_buy_rate_heat_btu", "Thermal buy rate", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "thermal_buy_rate_flat_heat_btu", "Thermal buy rate flat", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "thermal_buy_rate_option", "Thermal buy rate option", "0-2", "0=flat,1=timestep,2=monthly", "Thermal Rate", "?=0", "INTEGER,MIN=0,MAX=2", "" }, + { SSC_INPUT, SSC_NUMBER, "thermal_buy_rate_flat_heat_btu", "Thermal buy rate flat", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "thermal_timestep_buy_rate_heat_btu", "Thermal buy rate", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "thermal_monthly_buy_rate_heat_btu", "Monthly thermal buy rate", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "thermal_sell_rate_option", "Thermal sell rate option", "0/1", "0=flat,1=timestep", "Thermal Rate", "?=0", "INTEGER,MIN=0,MAX=1", "" }, - { SSC_INPUT, SSC_ARRAY, "thermal_sell_rate_heat_btu", "Thermal sell rate", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "thermal_sell_rate_flat_heat_btu", "Thermal sell rate flat", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "thermal_sell_rate_option", "Thermal sell rate option", "0/1", "0=flat,1=timestep", "Thermal Rate", "?=0", "INTEGER,MIN=0,MAX=2", "" }, + { SSC_INPUT, SSC_NUMBER, "thermal_sell_rate_flat_heat_btu", "Thermal sell rate flat", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "thermal_timestep_sell_rate_heat_btu", "Thermal sell rate timestep","$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "thermal_monthly_sell_rate_heat_btu", "Thermal sell rate monthly", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "annual_thermal_value", "Thermal value", "$", "", "Annual", "*", "", "" }, @@ -246,11 +248,12 @@ class cm_thermalrate_iph : public compute_module size_t idx = 0; + // Timestep Buy Rate if (as_integer("thermal_buy_rate_option") == 1) { size_t nbuyrate,step_per_hour_br; ssc_number_t br; - pbuyrate = as_array("thermal_buy_rate_heat_btu", &nbuyrate); + pbuyrate = as_array("thermal_timestep_buy_rate_heat_btu", &nbuyrate); step_per_hour_br = nbuyrate / 8760; if (step_per_hour_br < 1 || step_per_hour_br > 60 || step_per_hour_br * 8760 != nbuyrate) throw exec_error("thermalrate", util::format("invalid number of buy rate records (%d): must be an integer multiple of 8760", (int)nbuyrate)); @@ -270,6 +273,26 @@ class cm_thermalrate_iph : public compute_module } } } + // Monthly Buy Rate + else if (as_integer("thermal_buy_rate_option") == 2) + { + std::vector br_monthly = as_vector_double("thermal_monthly_buy_rate_heat_btu"); + if (br_monthly.size() != 12) + { + throw exec_error("thermalrate", util::format("invalid number of monthly buy rate records (%d): must be equal to 12", (int)br_monthly.size())); + } + // Assign buy rate for every hour in each month + int hr_count = 0; + for (int month = 1; month <= 12; month++) + { + int hr_in_current_month = util::hours_in_month(month); + for (int hr_in_mnth = 0; hr_in_mnth < hr_in_current_month; hr_in_mnth++) + { + p_buyrate[hr_count] = br_monthly[month - 1]; + hr_count++; + } + } + } else // flat rate { ssc_number_t br = as_number("thermal_buy_rate_flat_heat_btu"); @@ -277,11 +300,12 @@ class cm_thermalrate_iph : public compute_module p_buyrate[i] = br; } + // Time step input if (as_integer("thermal_sell_rate_option") == 1) { size_t nsellrate, step_per_hour_br; ssc_number_t br; - psellrate = as_array("thermal_sell_rate_heat_btu", &nsellrate); + psellrate = as_array("thermal_timestep_sell_rate_heat_btu", &nsellrate); step_per_hour_br = nsellrate / 8760; if (step_per_hour_br < 1 || step_per_hour_br > 60 || step_per_hour_br * 8760 != nsellrate) throw exec_error("thermalrate", util::format("invalid number of sell rate records (%d): must be an integer multiple of 8760", (int)nsellrate)); @@ -301,6 +325,26 @@ class cm_thermalrate_iph : public compute_module } } } + // Monthly input + else if (as_integer("thermal_sell_rate_option") == 2) + { + std::vector sr_monthly = as_vector_double("thermal_monthly_sell_rate_heat_btu"); + if (sr_monthly.size() != 12) + { + throw exec_error("thermalrate", util::format("invalid number of monthly sell rate records (%d): must be equal to 12", (int)sr_monthly.size())); + } + // Assign sell rate for every hour in each month + int hr_count = 0; + for (int month = 1; month <= 12; month++) + { + int hr_in_current_month = util::hours_in_month(month); + for (int hr_in_mnth = 0; hr_in_mnth < hr_in_current_month; hr_in_mnth++) + { + p_sellrate[hr_count] = sr_monthly[month - 1]; + hr_count++; + } + } + } else // flat rate { ssc_number_t br = as_number("thermal_sell_rate_flat_heat_btu"); From 8fa9f2915efe42a2c5a52bda74dd2c82212097c9 Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:15:02 -0600 Subject: [PATCH 55/85] Add conversion efficiency to thermal buy rates. --- ssc/cmod_thermalrate_iph.cpp | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/ssc/cmod_thermalrate_iph.cpp b/ssc/cmod_thermalrate_iph.cpp index 1b6e624aa..19d869dbd 100644 --- a/ssc/cmod_thermalrate_iph.cpp +++ b/ssc/cmod_thermalrate_iph.cpp @@ -57,6 +57,9 @@ static var_info vtab_thermal_rate_iph[] = { { SSC_INPUT, SSC_ARRAY, "thermal_load_escalation", "Annual load escalation", "%/year", "", "Thermal Rate", "?=0", "", "" }, { SSC_INPUT, SSC_ARRAY, "thermal_rate_escalation", "Annual thermal rate escalation", "%/year", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "thermal_conversion_efficiency", "Heat conversion efficiency (buy)", "%", "", "Thermal Rate", "?=100", "", "" }, + + { SSC_INPUT, SSC_NUMBER, "thermal_buy_rate_option", "Thermal buy rate option", "0-2", "0=flat,1=timestep,2=monthly", "Thermal Rate", "?=0", "INTEGER,MIN=0,MAX=2", "" }, { SSC_INPUT, SSC_NUMBER, "thermal_buy_rate_flat_heat_btu", "Thermal buy rate flat", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, { SSC_INPUT, SSC_ARRAY, "thermal_timestep_buy_rate_heat_btu", "Thermal buy rate", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, @@ -210,7 +213,7 @@ class cm_thermalrate_iph : public compute_module nrec_gen_per_year = nrec_gen / nyears; step_per_hour_gen = nrec_gen_per_year / 8760; if (step_per_hour_gen < 1 || step_per_hour_gen > 60 || step_per_hour_gen * 8760 != nrec_gen_per_year) - throw exec_error("thermalrate", util::format("invalid number of thermal records (%d): must be an integer multiple of 8760", (int)nrec_gen_per_year)); + throw exec_error("thermalrate_iph", util::format("invalid number of thermal records (%d): must be an integer multiple of 8760", (int)nrec_gen_per_year)); ssc_number_t ts_hour_gen = 1.0f / step_per_hour_gen; m_num_rec_yearly = nrec_gen_per_year; @@ -226,9 +229,9 @@ class cm_thermalrate_iph : public compute_module step_per_hour_load = nrec_load / 8760; if (step_per_hour_load < 1 || step_per_hour_load > 60 || step_per_hour_load * 8760 != nrec_load) - throw exec_error("thermalrate", util::format("invalid number of load records (%d): must be an integer multiple of 8760", (int)nrec_load)); + throw exec_error("thermalrate_iph", util::format("invalid number of load records (%d): must be an integer multiple of 8760", (int)nrec_load)); if ((nrec_load != m_num_rec_yearly) && (nrec_load != 8760)) - throw exec_error("thermalrate", util::format("number of load records (%d) must be equal to number of gen records (%d) or 8760 for each year", (int)nrec_load, (int)m_num_rec_yearly)); + throw exec_error("thermalrate_iph", util::format("number of load records (%d) must be equal to number of gen records (%d) or 8760 for each year", (int)nrec_load, (int)m_num_rec_yearly)); } // ssc_number_t ts_hour_load = 1.0f / step_per_hour_load; @@ -248,6 +251,13 @@ class cm_thermalrate_iph : public compute_module size_t idx = 0; + // Get conversion efficiency + double eff_buy_frac = as_double("thermal_conversion_efficiency") / 100.0; // Bought heat conversion efficiency (converted to fraction) + if (eff_buy_frac <= 0) + { + throw exec_error("thermalrate_iph", "Conversion efficiency must be greater than 0"); + } + // Timestep Buy Rate if (as_integer("thermal_buy_rate_option") == 1) { @@ -256,16 +266,16 @@ class cm_thermalrate_iph : public compute_module pbuyrate = as_array("thermal_timestep_buy_rate_heat_btu", &nbuyrate); step_per_hour_br = nbuyrate / 8760; if (step_per_hour_br < 1 || step_per_hour_br > 60 || step_per_hour_br * 8760 != nbuyrate) - throw exec_error("thermalrate", util::format("invalid number of buy rate records (%d): must be an integer multiple of 8760", (int)nbuyrate)); + throw exec_error("thermalrate_iph", util::format("invalid number of buy rate records (%d): must be an integer multiple of 8760", (int)nbuyrate)); if ((nbuyrate != m_num_rec_yearly) && (nbuyrate != 8760)) - throw exec_error("thermalrate", util::format("number of buy rate records (%d) must be equal to number of gen records (%d) or 8760 for each year", (int)nbuyrate, (int)m_num_rec_yearly)); + throw exec_error("thermalrate_iph", util::format("number of buy rate records (%d) must be equal to number of gen records (%d) or 8760 for each year", (int)nbuyrate, (int)m_num_rec_yearly)); for (i = 0; i < 8760; i++) { for (size_t ii = 0; ii < step_per_hour_gen; ii++) { size_t ndx = i * step_per_hour_gen + ii; br = ((idx < nbuyrate) ? pbuyrate[idx] : 0); - p_buyrate[ndx] = br; + p_buyrate[ndx] = br / eff_buy_frac; // account for heat conversion efficiency if (step_per_hour_gen == step_per_hour_br) idx++; else if (ii == (step_per_hour_gen - 1)) @@ -279,7 +289,7 @@ class cm_thermalrate_iph : public compute_module std::vector br_monthly = as_vector_double("thermal_monthly_buy_rate_heat_btu"); if (br_monthly.size() != 12) { - throw exec_error("thermalrate", util::format("invalid number of monthly buy rate records (%d): must be equal to 12", (int)br_monthly.size())); + throw exec_error("thermalrate_iph", util::format("invalid number of monthly buy rate records (%d): must be equal to 12", (int)br_monthly.size())); } // Assign buy rate for every hour in each month int hr_count = 0; @@ -288,7 +298,7 @@ class cm_thermalrate_iph : public compute_module int hr_in_current_month = util::hours_in_month(month); for (int hr_in_mnth = 0; hr_in_mnth < hr_in_current_month; hr_in_mnth++) { - p_buyrate[hr_count] = br_monthly[month - 1]; + p_buyrate[hr_count] = br_monthly[month - 1] / eff_buy_frac; // account for heat conversion efficiency hr_count++; } } @@ -297,7 +307,7 @@ class cm_thermalrate_iph : public compute_module { ssc_number_t br = as_number("thermal_buy_rate_flat_heat_btu"); for (i = 0; i < m_num_rec_yearly; i++) - p_buyrate[i] = br; + p_buyrate[i] = br / eff_buy_frac; // account for heat conversion efficiency } // Time step input @@ -308,9 +318,9 @@ class cm_thermalrate_iph : public compute_module psellrate = as_array("thermal_timestep_sell_rate_heat_btu", &nsellrate); step_per_hour_br = nsellrate / 8760; if (step_per_hour_br < 1 || step_per_hour_br > 60 || step_per_hour_br * 8760 != nsellrate) - throw exec_error("thermalrate", util::format("invalid number of sell rate records (%d): must be an integer multiple of 8760", (int)nsellrate)); + throw exec_error("thermalrate_iph", util::format("invalid number of sell rate records (%d): must be an integer multiple of 8760", (int)nsellrate)); if ((nsellrate != m_num_rec_yearly) && (nsellrate != 8760)) - throw exec_error("thermalrate", util::format("number of sell rate records (%d) must be equal to number of gen records (%d) or 8760 for each year", (int)nsellrate, (int)m_num_rec_yearly)); + throw exec_error("thermalrate_iph", util::format("number of sell rate records (%d) must be equal to number of gen records (%d) or 8760 for each year", (int)nsellrate, (int)m_num_rec_yearly)); for (i = 0; i < 8760; i++) { for (size_t ii = 0; ii < step_per_hour_gen; ii++) @@ -331,7 +341,7 @@ class cm_thermalrate_iph : public compute_module std::vector sr_monthly = as_vector_double("thermal_monthly_sell_rate_heat_btu"); if (sr_monthly.size() != 12) { - throw exec_error("thermalrate", util::format("invalid number of monthly sell rate records (%d): must be equal to 12", (int)sr_monthly.size())); + throw exec_error("thermalrate_iph", util::format("invalid number of monthly sell rate records (%d): must be equal to 12", (int)sr_monthly.size())); } // Assign sell rate for every hour in each month int hr_count = 0; From 5e770796a48b54bd21edad3f84f8b1fe3d8674e5 Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:28:11 -0600 Subject: [PATCH 56/85] Update IPH direct steam linear fresnel cmod to work for single owner --- ssc/cmod_linear_fresnel_dsg_iph.cpp | 41 ++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/ssc/cmod_linear_fresnel_dsg_iph.cpp b/ssc/cmod_linear_fresnel_dsg_iph.cpp index ea81d53a0..3ed1b8111 100644 --- a/ssc/cmod_linear_fresnel_dsg_iph.cpp +++ b/ssc/cmod_linear_fresnel_dsg_iph.cpp @@ -127,7 +127,10 @@ static var_info _cm_vtab_linear_fresnel_dsg_iph[] = { // Heat Sink { SSC_INPUT, SSC_NUMBER, "heat_sink_dP_frac", "Fractional pressure drop through heat sink", "", "", "heat_sink", "*", "", "" }, - + + // Prices for heat purchases + { SSC_INPUT, SSC_ARRAY, "ppa_price_input_heat_btu", "PPA prices - yearly", "$/MMBtu", "", "Revenue", "", "", "" }, + // ************************************************************************************************* // OUTPUTS @@ -180,6 +183,9 @@ static var_info _cm_vtab_linear_fresnel_dsg_iph[] = { // SYSTEM { SSC_OUTPUT, SSC_ARRAY, "W_dot_parasitic_tot", "System total electrical parasitic", "MWe", "", "Controller", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "Total thermal power to grid w/ avail. derate", "kWt", "", "system", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen", "Total electric power to grid w/ avail. derate", "kWe", "", "system", "*", "", "" }, + // Controller { SSC_OUTPUT, SSC_ARRAY, "op_mode_1", "1st operating mode", "", "", "Controller", "*", "", "" }, @@ -195,6 +201,9 @@ static var_info _cm_vtab_linear_fresnel_dsg_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "kwh_per_kw", "First year kWh/kW", "kWht/kWt", "", "Post-process", "*", "", "" }, + // Financing + { SSC_OUTPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "","", "" }, + var_info_invalid }; @@ -206,11 +215,30 @@ class cm_linear_fresnel_dsg_iph : public compute_module { add_var_info(_cm_vtab_linear_fresnel_dsg_iph); add_var_info(vtab_adjustment_factors); - add_var_info(vtab_technology_outputs); + //add_var_info(vtab_technology_outputs); } void exec( ) { + // Convert IPH Input Units + { + const double MMBTU_TO_KWh = 293.07107; + if (is_assigned("ppa_price_input_heat_btu")) + { + size_t count_ppa_price_MMBTU_input; + ssc_number_t* ppa_price_MMBTU_input_array = as_array("ppa_price_input_heat_btu", &count_ppa_price_MMBTU_input); + std::vector ppa_price_input_vec; + for (int i = 0; i < count_ppa_price_MMBTU_input; i++) + { + ppa_price_input_vec.push_back(ppa_price_MMBTU_input_array[i] / MMBTU_TO_KWh); + } + int size = ppa_price_input_vec.size(); + ssc_number_t* alloc_vals = allocate("ppa_price_input", size); + for (int i = 0; i < size; i++) + alloc_vals[i] = ppa_price_input_vec[i]; // [] + } + } + // Weather reader C_csp_weatherreader weather_reader; if (is_assigned("file_name")) { @@ -586,15 +614,20 @@ class cm_linear_fresnel_dsg_iph : public compute_module if( !haf.setup(n_steps_fixed) ) throw exec_error("linear_fresnel_dsg_iph", "failed to setup adjustment factors: " + haf.error()); - ssc_number_t *p_gen = allocate("gen", n_steps_fixed); + ssc_number_t *p_gen_heat = allocate("gen_heat", n_steps_fixed); + ssc_number_t *p_gen = allocate("gen", n_steps_fixed); ssc_number_t *p_W_dot_par_tot_haf = allocate("W_dot_par_tot_haf", n_steps_fixed); ssc_number_t *p_W_dot_parasitic_tot = as_array("W_dot_parasitic_tot", &count); + ssc_number_t* p_load = allocate("load", n_steps_fixed); // testing using cmod_utilityrate5 for electricity rates p_load = p_W_dot_par_tot_haf + for( size_t i = 0; i < n_steps_fixed; i++ ) { size_t hour = (size_t)ceil(p_time_final_hr[i]); - p_gen[i] = p_q_dot_heat_sink[i] * (ssc_number_t)(haf(hour) * 1.E3); //[kWt] + p_gen_heat[i] = p_q_dot_heat_sink[i] * (ssc_number_t)(haf(hour) * 1.E3); //[kWt] + p_gen[i] = (ssc_number_t)0.0; //[kWt] (no electrical generation for direct steam linear fresnel IPH) p_W_dot_parasitic_tot[i] *= -1.0; //[kWe] Label is total parasitics, so change to a positive value p_W_dot_par_tot_haf[i] = (ssc_number_t)(p_W_dot_parasitic_tot[i] * haf(hour) * 1.E3); //[kWe] + p_load[i] = p_W_dot_par_tot_haf[i]; } ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour); From afa33b9c90491d4986f20bc9dfefd4ff6447837b Mon Sep 17 00:00:00 2001 From: Steven Janzou Date: Thu, 31 Oct 2024 04:31:34 -0600 Subject: [PATCH 57/85] Update heatmaps for all iph technologies --- ssc/cmod_fresnel_physical_iph.cpp | 2 +- ssc/cmod_linear_fresnel_dsg_iph.cpp | 4 ++-- ssc/cmod_mspt_iph.cpp | 2 +- ssc/cmod_trough_physical_iph.cpp | 4 ++-- ssc/common.cpp | 9 +++++++-- ssc/common.h | 2 +- 6 files changed, 14 insertions(+), 9 deletions(-) diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 1a5243c20..8cc0f2f7e 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -1625,7 +1625,7 @@ class cm_fresnel_physical_iph : public compute_module // Do unit post-processing here - ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour); + ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour, true); // Non-timeseries array outputs double P_adj = storage.P_in_des; // slightly adjust all field design pressures to account for pressure drop in TES before hot tank transform(c_fresnel.m_P_rnr_dsn.begin(), c_fresnel.m_P_rnr_dsn.end(), c_fresnel.m_P_rnr_dsn.begin(), [P_adj](double x) {return x + P_adj; }); diff --git a/ssc/cmod_linear_fresnel_dsg_iph.cpp b/ssc/cmod_linear_fresnel_dsg_iph.cpp index 3ed1b8111..ead00c0ba 100644 --- a/ssc/cmod_linear_fresnel_dsg_iph.cpp +++ b/ssc/cmod_linear_fresnel_dsg_iph.cpp @@ -215,7 +215,7 @@ class cm_linear_fresnel_dsg_iph : public compute_module { add_var_info(_cm_vtab_linear_fresnel_dsg_iph); add_var_info(vtab_adjustment_factors); - //add_var_info(vtab_technology_outputs); + add_var_info(vtab_technology_outputs); } void exec( ) @@ -630,7 +630,7 @@ class cm_linear_fresnel_dsg_iph : public compute_module p_load[i] = p_W_dot_par_tot_haf[i]; } - ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour); + ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour, true); accumulate_annual_for_year("gen", "annual_field_energy", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWt-hr] diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index 6769aabeb..427625c28 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -2327,7 +2327,7 @@ class cm_mspt_iph : public compute_module p_load[i] = p_W_dot_par_tot_haf[i]; } - ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour); + ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour, true); accumulate_annual_for_year("gen_heat", "annual_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[kWt-hr] diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 9340040c7..df6f36b52 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -740,7 +740,7 @@ class cm_trough_physical_iph : public compute_module { add_var_info( _cm_vtab_trough_physical_iph ); add_var_info( vtab_adjustment_factors ); - //add_var_info(vtab_technology_outputs); + add_var_info(vtab_technology_outputs); add_var_info(vtab_utility_rate_common); // Required for dispatch w/ utility rates } @@ -2276,7 +2276,7 @@ class cm_trough_physical_iph : public compute_module annual_elec_cost += i_elec_cost; //[$] } - ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour); + ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour, true); // Non-timeseries array outputs double P_adj = 0; // slightly adjust all field design pressures to account for pressure drop in TES before hot tank if (tes_type == C_csp_tes::csp_tes_types::E_TES_TWO_TANK) diff --git a/ssc/common.cpp b/ssc/common.cpp index 2cd726697..791aa1d59 100644 --- a/ssc/common.cpp +++ b/ssc/common.cpp @@ -649,7 +649,7 @@ var_info vtab_technology_outputs[] = { var_info_invalid }; -ssc_number_t* gen_heatmap(compute_module* cm, double step_per_hour) { +ssc_number_t* gen_heatmap(compute_module* cm, double step_per_hour, bool heat) { if (!cm) return 0; size_t count = (size_t)(8760 * step_per_hour); @@ -657,7 +657,12 @@ ssc_number_t* gen_heatmap(compute_module* cm, double step_per_hour) { size_t iday = 0; size_t hour; size_t count_gen; - ssc_number_t* p_gen = cm->as_array("gen", &count_gen); + ssc_number_t* p_gen = NULL; + if (heat) + p_gen = cm->as_array("gen_heat", &count_gen); + else + p_gen = cm->as_array("gen", &count_gen); + ssc_number_t* p_annual_energy_dist_time = cm->allocate("annual_energy_distribution_time", 25, 366); for (size_t i = 0; i < count; i++) { hour = (size_t)fmod(floor(double(i) / step_per_hour), 24); diff --git a/ssc/common.h b/ssc/common.h index 750a1ca8e..211f6bc9f 100644 --- a/ssc/common.h +++ b/ssc/common.h @@ -75,7 +75,7 @@ bool calculate_p50p90(compute_module *cm); void calculate_resilience_outputs(compute_module *cm, std::unique_ptr &resilience); -ssc_number_t* gen_heatmap(compute_module* cm, double step_per_hour); +ssc_number_t* gen_heatmap(compute_module* cm, double step_per_hour, bool heat=false); void prepend_to_output(compute_module* cm, std::string var_name, size_t count, ssc_number_t value); From 7ad514d2578e3919c8ae56e8b140ad550e4396b5 Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:37:07 -0600 Subject: [PATCH 58/85] Add nameplate capacity outputs to IPH fresnel direct steam. --- ssc/cmod_linear_fresnel_dsg_iph.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ssc/cmod_linear_fresnel_dsg_iph.cpp b/ssc/cmod_linear_fresnel_dsg_iph.cpp index ead00c0ba..7d59a6ce5 100644 --- a/ssc/cmod_linear_fresnel_dsg_iph.cpp +++ b/ssc/cmod_linear_fresnel_dsg_iph.cpp @@ -202,7 +202,12 @@ static var_info _cm_vtab_linear_fresnel_dsg_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "kwh_per_kw", "First year kWh/kW", "kWht/kWt", "", "Post-process", "*", "", "" }, // Financing - { SSC_OUTPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "","", "" }, + { SSC_OUTPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "", "", "" }, + + // System capacity required by downstream financial model + { SSC_OUTPUT, SSC_NUMBER, "nameplate", "Nameplate capacity", "MWt", "", "System Design", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "system_capacity", "System capacity", "kWt", "", "System Design", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "cp_system_nameplate", "System capacity for capacity payments", "MWt", "", "System Design", "*", "", "" }, var_info_invalid }; @@ -651,6 +656,10 @@ class cm_linear_fresnel_dsg_iph : public compute_module double kWh_per_kW = ae / nameplate; assign("capacity_factor", (ssc_number_t)(kWh_per_kW / 8760. * 100.)); assign("kwh_per_kw", (ssc_number_t)kWh_per_kW); + + assign("nameplate", nameplate * 1.e-3); //[MWt] + assign("system_capacity", nameplate); //[kWt] + assign("cp_system_nameplate", nameplate * 1.e-3); //[MWt] } }; From 1a1d21a4178f05d881447914dbc51100d391cfbf Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu, 31 Oct 2024 18:59:39 -0600 Subject: [PATCH 59/85] Fix bug with annual_field_energy. --- ssc/cmod_linear_fresnel_dsg_iph.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssc/cmod_linear_fresnel_dsg_iph.cpp b/ssc/cmod_linear_fresnel_dsg_iph.cpp index 7d59a6ce5..e5440a758 100644 --- a/ssc/cmod_linear_fresnel_dsg_iph.cpp +++ b/ssc/cmod_linear_fresnel_dsg_iph.cpp @@ -638,7 +638,7 @@ class cm_linear_fresnel_dsg_iph : public compute_module ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour, true); - accumulate_annual_for_year("gen", "annual_field_energy", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWt-hr] + accumulate_annual_for_year("gen_heat", "annual_field_energy", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWt-hr] accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWe-hr] accumulate_annual_for_year("q_dot_freeze_prot", "annual_thermal_consumption", sim_setup.m_report_step/3600.0*1.E3, steps_per_hour); //[kWt-hr] From 291788497517da7e2980402f07e04e48164ababd Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu, 31 Oct 2024 19:39:36 -0600 Subject: [PATCH 60/85] Align IPH tech cmod kWht and kWhe naming convention. --- ssc/cmod_fresnel_physical_iph.cpp | 24 ++++++++++++------------ ssc/cmod_linear_fresnel_dsg_iph.cpp | 22 +++++++++++----------- ssc/cmod_mspt_iph.cpp | 16 ++++++++-------- ssc/cmod_trough_physical_iph.cpp | 20 ++++++++++---------- 4 files changed, 41 insertions(+), 41 deletions(-) diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 8cc0f2f7e..5037047ed 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -93,7 +93,7 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "T_startup", "Power block startup temperature", "C", "", "Solar_Field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "rec_su_delay", "Fixed startup delay time for the receiver", "hr", "", "Solar_Field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "rec_qf_delay", "Energy-based receiver startup delay (fraction of rated thermal power)", "-", "", "Solar_Field", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "p_start", "Collector startup energy, per SCA", "kWe-hr", "", "Solar_Field", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "p_start", "Collector startup energy, per SCA", "kWhe", "", "Solar_Field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "L_rnr_pb", "Length of runner pipe in power block", "m", "", "Solar_Field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "use_abs_or_rel_mdot_limit", "Use mass flow abs (0) or relative (1) limits", "", "", "solar_field", "?=0", "", "" }, @@ -202,7 +202,7 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "disp_inventory_incentive", "Dispatch storage terminal inventory incentive multiplier", "", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER" }, // Receiver control - /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWe-hr", "", "tou", "?=0.0", "", "SIMULATION_PARAMETER" }, + /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWhe", "", "tou", "?=0.0", "", "SIMULATION_PARAMETER" }, /*LK Only*/{ SSC_INPUT, SSC_NUMBER, "q_rec_standby", "Receiver standby energy consumption", "kWt", "", "tou", "?=9e99", "", "SIMULATION_PARAMETER" }, @@ -570,12 +570,12 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "monthly_energy", "Monthly Energy", "kWh", "", "Post-process", "sim_type=1", "LENGTH=12", "" }, // Annual Outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Net Electrical Energy Production w/ avail derate", "kWe-hr", "", "Post-process", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption w/ avail derate", "kWe-hr", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Net Electrical Energy Production w/ avail derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWht", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption w/ avail derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_total_water_use", "Total Annual Water Usage", "m^3", "", "Post-process", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_field_freeze_protection", "Annual thermal power for field freeze protection", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_tes_freeze_protection", "Annual thermal power for TES freeze protection", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_field_freeze_protection", "Annual thermal power for field freeze protection", "kWht", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_tes_freeze_protection", "Annual thermal power for TES freeze protection", "kWht", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "kwh_per_kw", "First year kWh/kW", "kWh/kW", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "sim_duration", "Computational time of timeseries simulation", "s", "", "system", "sim_type=1", "", "" }, @@ -1587,15 +1587,15 @@ class cm_fresnel_physical_iph : public compute_module accumulate_annual_for_year("gen_heat", "annual_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); // This term currently includes TES freeze protection - accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWe-hr] + accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWhe] double V_water_mirrors = as_double("water_per_wash") / 1000.0 * c_fresnel.m_Ap_tot * as_double("washes_per_year"); assign("annual_total_water_use", (ssc_number_t)V_water_mirrors); - ssc_number_t annual_field_fp = accumulate_annual_for_year("q_dot_freeze_prot", "annual_field_freeze_protection", sim_setup.m_report_step / 3600.0 * 1.E3, steps_per_hour); //[kWt-hr] - ssc_number_t annual_tes_fp = accumulate_annual_for_year("q_tes_heater", "annual_tes_freeze_protection", sim_setup.m_report_step / 3600.0 * 1.E3, steps_per_hour); //[kWt-hr] + ssc_number_t annual_field_fp = accumulate_annual_for_year("q_dot_freeze_prot", "annual_field_freeze_protection", sim_setup.m_report_step / 3600.0 * 1.E3, steps_per_hour); //[kWht] + ssc_number_t annual_tes_fp = accumulate_annual_for_year("q_tes_heater", "annual_tes_freeze_protection", sim_setup.m_report_step / 3600.0 * 1.E3, steps_per_hour); //[kWht] - ssc_number_t annual_thermal_consumption = annual_field_fp + annual_tes_fp; //[kWt-hr] + ssc_number_t annual_thermal_consumption = annual_field_fp + annual_tes_fp; //[kWht] assign("annual_thermal_consumption", annual_thermal_consumption); // Reporting dispatch solution counts @@ -1612,7 +1612,7 @@ class cm_fresnel_physical_iph : public compute_module assign("avg_suboptimal_rel_mip_gap", (ssc_number_t)avg_gap); - ssc_number_t ae = as_number("annual_energy"); //[kWt-hr] + ssc_number_t ae = as_number("annual_energy"); //[kWht] double kWh_per_kW = ae / (nameplate*1.E3); // convert nameplate to kW assign("capacity_factor", (ssc_number_t)(kWh_per_kW / 8760. * 100.)); assign("kwh_per_kw", (ssc_number_t)kWh_per_kW); diff --git a/ssc/cmod_linear_fresnel_dsg_iph.cpp b/ssc/cmod_linear_fresnel_dsg_iph.cpp index e5440a758..c2c7f40d0 100644 --- a/ssc/cmod_linear_fresnel_dsg_iph.cpp +++ b/ssc/cmod_linear_fresnel_dsg_iph.cpp @@ -193,10 +193,10 @@ static var_info _cm_vtab_linear_fresnel_dsg_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "op_mode_3", "3rd op. mode, if applicable", "", "", "Controller", "*", "", "" }, // Annual Outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Net Thermal Energy Production w/ avail derate", "kWt-hr", "", "Post-process", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_field_energy", "Annual Gross Thermal Energy Production w/ avail derate", "kWt-hr", "", "Post-process", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWt-hr", "", "Post-process", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumptoin w/ avail derate", "kWe-hr", "", "Post-process", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Net Thermal Energy Production w/ avail derate", "kWht", "", "Post-process", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_field_energy", "Annual Gross Thermal Energy Production w/ avail derate", "kWht", "", "Post-process", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWht", "", "Post-process", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumptoin w/ avail derate", "kWhe", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_total_water_use", "Total Annual Water Usage", "m^3", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "kwh_per_kw", "First year kWh/kW", "kWht/kWt", "", "Post-process", "*", "", "" }, @@ -638,20 +638,20 @@ class cm_linear_fresnel_dsg_iph : public compute_module ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour, true); - accumulate_annual_for_year("gen_heat", "annual_field_energy", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWt-hr] - accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWe-hr] - accumulate_annual_for_year("q_dot_freeze_prot", "annual_thermal_consumption", sim_setup.m_report_step/3600.0*1.E3, steps_per_hour); //[kWt-hr] + accumulate_annual_for_year("gen_heat", "annual_field_energy", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWht] + accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWhe] + accumulate_annual_for_year("q_dot_freeze_prot", "annual_thermal_consumption", sim_setup.m_report_step/3600.0*1.E3, steps_per_hour); //[kWht] - ssc_number_t annual_field_energy = as_number("annual_field_energy"); //[kWt-hr] - ssc_number_t annual_thermal_consumption = as_number("annual_thermal_consumption"); //[kWt-hr] - assign("annual_energy", annual_field_energy - annual_thermal_consumption); //[kWt-hr] + ssc_number_t annual_field_energy = as_number("annual_field_energy"); //[kWht] + ssc_number_t annual_thermal_consumption = as_number("annual_thermal_consumption"); //[kWht] + assign("annual_energy", annual_field_energy - annual_thermal_consumption); //[kWht] // Calculate water use double A_aper_tot = csp_solver.get_cr_aperture_area(); //[m2] double V_water_mirrors = as_double("csp.lf.sf.water_per_wash") / 1000.0*A_aper_tot*as_double("csp.lf.sf.washes_per_year"); assign("annual_total_water_use", (ssc_number_t)V_water_mirrors); //[m3] - ssc_number_t ae = as_number("annual_energy"); //[kWt-hr] + ssc_number_t ae = as_number("annual_energy"); //[kWht] double nameplate = as_double("q_pb_des") * 1.e3; //[kWt] double kWh_per_kW = ae / nameplate; assign("capacity_factor", (ssc_number_t)(kWh_per_kW / 8760. * 100.)); diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index 427625c28..fd2d60115 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -98,7 +98,7 @@ static var_info _cm_vtab_mspt_iph[] = { { SSC_INPUT, SSC_NUMBER, "land_min", "Land min boundary", "-ORm", "", "Heliostat Field", "?=0.75", "", ""}, { SSC_INPUT, SSC_MATRIX, "land_bound_table", "Land boundary table", "m", "", "Heliostat Field", "?", "", "SIMULATION_PARAMETER"}, { SSC_INPUT, SSC_ARRAY, "land_bound_list", "Land boundary table listing", "", "", "Heliostat Field", "?", "", "SIMULATION_PARAMETER"}, - { SSC_INPUT, SSC_NUMBER, "p_start", "Heliostat startup energy", "kWe-hr", "", "Heliostat Field", "*", "", ""}, + { SSC_INPUT, SSC_NUMBER, "p_start", "Heliostat startup energy", "kWhe", "", "Heliostat Field", "*", "", ""}, { SSC_INPUT, SSC_NUMBER, "p_track", "Heliostat tracking energy", "kWe", "", "Heliostat Field", "*", "", ""}, { SSC_INPUT, SSC_NUMBER, "hel_stow_deploy", "Stow/deploy elevation angle", "deg", "", "Heliostat Field", "*", "", ""}, { SSC_INPUT, SSC_NUMBER, "v_wind_max", "Heliostat max wind velocity", "m/s", "", "Heliostat Field", "*", "", ""}, @@ -247,7 +247,7 @@ static var_info _cm_vtab_mspt_iph[] = { // Receiver control { SSC_INPUT, SSC_NUMBER, "q_rec_standby", "Receiver standby energy consumption", "kWt", "", "System Control", "?=9e99", "", "SIMULATION_PARAMETER"}, -{ SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWe-hr", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER"}, +{ SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWhe", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER"}, // System Control // Required if dispatch @@ -605,11 +605,11 @@ static var_info _cm_vtab_mspt_iph[] = { // Annual single-value outputs -{ SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Thermal Energy to Heat Sink w/ avail derate", "kWt-hr", "", "Post-process", "sim_type=1", "", ""}, +{ SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Thermal Energy to Heat Sink w/ avail derate", "kWht", "", "Post-process", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_htf", "Annual receiver power delivered to HTF", "MWt-hr", "", "Tower and Receiver", "sim_type=1", "", ""}, -{ SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption w/ avail derate", "kWe-hr", "", "Post-process", "sim_type=1", "", ""}, +{ SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption w/ avail derate", "kWhe", "", "Post-process", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "Post-process", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_NUMBER, "kwh_per_kw", "First year kWh/kW", "kWth/kWt", "", "Post-process", "sim_type=1", "", ""}, @@ -1411,7 +1411,7 @@ class cm_mspt_iph : public compute_module // so it can use the active receiver area C_pt_sf_perf_interp heliostatfield(A_rec); - heliostatfield.ms_params.m_p_start = as_double("p_start"); //[kWe-hr] Heliostat startup energy + heliostatfield.ms_params.m_p_start = as_double("p_start"); //[kWhe] Heliostat startup energy heliostatfield.ms_params.m_p_track = as_double("p_track"); //[kWe] Heliostat tracking power heliostatfield.ms_params.m_hel_stow_deploy = as_double("hel_stow_deploy"); // N/A heliostatfield.ms_params.m_v_wind_max = as_double("v_wind_max"); // N/A @@ -2329,15 +2329,15 @@ class cm_mspt_iph : public compute_module ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour, true); - accumulate_annual_for_year("gen_heat", "annual_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[kWt-hr] + accumulate_annual_for_year("gen_heat", "annual_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[kWht] // This term currently includes TES freeze protection - accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWe-hr] + accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWhe] double V_water_mirrors = as_double("water_usage_per_wash") / 1000.0 * A_sf * as_double("washing_frequency"); assign("annual_total_water_use", (ssc_number_t) V_water_mirrors); - ssc_number_t ae = as_number("annual_energy"); //[kWt-hr] + ssc_number_t ae = as_number("annual_energy"); //[kWht] double nameplate = q_dot_pc_des * 1.E3; //[kWt] double kWh_per_kW = ae / nameplate; assign("capacity_factor", (ssc_number_t)(kWh_per_kW / 8760. * 100.)); diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index df6f36b52..005666fc6 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -170,7 +170,7 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_MATRIX, "Design_loss", "Receiver heat loss at design", "W/m", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "rec_su_delay", "Fixed startup delay time for the receiver", "hr", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "rec_qf_delay", "Energy-based receiver startup delay (fraction of rated thermal power)", "-", "", "solar_field", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "p_start", "Collector startup energy, per SCA", "kWe-hr", "", "solar_field", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "p_start", "Collector startup energy, per SCA", "kWhe", "", "solar_field", "*", "", "" }, // Heat Sink { SSC_INPUT, SSC_NUMBER, "pb_pump_coef", "Pumping power to move 1kg of HTF through PB loop", "kW/kg", "", "Heat Sink", "*", "", "" }, @@ -642,10 +642,10 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "monthly_energy", "Monthly Energy Gross", "kWh", "", "Post-process", "sim_type=1", "LENGTH=12", "" }, // Annual Outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Net Electrical Energy Production w/ avail derate", "kWe-hr", "", "Post-process", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_NUMBER, "annual_gross_energy", "Annual Gross Electrical Energy Production w/ avail derate", "kWe-hr", "", "Post-process", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Net Electrical Energy Production w/ avail derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, + //{ SSC_OUTPUT, SSC_NUMBER, "annual_gross_energy", "Annual Gross Electrical Energy Production w/ avail derate", "kWhe", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption w/ avail derate", "kWe-hr", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption w/ avail derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_total_water_use", "Total Annual Water Usage", "m^3", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_field_freeze_protection", "Annual thermal power for field freeze protection", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_tes_freeze_protection", "Annual thermal power for TES freeze protection", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, @@ -725,7 +725,7 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_P_dsn", "Pressure in TES pipes at design conditions", "bar", "", "TES", "sim_type=1&tes_type=0", "", "" }, //{ SSC_OUTPUT, SSC_ARRAY, "defocus", "Field optical focus fraction", "", "", "solver", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "electricity_rate", "Electricity price = calculated annual elec cost / total elec energy consusmed", "$/kWe-hr", "", "Post-process", "sim_type=1&csp_financial_model=7", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "electricity_rate", "Electricity price = calculated annual elec cost / total elec energy consusmed", "$/kWhe", "", "Post-process", "sim_type=1&csp_financial_model=7", "", "" }, var_info_invalid }; @@ -1066,7 +1066,7 @@ class cm_trough_physical_iph : public compute_module c_trough.m_Design_loss = as_matrix("Design_loss"); //[-] Receiver heat loss at design c_trough.m_rec_su_delay = as_double("rec_su_delay"); //[hr] Fixed startup delay time for the receiver c_trough.m_rec_qf_delay = as_double("rec_qf_delay"); //[-] Energy-based receiver startup delay (fraction of rated thermal power) - c_trough.m_p_start = as_double("p_start"); //[kWe-hr] Collector startup energy, per SCA + c_trough.m_p_start = as_double("p_start"); //[kWhe] Collector startup energy, per SCA c_trough.m_calc_design_pipe_vals = as_boolean("calc_design_pipe_vals"); //[-] Should the HTF state be calculated at design conditions c_trough.m_L_rnr_pb = as_double("L_rnr_pb"); //[m] Length of hot or cold runner pipe around the power block c_trough.m_N_max_hdr_diams = as_double("N_max_hdr_diams"); //[-] Maximum number of allowed diameters in each of the hot and cold headers @@ -1616,7 +1616,7 @@ class cm_trough_physical_iph : public compute_module C_csp_tou tou(offtaker_schedule, elec_pricing_schedule, dispatch_model_type, is_offtaker_frac_also_max); // Placeholder for heat price schedule - double heat_price = 0.02; //[$/kWh-t] + double heat_price = 0.02; //[$/kWht] C_timeseries_schedule_inputs heat_pricing_schedule = C_timeseries_schedule_inputs(1.0, heat_price); tou.mc_heat_pricing_schedule = heat_pricing_schedule; @@ -2361,10 +2361,10 @@ class cm_trough_physical_iph : public compute_module accumulate_annual_for_year("q_ch_tes", "annual_q_ch_tes", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); // This term currently includes TES freeze protection - accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWe-hr] + accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWhe] - double annual_electricity_consumption = as_double("annual_electricity_consumption"); //[kWe-hr] - double electricity_rate_calc = annual_elec_cost / annual_electricity_consumption; //[$/kWe-hr] + double annual_electricity_consumption = as_double("annual_electricity_consumption"); //[kWhe] + double electricity_rate_calc = annual_elec_cost / annual_electricity_consumption; //[$/kWhe] assign("electricity_rate", electricity_rate_calc); ssc_number_t annual_field_fp = accumulate_annual_for_year("q_dot_freeze_prot", "annual_field_freeze_protection", sim_setup.m_report_step / 3600.0*1.E3, steps_per_hour); //[kWt-hr] From bb61128fbdf6649ed59cd93e5b6a15a6701d24be Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:55:53 -0600 Subject: [PATCH 61/85] Add MMBtu energy outputs to IPH tech cmods for display. --- ssc/cmod_fresnel_physical_iph.cpp | 13 ++++++++-- ssc/cmod_linear_fresnel_dsg_iph.cpp | 35 ++++++++++++++++----------- ssc/cmod_mspt_iph.cpp | 37 ++++++++++++++++------------- ssc/cmod_trough_physical_iph.cpp | 28 ++++++++++++++-------- 4 files changed, 71 insertions(+), 42 deletions(-) diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 5037047ed..b2a4c1f6e 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -564,13 +564,17 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "operating_modes_c", "Final 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "Total thermal power to heat sink with available derate", "kWe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "gen", "Total electric power to grid w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "Total thermal power to heat sink with available derate in MMBtu/hr", "MMBtu/hr", "", "system", "sim_type=1", "", "" }, // Monthly Outputs { SSC_OUTPUT, SSC_ARRAY, "monthly_energy", "Monthly Energy", "kWh", "", "Post-process", "sim_type=1", "LENGTH=12", "" }, + { SSC_OUTPUT, SSC_ARRAY, "monthly_energy_heat_btu", "Monthly Energy in MMBtu", "MMBtu", "", "Post-process", "sim_type=1", "LENGTH=12", "" }, + // Annual Outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Net Electrical Energy Production w/ avail derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Net Thermal Energy Production w/ avail derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual Net Thermal Energy Production w/ avail derate in MMBtu", "MMBtu", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWht", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption w/ avail derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_total_water_use", "Total Annual Water Usage", "m^3", "", "Post-process", "sim_type=1", "", "" }, @@ -612,10 +616,10 @@ class cm_fresnel_physical_iph : public compute_module double tshours = as_double("tshours"); //[-] double q_dot_pc_des = as_double("q_pb_design"); //[MWt] HEAT SINK design thermal power double Q_tes = q_dot_pc_des * tshours; //[MWt-hr] + const double MMBTU_TO_KWh = 293.07107; // 1 MMBtu = 293.07107 kWh // Convert IPH Input Units { - const double MMBTU_TO_KWh = 293.07107; // 1 MMBtu = 293.07107 kWh if (is_assigned("ppa_price_input_heat_btu")) { size_t count_ppa_price_MMBTU_input; @@ -1556,6 +1560,7 @@ class cm_fresnel_physical_iph : public compute_module ssc_number_t* p_gen_heat = allocate("gen_heat", n_steps_fixed); ssc_number_t* p_gen = allocate("gen", n_steps_fixed); + ssc_number_t* p_gen_heat_btu = allocate("gen_heat_btu", n_steps_fixed); ssc_number_t* p_q_dot_defocus_est = allocate("q_dot_defocus_est", n_steps_fixed); ssc_number_t* p_SCAs_def = as_array("SCAs_def", &count); if ((int)count != n_steps_fixed) @@ -1574,6 +1579,7 @@ class cm_fresnel_physical_iph : public compute_module size_t hour = (size_t)ceil(p_time_final_hr[i]); p_gen_heat[i] = (ssc_number_t)(p_q_dot_heat_sink[i] * haf(hour) * 1.E3); //[kWe] p_gen[i] = (ssc_number_t)0.0; //[kWt] (no electrical generation for IPH mslf) + p_gen_heat_btu[i] = p_gen_heat[i] / MMBTU_TO_KWh; //[MMBtu/hr] p_q_dot_defocus_est[i] = (ssc_number_t)(1.0 - p_SCAs_def[i]) * p_q_dot_htf_sf_out[i]; //[MWt] p_W_dot_parasitic_tot[i] *= -1.0; //[MWe] Label is total parasitics, so change to a positive value p_W_dot_par_tot_haf[i] = (ssc_number_t)(p_W_dot_parasitic_tot[i] * haf(hour) * 1.E3); //[kWe] apply availability derate and convert from MWe @@ -1582,9 +1588,12 @@ class cm_fresnel_physical_iph : public compute_module // Monthly outputs accumulate_monthly_for_year("gen_heat", "monthly_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1); + accumulate_monthly_for_year("gen_heat_btu", "monthly_energy_heat_btu", sim_setup.m_report_step / 3600.0, steps_per_hour, 1); // Annual outputs accumulate_annual_for_year("gen_heat", "annual_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); + accumulate_annual_for_year("gen_heat_btu", "annual_energy_heat_btu", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); + // This term currently includes TES freeze protection accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWhe] diff --git a/ssc/cmod_linear_fresnel_dsg_iph.cpp b/ssc/cmod_linear_fresnel_dsg_iph.cpp index c2c7f40d0..71b059db5 100644 --- a/ssc/cmod_linear_fresnel_dsg_iph.cpp +++ b/ssc/cmod_linear_fresnel_dsg_iph.cpp @@ -185,6 +185,7 @@ static var_info _cm_vtab_linear_fresnel_dsg_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "W_dot_parasitic_tot", "System total electrical parasitic", "MWe", "", "Controller", "*", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "Total thermal power to grid w/ avail. derate", "kWt", "", "system", "*", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "gen", "Total electric power to grid w/ avail. derate", "kWe", "", "system", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "Total thermal power to grid w/ avail. derate in MMBtu/hr","MMBtu/hr","","system", "*", "", "" }, // Controller @@ -194,7 +195,8 @@ static var_info _cm_vtab_linear_fresnel_dsg_iph[] = { // Annual Outputs { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Net Thermal Energy Production w/ avail derate", "kWht", "", "Post-process", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_field_energy", "Annual Gross Thermal Energy Production w/ avail derate", "kWht", "", "Post-process", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual Net Thermal Energy Production w/ avail derate in MMBtu", "MMBtu","","Post - process", "", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_field_energy", "Annual Gross Thermal Energy Production w/ avail derate", "kWht", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWht", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumptoin w/ avail derate", "kWhe", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_total_water_use", "Total Annual Water Usage", "m^3", "", "Post-process", "*", "", "" }, @@ -226,23 +228,22 @@ class cm_linear_fresnel_dsg_iph : public compute_module void exec( ) { // Convert IPH Input Units + const double MMBTU_TO_KWh = 293.07107; + if (is_assigned("ppa_price_input_heat_btu")) { - const double MMBTU_TO_KWh = 293.07107; - if (is_assigned("ppa_price_input_heat_btu")) + size_t count_ppa_price_MMBTU_input; + ssc_number_t* ppa_price_MMBTU_input_array = as_array("ppa_price_input_heat_btu", &count_ppa_price_MMBTU_input); + std::vector ppa_price_input_vec; + for (int i = 0; i < count_ppa_price_MMBTU_input; i++) { - size_t count_ppa_price_MMBTU_input; - ssc_number_t* ppa_price_MMBTU_input_array = as_array("ppa_price_input_heat_btu", &count_ppa_price_MMBTU_input); - std::vector ppa_price_input_vec; - for (int i = 0; i < count_ppa_price_MMBTU_input; i++) - { - ppa_price_input_vec.push_back(ppa_price_MMBTU_input_array[i] / MMBTU_TO_KWh); - } - int size = ppa_price_input_vec.size(); - ssc_number_t* alloc_vals = allocate("ppa_price_input", size); - for (int i = 0; i < size; i++) - alloc_vals[i] = ppa_price_input_vec[i]; // [] + ppa_price_input_vec.push_back(ppa_price_MMBTU_input_array[i] / MMBTU_TO_KWh); } + int size = ppa_price_input_vec.size(); + ssc_number_t* alloc_vals = allocate("ppa_price_input", size); + for (int i = 0; i < size; i++) + alloc_vals[i] = ppa_price_input_vec[i]; // [] } + // Weather reader C_csp_weatherreader weather_reader; @@ -621,6 +622,7 @@ class cm_linear_fresnel_dsg_iph : public compute_module ssc_number_t *p_gen_heat = allocate("gen_heat", n_steps_fixed); ssc_number_t *p_gen = allocate("gen", n_steps_fixed); + ssc_number_t* p_gen_heat_btu = allocate("gen_heat_btu", n_steps_fixed); ssc_number_t *p_W_dot_par_tot_haf = allocate("W_dot_par_tot_haf", n_steps_fixed); ssc_number_t *p_W_dot_parasitic_tot = as_array("W_dot_parasitic_tot", &count); ssc_number_t* p_load = allocate("load", n_steps_fixed); // testing using cmod_utilityrate5 for electricity rates p_load = p_W_dot_par_tot_haf @@ -630,6 +632,7 @@ class cm_linear_fresnel_dsg_iph : public compute_module size_t hour = (size_t)ceil(p_time_final_hr[i]); p_gen_heat[i] = p_q_dot_heat_sink[i] * (ssc_number_t)(haf(hour) * 1.E3); //[kWt] p_gen[i] = (ssc_number_t)0.0; //[kWt] (no electrical generation for direct steam linear fresnel IPH) + p_gen_heat_btu[i] = p_gen_heat[i] / MMBTU_TO_KWh; //[MMBtu/hr] p_W_dot_parasitic_tot[i] *= -1.0; //[kWe] Label is total parasitics, so change to a positive value p_W_dot_par_tot_haf[i] = (ssc_number_t)(p_W_dot_parasitic_tot[i] * haf(hour) * 1.E3); //[kWe] p_load[i] = p_W_dot_par_tot_haf[i]; @@ -646,6 +649,10 @@ class cm_linear_fresnel_dsg_iph : public compute_module ssc_number_t annual_thermal_consumption = as_number("annual_thermal_consumption"); //[kWht] assign("annual_energy", annual_field_energy - annual_thermal_consumption); //[kWht] + ssc_number_t annual_field_energy_MMBtu = annual_field_energy / MMBTU_TO_KWh; //[MMBtu] + ssc_number_t annual_thermal_consumption_MMBtu = annual_thermal_consumption / MMBTU_TO_KWh; //[MMBtu] + assign("annual_energy_heat_btu", annual_field_energy_MMBtu - annual_thermal_consumption_MMBtu); //[MMBtu] + // Calculate water use double A_aper_tot = csp_solver.get_cr_aperture_area(); //[m2] double V_water_mirrors = as_double("csp.lf.sf.water_per_wash") / 1000.0*A_aper_tot*as_double("csp.lf.sf.washes_per_year"); diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index fd2d60115..e86730de5 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -602,11 +602,13 @@ static var_info _cm_vtab_mspt_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "Total thermal power to heat sink with available derate", "kWt", "", "", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_ARRAY, "gen", "Total electric power to grid w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, +{ SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "Total thermal power to heat sink with available derate in MMBtu/hr", "MMBtu/hr", "", "system", "sim_type=1", "", "" }, // Annual single-value outputs -{ SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Thermal Energy to Heat Sink w/ avail derate", "kWht", "", "Post-process", "sim_type=1", "", ""}, -{ SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_htf", "Annual receiver power delivered to HTF", "MWt-hr", "", "Tower and Receiver", "sim_type=1", "", ""}, +{ SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Thermal Energy to Heat Sink w/ avail derate", "kWht", "", "Post-process", "sim_type=1", "", ""}, +{ SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_htf", "Annual receiver power delivered to HTF", "MWt-hr", "", "Tower and Receiver", "sim_type=1", "", ""}, +{ SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual Thermal Energy to Heat Sink w/ avail derate in MMBtu", "MMBtu", "", "Post-process", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption w/ avail derate", "kWhe", "", "Post-process", "sim_type=1", "", ""}, @@ -659,24 +661,23 @@ class cm_mspt_iph : public compute_module // ***************************************************** // Convert IPH Input Units + const double MMBTU_TO_KWh = 293.07107; // 1 + if (is_assigned("ppa_price_input_heat_btu")) { - const double MMBTU_TO_KWh = 293.07107; // 1 - if (is_assigned("ppa_price_input_heat_btu")) + size_t count_ppa_price_MMBTU_input; + ssc_number_t* ppa_price_MMBTU_input_array = as_array("ppa_price_input_heat_btu", &count_ppa_price_MMBTU_input); + std::vector ppa_price_input_vec; + for (int i = 0; i < count_ppa_price_MMBTU_input; i++) { - size_t count_ppa_price_MMBTU_input; - ssc_number_t* ppa_price_MMBTU_input_array = as_array("ppa_price_input_heat_btu", &count_ppa_price_MMBTU_input); - std::vector ppa_price_input_vec; - for (int i = 0; i < count_ppa_price_MMBTU_input; i++) - { - ppa_price_input_vec.push_back(ppa_price_MMBTU_input_array[i] / MMBTU_TO_KWh); - } - - int size = ppa_price_input_vec.size(); - ssc_number_t* alloc_vals = allocate("ppa_price_input", size); - for (int i = 0; i < size; i++) - alloc_vals[i] = ppa_price_input_vec[i]; // [] + ppa_price_input_vec.push_back(ppa_price_MMBTU_input_array[i] / MMBTU_TO_KWh); } + + int size = ppa_price_input_vec.size(); + ssc_number_t* alloc_vals = allocate("ppa_price_input", size); + for (int i = 0; i < size; i++) + alloc_vals[i] = ppa_price_input_vec[i]; // [] } + // ***************************************************** // System Design Parameters @@ -2310,6 +2311,7 @@ class cm_mspt_iph : public compute_module ssc_number_t* p_gen_heat = allocate("gen_heat", count); ssc_number_t* p_gen = allocate("gen", n_steps_fixed); + ssc_number_t* p_gen_heat_btu = allocate("gen_heat_btu", n_steps_fixed); ssc_number_t* p_W_dot_parasitic_tot = as_array("W_dot_parasitic_tot", &count); ssc_number_t* p_W_dot_par_tot_haf = allocate("W_dot_par_tot_haf", n_steps_fixed); ssc_number_t* p_load = allocate("load", n_steps_fixed); // testing using cmod_utilityrate5 for electricity rates p_load = p_W_dot_par_tot_haf @@ -2322,6 +2324,7 @@ class cm_mspt_iph : public compute_module size_t hour = (size_t)ceil(p_time_final_hr[i]); p_gen_heat[i] = (ssc_number_t)(p_q_dot_heat_sink[i] * 1.E3 * haf(hour)); //[kWt] p_gen[i] = (ssc_number_t)0.0; //[kWt] (no electrical generation for IPH tower) + p_gen_heat_btu[i] = p_gen_heat[i] / MMBTU_TO_KWh; //[MMBtu/hr] p_W_dot_parasitic_tot[i] *= -1.0; //[MWe] Label is total parasitics, so change to a positive value p_W_dot_par_tot_haf[i] = (ssc_number_t)(p_W_dot_parasitic_tot[i] * haf(hour) * 1.E3); //[kWe] apply availability derate and convert from MWe p_load[i] = p_W_dot_par_tot_haf[i]; @@ -2330,6 +2333,8 @@ class cm_mspt_iph : public compute_module ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour, true); accumulate_annual_for_year("gen_heat", "annual_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[kWht] + accumulate_annual_for_year("gen_heat_btu", "annual_energy_heat_btu", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[MMBtu] + // This term currently includes TES freeze protection accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWhe] diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 005666fc6..7b1bc003b 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -639,16 +639,20 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "q_balance", "Relative energy balance error", "", "", "solver", "sim_type=1", "", "" }, // Monthly Outputs - { SSC_OUTPUT, SSC_ARRAY, "monthly_energy", "Monthly Energy Gross", "kWh", "", "Post-process", "sim_type=1", "LENGTH=12", "" }, + { SSC_OUTPUT, SSC_ARRAY, "monthly_energy", "Monthly Energy Gross", "kWht", "", "Post-process", "sim_type=1", "LENGTH=12", "" }, + { SSC_OUTPUT, SSC_ARRAY, "monthly_energy_heat_btu", "Monthly Energy Gross in MMBtu", "MMBtu", "", "Post-process", "sim_type=1", "LENGTH=12", "" }, + // Annual Outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Net Electrical Energy Production w/ avail derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Net Thermal Energy Production w/ avail derate", "kWht", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual Net Thermal Energy Production w/ avail derate in MMBtu", "MMBtu", "", "Post-process", "sim_type=1", "", "" }, + //{ SSC_OUTPUT, SSC_NUMBER, "annual_gross_energy", "Annual Gross Electrical Energy Production w/ avail derate", "kWhe", "", "Post-process", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWht", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption w/ avail derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_total_water_use", "Total Annual Water Usage", "m^3", "", "Post-process", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_field_freeze_protection", "Annual thermal power for field freeze protection", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_tes_freeze_protection", "Annual thermal power for TES freeze protection", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_field_freeze_protection", "Annual thermal power for field freeze protection", "kWht", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_tes_freeze_protection", "Annual thermal power for TES freeze protection", "kWht", "", "Post-process", "sim_type=1", "", "" }, // Newly added { SSC_OUTPUT, SSC_ARRAY, "n_op_modes", "Operating modes in reporting timestep", "", "", "solver", "sim_type=1", "", "" }, @@ -706,6 +710,7 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "Total thermal power to grid w/ avail. derate", "kWt", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "gen", "Total electric power to grid w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "Total thermal power to grid w/ avail. derate in MMBtu/hr", "MMBtu/hr", "", "system", "sim_type=1", "", "" }, //{ SSC_OUTPUT, SSC_NUMBER, "conversion_factor", "Gross to Net Conversion Factor", "%", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "system", "sim_type=1", "", "" }, @@ -762,7 +767,7 @@ class cm_trough_physical_iph : public compute_module int is_dispatch = as_boolean("is_dispatch"); int tes_type = as_integer("tes_type"); - const double MMBTU_TO_KWh = 293.07107; // 1 + const double MMBTU_TO_KWh = 293.07107; // 1 MMBtu = 293.07107 kWh // Convert IPH Input Units { @@ -2226,6 +2231,7 @@ class cm_trough_physical_iph : public compute_module ssc_number_t *p_gen_heat = allocate("gen_heat", n_steps_fixed); ssc_number_t* p_gen = allocate("gen", n_steps_fixed); + ssc_number_t* p_gen_heat_btu = allocate("gen_heat_btu", n_steps_fixed); ssc_number_t *p_W_dot_par_tot_haf = allocate("W_dot_par_tot_haf", n_steps_fixed); ssc_number_t *p_q_dot_defocus_est = allocate("q_dot_defocus_est", n_steps_fixed); ssc_number_t* p_load = allocate("load", n_steps_fixed); // testing using cmod_utilityrate5 for electricity rates p_load = p_W_dot_par_tot_haf @@ -2264,6 +2270,7 @@ class cm_trough_physical_iph : public compute_module size_t hour = (size_t)ceil(p_time_final_hr[i]); p_gen_heat[i] = (ssc_number_t)(p_q_dot_heat_sink[i] * haf(hour) * 1.E3); //[kWt] p_gen[i] = (ssc_number_t)0.0; //[kWt] (no electrical generation for IPH trough) + p_gen_heat_btu[i] = p_gen_heat[i] / MMBTU_TO_KWh; //[MMBtu/hr] p_W_dot_parasitic_tot[i] *= -1.0; //[kWe] Label is total parasitics, so change to a positive value p_W_dot_par_tot_haf[i] = (ssc_number_t)(p_W_dot_parasitic_tot[i] * haf(hour) * 1.E3); //[kWe] p_q_dot_defocus_est[i] = (ssc_number_t)(1.0 - p_SCAs_def[i])*p_q_dot_htf_sf_out[i]; //[MWt] @@ -2348,10 +2355,11 @@ class cm_trough_physical_iph : public compute_module // Monthly outputs accumulate_monthly_for_year("gen_heat", "monthly_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1); - + accumulate_monthly_for_year("gen_heat_btu", "monthly_energy_heat_btu", sim_setup.m_report_step / 3600.0, steps_per_hour, 1); // Annual outputs accumulate_annual_for_year("gen_heat", "annual_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); + accumulate_annual_for_year("gen_heat_btu", "annual_energy_heat_btu", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //accumulate_annual_for_year("disp_objective", "disp_objective_ann", 1000.0*sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //accumulate_annual_for_year("disp_solve_iter", "disp_iter_ann", 1000.0*sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //accumulate_annual_for_year("disp_presolve_nconstr", "disp_presolve_nconstr_ann", sim_setup.m_report_step / 3600.0 / as_double("disp_frequency"), steps_per_hour, 1, n_steps_fixed / steps_per_hour); @@ -2367,10 +2375,10 @@ class cm_trough_physical_iph : public compute_module double electricity_rate_calc = annual_elec_cost / annual_electricity_consumption; //[$/kWhe] assign("electricity_rate", electricity_rate_calc); - ssc_number_t annual_field_fp = accumulate_annual_for_year("q_dot_freeze_prot", "annual_field_freeze_protection", sim_setup.m_report_step / 3600.0*1.E3, steps_per_hour); //[kWt-hr] - ssc_number_t annual_tes_fp = accumulate_annual_for_year("q_tes_heater", "annual_tes_freeze_protection", sim_setup.m_report_step / 3600.0*1.E3, steps_per_hour); //[kWt-hr] + ssc_number_t annual_field_fp = accumulate_annual_for_year("q_dot_freeze_prot", "annual_field_freeze_protection", sim_setup.m_report_step / 3600.0*1.E3, steps_per_hour); //[kWht] + ssc_number_t annual_tes_fp = accumulate_annual_for_year("q_tes_heater", "annual_tes_freeze_protection", sim_setup.m_report_step / 3600.0*1.E3, steps_per_hour); //[kWht] - ssc_number_t annual_thermal_consumption = annual_field_fp + annual_tes_fp; //[kWt-hr] + ssc_number_t annual_thermal_consumption = annual_field_fp + annual_tes_fp; //[kWht] assign("annual_thermal_consumption", annual_thermal_consumption); // Reporting dispatch solution counts From abea27ecb28a530d96a638f0f13245438ad4c2d9 Mon Sep 17 00:00:00 2001 From: Steven Janzou Date: Sun, 3 Nov 2024 04:48:10 -0700 Subject: [PATCH 62/85] Heat incentive updates --- ssc/cmod_cashloan_heat.cpp | 35 ++++++++++++++++++++++++++++- ssc/cmod_singleowner_heat.cpp | 42 +++++++++++++++++++++++++++++++---- ssc/common.cpp | 20 +++++++++++++++++ ssc/common.h | 2 ++ 4 files changed, 94 insertions(+), 5 deletions(-) diff --git a/ssc/cmod_cashloan_heat.cpp b/ssc/cmod_cashloan_heat.cpp index 3f6dbfac0..0b2fd1fa7 100644 --- a/ssc/cmod_cashloan_heat.cpp +++ b/ssc/cmod_cashloan_heat.cpp @@ -201,6 +201,8 @@ extern var_info vtab_fuelcell_replacement_cost[], vtab_tax_credits[], vtab_payment_incentives[], + vtab_tax_credits_heat[], + vtab_payment_incentives_heat[], vtab_lcos_inputs[], vtab_update_tech_outputs[], vtab_utility_rate_common[]; @@ -360,6 +362,8 @@ class cm_cashloan_heat : public compute_module add_var_info( vtab_depreciation ); add_var_info( vtab_tax_credits ); add_var_info( vtab_payment_incentives ); + add_var_info(vtab_tax_credits_heat); + add_var_info(vtab_payment_incentives_heat); add_var_info(vtab_battery_replacement_cost); add_var_info(vtab_fuelcell_replacement_cost); add_var_info(vtab_cashloan_heat); @@ -420,7 +424,7 @@ class cm_cashloan_heat : public compute_module - hourly_energy_calcs.calculate(this); + hourly_energy_calcs.calculate(this, true); // dispatch if (as_integer("system_use_lifetime_output") == 1) @@ -688,6 +692,35 @@ class cm_cashloan_heat : public compute_module ibi_oth_per = as_double("ibi_oth_percent")*0.01*total_cost; if (ibi_oth_per > as_double("ibi_oth_percent_maxvalue")) ibi_oth_per = as_double("ibi_oth_percent_maxvalue"); + // for heat models, convert input incentives to kW (capacity) and kWh (production) + const double BTUh_TO_W = 293.07107e-3; // 1 + + assign("cbi_fed_amount", var_data(as_double("cbi_fed_amount_heat_btu") / BTUh_TO_W)); + assign("cbi_sta_amount", var_data(as_double("cbi_sta_amount_heat_btu") / BTUh_TO_W)); + assign("cbi_uti_amount", var_data(as_double("cbi_uti_amount_heat_btu") / BTUh_TO_W)); + assign("cbi_oth_amount", var_data(as_double("cbi_oth_amount_heat_btu") / BTUh_TO_W)); + /* + auto vd = as_vector_double("pbi_fed_amount_heat_btu"); + for (auto& d : vd) + d = d / MMBTU_TO_KWh; +*/ + auto funcVecMMBTUtokWh = [](compute_module* cm, const char* name) { + const double MMBTU_TO_KWh = 293.07107; // 1 + auto vd = cm->as_vector_double(name);// only working for multiple value inputs - not single value + for (auto& d : vd) + d = d / MMBTU_TO_KWh; + return vd; + }; + + assign("pbi_fed_amount", var_data(funcVecMMBTUtokWh(this, "pbi_fed_amount_heat_btu"))); + assign("pbi_sta_amount", var_data(funcVecMMBTUtokWh(this, "pbi_sta_amount_heat_btu"))); + assign("pbi_uti_amount", var_data(funcVecMMBTUtokWh(this, "pbi_uti_amount_heat_btu"))); + assign("pbi_oth_amount", var_data(funcVecMMBTUtokWh(this, "pbi_oth_amount_heat_btu"))); + + assign("ptc_fed_amount", var_data(funcVecMMBTUtokWh(this, "ptc_fed_amount_heat_btu"))); + assign("ptc_sta_amount", var_data(funcVecMMBTUtokWh(this, "ptc_sta_amount_heat_btu"))); + + // cbi cbi_fed_amount = 1000.0*nameplate*as_double("cbi_fed_amount"); if (cbi_fed_amount > as_double("cbi_fed_maxvalue")) cbi_fed_amount = as_double("cbi_fed_maxvalue"); diff --git a/ssc/cmod_singleowner_heat.cpp b/ssc/cmod_singleowner_heat.cpp index 5e3bdd190..8abee4d99 100644 --- a/ssc/cmod_singleowner_heat.cpp +++ b/ssc/cmod_singleowner_heat.cpp @@ -671,6 +671,8 @@ vtab_tax_credits[], vtab_depreciation_inputs[], vtab_depreciation_outputs[], vtab_payment_incentives[], +vtab_tax_credits_heat[], +vtab_payment_incentives_heat[], vtab_debt[], vtab_financial_metrics[], // vtab_financial_capacity_payments[], @@ -926,8 +928,10 @@ class cm_singleowner_heat : public compute_module add_var_info( vtab_standard_financial ); add_var_info( vtab_oandm_heat ); add_var_info( vtab_equip_reserve ); - add_var_info( vtab_tax_credits ); - add_var_info(vtab_depreciation_inputs ); + add_var_info(vtab_tax_credits); + add_var_info(vtab_tax_credits_heat); + add_var_info(vtab_payment_incentives_heat); + add_var_info(vtab_depreciation_inputs ); add_var_info(vtab_depreciation_outputs ); add_var_info( vtab_payment_incentives ); add_var_info( vtab_debt ); @@ -1748,8 +1752,38 @@ class cm_singleowner_heat : public compute_module cf.at(CF_itc_fed_percent_maxvalue, k + 1) = itc_fed_percent_maxvalue[k]; } + // for heat models, convert input incentives to kW (capacity) and kWh (production) + const double BTUh_TO_W = 293.07107e-3; // 1 + const double MMBTU_TO_KWh = 293.07107; // 1 + + assign("cbi_fed_amount", var_data(as_double("cbi_fed_amount_heat_btu") / BTUh_TO_W)); + assign("cbi_sta_amount", var_data(as_double("cbi_sta_amount_heat_btu") / BTUh_TO_W)); + assign("cbi_uti_amount", var_data(as_double("cbi_uti_amount_heat_btu") / BTUh_TO_W)); + assign("cbi_oth_amount", var_data(as_double("cbi_oth_amount_heat_btu") / BTUh_TO_W)); + + /* + auto vd = as_vector_double("pbi_fed_amount_heat_btu"); + for (auto& d : vd) + d = d / MMBTU_TO_KWh; +*/ + auto funcVecMMBTUtokWh = [](compute_module* cm, const char* name) { + const double MMBTU_TO_KWh = 293.07107; // 1 + auto vd = cm->as_vector_double(name);// only working for multiple value inputs - not single value + for (auto& d : vd) + d = d / MMBTU_TO_KWh; + return vd; + }; - // cbi + assign("pbi_fed_amount", var_data(funcVecMMBTUtokWh(this, "pbi_fed_amount_heat_btu"))); + assign("pbi_sta_amount", var_data(funcVecMMBTUtokWh(this, "pbi_sta_amount_heat_btu"))); + assign("pbi_uti_amount", var_data(funcVecMMBTUtokWh(this, "pbi_uti_amount_heat_btu"))); + assign("pbi_oth_amount", var_data(funcVecMMBTUtokWh(this, "pbi_oth_amount_heat_btu"))); + + assign("ptc_fed_amount", var_data(funcVecMMBTUtokWh(this, "ptc_fed_amount_heat_btu"))); + assign("ptc_sta_amount", var_data(funcVecMMBTUtokWh(this, "ptc_sta_amount_heat_btu"))); + + + // cbi double cbi_fed_amount = 1000.0*nameplate*as_double("cbi_fed_amount"); if (cbi_fed_amount > as_double("cbi_fed_maxvalue")) cbi_fed_amount = as_double("cbi_fed_maxvalue"); double cbi_sta_amount = 1000.0*nameplate*as_double("cbi_sta_amount"); @@ -3459,7 +3493,7 @@ class cm_singleowner_heat : public compute_module // save_cf(CF_curtailment_value, nyears, "cf_curtailment_value"); // save_cf(CF_capacity_payment, nyears, "cf_capacity_payment"); // save_cf(CF_energy_curtailed, nyears, "cf_energy_curtailed"); - const double MMBTU_TO_KWh = 293.07107; // 1 +// const double MMBTU_TO_KWh = 293.07107; // 1 save_cf(CF_ppa_price, nyears, "cf_ppa_price"); for (size_t i = 0; i < nyears; i++) cf.at(CF_ppa_price_heat_btu, i) = cf.at(CF_ppa_price, i) * MMBTU_TO_KWh; diff --git a/ssc/common.cpp b/ssc/common.cpp index 52f428878..7b30f6705 100644 --- a/ssc/common.cpp +++ b/ssc/common.cpp @@ -319,6 +319,13 @@ var_info vtab_depreciation_outputs[] = { var_info_invalid }; +var_info vtab_tax_credits_heat[] = { +{ SSC_INPUT,SSC_ARRAY , "ptc_fed_amount_heat_btu" , "Federal PTC amount" , "$/MMBtu" , "" , "Tax Credit Incentives", "?=0" , "" , ""}, +{ SSC_INPUT,SSC_ARRAY , "ptc_sta_amount_heat_btu" , "State PTC amount" , "$/MMBtu" , "" , "Tax Credit Incentives", "?=0" , "" , ""}, +var_info_invalid +}; + + var_info vtab_tax_credits[] = { { SSC_INPUT,SSC_ARRAY , "itc_fed_amount" , "Federal amount-based ITC amount" , "$" , "" , "Tax Credit Incentives", "?=0" , "" , ""}, { SSC_INPUT,SSC_NUMBER , "itc_fed_amount_deprbas_fed" , "Federal amount-based ITC reduces federal depreciation basis" , "0/1" , "" , "Tax Credit Incentives", "?=1" , "BOOLEAN" , ""}, @@ -347,6 +354,19 @@ var_info vtab_tax_credits[] = { { SSC_INPUT,SSC_NUMBER , "ptc_sta_escal" , "State PTC escalation" , "%/year" , "" , "Tax Credit Incentives", "?=0" , "" , ""}, var_info_invalid }; +var_info vtab_payment_incentives_heat[] = { + { SSC_INPUT,SSC_NUMBER , "cbi_fed_amount_heat_btu" , "Federal CBI amount" , "$/(Btu/hr))" , "" , "Payment Incentives" , "?=0.0" , "" , ""}, + { SSC_INPUT,SSC_NUMBER , "cbi_sta_amount_heat_btu" , "State CBI amount" , "$/(Btu/hr))" , "" , "Payment Incentives" , "?=0.0" , "" , ""}, + { SSC_INPUT,SSC_NUMBER , "cbi_uti_amount_heat_btu" , "Utility CBI amount" , "$/(Btu/hr))" , "" , "Payment Incentives" , "?=0.0" , "" , ""}, + { SSC_INPUT,SSC_NUMBER , "cbi_oth_amount_heat_btu" , "Other CBI amount" , "$/(Btu/hr))" , "" , "Payment Incentives" , "?=0.0" , "" , ""}, + { SSC_INPUT,SSC_ARRAY , "pbi_fed_amount_heat_btu" , "Federal PBI amount" , "$/MMBtu" , "" , "Payment Incentives" , "?=0" , "" , ""}, + { SSC_INPUT,SSC_ARRAY , "pbi_sta_amount_heat_btu" , "State PBI amount" , "$/MMBtu" , "" , "Payment Incentives" , "?=0" , "" , ""}, + { SSC_INPUT,SSC_ARRAY , "pbi_uti_amount_heat_btu" , "Utility PBI amount" , "$/MMBtu" , "" , "Payment Incentives" , "?=0" , "" , ""}, + { SSC_INPUT,SSC_ARRAY , "pbi_oth_amount_heat_btu" , "Other PBI amount" , "$/MMBtu" , "" , "Payment Incentives" , "?=0" , "" , ""}, + +var_info_invalid }; + + var_info vtab_payment_incentives[] = { { SSC_INPUT,SSC_NUMBER , "ibi_fed_amount" , "Federal amount-based IBI amount" , "$" , "" , "Payment Incentives" , "?=0" , "" , ""}, { SSC_INPUT,SSC_NUMBER , "ibi_fed_amount_tax_fed" , "Federal amount-based IBI federal taxable" , "0/1" , "" , "Payment Incentives" , "?=1" , "BOOLEAN" , ""}, diff --git a/ssc/common.h b/ssc/common.h index 211f6bc9f..a5d4fc517 100644 --- a/ssc/common.h +++ b/ssc/common.h @@ -52,6 +52,8 @@ extern var_info vtab_depreciation_inputs[]; extern var_info vtab_depreciation_outputs[]; extern var_info vtab_tax_credits[]; extern var_info vtab_payment_incentives[]; +extern var_info vtab_tax_credits_heat[]; +extern var_info vtab_payment_incentives_heat[]; extern var_info vtab_debt[]; extern var_info vtab_ppa_inout[]; extern var_info vtab_financial_metrics[]; From 5b7a91008ef426ee653a717ce8861d4204021c6d Mon Sep 17 00:00:00 2001 From: Steven Janzou Date: Mon, 4 Nov 2024 02:01:01 -0700 Subject: [PATCH 63/85] cmod_singleowner_heat ready for testing --- ssc/cmod_singleowner_heat.cpp | 8 +++--- ssc/common.cpp | 53 +++++++++++++++++++++++++++++++++++ ssc/common.h | 2 ++ 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/ssc/cmod_singleowner_heat.cpp b/ssc/cmod_singleowner_heat.cpp index 8abee4d99..e73f63d17 100644 --- a/ssc/cmod_singleowner_heat.cpp +++ b/ssc/cmod_singleowner_heat.cpp @@ -663,7 +663,7 @@ var_info_invalid }; extern var_info vtab_update_tech_outputs[], -vtab_ppa_inout[], +vtab_ppa_inout_heat[], vtab_standard_financial[], vtab_oandm_heat[], vtab_equip_reserve[], @@ -674,7 +674,7 @@ vtab_payment_incentives[], vtab_tax_credits_heat[], vtab_payment_incentives_heat[], vtab_debt[], -vtab_financial_metrics[], +vtab_financial_metrics_heat[], // vtab_financial_capacity_payments[], // vtab_financial_grid[], vtab_fuelcell_replacement_cost[], @@ -924,7 +924,7 @@ class cm_singleowner_heat : public compute_module public: cm_singleowner_heat() { - add_var_info(vtab_ppa_inout ); + add_var_info(vtab_ppa_inout_heat ); add_var_info( vtab_standard_financial ); add_var_info( vtab_oandm_heat ); add_var_info( vtab_equip_reserve ); @@ -935,7 +935,7 @@ class cm_singleowner_heat : public compute_module add_var_info(vtab_depreciation_outputs ); add_var_info( vtab_payment_incentives ); add_var_info( vtab_debt ); - add_var_info( vtab_financial_metrics ); + add_var_info( vtab_financial_metrics_heat ); add_var_info( _cm_vtab_singleowner_heat ); add_var_info(vtab_battery_replacement_cost); add_var_info(vtab_fuelcell_replacement_cost); diff --git a/ssc/common.cpp b/ssc/common.cpp index 7b30f6705..d1a5ba01f 100644 --- a/ssc/common.cpp +++ b/ssc/common.cpp @@ -533,6 +533,27 @@ var_info vtab_ppa_inout[] = { var_info_invalid }; + +var_info vtab_ppa_inout_heat[] = { +{ SSC_INPUT, SSC_NUMBER, "ppa_soln_mode", "PPA solution mode", "0/1", "0=solve ppa,1=specify ppa", "Revenue", "?=0", "INTEGER,MIN=0,MAX=1", "" }, +{ SSC_INPUT, SSC_NUMBER, "ppa_soln_tolerance", "PPA solution tolerance", "", "", "Revenue", "?=1e-5", "", "" }, +{ SSC_INPUT, SSC_NUMBER, "ppa_soln_min", "PPA solution minimum ppa", "cents/kWht", "", "Revenue", "?=0", "", "" }, +{ SSC_INPUT, SSC_NUMBER, "ppa_soln_max", "PPA solution maximum ppa", "cents/kWht", "", "Revenue", "?=100", "", "" }, +{ SSC_INPUT, SSC_NUMBER, "ppa_soln_max_iterations", "PPA solution maximum number of iterations", "", "", "Revenue", "?=100", "INTEGER,MIN=1", "" }, + +{ SSC_INPUT, SSC_ARRAY, "ppa_price_input", "PPA price in first year input", "$/kWht", "", "Revenue", "*", "", "" }, +{ SSC_INPUT, SSC_NUMBER, "ppa_escalation", "PPA escalation rate", "%/year", "", "Revenue", "?=0", "", "" }, + +{ SSC_OUTPUT, SSC_NUMBER, "lppa_real", "LPPA Levelized PPA price real", "cents/kWht", "", "Metrics", "*", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "lppa_nom", "LPPA Levelized PPA price nominal", "cents/kWht", "", "Metrics", "*", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "ppa", "PPA price in Year 1", "cents/kWht", "", "Metrics", "*", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "ppa_escalation", "PPA price escalation", "%/year", "", "Metrics", "*", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "npv_ppa_revenue", "Present value of PPA revenue", "$", "", "Metrics", "*", "", "" }, + +var_info_invalid }; + + + var_info vtab_financial_metrics[] = { // { SSC_OUTPUT, SSC_NUMBER, "first_year_energy_net", "Annual energy", "kWh", "", "Metrics", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "debt_fraction", "Debt percent", "%", "", "Metrics", "*", "", "" }, @@ -561,6 +582,38 @@ var_info vtab_financial_metrics[] = { var_info_invalid }; + + +var_info vtab_financial_metrics_heat[] = { + // { SSC_OUTPUT, SSC_NUMBER, "first_year_energy_net", "Annual energy", "kWh", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "debt_fraction", "Debt percent", "%", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "flip_target_year", "Target year to meet IRR", "", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "flip_target_irr", "IRR target", "%", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "flip_actual_year", "Year target IRR was achieved", "year", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "flip_actual_irr", "IRR in target year", "%", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "lcoe_real", "LCOH Levelized cost of heat real", "cents/kWht", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "lcoe_nom", "LCOH Levelized cost of heat nominal", "cents/kWht", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "lcos_real", "LCOS Levelized cost of storage real", "cents/kWht", "", "Metrics", "?", "", "" }, + //{ SSC_OUTPUT, SSC_NUMBER, "lcos_nom", "LCOS Levelized cost of storage nominal", "cents/kWh", "", "Metrics", "?", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "npv_energy_nom", "Present value of annual energy nominal", "kWht", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "npv_energy_real", "Present value of annual energy real", "kWht", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "present_value_oandm", "Present value of O&M", "$", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "present_value_oandm_nonfuel", "Present value of non-fuel O&M", "$", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "present_value_fuel", "Present value of fuel O&M", "$", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "present_value_insandproptax", "Present value of insurance and prop tax", "$", "", "Metrics", "*", "", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "lcoptc_fed_real", "Levelized federal PTC real", "cents/kWht", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "lcoptc_fed_nom", "Levelized federal PTC nominal", "cents/kWht", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "lcoptc_sta_real", "Levelized state PTC real", "cents/kWht", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "lcoptc_sta_nom", "Levelized state PTC nominal", "cents/kWht", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "wacc", "WACC Weighted average cost of capital", "$", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "effective_tax_rate", "Effective tax rate", "%", "", "Metrics", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "analysis_period_irr", "IRR at end of analysis period", "%", "", "Metrics", "*", "", "" }, + var_info_invalid +}; + + + var_info vtab_debt[] = { /* term financing */ { SSC_INPUT, SSC_NUMBER, "term_tenor", "Term financing period", "years", "", "Financial Parameters", "?=10", "INTEGER,MIN=0", "" }, diff --git a/ssc/common.h b/ssc/common.h index a5d4fc517..4dc241c0f 100644 --- a/ssc/common.h +++ b/ssc/common.h @@ -57,6 +57,8 @@ extern var_info vtab_payment_incentives_heat[]; extern var_info vtab_debt[]; extern var_info vtab_ppa_inout[]; extern var_info vtab_financial_metrics[]; +extern var_info vtab_ppa_inout_heat[]; +extern var_info vtab_financial_metrics_heat[]; extern var_info vtab_adjustment_factors[]; extern var_info vtab_dc_adjustment_factors[]; From 3bffdaa40b61ade94b87e335682ae96c90f3aa3a Mon Sep 17 00:00:00 2001 From: Steven Janzou Date: Mon, 4 Nov 2024 04:34:33 -0700 Subject: [PATCH 64/85] Fix load assignment in thermal rate compute module and cashloan_heat ready for initial testing --- ssc/cmod_cashloan_heat.cpp | 31 ++++++++++++++++++++++++------- ssc/cmod_thermalrate_iph.cpp | 31 ++++++++++++++++++------------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/ssc/cmod_cashloan_heat.cpp b/ssc/cmod_cashloan_heat.cpp index 0b2fd1fa7..30f613980 100644 --- a/ssc/cmod_cashloan_heat.cpp +++ b/ssc/cmod_cashloan_heat.cpp @@ -101,7 +101,7 @@ static var_info vtab_cashloan_heat[] = { { SSC_OUTPUT, SSC_NUMBER, "total_cost", "Total installed cost", "$", "", "Financial Metrics", "*", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "cf_energy_net", "Electricity net generation", "kWh", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_net", "Thermal energy", "kWht", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales", "Electricity generation", "kWh", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_energy_purchases", "Electricity from grid to system", "kWh", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_energy_without_battery","Electricity generated without the battery or curtailment", "kWh", "", "Cash Flow Electricity", "", "LENGTH_EQUAL=cf_length", "" }, @@ -125,6 +125,8 @@ static var_info vtab_cashloan_heat[] = { { SSC_OUTPUT, SSC_ARRAY, "cf_om_opt_fuel_1_expense", "Feedstock biomass expense", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_om_opt_fuel_2_expense", "Feedstock coal expense", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_om_elec_price_for_heat_techs", "Electricity expense in heat models", "$", "", "Cash Flow Expenses", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_property_tax_assessed_value","Property tax net assessed value", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_property_tax_expense", "Property tax expense", "$", "", "Cash Flow", "*", "LENGTH_EQUAL=cf_length", "" }, @@ -195,7 +197,7 @@ var_info_invalid }; extern var_info vtab_standard_financial[], vtab_standard_loan[], - vtab_oandm[], + vtab_oandm_heat[], vtab_depreciation[], vtab_battery_replacement_cost[], vtab_fuelcell_replacement_cost[], @@ -301,6 +303,9 @@ enum { CF_om_production2_expense, CF_om_capacity2_expense, CF_om_fuel_expense, + CF_om_elec_price_for_heat_techs, + + CF_energy_charged_grid, CF_energy_charged_pv, CF_energy_discharged, @@ -358,7 +363,7 @@ class cm_cashloan_heat : public compute_module { add_var_info( vtab_standard_financial ); add_var_info( vtab_standard_loan ); - add_var_info( vtab_oandm ); + add_var_info( vtab_oandm_heat ); add_var_info( vtab_depreciation ); add_var_info( vtab_tax_credits ); add_var_info( vtab_payment_incentives ); @@ -581,10 +586,16 @@ class cm_cashloan_heat : public compute_module // precompute expenses from annual schedules or value+escalation escal_or_annual( CF_om_fixed_expense, nyears, "om_fixed", inflation_rate, 1.0, false, as_double("om_fixed_escal")*0.01 ); - escal_or_annual( CF_om_production_expense, nyears, "om_production", inflation_rate, 0.001, false, as_double("om_production_escal")*0.01 ); - escal_or_annual( CF_om_capacity_expense, nyears, "om_capacity", inflation_rate, 1.0, false, as_double("om_capacity_escal")*0.01 ); + escal_or_annual( CF_om_production_expense, nyears, "om_production_heat", inflation_rate, 0.001, false, as_double("om_production_escal")*0.01 ); + escal_or_annual( CF_om_capacity_expense, nyears, "om_capacity_heat", inflation_rate, 1.0, false, as_double("om_capacity_escal")*0.01 ); escal_or_annual( CF_om_fuel_expense, nyears, "om_fuel_cost", inflation_rate, as_double("system_heat_rate")*0.001, false, as_double("om_fuel_cost_escal")*0.01 ); + arrp = as_array("utility_bill_wo_sys", &count); + for (i = 0; i < count && i <= nyears; i++) + cf.at(CF_om_elec_price_for_heat_techs, i) = arrp[i]; + + + // additional o and m sub types (e.g. batteries and fuel cells) int add_om_num_types = as_integer("add_om_num_types"); ssc_number_t nameplate1 = 0; @@ -971,6 +982,7 @@ class cm_cashloan_heat : public compute_module + cf.at(CF_om_production2_expense, i) + cf.at(CF_om_capacity2_expense, i) + cf.at(CF_om_fuel_expense,i) + + cf.at(CF_om_elec_price_for_heat_techs, i) + cf.at(CF_om_opt_fuel_1_expense,i) + cf.at(CF_om_opt_fuel_2_expense,i) + cf.at(CF_property_tax_expense,i) @@ -1422,6 +1434,9 @@ class cm_cashloan_heat : public compute_module save_cf(CF_om_production2_expense, nyears, "cf_om_production2_expense"); save_cf(CF_om_capacity2_expense, nyears, "cf_om_capacity2_expense"); } + + save_cf(CF_om_elec_price_for_heat_techs, nyears, "cf_om_elec_price_for_heat_techs"); + save_cf( CF_om_fuel_expense, nyears, "cf_om_fuel_expense" ); save_cf( CF_om_opt_fuel_1_expense, nyears, "cf_om_opt_fuel_1_expense" ); save_cf( CF_om_opt_fuel_2_expense, nyears, "cf_om_opt_fuel_2_expense" ); @@ -1521,13 +1536,15 @@ class cm_cashloan_heat : public compute_module double pvFixedOandM = npv(CF_om_capacity_expense, nyears, nom_discount_rate); double pvVariableOandM = npv(CF_om_production_expense, nyears, nom_discount_rate); double pvFuelOandM = npv(CF_om_fuel_expense, nyears, nom_discount_rate); + double pvElec_price_for_heat_techs = npv(CF_om_elec_price_for_heat_techs, nyears, nom_discount_rate); + double pvOptFuel1OandM = npv(CF_om_opt_fuel_1_expense, nyears, nom_discount_rate); double pvOptFuel2OandM = npv(CF_om_opt_fuel_2_expense, nyears, nom_discount_rate); // double pvWaterOandM = NetPresentValue(sv[svNominalDiscountRate], cf[cfAnnualWaterCost], analysis_period); - assign( "present_value_oandm", var_data((ssc_number_t)(pvAnnualOandM + pvFixedOandM + pvVariableOandM + pvFuelOandM))); // + pvWaterOandM); + assign( "present_value_oandm", var_data((ssc_number_t)(pvAnnualOandM + pvFixedOandM + pvVariableOandM + pvFuelOandM + pvElec_price_for_heat_techs))); // + pvWaterOandM); - assign( "present_value_oandm_nonfuel", var_data((ssc_number_t)(pvAnnualOandM + pvFixedOandM + pvVariableOandM))); + assign( "present_value_oandm_nonfuel", var_data((ssc_number_t)(pvAnnualOandM + pvFixedOandM + pvVariableOandM + pvElec_price_for_heat_techs))); assign( "present_value_fuel", var_data((ssc_number_t)(pvFuelOandM + pvOptFuel1OandM + pvOptFuel2OandM))); // present value of insurance and property tax diff --git a/ssc/cmod_thermalrate_iph.cpp b/ssc/cmod_thermalrate_iph.cpp index 19d869dbd..cb91b0d75 100644 --- a/ssc/cmod_thermalrate_iph.cpp +++ b/ssc/cmod_thermalrate_iph.cpp @@ -46,7 +46,7 @@ static var_info vtab_thermal_rate_iph[] = { // First year or lifetime hourly or subhourly // load and gen expected to be > 0 - { SSC_INPUT, SSC_ARRAY, "gen_heat", "Thermal power generated", "kW-t", "", "Thermal Rate", "*", "", "" }, + { SSC_INPUT, SSC_ARRAY, "gen_heat", "Thermal power generated", "kWt", "", "Thermal Rate", "*", "", "" }, // input from user as MMBtu/hr and output as MMBtu/hr { SSC_INOUT, SSC_ARRAY, "thermal_load_heat_btu", "Thermal load (year 1)", "MMBtu/hr", "", "Thermal Rate", "", "", "" }, @@ -72,9 +72,11 @@ static var_info vtab_thermal_rate_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "annual_thermal_value", "Thermal value", "$", "", "Annual", "*", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "thermal_revenue_with_system", "Thermal revenue with system", "$", "", "Time Series", "*", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "thermal_revenue_without_system", "Thermal revenue without system", "$", "", "Time Series", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "thermal_load_year1", "Thermal load (year 1)", "$", "", "", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "thermal_revenue_with_system", "Thermal revenue with system", "$", "", "Time Series", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "thermal_revenue_without_system", "Thermal revenue without system", "$", "", "Time Series", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "thermal_cost_with_system", "Thermal cost with system", "$", "", "Time Series", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "thermal_cost_without_system", "Thermal cost without system", "$", "", "Time Series", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "thermal_load_year1", "Thermal load total", "MMBtu/hr", "", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "thermal_savings_year1", "Thermal savings (year 1)", "$", "", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "thermal_cost_with_system_year1", "Thermal cost with sytem (year 1)", "$", "", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "thermal_cost_without_system_year1", "Thermal cost without system (year 1)", "$", "", "", "*", "", "" }, @@ -217,6 +219,18 @@ class cm_thermalrate_iph : public compute_module ssc_number_t ts_hour_gen = 1.0f / step_per_hour_gen; m_num_rec_yearly = nrec_gen_per_year; + + // prepare timestep arrays for load and grid values + std::vector + e_sys_cy(m_num_rec_yearly), p_sys_cy(m_num_rec_yearly), + p_load(m_num_rec_yearly), // to handle no load, or num load != num gen resets above assignment + p_buyrate(m_num_rec_yearly), + p_sellrate(m_num_rec_yearly), + e_grid_cy(m_num_rec_yearly), p_grid_cy(m_num_rec_yearly), + e_load_cy(m_num_rec_yearly), p_load_cy(m_num_rec_yearly); // current year load (accounts for escal) + + + if (is_assigned("thermal_load_heat_btu")) { // hourly or sub hourly loads for single year @@ -237,15 +251,6 @@ class cm_thermalrate_iph : public compute_module - // prepare timestep arrays for load and grid values - std::vector - e_sys_cy(m_num_rec_yearly), p_sys_cy(m_num_rec_yearly), - p_load(m_num_rec_yearly), // to handle no load, or num load != num gen - p_buyrate(m_num_rec_yearly), - p_sellrate(m_num_rec_yearly), - e_grid_cy(m_num_rec_yearly), p_grid_cy(m_num_rec_yearly), - e_load_cy(m_num_rec_yearly), p_load_cy(m_num_rec_yearly); // current year load (accounts for escal) - From 9aa17dcdd2693d2c501c411d19b78adece2699cd Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:56:57 -0700 Subject: [PATCH 65/85] Update trough LCOH model to match tower and MSLF method. --- ssc/cmod_mspt_iph.cpp | 2 +- ssc/cmod_trough_physical_iph.cpp | 27 ++------------------------- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index e86730de5..d5f5623a2 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -1617,7 +1617,7 @@ class cm_mspt_iph : public compute_module if (sim_type == 1) { if (csp_financial_model == 8 || csp_financial_model == 7) { // No Financial Model or LCOH if (is_dispatch) { - throw exec_error("mspt_iph", "Can't select dispatch optimization if No Financial model"); + throw exec_error("mspt_iph", "Can't select dispatch optimization if No Financial or LCOH model"); } else { // if no dispatch optimization, don't need an input pricing schedule // If electricity pricing data is not available, then dispatch to a uniform schedule diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 7b1bc003b..23a03ce81 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -730,8 +730,6 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_P_dsn", "Pressure in TES pipes at design conditions", "bar", "", "TES", "sim_type=1&tes_type=0", "", "" }, //{ SSC_OUTPUT, SSC_ARRAY, "defocus", "Field optical focus fraction", "", "", "solver", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "electricity_rate", "Electricity price = calculated annual elec cost / total elec energy consusmed", "$/kWhe", "", "Post-process", "sim_type=1&csp_financial_model=7", "", "" }, - var_info_invalid }; @@ -1501,34 +1499,15 @@ class cm_trough_physical_iph : public compute_module if (sim_type == 1) { - if (csp_financial_model == 8) { // No Financial Model + if (csp_financial_model == 8 || csp_financial_model == 7) { // No Financial Model or LCOH if (is_dispatch) { - throw exec_error("trough_physical_iph", "Can't select dispatch optimization if No Financial model"); + throw exec_error("trough_physical_iph", "Can't select dispatch optimization if No Financial or LCOH model"); } else { // if no dispatch optimization, don't need an input pricing schedule // If electricity pricing data is not available, then dispatch to a uniform schedule elec_pricing_schedule = C_timeseries_schedule_inputs(-1.0, std::numeric_limits::quiet_NaN()); } } - else if (csp_financial_model == 7) { // LCOH - - size_t count_ppa_price_input; - ssc_number_t* ppa_price_input_array = as_array("ppa_price_input", &count_ppa_price_input); - double ppa_price_year1 = (double)ppa_price_input_array[0]; // [$/kWh] - - // Time-of-Delivery factors by time step: - int ppa_mult_model = as_integer("ppa_multiplier_model"); - if (ppa_mult_model == 1) // use dispatch_ts input - { - auto vec = as_vector_double("dispatch_factors_ts"); - elec_pricing_schedule = C_timeseries_schedule_inputs(vec, ppa_price_year1); - } - else if (ppa_mult_model == 0) // standard diuranal input - { - elec_pricing_schedule = C_timeseries_schedule_inputs(as_matrix("dispatch_sched_weekday"), - as_matrix("dispatch_sched_weekend"), as_vector_double("dispatch_tod_factors"), ppa_price_year1); - } - } else if (csp_financial_model == 1) { // Single Owner double ppa_price_year1 = std::numeric_limits::quiet_NaN(); @@ -2372,8 +2351,6 @@ class cm_trough_physical_iph : public compute_module accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step / 3600.0, steps_per_hour); //[kWhe] double annual_electricity_consumption = as_double("annual_electricity_consumption"); //[kWhe] - double electricity_rate_calc = annual_elec_cost / annual_electricity_consumption; //[$/kWhe] - assign("electricity_rate", electricity_rate_calc); ssc_number_t annual_field_fp = accumulate_annual_for_year("q_dot_freeze_prot", "annual_field_freeze_protection", sim_setup.m_report_step / 3600.0*1.E3, steps_per_hour); //[kWht] ssc_number_t annual_tes_fp = accumulate_annual_for_year("q_tes_heater", "annual_tes_freeze_protection", sim_setup.m_report_step / 3600.0*1.E3, steps_per_hour); //[kWht] From 00eb9a80b256b701478336eb661df55fdf1fc25f Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon, 4 Nov 2024 16:42:32 -0700 Subject: [PATCH 66/85] Rename piston cylinder TES model. --- ssc/cmod_csp_subcomponent.cpp | 74 ++--- ssc/cmod_trough_physical.cpp | 96 +++--- ssc/cmod_trough_physical_iph.cpp | 96 +++--- tcs/CMakeLists.txt | 4 +- tcs/csp_solver_core.h | 2 +- ...cpp => csp_solver_piston_cylinder_tes.cpp} | 300 +++++++++--------- ...tes.h => csp_solver_piston_cylinder_tes.h} | 24 +- tcs/csp_solver_tes_core.cpp | 4 +- tcs/csp_solver_tes_core.h | 4 +- 9 files changed, 302 insertions(+), 302 deletions(-) rename tcs/{csp_solver_NTHeatTrap_tes.cpp => csp_solver_piston_cylinder_tes.cpp} (87%) rename tcs/{csp_solver_NTHeatTrap_tes.h => csp_solver_piston_cylinder_tes.h} (97%) diff --git a/ssc/cmod_csp_subcomponent.cpp b/ssc/cmod_csp_subcomponent.cpp index bbb3bc759..12cffd4da 100644 --- a/ssc/cmod_csp_subcomponent.cpp +++ b/ssc/cmod_csp_subcomponent.cpp @@ -33,7 +33,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "core.h" #include "csp_solver_two_tank_tes.h" -#include "csp_solver_NTHeatTrap_tes.h" +#include "csp_solver_piston_cylinder_tes.h" #include "csp_solver_packedbed_tes.h" // Forward declarations @@ -62,7 +62,7 @@ static var_info _cm_vtab_csp_subcomponent[] = { { SSC_INPUT, SSC_NUMBER, "pb_pump_coef", "Pumping power to move 1kg of HTF through PB loop", "kW/kg", "", "powerblock", "*", "", "" }, // General TES Parameters - { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), Packed Bed (1), HeatTrap Single Tank (2)", "-", "", "TES", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), Packed Bed (1), Piston Cylinder (2)", "-", "", "TES", "?=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "Fluid", "Field HTF fluid ID number", "-", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_MATRIX, "field_fl_props", "User defined field fluid property data", "-", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "tshours", "Equivalent full-load thermal storage hours", "hr", "", "TES", "*", "", "" }, @@ -114,12 +114,12 @@ static var_info _cm_vtab_csp_subcomponent[] = { { SSC_INPUT, SSC_NUMBER, "tes_pb_f_oversize", "Packed bed oversize factor", "", "", "TES", "tes_type=1", "", "" }, { SSC_INPUT, SSC_ARRAY, "tes_pb_T_grad_ini", "TES Temperature gradient at beginning of timestep", "C", "", "TES", "?=[-274]", "", "" }, - // TES Norwich HeatTrap - { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_thick", "Tank wall thickness (used for Norwich HeatTrap)", "m", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_cp", "Tank wall cp (used for Norwich HeatTrap)", "kJ/kg-K", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_dens", "Tank wall thickness (used for Norwich HeatTrap)", "kg/m3", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_ARRAY, "tes_nt_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_insul_percent", "Percent additional wall mass due to insulation (used for Norwich HeatTrap)", "%", "", "TES", "?=0", "", "" }, + // TES Piston Cylinder + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_thick", "Tank wall thickness (used for Piston Cylinder)", "m", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_cp", "Tank wall cp (used for Piston Cylinder)", "kJ/kg-K", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_dens", "Tank wall thickness (used for Piston Cylinder)", "kg/m3", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_ARRAY, "tes_cyl_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_insul_percent", "Percent additional wall mass due to insulation (used for Piston Cylinder)", "%", "", "TES", "?=0", "", "" }, // Outputs { SSC_OUTPUT, SSC_ARRAY, "T_src_in", "Temperature to heat source", "C", "", "TES", "*", "", "" }, @@ -184,7 +184,7 @@ class cm_csp_subcomponent : public compute_module C_csp_tes* storage_pointer; C_csp_two_tank_tes storage_two_tank; - C_csp_NTHeatTrap_tes storage_NT; + C_csp_piston_cylinder_tes storage_cyl; C_csp_packedbed_tes storage_packedbed; double P_ref = as_double("P_ref"); @@ -298,8 +298,8 @@ class cm_csp_subcomponent : public compute_module storage_pointer = &storage_packedbed; } - // Norwich HeatTrap - else if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) + // Piston Cylinder + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_CYL) { int nstep = as_integer("tes_n_tsteps"); @@ -325,8 +325,8 @@ class cm_csp_subcomponent : public compute_module } // Modify wall density to account for insulation mass - double mass_factor = 1.0 + (0.01 * as_double("tes_nt_tank_insul_percent")); - double dens_orig = as_double("tes_nt_tank_dens"); + double mass_factor = 1.0 + (0.01 * as_double("tes_cyl_tank_insul_percent")); + double dens_orig = as_double("tes_cyl_tank_dens"); double dens_w_insulation = dens_orig * mass_factor; double T_tank_hot_ini = is_assigned("T_tank_hot_ini") == true ? as_double("T_tank_hot_ini") : as_double("T_loop_out"); @@ -335,7 +335,7 @@ class cm_csp_subcomponent : public compute_module double h_tank_in = is_assigned("h_tank_in") == true ? as_double("h_tank_in") : std::numeric_limits::quiet_NaN(); double d_tank_in = is_assigned("d_tank_in") == true ? as_double("d_tank_in") : std::numeric_limits::quiet_NaN(); - storage_NT = C_csp_NTHeatTrap_tes( + storage_cyl = C_csp_piston_cylinder_tes( as_integer("Fluid"), // [-] field fluid identifier as_matrix("field_fl_props"), // [-] field fluid properties //as_integer("store_fluid"), // [-] tes fluid identifier @@ -359,11 +359,11 @@ class cm_csp_subcomponent : public compute_module as_double("h_tank_min"), // [m] Minimum allowable HTF height in storage tank as_double("init_hot_htf_percent"), // [%] Initial fraction of available volume that is hot as_double("pb_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through power cycle - as_double("tes_nt_tank_cp") * 1000, // convert to J/kgK + as_double("tes_cyl_tank_cp") * 1000, // convert to J/kgK dens_w_insulation, // Tank Wall density - as_double("tes_nt_tank_thick"), // Tank wall thickness + as_double("tes_cyl_tank_thick"), // Tank wall thickness nstep, // Number subtimesteps - as_vector_double("tes_nt_piston_loss_poly"), // Leakage polynomial (%) + as_vector_double("tes_cyl_piston_loss_poly"), // Leakage polynomial (%) as_double("V_tes_des"), // [m/s] Design-point velocity for sizing the diameters of the TES piping as_boolean("calc_design_pipe_vals"), // [-] Should the HTF state be calculated at design conditions as_double("tes_pump_coef"), // [kW/kg/s] Pumping power to move 1 kg/s of HTF through tes loop @@ -380,7 +380,7 @@ class cm_csp_subcomponent : public compute_module as_double("DP_SGS") // [bar] Pressure drop on the TES discharge side (e.g., within the steam generator) ); - storage_pointer = &storage_NT; + storage_pointer = &storage_cyl; } else { @@ -427,9 +427,9 @@ class cm_csp_subcomponent : public compute_module storage_packedbed.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); } - else if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_CYL) { - storage_NT.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, + storage_cyl.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); } @@ -510,21 +510,21 @@ class cm_csp_subcomponent : public compute_module // Add NT specific outputs - if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) + if (tes_type == C_csp_tes::csp_tes_types::E_TES_CYL) { double piston_location, piston_fraction; - storage_NT.calc_piston_location(piston_location, piston_fraction); + storage_cyl.calc_piston_location(piston_location, piston_fraction); piston_loc_vec.push_back(piston_location); piston_frac_vec.push_back(piston_fraction); - double tes_error = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_ERROR); - double tes_error_percent = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_ERROR_PERCENT); - double tes_error_leak = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_LEAK_ERROR); - double tes_E_hot = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_E_HOT); - double tes_E_cold = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_E_COLD); - double tes_wall_error = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_WALL_ERROR); - double tes_error_corrected = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_ERROR_CORRECTED); + double tes_error = storage_cyl.mc_reported_outputs.value(C_csp_piston_cylinder_tes::E_ERROR); + double tes_error_percent = storage_cyl.mc_reported_outputs.value(C_csp_piston_cylinder_tes::E_ERROR_PERCENT); + double tes_error_leak = storage_cyl.mc_reported_outputs.value(C_csp_piston_cylinder_tes::E_LEAK_ERROR); + double tes_E_hot = storage_cyl.mc_reported_outputs.value(C_csp_piston_cylinder_tes::E_E_HOT); + double tes_E_cold = storage_cyl.mc_reported_outputs.value(C_csp_piston_cylinder_tes::E_E_COLD); + double tes_wall_error = storage_cyl.mc_reported_outputs.value(C_csp_piston_cylinder_tes::E_WALL_ERROR); + double tes_error_corrected = storage_cyl.mc_reported_outputs.value(C_csp_piston_cylinder_tes::E_ERROR_CORRECTED); tes_error_vec.push_back(tes_error); tes_error_percent_vec.push_back(tes_error_percent); @@ -534,13 +534,13 @@ class cm_csp_subcomponent : public compute_module tes_wall_error_vec.push_back(tes_wall_error); tes_error_corrected_vec.push_back(tes_error_corrected); - hot_tank_mass_perc[i] = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_HOT_TANK_HTF_PERC_FINAL); - exp_wall_mass[i] = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_EXP_WALL_MASS); - exp_length[i] = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_EXP_LENGTH); - mass_hot[i] = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_MASS_HOT_TANK); - mass_cold[i] = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_MASS_COLD_TANK); - V_cold[i] = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_VOL_COLD); - V_hot[i] = storage_NT.mc_reported_outputs.value(C_csp_NTHeatTrap_tes::E_VOL_HOT); + hot_tank_mass_perc[i] = storage_cyl.mc_reported_outputs.value(C_csp_piston_cylinder_tes::E_HOT_TANK_HTF_PERC_FINAL); + exp_wall_mass[i] = storage_cyl.mc_reported_outputs.value(C_csp_piston_cylinder_tes::E_EXP_WALL_MASS); + exp_length[i] = storage_cyl.mc_reported_outputs.value(C_csp_piston_cylinder_tes::E_EXP_LENGTH); + mass_hot[i] = storage_cyl.mc_reported_outputs.value(C_csp_piston_cylinder_tes::E_MASS_HOT_TANK); + mass_cold[i] = storage_cyl.mc_reported_outputs.value(C_csp_piston_cylinder_tes::E_MASS_COLD_TANK); + V_cold[i] = storage_cyl.mc_reported_outputs.value(C_csp_piston_cylinder_tes::E_VOL_COLD); + V_hot[i] = storage_cyl.mc_reported_outputs.value(C_csp_piston_cylinder_tes::E_VOL_HOT); } // Add packed bed specific outputs @@ -555,7 +555,7 @@ class cm_csp_subcomponent : public compute_module } - if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) + if (tes_type == C_csp_tes::csp_tes_types::E_TES_CYL) { set_vector("piston_loc", piston_loc_vec); set_vector("piston_frac", piston_frac_vec); diff --git a/ssc/cmod_trough_physical.cpp b/ssc/cmod_trough_physical.cpp index a93fec7ef..3afea6efd 100644 --- a/ssc/cmod_trough_physical.cpp +++ b/ssc/cmod_trough_physical.cpp @@ -43,7 +43,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "csp_solver_trough_collector_receiver.h" #include "csp_solver_pc_Rankine_indirect_224.h" #include "csp_solver_two_tank_tes.h" -#include "csp_solver_NTHeatTrap_tes.h" +#include "csp_solver_piston_cylinder_tes.h" #include "csp_solver_packedbed_tes.h" //#include "csp_solver_tou_block_schedules.h" #include "csp_dispatch.h" @@ -202,7 +202,7 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_INPUT, SSC_MATRIX, "ud_ind_od", "Off design user-defined power cycle performance as function of T_htf, m_dot_htf [ND], and T_amb", "", "", "powerblock", "pc_config=1", "", "" }, // General TES Parameters - { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), Packed Bed (1), HeatTrap Single Tank (2)", "-", "", "TES", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), Packed Bed (1), Piston Cylinder (2)", "-", "", "TES", "?=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "tshours", "Equivalent full-load thermal storage hours", "hr", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "is_h_tank_fixed", "[1] Use fixed height (calculate diameter) [0] Use fixed diameter [2] Use fixed d and h (for packed bed)", "-", "", "TES", "?=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "h_tank_in", "Total height of tank input (height of HTF when tank is full", "m", "", "TES", "is_h_tank_fixed=1", "", "" }, @@ -226,12 +226,12 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_INPUT, SSC_NUMBER, "dt_hot", "Hot side HX approach temp", "C", "", "TES", "tes_type=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "tanks_in_parallel", "Tanks are in parallel, not in series, with solar field", "-", "", "controller", "tes_type=0", "", "" }, - // TES Norwich HeatTrap - { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_thick", "Tank wall thickness (used for Norwich HeatTrap)", "m", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_cp", "Tank wall cp (used for Norwich HeatTrap)", "kJ/kg-K", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_dens", "Tank wall thickness (used for Norwich HeatTrap)", "kg/m3", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_ARRAY, "tes_nt_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_insul_percent", "Percent additional wall mass due to insulation (used for Norwich HeatTrap)", "%", "", "TES", "?=0", "", "" }, + // TES Piston Cylinder + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_thick", "Tank wall thickness (used for Piston Cylinder)", "m", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_cp", "Tank wall cp (used for Piston Cylinder)", "kJ/kg-K", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_dens", "Tank wall thickness (used for Piston Cylinder)", "kg/m3", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_ARRAY, "tes_cyl_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_insul_percent", "Percent additional wall mass due to insulation (used for Piston Cylinder)", "%", "", "TES", "?=0", "", "" }, // TES Packed Bed { SSC_INPUT, SSC_NUMBER, "tes_pb_n_xsteps", "Number of spatial segments", "", "", "TES", "tes_type=1", "", "" }, @@ -1348,7 +1348,7 @@ class cm_trough_physical : public compute_module // ******************************** C_csp_tes* storage_pointer; C_csp_two_tank_tes storage_two_tank; - C_csp_NTHeatTrap_tes storage_NT; + C_csp_piston_cylinder_tes storage_cyl; C_csp_packedbed_tes storage_packedbed; // Two Tank if (tes_type == C_csp_tes::csp_tes_types::E_TES_TWO_TANK) @@ -1491,8 +1491,8 @@ class cm_trough_physical : public compute_module storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_8, allocate("T_grad_8", n_steps_fixed), n_steps_fixed); storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_9, allocate("T_grad_9", n_steps_fixed), n_steps_fixed); } - // Norwich - else if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) + // Piston Cylinder + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_CYL) { // Get number of sub time steps int nstep = as_integer("tes_n_tsteps"); @@ -1520,14 +1520,14 @@ class cm_trough_physical : public compute_module } // Modify wall density to account for insulation mass - double mass_factor = 1.0 + (0.01 * as_double("tes_nt_tank_insul_percent")); - double dens_orig = as_double("tes_nt_tank_dens"); + double mass_factor = 1.0 + (0.01 * as_double("tes_cyl_tank_insul_percent")); + double dens_orig = as_double("tes_cyl_tank_dens"); double dens_w_insulation = dens_orig * mass_factor; double h_tank_in = is_assigned("h_tank_in") == true ? as_double("h_tank_in") : std::numeric_limits::quiet_NaN(); double d_tank_in = is_assigned("d_tank_in") == true ? as_double("d_tank_in") : std::numeric_limits::quiet_NaN(); - storage_NT = C_csp_NTHeatTrap_tes( + storage_cyl = C_csp_piston_cylinder_tes( c_trough.m_Fluid, c_trough.m_field_fl_props, //as_integer("store_fluid"), @@ -1552,11 +1552,11 @@ class cm_trough_physical : public compute_module as_double("h_tank_min"), as_double("init_hot_htf_percent"), as_double("pb_pump_coef"), - as_double("tes_nt_tank_cp") * 1000, // convert to J/kgK + as_double("tes_cyl_tank_cp") * 1000, // convert to J/kgK dens_w_insulation, - as_double("tes_nt_tank_thick"), + as_double("tes_cyl_tank_thick"), nstep, - as_vector_double("tes_nt_piston_loss_poly"), + as_vector_double("tes_cyl_piston_loss_poly"), as_double("V_tes_des"), as_boolean("calc_design_pipe_vals"), as_double("tes_pump_coef"), @@ -1573,34 +1573,34 @@ class cm_trough_physical : public compute_module as_double("DP_SGS") ); - storage_pointer = &storage_NT; + storage_pointer = &storage_cyl; // Set storage outputs - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_Q_DOT_LOSS, allocate("tank_losses", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_W_DOT_HEATER, allocate("q_tes_heater", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_TES_T_HOT, allocate("T_tes_hot", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_TES_T_COLD, allocate("T_tes_cold", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_M_DOT_TANK_TO_TANK, allocate("m_dot_cold_tank_to_hot_tank", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_MASS_COLD_TANK, allocate("mass_tes_cold", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_MASS_HOT_TANK, allocate("mass_tes_hot", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_W_DOT_HTF_PUMP, allocate("tes_htf_pump_power", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_HOT_TANK_HTF_PERC_FINAL, allocate("hot_tank_htf_percent_final", n_steps_fixed), n_steps_fixed); - - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_VOL_COLD, allocate("vol_tes_cold", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_VOL_HOT, allocate("vol_tes_hot", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_VOL_TOT, allocate("vol_tes_tot", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_PIST_LOC, allocate("tes_piston_loc", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_PIST_FRAC, allocate("tes_piston_frac", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_COLD_FRAC, allocate("tes_cold_vol_frac", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_MASS_TOT, allocate("tes_mass_tot", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_SA_COLD, allocate("tes_SA_cold", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_SA_HOT, allocate("tes_SA_hot", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_SA_TOT, allocate("tes_SA_tot", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_ERROR, allocate("tes_error", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_ERROR_PERCENT, allocate("tes_error_percent", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_LEAK_ERROR, allocate("tes_leak_error", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_WALL_ERROR, allocate("tes_wall_error", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_ERROR_CORRECTED, allocate("tes_error_corrected", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_Q_DOT_LOSS, allocate("tank_losses", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_W_DOT_HEATER, allocate("q_tes_heater", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_TES_T_HOT, allocate("T_tes_hot", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_TES_T_COLD, allocate("T_tes_cold", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_M_DOT_TANK_TO_TANK, allocate("m_dot_cold_tank_to_hot_tank", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_MASS_COLD_TANK, allocate("mass_tes_cold", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_MASS_HOT_TANK, allocate("mass_tes_hot", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_W_DOT_HTF_PUMP, allocate("tes_htf_pump_power", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_HOT_TANK_HTF_PERC_FINAL, allocate("hot_tank_htf_percent_final", n_steps_fixed), n_steps_fixed); + + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_VOL_COLD, allocate("vol_tes_cold", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_VOL_HOT, allocate("vol_tes_hot", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_VOL_TOT, allocate("vol_tes_tot", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_PIST_LOC, allocate("tes_piston_loc", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_PIST_FRAC, allocate("tes_piston_frac", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_COLD_FRAC, allocate("tes_cold_vol_frac", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_MASS_TOT, allocate("tes_mass_tot", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_SA_COLD, allocate("tes_SA_cold", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_SA_HOT, allocate("tes_SA_hot", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_SA_TOT, allocate("tes_SA_tot", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_ERROR, allocate("tes_error", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_ERROR_PERCENT, allocate("tes_error_percent", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_LEAK_ERROR, allocate("tes_leak_error", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_WALL_ERROR, allocate("tes_wall_error", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_ERROR_CORRECTED, allocate("tes_error_corrected", n_steps_fixed), n_steps_fixed); } else { @@ -2084,11 +2084,11 @@ class cm_trough_physical : public compute_module h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); hot_vol_frac = storage_packedbed.get_hot_tank_vol_frac(); } - else if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_CYL) { - storage_NT.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, + storage_cyl.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); - hot_vol_frac = storage_NT.get_hot_tank_vol_frac(); + hot_vol_frac = storage_cyl.get_hot_tank_vol_frac(); } Q_tes = Q_tes_des_calc; @@ -2459,8 +2459,8 @@ class cm_trough_physical : public compute_module double P_adj = 0; // slightly adjust all field design pressures to account for pressure drop in TES before hot tank if (tes_type == C_csp_tes::csp_tes_types::E_TES_TWO_TANK) P_adj = storage_two_tank.P_in_des; - else if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) - P_adj = storage_NT.P_in_des; + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_CYL) + P_adj = storage_cyl.P_in_des; transform(c_trough.m_P_rnr_dsn.begin(), c_trough.m_P_rnr_dsn.end(), c_trough.m_P_rnr_dsn.begin(), [P_adj](double x) {return x + P_adj; }); transform(c_trough.m_P_hdr_dsn.begin(), c_trough.m_P_hdr_dsn.end(), c_trough.m_P_hdr_dsn.begin(), [P_adj](double x) {return x + P_adj; }); diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 23a03ce81..96fd646f8 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -44,7 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "csp_solver_pc_heat_sink.h" #include "csp_solver_two_tank_tes.h" #include "cst_iph_dispatch.h" -#include "csp_solver_NTHeatTrap_tes.h" +#include "csp_solver_piston_cylinder_tes.h" #include "csp_solver_packedbed_tes.h" #include "csp_system_costs.h" //#include "cmod_csp_common_eqns.h" @@ -185,7 +185,7 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "allow_heater_no_dispatch_opt","Allow heater with no dispatch optimization? SAM UI relies on cmod default", "", "", "System Costs", "?=0", "", "SIMULATION_PARAMETER" }, // General TES Parameters - { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), Packed Bed (1), HeatTrap Single Tank (2)", "-", "", "TES", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), Packed Bed (1), Piston Cylinder (2)", "-", "", "TES", "?=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "tshours", "Equivalent full-load thermal storage hours", "hr", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "is_h_tank_fixed", "[1] Use fixed height (calculate diameter) [0] Use fixed diameter [2] Use fixed d and h (for packed bed)", "-", "", "TES", "?=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "h_tank_in", "Total height of tank input (height of HTF when tank is full", "m", "", "TES", "is_h_tank_fixed=1", "", "" }, @@ -206,12 +206,12 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "dt_hot", "Hot side HX approach temp", "C", "", "TES", "tes_type=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "tanks_in_parallel", "Tanks are in parallel, not in series, with solar field", "-", "", "controller", "tes_type=0", "", "" }, - // TES Norwich HeatTrap - { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_thick", "Tank wall thickness (used for Norwich HeatTrap)", "m", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_cp", "Tank wall cp (used for Norwich HeatTrap)", "kJ/kg-K", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_dens", "Tank wall thickness (used for Norwich HeatTrap)", "kg/m3", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_ARRAY, "tes_nt_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_nt_tank_insul_percent", "Percent additional wall mass due to insulation (used for Norwich HeatTrap)", "%", "", "TES", "?=0", "", "" }, + // TES Piston Cylinder + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_thick", "Tank wall thickness (used for Piston Cylinder)", "m", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_cp", "Tank wall cp (used for Piston Cylinder)", "kJ/kg-K", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_dens", "Tank wall thickness (used for Piston Cylinder)", "kg/m3", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_ARRAY, "tes_cyl_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_insul_percent", "Percent additional wall mass due to insulation (used for Piston Cylinder)", "%", "", "TES", "?=0", "", "" }, // TES Packed Bed { SSC_INPUT, SSC_NUMBER, "tes_pb_n_xsteps", "Number of spatial segments", "", "", "TES", "tes_type=1", "", "" }, @@ -1215,7 +1215,7 @@ class cm_trough_physical_iph : public compute_module // ******************************** C_csp_tes* storage_pointer; C_csp_two_tank_tes storage_two_tank; - C_csp_NTHeatTrap_tes storage_NT; + C_csp_piston_cylinder_tes storage_cyl; C_csp_packedbed_tes storage_packedbed; // Two Tank if (tes_type == C_csp_tes::csp_tes_types::E_TES_TWO_TANK) @@ -1357,8 +1357,8 @@ class cm_trough_physical_iph : public compute_module storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_8, allocate("T_grad_8", n_steps_fixed), n_steps_fixed); storage_packedbed.mc_reported_outputs.assign(C_csp_packedbed_tes::E_T_GRAD_9, allocate("T_grad_9", n_steps_fixed), n_steps_fixed); } - // Norwich - else if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) + // Piston Cylinder + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_CYL) { // Get number of sub time steps int nstep = as_integer("tes_n_tsteps"); @@ -1386,14 +1386,14 @@ class cm_trough_physical_iph : public compute_module } // Modify wall density to account for insulation mass - double mass_factor = 1.0 + (0.01 * as_double("tes_nt_tank_insul_percent")); - double dens_orig = as_double("tes_nt_tank_dens"); + double mass_factor = 1.0 + (0.01 * as_double("tes_cyl_tank_insul_percent")); + double dens_orig = as_double("tes_cyl_tank_dens"); double dens_w_insulation = dens_orig * mass_factor; double h_tank_in = is_assigned("h_tank_in") == true ? as_double("h_tank_in") : std::numeric_limits::quiet_NaN(); double d_tank_in = is_assigned("d_tank_in") == true ? as_double("d_tank_in") : std::numeric_limits::quiet_NaN(); - storage_NT = C_csp_NTHeatTrap_tes( + storage_cyl = C_csp_piston_cylinder_tes( c_trough.m_Fluid, c_trough.m_field_fl_props, //as_integer("store_fluid"), @@ -1418,11 +1418,11 @@ class cm_trough_physical_iph : public compute_module as_double("h_tank_min"), as_double("init_hot_htf_percent"), as_double("pb_pump_coef"), - as_double("tes_nt_tank_cp") * 1000, // convert to J/kgK + as_double("tes_cyl_tank_cp") * 1000, // convert to J/kgK dens_w_insulation, - as_double("tes_nt_tank_thick"), + as_double("tes_cyl_tank_thick"), nstep, - as_vector_double("tes_nt_piston_loss_poly"), + as_vector_double("tes_cyl_piston_loss_poly"), as_double("V_tes_des"), as_boolean("calc_design_pipe_vals"), as_double("tes_pump_coef"), @@ -1438,34 +1438,34 @@ class cm_trough_physical_iph : public compute_module as_double("HDR_rough") ); - storage_pointer = &storage_NT; + storage_pointer = &storage_cyl; // Set storage outputs - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_Q_DOT_LOSS, allocate("tank_losses", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_W_DOT_HEATER, allocate("q_tes_heater", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_TES_T_HOT, allocate("T_tes_hot", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_TES_T_COLD, allocate("T_tes_cold", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_M_DOT_TANK_TO_TANK, allocate("m_dot_cold_tank_to_hot_tank", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_MASS_COLD_TANK, allocate("mass_tes_cold", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_MASS_HOT_TANK, allocate("mass_tes_hot", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_W_DOT_HTF_PUMP, allocate("tes_htf_pump_power", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_HOT_TANK_HTF_PERC_FINAL, allocate("hot_tank_htf_percent_final", n_steps_fixed), n_steps_fixed); - - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_VOL_COLD, allocate("vol_tes_cold", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_VOL_HOT, allocate("vol_tes_hot", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_VOL_TOT, allocate("vol_tes_tot", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_PIST_LOC, allocate("tes_piston_loc", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_PIST_FRAC, allocate("tes_piston_frac", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_COLD_FRAC, allocate("tes_cold_vol_frac", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_MASS_TOT, allocate("tes_mass_tot", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_SA_COLD, allocate("tes_SA_cold", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_SA_HOT, allocate("tes_SA_hot", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_SA_TOT, allocate("tes_SA_tot", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_ERROR, allocate("tes_error", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_ERROR_PERCENT, allocate("tes_error_percent", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_LEAK_ERROR, allocate("tes_leak_error", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_WALL_ERROR, allocate("tes_wall_error", n_steps_fixed), n_steps_fixed); - storage_NT.mc_reported_outputs.assign(C_csp_NTHeatTrap_tes::E_ERROR_CORRECTED, allocate("tes_error_corrected", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_Q_DOT_LOSS, allocate("tank_losses", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_W_DOT_HEATER, allocate("q_tes_heater", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_TES_T_HOT, allocate("T_tes_hot", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_TES_T_COLD, allocate("T_tes_cold", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_M_DOT_TANK_TO_TANK, allocate("m_dot_cold_tank_to_hot_tank", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_MASS_COLD_TANK, allocate("mass_tes_cold", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_MASS_HOT_TANK, allocate("mass_tes_hot", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_W_DOT_HTF_PUMP, allocate("tes_htf_pump_power", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_HOT_TANK_HTF_PERC_FINAL, allocate("hot_tank_htf_percent_final", n_steps_fixed), n_steps_fixed); + + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_VOL_COLD, allocate("vol_tes_cold", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_VOL_HOT, allocate("vol_tes_hot", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_VOL_TOT, allocate("vol_tes_tot", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_PIST_LOC, allocate("tes_piston_loc", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_PIST_FRAC, allocate("tes_piston_frac", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_COLD_FRAC, allocate("tes_cold_vol_frac", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_MASS_TOT, allocate("tes_mass_tot", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_SA_COLD, allocate("tes_SA_cold", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_SA_HOT, allocate("tes_SA_hot", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_SA_TOT, allocate("tes_SA_tot", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_ERROR, allocate("tes_error", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_ERROR_PERCENT, allocate("tes_error_percent", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_LEAK_ERROR, allocate("tes_leak_error", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_WALL_ERROR, allocate("tes_wall_error", n_steps_fixed), n_steps_fixed); + storage_cyl.mc_reported_outputs.assign(C_csp_piston_cylinder_tes::E_ERROR_CORRECTED, allocate("tes_error_corrected", n_steps_fixed), n_steps_fixed); } else { @@ -1864,11 +1864,11 @@ class cm_trough_physical_iph : public compute_module h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); hot_vol_frac = storage_packedbed.get_hot_tank_vol_frac(); } - else if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_CYL) { - storage_NT.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, + storage_cyl.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); - hot_vol_frac = storage_NT.get_hot_tank_vol_frac(); + hot_vol_frac = storage_cyl.get_hot_tank_vol_frac(); } Q_tes = Q_tes_des_calc; @@ -2267,8 +2267,8 @@ class cm_trough_physical_iph : public compute_module double P_adj = 0; // slightly adjust all field design pressures to account for pressure drop in TES before hot tank if (tes_type == C_csp_tes::csp_tes_types::E_TES_TWO_TANK) P_adj = storage_two_tank.P_in_des; - else if (tes_type == C_csp_tes::csp_tes_types::E_TES_NT) - P_adj = storage_NT.P_in_des; + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_CYL) + P_adj = storage_cyl.P_in_des; transform(c_trough.m_P_rnr_dsn.begin(), c_trough.m_P_rnr_dsn.end(), c_trough.m_P_rnr_dsn.begin(), [P_adj](double x) {return x + P_adj; }); transform(c_trough.m_P_hdr_dsn.begin(), c_trough.m_P_hdr_dsn.end(), c_trough.m_P_hdr_dsn.begin(), [P_adj](double x) {return x + P_adj; }); diff --git a/tcs/CMakeLists.txt b/tcs/CMakeLists.txt index a1f28c97d..3e75609b5 100644 --- a/tcs/CMakeLists.txt +++ b/tcs/CMakeLists.txt @@ -24,7 +24,7 @@ set(TCS_SRC csp_solver_mspt_collector_receiver.cpp csp_solver_mspt_receiver.cpp csp_solver_mspt_receiver_222.cpp - csp_solver_NTHeatTrap_tes.cpp + csp_solver_piston_cylinder_tes.cpp csp_solver_packedbed_tes.cpp csp_solver_pc_gen.cpp csp_solver_pc_heat_sink.cpp @@ -120,7 +120,7 @@ set(TCS_SRC csp_solver_mspt_collector_receiver.h csp_solver_mspt_receiver.h csp_solver_mspt_receiver_222.h - csp_solver_NTHeatTrap_tes.h + csp_solver_piston_cylinder_tes.h csp_solver_packedbed_tes.h csp_solver_pc_gen.h csp_solver_pc_heat_sink.h diff --git a/tcs/csp_solver_core.h b/tcs/csp_solver_core.h index fdfe16afe..6710693b2 100644 --- a/tcs/csp_solver_core.h +++ b/tcs/csp_solver_core.h @@ -732,7 +732,7 @@ class C_csp_tes { E_TES_TWO_TANK, E_TES_PACKED_BED, - E_TES_NT + E_TES_CYL }; // Class to save messages for up stream classes diff --git a/tcs/csp_solver_NTHeatTrap_tes.cpp b/tcs/csp_solver_piston_cylinder_tes.cpp similarity index 87% rename from tcs/csp_solver_NTHeatTrap_tes.cpp rename to tcs/csp_solver_piston_cylinder_tes.cpp index 7dcc5e80d..02c0d99fd 100644 --- a/tcs/csp_solver_NTHeatTrap_tes.cpp +++ b/tcs/csp_solver_piston_cylinder_tes.cpp @@ -31,44 +31,44 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "csp_solver_NTHeatTrap_tes.h" +#include "csp_solver_piston_cylinder_tes.h" static C_csp_reported_outputs::S_output_info S_output_info[] = { - {C_csp_NTHeatTrap_tes::E_Q_DOT_LOSS, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] TES thermal losses - {C_csp_NTHeatTrap_tes::E_W_DOT_HEATER, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWe] TES freeze protection power - {C_csp_NTHeatTrap_tes::E_TES_T_HOT, C_csp_reported_outputs::TS_LAST}, //[C] TES final hot tank temperature - {C_csp_NTHeatTrap_tes::E_TES_T_COLD, C_csp_reported_outputs::TS_LAST}, //[C] TES cold temperature at end of timestep - {C_csp_NTHeatTrap_tes::E_M_DOT_TANK_TO_TANK, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] TES thermal losses - {C_csp_NTHeatTrap_tes::E_MASS_COLD_TANK, C_csp_reported_outputs::TS_LAST}, //[kg] Mass in cold tank at end of timestep - {C_csp_NTHeatTrap_tes::E_MASS_HOT_TANK, C_csp_reported_outputs::TS_LAST}, //[kg] Mass in hot tank at end of timestep - {C_csp_NTHeatTrap_tes::E_HOT_TANK_HTF_PERC_FINAL, C_csp_reported_outputs::TS_LAST}, //[%] Final percent fill of available hot tank mass - {C_csp_NTHeatTrap_tes::E_W_DOT_HTF_PUMP, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWe] - - {C_csp_NTHeatTrap_tes::E_VOL_COLD, C_csp_reported_outputs::TS_LAST}, //[m3] - {C_csp_NTHeatTrap_tes::E_VOL_HOT, C_csp_reported_outputs::TS_LAST}, //[m3] - {C_csp_NTHeatTrap_tes::E_VOL_TOT, C_csp_reported_outputs::TS_LAST}, //[m3] - {C_csp_NTHeatTrap_tes::E_PIST_LOC, C_csp_reported_outputs::TS_LAST}, //[m] - {C_csp_NTHeatTrap_tes::E_PIST_FRAC, C_csp_reported_outputs::TS_LAST}, //[] - {C_csp_NTHeatTrap_tes::E_COLD_FRAC, C_csp_reported_outputs::TS_LAST}, //[] - {C_csp_NTHeatTrap_tes::E_MASS_TOT, C_csp_reported_outputs::TS_LAST}, //[kg] - {C_csp_NTHeatTrap_tes::E_SA_COLD, C_csp_reported_outputs::TS_LAST}, //[kg] - {C_csp_NTHeatTrap_tes::E_SA_HOT, C_csp_reported_outputs::TS_LAST}, //[kg] - {C_csp_NTHeatTrap_tes::E_SA_TOT, C_csp_reported_outputs::TS_LAST}, //[kg] - {C_csp_NTHeatTrap_tes::E_ERROR, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] - {C_csp_NTHeatTrap_tes::E_ERROR_PERCENT, C_csp_reported_outputs::TS_LAST}, //[%] - {C_csp_NTHeatTrap_tes::E_LEAK_ERROR, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] - {C_csp_NTHeatTrap_tes::E_E_HOT, C_csp_reported_outputs::TS_LAST}, //[MJ] - {C_csp_NTHeatTrap_tes::E_E_COLD, C_csp_reported_outputs::TS_LAST}, //[MJ] - {C_csp_NTHeatTrap_tes::E_WALL_ERROR, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MW] - {C_csp_NTHeatTrap_tes::E_ERROR_CORRECTED, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MW] - {C_csp_NTHeatTrap_tes::E_EXP_WALL_MASS, C_csp_reported_outputs::TS_LAST}, //[kg] - {C_csp_NTHeatTrap_tes::E_EXP_LENGTH, C_csp_reported_outputs::TS_LAST}, //[m] + {C_csp_piston_cylinder_tes::E_Q_DOT_LOSS, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] TES thermal losses + {C_csp_piston_cylinder_tes::E_W_DOT_HEATER, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWe] TES freeze protection power + {C_csp_piston_cylinder_tes::E_TES_T_HOT, C_csp_reported_outputs::TS_LAST}, //[C] TES final hot tank temperature + {C_csp_piston_cylinder_tes::E_TES_T_COLD, C_csp_reported_outputs::TS_LAST}, //[C] TES cold temperature at end of timestep + {C_csp_piston_cylinder_tes::E_M_DOT_TANK_TO_TANK, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] TES thermal losses + {C_csp_piston_cylinder_tes::E_MASS_COLD_TANK, C_csp_reported_outputs::TS_LAST}, //[kg] Mass in cold tank at end of timestep + {C_csp_piston_cylinder_tes::E_MASS_HOT_TANK, C_csp_reported_outputs::TS_LAST}, //[kg] Mass in hot tank at end of timestep + {C_csp_piston_cylinder_tes::E_HOT_TANK_HTF_PERC_FINAL, C_csp_reported_outputs::TS_LAST}, //[%] Final percent fill of available hot tank mass + {C_csp_piston_cylinder_tes::E_W_DOT_HTF_PUMP, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWe] + + {C_csp_piston_cylinder_tes::E_VOL_COLD, C_csp_reported_outputs::TS_LAST}, //[m3] + {C_csp_piston_cylinder_tes::E_VOL_HOT, C_csp_reported_outputs::TS_LAST}, //[m3] + {C_csp_piston_cylinder_tes::E_VOL_TOT, C_csp_reported_outputs::TS_LAST}, //[m3] + {C_csp_piston_cylinder_tes::E_PIST_LOC, C_csp_reported_outputs::TS_LAST}, //[m] + {C_csp_piston_cylinder_tes::E_PIST_FRAC, C_csp_reported_outputs::TS_LAST}, //[] + {C_csp_piston_cylinder_tes::E_COLD_FRAC, C_csp_reported_outputs::TS_LAST}, //[] + {C_csp_piston_cylinder_tes::E_MASS_TOT, C_csp_reported_outputs::TS_LAST}, //[kg] + {C_csp_piston_cylinder_tes::E_SA_COLD, C_csp_reported_outputs::TS_LAST}, //[kg] + {C_csp_piston_cylinder_tes::E_SA_HOT, C_csp_reported_outputs::TS_LAST}, //[kg] + {C_csp_piston_cylinder_tes::E_SA_TOT, C_csp_reported_outputs::TS_LAST}, //[kg] + {C_csp_piston_cylinder_tes::E_ERROR, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] + {C_csp_piston_cylinder_tes::E_ERROR_PERCENT, C_csp_reported_outputs::TS_LAST}, //[%] + {C_csp_piston_cylinder_tes::E_LEAK_ERROR, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MWt] + {C_csp_piston_cylinder_tes::E_E_HOT, C_csp_reported_outputs::TS_LAST}, //[MJ] + {C_csp_piston_cylinder_tes::E_E_COLD, C_csp_reported_outputs::TS_LAST}, //[MJ] + {C_csp_piston_cylinder_tes::E_WALL_ERROR, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MW] + {C_csp_piston_cylinder_tes::E_ERROR_CORRECTED, C_csp_reported_outputs::TS_WEIGHTED_AVE}, //[MW] + {C_csp_piston_cylinder_tes::E_EXP_WALL_MASS, C_csp_reported_outputs::TS_LAST}, //[kg] + {C_csp_piston_cylinder_tes::E_EXP_LENGTH, C_csp_reported_outputs::TS_LAST}, //[m] csp_info_invalid }; -C_storage_tank_dynamic_NT::C_storage_tank_dynamic_NT() +C_storage_tank_dynamic_cyl::C_storage_tank_dynamic_cyl() { m_V_prev = m_T_prev = m_m_prev = m_E_prev = @@ -78,7 +78,7 @@ C_storage_tank_dynamic_NT::C_storage_tank_dynamic_NT() m_T_design = m_mass_total = m_mass_inactive = m_mass_active = std::numeric_limits::quiet_NaN(); } -void C_storage_tank_dynamic_NT::init(HTFProperties htf_class_in, double V_tank /*m3*/, +void C_storage_tank_dynamic_cyl::init(HTFProperties htf_class_in, double V_tank /*m3*/, double h_tank /*m*/, double h_min /*m*/, double u_tank /*W/m2-K*/, double tank_pairs /*-*/, double T_htr /*K*/, double max_q_htr /*MWt*/, double V_ini /*m3*/, double T_ini /*K*/, @@ -129,12 +129,12 @@ void C_storage_tank_dynamic_NT::init(HTFProperties htf_class_in, double V_tank / m_m_wall_prev = calc_mass_wall(m_T_prev, m_m_prev); } -double C_storage_tank_dynamic_NT::calc_mass_at_prev() +double C_storage_tank_dynamic_cyl::calc_mass_at_prev() { return m_V_prev * mc_htf.dens(m_T_prev, 1.0); //[kg] } -double C_storage_tank_dynamic_NT::get_m_UA() +double C_storage_tank_dynamic_cyl::get_m_UA() { // Calculate UA value double SA_prev = calc_SA(m_V_calc); @@ -143,67 +143,67 @@ double C_storage_tank_dynamic_NT::get_m_UA() return UA; //[W/K] } -double C_storage_tank_dynamic_NT::get_m_T_prev() +double C_storage_tank_dynamic_cyl::get_m_T_prev() { return m_T_prev; //[K] } -double C_storage_tank_dynamic_NT::get_m_T_calc() +double C_storage_tank_dynamic_cyl::get_m_T_calc() { return m_T_calc; } -double C_storage_tank_dynamic_NT::get_m_m_calc() // Get Current FLUID mass +double C_storage_tank_dynamic_cyl::get_m_m_calc() // Get Current FLUID mass { return m_m_calc; //[kg] } -double C_storage_tank_dynamic_NT::get_m_m_prev() // Get Previous FLUID mass +double C_storage_tank_dynamic_cyl::get_m_m_prev() // Get Previous FLUID mass { return m_m_prev; //[kg] } -double C_storage_tank_dynamic_NT::get_m_E_prev() +double C_storage_tank_dynamic_cyl::get_m_E_prev() { return m_E_prev; //[MJ] } -double C_storage_tank_dynamic_NT::get_m_E_calc() +double C_storage_tank_dynamic_cyl::get_m_E_calc() { return m_E_calc; //[MJ] } -double C_storage_tank_dynamic_NT::get_vol_frac() +double C_storage_tank_dynamic_cyl::get_vol_frac() { return (m_V_prev - m_V_inactive) / m_V_active; } -double C_storage_tank_dynamic_NT::get_mass_avail() +double C_storage_tank_dynamic_cyl::get_mass_avail() { return std::max(m_m_prev - m_mass_inactive, 0.0); //[kg] } -double C_storage_tank_dynamic_NT::get_fluid_vol() +double C_storage_tank_dynamic_cyl::get_fluid_vol() { return m_V_calc; } -double C_storage_tank_dynamic_NT::get_fluid_vol_prev() +double C_storage_tank_dynamic_cyl::get_fluid_vol_prev() { return m_V_prev; } -double C_storage_tank_dynamic_NT::get_radius() +double C_storage_tank_dynamic_cyl::get_radius() { return m_radius; } -double C_storage_tank_dynamic_NT::get_SA_calc() +double C_storage_tank_dynamic_cyl::get_SA_calc() { return m_SA_calc; } -double C_storage_tank_dynamic_NT::calc_mass_wall(double T_fluid, double mass_fluid) +double C_storage_tank_dynamic_cyl::calc_mass_wall(double T_fluid, double mass_fluid) { double rho_fluid = mc_htf.dens(T_fluid, 0); // kg/m3 double V_fluid = mass_fluid / rho_fluid; // m3 @@ -217,22 +217,22 @@ double C_storage_tank_dynamic_NT::calc_mass_wall(double T_fluid, double mass_flu return mass_wall; } -double C_storage_tank_dynamic_NT::get_m_m_wall_prev() +double C_storage_tank_dynamic_cyl::get_m_m_wall_prev() { return m_m_wall_prev; } -double C_storage_tank_dynamic_NT::get_m_m_wall_calc() +double C_storage_tank_dynamic_cyl::get_m_m_wall_calc() { return m_m_wall_calc; } -double C_storage_tank_dynamic_NT::get_m_L_calc() +double C_storage_tank_dynamic_cyl::get_m_L_calc() { return m_L_calc; } -double C_storage_tank_dynamic_NT::m_dot_available(double f_unavail, double timestep) +double C_storage_tank_dynamic_cyl::m_dot_available(double f_unavail, double timestep) { //double rho = mc_htf.dens(m_T_prev, 1.0); //[kg/m^3] //double V = m_m_prev / rho; //[m^3] Volume available in tank (one temperature) @@ -247,7 +247,7 @@ double C_storage_tank_dynamic_NT::m_dot_available(double f_unavail, double times return m_dot_avail; //[kg/s] } -void C_storage_tank_dynamic_NT::converged() +void C_storage_tank_dynamic_cyl::converged() { // Reset 'previous' timestep values to 'calculated' values m_V_prev = m_V_calc; //[m^3] @@ -258,7 +258,7 @@ void C_storage_tank_dynamic_NT::converged() m_m_wall_prev = m_m_wall_calc; //[kg] } -void C_storage_tank_dynamic_NT::energy_balance_core(double timestep /*s*/, double mdot_fluid_in_before_leak /*kg/s*/, double mdot_fluid_out_before_leak /*kg/s*/, +void C_storage_tank_dynamic_cyl::energy_balance_core(double timestep /*s*/, double mdot_fluid_in_before_leak /*kg/s*/, double mdot_fluid_out_before_leak /*kg/s*/, double T_fluid_in /*K*/, double T_amb /*K*/, double mass_fluid_prev_inner /*kg*/, double T_tank_in /*K*/, double T_prev_inner /*K*/, double T_leak_in /*K*/, @@ -727,7 +727,7 @@ void C_storage_tank_dynamic_NT::energy_balance_core(double timestep /*s*/, doubl } } -void C_storage_tank_dynamic_NT::energy_balance_iterated(double timestep /*s*/, double m_dot_in /*kg/s*/, double m_dot_out /*kg/s*/, +void C_storage_tank_dynamic_cyl::energy_balance_iterated(double timestep /*s*/, double m_dot_in /*kg/s*/, double m_dot_out /*kg/s*/, double T_in /*K*/, double T_amb /*K*/, double T_tank_in, /*K*/ double T_leak_in, /*K*/ @@ -782,7 +782,7 @@ void C_storage_tank_dynamic_NT::energy_balance_iterated(double timestep /*s*/, d } -double C_storage_tank_dynamic_NT::calc_SA_rate(double mdot_htf /*kg/s*/, double T_htf /*K*/) +double C_storage_tank_dynamic_cyl::calc_SA_rate(double mdot_htf /*kg/s*/, double T_htf /*K*/) { // Get Density double rho = mc_htf.dens(T_htf, 1.0); @@ -794,7 +794,7 @@ double C_storage_tank_dynamic_NT::calc_SA_rate(double mdot_htf /*kg/s*/, double return SA_rate; } -double C_storage_tank_dynamic_NT::calc_mdot_expansion(double piston_rate /*m/s*/) +double C_storage_tank_dynamic_cyl::calc_mdot_expansion(double piston_rate /*m/s*/) { double A_outer = CSP::pi * std::pow(m_radius + m_tank_wall_thick, 2.0); double A_inner = CSP::pi * std::pow(m_radius, 2.0); @@ -806,7 +806,7 @@ double C_storage_tank_dynamic_NT::calc_mdot_expansion(double piston_rate /*m/s*/ return mdot_rate; } -double C_storage_tank_dynamic_NT::calc_tank_wall_volume(double fluid_mass /*kg*/, double T_htf /*K*/) +double C_storage_tank_dynamic_cyl::calc_tank_wall_volume(double fluid_mass /*kg*/, double T_htf /*K*/) { double A_outer = CSP::pi * std::pow(m_radius + m_tank_wall_thick, 2.0); double A_inner = CSP::pi * std::pow(m_radius, 2.0); @@ -823,12 +823,12 @@ double C_storage_tank_dynamic_NT::calc_tank_wall_volume(double fluid_mass /*kg*/ return tank_wall_vol; } -double C_storage_tank_dynamic_NT::calc_SA(double volume /*m3*/) +double C_storage_tank_dynamic_cyl::calc_SA(double volume /*m3*/) { return 2.0 * volume / m_radius; } -double C_storage_tank_dynamic_NT::calc_leakage_fraction(double mdot) +double C_storage_tank_dynamic_cyl::calc_leakage_fraction(double mdot) { double N_terms = m_piston_loss_poly.size(); double frac = 0; @@ -844,7 +844,7 @@ double C_storage_tank_dynamic_NT::calc_leakage_fraction(double mdot) -C_csp_NTHeatTrap_tes::C_csp_NTHeatTrap_tes( +C_csp_piston_cylinder_tes::C_csp_piston_cylinder_tes( int external_fl, // [-] external fluid identifier util::matrix_t external_fl_props, // [-] external fluid properties //int tes_fl, // [-] tes fluid identifier @@ -917,7 +917,7 @@ C_csp_NTHeatTrap_tes::C_csp_NTHeatTrap_tes( mc_reported_outputs.construct(S_output_info); } -C_csp_NTHeatTrap_tes::C_csp_NTHeatTrap_tes() +C_csp_piston_cylinder_tes::C_csp_piston_cylinder_tes() { m_vol_tank = m_V_tank_active = m_q_pb_design = m_Q_tes_des = m_V_tank_hot_ini = m_mass_total_active = m_h_tank_calc = m_d_tank_calc = m_q_dot_loss_des = @@ -927,7 +927,7 @@ C_csp_NTHeatTrap_tes::C_csp_NTHeatTrap_tes() } -void C_csp_NTHeatTrap_tes::init(const C_csp_tes::S_csp_tes_init_inputs init_inputs) +void C_csp_piston_cylinder_tes::init(const C_csp_tes::S_csp_tes_init_inputs init_inputs) { if (!(m_Q_tes_des > 0.0)) { @@ -969,7 +969,7 @@ void C_csp_NTHeatTrap_tes::init(const C_csp_tes::S_csp_tes_init_inputs init_inpu throw(C_csp_exception("External HTF code is not recognized", "Two Tank TES Initialization")); } - // For NT Heattrap, storage fluid IS external fluid + // For Piston Cylinder, storage fluid IS external fluid mc_store_htfProps = mc_external_htfProps; //// Declare instance of fluid class for STORAGE fluid. @@ -1048,7 +1048,7 @@ void C_csp_NTHeatTrap_tes::init(const C_csp_tes::S_csp_tes_init_inputs init_inpu // Size Tank with Fixed Height if (m_is_h_fixed) { - heattrap_tes_sizing(mc_store_htfProps, m_Q_tes_des, T_tes_hot_des, T_tes_cold_des, + piston_cylinder_tes_sizing(mc_store_htfProps, m_Q_tes_des, T_tes_hot_des, T_tes_cold_des, m_h_tank_min, m_h_tank_in, m_tank_pairs, m_u_tank, m_V_tank_active, m_vol_tank, m_d_tank_calc, m_q_dot_loss_des); m_h_tank_calc = m_h_tank_in; @@ -1056,7 +1056,7 @@ void C_csp_NTHeatTrap_tes::init(const C_csp_tes::S_csp_tes_init_inputs init_inpu // Size Tank with Fixed Diameter else { - heattrap_tes_sizing_fixed_diameter(mc_store_htfProps, m_Q_tes_des, T_tes_hot_des, T_tes_cold_des, + piston_cylinder_tes_sizing_fixed_diameter(mc_store_htfProps, m_Q_tes_des, T_tes_hot_des, T_tes_cold_des, m_h_tank_min, m_d_tank_in, m_tank_pairs, m_u_tank, m_V_tank_active, m_vol_tank, m_h_tank_calc, m_q_dot_loss_des); m_d_tank_calc = m_d_tank_in; @@ -1098,16 +1098,16 @@ void C_csp_NTHeatTrap_tes::init(const C_csp_tes::S_csp_tes_init_inputs init_inpu // Initialize cold and hot tanks // Hot tank - mc_hot_tank_NT.init(mc_store_htfProps, m_vol_tank, m_h_tank_calc, m_h_tank_min, + mc_hot_tank_cyl.init(mc_store_htfProps, m_vol_tank, m_h_tank_calc, m_h_tank_min, m_u_tank, m_tank_pairs, m_hot_tank_Thtr, m_hot_tank_max_heat, V_hot_ini, T_hot_ini, T_tes_hot_des, m_tank_wall_cp, m_tank_wall_dens, m_tank_wall_thick, m_nstep, m_piston_loss_poly); // Cold tank - mc_cold_tank_NT.init(mc_store_htfProps, m_vol_tank, m_h_tank_calc, m_h_tank_min, + mc_cold_tank_cyl.init(mc_store_htfProps, m_vol_tank, m_h_tank_calc, m_h_tank_min, m_u_tank, m_tank_pairs, m_cold_tank_Thtr, m_cold_tank_max_heat, V_cold_ini, T_cold_ini, T_tes_cold_des, m_tank_wall_cp, m_tank_wall_dens, m_tank_wall_thick, m_nstep, m_piston_loss_poly); - double hot_radius = mc_hot_tank_NT.get_radius(); - double cold_radius = mc_cold_tank_NT.get_radius(); + double hot_radius = mc_hot_tank_cyl.get_radius(); + double cold_radius = mc_cold_tank_cyl.get_radius(); if (hot_radius != cold_radius) { @@ -1123,32 +1123,32 @@ void C_csp_NTHeatTrap_tes::init(const C_csp_tes::S_csp_tes_init_inputs init_inpu } -bool C_csp_NTHeatTrap_tes::does_tes_exist() +bool C_csp_piston_cylinder_tes::does_tes_exist() { return m_is_tes; } -bool C_csp_NTHeatTrap_tes::is_cr_to_cold_allowed() +bool C_csp_piston_cylinder_tes::is_cr_to_cold_allowed() { return m_is_cr_to_cold_tank_allowed; } -double C_csp_NTHeatTrap_tes::get_hot_temp() +double C_csp_piston_cylinder_tes::get_hot_temp() { - return mc_hot_tank_NT.get_m_T_prev(); //[K] + return mc_hot_tank_cyl.get_m_T_prev(); //[K] } -double C_csp_NTHeatTrap_tes::get_cold_temp() +double C_csp_piston_cylinder_tes::get_cold_temp() { - return mc_cold_tank_NT.get_m_T_prev(); //[K] + return mc_cold_tank_cyl.get_m_T_prev(); //[K] } -double C_csp_NTHeatTrap_tes::get_hot_tank_vol_frac() +double C_csp_piston_cylinder_tes::get_hot_tank_vol_frac() { - return mc_hot_tank_NT.get_vol_frac(); + return mc_hot_tank_cyl.get_vol_frac(); } -double C_csp_NTHeatTrap_tes::get_initial_charge_energy() +double C_csp_piston_cylinder_tes::get_initial_charge_energy() { //MWh if (std::isnan(m_V_tank_hot_ini)) @@ -1162,18 +1162,18 @@ double C_csp_NTHeatTrap_tes::get_initial_charge_energy() } } -double C_csp_NTHeatTrap_tes::get_min_charge_energy() +double C_csp_piston_cylinder_tes::get_min_charge_energy() { //MWh return 0.; //m_q_pb_design * m_ts_hours * m_h_tank_min / m_h_tank*1.e-6; } -double C_csp_NTHeatTrap_tes::get_max_charge_energy() +double C_csp_piston_cylinder_tes::get_max_charge_energy() { return m_q_pb_design * m_ts_hours / 1.e6; } -double C_csp_NTHeatTrap_tes::get_degradation_rate() +double C_csp_piston_cylinder_tes::get_degradation_rate() { //calculates an approximate "average" tank heat loss rate based on some assumptions. Good for simple optimization performance projections. double d_tank = sqrt(m_vol_tank / ((double)m_tank_pairs * m_h_tank_calc * 3.14159)); @@ -1181,7 +1181,7 @@ double C_csp_NTHeatTrap_tes::get_degradation_rate() return e_loss / (m_q_pb_design * m_ts_hours * 3600.); //s^-1 -- fraction of heat loss per second based on full charge } -void C_csp_NTHeatTrap_tes::reset_storage_to_initial_state() +void C_csp_piston_cylinder_tes::reset_storage_to_initial_state() { // Initial storage charge based on % mass double Q_tes_des = m_q_pb_design / 1.E6 * m_ts_hours; //[MWt-hr] TES thermal capacity at design @@ -1199,21 +1199,21 @@ void C_csp_NTHeatTrap_tes::reset_storage_to_initial_state() // Initialize cold and hot tanks // Hot tank - mc_hot_tank_NT.init(mc_store_htfProps, m_vol_tank, m_h_tank_calc, m_h_tank_min, + mc_hot_tank_cyl.init(mc_store_htfProps, m_vol_tank, m_h_tank_calc, m_h_tank_min, m_u_tank, m_tank_pairs, m_hot_tank_Thtr, m_hot_tank_max_heat, V_hot_ini, T_hot_ini, m_T_hot_des, m_tank_wall_cp, m_tank_wall_dens, m_tank_wall_thick, m_nstep, m_piston_loss_poly); // Cold tank - mc_cold_tank_NT.init(mc_store_htfProps, m_vol_tank, m_h_tank_calc, m_h_tank_min, + mc_cold_tank_cyl.init(mc_store_htfProps, m_vol_tank, m_h_tank_calc, m_h_tank_min, m_u_tank, m_tank_pairs, m_cold_tank_Thtr, m_cold_tank_max_heat, V_cold_ini, T_cold_ini, m_T_cold_des, m_tank_wall_cp, m_tank_wall_dens, m_tank_wall_thick, m_nstep, m_piston_loss_poly); } -void C_csp_NTHeatTrap_tes::discharge_avail_est(double T_cold_K, double step_s, +void C_csp_piston_cylinder_tes::discharge_avail_est(double T_cold_K, double step_s, double& q_dot_dc_est /*MWt*/, double& m_dot_external_est /*kg/s*/, double& T_hot_external_est /*K*/) { double f_storage = 0.0; // for now, hardcode such that storage always completely discharges - double m_dot_tank_disch_avail = mc_hot_tank_NT.m_dot_available(f_storage, step_s); //[kg/s] + double m_dot_tank_disch_avail = mc_hot_tank_cyl.m_dot_available(f_storage, step_s); //[kg/s] if (m_dot_tank_disch_avail == 0) { q_dot_dc_est = 0.; @@ -1222,7 +1222,7 @@ void C_csp_NTHeatTrap_tes::discharge_avail_est(double T_cold_K, double step_s, return; } - double T_hot_ini = mc_hot_tank_NT.get_m_T_prev(); //[K] + double T_hot_ini = mc_hot_tank_cyl.get_m_T_prev(); //[K] double cp_T_avg = mc_store_htfProps.Cp_ave(T_cold_K, T_hot_ini); //[kJ/kg-K] spec heat at average temperature during discharge from hot to cold q_dot_dc_est = m_dot_tank_disch_avail * cp_T_avg * (T_hot_ini - T_cold_K) * 1.E-3; //[MW] @@ -1231,7 +1231,7 @@ void C_csp_NTHeatTrap_tes::discharge_avail_est(double T_cold_K, double step_s, } -void C_csp_NTHeatTrap_tes::charge_avail_est(double T_hot_K, double step_s, +void C_csp_piston_cylinder_tes::charge_avail_est(double T_hot_K, double step_s, double& q_dot_ch_est /*MWt*/, double& m_dot_external_est /*kg/s*/, double& T_cold_external_est /*K*/) { // Only allow charge up to the 'main' tank volume @@ -1243,14 +1243,14 @@ void C_csp_NTHeatTrap_tes::charge_avail_est(double T_hot_K, double step_s, // This is amount of cold mass in tank (available for use - i.e., not dead space volume) - double m_dot_tank_charge_avail = mc_cold_tank_NT.m_dot_available(f_ch_storage, step_s); //[kg/s] + double m_dot_tank_charge_avail = mc_cold_tank_cyl.m_dot_available(f_ch_storage, step_s); //[kg/s] - double T_cold_ini = mc_cold_tank_NT.get_m_T_prev(); //[K] + double T_cold_ini = mc_cold_tank_cyl.get_m_T_prev(); //[K] // for debugging - double T_hot_ini = mc_hot_tank_NT.get_m_T_prev(); //[K] + double T_hot_ini = mc_hot_tank_cyl.get_m_T_prev(); //[K] double cp_T_tanks_avg = mc_store_htfProps.Cp_ave(T_cold_ini, T_hot_ini); // [kJ/kg-K] - double mass_avail_hot_tank = mc_hot_tank_NT.m_dot_available(f_ch_storage, step_s) * step_s; //[kg] + double mass_avail_hot_tank = mc_hot_tank_cyl.m_dot_available(f_ch_storage, step_s) * step_s; //[kg] double tes_charge_state = mass_avail_hot_tank * cp_T_tanks_avg * (T_hot_ini - T_cold_ini) * 1.e-3 / 3600.; // [MWht] double cp_T_avg = mc_store_htfProps.Cp_ave(T_cold_ini, T_hot_K); //[kJ/kg-K] spec heat at average temperature during charging from cold to hot @@ -1260,7 +1260,7 @@ void C_csp_NTHeatTrap_tes::charge_avail_est(double T_hot_K, double step_s, } -int C_csp_NTHeatTrap_tes::solve_tes_off_design(double timestep /*s*/, double T_amb /*K*/, +int C_csp_piston_cylinder_tes::solve_tes_off_design(double timestep /*s*/, double T_amb /*K*/, double m_dot_cr_to_cv_hot /*kg/s*/, double m_dot_cv_hot_to_sink /*kg/s*/, double m_dot_cr_to_cv_cold /*kg/s*/, double T_cr_out_hot /*K*/, double T_sink_out_cold /*K*/, double& T_sink_htf_in_hot /*K*/, double& T_cr_in_cold /*K*/, @@ -1412,11 +1412,11 @@ int C_csp_NTHeatTrap_tes::solve_tes_off_design(double timestep /*s*/, double T_ double expansion_wall_mass = 0.0; double expansion_wall_L = 0.0; { - double wall_mass_hot = mc_hot_tank_NT.get_m_m_wall_calc(); - double wall_mass_cold = mc_cold_tank_NT.get_m_m_wall_calc(); + double wall_mass_hot = mc_hot_tank_cyl.get_m_m_wall_calc(); + double wall_mass_cold = mc_cold_tank_cyl.get_m_m_wall_calc(); - double L_hot = mc_hot_tank_NT.get_m_L_calc(); - double L_cold = mc_cold_tank_NT.get_m_L_calc(); + double L_hot = mc_hot_tank_cyl.get_m_L_calc(); + double L_cold = mc_cold_tank_cyl.get_m_L_calc(); expansion_wall_mass = (wall_mass_hot + wall_mass_cold) - m_mass_wall_nominal; expansion_wall_L = (L_hot + L_cold) - m_h_tank_calc; // This is length of 'bonus' tank @@ -1430,10 +1430,10 @@ int C_csp_NTHeatTrap_tes::solve_tes_off_design(double timestep /*s*/, double T_ // Calculate NT Specific Outputs - double mass_cold = mc_cold_tank_NT.get_m_m_calc(); - double mass_hot = mc_hot_tank_NT.get_m_m_calc(); - double vol_cold = mc_cold_tank_NT.get_fluid_vol(); - double vol_hot = mc_hot_tank_NT.get_fluid_vol(); + double mass_cold = mc_cold_tank_cyl.get_m_m_calc(); + double mass_hot = mc_hot_tank_cyl.get_m_m_calc(); + double vol_cold = mc_cold_tank_cyl.get_fluid_vol(); + double vol_hot = mc_hot_tank_cyl.get_fluid_vol(); double vol_tot = vol_cold + vol_hot; double vol_tot_assigned = CSP::pi * std::pow(m_radius, 2.0) * m_length_total; double mass_tot = mass_cold + mass_hot; @@ -1464,8 +1464,8 @@ int C_csp_NTHeatTrap_tes::solve_tes_off_design(double timestep /*s*/, double T_ mc_reported_outputs.value(E_TES_T_HOT, T_hot_final - 273.15); //[C] mc_reported_outputs.value(E_TES_T_COLD, T_cold_final - 273.15); //[C] mc_reported_outputs.value(E_M_DOT_TANK_TO_TANK, m_dot_cold_tank_to_hot_tank); //[kg/s] - mc_reported_outputs.value(E_MASS_COLD_TANK, mc_cold_tank_NT.get_m_m_calc()); //[kg] - mc_reported_outputs.value(E_MASS_HOT_TANK, mc_hot_tank_NT.get_m_m_calc()); //[kg] + mc_reported_outputs.value(E_MASS_COLD_TANK, mc_cold_tank_cyl.get_m_m_calc()); //[kg] + mc_reported_outputs.value(E_MASS_HOT_TANK, mc_hot_tank_cyl.get_m_m_calc()); //[kg] mc_reported_outputs.value(E_W_DOT_HTF_PUMP, W_dot_htf_pump); //[MWe] // Added NT Outputs @@ -1476,15 +1476,15 @@ int C_csp_NTHeatTrap_tes::solve_tes_off_design(double timestep /*s*/, double T_ mc_reported_outputs.value(E_PIST_FRAC, piston_frac); //[] mc_reported_outputs.value(E_COLD_FRAC, cold_frac); //[] mc_reported_outputs.value(E_MASS_TOT, mass_tot); //[kg] - mc_reported_outputs.value(E_SA_COLD, mc_cold_tank_NT.get_SA_calc()); //[m2] - mc_reported_outputs.value(E_SA_HOT, mc_hot_tank_NT.get_SA_calc()); //[m2] - mc_reported_outputs.value(E_SA_TOT, mc_cold_tank_NT.get_SA_calc() + mc_hot_tank_NT.get_SA_calc()); //[m2] + mc_reported_outputs.value(E_SA_COLD, mc_cold_tank_cyl.get_SA_calc()); //[m2] + mc_reported_outputs.value(E_SA_HOT, mc_hot_tank_cyl.get_SA_calc()); //[m2] + mc_reported_outputs.value(E_SA_TOT, mc_cold_tank_cyl.get_SA_calc() + mc_hot_tank_cyl.get_SA_calc()); //[m2] mc_reported_outputs.value(E_ERROR, q_dot_error_total); //[MW] mc_reported_outputs.value(E_ERROR_PERCENT, energy_balance_error_percent); //[%] mc_reported_outputs.value(E_LEAK_ERROR, q_dot_error_leak); //[MWt] - mc_reported_outputs.value(E_E_HOT, mc_hot_tank_NT.get_m_E_calc()); //[MJ] - mc_reported_outputs.value(E_E_COLD, mc_cold_tank_NT.get_m_E_calc());//[MJ] - mc_reported_outputs.value(E_E_COLD, mc_cold_tank_NT.get_m_E_calc());//[MJ] + mc_reported_outputs.value(E_E_HOT, mc_hot_tank_cyl.get_m_E_calc()); //[MJ] + mc_reported_outputs.value(E_E_COLD, mc_cold_tank_cyl.get_m_E_calc());//[MJ] + mc_reported_outputs.value(E_E_COLD, mc_cold_tank_cyl.get_m_E_calc());//[MJ] mc_reported_outputs.value(E_WALL_ERROR, q_dot_error_wall); //[MWt] mc_reported_outputs.value(E_ERROR_CORRECTED, q_dot_error_corrected); //[MW] mc_reported_outputs.value(E_EXP_WALL_MASS, expansion_wall_mass); //[kg] @@ -1493,14 +1493,14 @@ int C_csp_NTHeatTrap_tes::solve_tes_off_design(double timestep /*s*/, double T_ return 0; } -void C_csp_NTHeatTrap_tes::converged() +void C_csp_piston_cylinder_tes::converged() { - mc_cold_tank_NT.converged(); - mc_hot_tank_NT.converged(); + mc_cold_tank_cyl.converged(); + mc_hot_tank_cyl.converged(); //mc_hx.converged(); // Set reported sink converged values - mc_reported_outputs.value(E_HOT_TANK_HTF_PERC_FINAL, mc_hot_tank_NT.get_mass_avail() / m_mass_total_active * 100.0); + mc_reported_outputs.value(E_HOT_TANK_HTF_PERC_FINAL, mc_hot_tank_cyl.get_mass_avail() / m_mass_total_active * 100.0); mc_reported_outputs.set_timestep_outputs(); @@ -1509,22 +1509,22 @@ void C_csp_NTHeatTrap_tes::converged() // m_m_dot_tes_dc_max = m_m_dot_tes_ch_max = std::numeric_limits::quiet_NaN(); } -void C_csp_NTHeatTrap_tes::write_output_intervals(double report_time_start, +void C_csp_piston_cylinder_tes::write_output_intervals(double report_time_start, const std::vector& v_temp_ts_time_end, double report_time_end) { mc_reported_outputs.send_to_reporting_ts_array(report_time_start, v_temp_ts_time_end, report_time_end); } -void C_csp_NTHeatTrap_tes::assign(int index, double* p_reporting_ts_array, size_t n_reporting_ts_array) +void C_csp_piston_cylinder_tes::assign(int index, double* p_reporting_ts_array, size_t n_reporting_ts_array) { mc_reported_outputs.assign(index, p_reporting_ts_array, n_reporting_ts_array); } -double /*MWe*/ C_csp_NTHeatTrap_tes::pumping_power(double m_dot_sf /*kg/s*/, double m_dot_pb /*kg/s*/, double m_dot_tank /*kg/s*/, +double /*MWe*/ C_csp_piston_cylinder_tes::pumping_power(double m_dot_sf /*kg/s*/, double m_dot_pb /*kg/s*/, double m_dot_tank /*kg/s*/, double T_sf_in /*K*/, double T_sf_out /*K*/, double T_pb_in /*K*/, double T_pb_out /*K*/, bool recirculating) { - // NTHeattrap never uses a hx -> pumping power = 0 (following two tank logic) + // Piston Cylinder never uses a hx -> pumping power = 0 (following two tank logic) // Might want to use tes_pump_coef here? double htf_pump_power = 0.0; //[MWe] @@ -1533,7 +1533,7 @@ double /*MWe*/ C_csp_NTHeatTrap_tes::pumping_power(double m_dot_sf /*kg/s*/, dou } -void C_csp_NTHeatTrap_tes::get_design_parameters(double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, +void C_csp_piston_cylinder_tes::get_design_parameters(double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, double& h_tank_calc /*m*/, double& d_tank_calc /*m*/, double& q_dot_loss_des /*MWt*/, double& dens_store_htf_at_T_ave /*kg/m3*/, double& Q_tes /*MWt-hr*/) { @@ -1546,7 +1546,7 @@ void C_csp_NTHeatTrap_tes::get_design_parameters(double& vol_one_temp_avail /*m3 Q_tes = m_Q_tes_des; //[MWt-hr] } -bool C_csp_NTHeatTrap_tes::charge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, +bool C_csp_piston_cylinder_tes::charge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, double T_htf_hot_in /*K*/, double& T_htf_cold_out /*K*/, double& q_dot_heater /*MWe*/, double& m_dot_tank_to_tank /*kg/s*/, double& W_dot_rhtf_pump /*MWe*/, double& q_dot_loss /*MWt*/, double& q_dot_dc_to_htf /*MWt*/, double& q_dot_ch_from_htf /*MWt*/, @@ -1574,7 +1574,7 @@ bool C_csp_NTHeatTrap_tes::charge(double timestep /*s*/, double T_amb /*K*/, dou q_dot_ch_est = m_dot_tes_ch_max = T_cold_to_src_est = std::numeric_limits::quiet_NaN(); charge_avail_est(T_htf_hot_in, timestep, q_dot_ch_est, m_dot_tes_ch_max, T_cold_to_src_est); - double m_dot_htf_in_net = m_dot_htf_in * (1.0 - mc_hot_tank_NT.calc_leakage_fraction(m_dot_htf_in)); + double m_dot_htf_in_net = m_dot_htf_in * (1.0 - mc_hot_tank_cyl.calc_leakage_fraction(m_dot_htf_in)); if (m_dot_htf_in_net > 1.0001 * m_dot_tes_ch_max && m_dot_htf_in_net > 1.E-6) { @@ -1600,8 +1600,8 @@ bool C_csp_NTHeatTrap_tes::charge(double timestep /*s*/, double T_amb /*K*/, dou m_dot_src = m_dot_tank = m_dot_htf_in; T_src_hot_in = T_hot_tank_in = T_htf_hot_in; - double T_hot_prev = mc_hot_tank_NT.get_m_T_prev(); - double T_cold_prev = mc_cold_tank_NT.get_m_T_prev(); + double T_hot_prev = mc_hot_tank_cyl.get_m_T_prev(); + double T_cold_prev = mc_cold_tank_cyl.get_m_T_prev(); solve_tanks_iterative(timestep, m_nstep, m_dot_tank, 0, T_hot_tank_in, 0, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold, q_dot_out_cold, q_dot_error_cold, @@ -1619,8 +1619,8 @@ bool C_csp_NTHeatTrap_tes::charge(double timestep /*s*/, double T_amb /*K*/, dou q_dot_dc_to_htf = 0.0; //[MWt] T_hot_ave = T_hot_ave; //[K] T_cold_ave = T_cold_ave; //[K] - T_hot_final = mc_hot_tank_NT.get_m_T_calc(); //[K] - T_cold_final = mc_cold_tank_NT.get_m_T_calc(); //[K] + T_hot_final = mc_hot_tank_cyl.get_m_T_calc(); //[K] + T_cold_final = mc_cold_tank_cyl.get_m_T_calc(); //[K] // Calculate thermal power to HTF double cp_htf_ave = mc_external_htfProps.Cp_ave(T_htf_cold_out, T_htf_hot_in); //[kJ/kg-K] @@ -1631,7 +1631,7 @@ bool C_csp_NTHeatTrap_tes::charge(double timestep /*s*/, double T_amb /*K*/, dou } -bool C_csp_NTHeatTrap_tes::discharge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, +bool C_csp_piston_cylinder_tes::discharge(double timestep /*s*/, double T_amb /*K*/, double m_dot_htf_in /*kg/s*/, double T_htf_cold_in /*K*/, double& T_htf_hot_out /*K*/, double& q_dot_heater /*MWe*/, double& m_dot_tank_to_tank /*kg/s*/, double& W_dot_rhtf_pump /*MWe*/, double& q_dot_loss /*MWt*/, double& q_dot_dc_to_htf /*MWt*/, double& q_dot_ch_from_htf /*MWt*/, @@ -1654,7 +1654,7 @@ bool C_csp_NTHeatTrap_tes::discharge(double timestep /*s*/, double T_amb /*K*/, q_dot_dc_est = m_dot_tes_dc_max = T_hot_to_pc_est = std::numeric_limits::quiet_NaN(); discharge_avail_est(T_htf_cold_in, timestep, q_dot_dc_est, m_dot_tes_dc_max, T_hot_to_pc_est); - double m_dot_htf_in_net = m_dot_htf_in * (1.0 - mc_hot_tank_NT.calc_leakage_fraction(m_dot_htf_in)); + double m_dot_htf_in_net = m_dot_htf_in * (1.0 - mc_hot_tank_cyl.calc_leakage_fraction(m_dot_htf_in)); if (m_dot_htf_in_net > 1.0001 * m_dot_tes_dc_max && m_dot_htf_in_net > 1.E-6) // mass flow in = mass flow out { @@ -1680,8 +1680,8 @@ bool C_csp_NTHeatTrap_tes::discharge(double timestep /*s*/, double T_amb /*K*/, m_dot_src = m_dot_tank = m_dot_htf_in; T_src_cold_in = T_cold_tank_in = T_htf_cold_in; - double T_hot_prev = mc_hot_tank_NT.get_m_T_prev(); - double T_cold_prev = mc_cold_tank_NT.get_m_T_prev(); + double T_hot_prev = mc_hot_tank_cyl.get_m_T_prev(); + double T_cold_prev = mc_cold_tank_cyl.get_m_T_prev(); solve_tanks_iterative(timestep, m_nstep, 0, m_dot_tank, 0, T_cold_tank_in, T_amb, T_cold_ave, q_heater_cold, q_dot_loss_cold, q_dot_out_cold, q_dot_error_cold, @@ -1698,8 +1698,8 @@ bool C_csp_NTHeatTrap_tes::discharge(double timestep /*s*/, double T_amb /*K*/, q_dot_ch_from_htf = 0.0; //[MWt] T_hot_ave = T_hot_ave; //[K] T_cold_ave = T_cold_ave; //[K] - T_hot_final = mc_hot_tank_NT.get_m_T_calc(); //[K] - T_cold_final = mc_cold_tank_NT.get_m_T_calc(); //[K] + T_hot_final = mc_hot_tank_cyl.get_m_T_calc(); //[K] + T_cold_final = mc_cold_tank_cyl.get_m_T_calc(); //[K] // Calculate thermal power to HTF double cp_htf_ave = mc_external_htfProps.Cp_ave(T_htf_cold_in, T_htf_hot_out); //[kJ/kg-K] @@ -1708,7 +1708,7 @@ bool C_csp_NTHeatTrap_tes::discharge(double timestep /*s*/, double T_amb /*K*/, return true; } -void C_csp_NTHeatTrap_tes::solve_tanks_iterative(double timestep /*s*/, double n_substep /**/, double mdot_charge /*kg/s*/, +void C_csp_piston_cylinder_tes::solve_tanks_iterative(double timestep /*s*/, double n_substep /**/, double mdot_charge /*kg/s*/, double mdot_discharge /*kg/s*/, double T_charge /*K*/, double T_discharge /*K*/, double T_amb /*K*/, double& T_ave_cold /*K*/, double& q_heater_cold /*MW*/, double& q_dot_loss_cold /*MW*/, double& q_dot_out_cold /*MW*/, double& q_dot_error_cold /*MW*/, @@ -1723,9 +1723,9 @@ void C_csp_NTHeatTrap_tes::solve_tanks_iterative(double timestep /*s*/, double n double q_heater_summed_cold = 0; double q_dot_loss_summed_cold = 0; double T_ave_innerstep_cold = 0; - double mass_prev_inner_cold = mc_cold_tank_NT.get_m_m_prev(); + double mass_prev_inner_cold = mc_cold_tank_cyl.get_m_m_prev(); double mass_calc_inner_cold = std::numeric_limits::quiet_NaN(); - double T_prev_inner_cold = mc_cold_tank_NT.get_m_T_prev(); + double T_prev_inner_cold = mc_cold_tank_cyl.get_m_T_prev(); double T_calc_inner_cold = std::numeric_limits::quiet_NaN(); double T_ave_weighted_cold = 0; double q_dot_out_summed_cold = 0; @@ -1735,9 +1735,9 @@ void C_csp_NTHeatTrap_tes::solve_tanks_iterative(double timestep /*s*/, double n double q_heater_summed_hot = 0; double q_dot_loss_summed_hot = 0; double T_ave_innerstep_hot = 0; - double mass_prev_inner_hot = mc_hot_tank_NT.get_m_m_prev(); + double mass_prev_inner_hot = mc_hot_tank_cyl.get_m_m_prev(); double mass_calc_inner_hot = std::numeric_limits::quiet_NaN(); - double T_prev_inner_hot = mc_hot_tank_NT.get_m_T_prev(); + double T_prev_inner_hot = mc_hot_tank_cyl.get_m_T_prev(); double T_calc_inner_hot = std::numeric_limits::quiet_NaN(); double T_ave_weighted_hot = 0; double q_dot_out_summed_hot = 0; @@ -1793,13 +1793,13 @@ void C_csp_NTHeatTrap_tes::solve_tanks_iterative(double timestep /*s*/, double n = std::numeric_limits::quiet_NaN(); // Simulate Cold Tank - mc_cold_tank_NT.energy_balance_core(ministep, mdot_in_cold, mdot_out_cold, T_in_cold, T_amb, mass_prev_inner_cold, + mc_cold_tank_cyl.energy_balance_core(ministep, mdot_in_cold, mdot_out_cold, T_in_cold, T_amb, mass_prev_inner_cold, T_prev_inner_hot, T_prev_inner_cold, T_prev_inner_hot, T_ave_innerstep_cold, q_heater_innerstep_cold, q_dot_loss_innerstep_cold, mass_calc_inner_cold, T_calc_inner_cold, q_dot_out_inner_cold, q_dot_error_innerstep_cold); // Simulate Hot Tank - mc_hot_tank_NT.energy_balance_core(ministep, mdot_in_hot, mdot_out_hot, T_in_hot, T_amb, mass_prev_inner_hot, + mc_hot_tank_cyl.energy_balance_core(ministep, mdot_in_hot, mdot_out_hot, T_in_hot, T_amb, mass_prev_inner_hot, T_prev_inner_cold, T_prev_inner_hot, T_prev_inner_cold, T_ave_innerstep_hot, q_heater_innerstep_hot, q_dot_loss_innerstep_hot, mass_calc_inner_hot, T_calc_inner_hot, q_dot_out_inner_hot, q_dot_error_innerstep_hot); @@ -1864,7 +1864,7 @@ void C_csp_NTHeatTrap_tes::solve_tanks_iterative(double timestep /*s*/, double n // Initial Energy double T_cold_prev = T_prev_inner_cold; double mass_fluid_cold_prev = mass_prev_inner_cold; - mass_wall_cold_prev = mc_cold_tank_NT.calc_mass_wall(T_cold_prev, mass_fluid_cold_prev); // kg + mass_wall_cold_prev = mc_cold_tank_cyl.calc_mass_wall(T_cold_prev, mass_fluid_cold_prev); // kg double cp_cold_fluid_prev = mc_external_htfProps.Cp(T_cold_prev) * 1e-3; // MJ/kg K double Q_cold_fluid_prev = T_cold_prev * mass_fluid_cold_prev * cp_cold_fluid_prev; // MJ @@ -1872,7 +1872,7 @@ void C_csp_NTHeatTrap_tes::solve_tanks_iterative(double timestep /*s*/, double n // Final Energy double mass_fluid_cold_final = mass_calc_inner_cold; - mass_wall_cold_final = mc_cold_tank_NT.get_m_m_wall_calc(); + mass_wall_cold_final = mc_cold_tank_cyl.get_m_m_wall_calc(); double cp_cold_fluid_final = mc_external_htfProps.Cp(T_calc_inner_cold) * 1e-3; // MJ/kg K double Q_cold_fluid_final = T_calc_inner_cold * mass_fluid_cold_final * cp_cold_fluid_final; // MJ @@ -1891,7 +1891,7 @@ void C_csp_NTHeatTrap_tes::solve_tanks_iterative(double timestep /*s*/, double n // Initial Energy double T_hot_prev = T_prev_inner_hot; double mass_fluid_hot_prev = mass_prev_inner_hot; - mass_wall_hot_prev = mc_hot_tank_NT.calc_mass_wall(T_hot_prev, mass_fluid_hot_prev); // kg + mass_wall_hot_prev = mc_hot_tank_cyl.calc_mass_wall(T_hot_prev, mass_fluid_hot_prev); // kg double cp_hot_fluid_prev = mc_external_htfProps.Cp(T_hot_prev) * 1e-3; // MJ/kg K double Q_hot_fluid_prev = T_hot_prev * mass_fluid_hot_prev * cp_hot_fluid_prev; // MJ @@ -1900,7 +1900,7 @@ void C_csp_NTHeatTrap_tes::solve_tanks_iterative(double timestep /*s*/, double n // Final Energy double T_hot = T_calc_inner_hot; double mass_fluid_hot_final = mass_calc_inner_hot; - mass_wall_hot_final = mc_hot_tank_NT.get_m_m_wall_calc(); + mass_wall_hot_final = mc_hot_tank_cyl.get_m_m_wall_calc(); double cp_hot_fluid_final = mc_external_htfProps.Cp(T_hot) * 1e-3; // MJ/kg K double Q_hot_fluid_final = T_hot * mass_fluid_hot_final * cp_hot_fluid_final; // MJ @@ -1989,7 +1989,7 @@ void C_csp_NTHeatTrap_tes::solve_tanks_iterative(double timestep /*s*/, double n // Hot side is expanding, taking on cold wall double T_wall_assumed = T_prev_inner_cold; //[K] double T_wall_actual = T_ave_innerstep_cold; //[K] - double mdot_wall = (mc_hot_tank_NT.get_m_m_wall_calc() - mass_wall_hot_prev) / ministep; + double mdot_wall = (mc_hot_tank_cyl.get_m_m_wall_calc() - mass_wall_hot_prev) / ministep; energy_error_wall = mdot_wall * m_tank_wall_cp * (T_wall_actual - T_wall_assumed) * 1e-6; //[MW] } @@ -1999,7 +1999,7 @@ void C_csp_NTHeatTrap_tes::solve_tanks_iterative(double timestep /*s*/, double n // Cold side is expanding, taking on hot wall double T_wall_assumed = T_prev_inner_hot; //[K] double T_wall_actual = T_ave_innerstep_hot; //[K] - double mdot_wall = (mc_cold_tank_NT.get_m_m_wall_calc() - mass_wall_cold_prev) / ministep; + double mdot_wall = (mc_cold_tank_cyl.get_m_m_wall_calc() - mass_wall_cold_prev) / ministep; energy_error_wall = mdot_wall * m_tank_wall_cp * (T_wall_actual - T_wall_assumed) * 1e-6; //[MW] } @@ -2053,32 +2053,32 @@ void C_csp_NTHeatTrap_tes::solve_tanks_iterative(double timestep /*s*/, double n } -double C_csp_NTHeatTrap_tes::get_min_storage_htf_temp() +double C_csp_piston_cylinder_tes::get_min_storage_htf_temp() { return mc_store_htfProps.min_temp(); } -double C_csp_NTHeatTrap_tes::get_max_storage_htf_temp() +double C_csp_piston_cylinder_tes::get_max_storage_htf_temp() { return mc_store_htfProps.max_temp(); } -double C_csp_NTHeatTrap_tes::get_storage_htf_density() +double C_csp_piston_cylinder_tes::get_storage_htf_density() { double avg_temp = (m_T_cold_des + m_T_hot_des) / 2.0; return mc_store_htfProps.dens(avg_temp, 0); } -double C_csp_NTHeatTrap_tes::get_storage_htf_cp() +double C_csp_piston_cylinder_tes::get_storage_htf_cp() { double avg_temp = (m_T_cold_des + m_T_hot_des) / 2.0; return mc_store_htfProps.Cp(avg_temp); } -void C_csp_NTHeatTrap_tes::calc_piston_location(double &piston_loc, double &piston_frac) +void C_csp_piston_cylinder_tes::calc_piston_location(double &piston_loc, double &piston_frac) { - double vol_cold = mc_cold_tank_NT.get_fluid_vol_prev(); - double vol_hot = mc_hot_tank_NT.get_fluid_vol_prev(); + double vol_cold = mc_cold_tank_cyl.get_fluid_vol_prev(); + double vol_hot = mc_hot_tank_cyl.get_fluid_vol_prev(); double length_cold = vol_cold / (CSP::pi * std::pow(m_radius, 2.0)); double length_hot = vol_hot / (CSP::pi * std::pow(m_radius, 2.0)); diff --git a/tcs/csp_solver_NTHeatTrap_tes.h b/tcs/csp_solver_piston_cylinder_tes.h similarity index 97% rename from tcs/csp_solver_NTHeatTrap_tes.h rename to tcs/csp_solver_piston_cylinder_tes.h index 8c0e7a501..acacc3d13 100644 --- a/tcs/csp_solver_NTHeatTrap_tes.h +++ b/tcs/csp_solver_piston_cylinder_tes.h @@ -30,15 +30,15 @@ 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 __csp_solver_NTHeatTrap_tes_ -#define __csp_solver_NTHeatTrap_tes_ +#ifndef __csp_solver_piston_cylinder_tes_ +#define __csp_solver_piston_cylinder_tes_ #include "csp_solver_core.h" #include "csp_solver_util.h" #include "sam_csp_util.h" #include "csp_solver_tes_core.h" -class C_storage_tank_dynamic_NT +class C_storage_tank_dynamic_cyl { private: HTFProperties mc_htf; @@ -97,7 +97,7 @@ class C_storage_tank_dynamic_NT public: - C_storage_tank_dynamic_NT(); + C_storage_tank_dynamic_cyl(); double calc_mass_at_prev(); @@ -170,15 +170,15 @@ class C_storage_tank_dynamic_NT }; -class C_csp_NTHeatTrap_tes : public C_csp_tes +class C_csp_piston_cylinder_tes : public C_csp_tes { private: HTFProperties mc_external_htfProps; // Instance of HTFProperties class for external HTF HTFProperties mc_store_htfProps; // Instance of HTFProperties class for storage HTF - C_storage_tank_dynamic_NT mc_cold_tank_NT; // Instance of storage tank class for the cold tank - C_storage_tank_dynamic_NT mc_hot_tank_NT; // Instance of storage tank class for the hot tank + C_storage_tank_dynamic_cyl mc_cold_tank_cyl; // Instance of storage tank class for the cold tank + C_storage_tank_dynamic_cyl mc_hot_tank_cyl; // Instance of storage tank class for the hot tank // member string for exception messages std::string error_msg; @@ -205,7 +205,7 @@ class C_csp_NTHeatTrap_tes : public C_csp_tes double m_m_dot_tes_des_over_m_dot_external_des; //[-] - // Added for NT + // Added for Piston cylinder model double m_tank_wall_cp; //[J/kg-K] double m_tank_wall_dens; //[kg/m3] double m_tank_wall_thick; //[m] @@ -243,7 +243,7 @@ class C_csp_NTHeatTrap_tes : public C_csp_tes E_HOT_TANK_HTF_PERC_FINAL, //[%] Final percent fill of available hot tank mass E_W_DOT_HTF_PUMP, //[MWe] - // NT Only Outputs + // Piston Cylinder Only Outputs E_VOL_COLD, E_VOL_HOT, E_VOL_TOT, @@ -312,7 +312,7 @@ class C_csp_NTHeatTrap_tes : public C_csp_tes double P_in_des; //[bar] Pressure at the inlet to the TES, at the external system side - C_csp_NTHeatTrap_tes( + C_csp_piston_cylinder_tes( int external_fl, util::matrix_t external_fl_props, //int tes_fl, @@ -358,9 +358,9 @@ class C_csp_NTHeatTrap_tes : public C_csp_tes double dP_discharge = std::numeric_limits::quiet_NaN() // [bar] Pressure drop on the TES discharge side (e.g., within the steam generator) ); - C_csp_NTHeatTrap_tes(); + C_csp_piston_cylinder_tes(); - ~C_csp_NTHeatTrap_tes() {}; + ~C_csp_piston_cylinder_tes() {}; virtual void init(const C_csp_tes::S_csp_tes_init_inputs init_inputs); diff --git a/tcs/csp_solver_tes_core.cpp b/tcs/csp_solver_tes_core.cpp index fd8a451b7..ba2fc3613 100644 --- a/tcs/csp_solver_tes_core.cpp +++ b/tcs/csp_solver_tes_core.cpp @@ -98,7 +98,7 @@ void two_tank_tes_sizing_fixed_diameter(HTFProperties& tes_htf_props, double Q_t } -void heattrap_tes_sizing(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, +void piston_cylinder_tes_sizing(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, double T_tes_cold /*K*/, double h_min /*m*/, double h_tank_in /*m*/, int tank_pairs /*-*/, double u_tank /*W/m^2-K*/, double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, double& d_tank_out /*m*/, double& q_dot_loss_des /*MWt*/) @@ -135,7 +135,7 @@ void heattrap_tes_sizing(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr } -void heattrap_tes_sizing_fixed_diameter(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, +void piston_cylinder_tes_sizing_fixed_diameter(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, double T_tes_cold /*K*/, double h_min /*m*/, double d_tank_in, int tank_pairs /*-*/, double u_tank /*W/m^2-K*/, double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, double& h_tank_out /*m*/, double& q_dot_loss_des /*MWt*/) diff --git a/tcs/csp_solver_tes_core.h b/tcs/csp_solver_tes_core.h index cde31f2f5..9f9b9dd50 100644 --- a/tcs/csp_solver_tes_core.h +++ b/tcs/csp_solver_tes_core.h @@ -48,12 +48,12 @@ void two_tank_tes_sizing_fixed_diameter(HTFProperties& tes_htf_props, double Q_t double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, double& h_tank_out /*m*/, double& q_dot_loss_des /*MWt*/); -void heattrap_tes_sizing(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, +void piston_cylinder_tes_sizing(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, double T_tes_cold /*K*/, double h_min /*m*/, double h_tank_in /*m*/, int tank_pairs /*-*/, double u_tank /*W/m^2-K*/, double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, double& d_tank_out /*m*/, double& q_dot_loss_des /*MWt*/); -void heattrap_tes_sizing_fixed_diameter(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, +void piston_cylinder_tes_sizing_fixed_diameter(HTFProperties& tes_htf_props, double Q_tes_des /*MWt-hr*/, double T_tes_hot /*K*/, double T_tes_cold /*K*/, double h_min /*m*/, double d_tank_in, int tank_pairs /*-*/, double u_tank /*W/m^2-K*/, double& vol_one_temp_avail /*m3*/, double& vol_one_temp_total /*m3*/, double& h_tank_out /*m*/, double& q_dot_loss_des /*MWt*/); From 7086167b19deeb9ff4acc59161c6584d6c9c0e71 Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu, 7 Nov 2024 09:57:56 -0700 Subject: [PATCH 67/85] Update thermal buy/sell rate unit. Fix bug importing thermal load variable. --- ssc/cmod_thermalrate_iph.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/ssc/cmod_thermalrate_iph.cpp b/ssc/cmod_thermalrate_iph.cpp index cb91b0d75..7f2308394 100644 --- a/ssc/cmod_thermalrate_iph.cpp +++ b/ssc/cmod_thermalrate_iph.cpp @@ -61,14 +61,14 @@ static var_info vtab_thermal_rate_iph[] = { { SSC_INPUT, SSC_NUMBER, "thermal_buy_rate_option", "Thermal buy rate option", "0-2", "0=flat,1=timestep,2=monthly", "Thermal Rate", "?=0", "INTEGER,MIN=0,MAX=2", "" }, - { SSC_INPUT, SSC_NUMBER, "thermal_buy_rate_flat_heat_btu", "Thermal buy rate flat", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, - { SSC_INPUT, SSC_ARRAY, "thermal_timestep_buy_rate_heat_btu", "Thermal buy rate", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, - { SSC_INPUT, SSC_ARRAY, "thermal_monthly_buy_rate_heat_btu", "Monthly thermal buy rate", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "thermal_buy_rate_flat_heat_btu", "Thermal buy rate flat", "$/MMBtu", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "thermal_timestep_buy_rate_heat_btu", "Thermal buy rate", "$/MMBtu", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "thermal_monthly_buy_rate_heat_btu", "Monthly thermal buy rate", "$/MMBtu", "", "Thermal Rate", "?=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "thermal_sell_rate_option", "Thermal sell rate option", "0/1", "0=flat,1=timestep", "Thermal Rate", "?=0", "INTEGER,MIN=0,MAX=2", "" }, - { SSC_INPUT, SSC_NUMBER, "thermal_sell_rate_flat_heat_btu", "Thermal sell rate flat", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, - { SSC_INPUT, SSC_ARRAY, "thermal_timestep_sell_rate_heat_btu", "Thermal sell rate timestep","$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, - { SSC_INPUT, SSC_ARRAY, "thermal_monthly_sell_rate_heat_btu", "Thermal sell rate monthly", "$/(MMBtu/hr)", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "thermal_sell_rate_option", "Thermal sell rate option", "0-2", "0=flat,1=timestep,2=monthly", "Thermal Rate", "?=0", "INTEGER,MIN=0,MAX=2", "" }, + { SSC_INPUT, SSC_NUMBER, "thermal_sell_rate_flat_heat_btu", "Thermal sell rate flat", "$/MMBtu", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "thermal_timestep_sell_rate_heat_btu", "Thermal sell rate timestep","$/MMBtu", "", "Thermal Rate", "?=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "thermal_monthly_sell_rate_heat_btu", "Thermal sell rate monthly", "$/MMBtu", "", "Thermal Rate", "?=0", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "annual_thermal_value", "Thermal value", "$", "", "Annual", "*", "", "" }, @@ -235,10 +235,9 @@ class cm_thermalrate_iph : public compute_module { // hourly or sub hourly loads for single year // Convert thermal load units - std::vector thermal_load_MMBtu_per_hr = as_vector_double("thermal_load_heat_btu"); //[MMBtu/hr] bload = true; - pload = &thermal_load_MMBtu_per_hr[0]; - nrec_load = thermal_load_MMBtu_per_hr.size(); + pload = as_array("thermal_load_heat_btu", &nrec_load);//[MMBtu/hr] + step_per_hour_load = nrec_load / 8760; From 2f962147a58224d7bc257fe8d4652c7cc6f9611a Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri, 8 Nov 2024 13:31:39 -0700 Subject: [PATCH 68/85] Squashed commit of the following: commit b5f47632e89bcd97887b031eb9dd26b0b2a46ebe Author: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu Nov 7 16:16:05 2024 -0700 Change tes_type starting integer to 1 --- ssc/cmod_csp_subcomponent.cpp | 85 +++++++++++------------ ssc/cmod_trough_physical.cpp | 114 +++++++++++++++---------------- ssc/cmod_trough_physical_iph.cpp | 114 +++++++++++++++---------------- tcs/csp_solver_core.h | 2 +- 4 files changed, 158 insertions(+), 157 deletions(-) diff --git a/ssc/cmod_csp_subcomponent.cpp b/ssc/cmod_csp_subcomponent.cpp index 12cffd4da..a348af8bc 100644 --- a/ssc/cmod_csp_subcomponent.cpp +++ b/ssc/cmod_csp_subcomponent.cpp @@ -62,22 +62,23 @@ static var_info _cm_vtab_csp_subcomponent[] = { { SSC_INPUT, SSC_NUMBER, "pb_pump_coef", "Pumping power to move 1kg of HTF through PB loop", "kW/kg", "", "powerblock", "*", "", "" }, // General TES Parameters - { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), Packed Bed (1), Piston Cylinder (2)", "-", "", "TES", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (1), Packed Bed (2), Piston Cylinder (3)", "-", "", "TES", "?=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "Fluid", "Field HTF fluid ID number", "-", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_MATRIX, "field_fl_props", "User defined field fluid property data", "-", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "tshours", "Equivalent full-load thermal storage hours", "hr", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "is_h_tank_fixed", "[1] Use fixed height (calculate diameter) [0] Use fixed diameter [2] Use fixed d and h (for packed bed)", "-", "", "TES", "?=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "h_tank_in", "Total height of tank input (height of HTF when tank is full", "m", "", "TES", "is_h_tank_fixed=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "d_tank_in", "Tank diameter input", "m", "", "TES", "is_h_tank_fixed=0|is_h_tank_fixed=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "TES", "tes_type=1|tes_type=3", "", "" }, { SSC_INPUT, SSC_NUMBER, "tank_pairs", "Number of equivalent tank pairs", "-", "", "TES", "*", "INTEGER", "" }, - { SSC_INPUT, SSC_NUMBER, "hot_tank_Thtr", "Minimum allowable hot tank HTF temp", "C", "", "TES", "tes_type=0|tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "hot_tank_max_heat", "Rated heater capacity for hot tank heating", "MWe", "", "TES", "tes_type=0|tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "cold_tank_Thtr", "Minimum allowable cold tank HTF temp", "C", "", "TES", "tes_type=0|tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "cold_tank_max_heat", "Rated heater capacity for cold tank heating", "MWe", "", "TES", "tes_type=0|tes_type=2", "", "" }, { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "tes_type=0|tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "hot_tank_Thtr", "Minimum allowable hot tank HTF temp", "C", "", "TES", "tes_type=1|tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "hot_tank_max_heat", "Rated heater capacity for hot tank heating", "MWe", "", "TES", "tes_type=1|tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "cold_tank_Thtr", "Minimum allowable cold tank HTF temp", "C", "", "TES", "tes_type=1|tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "cold_tank_max_heat", "Rated heater capacity for cold tank heating", "MWe", "", "TES", "tes_type=1|tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "tes_type=1|tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "tes_type=1|tes_type=3", "", "" }, { SSC_INPUT, SSC_NUMBER, "init_hot_htf_percent", "Initial fraction of avail. vol that is hot", "%", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_n_tsteps", "Number of subtimesteps (for NT and packed bed)", "", "", "TES", "tes_type>0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_n_tsteps", "Number of subtimesteps (for NT and packed bed)", "", "", "TES", "tes_type>1", "", "" }, // TES { SSC_INPUT, SSC_NUMBER, "V_tes_des", "Design-point velocity to size the TES pipe diameters", "m/s", "", "controller", "*", "", "" }, @@ -96,29 +97,29 @@ static var_info _cm_vtab_csp_subcomponent[] = { { SSC_INPUT, SSC_NUMBER, "DP_SGS", "Pressure drop within the steam generator", "bar", "", "controller", "*", "", "" }, // TES Two Tank Specific - { SSC_INPUT, SSC_NUMBER, "store_fluid", "Material number for storage fluid", "-", "", "TES", "tes_type=0", "", "" }, - { SSC_INPUT, SSC_MATRIX, "store_fl_props", "User defined storage fluid property data", "-", "", "TES", "tes_type=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "dt_hot", "Hot side HX approach temp", "C", "", "TES", "tes_type=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tanks_in_parallel", "Tanks are in parallel, not in series, with solar field", "-", "", "controller", "tes_type=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "store_fluid", "Material number for storage fluid", "-", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_MATRIX, "store_fl_props", "User defined storage fluid property data", "-", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "dt_hot", "Hot side HX approach temp", "C", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tanks_in_parallel", "Tanks are in parallel, not in series, with solar field", "-", "", "controller", "tes_type=1", "", "" }, // TES Packed Bed - { SSC_INPUT, SSC_NUMBER, "tes_pb_n_xsteps", "Number of spatial segments", "", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_k_eff", "TES packed bed effective conductivity", "W/m K", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_void_frac", "TES packed bed void fraction", "", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_dens_solid", "TES packed bed media density", "kg/m3", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_cp_solid", "TES particle specific heat", "kJ/kg K", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_T_hot_delta", "Max allowable decrease in hot discharge temp", "C", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_T_cold_delta", "Max allowable increase in cold discharge temp", "C", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_T_charge_min", "Min charge temp", "C", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_f_oversize", "Packed bed oversize factor", "", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_n_xsteps", "Number of spatial segments", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_k_eff", "TES packed bed effective conductivity", "W/m K", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_void_frac", "TES packed bed void fraction", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_dens_solid", "TES packed bed media density", "kg/m3", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_cp_solid", "TES particle specific heat", "kJ/kg K", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_hot_delta", "Max allowable decrease in hot discharge temp", "C", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_cold_delta", "Max allowable increase in cold discharge temp", "C", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_charge_min", "Min charge temp", "C", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_f_oversize", "Packed bed oversize factor", "", "", "TES", "tes_type=2", "", "" }, { SSC_INPUT, SSC_ARRAY, "tes_pb_T_grad_ini", "TES Temperature gradient at beginning of timestep", "C", "", "TES", "?=[-274]", "", "" }, // TES Piston Cylinder - { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_thick", "Tank wall thickness (used for Piston Cylinder)", "m", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_cp", "Tank wall cp (used for Piston Cylinder)", "kJ/kg-K", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_dens", "Tank wall thickness (used for Piston Cylinder)", "kg/m3", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_ARRAY, "tes_cyl_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_thick", "Tank wall thickness (used for Piston Cylinder)", "m", "", "TES", "tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_cp", "Tank wall cp (used for Piston Cylinder)", "kJ/kg-K", "", "TES", "tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_dens", "Tank wall thickness (used for Piston Cylinder)", "kg/m3", "", "TES", "tes_type=3", "", "" }, + { SSC_INPUT, SSC_ARRAY, "tes_cyl_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=3", "", "" }, { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_insul_percent", "Percent additional wall mass due to insulation (used for Piston Cylinder)", "%", "", "TES", "?=0", "", "" }, // Outputs @@ -137,25 +138,25 @@ static var_info _cm_vtab_csp_subcomponent[] = { - { SSC_OUTPUT, SSC_ARRAY, "tes_error", "TES energy balance error", "MW", "", "TES", "tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_error_percent", "TES energy balance error percent", "%", "", "TES", "tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "piston_loc", "Piston Location (distance from left cold side)", "m", "", "TES", "tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "piston_frac", "Piston Fraction (distance from left cold side)", "", "", "TES", "tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_leak_error", "TES energy balance error due to leakage assumption", "MWt", "", "TES", "tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_E_hot", "TES hot side internal energy", "MJ", "", "TES", "tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_E_cold", "TES cold side internal energy", "MJ", "", "TES", "tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_wall_error", "TES energy balance error due to wall temperature assumption", "MWt", "", "TES", "tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_error_corrected", "TES energy balance error, accounting for wall and temperature assumption error", "MWt", "", "TES", "tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error", "TES energy balance error", "MW", "", "TES", "tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error_percent", "TES energy balance error percent", "%", "", "TES", "tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "piston_loc", "Piston Location (distance from left cold side)", "m", "", "TES", "tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "piston_frac", "Piston Fraction (distance from left cold side)", "", "", "TES", "tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_leak_error", "TES energy balance error due to leakage assumption", "MWt", "", "TES", "tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_E_hot", "TES hot side internal energy", "MJ", "", "TES", "tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_E_cold", "TES cold side internal energy", "MJ", "", "TES", "tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_wall_error", "TES energy balance error due to wall temperature assumption", "MWt", "", "TES", "tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error_corrected", "TES energy balance error, accounting for wall and temperature assumption error", "MWt", "", "TES", "tes_type=3", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_exp_wall_mass", "TES expansion tank effective wall mass", "kg", "", "TES", "tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_exp_length", "TES expansion tank effective length", "m", "", "TES", "tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_mass_cold", "TES cold fluid mass", "kg", "", "TES", "tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_mass_hot", "TES hot fluid mass", "kg", "", "TES", "tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_V_cold", "TES cold fluid volume", "kg", "", "TES", "tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_V_hot", "TES hot fluid volume", "kg", "", "TES", "tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_exp_wall_mass", "TES expansion tank effective wall mass", "kg", "", "TES", "tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_exp_length", "TES expansion tank effective length", "m", "", "TES", "tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_mass_cold", "TES cold fluid mass", "kg", "", "TES", "tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_mass_hot", "TES hot fluid mass", "kg", "", "TES", "tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_V_cold", "TES cold fluid volume", "kg", "", "TES", "tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_V_hot", "TES hot fluid volume", "kg", "", "TES", "tes_type=3", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "hot_tank_mass_perc", "TES hot tank mass percent of total (end)", "kg", "", "TES", "*", "", "" }, - { SSC_OUTPUT, SSC_MATRIX, "T_grad_final", "TES Temperature gradient at end of timestep", "C", "", "TES", "tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_MATRIX, "T_grad_final", "TES Temperature gradient at end of timestep", "C", "", "TES", "tes_type=2", "", "" }, var_info_invalid }; @@ -384,7 +385,7 @@ class cm_csp_subcomponent : public compute_module } else { - throw exec_error("csp_subcomponent", "tes_type must be 0-2"); + throw exec_error("csp_subcomponent", "tes_type must be 1-3"); } // Initialization -> this is necessary to fully instantiate the TES diff --git a/ssc/cmod_trough_physical.cpp b/ssc/cmod_trough_physical.cpp index 7fbc29194..611e555e5 100644 --- a/ssc/cmod_trough_physical.cpp +++ b/ssc/cmod_trough_physical.cpp @@ -202,47 +202,47 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_INPUT, SSC_MATRIX, "ud_ind_od", "Off design user-defined power cycle performance as function of T_htf, m_dot_htf [ND], and T_amb", "", "", "powerblock", "pc_config=1", "", "" }, // General TES Parameters - { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), Packed Bed (1), Piston Cylinder (2)", "-", "", "TES", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (1), Packed Bed (2), Piston Cylinder (3)", "-", "", "TES", "?=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "tshours", "Equivalent full-load thermal storage hours", "hr", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "is_h_tank_fixed", "[1] Use fixed height (calculate diameter) [0] Use fixed diameter [2] Use fixed d and h (for packed bed)", "-", "", "TES", "?=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "h_tank_in", "Total height of tank input (height of HTF when tank is full", "m", "", "TES", "is_h_tank_fixed=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "d_tank_in", "Tank diameter input", "m", "", "TES", "is_h_tank_fixed=0|is_h_tank_fixed=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "TES", "tes_type=1|tes_type=3", "", "" }, { SSC_INPUT, SSC_NUMBER, "tank_pairs", "Number of equivalent tank pairs", "-", "", "TES", "*", "INTEGER", "" }, - { SSC_INPUT, SSC_NUMBER, "hot_tank_Thtr", "Minimum allowable hot tank HTF temp", "C", "", "TES", "tes_type=0|tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "hot_tank_max_heat", "Rated heater capacity for hot tank heating", "MWe", "", "TES", "tes_type=0|tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "cold_tank_Thtr", "Minimum allowable cold tank HTF temp", "C", "", "TES", "tes_type=0|tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "cold_tank_max_heat", "Rated heater capacity for cold tank heating", "MWe", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "hot_tank_Thtr", "Minimum allowable hot tank HTF temp", "C", "", "TES", "tes_type=1|tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "hot_tank_max_heat", "Rated heater capacity for hot tank heating", "MWe", "", "TES", "tes_type=1|tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "cold_tank_Thtr", "Minimum allowable cold tank HTF temp", "C", "", "TES", "tes_type=1|tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "cold_tank_max_heat", "Rated heater capacity for cold tank heating", "MWe", "", "TES", "tes_type=1|tes_type=3", "", "" }, //{ SSC_INPUT, SSC_NUMBER, "dt_cold", "Cold side HX approach temp", "C", "", "TES", "*", "", "" }, //{ SSC_INPUT, SSC_NUMBER, "T_tank_hot_ini", "Initial hot tank fluid temperature", "C", "", "TES", "*", "", "" }, //{ SSC_INPUT, SSC_NUMBER, "T_tank_cold_ini", "Initial cold tank fluid temperature", "C", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "tes_type=1|tes_type=3", "", "" }, { SSC_INPUT, SSC_NUMBER, "init_hot_htf_percent", "Initial fraction of avail. vol that is hot", "%", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_n_tsteps", "Number of subtimesteps (for NT and packed bed)", "", "", "TES", "tes_type>0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_n_tsteps", "Number of subtimesteps (for NT and packed bed)", "", "", "TES", "tes_type>1", "", "" }, // TES Two Tank Specific - { SSC_INPUT, SSC_NUMBER, "store_fluid", "Material number for storage fluid", "-", "", "TES", "tes_type=0", "", "" }, - { SSC_INPUT, SSC_MATRIX, "store_fl_props", "User defined storage fluid property data", "-", "", "TES", "tes_type=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "dt_hot", "Hot side HX approach temp", "C", "", "TES", "tes_type=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tanks_in_parallel", "Tanks are in parallel, not in series, with solar field", "-", "", "controller", "tes_type=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "store_fluid", "Material number for storage fluid", "-", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_MATRIX, "store_fl_props", "User defined storage fluid property data", "-", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "dt_hot", "Hot side HX approach temp", "C", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tanks_in_parallel", "Tanks are in parallel, not in series, with solar field", "-", "", "controller", "tes_type=1", "", "" }, // TES Piston Cylinder - { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_thick", "Tank wall thickness (used for Piston Cylinder)", "m", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_cp", "Tank wall cp (used for Piston Cylinder)", "kJ/kg-K", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_dens", "Tank wall thickness (used for Piston Cylinder)", "kg/m3", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_ARRAY, "tes_cyl_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_thick", "Tank wall thickness (used for Piston Cylinder)", "m", "", "TES", "tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_cp", "Tank wall cp (used for Piston Cylinder)", "kJ/kg-K", "", "TES", "tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_dens", "Tank wall thickness (used for Piston Cylinder)", "kg/m3", "", "TES", "tes_type=3", "", "" }, + { SSC_INPUT, SSC_ARRAY, "tes_cyl_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=3", "", "" }, { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_insul_percent", "Percent additional wall mass due to insulation (used for Piston Cylinder)", "%", "", "TES", "?=0", "", "" }, // TES Packed Bed - { SSC_INPUT, SSC_NUMBER, "tes_pb_n_xsteps", "Number of spatial segments", "", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_k_eff", "TES packed bed effective conductivity", "W/m K", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_void_frac", "TES packed bed void fraction", "", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_dens_solid", "TES packed bed media density", "kg/m3", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_cp_solid", "TES particle specific heat", "kJ/kg K", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_T_hot_delta", "Max allowable decrease in hot discharge temp", "C", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_T_cold_delta", "Max allowable increase in cold discharge temp", "C", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_T_charge_min", "Min charge temp", "C", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_f_oversize", "Packed bed oversize factor", "", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_n_xsteps", "Number of spatial segments", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_k_eff", "TES packed bed effective conductivity", "W/m K", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_void_frac", "TES packed bed void fraction", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_dens_solid", "TES packed bed media density", "kg/m3", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_cp_solid", "TES particle specific heat", "kJ/kg K", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_hot_delta", "Max allowable decrease in hot discharge temp", "C", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_cold_delta", "Max allowable increase in cold discharge temp", "C", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_charge_min", "Min charge temp", "C", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_f_oversize", "Packed bed oversize factor", "", "", "TES", "tes_type=2", "", "" }, // Optional Component Initialization (state at start of first timestep) @@ -628,33 +628,33 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_OUTPUT, SSC_ARRAY, "tes_htf_pump_power", "TES HTF pump power", "MWe", "", "TES", "sim_type=1", "", "" }, // NT TES - { SSC_OUTPUT, SSC_ARRAY, "vol_tes_cold", "TES cold fluid volume", "m3", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "vol_tes_hot", "TES hot fluid volume", "m3" , "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "vol_tes_cold", "TES cold fluid volume", "m3", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "vol_tes_hot", "TES hot fluid volume", "m3" , "", "TES", "sim_type=1&tes_type=3", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "vol_tes_tot", "TES total fluid volume", "m3", "", "TES", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_piston_loc", "TES piston distance from left (cold) side", "m", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_piston_frac", "TES piston fraction of cold distance over total", "", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_cold_vol_frac", "TES volume fraction of cold over total", "", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_mass_tot", "TES total fluid mass", "kg", "", "TES", "tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_SA_cold", "TES cold side surface area", "m2", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_SA_hot", "TES hot side surface area", "m2", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_SA_tot", "TES total surface area", "m2", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_error", "TES energy balance error", "MWt", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_error_percent", "TES energy balance error percent", "%", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_leak_error", "TES energy balance error due to leakage assumption", "MWt", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_wall_error", "TES energy balance error due to wall temperature assumption", "MWt", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_error_corrected", "TES energy balance error, accounting for wall and temperature assumption error", "MWt", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_piston_loc", "TES piston distance from left (cold) side", "m", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_piston_frac", "TES piston fraction of cold distance over total", "", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_cold_vol_frac", "TES volume fraction of cold over total", "", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_mass_tot", "TES total fluid mass", "kg", "", "TES", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_SA_cold", "TES cold side surface area", "m2", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_SA_hot", "TES hot side surface area", "m2", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_SA_tot", "TES total surface area", "m2", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error", "TES energy balance error", "MWt", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error_percent", "TES energy balance error percent", "%", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_leak_error", "TES energy balance error due to leakage assumption", "MWt", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_wall_error", "TES energy balance error due to wall temperature assumption", "MWt", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error_corrected", "TES energy balance error, accounting for wall and temperature assumption error", "MWt", "", "TES", "sim_type=1&tes_type=3", "", "" }, // Packed Bed TES - { SSC_OUTPUT, SSC_ARRAY, "T_grad_0", "TES Temperature gradient 0 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_1", "TES Temperature gradient 1 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_2", "TES Temperature gradient 2 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_3", "TES Temperature gradient 3 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_4", "TES Temperature gradient 4 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_5", "TES Temperature gradient 5 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_6", "TES Temperature gradient 6 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_7", "TES Temperature gradient 7 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_8", "TES Temperature gradient 8 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_9", "TES Temperature gradient 9 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_0", "TES Temperature gradient 0 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_1", "TES Temperature gradient 1 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_2", "TES Temperature gradient 2 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_3", "TES Temperature gradient 3 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_4", "TES Temperature gradient 4 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_5", "TES Temperature gradient 5 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_6", "TES Temperature gradient 6 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_7", "TES Temperature gradient 7 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_8", "TES Temperature gradient 8 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_9", "TES Temperature gradient 9 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, //{ SSC_OUTPUT, SSC_ARRAY, "m_dot_tes_dc", "TES discharge mass flow rate", "kg/s", "", "TES", "*", "", "" }, @@ -732,13 +732,13 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_OUTPUT, SSC_NUMBER, "sim_duration", "Computational time of timeseries simulation", "s", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "recirculating", "Field recirculating (bypass valve open)", "-", "", "solar_field", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_diams", "Pipe diameters in TES", "m", "", "TES", "sim_type=1&tes_type=0", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_wallthk", "Pipe wall thickness in TES", "m", "", "TES", "sim_type=1&tes_type=0", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_lengths", "Pipe lengths in TES", "m", "", "TES", "sim_type=1&tes_type=0", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_mdot_dsn", "Mass flow TES pipes at design conditions", "kg/s", "", "TES", "sim_type=1&tes_type=0", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_vel_dsn", "Velocity in TES pipes at design conditions", "m/s", "", "TES", "sim_type=1&tes_type=0", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_T_dsn", "Temperature in TES pipes at design conditions", "C", "", "TES", "sim_type=1&tes_type=0", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_P_dsn", "Pressure in TES pipes at design conditions", "bar", "", "TES", "sim_type=1&tes_type=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_diams", "Pipe diameters in TES", "m", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_wallthk", "Pipe wall thickness in TES", "m", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_lengths", "Pipe lengths in TES", "m", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_mdot_dsn", "Mass flow TES pipes at design conditions", "kg/s", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_vel_dsn", "Velocity in TES pipes at design conditions", "m/s", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_T_dsn", "Temperature in TES pipes at design conditions", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_P_dsn", "Pressure in TES pipes at design conditions", "bar", "", "TES", "sim_type=1&tes_type=1", "", "" }, //{ SSC_OUTPUT, SSC_ARRAY, "defocus", "Field optical focus fraction", "", "", "solver", "*", "", "" }, @@ -1604,7 +1604,7 @@ class cm_trough_physical : public compute_module } else { - throw exec_error("trough_physical", "tes_type must be 0-2"); + throw exec_error("trough_physical", "tes_type must be 1-3"); } // ************************************************************************* diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index a27754620..5af75bc61 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -185,44 +185,44 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "allow_heater_no_dispatch_opt","Allow heater with no dispatch optimization? SAM UI relies on cmod default", "", "", "System Costs", "?=0", "", "SIMULATION_PARAMETER" }, // General TES Parameters - { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (0), Packed Bed (1), Piston Cylinder (2)", "-", "", "TES", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_type", "Standard two tank (1), Packed Bed (2), Piston Cylinder (3)", "-", "", "TES", "?=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "tshours", "Equivalent full-load thermal storage hours", "hr", "", "TES", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "is_h_tank_fixed", "[1] Use fixed height (calculate diameter) [0] Use fixed diameter [2] Use fixed d and h (for packed bed)", "-", "", "TES", "?=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "h_tank_in", "Total height of tank input (height of HTF when tank is full", "m", "", "TES", "is_h_tank_fixed=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "d_tank_in", "Tank diameter input", "m", "", "TES", "is_h_tank_fixed=0|is_h_tank_fixed=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "TES", "tes_type=1|tes_type=3", "", "" }, { SSC_INPUT, SSC_NUMBER, "tank_pairs", "Number of equivalent tank pairs", "-", "", "TES", "*", "INTEGER", "" }, - { SSC_INPUT, SSC_NUMBER, "hot_tank_Thtr", "Minimum allowable hot tank HTF temp", "C", "", "TES", "tes_type=0|tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "hot_tank_max_heat", "Rated heater capacity for hot tank heating", "MWe", "", "TES", "tes_type=0|tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "cold_tank_Thtr", "Minimum allowable cold tank HTF temp", "C", "", "TES", "tes_type=0|tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "cold_tank_max_heat", "Rated heater capacity for cold tank heating", "MWe", "", "TES", "tes_type=0|tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "tes_type=0|tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "hot_tank_Thtr", "Minimum allowable hot tank HTF temp", "C", "", "TES", "tes_type=1|tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "hot_tank_max_heat", "Rated heater capacity for hot tank heating", "MWe", "", "TES", "tes_type=1|tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "cold_tank_Thtr", "Minimum allowable cold tank HTF temp", "C", "", "TES", "tes_type=1|tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "cold_tank_max_heat", "Rated heater capacity for cold tank heating", "MWe", "", "TES", "tes_type=1|tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "h_tank_min", "Minimum allowable HTF height in storage tank", "m", "", "TES", "tes_type=1|tes_type=3", "", "" }, { SSC_INPUT, SSC_NUMBER, "init_hot_htf_percent", "Initial fraction of avail. vol that is hot", "%", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_n_tsteps", "Number of subtimesteps (for NT and packed bed)", "", "", "TES", "tes_type>0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_n_tsteps", "Number of subtimesteps (for NT and packed bed)", "", "", "TES", "tes_type>1", "", "" }, // TES Two Tank Specific - { SSC_INPUT, SSC_NUMBER, "store_fluid", "Material number for storage fluid", "-", "", "TES", "tes_type=0", "", "" }, - { SSC_INPUT, SSC_MATRIX, "store_fl_props", "User defined storage fluid property data", "-", "", "TES", "tes_type=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "dt_hot", "Hot side HX approach temp", "C", "", "TES", "tes_type=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tanks_in_parallel", "Tanks are in parallel, not in series, with solar field", "-", "", "controller", "tes_type=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "store_fluid", "Material number for storage fluid", "-", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_MATRIX, "store_fl_props", "User defined storage fluid property data", "-", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "dt_hot", "Hot side HX approach temp", "C", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tanks_in_parallel", "Tanks are in parallel, not in series, with solar field", "-", "", "controller", "tes_type=1", "", "" }, // TES Piston Cylinder - { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_thick", "Tank wall thickness (used for Piston Cylinder)", "m", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_cp", "Tank wall cp (used for Piston Cylinder)", "kJ/kg-K", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_dens", "Tank wall thickness (used for Piston Cylinder)", "kg/m3", "", "TES", "tes_type=2", "", "" }, - { SSC_INPUT, SSC_ARRAY, "tes_cyl_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_thick", "Tank wall thickness (used for Piston Cylinder)", "m", "", "TES", "tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_cp", "Tank wall cp (used for Piston Cylinder)", "kJ/kg-K", "", "TES", "tes_type=3", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_dens", "Tank wall thickness (used for Piston Cylinder)", "kg/m3", "", "TES", "tes_type=3", "", "" }, + { SSC_INPUT, SSC_ARRAY, "tes_cyl_piston_loss_poly", "Polynomial coefficients describing piston heat loss function (f(kg/s)=%)", "", "", "TES", "tes_type=3", "", "" }, { SSC_INPUT, SSC_NUMBER, "tes_cyl_tank_insul_percent", "Percent additional wall mass due to insulation (used for Piston Cylinder)", "%", "", "TES", "?=0", "", "" }, // TES Packed Bed - { SSC_INPUT, SSC_NUMBER, "tes_pb_n_xsteps", "Number of spatial segments", "", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_k_eff", "TES packed bed effective conductivity", "W/m K", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_void_frac", "TES packed bed void fraction", "", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_dens_solid", "TES packed bed media density", "kg/m3", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_cp_solid", "TES particle specific heat", "kJ/kg K", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_T_hot_delta", "Max allowable decrease in hot discharge temp", "C", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_T_cold_delta", "Max allowable increase in cold discharge temp", "C", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_T_charge_min", "Min charge temp", "C", "", "TES", "tes_type=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "tes_pb_f_oversize", "Packed bed oversize factor", "", "", "TES", "tes_type=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_n_xsteps", "Number of spatial segments", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_k_eff", "TES packed bed effective conductivity", "W/m K", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_void_frac", "TES packed bed void fraction", "", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_dens_solid", "TES packed bed media density", "kg/m3", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_cp_solid", "TES particle specific heat", "kJ/kg K", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_hot_delta", "Max allowable decrease in hot discharge temp", "C", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_cold_delta", "Max allowable increase in cold discharge temp", "C", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_T_charge_min", "Min charge temp", "C", "", "TES", "tes_type=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tes_pb_f_oversize", "Packed bed oversize factor", "", "", "TES", "tes_type=2", "", "" }, // Dispatch optimization @@ -597,33 +597,33 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "tes_htf_pump_power", "TES HTF pump power", "MWe", "", "TES", "sim_type=1", "", "" }, // NT TES - { SSC_OUTPUT, SSC_ARRAY, "vol_tes_cold", "TES cold fluid volume", "m3", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "vol_tes_hot", "TES hot fluid volume", "m3" , "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "vol_tes_cold", "TES cold fluid volume", "m3", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "vol_tes_hot", "TES hot fluid volume", "m3" , "", "TES", "sim_type=1&tes_type=3", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "vol_tes_tot", "TES total fluid volume", "m3", "", "TES", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_piston_loc", "TES piston distance from left (cold) side", "m", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_piston_frac", "TES piston fraction of cold distance over total", "", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_cold_vol_frac", "TES volume fraction of cold over total", "", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_mass_tot", "TES total fluid mass", "kg", "", "TES", "tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_SA_cold", "TES cold side surface area", "m2", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_SA_hot", "TES hot side surface area", "m2", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_SA_tot", "TES total surface area", "m2", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_error", "TES energy balance error", "MWt", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_error_percent", "TES energy balance error percent", "%", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_leak_error", "TES energy balance error due to leakage assumption", "MWt", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_wall_error", "TES energy balance error due to wall temperature assumption", "MWt", "", "TES", "sim_type=1&tes_type=2", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "tes_error_corrected", "TES energy balance error, accounting for wall and temperature assumption error", "MWt", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_piston_loc", "TES piston distance from left (cold) side", "m", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_piston_frac", "TES piston fraction of cold distance over total", "", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_cold_vol_frac", "TES volume fraction of cold over total", "", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_mass_tot", "TES total fluid mass", "kg", "", "TES", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_SA_cold", "TES cold side surface area", "m2", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_SA_hot", "TES hot side surface area", "m2", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_SA_tot", "TES total surface area", "m2", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error", "TES energy balance error", "MWt", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error_percent", "TES energy balance error percent", "%", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_leak_error", "TES energy balance error due to leakage assumption", "MWt", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_wall_error", "TES energy balance error due to wall temperature assumption", "MWt", "", "TES", "sim_type=1&tes_type=3", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tes_error_corrected", "TES energy balance error, accounting for wall and temperature assumption error", "MWt", "", "TES", "sim_type=1&tes_type=3", "", "" }, // Packed Bed TES - { SSC_OUTPUT, SSC_ARRAY, "T_grad_0", "TES Temperature gradient 0 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_1", "TES Temperature gradient 1 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_2", "TES Temperature gradient 2 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_3", "TES Temperature gradient 3 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_4", "TES Temperature gradient 4 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_5", "TES Temperature gradient 5 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_6", "TES Temperature gradient 6 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_7", "TES Temperature gradient 7 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_8", "TES Temperature gradient 8 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "T_grad_9", "TES Temperature gradient 9 indice", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_0", "TES Temperature gradient 0 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_1", "TES Temperature gradient 1 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_2", "TES Temperature gradient 2 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_3", "TES Temperature gradient 3 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_4", "TES Temperature gradient 4 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_5", "TES Temperature gradient 5 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_6", "TES Temperature gradient 6 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_7", "TES Temperature gradient 7 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_8", "TES Temperature gradient 8 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "T_grad_9", "TES Temperature gradient 9 indice", "C", "", "TES", "sim_type=1&tes_type=2", "", "" }, //{ SSC_OUTPUT, SSC_ARRAY, "m_dot_tes_dc", "TES discharge mass flow rate", "kg/s", "", "TES", "*", "", "" }, //{ SSC_OUTPUT, SSC_ARRAY, "m_dot_tes_ch", "TES charge mass flow rate", "kg/s", "", "TES", "*", "", "" }, @@ -721,13 +721,13 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "heat_load_capacity_factor", "Percentage of heat load met", "%", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "recirculating", "Field recirculating (bypass valve open)", "-", "", "solar_field", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_diams", "Pipe diameters in TES", "m", "", "TES", "sim_type=1&tes_type=0", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_wallthk", "Pipe wall thickness in TES", "m", "", "TES", "sim_type=1&tes_type=0", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_lengths", "Pipe lengths in TES", "m", "", "TES", "sim_type=1&tes_type=0", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_mdot_dsn", "Mass flow TES pipes at design conditions", "kg/s", "", "TES", "sim_type=1&tes_type=0", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_vel_dsn", "Velocity in TES pipes at design conditions", "m/s", "", "TES", "sim_type=1&tes_type=0", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_T_dsn", "Temperature in TES pipes at design conditions", "C", "", "TES", "sim_type=1&tes_type=0", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_P_dsn", "Pressure in TES pipes at design conditions", "bar", "", "TES", "sim_type=1&tes_type=0", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_diams", "Pipe diameters in TES", "m", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_wallthk", "Pipe wall thickness in TES", "m", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_lengths", "Pipe lengths in TES", "m", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_mdot_dsn", "Mass flow TES pipes at design conditions", "kg/s", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_vel_dsn", "Velocity in TES pipes at design conditions", "m/s", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_T_dsn", "Temperature in TES pipes at design conditions", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_P_dsn", "Pressure in TES pipes at design conditions", "bar", "", "TES", "sim_type=1&tes_type=1", "", "" }, //{ SSC_OUTPUT, SSC_ARRAY, "defocus", "Field optical focus fraction", "", "", "solver", "*", "", "" }, @@ -1469,7 +1469,7 @@ class cm_trough_physical_iph : public compute_module } else { - throw exec_error("trough_physical", "tes_type must be 0-2"); + throw exec_error("trough_physical", "tes_type must be 1-3"); } diff --git a/tcs/csp_solver_core.h b/tcs/csp_solver_core.h index 6710693b2..0469d5cf9 100644 --- a/tcs/csp_solver_core.h +++ b/tcs/csp_solver_core.h @@ -730,7 +730,7 @@ class C_csp_tes enum csp_tes_types { - E_TES_TWO_TANK, + E_TES_TWO_TANK = 1, E_TES_PACKED_BED, E_TES_CYL }; From 787cb767dea62e3246ecc07df71c2199f9e86899 Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:33:44 -0700 Subject: [PATCH 69/85] Add option for absolute thermal load hourly input for system control. --- ssc/cmod_trough_physical_iph.cpp | 138 +++++++++++++++++++++---------- 1 file changed, 96 insertions(+), 42 deletions(-) diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 5af75bc61..da39ac598 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -254,12 +254,15 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_ARRAY, "dispatch_factors_ts", "Time series electricity price multipliers", "", "", "tou", "ppa_multiplier_model=1&csp_financial_model<5&is_dispatch=1","", "SIMULATION_PARAMETER" }, // Control for *heat* output - { SSC_INPUT, SSC_NUMBER, "is_timestep_load_fractions","Use turbine load fraction for each timestep instead of block dispatch?", "", "", "tou", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "is_timestep_load_fractions","0: block dispatch, 1: hourly load fraction, 2: absolute load", "", "", "tou", "?=0", "", "" }, { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Heat sink load fraction for each timestep, alternative to block dispatch", "", "", "tou", "is_timestep_load_fractions=1", "", "" }, { SSC_INPUT, SSC_MATRIX, "weekday_schedule", "12x24 CSP operation Time-of-Use Weekday schedule", "", "", "tou", "is_timestep_load_fractions=0", "", "" }, { SSC_INPUT, SSC_MATRIX, "weekend_schedule", "12x24 CSP operation Time-of-Use Weekend schedule", "", "", "tou", "is_timestep_load_fractions=0", "", "" }, { SSC_INPUT, SSC_ARRAY, "f_turb_tou_periods", "Time series heat sink load fractions", "", "", "tou", "is_timestep_load_fractions=0", "", "" }, - // + { SSC_INPUT, SSC_ARRAY, "timestep_load_abs", "Heat sink hourly load (not normalized)", "kWt", "", "tou", "is_timestep_load_fractions=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "timestep_load_abs_factor", "Heat sink hourly load scale factor", "", "", "tou", "?=1", "", "" }, + + // { SSC_INPUT, SSC_NUMBER, "is_tod_pc_target_also_pc_max", "Is the TOD target cycle heat input also the max cycle heat input?", "", "", "tou", "?=0", "", "" }, // System @@ -449,6 +452,11 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "bop_design", "BOP parasitics at design", "MWe", "", "System Control", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "aux_design", "Aux parasitics at design", "MWe", "", "System Control", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "timestep_load_fractions_calc", "Calculated timestep load fractions", "", "", "System Control", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "timestep_load_abs_calc", "Calculated timestep load data", "kWt", "", "System Control", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "thermal_load_heat_btu", "Thermal load (year 1)", "MMBtu/hr", "", "Thermal Rate", "csp_financial_model=5", "", "" }, + + // Capital Costs // Direct Capital Costs @@ -729,6 +737,7 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_T_dsn", "Temperature in TES pipes at design conditions", "C", "", "TES", "sim_type=1&tes_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "pipe_tes_P_dsn", "Pressure in TES pipes at design conditions", "bar", "", "TES", "sim_type=1&tes_type=1", "", "" }, + //{ SSC_OUTPUT, SSC_ARRAY, "defocus", "Field optical focus fraction", "", "", "solver", "*", "", "" }, var_info_invalid }; @@ -1179,35 +1188,6 @@ class cm_trough_physical_iph : public compute_module } p_heater = p_electric_resistance; - // Heat Sink - C_pc_heat_sink c_heat_sink; - { - size_t n_f_turbine1 = 0; - ssc_number_t* p_f_turbine1 = as_array("f_turb_tou_periods", &n_f_turbine1); // heat sink, not turbine - double f_turbine_max1 = 1.0; - for (size_t i = 0; i < n_f_turbine1; i++) { - f_turbine_max1 = max(f_turbine_max1, p_f_turbine1[i]); - } - - c_heat_sink.ms_params.m_T_htf_hot_des = T_htf_hot_des; //[C] FIELD design outlet temperature - c_heat_sink.ms_params.m_T_htf_cold_des = T_htf_cold_des; //[C] FIELD design inlet temperature - c_heat_sink.ms_params.m_q_dot_des = q_dot_hs_des; //[MWt] HEAT SINK design thermal power (could have field solar multiple...) - // 9.18.2016 twn: assume for now there's no pressure drop though heat sink - c_heat_sink.ms_params.m_htf_pump_coef = as_double("pb_pump_coef"); //[kWe/kg/s] - c_heat_sink.ms_params.m_max_frac = f_turbine_max1; - - c_heat_sink.ms_params.m_pc_fl = as_integer("Fluid"); - c_heat_sink.ms_params.m_pc_fl_props = as_matrix("field_fl_props"); - - - // Allocate heat sink outputs - c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_Q_DOT_HEAT_SINK, allocate("q_dot_to_heat_sink", n_steps_fixed), n_steps_fixed); - c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_W_DOT_PUMPING, allocate("W_dot_pc_pump", n_steps_fixed), n_steps_fixed); - c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_M_DOT_HTF, allocate("m_dot_htf_heat_sink", n_steps_fixed), n_steps_fixed); - c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_T_HTF_IN, allocate("T_heat_sink_in", n_steps_fixed), n_steps_fixed); - c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_T_HTF_OUT, allocate("T_heat_sink_out", n_steps_fixed), n_steps_fixed); - } - // ******************************** // ******************************** // TES @@ -1482,16 +1462,68 @@ class cm_trough_physical_iph : public compute_module // Off-taker schedule C_timeseries_schedule_inputs offtaker_schedule; - bool is_timestep_load_fractions = as_boolean("is_timestep_load_fractions"); - if (is_timestep_load_fractions) { + int is_timestep_load_fractions = as_integer("is_timestep_load_fractions"); + std::vector timestep_load_fractions_calc; + std::vector timestep_load_abs_calc; + + // Block schedules + if(is_timestep_load_fractions == 0) { // Block schedules + C_timeseries_schedule_inputs offtaker_block = C_timeseries_schedule_inputs(as_matrix("weekday_schedule"), + as_matrix("weekend_schedule"), as_vector_double("f_turb_tou_periods"), std::numeric_limits::quiet_NaN()); + offtaker_schedule = offtaker_block; + } + // Hourly load fractions + else if (is_timestep_load_fractions == 1) { auto vec = as_vector_double("timestep_load_fractions"); C_timeseries_schedule_inputs offtaker_series = C_timeseries_schedule_inputs(vec, std::numeric_limits::quiet_NaN()); offtaker_schedule = offtaker_series; } - else { // Block schedules - C_timeseries_schedule_inputs offtaker_block = C_timeseries_schedule_inputs(as_matrix("weekday_schedule"), - as_matrix("weekend_schedule"), as_vector_double("f_turb_tou_periods"), std::numeric_limits::quiet_NaN()); - offtaker_schedule = offtaker_block; + // Absolute load values + else if (is_timestep_load_fractions == 2) { + std::vector vec_abs = as_vector_double("timestep_load_abs"); //[kWt] + double scale_factor = as_double("timestep_load_abs_factor"); + std::vector vec_abs_scaled; + for (double abs_val : vec_abs) + vec_abs_scaled.push_back(abs_val * scale_factor); //[kWt] + std::vector vec_norm; + double q_pb_design_kW = q_dot_hs_des * 1.e3; //[kWt] + for (double abs_val_scaled : vec_abs_scaled) + vec_norm.push_back(abs_val_scaled / q_pb_design_kW); + C_timeseries_schedule_inputs offtaker_series = C_timeseries_schedule_inputs(vec_norm, std::numeric_limits::quiet_NaN()); + offtaker_schedule = offtaker_series; + } + + // Heat Sink + C_pc_heat_sink c_heat_sink; + { + //size_t n_f_turbine1 = 0; + //ssc_number_t* p_f_turbine1 = as_array("f_turb_tou_periods", &n_f_turbine1); // heat sink, not turbine + //double f_turbine_max1 = 1.0; + //for (size_t i = 0; i < n_f_turbine1; i++) { + // f_turbine_max1 = max(f_turbine_max1, p_f_turbine1[i]); + //} + double f_turbine_max1 = 1.0; + for (S_timeseries_schedule_data data : offtaker_schedule.mv_timeseries_schedule_data) + f_turbine_max1 = max(f_turbine_max1, data.nondim_value); + + + c_heat_sink.ms_params.m_T_htf_hot_des = T_htf_hot_des; //[C] FIELD design outlet temperature + c_heat_sink.ms_params.m_T_htf_cold_des = T_htf_cold_des; //[C] FIELD design inlet temperature + c_heat_sink.ms_params.m_q_dot_des = q_dot_hs_des; //[MWt] HEAT SINK design thermal power (could have field solar multiple...) + // 9.18.2016 twn: assume for now there's no pressure drop though heat sink + c_heat_sink.ms_params.m_htf_pump_coef = as_double("pb_pump_coef"); //[kWe/kg/s] + c_heat_sink.ms_params.m_max_frac = f_turbine_max1; + + c_heat_sink.ms_params.m_pc_fl = as_integer("Fluid"); + c_heat_sink.ms_params.m_pc_fl_props = as_matrix("field_fl_props"); + + + // Allocate heat sink outputs + c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_Q_DOT_HEAT_SINK, allocate("q_dot_to_heat_sink", n_steps_fixed), n_steps_fixed); + c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_W_DOT_PUMPING, allocate("W_dot_pc_pump", n_steps_fixed), n_steps_fixed); + c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_M_DOT_HTF, allocate("m_dot_htf_heat_sink", n_steps_fixed), n_steps_fixed); + c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_T_HTF_IN, allocate("T_heat_sink_in", n_steps_fixed), n_steps_fixed); + c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_T_HTF_OUT, allocate("T_heat_sink_out", n_steps_fixed), n_steps_fixed); } // Electricity pricing schedule @@ -1578,7 +1610,7 @@ class cm_trough_physical_iph : public compute_module elec_pricing_schedule = C_timeseries_schedule_inputs(-1.0, std::numeric_limits::quiet_NaN()); } else { - throw exec_error("trough_physical_iph", "csp_financial_model must be 1, 7, or 8"); + throw exec_error("trough_physical_iph", "csp_financial_model must be 1, 5, 7, or 8"); } } else if (sim_type == 2) @@ -2004,10 +2036,6 @@ class cm_trough_physical_iph : public compute_module // System Control { - //double adjust_constant = as_double("adjust_constant"); - //double W_dot_bop_design, W_dot_fixed_parasitic_design; //[MWe] - //csp_solver.get_design_parameters(W_dot_bop_design, W_dot_fixed_parasitic_design); - vector bop_vec = as_vector_double("bop_array"); double bop_design = bop_vec[0] * bop_vec[1] * (bop_vec[2] + bop_vec[3] + bop_vec[4]) * q_dot_hs_des; vector aux_vec = as_vector_double("aux_array"); @@ -2015,6 +2043,32 @@ class cm_trough_physical_iph : public compute_module assign("bop_design", bop_design); // MWe assign("aux_design", aux_design); // MWe + + std::vector timestep_load_fractions_calc; + std::vector timestep_load_abs_calc; + for (S_timeseries_schedule_data data : offtaker_schedule.mv_timeseries_schedule_data) + { + double frac_val = data.nondim_value; + double abs_val = q_dot_hs_des * frac_val * 1.e3; //[kWt] + timestep_load_fractions_calc.push_back(frac_val); + timestep_load_abs_calc.push_back(abs_val); + } + + set_vector("timestep_load_fractions_calc", timestep_load_fractions_calc); + set_vector("timestep_load_abs_calc", timestep_load_abs_calc); + + + // Need to assign thermal load in Btu for thermalrate_iph cmod if commercial + if (csp_financial_model == 5) + { + std::vector load_abs_MMBtu; + for (double val_kW : timestep_load_abs_calc) + { + load_abs_MMBtu.push_back(val_kW / MMBTU_TO_KWh); + } + set_vector("thermal_load_heat_btu", load_abs_MMBtu); + } + } } From 46012027a4f5825219a1bfb0dfe13a97865a710f Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:06:01 -0700 Subject: [PATCH 70/85] Remove unused heat dispatch calls. --- ssc/cmod_trough_physical_iph.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index da39ac598..afdf565dd 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -791,8 +791,6 @@ class cm_trough_physical_iph : public compute_module } } - - // ***************************************************** // System Design Parameters @@ -1632,9 +1630,9 @@ class cm_trough_physical_iph : public compute_module C_csp_tou tou(offtaker_schedule, elec_pricing_schedule, dispatch_model_type, is_offtaker_frac_also_max); // Placeholder for heat price schedule - double heat_price = 0.02; //[$/kWht] - C_timeseries_schedule_inputs heat_pricing_schedule = C_timeseries_schedule_inputs(1.0, heat_price); - tou.mc_heat_pricing_schedule = heat_pricing_schedule; + //double heat_price = 0.02; //[$/kWht] + //C_timeseries_schedule_inputs heat_pricing_schedule = C_timeseries_schedule_inputs(1.0, heat_price); + //tou.mc_heat_pricing_schedule = heat_pricing_schedule; // System parameters C_csp_solver::S_csp_system_params system; From 957a79110b243fe9a807291ba6b53cab5273648a Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:02:16 -0700 Subject: [PATCH 71/85] Remove construction financials from non single owner IPH models. --- ssc/cmod_fresnel_physical_iph.cpp | 207 +++++++++++++++--------------- ssc/cmod_mspt_iph.cpp | 205 ++++++++++++++--------------- ssc/cmod_trough_physical_iph.cpp | 204 ++++++++++++++--------------- 3 files changed, 311 insertions(+), 305 deletions(-) diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 063a41aab..4dbcd63ad 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -253,26 +253,26 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "sales_tax_rate", "Sales Tax Rate", "%", "", "Capital_Costs", "?=0", "", "" }, // Construction financing inputs/outputs (SSC variable table from cmod_cb_construction_financing) - { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate1", "Interest rate, loan 1", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate2", "Interest rate, loan 2", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate3", "Interest rate, loan 3", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate4", "Interest rate, loan 4", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate5", "Interest rate, loan 5", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_months1", "Months prior to operation, loan 1", "", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_months2", "Months prior to operation, loan 2", "", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_months3", "Months prior to operation, loan 3", "", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_months4", "Months prior to operation, loan 4", "", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_months5", "Months prior to operation, loan 5", "", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_percent1", "Percent of total installed cost, loan 1", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_percent2", "Percent of total installed cost, loan 2", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_percent3", "Percent of total installed cost, loan 3", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_percent4", "Percent of total installed cost, loan 4", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_percent5", "Percent of total installed cost, loan 5", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate1", "Upfront fee on principal, loan 1", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate2", "Upfront fee on principal, loan 2", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate3", "Upfront fee on principal, loan 3", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate4", "Upfront fee on principal, loan 4", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate5", "Upfront fee on principal, loan 5", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate1", "Interest rate, loan 1", "%", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate2", "Interest rate, loan 2", "%", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate3", "Interest rate, loan 3", "%", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate4", "Interest rate, loan 4", "%", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate5", "Interest rate, loan 5", "%", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months1", "Months prior to operation, loan 1", "", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months2", "Months prior to operation, loan 2", "", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months3", "Months prior to operation, loan 3", "", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months4", "Months prior to operation, loan 4", "", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months5", "Months prior to operation, loan 5", "", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent1", "Percent of total installed cost, loan 1", "%", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent2", "Percent of total installed cost, loan 2", "%", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent3", "Percent of total installed cost, loan 3", "%", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent4", "Percent of total installed cost, loan 4", "%", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent5", "Percent of total installed cost, loan 5", "%", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate1", "Upfront fee on principal, loan 1", "%", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate2", "Upfront fee on principal, loan 2", "%", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate3", "Upfront fee on principal, loan 3", "%", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate4", "Upfront fee on principal, loan 4", "%", "", "Financial Parameters", "csp_financial_model=1","", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate5", "Upfront fee on principal, loan 5", "%", "", "Financial Parameters", "csp_financial_model=1","", "" }, // OUTPUTS @@ -386,26 +386,26 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "installed_per_capacity", "Estimated total installed cost per net capacity ($/kW)", "$/kW", "", "Capital Costs", "", "", "" }, // Financing - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal1", "Principal, loan 1", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal2", "Principal, loan 2", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal3", "Principal, loan 3", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal4", "Principal, loan 4", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal5", "Principal, loan 5", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest1", "Interest cost, loan 1", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest2", "Interest cost, loan 2", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest3", "Interest cost, loan 3", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest4", "Interest cost, loan 4", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest5", "Interest cost, loan 5", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_total1", "Total financing cost, loan 1", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_total2", "Total financing cost, loan 2", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_total3", "Total financing cost, loan 3", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_total4", "Total financing cost, loan 4", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_total5", "Total financing cost, loan 5", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_percent_total", "Total percent of installed costs, all loans", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal_total", "Total principal, all loans", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest_total", "Total interest costs, all loans", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "construction_financing_cost", "Total construction financing cost", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal1", "Principal, loan 1", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal2", "Principal, loan 2", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal3", "Principal, loan 3", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal4", "Principal, loan 4", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal5", "Principal, loan 5", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest1", "Interest cost, loan 1", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest2", "Interest cost, loan 2", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest3", "Interest cost, loan 3", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest4", "Interest cost, loan 4", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest5", "Interest cost, loan 5", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total1", "Total financing cost, loan 1", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total2", "Total financing cost, loan 2", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total3", "Total financing cost, loan 3", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total4", "Total financing cost, loan 4", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total5", "Total financing cost, loan 5", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_percent_total", "Total percent of installed costs, all loans", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal_total", "Total principal, all loans", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest_total", "Total interest costs, all loans", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "construction_financing_cost", "Total construction financing cost", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1", "", "" }, // **************************************************************************************************************************************** // Timeseries Simulation Outputs here (sim_type = 1): @@ -1442,69 +1442,70 @@ class cm_fresnel_physical_iph : public compute_module } // Update construction financing costs, specifically, update: "construction_financing_cost" - double const_per_interest_rate1 = as_double("const_per_interest_rate1"); - double const_per_interest_rate2 = as_double("const_per_interest_rate2"); - double const_per_interest_rate3 = as_double("const_per_interest_rate3"); - double const_per_interest_rate4 = as_double("const_per_interest_rate4"); - double const_per_interest_rate5 = as_double("const_per_interest_rate5"); - double const_per_months1 = as_double("const_per_months1"); - double const_per_months2 = as_double("const_per_months2"); - double const_per_months3 = as_double("const_per_months3"); - double const_per_months4 = as_double("const_per_months4"); - double const_per_months5 = as_double("const_per_months5"); - double const_per_percent1 = as_double("const_per_percent1"); - double const_per_percent2 = as_double("const_per_percent2"); - double const_per_percent3 = as_double("const_per_percent3"); - double const_per_percent4 = as_double("const_per_percent4"); - double const_per_percent5 = as_double("const_per_percent5"); - double const_per_upfront_rate1 = as_double("const_per_upfront_rate1"); - double const_per_upfront_rate2 = as_double("const_per_upfront_rate2"); - double const_per_upfront_rate3 = as_double("const_per_upfront_rate3"); - double const_per_upfront_rate4 = as_double("const_per_upfront_rate4"); - double const_per_upfront_rate5 = as_double("const_per_upfront_rate5"); - - double const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5; - double const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5; - double const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5; - double const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost; - - const_per_principal1 = const_per_principal2 = const_per_principal3 = const_per_principal4 = const_per_principal5 = - const_per_interest1 = const_per_interest2 = const_per_interest3 = const_per_interest4 = const_per_interest5 = - const_per_total1 = const_per_total2 = const_per_total3 = const_per_total4 = const_per_total5 = - const_per_percent_total = const_per_principal_total = const_per_interest_total = construction_financing_cost = - std::numeric_limits::quiet_NaN(); - - N_financial_parameters::construction_financing_total_cost(total_installed_cost_out, - const_per_interest_rate1, const_per_interest_rate2, const_per_interest_rate3, const_per_interest_rate4, const_per_interest_rate5, - const_per_months1, const_per_months2, const_per_months3, const_per_months4, const_per_months5, - const_per_percent1, const_per_percent2, const_per_percent3, const_per_percent4, const_per_percent5, - const_per_upfront_rate1, const_per_upfront_rate2, const_per_upfront_rate3, const_per_upfront_rate4, const_per_upfront_rate5, - const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5, - const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5, - const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5, - const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost); - - assign("const_per_principal1", (ssc_number_t)const_per_principal1); - assign("const_per_principal2", (ssc_number_t)const_per_principal2); - assign("const_per_principal3", (ssc_number_t)const_per_principal3); - assign("const_per_principal4", (ssc_number_t)const_per_principal4); - assign("const_per_principal5", (ssc_number_t)const_per_principal5); - assign("const_per_interest1", (ssc_number_t)const_per_interest1); - assign("const_per_interest2", (ssc_number_t)const_per_interest2); - assign("const_per_interest3", (ssc_number_t)const_per_interest3); - assign("const_per_interest4", (ssc_number_t)const_per_interest4); - assign("const_per_interest5", (ssc_number_t)const_per_interest5); - assign("const_per_total1", (ssc_number_t)const_per_total1); - assign("const_per_total2", (ssc_number_t)const_per_total2); - assign("const_per_total3", (ssc_number_t)const_per_total3); - assign("const_per_total4", (ssc_number_t)const_per_total4); - assign("const_per_total5", (ssc_number_t)const_per_total5); - assign("const_per_percent_total", (ssc_number_t)const_per_percent_total); - assign("const_per_principal_total", (ssc_number_t)const_per_principal_total); - assign("const_per_interest_total", (ssc_number_t)const_per_interest_total); - assign("construction_financing_cost", (ssc_number_t)construction_financing_cost); - - + if (csp_financial_model == 1) + { + double const_per_interest_rate1 = as_double("const_per_interest_rate1"); + double const_per_interest_rate2 = as_double("const_per_interest_rate2"); + double const_per_interest_rate3 = as_double("const_per_interest_rate3"); + double const_per_interest_rate4 = as_double("const_per_interest_rate4"); + double const_per_interest_rate5 = as_double("const_per_interest_rate5"); + double const_per_months1 = as_double("const_per_months1"); + double const_per_months2 = as_double("const_per_months2"); + double const_per_months3 = as_double("const_per_months3"); + double const_per_months4 = as_double("const_per_months4"); + double const_per_months5 = as_double("const_per_months5"); + double const_per_percent1 = as_double("const_per_percent1"); + double const_per_percent2 = as_double("const_per_percent2"); + double const_per_percent3 = as_double("const_per_percent3"); + double const_per_percent4 = as_double("const_per_percent4"); + double const_per_percent5 = as_double("const_per_percent5"); + double const_per_upfront_rate1 = as_double("const_per_upfront_rate1"); + double const_per_upfront_rate2 = as_double("const_per_upfront_rate2"); + double const_per_upfront_rate3 = as_double("const_per_upfront_rate3"); + double const_per_upfront_rate4 = as_double("const_per_upfront_rate4"); + double const_per_upfront_rate5 = as_double("const_per_upfront_rate5"); + + double const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5; + double const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5; + double const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5; + double const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost; + + const_per_principal1 = const_per_principal2 = const_per_principal3 = const_per_principal4 = const_per_principal5 = + const_per_interest1 = const_per_interest2 = const_per_interest3 = const_per_interest4 = const_per_interest5 = + const_per_total1 = const_per_total2 = const_per_total3 = const_per_total4 = const_per_total5 = + const_per_percent_total = const_per_principal_total = const_per_interest_total = construction_financing_cost = + std::numeric_limits::quiet_NaN(); + + N_financial_parameters::construction_financing_total_cost(total_installed_cost_out, + const_per_interest_rate1, const_per_interest_rate2, const_per_interest_rate3, const_per_interest_rate4, const_per_interest_rate5, + const_per_months1, const_per_months2, const_per_months3, const_per_months4, const_per_months5, + const_per_percent1, const_per_percent2, const_per_percent3, const_per_percent4, const_per_percent5, + const_per_upfront_rate1, const_per_upfront_rate2, const_per_upfront_rate3, const_per_upfront_rate4, const_per_upfront_rate5, + const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5, + const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5, + const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5, + const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost); + + assign("const_per_principal1", (ssc_number_t)const_per_principal1); + assign("const_per_principal2", (ssc_number_t)const_per_principal2); + assign("const_per_principal3", (ssc_number_t)const_per_principal3); + assign("const_per_principal4", (ssc_number_t)const_per_principal4); + assign("const_per_principal5", (ssc_number_t)const_per_principal5); + assign("const_per_interest1", (ssc_number_t)const_per_interest1); + assign("const_per_interest2", (ssc_number_t)const_per_interest2); + assign("const_per_interest3", (ssc_number_t)const_per_interest3); + assign("const_per_interest4", (ssc_number_t)const_per_interest4); + assign("const_per_interest5", (ssc_number_t)const_per_interest5); + assign("const_per_total1", (ssc_number_t)const_per_total1); + assign("const_per_total2", (ssc_number_t)const_per_total2); + assign("const_per_total3", (ssc_number_t)const_per_total3); + assign("const_per_total4", (ssc_number_t)const_per_total4); + assign("const_per_total5", (ssc_number_t)const_per_total5); + assign("const_per_percent_total", (ssc_number_t)const_per_percent_total); + assign("const_per_principal_total", (ssc_number_t)const_per_principal_total); + assign("const_per_interest_total", (ssc_number_t)const_per_interest_total); + assign("construction_financing_cost", (ssc_number_t)construction_financing_cost); + } } diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index 9c098d1f8..8d00b6efe 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -305,26 +305,26 @@ static var_info _cm_vtab_mspt_iph[] = { { SSC_INPUT, SSC_NUMBER, "csp.pt.sf.land_overhead_factor", "Land overhead factor", "", "", "Heliostat Field", "*", "", "" }, // Construction financing inputs/outputs (SSC variable table from cmod_cb_construction_financing) -{ SSC_INPUT, SSC_NUMBER, "const_per_interest_rate1", "Interest rate, loan 1", "%", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_interest_rate2", "Interest rate, loan 2", "%", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_interest_rate3", "Interest rate, loan 3", "%", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_interest_rate4", "Interest rate, loan 4", "%", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_interest_rate5", "Interest rate, loan 5", "%", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_months1", "Months prior to operation, loan 1", "", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_months2", "Months prior to operation, loan 2", "", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_months3", "Months prior to operation, loan 3", "", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_months4", "Months prior to operation, loan 4", "", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_months5", "Months prior to operation, loan 5", "", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_percent1", "Percent of total installed cost, loan 1", "%", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_percent2", "Percent of total installed cost, loan 2", "%", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_percent3", "Percent of total installed cost, loan 3", "%", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_percent4", "Percent of total installed cost, loan 4", "%", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_percent5", "Percent of total installed cost, loan 5", "%", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate1", "Upfront fee on principal, loan 1", "%", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate2", "Upfront fee on principal, loan 2", "%", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate3", "Upfront fee on principal, loan 3", "%", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate4", "Upfront fee on principal, loan 4", "%", "", "Financial Parameters", "*", "", ""}, -{ SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate5", "Upfront fee on principal, loan 5", "%", "", "Financial Parameters", "*", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_interest_rate1", "Interest rate, loan 1", "%", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_interest_rate2", "Interest rate, loan 2", "%", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_interest_rate3", "Interest rate, loan 3", "%", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_interest_rate4", "Interest rate, loan 4", "%", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_interest_rate5", "Interest rate, loan 5", "%", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_months1", "Months prior to operation, loan 1", "", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_months2", "Months prior to operation, loan 2", "", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_months3", "Months prior to operation, loan 3", "", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_months4", "Months prior to operation, loan 4", "", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_months5", "Months prior to operation, loan 5", "", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_percent1", "Percent of total installed cost, loan 1", "%", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_percent2", "Percent of total installed cost, loan 2", "%", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_percent3", "Percent of total installed cost, loan 3", "%", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_percent4", "Percent of total installed cost, loan 4", "%", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_percent5", "Percent of total installed cost, loan 5", "%", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate1", "Upfront fee on principal, loan 1", "%", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate2", "Upfront fee on principal, loan 2", "%", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate3", "Upfront fee on principal, loan 3", "%", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate4", "Upfront fee on principal, loan 4", "%", "", "Financial Parameters", "csp_financial_model=1", "", ""}, +{ SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate5", "Upfront fee on principal, loan 5", "%", "", "Financial Parameters", "csp_financial_model=1", "", ""}, // **************************************************************************************************************************************** // DEPRECATED INPUTS -- exec() checks if a) variable is assigned and b) if replacement variable is assigned. throws exception if a=true and b=false @@ -434,25 +434,25 @@ static var_info _cm_vtab_mspt_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "csp.pt.cost.installed_per_capacity", "Estimated installed cost per cap", "$", "", "System Costs", "*", "", "" }, // Financing -{ SSC_OUTPUT, SSC_NUMBER, "const_per_principal1", "Principal, loan 1", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_principal2", "Principal, loan 2", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_principal3", "Principal, loan 3", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_principal4", "Principal, loan 4", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_principal5", "Principal, loan 5", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_interest1", "Interest cost, loan 1", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_interest2", "Interest cost, loan 2", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_interest3", "Interest cost, loan 3", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_interest4", "Interest cost, loan 4", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_interest5", "Interest cost, loan 5", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_total1", "Total financing cost, loan 1", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_total2", "Total financing cost, loan 2", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_total3", "Total financing cost, loan 3", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_total4", "Total financing cost, loan 4", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_total5", "Total financing cost, loan 5", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_percent_total", "Total percent of installed costs, all loans", "%", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_principal_total", "Total principal, all loans", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "const_per_interest_total", "Total interest costs, all loans", "$", "", "Financial Parameters", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "construction_financing_cost", "Total construction financing cost", "$", "", "Financial Parameters", "*", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_principal1", "Principal, loan 1", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_principal2", "Principal, loan 2", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_principal3", "Principal, loan 3", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_principal4", "Principal, loan 4", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_principal5", "Principal, loan 5", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_interest1", "Interest cost, loan 1", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_interest2", "Interest cost, loan 2", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_interest3", "Interest cost, loan 3", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_interest4", "Interest cost, loan 4", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_interest5", "Interest cost, loan 5", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_total1", "Total financing cost, loan 1", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_total2", "Total financing cost, loan 2", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_total3", "Total financing cost, loan 3", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_total4", "Total financing cost, loan 4", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_total5", "Total financing cost, loan 5", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_percent_total", "Total percent of installed costs, all loans", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_principal_total", "Total principal, all loans", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "const_per_interest_total", "Total interest costs, all loans", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "construction_financing_cost", "Total construction financing cost", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, @@ -2204,69 +2204,70 @@ class cm_mspt_iph : public compute_module assign("csp.pt.cost.installed_per_capacity", (ssc_number_t)estimated_installed_cost_per_cap); // Update construction financing costs, specifically, update: "construction_financing_cost" - double const_per_interest_rate1 = as_double("const_per_interest_rate1"); - double const_per_interest_rate2 = as_double("const_per_interest_rate2"); - double const_per_interest_rate3 = as_double("const_per_interest_rate3"); - double const_per_interest_rate4 = as_double("const_per_interest_rate4"); - double const_per_interest_rate5 = as_double("const_per_interest_rate5"); - double const_per_months1 = as_double("const_per_months1"); - double const_per_months2 = as_double("const_per_months2"); - double const_per_months3 = as_double("const_per_months3"); - double const_per_months4 = as_double("const_per_months4"); - double const_per_months5 = as_double("const_per_months5"); - double const_per_percent1 = as_double("const_per_percent1"); - double const_per_percent2 = as_double("const_per_percent2"); - double const_per_percent3 = as_double("const_per_percent3"); - double const_per_percent4 = as_double("const_per_percent4"); - double const_per_percent5 = as_double("const_per_percent5"); - double const_per_upfront_rate1 = as_double("const_per_upfront_rate1"); - double const_per_upfront_rate2 = as_double("const_per_upfront_rate2"); - double const_per_upfront_rate3 = as_double("const_per_upfront_rate3"); - double const_per_upfront_rate4 = as_double("const_per_upfront_rate4"); - double const_per_upfront_rate5 = as_double("const_per_upfront_rate5"); - - double const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5; - double const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5; - double const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5; - double const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost; - - const_per_principal1 = const_per_principal2 = const_per_principal3 = const_per_principal4 = const_per_principal5 = - const_per_interest1 = const_per_interest2 = const_per_interest3 = const_per_interest4 = const_per_interest5 = - const_per_total1 = const_per_total2 = const_per_total3 = const_per_total4 = const_per_total5 = - const_per_percent_total = const_per_principal_total = const_per_interest_total = construction_financing_cost = - std::numeric_limits::quiet_NaN(); - - N_financial_parameters::construction_financing_total_cost(total_installed_cost, - const_per_interest_rate1, const_per_interest_rate2, const_per_interest_rate3, const_per_interest_rate4, const_per_interest_rate5, - const_per_months1, const_per_months2, const_per_months3, const_per_months4, const_per_months5, - const_per_percent1, const_per_percent2, const_per_percent3, const_per_percent4, const_per_percent5, - const_per_upfront_rate1, const_per_upfront_rate2, const_per_upfront_rate3, const_per_upfront_rate4, const_per_upfront_rate5, - const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5, - const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5, - const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5, - const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost); - - assign("const_per_principal1", (ssc_number_t)const_per_principal1); - assign("const_per_principal2", (ssc_number_t)const_per_principal2); - assign("const_per_principal3", (ssc_number_t)const_per_principal3); - assign("const_per_principal4", (ssc_number_t)const_per_principal4); - assign("const_per_principal5", (ssc_number_t)const_per_principal5); - assign("const_per_interest1", (ssc_number_t)const_per_interest1); - assign("const_per_interest2", (ssc_number_t)const_per_interest2); - assign("const_per_interest3", (ssc_number_t)const_per_interest3); - assign("const_per_interest4", (ssc_number_t)const_per_interest4); - assign("const_per_interest5", (ssc_number_t)const_per_interest5); - assign("const_per_total1", (ssc_number_t)const_per_total1); - assign("const_per_total2", (ssc_number_t)const_per_total2); - assign("const_per_total3", (ssc_number_t)const_per_total3); - assign("const_per_total4", (ssc_number_t)const_per_total4); - assign("const_per_total5", (ssc_number_t)const_per_total5); - assign("const_per_percent_total", (ssc_number_t)const_per_percent_total); - assign("const_per_principal_total", (ssc_number_t)const_per_principal_total); - assign("const_per_interest_total", (ssc_number_t)const_per_interest_total); - assign("construction_financing_cost", (ssc_number_t)construction_financing_cost); - - + if (csp_financial_model == 1) + { + double const_per_interest_rate1 = as_double("const_per_interest_rate1"); + double const_per_interest_rate2 = as_double("const_per_interest_rate2"); + double const_per_interest_rate3 = as_double("const_per_interest_rate3"); + double const_per_interest_rate4 = as_double("const_per_interest_rate4"); + double const_per_interest_rate5 = as_double("const_per_interest_rate5"); + double const_per_months1 = as_double("const_per_months1"); + double const_per_months2 = as_double("const_per_months2"); + double const_per_months3 = as_double("const_per_months3"); + double const_per_months4 = as_double("const_per_months4"); + double const_per_months5 = as_double("const_per_months5"); + double const_per_percent1 = as_double("const_per_percent1"); + double const_per_percent2 = as_double("const_per_percent2"); + double const_per_percent3 = as_double("const_per_percent3"); + double const_per_percent4 = as_double("const_per_percent4"); + double const_per_percent5 = as_double("const_per_percent5"); + double const_per_upfront_rate1 = as_double("const_per_upfront_rate1"); + double const_per_upfront_rate2 = as_double("const_per_upfront_rate2"); + double const_per_upfront_rate3 = as_double("const_per_upfront_rate3"); + double const_per_upfront_rate4 = as_double("const_per_upfront_rate4"); + double const_per_upfront_rate5 = as_double("const_per_upfront_rate5"); + + double const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5; + double const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5; + double const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5; + double const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost; + + const_per_principal1 = const_per_principal2 = const_per_principal3 = const_per_principal4 = const_per_principal5 = + const_per_interest1 = const_per_interest2 = const_per_interest3 = const_per_interest4 = const_per_interest5 = + const_per_total1 = const_per_total2 = const_per_total3 = const_per_total4 = const_per_total5 = + const_per_percent_total = const_per_principal_total = const_per_interest_total = construction_financing_cost = + std::numeric_limits::quiet_NaN(); + + N_financial_parameters::construction_financing_total_cost(total_installed_cost, + const_per_interest_rate1, const_per_interest_rate2, const_per_interest_rate3, const_per_interest_rate4, const_per_interest_rate5, + const_per_months1, const_per_months2, const_per_months3, const_per_months4, const_per_months5, + const_per_percent1, const_per_percent2, const_per_percent3, const_per_percent4, const_per_percent5, + const_per_upfront_rate1, const_per_upfront_rate2, const_per_upfront_rate3, const_per_upfront_rate4, const_per_upfront_rate5, + const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5, + const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5, + const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5, + const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost); + + assign("const_per_principal1", (ssc_number_t)const_per_principal1); + assign("const_per_principal2", (ssc_number_t)const_per_principal2); + assign("const_per_principal3", (ssc_number_t)const_per_principal3); + assign("const_per_principal4", (ssc_number_t)const_per_principal4); + assign("const_per_principal5", (ssc_number_t)const_per_principal5); + assign("const_per_interest1", (ssc_number_t)const_per_interest1); + assign("const_per_interest2", (ssc_number_t)const_per_interest2); + assign("const_per_interest3", (ssc_number_t)const_per_interest3); + assign("const_per_interest4", (ssc_number_t)const_per_interest4); + assign("const_per_interest5", (ssc_number_t)const_per_interest5); + assign("const_per_total1", (ssc_number_t)const_per_total1); + assign("const_per_total2", (ssc_number_t)const_per_total2); + assign("const_per_total3", (ssc_number_t)const_per_total3); + assign("const_per_total4", (ssc_number_t)const_per_total4); + assign("const_per_total5", (ssc_number_t)const_per_total5); + assign("const_per_percent_total", (ssc_number_t)const_per_percent_total); + assign("const_per_principal_total", (ssc_number_t)const_per_principal_total); + assign("const_per_interest_total", (ssc_number_t)const_per_interest_total); + assign("construction_financing_cost", (ssc_number_t)construction_financing_cost); + } // ***************************************************** // If calling cmod to run design only, return here diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index afdf565dd..c273478ce 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -338,26 +338,26 @@ static var_info _cm_vtab_trough_physical_iph[] = { // Construction financing inputs/outputs (SSC variable table from cmod_cb_construction_financing) - { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate1", "Interest rate, loan 1", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate2", "Interest rate, loan 2", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate3", "Interest rate, loan 3", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate4", "Interest rate, loan 4", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate5", "Interest rate, loan 5", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_months1", "Months prior to operation, loan 1", "", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_months2", "Months prior to operation, loan 2", "", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_months3", "Months prior to operation, loan 3", "", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_months4", "Months prior to operation, loan 4", "", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_months5", "Months prior to operation, loan 5", "", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_percent1", "Percent of total installed cost, loan 1", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_percent2", "Percent of total installed cost, loan 2", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_percent3", "Percent of total installed cost, loan 3", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_percent4", "Percent of total installed cost, loan 4", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_percent5", "Percent of total installed cost, loan 5", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate1", "Upfront fee on principal, loan 1", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate2", "Upfront fee on principal, loan 2", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate3", "Upfront fee on principal, loan 3", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate4", "Upfront fee on principal, loan 4", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate5", "Upfront fee on principal, loan 5", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate1", "Interest rate, loan 1", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate2", "Interest rate, loan 2", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate3", "Interest rate, loan 3", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate4", "Interest rate, loan 4", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate5", "Interest rate, loan 5", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months1", "Months prior to operation, loan 1", "", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months2", "Months prior to operation, loan 2", "", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months3", "Months prior to operation, loan 3", "", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months4", "Months prior to operation, loan 4", "", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months5", "Months prior to operation, loan 5", "", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent1", "Percent of total installed cost, loan 1", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent2", "Percent of total installed cost, loan 2", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent3", "Percent of total installed cost, loan 3", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent4", "Percent of total installed cost, loan 4", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent5", "Percent of total installed cost, loan 5", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate1", "Upfront fee on principal, loan 1", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate2", "Upfront fee on principal, loan 2", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate3", "Upfront fee on principal, loan 3", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate4", "Upfront fee on principal, loan 4", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate5", "Upfront fee on principal, loan 5", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, // ************************************************************************************************* // OUTPUTS @@ -485,25 +485,25 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "csp.dtr.cost.installed_per_capacity", "Estimated total installed cost per net capacity ($/kW)", "$/kW", "", "Capital Costs", "", "", "" }, // Financing - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal1", "Principal, loan 1", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal2", "Principal, loan 2", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal3", "Principal, loan 3", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal4", "Principal, loan 4", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal5", "Principal, loan 5", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest1", "Interest cost, loan 1", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest2", "Interest cost, loan 2", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest3", "Interest cost, loan 3", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest4", "Interest cost, loan 4", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest5", "Interest cost, loan 5", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_total1", "Total financing cost, loan 1", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_total2", "Total financing cost, loan 2", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_total3", "Total financing cost, loan 3", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_total4", "Total financing cost, loan 4", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_total5", "Total financing cost, loan 5", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_percent_total", "Total percent of installed costs, all loans", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal_total", "Total principal, all loans", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest_total", "Total interest costs, all loans", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "construction_financing_cost", "Total construction financing cost", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal1", "Principal, loan 1", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal2", "Principal, loan 2", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal3", "Principal, loan 3", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal4", "Principal, loan 4", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal5", "Principal, loan 5", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest1", "Interest cost, loan 1", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest2", "Interest cost, loan 2", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest3", "Interest cost, loan 3", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest4", "Interest cost, loan 4", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest5", "Interest cost, loan 5", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total1", "Total financing cost, loan 1", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total2", "Total financing cost, loan 2", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total3", "Total financing cost, loan 3", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total4", "Total financing cost, loan 4", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total5", "Total financing cost, loan 5", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_percent_total", "Total percent of installed costs, all loans", "%", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal_total", "Total principal, all loans", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest_total", "Total interest costs, all loans", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "construction_financing_cost", "Total construction financing cost", "$", "", "Financial Parameters", "csp_financial_model=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "ppa_price_input", "PPA prices - yearly", "$/kWh", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1","", "" }, @@ -2144,67 +2144,71 @@ class cm_trough_physical_iph : public compute_module // Update construction financing costs, specifically, update: "construction_financing_cost" - double const_per_interest_rate1 = as_double("const_per_interest_rate1"); - double const_per_interest_rate2 = as_double("const_per_interest_rate2"); - double const_per_interest_rate3 = as_double("const_per_interest_rate3"); - double const_per_interest_rate4 = as_double("const_per_interest_rate4"); - double const_per_interest_rate5 = as_double("const_per_interest_rate5"); - double const_per_months1 = as_double("const_per_months1"); - double const_per_months2 = as_double("const_per_months2"); - double const_per_months3 = as_double("const_per_months3"); - double const_per_months4 = as_double("const_per_months4"); - double const_per_months5 = as_double("const_per_months5"); - double const_per_percent1 = as_double("const_per_percent1"); - double const_per_percent2 = as_double("const_per_percent2"); - double const_per_percent3 = as_double("const_per_percent3"); - double const_per_percent4 = as_double("const_per_percent4"); - double const_per_percent5 = as_double("const_per_percent5"); - double const_per_upfront_rate1 = as_double("const_per_upfront_rate1"); - double const_per_upfront_rate2 = as_double("const_per_upfront_rate2"); - double const_per_upfront_rate3 = as_double("const_per_upfront_rate3"); - double const_per_upfront_rate4 = as_double("const_per_upfront_rate4"); - double const_per_upfront_rate5 = as_double("const_per_upfront_rate5"); - - double const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5; - double const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5; - double const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5; - double const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost; - - const_per_principal1 = const_per_principal2 = const_per_principal3 = const_per_principal4 = const_per_principal5 = - const_per_interest1 = const_per_interest2 = const_per_interest3 = const_per_interest4 = const_per_interest5 = - const_per_total1 = const_per_total2 = const_per_total3 = const_per_total4 = const_per_total5 = - const_per_percent_total = const_per_principal_total = const_per_interest_total = construction_financing_cost = - std::numeric_limits::quiet_NaN(); - - N_financial_parameters::construction_financing_total_cost(total_installed_cost_out, - const_per_interest_rate1, const_per_interest_rate2, const_per_interest_rate3, const_per_interest_rate4, const_per_interest_rate5, - const_per_months1, const_per_months2, const_per_months3, const_per_months4, const_per_months5, - const_per_percent1, const_per_percent2, const_per_percent3, const_per_percent4, const_per_percent5, - const_per_upfront_rate1, const_per_upfront_rate2, const_per_upfront_rate3, const_per_upfront_rate4, const_per_upfront_rate5, - const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5, - const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5, - const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5, - const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost); - - assign("const_per_principal1", (ssc_number_t)const_per_principal1); - assign("const_per_principal2", (ssc_number_t)const_per_principal2); - assign("const_per_principal3", (ssc_number_t)const_per_principal3); - assign("const_per_principal4", (ssc_number_t)const_per_principal4); - assign("const_per_principal5", (ssc_number_t)const_per_principal5); - assign("const_per_interest1", (ssc_number_t)const_per_interest1); - assign("const_per_interest2", (ssc_number_t)const_per_interest2); - assign("const_per_interest3", (ssc_number_t)const_per_interest3); - assign("const_per_interest4", (ssc_number_t)const_per_interest4); - assign("const_per_interest5", (ssc_number_t)const_per_interest5); - assign("const_per_total1", (ssc_number_t)const_per_total1); - assign("const_per_total2", (ssc_number_t)const_per_total2); - assign("const_per_total3", (ssc_number_t)const_per_total3); - assign("const_per_total4", (ssc_number_t)const_per_total4); - assign("const_per_total5", (ssc_number_t)const_per_total5); - assign("const_per_percent_total", (ssc_number_t)const_per_percent_total); - assign("const_per_principal_total", (ssc_number_t)const_per_principal_total); - assign("const_per_interest_total", (ssc_number_t)const_per_interest_total); - assign("construction_financing_cost", (ssc_number_t)construction_financing_cost); + if (csp_financial_model == 1) + { + double const_per_interest_rate1 = as_double("const_per_interest_rate1"); + double const_per_interest_rate2 = as_double("const_per_interest_rate2"); + double const_per_interest_rate3 = as_double("const_per_interest_rate3"); + double const_per_interest_rate4 = as_double("const_per_interest_rate4"); + double const_per_interest_rate5 = as_double("const_per_interest_rate5"); + double const_per_months1 = as_double("const_per_months1"); + double const_per_months2 = as_double("const_per_months2"); + double const_per_months3 = as_double("const_per_months3"); + double const_per_months4 = as_double("const_per_months4"); + double const_per_months5 = as_double("const_per_months5"); + double const_per_percent1 = as_double("const_per_percent1"); + double const_per_percent2 = as_double("const_per_percent2"); + double const_per_percent3 = as_double("const_per_percent3"); + double const_per_percent4 = as_double("const_per_percent4"); + double const_per_percent5 = as_double("const_per_percent5"); + double const_per_upfront_rate1 = as_double("const_per_upfront_rate1"); + double const_per_upfront_rate2 = as_double("const_per_upfront_rate2"); + double const_per_upfront_rate3 = as_double("const_per_upfront_rate3"); + double const_per_upfront_rate4 = as_double("const_per_upfront_rate4"); + double const_per_upfront_rate5 = as_double("const_per_upfront_rate5"); + + double const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5; + double const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5; + double const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5; + double const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost; + + const_per_principal1 = const_per_principal2 = const_per_principal3 = const_per_principal4 = const_per_principal5 = + const_per_interest1 = const_per_interest2 = const_per_interest3 = const_per_interest4 = const_per_interest5 = + const_per_total1 = const_per_total2 = const_per_total3 = const_per_total4 = const_per_total5 = + const_per_percent_total = const_per_principal_total = const_per_interest_total = construction_financing_cost = + std::numeric_limits::quiet_NaN(); + + N_financial_parameters::construction_financing_total_cost(total_installed_cost_out, + const_per_interest_rate1, const_per_interest_rate2, const_per_interest_rate3, const_per_interest_rate4, const_per_interest_rate5, + const_per_months1, const_per_months2, const_per_months3, const_per_months4, const_per_months5, + const_per_percent1, const_per_percent2, const_per_percent3, const_per_percent4, const_per_percent5, + const_per_upfront_rate1, const_per_upfront_rate2, const_per_upfront_rate3, const_per_upfront_rate4, const_per_upfront_rate5, + const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5, + const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5, + const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5, + const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost); + + assign("const_per_principal1", (ssc_number_t)const_per_principal1); + assign("const_per_principal2", (ssc_number_t)const_per_principal2); + assign("const_per_principal3", (ssc_number_t)const_per_principal3); + assign("const_per_principal4", (ssc_number_t)const_per_principal4); + assign("const_per_principal5", (ssc_number_t)const_per_principal5); + assign("const_per_interest1", (ssc_number_t)const_per_interest1); + assign("const_per_interest2", (ssc_number_t)const_per_interest2); + assign("const_per_interest3", (ssc_number_t)const_per_interest3); + assign("const_per_interest4", (ssc_number_t)const_per_interest4); + assign("const_per_interest5", (ssc_number_t)const_per_interest5); + assign("const_per_total1", (ssc_number_t)const_per_total1); + assign("const_per_total2", (ssc_number_t)const_per_total2); + assign("const_per_total3", (ssc_number_t)const_per_total3); + assign("const_per_total4", (ssc_number_t)const_per_total4); + assign("const_per_total5", (ssc_number_t)const_per_total5); + assign("const_per_percent_total", (ssc_number_t)const_per_percent_total); + assign("const_per_principal_total", (ssc_number_t)const_per_principal_total); + assign("const_per_interest_total", (ssc_number_t)const_per_interest_total); + assign("construction_financing_cost", (ssc_number_t)construction_financing_cost); + } + } // Return if only called for design point From 87270549d74f61dee36ece383ce8e3b2d75cd8fd Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon, 11 Nov 2024 18:38:17 -0700 Subject: [PATCH 72/85] Add construction financing equations to trough and fresnel cmods. --- ssc/cmod_fresnel_physical.cpp | 205 +++++++++++++++++----------------- ssc/cmod_trough_physical.cpp | 108 ++++++++++++++++++ 2 files changed, 211 insertions(+), 102 deletions(-) diff --git a/ssc/cmod_fresnel_physical.cpp b/ssc/cmod_fresnel_physical.cpp index 703b2ffa1..68be3707c 100644 --- a/ssc/cmod_fresnel_physical.cpp +++ b/ssc/cmod_fresnel_physical.cpp @@ -289,26 +289,26 @@ static var_info _cm_vtab_fresnel_physical[] = { // Construction financing inputs/outputs (SSC variable table from cmod_cb_construction_financing) - { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate1", "Interest rate, loan 1", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate2", "Interest rate, loan 2", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate3", "Interest rate, loan 3", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate4", "Interest rate, loan 4", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate5", "Interest rate, loan 5", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_months1", "Months prior to operation, loan 1", "", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_months2", "Months prior to operation, loan 2", "", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_months3", "Months prior to operation, loan 3", "", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_months4", "Months prior to operation, loan 4", "", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_months5", "Months prior to operation, loan 5", "", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_percent1", "Percent of total installed cost, loan 1", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_percent2", "Percent of total installed cost, loan 2", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_percent3", "Percent of total installed cost, loan 3", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_percent4", "Percent of total installed cost, loan 4", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_percent5", "Percent of total installed cost, loan 5", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate1", "Upfront fee on principal, loan 1", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate2", "Upfront fee on principal, loan 2", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate3", "Upfront fee on principal, loan 3", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate4", "Upfront fee on principal, loan 4", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate5", "Upfront fee on principal, loan 5", "%", "", "Financial Parameters", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate1", "Interest rate, loan 1", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate2", "Interest rate, loan 2", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate3", "Interest rate, loan 3", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate4", "Interest rate, loan 4", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate5", "Interest rate, loan 5", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months1", "Months prior to operation, loan 1", "", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months2", "Months prior to operation, loan 2", "", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months3", "Months prior to operation, loan 3", "", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months4", "Months prior to operation, loan 4", "", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months5", "Months prior to operation, loan 5", "", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent1", "Percent of total installed cost, loan 1", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent2", "Percent of total installed cost, loan 2", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent3", "Percent of total installed cost, loan 3", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent4", "Percent of total installed cost, loan 4", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent5", "Percent of total installed cost, loan 5", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate1", "Upfront fee on principal, loan 1", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate2", "Upfront fee on principal, loan 2", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate3", "Upfront fee on principal, loan 3", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate4", "Upfront fee on principal, loan 4", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate5", "Upfront fee on principal, loan 5", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, @@ -427,25 +427,25 @@ static var_info _cm_vtab_fresnel_physical[] = { { SSC_OUTPUT, SSC_NUMBER, "installed_per_capacity", "Estimated total installed cost per net capacity ($/kW)", "$/kW", "", "Capital Costs", "", "", "" }, // Financing - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal1", "Principal, loan 1", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal2", "Principal, loan 2", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal3", "Principal, loan 3", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal4", "Principal, loan 4", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal5", "Principal, loan 5", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest1", "Interest cost, loan 1", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest2", "Interest cost, loan 2", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest3", "Interest cost, loan 3", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest4", "Interest cost, loan 4", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest5", "Interest cost, loan 5", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_total1", "Total financing cost, loan 1", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_total2", "Total financing cost, loan 2", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_total3", "Total financing cost, loan 3", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_total4", "Total financing cost, loan 4", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_total5", "Total financing cost, loan 5", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_percent_total", "Total percent of installed costs, all loans", "%", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_principal_total", "Total principal, all loans", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "const_per_interest_total", "Total interest costs, all loans", "$", "", "Financial Parameters", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "construction_financing_cost", "Total construction financing cost", "$", "", "Financial Parameters", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal1", "Principal, loan 1", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal2", "Principal, loan 2", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal3", "Principal, loan 3", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal4", "Principal, loan 4", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal5", "Principal, loan 5", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest1", "Interest cost, loan 1", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest2", "Interest cost, loan 2", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest3", "Interest cost, loan 3", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest4", "Interest cost, loan 4", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest5", "Interest cost, loan 5", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total1", "Total financing cost, loan 1", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total2", "Total financing cost, loan 2", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total3", "Total financing cost, loan 3", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total4", "Total financing cost, loan 4", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total5", "Total financing cost, loan 5", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_percent_total", "Total percent of installed costs, all loans", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal_total", "Total principal, all loans", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest_total", "Total interest costs, all loans", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "construction_financing_cost", "Total construction financing cost", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, // **************************************************************************************************************************************** // Timeseries Simulation Outputs here (sim_type = 1): @@ -1628,69 +1628,70 @@ class cm_fresnel_physical : public compute_module } // Update construction financing costs, specifically, update: "construction_financing_cost" - double const_per_interest_rate1 = as_double("const_per_interest_rate1"); - double const_per_interest_rate2 = as_double("const_per_interest_rate2"); - double const_per_interest_rate3 = as_double("const_per_interest_rate3"); - double const_per_interest_rate4 = as_double("const_per_interest_rate4"); - double const_per_interest_rate5 = as_double("const_per_interest_rate5"); - double const_per_months1 = as_double("const_per_months1"); - double const_per_months2 = as_double("const_per_months2"); - double const_per_months3 = as_double("const_per_months3"); - double const_per_months4 = as_double("const_per_months4"); - double const_per_months5 = as_double("const_per_months5"); - double const_per_percent1 = as_double("const_per_percent1"); - double const_per_percent2 = as_double("const_per_percent2"); - double const_per_percent3 = as_double("const_per_percent3"); - double const_per_percent4 = as_double("const_per_percent4"); - double const_per_percent5 = as_double("const_per_percent5"); - double const_per_upfront_rate1 = as_double("const_per_upfront_rate1"); - double const_per_upfront_rate2 = as_double("const_per_upfront_rate2"); - double const_per_upfront_rate3 = as_double("const_per_upfront_rate3"); - double const_per_upfront_rate4 = as_double("const_per_upfront_rate4"); - double const_per_upfront_rate5 = as_double("const_per_upfront_rate5"); - - double const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5; - double const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5; - double const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5; - double const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost; - - const_per_principal1 = const_per_principal2 = const_per_principal3 = const_per_principal4 = const_per_principal5 = - const_per_interest1 = const_per_interest2 = const_per_interest3 = const_per_interest4 = const_per_interest5 = - const_per_total1 = const_per_total2 = const_per_total3 = const_per_total4 = const_per_total5 = - const_per_percent_total = const_per_principal_total = const_per_interest_total = construction_financing_cost = - std::numeric_limits::quiet_NaN(); - - N_financial_parameters::construction_financing_total_cost(total_installed_cost_out, - const_per_interest_rate1, const_per_interest_rate2, const_per_interest_rate3, const_per_interest_rate4, const_per_interest_rate5, - const_per_months1, const_per_months2, const_per_months3, const_per_months4, const_per_months5, - const_per_percent1, const_per_percent2, const_per_percent3, const_per_percent4, const_per_percent5, - const_per_upfront_rate1, const_per_upfront_rate2, const_per_upfront_rate3, const_per_upfront_rate4, const_per_upfront_rate5, - const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5, - const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5, - const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5, - const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost); - - assign("const_per_principal1", (ssc_number_t)const_per_principal1); - assign("const_per_principal2", (ssc_number_t)const_per_principal2); - assign("const_per_principal3", (ssc_number_t)const_per_principal3); - assign("const_per_principal4", (ssc_number_t)const_per_principal4); - assign("const_per_principal5", (ssc_number_t)const_per_principal5); - assign("const_per_interest1", (ssc_number_t)const_per_interest1); - assign("const_per_interest2", (ssc_number_t)const_per_interest2); - assign("const_per_interest3", (ssc_number_t)const_per_interest3); - assign("const_per_interest4", (ssc_number_t)const_per_interest4); - assign("const_per_interest5", (ssc_number_t)const_per_interest5); - assign("const_per_total1", (ssc_number_t)const_per_total1); - assign("const_per_total2", (ssc_number_t)const_per_total2); - assign("const_per_total3", (ssc_number_t)const_per_total3); - assign("const_per_total4", (ssc_number_t)const_per_total4); - assign("const_per_total5", (ssc_number_t)const_per_total5); - assign("const_per_percent_total", (ssc_number_t)const_per_percent_total); - assign("const_per_principal_total", (ssc_number_t)const_per_principal_total); - assign("const_per_interest_total", (ssc_number_t)const_per_interest_total); - assign("construction_financing_cost", (ssc_number_t)construction_financing_cost); - - + if (csp_financial_model < 5 || csp_financial_model == 6) + { + double const_per_interest_rate1 = as_double("const_per_interest_rate1"); + double const_per_interest_rate2 = as_double("const_per_interest_rate2"); + double const_per_interest_rate3 = as_double("const_per_interest_rate3"); + double const_per_interest_rate4 = as_double("const_per_interest_rate4"); + double const_per_interest_rate5 = as_double("const_per_interest_rate5"); + double const_per_months1 = as_double("const_per_months1"); + double const_per_months2 = as_double("const_per_months2"); + double const_per_months3 = as_double("const_per_months3"); + double const_per_months4 = as_double("const_per_months4"); + double const_per_months5 = as_double("const_per_months5"); + double const_per_percent1 = as_double("const_per_percent1"); + double const_per_percent2 = as_double("const_per_percent2"); + double const_per_percent3 = as_double("const_per_percent3"); + double const_per_percent4 = as_double("const_per_percent4"); + double const_per_percent5 = as_double("const_per_percent5"); + double const_per_upfront_rate1 = as_double("const_per_upfront_rate1"); + double const_per_upfront_rate2 = as_double("const_per_upfront_rate2"); + double const_per_upfront_rate3 = as_double("const_per_upfront_rate3"); + double const_per_upfront_rate4 = as_double("const_per_upfront_rate4"); + double const_per_upfront_rate5 = as_double("const_per_upfront_rate5"); + + double const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5; + double const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5; + double const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5; + double const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost; + + const_per_principal1 = const_per_principal2 = const_per_principal3 = const_per_principal4 = const_per_principal5 = + const_per_interest1 = const_per_interest2 = const_per_interest3 = const_per_interest4 = const_per_interest5 = + const_per_total1 = const_per_total2 = const_per_total3 = const_per_total4 = const_per_total5 = + const_per_percent_total = const_per_principal_total = const_per_interest_total = construction_financing_cost = + std::numeric_limits::quiet_NaN(); + + N_financial_parameters::construction_financing_total_cost(total_installed_cost_out, + const_per_interest_rate1, const_per_interest_rate2, const_per_interest_rate3, const_per_interest_rate4, const_per_interest_rate5, + const_per_months1, const_per_months2, const_per_months3, const_per_months4, const_per_months5, + const_per_percent1, const_per_percent2, const_per_percent3, const_per_percent4, const_per_percent5, + const_per_upfront_rate1, const_per_upfront_rate2, const_per_upfront_rate3, const_per_upfront_rate4, const_per_upfront_rate5, + const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5, + const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5, + const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5, + const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost); + + assign("const_per_principal1", (ssc_number_t)const_per_principal1); + assign("const_per_principal2", (ssc_number_t)const_per_principal2); + assign("const_per_principal3", (ssc_number_t)const_per_principal3); + assign("const_per_principal4", (ssc_number_t)const_per_principal4); + assign("const_per_principal5", (ssc_number_t)const_per_principal5); + assign("const_per_interest1", (ssc_number_t)const_per_interest1); + assign("const_per_interest2", (ssc_number_t)const_per_interest2); + assign("const_per_interest3", (ssc_number_t)const_per_interest3); + assign("const_per_interest4", (ssc_number_t)const_per_interest4); + assign("const_per_interest5", (ssc_number_t)const_per_interest5); + assign("const_per_total1", (ssc_number_t)const_per_total1); + assign("const_per_total2", (ssc_number_t)const_per_total2); + assign("const_per_total3", (ssc_number_t)const_per_total3); + assign("const_per_total4", (ssc_number_t)const_per_total4); + assign("const_per_total5", (ssc_number_t)const_per_total5); + assign("const_per_percent_total", (ssc_number_t)const_per_percent_total); + assign("const_per_principal_total", (ssc_number_t)const_per_principal_total); + assign("const_per_interest_total", (ssc_number_t)const_per_interest_total); + assign("construction_financing_cost", (ssc_number_t)construction_financing_cost); + } } diff --git a/ssc/cmod_trough_physical.cpp b/ssc/cmod_trough_physical.cpp index 611e555e5..84a77430b 100644 --- a/ssc/cmod_trough_physical.cpp +++ b/ssc/cmod_trough_physical.cpp @@ -398,6 +398,28 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_INPUT, SSC_NUMBER, "csp.dtr.cost.sales_tax.percent", "Sales Tax Percentage of Direct Cost", "%", "", "Capital_Costs", "?=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "sales_tax_rate", "Sales Tax Rate", "%", "", "Capital_Costs", "?=0", "", "" }, + // Construction financing inputs/outputs (SSC variable table from cmod_cb_construction_financing) + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate1", "Interest rate, loan 1", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate2", "Interest rate, loan 2", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate3", "Interest rate, loan 3", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate4", "Interest rate, loan 4", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_interest_rate5", "Interest rate, loan 5", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months1", "Months prior to operation, loan 1", "", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months2", "Months prior to operation, loan 2", "", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months3", "Months prior to operation, loan 3", "", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months4", "Months prior to operation, loan 4", "", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_months5", "Months prior to operation, loan 5", "", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent1", "Percent of total installed cost, loan 1", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent2", "Percent of total installed cost, loan 2", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent3", "Percent of total installed cost, loan 3", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent4", "Percent of total installed cost, loan 4", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_percent5", "Percent of total installed cost, loan 5", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate1", "Upfront fee on principal, loan 1", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate2", "Upfront fee on principal, loan 2", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate3", "Upfront fee on principal, loan 3", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate4", "Upfront fee on principal, loan 4", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_INPUT, SSC_NUMBER, "const_per_upfront_rate5", "Upfront fee on principal, loan 5", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + // ************************************************************************************************* // OUTPUTS @@ -523,6 +545,26 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_OUTPUT, SSC_NUMBER, "total_installed_cost", "Total installed cost", "$", "", "Capital Costs", "", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "csp.dtr.cost.installed_per_capacity", "Estimated total installed cost per net capacity ($/kW)", "$/kW", "", "Capital Costs", "", "", "" }, + // Financing + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal1", "Principal, loan 1", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal2", "Principal, loan 2", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal3", "Principal, loan 3", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal4", "Principal, loan 4", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal5", "Principal, loan 5", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest1", "Interest cost, loan 1", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest2", "Interest cost, loan 2", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest3", "Interest cost, loan 3", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest4", "Interest cost, loan 4", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest5", "Interest cost, loan 5", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total1", "Total financing cost, loan 1", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total2", "Total financing cost, loan 2", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total3", "Total financing cost, loan 3", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total4", "Total financing cost, loan 4", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_total5", "Total financing cost, loan 5", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_percent_total", "Total percent of installed costs, all loans", "%", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_principal_total", "Total principal, all loans", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "const_per_interest_total", "Total interest costs, all loans", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "construction_financing_cost", "Total construction financing cost", "$", "", "Financial Parameters", "csp_financial_model<5|csp_financial_model=6", "", "" }, // Simulation Kernel { SSC_OUTPUT, SSC_ARRAY, "time_hr", "Time at end of timestep", "hr", "", "solver", "sim_type=1", "", "" }, @@ -2335,6 +2377,72 @@ class cm_trough_physical : public compute_module assign("total_installed_cost", total_installed_cost_out); assign("csp.dtr.cost.installed_per_capacity", installed_per_capacity_out); } + + // Update construction financing costs, specifically, update: "construction_financing_cost" + if (csp_financial_model < 5 || csp_financial_model == 6) + { + double const_per_interest_rate1 = as_double("const_per_interest_rate1"); + double const_per_interest_rate2 = as_double("const_per_interest_rate2"); + double const_per_interest_rate3 = as_double("const_per_interest_rate3"); + double const_per_interest_rate4 = as_double("const_per_interest_rate4"); + double const_per_interest_rate5 = as_double("const_per_interest_rate5"); + double const_per_months1 = as_double("const_per_months1"); + double const_per_months2 = as_double("const_per_months2"); + double const_per_months3 = as_double("const_per_months3"); + double const_per_months4 = as_double("const_per_months4"); + double const_per_months5 = as_double("const_per_months5"); + double const_per_percent1 = as_double("const_per_percent1"); + double const_per_percent2 = as_double("const_per_percent2"); + double const_per_percent3 = as_double("const_per_percent3"); + double const_per_percent4 = as_double("const_per_percent4"); + double const_per_percent5 = as_double("const_per_percent5"); + double const_per_upfront_rate1 = as_double("const_per_upfront_rate1"); + double const_per_upfront_rate2 = as_double("const_per_upfront_rate2"); + double const_per_upfront_rate3 = as_double("const_per_upfront_rate3"); + double const_per_upfront_rate4 = as_double("const_per_upfront_rate4"); + double const_per_upfront_rate5 = as_double("const_per_upfront_rate5"); + + double const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5; + double const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5; + double const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5; + double const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost; + + const_per_principal1 = const_per_principal2 = const_per_principal3 = const_per_principal4 = const_per_principal5 = + const_per_interest1 = const_per_interest2 = const_per_interest3 = const_per_interest4 = const_per_interest5 = + const_per_total1 = const_per_total2 = const_per_total3 = const_per_total4 = const_per_total5 = + const_per_percent_total = const_per_principal_total = const_per_interest_total = construction_financing_cost = + std::numeric_limits::quiet_NaN(); + + N_financial_parameters::construction_financing_total_cost(total_installed_cost_out, + const_per_interest_rate1, const_per_interest_rate2, const_per_interest_rate3, const_per_interest_rate4, const_per_interest_rate5, + const_per_months1, const_per_months2, const_per_months3, const_per_months4, const_per_months5, + const_per_percent1, const_per_percent2, const_per_percent3, const_per_percent4, const_per_percent5, + const_per_upfront_rate1, const_per_upfront_rate2, const_per_upfront_rate3, const_per_upfront_rate4, const_per_upfront_rate5, + const_per_principal1, const_per_principal2, const_per_principal3, const_per_principal4, const_per_principal5, + const_per_interest1, const_per_interest2, const_per_interest3, const_per_interest4, const_per_interest5, + const_per_total1, const_per_total2, const_per_total3, const_per_total4, const_per_total5, + const_per_percent_total, const_per_principal_total, const_per_interest_total, construction_financing_cost); + + assign("const_per_principal1", (ssc_number_t)const_per_principal1); + assign("const_per_principal2", (ssc_number_t)const_per_principal2); + assign("const_per_principal3", (ssc_number_t)const_per_principal3); + assign("const_per_principal4", (ssc_number_t)const_per_principal4); + assign("const_per_principal5", (ssc_number_t)const_per_principal5); + assign("const_per_interest1", (ssc_number_t)const_per_interest1); + assign("const_per_interest2", (ssc_number_t)const_per_interest2); + assign("const_per_interest3", (ssc_number_t)const_per_interest3); + assign("const_per_interest4", (ssc_number_t)const_per_interest4); + assign("const_per_interest5", (ssc_number_t)const_per_interest5); + assign("const_per_total1", (ssc_number_t)const_per_total1); + assign("const_per_total2", (ssc_number_t)const_per_total2); + assign("const_per_total3", (ssc_number_t)const_per_total3); + assign("const_per_total4", (ssc_number_t)const_per_total4); + assign("const_per_total5", (ssc_number_t)const_per_total5); + assign("const_per_percent_total", (ssc_number_t)const_per_percent_total); + assign("const_per_principal_total", (ssc_number_t)const_per_principal_total); + assign("const_per_interest_total", (ssc_number_t)const_per_interest_total); + assign("construction_financing_cost", (ssc_number_t)construction_financing_cost); + } } // Return if only called for design point From 47e6211ed797dde5794693c6932dd63557aa93bf Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon, 11 Nov 2024 22:37:51 -0700 Subject: [PATCH 73/85] Define trough financial model in tests. --- test/input_cases/trough_physical_defaults.h | 1 + 1 file changed, 1 insertion(+) diff --git a/test/input_cases/trough_physical_defaults.h b/test/input_cases/trough_physical_defaults.h index 959ff03b6..7a6a42098 100644 --- a/test/input_cases/trough_physical_defaults.h +++ b/test/input_cases/trough_physical_defaults.h @@ -336,6 +336,7 @@ ssc_data_t trough_physical_defaults() ssc_number_t p_trough_loop_control[25] = { 8, 1, 1, 8, 1, 1, 7, 1, 1, 6, 1, 1, 5, 1, 1, 4, 1, 1, 3, 1, 1, 2, 1, 1, 1 }; ssc_data_set_array(data, "trough_loop_control", p_trough_loop_control, 25); ssc_data_set_number(data, "ppa_soln_mode", 0); + ssc_data_set_number(data, "csp_financial_model", 8); // No financial return data; } From cb70a184904c78315ee0b0202ef5498897717a0b Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:27:20 -0700 Subject: [PATCH 74/85] Clarify and sync CSP output labels to remove 'grid' terminology. Synchronize watt-hr labels. --- ssc/cmod_fresnel_physical.cpp | 6 ++-- ssc/cmod_fresnel_physical_iph.cpp | 18 +++++------ ssc/cmod_linear_fresnel_dsg_iph.cpp | 10 +++---- ssc/cmod_mspt_iph.cpp | 46 ++++++++++++++--------------- ssc/cmod_tcsmolten_salt.cpp | 6 ++-- ssc/cmod_trough_physical.cpp | 6 ++-- ssc/cmod_trough_physical_iph.cpp | 26 ++++++++-------- 7 files changed, 59 insertions(+), 59 deletions(-) diff --git a/ssc/cmod_fresnel_physical.cpp b/ssc/cmod_fresnel_physical.cpp index 68be3707c..ba5aa0fed 100644 --- a/ssc/cmod_fresnel_physical.cpp +++ b/ssc/cmod_fresnel_physical.cpp @@ -542,7 +542,7 @@ static var_info _cm_vtab_fresnel_physical[] = { { SSC_OUTPUT, SSC_ARRAY, "monthly_energy", "Monthly AC energy in Year 1", "kWh", "", "Post-process", "sim_type=1", "LENGTH=12", "" }, // Annual Outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net electricity production with availability derate", "kWe-hr", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net electrical energy generated with availability derate", "kWe-hr", "", "Post-process", "sim_type=1", "", "" }, //{ SSC_OUTPUT, SSC_NUMBER, "annual_gross_energy", "Annual Gross Electrical Energy Production w/ avail derate", "kWe-hr", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, //{ SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumptoin w/ avail derate", "kWe-hr", "", "Post-process", "*", "", "" }, @@ -594,8 +594,8 @@ static var_info _cm_vtab_fresnel_physical[] = { { SSC_OUTPUT, SSC_ARRAY, "P_fixed", "Parasitic power fixed load", "MWe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "P_plant_balance_tot", "Parasitic power generation-dependent load", "MWe", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "P_out_net", "Total electric power to grid", "MWe", "", "system", "*", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen", "Total electric power to grid w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "P_out_net", "System net electric power generated", "MWe", "", "system", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electric power generated with availability derate", "kWe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_W_cycle_gross", "Electrical source - Power cycle gross output", "kWhe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "conversion_factor", "Gross to Net Conversion Factor", "%", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "system", "sim_type=1", "", "" }, diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 4dbcd63ad..c7914f518 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -347,7 +347,7 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { // Thermal Storage { SSC_OUTPUT, SSC_NUMBER, "vol_tank", "Total tank volume", "m3", "", "Power Cycle", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "Q_tes_des", "TES design capacity", "MWt-hr", "", "Power Cycle", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "Q_tes_des", "TES design capacity", "MWht", "", "Power Cycle", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "d_tank", "Tank diameter", "m", "", "Power Cycle", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "vol_min", "Minimum Fluid Volume", "m3", "", "Power Cycle", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "q_dot_loss_tes_des", "Estimated TES Heat Loss", "MW", "", "Power Cycle", "*", "", "" }, @@ -562,9 +562,9 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "operating_modes_a", "First 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "operating_modes_b", "Next 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "operating_modes_c", "Final 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "Total thermal power to heat sink with available derate", "kWe", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen", "Total electric power to grid w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "Total thermal power to heat sink with available derate in MMBtu/hr", "MMBtu/hr", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "System net thermal power generated with availability derate", "kWt", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electric power generated with availability derate", "kWe", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "System net thermal power generated with availability derate in MMBtu/hr","MMBtu/hr", "", "system", "sim_type=1", "", "" }, // Monthly Outputs @@ -573,8 +573,8 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { // Annual Outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Net Thermal Energy Production w/ avail derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual Net Thermal Energy Production w/ avail derate in MMBtu", "MMBtu", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net thermal energy generated with availability derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual net thermal energy generated with availability derate in MMBtu","MMBtu", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWht", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption w/ avail derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_total_water_use", "Total Annual Water Usage", "m^3", "", "Post-process", "sim_type=1", "", "" }, @@ -615,7 +615,7 @@ class cm_fresnel_physical_iph : public compute_module double T_htf_hot_des = as_double("T_loop_out"); //[C] double tshours = as_double("tshours"); //[-] double q_dot_pc_des = as_double("q_pb_design"); //[MWt] HEAT SINK design thermal power - double Q_tes = q_dot_pc_des * tshours; //[MWt-hr] + double Q_tes = q_dot_pc_des * tshours; //[MWht] const double MMBTU_TO_KWh = 293.07107; // 1 MMBtu = 293.07107 kWh // Convert IPH Input Units @@ -1173,7 +1173,7 @@ class cm_fresnel_physical_iph : public compute_module double q_pb_design = as_double("q_pb_design"); double q_dot_pc_des = q_pb_design; //[MWt] - Q_tes = q_dot_pc_des * tshours; //[MWt-hr] + Q_tes = q_dot_pc_des * tshours; //[MWht] double mdot_field_des = c_fresnel.m_m_dot_design; // [kg/s] @@ -1284,7 +1284,7 @@ class cm_fresnel_physical_iph : public compute_module double V_tes_htf_avail_calc /*m3*/, V_tes_htf_total_calc /*m3*/, h_tank_calc /*m*/, d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, - Q_tes_des_calc /*MWt-hr*/; + Q_tes_des_calc /*MWht*/; storage.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, h_tank_calc, d_tank_calc, diff --git a/ssc/cmod_linear_fresnel_dsg_iph.cpp b/ssc/cmod_linear_fresnel_dsg_iph.cpp index f9ec14012..6d16c2f91 100644 --- a/ssc/cmod_linear_fresnel_dsg_iph.cpp +++ b/ssc/cmod_linear_fresnel_dsg_iph.cpp @@ -183,9 +183,9 @@ static var_info _cm_vtab_linear_fresnel_dsg_iph[] = { // SYSTEM { SSC_OUTPUT, SSC_ARRAY, "W_dot_parasitic_tot", "System total electrical parasitic", "MWe", "", "Controller", "*", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "Total thermal power to grid w/ avail. derate", "kWt", "", "system", "*", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen", "Total electric power to grid w/ avail. derate", "kWe", "", "system", "*", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "Total thermal power to grid w/ avail. derate in MMBtu/hr","MMBtu/hr","","system", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "System net thermal power generated with availability derate","kWt", "","system", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electric power generated with availability derate","kWe", "","system", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "System net thermal power generated with availability derate in MMBtu/hr","MMBtu/hr","","system", "*", "", "" }, // Controller @@ -194,8 +194,8 @@ static var_info _cm_vtab_linear_fresnel_dsg_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "op_mode_3", "3rd op. mode, if applicable", "", "", "Controller", "*", "", "" }, // Annual Outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Net Thermal Energy Production w/ avail derate", "kWht", "", "Post-process", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual Net Thermal Energy Production w/ avail derate in MMBtu", "MMBtu","","Post - process", "", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net thermal energy generated with availability derate", "kWht", "", "Post-process", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual net thermal energy generated with availability derate in MMBtu","MMBtu","","Post - process", "", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_field_energy", "Annual Gross Thermal Energy Production w/ avail derate", "kWht", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWht", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumptoin w/ avail derate", "kWhe", "", "Post-process", "*", "", "" }, diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index 8d00b6efe..c287bd8bf 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -389,7 +389,7 @@ static var_info _cm_vtab_mspt_iph[] = { // Heater { SSC_OUTPUT, SSC_NUMBER, "q_dot_heater_des", "Heater design thermal power", "MWt", "", "Heater", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "W_dot_heater_des", "Heater electricity consumption at design", "MWe", "", "Heater", "*", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "E_heater_su_des", "Heater startup energy", "MWt-hr", "", "Heater", "*", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "E_heater_su_des", "Heater startup energy", "MWht", "", "Heater", "*", "", "" }, // Power Cycle //{ SSC_OUTPUT, SSC_NUMBER, "m_dot_htf_cycle_des", "PC HTF mass flow rate at design", "kg/s", "", "Power Cycle", "*", "", "" }, @@ -398,7 +398,7 @@ static var_info _cm_vtab_mspt_iph[] = { //{ SSC_OUTPUT, SSC_NUMBER, "W_dot_cycle_cooling_des", "PC cooling power at design", "MWe", "", "Power Cycle", "*", "", "" }, // TES -{ SSC_OUTPUT, SSC_NUMBER, "Q_tes_des", "TES design capacity", "MWt-hr", "", "TES Design Calc", "*", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "Q_tes_des", "TES design capacity", "MWht", "", "TES Design Calc", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "V_tes_htf_avail_des", "TES volume of HTF available for heat transfer", "m3", "", "TES Design Calc", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "V_tes_htf_total_des", "TES total HTF volume", "m3", "", "TES Design Calc", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "d_tank_tes", "TES tank diameter", "m", "", "TES Design Calc", "*", "", "" }, @@ -600,15 +600,15 @@ static var_info _cm_vtab_mspt_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "operating_modes_b", "Next 3 operating modes tried", "", "", "", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_ARRAY, "operating_modes_c", "Final 3 operating modes tried", "", "", "", "sim_type=1", "", ""}, -{ SSC_OUTPUT, SSC_ARRAY, "gen_heat", "Total thermal power to heat sink with available derate", "kWt", "", "", "sim_type=1", "", ""}, -{ SSC_OUTPUT, SSC_ARRAY, "gen", "Total electric power to grid w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, -{ SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "Total thermal power to heat sink with available derate in MMBtu/hr", "MMBtu/hr", "", "system", "sim_type=1", "", "" }, +{ SSC_OUTPUT, SSC_ARRAY, "gen_heat", "System net thermal power generated with availability derate", "kWt", "", "", "sim_type=1", "", ""}, +{ SSC_OUTPUT, SSC_ARRAY, "gen", "System net electric power generated with availability derate", "kWe", "", "system", "sim_type=1", "", "" }, +{ SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "System net thermal power generated with availability derate in MMBtu/hr", "MMBtu/hr", "", "system", "sim_type=1", "", "" }, // Annual single-value outputs -{ SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Thermal Energy to Heat Sink w/ avail derate", "kWht", "", "Post-process", "sim_type=1", "", ""}, -{ SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_htf", "Annual receiver power delivered to HTF", "MWt-hr", "", "Tower and Receiver", "sim_type=1", "", ""}, -{ SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual Thermal Energy to Heat Sink w/ avail derate in MMBtu", "MMBtu", "", "Post-process", "sim_type=1", "", ""}, +{ SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net thermal energy generated with availability derate", "kWht", "", "Post-process", "sim_type=1", "", ""}, +{ SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_htf", "Annual receiver power delivered to HTF", "MWht", "", "Tower and Receiver", "sim_type=1", "", ""}, +{ SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual net thermal energy generated with availability derate in MMBtu", "MMBtu", "", "Post-process", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption w/ avail derate", "kWhe", "", "Post-process", "sim_type=1", "", ""}, @@ -618,14 +618,14 @@ static var_info _cm_vtab_mspt_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "annual_total_water_use", "Total annual water usage from mirror washing", "m3", "", "Post-process", "sim_type=1", "", ""}, -{ SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_inc", "Annual receiver incident thermal power after reflective losses", "MWt-hr", "", "Tower and Receiver", "sim_type=1", "", ""}, -{ SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_loss", "Annual receiver convective and radiative losses", "MWt-hr", "", "Tower and Receiver", "sim_type=1", "", ""}, -{ SSC_OUTPUT, SSC_NUMBER, "annual_q_piping_loss", "Annual tower piping losses", "MWt-hr", "", "Tower and Receiver", "sim_type=1", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_startup", "Annual receiver startup energy", "MWt-hr", "", "Tower and Receiver", "sim_type=1", "", "" }, -{ SSC_OUTPUT, SSC_NUMBER, "annual_E_tower_pump", "Annual tower pumping power", "MWe-hr", "", "Tower and Receiver", "sim_type=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_inc", "Annual receiver incident thermal power after reflective losses", "MWht", "", "Tower and Receiver", "sim_type=1", "", ""}, +{ SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_loss", "Annual receiver convective and radiative losses", "MWht", "", "Tower and Receiver", "sim_type=1", "", ""}, +{ SSC_OUTPUT, SSC_NUMBER, "annual_q_piping_loss", "Annual tower piping losses", "MWht", "", "Tower and Receiver", "sim_type=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_startup", "Annual receiver startup energy", "MWht", "", "Tower and Receiver", "sim_type=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "annual_E_tower_pump", "Annual tower pumping power", "MWhe", "", "Tower and Receiver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_eta_rec_th", "Annual receiver thermal efficiency ignoring rec reflective loss", "", "", "Tower and Receiver", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_NUMBER, "annual_eta_rec_th_incl_refl", "Annual receiver thermal efficiency including reflective loss", "", "", "Tower and Receiver", "sim_type=1", "", ""}, -{ SSC_OUTPUT, SSC_NUMBER, "annual_q_defocus_est", "Annual defocus loss estimate", "MWt-hr", "", "Tower and Receiver", "sim_type=1", "", "" }, +{ SSC_OUTPUT, SSC_NUMBER, "annual_q_defocus_est", "Annual defocus loss estimate", "MWht", "", "Tower and Receiver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "sim_cpu_run_time", "Simulation duration clock time", "s", "", "", "sim_type=1", "", ""}, @@ -687,7 +687,7 @@ class cm_mspt_iph : public compute_module // System Design Calcs double q_dot_pc_des = as_double("q_pb_design"); //[MWt] HEAT SINK design thermal power - double Q_tes = q_dot_pc_des * tshours; //[MWt-hr] + double Q_tes = q_dot_pc_des * tshours; //[MWht] double q_dot_rec_des = q_dot_pc_des * as_number("solarm"); //[MWt] // Weather reader @@ -1929,23 +1929,23 @@ class cm_mspt_iph : public compute_module // Heater assign("q_dot_heater_des", q_dot_heater_des); //[MWt] double W_dot_heater_des_calc = 0.0; //[MWe] - double E_heater_su_des = 0.0; //[MWt-hr] + double E_heater_su_des = 0.0; //[MWht] if (is_parallel_heater) { p_electric_resistance->get_design_parameters(E_heater_su_des, W_dot_heater_des_calc); } assign("W_dot_heater_des", (ssc_number_t)W_dot_heater_des_calc); //[MWe] - assign("E_heater_su_des", (ssc_number_t)E_heater_su_des); //[MWt-hr] + assign("E_heater_su_des", (ssc_number_t)E_heater_su_des); //[MWht] // ************************* // Thermal Energy Storage double V_tes_htf_avail_calc /*m3*/, V_tes_htf_total_calc /*m3*/, h_tank_calc /*m*/, d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, - Q_tes_des_calc /*MWt-hr*/; + Q_tes_des_calc /*MWht*/; storage.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); - assign("Q_tes_des", Q_tes_des_calc); //[MWt-hr] + assign("Q_tes_des", Q_tes_des_calc); //[MWht] assign("V_tes_htf_avail_des", V_tes_htf_avail_calc); //[m3] assign("V_tes_htf_total_des", V_tes_htf_total_calc); //[m3] assign("d_tank_tes", d_tank_calc); //[m] @@ -2396,8 +2396,8 @@ class cm_mspt_iph : public compute_module } } - accumulate_annual_for_year("Q_thermal", "annual_q_rec_htf", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[MWt-hr] - accumulate_annual_for_year("q_dot_rec_inc", "annual_q_rec_inc", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[MWt-hr] + accumulate_annual_for_year("Q_thermal", "annual_q_rec_htf", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[MWht] + accumulate_annual_for_year("q_dot_rec_inc", "annual_q_rec_inc", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[MWht] accumulate_annual_for_year("q_thermal_loss", "annual_q_rec_loss", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); accumulate_annual_for_year("q_piping_losses", "annual_q_piping_loss", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); accumulate_annual_for_year("q_startup", "annual_q_rec_startup", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); @@ -2417,8 +2417,8 @@ class cm_mspt_iph : public compute_module i_defocus = min(1.0, max(0.0, p_defocus[i])); q_defocus_sum += p_q_rec_in[i] * (1.0 - i_defocus); //[MWt] } - q_defocus_sum *= sim_setup.m_report_step / 3600.0; //[MWt-hr] - assign("annual_q_defocus_est", q_defocus_sum); //[MWt-hr] + q_defocus_sum *= sim_setup.m_report_step / 3600.0; //[MWht] + assign("annual_q_defocus_est", q_defocus_sum); //[MWht] std::clock_t clock_end = std::clock(); double sim_cpu_run_time = (clock_end - clock_start) / (double)CLOCKS_PER_SEC; //[s] diff --git a/ssc/cmod_tcsmolten_salt.cpp b/ssc/cmod_tcsmolten_salt.cpp index 21d51b485..e510bba77 100644 --- a/ssc/cmod_tcsmolten_salt.cpp +++ b/ssc/cmod_tcsmolten_salt.cpp @@ -686,7 +686,7 @@ static var_info _cm_vtab_tcsmolten_salt[] = { { SSC_OUTPUT, SSC_ARRAY, "P_rec_heattrace", "Receiver heat trace parasitic load", "MWe", "", "System", "sim_type=1&is_rec_model_trans=1", "", ""}, // System outputs - { SSC_OUTPUT, SSC_ARRAY, "P_out_net", "Total electric power to grid", "MWe", "", "", "sim_type=1", "", ""}, + { SSC_OUTPUT, SSC_ARRAY, "P_out_net", "System net electric power generated", "MWe", "", "", "sim_type=1", "", ""}, // Controller outputs { SSC_OUTPUT, SSC_ARRAY, "tou_value", "CSP operating time-of-use value", "", "", "", "sim_type=1", "", ""}, @@ -740,10 +740,10 @@ static var_info _cm_vtab_tcsmolten_salt[] = { { SSC_OUTPUT, SSC_ARRAY, "operating_modes_b", "Next 3 operating modes tried", "", "", "", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_ARRAY, "operating_modes_c", "Final 3 operating modes tried", "", "", "", "sim_type=1", "", ""}, - { SSC_OUTPUT, SSC_ARRAY, "gen", "Total electric power to grid with available derate", "kWe", "", "", "sim_type=1", "", ""}, + { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electric power generated with availability derate", "kWe", "", "", "sim_type=1", "", ""}, // Annual single-value outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual total electric power to grid", "kWhe", "", "", "sim_type=1", "", ""}, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net electrical energy generated with availability derate", "kWhe", "", "", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_NUMBER, "annual_W_cycle_gross", "Electrical source - power cycle gross output", "kWhe", "", "", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_NUMBER, "annual_W_cooling_tower", "Total of condenser operation parasitic", "kWhe", "", "PC", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_htf", "Annual receiver power delivered to HTF", "MWt-hr", "", "Tower and Receiver", "sim_type=1", "", "" }, diff --git a/ssc/cmod_trough_physical.cpp b/ssc/cmod_trough_physical.cpp index 84a77430b..afcf126b4 100644 --- a/ssc/cmod_trough_physical.cpp +++ b/ssc/cmod_trough_physical.cpp @@ -715,7 +715,7 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_OUTPUT, SSC_ARRAY, "monthly_energy", "Monthly AC energy in Year 1", "kWh", "", "Post-process", "sim_type=1", "LENGTH=12", "" }, // Annual Outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net electrical energy production with availability derate", "kWe-hr", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net electrical energy generated with availability derate", "kWe-hr", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_total_water_use", "Total Annual Water Usage", "m^3", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_field_freeze_protection", "Annual thermal power for field freeze protection", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, @@ -765,8 +765,8 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_OUTPUT, SSC_ARRAY, "P_fixed", "Parasitic power fixed load", "MWe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "P_plant_balance_tot", "Parasitic power generation-dependent load", "MWe", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "P_out_net", "Total electric power to grid", "MWe", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen", "Total electric power to grid w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "P_out_net", "System net electric power generated", "MWe", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electric power generated with availability derate", "kWe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_W_cycle_gross", "Electrical source - Power cycle gross output", "kWhe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "conversion_factor", "Gross to Net Conversion Factor", "%", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "system", "sim_type=1", "", "" }, diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index c273478ce..75d189e18 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -419,11 +419,11 @@ static var_info _cm_vtab_trough_physical_iph[] = { // Heater { SSC_OUTPUT, SSC_NUMBER, "q_dot_heater_des", "Heater design thermal power", "MWt", "", "Heater", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "W_dot_heater_des", "Heater electricity consumption at design", "MWe", "", "Heater", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "E_heater_su_des", "Heater startup energy", "MWt-hr", "", "Heater", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "E_heater_su_des", "Heater startup energy", "MWht", "", "Heater", "*", "", "" }, // Thermal Storage { SSC_OUTPUT, SSC_NUMBER, "vol_tank", "Total tank volume", "m3", "", "Thermal Storage","*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "q_tes", "TES design capacity", "MWt-hr", "", "Thermal Storage","*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "q_tes", "TES design capacity", "MWht", "", "Thermal Storage","*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "csp_pt_tes_tank_diameter", "Tank diameter", "m", "", "Thermal Storage","*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "csp_pt_tes_tank_height", "Tank height", "m", "", "Thermal Storage","*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "q_dot_tes_est", "Estimated TES Heat Loss", "MW", "", "Thermal Storage","*", "", "" }, @@ -652,8 +652,8 @@ static var_info _cm_vtab_trough_physical_iph[] = { // Annual Outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Net Thermal Energy Production w/ avail derate", "kWht", "", "Post-process", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual Net Thermal Energy Production w/ avail derate in MMBtu", "MMBtu", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net thermal energy generated with availability derate", "kWht", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual net thermal energy generated with availability derate in MMBtu", "MMBtu", "", "Post-process", "sim_type=1", "", "" }, //{ SSC_OUTPUT, SSC_NUMBER, "annual_gross_energy", "Annual Gross Electrical Energy Production w/ avail derate", "kWhe", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWht", "", "Post-process", "sim_type=1", "", "" }, @@ -716,9 +716,9 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "P_fixed", "Parasitic power fixed load", "MWe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "P_plant_balance_tot", "Parasitic power generation-dependent load", "MWe", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "Total thermal power to grid w/ avail. derate", "kWt", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen", "Total electric power to grid w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "Total thermal power to grid w/ avail. derate in MMBtu/hr", "MMBtu/hr", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "System net thermal power generated with availability derate", "kWt", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electric power generated with availability derate", "kWe", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "System net thermal power generated with availability derate in MMBtu/hr", "MMBtu/hr", "", "system", "sim_type=1", "", "" }, //{ SSC_OUTPUT, SSC_NUMBER, "conversion_factor", "Gross to Net Conversion Factor", "%", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "system", "sim_type=1", "", "" }, @@ -770,7 +770,7 @@ class cm_trough_physical_iph : public compute_module double T_htf_hot_des = as_double("T_loop_out"); //[C] double tshours = as_double("tshours"); //[-] double q_dot_hs_des = as_double("q_pb_design"); //[MWt] HEAT SINK design thermal power - double Q_tes = q_dot_hs_des * tshours; //[MWt-hr] + double Q_tes = q_dot_hs_des * tshours; //[MWht] int is_dispatch = as_boolean("is_dispatch"); int tes_type = as_integer("tes_type"); @@ -1288,7 +1288,7 @@ class cm_trough_physical_iph : public compute_module as_integer("Fluid"), // [-] field fluid identifier as_matrix("field_fl_props"), // [-] field fluid properties q_dot_hs_des, // [MWt] Design heat rate in and out of tes - Q_tes, // [MWt-hr] design storage capacity + Q_tes, // [MWht] design storage capacity as_integer("is_h_tank_fixed"), // [] Sizing Method (0) use fixed diameter, (1) use fixed height, (2) use preset inputs as_double("h_tank_in"), // [m] Tank height as_double("d_tank_in"), // [m] Tank diameter @@ -1855,12 +1855,12 @@ class cm_trough_physical_iph : public compute_module { assign("q_dot_heater_des", q_dot_heater_des); //[MWt] double W_dot_heater_des_calc = 0.0; //[MWe] - double E_heater_su_des = 0.0; //[MWt-hr] + double E_heater_su_des = 0.0; //[MWhr] if (is_parallel_heater) { p_electric_resistance->get_design_parameters(E_heater_su_des, W_dot_heater_des_calc); } assign("W_dot_heater_des", (ssc_number_t)W_dot_heater_des_calc); //[MWe] - assign("E_heater_su_des", (ssc_number_t)E_heater_su_des); //[MWt-hr] + assign("E_heater_su_des", (ssc_number_t)E_heater_su_des); //[MWhr] } @@ -1869,7 +1869,7 @@ class cm_trough_physical_iph : public compute_module double V_tes_htf_avail_calc /*m3*/, V_tes_htf_total_calc /*m3*/, h_tank_calc /*m*/, d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, - Q_tes_des_calc /*MWt-hr*/; + Q_tes_des_calc /*MWht*/; double tes_htf_min_temp = 0; double tes_htf_max_temp = 0; @@ -1906,7 +1906,7 @@ class cm_trough_physical_iph : public compute_module double V_tank_hot_ini = hot_vol_frac * V_tes_htf_total_calc; // m3 double T_avg = (as_double("T_loop_in_des") + as_double("T_loop_out")) / 2.0; // C - assign("q_tes", Q_tes_des_calc); // MWt-hr + assign("q_tes", Q_tes_des_calc); // MWht assign("tes_avail_vol", V_tes_htf_avail_calc); // m3 assign("vol_tank", V_tes_htf_total_calc); // m3 assign("csp_pt_tes_tank_height", h_tank_calc); // m From bdf82331ee0e72e56683d35ec53b929892b8a9b1 Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:28:55 -0700 Subject: [PATCH 75/85] Shorten CSP generation vartable labels. Synchronize watt-hr labels. --- ssc/cmod_fresnel_physical.cpp | 32 ++++++------ ssc/cmod_fresnel_physical_iph.cpp | 10 ++-- ssc/cmod_linear_fresnel_dsg_iph.cpp | 12 ++--- ssc/cmod_mspt_iph.cpp | 10 ++-- ssc/cmod_tcsmolten_salt.cpp | 78 ++++++++++++++--------------- ssc/cmod_trough_physical.cpp | 30 +++++------ ssc/cmod_trough_physical_iph.cpp | 10 ++-- 7 files changed, 91 insertions(+), 91 deletions(-) diff --git a/ssc/cmod_fresnel_physical.cpp b/ssc/cmod_fresnel_physical.cpp index ba5aa0fed..dcad18053 100644 --- a/ssc/cmod_fresnel_physical.cpp +++ b/ssc/cmod_fresnel_physical.cpp @@ -94,7 +94,7 @@ static var_info _cm_vtab_fresnel_physical[] = { { SSC_INPUT, SSC_NUMBER, "T_startup", "Power block startup temperature", "C", "", "Solar_Field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "rec_su_delay", "Fixed startup delay time for the receiver", "hr", "", "Solar_Field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "rec_qf_delay", "Energy-based receiver startup delay (fraction of rated thermal power)", "-", "", "Solar_Field", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "p_start", "Collector startup energy, per SCA", "kWe-hr", "", "Solar_Field", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "p_start", "Collector startup energy, per SCA", "kWhe", "", "Solar_Field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "L_rnr_pb", "Length of runner pipe in power block", "m", "", "Solar_Field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "use_abs_or_rel_mdot_limit", "Use mass flow abs (0) or relative (1) limits", "", "", "solar_field", "?=0", "", "" }, @@ -231,7 +231,7 @@ static var_info _cm_vtab_fresnel_physical[] = { { SSC_INPUT, SSC_NUMBER, "disp_spec_scaling", "Dispatch optimization scaling heuristic", "-", "", "tou", "?=-1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "disp_inventory_incentive", "Dispatch storage terminal inventory incentive multiplier", "", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "q_rec_standby", "Receiver standby energy consumption", "kWt", "", "tou", "?=9e99", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWe-hr", "", "tou", "?=0.0", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWhe", "", "tou", "?=0.0", "", "SIMULATION_PARAMETER" }, @@ -539,16 +539,16 @@ static var_info _cm_vtab_fresnel_physical[] = { { SSC_OUTPUT, SSC_ARRAY, "q_balance", "Relative energy balance error", "", "", "solver", "sim_type=1", "", "" }, // Monthly Outputs - { SSC_OUTPUT, SSC_ARRAY, "monthly_energy", "Monthly AC energy in Year 1", "kWh", "", "Post-process", "sim_type=1", "LENGTH=12", "" }, + { SSC_OUTPUT, SSC_ARRAY, "monthly_energy", "Monthly AC energy in Year 1", "kWh", "", "Post-process", "sim_type=1", "LENGTH=12", "" }, // Annual Outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net electrical energy generated with availability derate", "kWe-hr", "", "Post-process", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_NUMBER, "annual_gross_energy", "Annual Gross Electrical Energy Production w/ avail derate", "kWe-hr", "", "Post-process", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, - //{ SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumptoin w/ avail derate", "kWe-hr", "", "Post-process", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net electrical energy w/ avail. derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, + //{ SSC_OUTPUT, SSC_NUMBER, "annual_gross_energy", "Annual Gross Electrical Energy Production w/ avail derate", "kWhe", "", "Post-process", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWht", "", "Post-process", "sim_type=1", "", "" }, + //{ SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumptoin w/ avail derate", "kWhe", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_total_water_use", "Total annual water usage", "m^3", "", "Post-process", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_field_freeze_protection", "Annual thermal power for field freeze protection", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_tes_freeze_protection", "Annual thermal power for TES freeze protection", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_field_freeze_protection", "Annual thermal power for field freeze protection", "kWht", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_tes_freeze_protection", "Annual thermal power for TES freeze protection", "kWht", "", "Post-process", "sim_type=1", "", "" }, // Newly added { SSC_OUTPUT, SSC_ARRAY, "n_op_modes", "Operating modes in reporting timestep", "", "", "solver", "sim_type=1", "", "" }, @@ -594,8 +594,8 @@ static var_info _cm_vtab_fresnel_physical[] = { { SSC_OUTPUT, SSC_ARRAY, "P_fixed", "Parasitic power fixed load", "MWe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "P_plant_balance_tot", "Parasitic power generation-dependent load", "MWe", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "P_out_net", "System net electric power generated", "MWe", "", "system", "*", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electric power generated with availability derate", "kWe", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "P_out_net", "System net electrical power", "MWe", "", "system", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electrical power w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_W_cycle_gross", "Electrical source - Power cycle gross output", "kWhe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "conversion_factor", "Gross to Net Conversion Factor", "%", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "system", "sim_type=1", "", "" }, @@ -1881,8 +1881,8 @@ class cm_fresnel_physical : public compute_module // Annual outputs accumulate_annual_for_year("gen", "annual_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); - accumulate_annual_for_year("P_cycle", "annual_W_cycle_gross", 1000.0 * sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[kWe-hr] - //accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step/3600.0, steps_per_hour); //[kWe-hr] + accumulate_annual_for_year("P_cycle", "annual_W_cycle_gross", 1000.0 * sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[kWhe] + //accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step/3600.0, steps_per_hour); //[kWhe] accumulate_annual_for_year("disp_objective", "disp_objective_ann", 1000.0 * sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); accumulate_annual_for_year("disp_solve_iter", "disp_iter_ann", 1000.0 * sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); accumulate_annual_for_year("disp_presolve_nconstr", "disp_presolve_nconstr_ann", sim_setup.m_report_step / 3600.0 / as_double("disp_frequency"), steps_per_hour, 1, n_steps_fixed / steps_per_hour); @@ -1892,10 +1892,10 @@ class cm_fresnel_physical : public compute_module accumulate_annual_for_year("q_ch_tes", "annual_q_ch_tes", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); - ssc_number_t annual_field_fp = accumulate_annual_for_year("q_dot_freeze_prot", "annual_field_freeze_protection", sim_setup.m_report_step / 3600.0 * 1.E3, steps_per_hour); //[kWt-hr] - ssc_number_t annual_tes_fp = accumulate_annual_for_year("q_tes_heater", "annual_tes_freeze_protection", sim_setup.m_report_step / 3600.0 * 1.E3, steps_per_hour); //[kWt-hr] + ssc_number_t annual_field_fp = accumulate_annual_for_year("q_dot_freeze_prot", "annual_field_freeze_protection", sim_setup.m_report_step / 3600.0 * 1.E3, steps_per_hour); //[kWht] + ssc_number_t annual_tes_fp = accumulate_annual_for_year("q_tes_heater", "annual_tes_freeze_protection", sim_setup.m_report_step / 3600.0 * 1.E3, steps_per_hour); //[kWht] - ssc_number_t annual_thermal_consumption = annual_field_fp + annual_tes_fp; //[kWt-hr] + ssc_number_t annual_thermal_consumption = annual_field_fp + annual_tes_fp; //[kWht] assign("annual_thermal_consumption", annual_thermal_consumption); // Reporting dispatch solution counts diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index c7914f518..8eee6609e 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -562,9 +562,9 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "operating_modes_a", "First 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "operating_modes_b", "Next 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "operating_modes_c", "Final 3 operating modes tried", "", "", "solver", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "System net thermal power generated with availability derate", "kWt", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electric power generated with availability derate", "kWe", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "System net thermal power generated with availability derate in MMBtu/hr","MMBtu/hr", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "System net thermal power w/ avail. derate", "kWt", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electrical power w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "System net thermal power w/ avail. derate", "MMBtu/hr", "", "system", "sim_type=1", "", "" }, // Monthly Outputs @@ -573,8 +573,8 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { // Annual Outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net thermal energy generated with availability derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual net thermal energy generated with availability derate in MMBtu","MMBtu", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net thermal energy w/ avail. derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual net thermal energy w/ avail. derate", "MMBtu", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWht", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption w/ avail derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_total_water_use", "Total Annual Water Usage", "m^3", "", "Post-process", "sim_type=1", "", "" }, diff --git a/ssc/cmod_linear_fresnel_dsg_iph.cpp b/ssc/cmod_linear_fresnel_dsg_iph.cpp index 6d16c2f91..22fadc941 100644 --- a/ssc/cmod_linear_fresnel_dsg_iph.cpp +++ b/ssc/cmod_linear_fresnel_dsg_iph.cpp @@ -182,10 +182,10 @@ static var_info _cm_vtab_linear_fresnel_dsg_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "W_dot_heat_sink_pump", "Heat sink pumping power", "MWe", "", "Heat_Sink", "*", "", "" }, // SYSTEM - { SSC_OUTPUT, SSC_ARRAY, "W_dot_parasitic_tot", "System total electrical parasitic", "MWe", "", "Controller", "*", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "System net thermal power generated with availability derate","kWt", "","system", "*", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electric power generated with availability derate","kWe", "","system", "*", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "System net thermal power generated with availability derate in MMBtu/hr","MMBtu/hr","","system", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "W_dot_parasitic_tot", "System total electrical parasitic", "MWe", "", "Controller", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "System net thermal power w/ avail. derate", "kWt", "", "system", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electrical power w/ avail. derate", "kWe", "", "system", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "System net thermal power w/ avail. derate", "MMBtu/hr", "", "system", "*", "", "" }, // Controller @@ -194,8 +194,8 @@ static var_info _cm_vtab_linear_fresnel_dsg_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "op_mode_3", "3rd op. mode, if applicable", "", "", "Controller", "*", "", "" }, // Annual Outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net thermal energy generated with availability derate", "kWht", "", "Post-process", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual net thermal energy generated with availability derate in MMBtu","MMBtu","","Post - process", "", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net thermal energy w/ avail. derate", "kWht", "", "Post-process", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual net thermal energy w/ avail. derate", "MMBtu", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_field_energy", "Annual Gross Thermal Energy Production w/ avail derate", "kWht", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWht", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumptoin w/ avail derate", "kWhe", "", "Post-process", "*", "", "" }, diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index c287bd8bf..992361564 100644 --- a/ssc/cmod_mspt_iph.cpp +++ b/ssc/cmod_mspt_iph.cpp @@ -600,15 +600,15 @@ static var_info _cm_vtab_mspt_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "operating_modes_b", "Next 3 operating modes tried", "", "", "", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_ARRAY, "operating_modes_c", "Final 3 operating modes tried", "", "", "", "sim_type=1", "", ""}, -{ SSC_OUTPUT, SSC_ARRAY, "gen_heat", "System net thermal power generated with availability derate", "kWt", "", "", "sim_type=1", "", ""}, -{ SSC_OUTPUT, SSC_ARRAY, "gen", "System net electric power generated with availability derate", "kWe", "", "system", "sim_type=1", "", "" }, -{ SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "System net thermal power generated with availability derate in MMBtu/hr", "MMBtu/hr", "", "system", "sim_type=1", "", "" }, +{ SSC_OUTPUT, SSC_ARRAY, "gen_heat", "System net thermal power w/ avail. derate", "kWt", "", "", "sim_type=1", "", ""}, +{ SSC_OUTPUT, SSC_ARRAY, "gen", "System net electrical power w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, +{ SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "System net thermal power w/ avail. derate", "MMBtu/hr", "", "system", "sim_type=1", "", "" }, // Annual single-value outputs -{ SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net thermal energy generated with availability derate", "kWht", "", "Post-process", "sim_type=1", "", ""}, +{ SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net thermal energy w/ avail. derate", "kWht", "", "Post-process", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_htf", "Annual receiver power delivered to HTF", "MWht", "", "Tower and Receiver", "sim_type=1", "", ""}, -{ SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual net thermal energy generated with availability derate in MMBtu", "MMBtu", "", "Post-process", "sim_type=1", "", ""}, +{ SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual net thermal energy w/ avail. derate", "MMBtu", "", "Post-process", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption w/ avail derate", "kWhe", "", "Post-process", "sim_type=1", "", ""}, diff --git a/ssc/cmod_tcsmolten_salt.cpp b/ssc/cmod_tcsmolten_salt.cpp index e510bba77..9a877606a 100644 --- a/ssc/cmod_tcsmolten_salt.cpp +++ b/ssc/cmod_tcsmolten_salt.cpp @@ -105,7 +105,7 @@ static var_info _cm_vtab_tcsmolten_salt[] = { { SSC_INPUT, SSC_NUMBER, "land_min", "Land min boundary", "-ORm", "", "Heliostat Field", "?=0.75", "", ""}, { SSC_INPUT, SSC_MATRIX, "land_bound_table", "Land boundary table", "m", "", "Heliostat Field", "?", "", "SIMULATION_PARAMETER"}, { SSC_INPUT, SSC_ARRAY, "land_bound_list", "Land boundary table listing", "", "", "Heliostat Field", "?", "", "SIMULATION_PARAMETER"}, - { SSC_INPUT, SSC_NUMBER, "p_start", "Heliostat startup energy", "kWe-hr", "", "Heliostat Field", "*", "", ""}, + { SSC_INPUT, SSC_NUMBER, "p_start", "Heliostat startup energy", "kWhe", "", "Heliostat Field", "*", "", ""}, { SSC_INPUT, SSC_NUMBER, "p_track", "Heliostat tracking energy", "kWe", "", "Heliostat Field", "*", "", ""}, { SSC_INPUT, SSC_NUMBER, "hel_stow_deploy", "Stow/deploy elevation angle", "deg", "", "Heliostat Field", "*", "", ""}, { SSC_INPUT, SSC_NUMBER, "v_wind_max", "Heliostat max wind velocity", "m/s", "", "Heliostat Field", "*", "", ""}, @@ -342,7 +342,7 @@ static var_info _cm_vtab_tcsmolten_salt[] = { { SSC_INPUT, SSC_NUMBER, "disp_pen_ramping", "Dispatch cycle production change penalty", "$/MWe-change", "", "System Control", "", "", ""}, { SSC_INPUT, SSC_NUMBER, "disp_inventory_incentive", "Dispatch storage terminal inventory incentive multiplier", "", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER"}, { SSC_INPUT, SSC_NUMBER, "q_rec_standby", "Receiver standby energy consumption", "kWt", "", "System Control", "?=9e99", "", "SIMULATION_PARAMETER"}, - { SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWe-hr", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER"}, + { SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWhe", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER"}, // Pricing schedules and multipliers // Ideally this would work with sim_type = 2, but UI inputs availability depends on financial mode @@ -363,7 +363,7 @@ static var_info _cm_vtab_tcsmolten_salt[] = { { SSC_INPUT, SSC_NUMBER, "is_field_tracking_init", "Is heliostat field tracking? (1 = true)", "-", "", "System Control", "", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "rec_op_mode_initial", "Initial receiver operating mode 0: off, 1: startup, 2: on", "-", "", "System Control", "", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "rec_startup_time_remain_init", "Initial receiver startup time remaining", "hr", "", "System Control", "", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "rec_startup_energy_remain_init", "Initial receiver startup energy remaining", "W-hr", "", "System Control", "", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "rec_startup_energy_remain_init", "Initial receiver startup energy remaining", "Wh", "", "System Control", "", "", "SIMULATION_PARAMETER" }, // Power cycle { SSC_INPUT, SSC_NUMBER, "pc_op_mode_initial", "Initial cycle operation mode 0:startup, 1:on, 2:standby, 3:off, 4:startup_controlled", "-", "", "System Control", "", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "pc_startup_time_remain_init", "Initial cycle startup time remaining", "hr", "", "System Control", "", "", "SIMULATION_PARAMETER" }, @@ -495,7 +495,7 @@ static var_info _cm_vtab_tcsmolten_salt[] = { // Heater { SSC_OUTPUT, SSC_NUMBER, "q_dot_heater_des", "Heater design thermal power", "MWt", "", "Heater", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "W_dot_heater_des", "Heater electricity consumption at design", "MWe", "", "Heater", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "E_heater_su_des", "Heater startup energy", "MWt-hr", "", "Heater", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "E_heater_su_des", "Heater startup energy", "MWht", "", "Heater", "*", "", "" }, // Power Cycle { SSC_OUTPUT, SSC_NUMBER, "m_dot_htf_cycle_des", "PC HTF mass flow rate at design", "kg/s", "", "Power Cycle", "*", "", "" }, @@ -521,7 +521,7 @@ static var_info _cm_vtab_tcsmolten_salt[] = { { SSC_OUTPUT, SSC_NUMBER, "m_dot_water_ND_des_calc", "UDPC calculated water use at design", "", "", "UDPC Design Calc", "*", "", "" }, // TES - { SSC_OUTPUT, SSC_NUMBER, "Q_tes_des", "TES design capacity", "MWt-hr", "", "TES Design Calc", "*", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "Q_tes_des", "TES design capacity", "MWht", "", "TES Design Calc", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "V_tes_htf_avail_des", "TES volume of HTF available for heat transfer", "m3", "", "TES Design Calc", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "V_tes_htf_total_des", "TES total HTF volume", "m3", "", "TES Design Calc", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "d_tank_tes", "TES tank diameter", "m", "", "TES Design Calc", "*", "", "" }, @@ -686,7 +686,7 @@ static var_info _cm_vtab_tcsmolten_salt[] = { { SSC_OUTPUT, SSC_ARRAY, "P_rec_heattrace", "Receiver heat trace parasitic load", "MWe", "", "System", "sim_type=1&is_rec_model_trans=1", "", ""}, // System outputs - { SSC_OUTPUT, SSC_ARRAY, "P_out_net", "System net electric power generated", "MWe", "", "", "sim_type=1", "", ""}, + { SSC_OUTPUT, SSC_ARRAY, "P_out_net", "System net electrical power", "MWe", "", "", "sim_type=1", "", ""}, // Controller outputs { SSC_OUTPUT, SSC_ARRAY, "tou_value", "CSP operating time-of-use value", "", "", "", "sim_type=1", "", ""}, @@ -707,7 +707,7 @@ static var_info _cm_vtab_tcsmolten_salt[] = { { SSC_OUTPUT, SSC_ARRAY, "disp_obj_relax", "Dispatch objective function - relaxed max", "", "", "", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_ARRAY, "disp_qsf_expected", "Dispatch expected solar field available energy", "MWt", "", "", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_ARRAY, "disp_qsfprod_expected", "Dispatch expected solar field generation", "MWt", "", "", "sim_type=1", "", ""}, - { SSC_OUTPUT, SSC_ARRAY, "disp_qsfsu_expected", "Dispatch expected solar field startup energy", "MWt", "", "", "sim_type=1", "", ""}, + { SSC_OUTPUT, SSC_ARRAY, "disp_qsfsu_expected", "Dispatch expected solar field startup energy", "MWt", "", "", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_ARRAY, "disp_tes_expected", "Dispatch expected TES charge level", "MWht", "", "", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_ARRAY, "disp_pceff_expected", "Dispatch expected power cycle efficiency adj.", "", "", "", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_ARRAY, "disp_thermeff_expected", "Dispatch expected SF thermal efficiency adj.", "", "", "", "sim_type=1", "", ""}, @@ -740,21 +740,21 @@ static var_info _cm_vtab_tcsmolten_salt[] = { { SSC_OUTPUT, SSC_ARRAY, "operating_modes_b", "Next 3 operating modes tried", "", "", "", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_ARRAY, "operating_modes_c", "Final 3 operating modes tried", "", "", "", "sim_type=1", "", ""}, - { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electric power generated with availability derate", "kWe", "", "", "sim_type=1", "", ""}, + { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electrical power w/ avail. derate", "kWe", "", "", "sim_type=1", "", ""}, // Annual single-value outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net electrical energy generated with availability derate", "kWhe", "", "", "sim_type=1", "", ""}, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net electrical energy w/ avail. derate", "kWhe", "", "", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_NUMBER, "annual_W_cycle_gross", "Electrical source - power cycle gross output", "kWhe", "", "", "sim_type=1", "", ""}, - { SSC_OUTPUT, SSC_NUMBER, "annual_W_cooling_tower", "Total of condenser operation parasitic", "kWhe", "", "PC", "sim_type=1", "", ""}, - { SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_htf", "Annual receiver power delivered to HTF", "MWt-hr", "", "Tower and Receiver", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_inc", "Annual receiver incident thermal power after reflective and defocus losses", "MWt-hr", "", "Tower and Receiver", "sim_type=1", "", ""}, - { SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_loss", "Annual receiver convective and radiative losses", "MWt-hr", "", "Tower and Receiver", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_q_piping_loss", "Annual tower piping losses", "MWt-hr", "", "Tower and Receiver", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_startup", "Annual receiver startup energy", "MWt-hr", "", "Tower and Receiver", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_E_tower_pump", "Annual tower pumping power", "MWe-hr", "", "Tower and Receiver", "sim_type=1", "", ""}, + { SSC_OUTPUT, SSC_NUMBER, "annual_W_cooling_tower", "Total of condenser operation parasitic", "kWhe", "", "PC", "sim_type=1", "", ""}, + { SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_htf", "Annual receiver power delivered to HTF", "MWht", "", "Tower and Receiver", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_inc", "Annual receiver incident thermal power after reflective and defocus losses", "MWht", "", "Tower and Receiver", "sim_type=1", "", ""}, + { SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_loss", "Annual receiver convective and radiative losses", "MWht", "", "Tower and Receiver", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_q_piping_loss", "Annual tower piping losses", "MWht", "", "Tower and Receiver", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_q_rec_startup", "Annual receiver startup energy", "MWht", "", "Tower and Receiver", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_E_tower_pump", "Annual tower pumping power", "MWhe", "", "Tower and Receiver", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_NUMBER, "annual_eta_rec_th", "Annual receiver thermal efficiency ignoring rec reflective loss", "", "", "Tower and Receiver", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_NUMBER, "annual_eta_rec_th_incl_refl", "Annual receiver thermal efficiency including reflective loss", "", "", "Tower and Receiver", "sim_type=1", "", ""}, - { SSC_OUTPUT, SSC_NUMBER, "annual_q_defocus_est", "Annual defocus loss estimate", "MWt-hr", "", "Tower and Receiver", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_q_defocus_est", "Annual defocus loss estimate", "MWht", "", "Tower and Receiver", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "conversion_factor", "Gross to net conversion factor", "%", "", "", "sim_type=1", "", ""}, { SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "", "sim_type=1", "", ""}, @@ -784,7 +784,7 @@ static var_info _cm_vtab_tcsmolten_salt[] = { { SSC_OUTPUT, SSC_ARRAY, "is_field_tracking_final", "Final heliostat field operation is tracking? (1 = true)", "-", "", "System Control", "", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "rec_op_mode_final", "Final receiver operating mode 0: off, 1: startup, 2: on", "-", "", "System Control", "", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "rec_startup_time_remain_final", "Final receiver startup time remaining", "hr", "", "System Control", "", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "rec_startup_energy_remain_final", "Final receiver startup energy remaining", "W-hr", "", "System Control", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "rec_startup_energy_remain_final", "Final receiver startup energy remaining", "Wh", "", "System Control", "", "", "" }, // Power cycle { SSC_OUTPUT, SSC_ARRAY, "pc_op_mode_final", "Final cycle operation mode 0:startup, 1:on, 2:standby, 3:off, 4:startup_controlled", "-", "", "System Control", "", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "pc_startup_time_remain_final", "Final cycle startup time remaining", "hr", "", "System Control", "", "", "" }, @@ -919,7 +919,7 @@ class cm_tcsmolten_salt : public compute_module // System Design Calcs double q_dot_pc_des = W_dot_cycle_des / eta_cycle; //[MWt] - double Q_tes = q_dot_pc_des * tshours; //[MWt-hr] + double Q_tes = q_dot_pc_des * tshours; //[MWht] double solar_mult = as_number("solarm"); //[-] double q_dot_rec_des = q_dot_pc_des * solar_mult; //[MWt] @@ -1839,7 +1839,7 @@ class cm_tcsmolten_salt : public compute_module // so it can use the active receiver area C_pt_sf_perf_interp heliostatfield(A_rec); - heliostatfield.ms_params.m_p_start = as_double("p_start"); //[kWe-hr] Heliostat startup energy + heliostatfield.ms_params.m_p_start = as_double("p_start"); //[kWhe] Heliostat startup energy heliostatfield.ms_params.m_p_track = as_double("p_track"); //[kWe] Heliostat tracking power heliostatfield.ms_params.m_hel_stow_deploy = as_double("hel_stow_deploy"); // N/A heliostatfield.ms_params.m_v_wind_max = as_double("v_wind_max"); // N/A @@ -2479,23 +2479,23 @@ class cm_tcsmolten_salt : public compute_module // Heater assign("q_dot_heater_des", q_dot_heater_des); //[MWt] double W_dot_heater_des_calc = 0.0; //[MWe] - double E_heater_su_des = 0.0; //[MWt-hr] + double E_heater_su_des = 0.0; //[MWht] if (is_parallel_heater) { p_electric_resistance->get_design_parameters(E_heater_su_des, W_dot_heater_des_calc); } assign("W_dot_heater_des", (ssc_number_t)W_dot_heater_des_calc); //[MWe] - assign("E_heater_su_des", (ssc_number_t)E_heater_su_des); //[MWt-hr] + assign("E_heater_su_des", (ssc_number_t)E_heater_su_des); //[MWht] // ************************* // Thermal Energy Storage double V_tes_htf_avail_calc /*m3*/, V_tes_htf_total_calc /*m3*/, h_tank_calc /*m*/, d_tank_calc /*m*/, q_dot_loss_tes_des_calc /*MWt*/, dens_store_htf_at_T_ave_calc /*kg/m3*/, - Q_tes_des_calc /*MWt-hr*/; + Q_tes_des_calc /*MWht*/; storage.get_design_parameters(V_tes_htf_avail_calc, V_tes_htf_total_calc, h_tank_calc, d_tank_calc, q_dot_loss_tes_des_calc, dens_store_htf_at_T_ave_calc, Q_tes_des_calc); - assign("Q_tes_des", Q_tes_des_calc); //[MWt-hr] + assign("Q_tes_des", Q_tes_des_calc); //[MWht] assign("V_tes_htf_avail_des", V_tes_htf_avail_calc); //[m3] assign("V_tes_htf_total_des", V_tes_htf_total_calc); //[m3] assign("d_tank_tes", d_tank_calc); //[m] @@ -2978,11 +2978,11 @@ class cm_tcsmolten_salt : public compute_module accumulate_annual_for_year("gen", "annual_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed/steps_per_hour); accumulate_annual_for_year("gensales_after_avail", "annual_sales_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); - accumulate_annual_for_year("P_cycle", "annual_W_cycle_gross", 1000.0*sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed/steps_per_hour); //[kWe-hr] - accumulate_annual_for_year("P_cooling_tower_tot", "annual_W_cooling_tower", 1000.0*sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[kWe-hr] + accumulate_annual_for_year("P_cycle", "annual_W_cycle_gross", 1000.0*sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed/steps_per_hour); //[kWhe] + accumulate_annual_for_year("P_cooling_tower_tot", "annual_W_cooling_tower", 1000.0*sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[kWhe] - accumulate_annual_for_year("Q_thermal", "annual_q_rec_htf", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[MWt-hr] - accumulate_annual_for_year("q_dot_rec_inc", "annual_q_rec_inc", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[MWt-hr] + accumulate_annual_for_year("Q_thermal", "annual_q_rec_htf", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[MWht] + accumulate_annual_for_year("q_dot_rec_inc", "annual_q_rec_inc", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[MWht] accumulate_annual_for_year("q_thermal_loss", "annual_q_rec_loss", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); accumulate_annual_for_year("q_piping_losses", "annual_q_piping_loss", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); accumulate_annual_for_year("q_startup", "annual_q_rec_startup", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); @@ -3002,8 +3002,8 @@ class cm_tcsmolten_salt : public compute_module i_defocus = min(1.0, max(0.0, p_defocus[i])); q_defocus_sum += p_q_rec_in[i]*(1.0 - i_defocus); //[MWt] } - q_defocus_sum *= sim_setup.m_report_step/3600.0; //[MWt-hr] - assign("annual_q_defocus_est", q_defocus_sum); //[MWt-hr] + q_defocus_sum *= sim_setup.m_report_step/3600.0; //[MWht] + assign("annual_q_defocus_est", q_defocus_sum); //[MWht] accumulate_annual_for_year("disp_objective", "disp_objective_ann", sim_setup.m_report_step / 3600.0 / as_double("disp_frequency"), steps_per_hour, 1, n_steps_fixed/steps_per_hour); accumulate_annual_for_year("disp_solve_iter", "disp_iter_ann", sim_setup.m_report_step / 3600.0 / as_double("disp_frequency"), steps_per_hour, 1, n_steps_fixed/steps_per_hour); @@ -3033,9 +3033,9 @@ class cm_tcsmolten_salt : public compute_module double V_water_mirrors = as_double("water_usage_per_wash") / 1000.0*A_sf*as_double("washing_frequency"); assign("annual_total_water_use", (ssc_number_t)(V_water_cycle + V_water_mirrors)); - ssc_number_t ae = as_number("annual_energy"); //[kWe-hr] - ssc_number_t pg = as_number("annual_W_cycle_gross"); //[kWe-hr] - ssc_number_t annual_sales_energy = as_number("annual_sales_energy"); //[kWe-hr] + ssc_number_t ae = as_number("annual_energy"); //[kWhe] + ssc_number_t pg = as_number("annual_W_cycle_gross"); //[kWhe] + ssc_number_t annual_sales_energy = as_number("annual_sales_energy"); //[kWhe] ssc_number_t convfactor = (pg != 0) ? 100 * ae / pg : (ssc_number_t)0.0; assign("conversion_factor", convfactor); @@ -3106,10 +3106,10 @@ class cm_tcsmolten_salt : public compute_module double total_energy_in_sub_period = 0.0; for (size_t i = 0; i < n_ppa_steps; i++) { size_t j = ppa_pairs[i].first; - total_energy_in_sub_period += p_gen[j] * sim_setup.m_report_step / 3600.0; //[kWe-hr] + total_energy_in_sub_period += p_gen[j] * sim_setup.m_report_step / 3600.0; //[kWhe] } - double total_energy_nameplate = nameplate * n_ppa_steps * sim_setup.m_report_step / 3600.0; //[kWe-hr] + double total_energy_nameplate = nameplate * n_ppa_steps * sim_setup.m_report_step / 3600.0; //[kWhe] double cap_fac_highest_1000_ppas = 0.0; if (nameplate > 0.0) { @@ -3123,10 +3123,10 @@ class cm_tcsmolten_salt : public compute_module total_energy_in_sub_period = 0.0; for (size_t i = 0; i < n_ppa_steps; i++) { size_t j = ppa_pairs[i].first; - total_energy_in_sub_period += p_gen[j] * sim_setup.m_report_step / 3600.0; //[kWe-hr] + total_energy_in_sub_period += p_gen[j] * sim_setup.m_report_step / 3600.0; //[kWhe] } - total_energy_nameplate = nameplate * n_ppa_steps * sim_setup.m_report_step / 3600.0; //[kWe-hr] + total_energy_nameplate = nameplate * n_ppa_steps * sim_setup.m_report_step / 3600.0; //[kWhe] double cap_fac_highest_2000_ppas = 0.0; if (nameplate > 0.0) { @@ -3154,10 +3154,10 @@ class cm_tcsmolten_salt : public compute_module double total_energy_in_sub_period_tdry = 0.0; for (size_t i = 0; i < n_tdry_steps; i++) { size_t j = tdry_pairs[i].first; - total_energy_in_sub_period_tdry += p_gen[j] * sim_setup.m_report_step / 3600.0; //[kWe-hr] + total_energy_in_sub_period_tdry += p_gen[j] * sim_setup.m_report_step / 3600.0; //[kWhe] } - double total_energy_nameplate_tdry = nameplate * n_tdry_steps * sim_setup.m_report_step / 3600.0; //[kWe-hr] + double total_energy_nameplate_tdry = nameplate * n_tdry_steps * sim_setup.m_report_step / 3600.0; //[kWhe] double cap_fac_warmest_100_tdrys = 0.0; if (nameplate > 0.0) { diff --git a/ssc/cmod_trough_physical.cpp b/ssc/cmod_trough_physical.cpp index afcf126b4..408036d75 100644 --- a/ssc/cmod_trough_physical.cpp +++ b/ssc/cmod_trough_physical.cpp @@ -168,7 +168,7 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_INPUT, SSC_MATRIX, "Design_loss", "Receiver heat loss at design", "W/m", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "rec_su_delay", "Fixed startup delay time for the receiver", "hr", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "rec_qf_delay", "Energy-based receiver startup delay (fraction of rated thermal power)", "-", "", "solar_field", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "p_start", "Collector startup energy, per SCA", "kWe-hr", "", "solar_field", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "p_start", "Collector startup energy, per SCA", "kWhe", "", "solar_field", "*", "", "" }, // Power Cycle //{ SSC_INPUT, SSC_NUMBER, "q_pb_design", "Design heat input to power block", "MWt", "", "controller", "*", "", "" }, @@ -294,7 +294,7 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_INPUT, SSC_NUMBER, "disp_pen_ramping", "Dispatch cycle production change penalty", "$/MWe-change", "", "tou", "is_dispatch=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "disp_inventory_incentive", "Dispatch storage terminal inventory incentive multiplier", "", "", "System Control", "?=0.0", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_NUMBER, "q_rec_standby", "Receiver standby energy consumption", "kWt", "", "tou", "?=9e99", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWe-hr", "", "tou", "?=0.0", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWhe", "", "tou", "?=0.0", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_ARRAY, "f_turb_tou_periods", "Dispatch logic for turbine load fraction", "-", "", "tou", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "csp_financial_model", "", "1-8", "", "Financial Model", "?=1", "INTEGER,MIN=0", "" }, @@ -712,14 +712,14 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_OUTPUT, SSC_ARRAY, "q_balance", "Relative energy balance error", "", "", "solver", "sim_type=1", "", "" }, // Monthly Outputs - { SSC_OUTPUT, SSC_ARRAY, "monthly_energy", "Monthly AC energy in Year 1", "kWh", "", "Post-process", "sim_type=1", "LENGTH=12", "" }, + { SSC_OUTPUT, SSC_ARRAY, "monthly_energy", "Monthly AC energy in Year 1", "kWh", "", "Post-process", "sim_type=1", "LENGTH=12", "" }, // Annual Outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net electrical energy generated with availability derate", "kWe-hr", "", "Post-process", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net electrical energy w/ avail. derate", "kWhe", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWht", "", "Post-process", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_total_water_use", "Total Annual Water Usage", "m^3", "", "Post-process", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_field_freeze_protection", "Annual thermal power for field freeze protection", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_tes_freeze_protection", "Annual thermal power for TES freeze protection", "kWt-hr", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_field_freeze_protection", "Annual thermal power for field freeze protection", "kWht", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_tes_freeze_protection", "Annual thermal power for TES freeze protection", "kWht", "", "Post-process", "sim_type=1", "", "" }, // Newly added { SSC_OUTPUT, SSC_ARRAY, "n_op_modes", "Operating modes in reporting timestep", "", "", "solver", "sim_type=1", "", "" }, @@ -765,8 +765,8 @@ static var_info _cm_vtab_trough_physical[] = { { SSC_OUTPUT, SSC_ARRAY, "P_fixed", "Parasitic power fixed load", "MWe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "P_plant_balance_tot", "Parasitic power generation-dependent load", "MWe", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "P_out_net", "System net electric power generated", "MWe", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electric power generated with availability derate", "kWe", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "P_out_net", "System net electrical power", "MWe", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electrical power w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_W_cycle_gross", "Electrical source - Power cycle gross output", "kWhe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "conversion_factor", "Gross to Net Conversion Factor", "%", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "system", "sim_type=1", "", "" }, @@ -1198,7 +1198,7 @@ class cm_trough_physical : public compute_module c_trough.m_Design_loss = as_matrix("Design_loss"); //[-] Receiver heat loss at design c_trough.m_rec_su_delay = as_double("rec_su_delay"); //[hr] Fixed startup delay time for the receiver c_trough.m_rec_qf_delay = as_double("rec_qf_delay"); //[-] Energy-based receiver startup delay (fraction of rated thermal power) - c_trough.m_p_start = as_double("p_start"); //[kWe-hr] Collector startup energy, per SCA + c_trough.m_p_start = as_double("p_start"); //[kWhe] Collector startup energy, per SCA c_trough.m_calc_design_pipe_vals = as_boolean("calc_design_pipe_vals"); //[-] Should the HTF state be calculated at design conditions c_trough.m_L_rnr_pb = as_double("L_rnr_pb"); //[m] Length of hot or cold runner pipe around the power block c_trough.m_N_max_hdr_diams = as_double("N_max_hdr_diams"); //[-] Maximum number of allowed diameters in each of the hot and cold headers @@ -2647,8 +2647,8 @@ class cm_trough_physical : public compute_module // Annual outputs accumulate_annual_for_year("gen", "annual_energy", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); - accumulate_annual_for_year("P_cycle", "annual_W_cycle_gross", 1000.0*sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[kWe-hr] - //accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step/3600.0, steps_per_hour); //[kWe-hr] + accumulate_annual_for_year("P_cycle", "annual_W_cycle_gross", 1000.0*sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[kWhe] + //accumulate_annual_for_year("W_dot_par_tot_haf", "annual_electricity_consumption", sim_setup.m_report_step/3600.0, steps_per_hour); //[kWhe] accumulate_annual_for_year("disp_objective", "disp_objective_ann", 1000.0*sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); accumulate_annual_for_year("disp_solve_iter", "disp_iter_ann", 1000.0*sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); accumulate_annual_for_year("disp_presolve_nconstr", "disp_presolve_nconstr_ann", sim_setup.m_report_step / 3600.0 / as_double("disp_frequency"), steps_per_hour, 1, n_steps_fixed / steps_per_hour); @@ -2657,10 +2657,10 @@ class cm_trough_physical : public compute_module accumulate_annual_for_year("q_dc_tes", "annual_q_dc_tes", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); accumulate_annual_for_year("q_ch_tes", "annual_q_ch_tes", sim_setup.m_report_step / 3600.0, steps_per_hour, 1, n_steps_fixed / steps_per_hour); - ssc_number_t annual_field_fp = accumulate_annual_for_year("q_dot_freeze_prot", "annual_field_freeze_protection", sim_setup.m_report_step / 3600.0*1.E3, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[kWt-hr] - ssc_number_t annual_tes_fp = accumulate_annual_for_year("q_tes_heater", "annual_tes_freeze_protection", sim_setup.m_report_step / 3600.0*1.E3, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[kWt-hr] + ssc_number_t annual_field_fp = accumulate_annual_for_year("q_dot_freeze_prot", "annual_field_freeze_protection", sim_setup.m_report_step / 3600.0*1.E3, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[kWht] + ssc_number_t annual_tes_fp = accumulate_annual_for_year("q_tes_heater", "annual_tes_freeze_protection", sim_setup.m_report_step / 3600.0*1.E3, steps_per_hour, 1, n_steps_fixed / steps_per_hour); //[kWht] - ssc_number_t annual_thermal_consumption = annual_field_fp + annual_tes_fp; //[kWt-hr] + ssc_number_t annual_thermal_consumption = annual_field_fp + annual_tes_fp; //[kWht] assign("annual_thermal_consumption", annual_thermal_consumption); // Reporting dispatch solution counts diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index 75d189e18..c1e1fcac7 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -652,8 +652,8 @@ static var_info _cm_vtab_trough_physical_iph[] = { // Annual Outputs - { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net thermal energy generated with availability derate", "kWht", "", "Post-process", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual net thermal energy generated with availability derate in MMBtu", "MMBtu", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual net thermal energy w/ avail. derate", "kWht", "", "Post-process", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_energy_heat_btu", "Annual net thermal energy w/ avail. derate", "MMBtu", "", "Post-process", "sim_type=1", "", "" }, //{ SSC_OUTPUT, SSC_NUMBER, "annual_gross_energy", "Annual Gross Electrical Energy Production w/ avail derate", "kWhe", "", "Post-process", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_thermal_consumption", "Annual thermal freeze protection required", "kWht", "", "Post-process", "sim_type=1", "", "" }, @@ -716,9 +716,9 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_OUTPUT, SSC_ARRAY, "P_fixed", "Parasitic power fixed load", "MWe", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "P_plant_balance_tot", "Parasitic power generation-dependent load", "MWe", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "System net thermal power generated with availability derate", "kWt", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electric power generated with availability derate", "kWe", "", "system", "sim_type=1", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "System net thermal power generated with availability derate in MMBtu/hr", "MMBtu/hr", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat", "System net thermal power w/ avail. derate", "kWt", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen", "System net electrical power w/ avail. derate", "kWe", "", "system", "sim_type=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen_heat_btu", "System net thermal power w/ avail. derate", "MMBtu/hr", "", "system", "sim_type=1", "", "" }, //{ SSC_OUTPUT, SSC_NUMBER, "conversion_factor", "Gross to Net Conversion Factor", "%", "", "system", "sim_type=1", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "system", "sim_type=1", "", "" }, From 1a06abaee807e30dd8510b004852bee9c5c52b1a Mon Sep 17 00:00:00 2001 From: Steven Janzou Date: Thu, 14 Nov 2024 03:34:10 -0700 Subject: [PATCH 76/85] 2024.11.14.ssc.295 IPH beta expires 1/31/2025 --- ssc/sscapi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssc/sscapi.cpp b/ssc/sscapi.cpp index 41adeded5..12386e71d 100644 --- a/ssc/sscapi.cpp +++ b/ssc/sscapi.cpp @@ -51,7 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SSCEXPORT int ssc_version() { - return 292; + return 295; } SSCEXPORT const char *ssc_build_info() From de250339c06e47d2b2357f4ba72fbe2a5937daa3 Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:05:13 -0700 Subject: [PATCH 77/85] Define IPH monthly load output. Remove unused variables and function. --- ssc/cmod_thermalrate_iph.cpp | 118 ++++++++--------------------------- 1 file changed, 26 insertions(+), 92 deletions(-) diff --git a/ssc/cmod_thermalrate_iph.cpp b/ssc/cmod_thermalrate_iph.cpp index 7f2308394..fb61f479a 100644 --- a/ssc/cmod_thermalrate_iph.cpp +++ b/ssc/cmod_thermalrate_iph.cpp @@ -81,6 +81,7 @@ static var_info vtab_thermal_rate_iph[] = { { SSC_OUTPUT, SSC_NUMBER, "thermal_cost_with_system_year1", "Thermal cost with sytem (year 1)", "$", "", "", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "thermal_cost_without_system_year1", "Thermal cost without system (year 1)", "$", "", "", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "year1_monthly_load_heat", "Thermal load", "kWht/mo", "", "Monthly", "*", "LENGTH=12", "" }, @@ -238,8 +239,6 @@ class cm_thermalrate_iph : public compute_module bload = true; pload = as_array("thermal_load_heat_btu", &nrec_load);//[MMBtu/hr] - - step_per_hour_load = nrec_load / 8760; if (step_per_hour_load < 1 || step_per_hour_load > 60 || step_per_hour_load * 8760 != nrec_load) throw exec_error("thermalrate_iph", util::format("invalid number of load records (%d): must be an integer multiple of 8760", (int)nrec_load)); @@ -396,17 +395,15 @@ class cm_thermalrate_iph : public compute_module assign("thermal_load_year1", year1_thermal_load* ts_hour_gen); - /* allocate intermediate data arrays */ std::vector revenue_w_sys(m_num_rec_yearly), revenue_wo_sys(m_num_rec_yearly), payment(m_num_rec_yearly), income(m_num_rec_yearly), thermal_charge_w_sys(m_num_rec_yearly), thermal_charge_wo_sys(m_num_rec_yearly), load(m_num_rec_yearly), salespurchases(m_num_rec_yearly); - std::vector monthly_revenue_w_sys(12), monthly_revenue_wo_sys(12), - monthly_thermal_charges(12), - monthly_ec_rates(12), - monthly_salespurchases(12), - monthly_load(12), monthly_system_generation(12), monthly_bill(12), monthly_peak(12), monthly_test(12); + std::vector + monthly_salespurchases(12), + monthly_load_heat_btu(12), + monthly_load_heat(12); /* allocate outputs */ ssc_number_t *annual_net_revenue = allocate("annual_thermal_value", nyears+1); @@ -417,48 +414,36 @@ class cm_thermalrate_iph : public compute_module ssc_number_t *annual_thermal_cost_w_sys = allocate("thermal_cost_with_system", nyears+1); ssc_number_t *annual_thermal_cost_wo_sys = allocate("thermal_cost_without_system", nyears+1); - - // matrices - //ssc_number_t *thermal_bill_w_sys_ym = allocate("thermal_bill_w_sys_ym", nyears + 1, 12); - //ssc_number_t *thermal_bill_wo_sys_ym = allocate("thermal_bill_wo_sys_ym", nyears + 1, 12); - - - // annual sums - //ssc_number_t *thermal_bill_w_sys = allocate("thermal_bill_w_sys", nyears + 1); - //ssc_number_t *utility_bill_wo_sys = allocate("thermal_bill_wo_sys", nyears + 1); - + size_t steps_per_hour = m_num_rec_yearly / 8760; + int c = 0; + for (int m = 0; m < 12; m++) + { + monthly_load_heat_btu[m] = 0; + monthly_load_heat[m] = 0; + for (int d = 0; d < (int)util::nday[m]; d++) + { + for (int h = 0; h < 24; h++) + { + for (int s = 0; s < (int)steps_per_hour && c < (int)m_num_rec_yearly; s++) + { + monthly_load_heat_btu[m] -= p_load[c]; //[MMBtu] + monthly_load_heat[m] -= p_load[c] * MMBTU_TO_KWh; //[kWht] + c++; + } + } + } + } + // Assign monthly load + assign("year1_monthly_load_heat", var_data(&monthly_load_heat[0], 12)); // lifetime hourly load ssc_number_t *lifetime_load = allocate("lifetime_thermal_load", nrec_gen); - idx = 0; for (i=0;i p_load[i]) ? ts_load : p_load[i]); - idx++; - } - lifetime_hourly_load[i*8760 + j] = e_load[i]; - // sign correction for utility rate calculations - e_load[i] = -e_load[i]; - p_load[i] = -p_load[i]; - } - */ - // apply load escalation appropriate for current year -// e_load_cy[j] = e_load[j] * load_scale[i]; -// p_load_cy[j] = p_load[j] * load_scale[i]; e_load_cy[j] = p_load[j] * load_scale[i] * ts_hour_gen; p_load_cy[j] = p_load[j] * load_scale[i]; @@ -466,10 +451,6 @@ class cm_thermalrate_iph : public compute_module // update e_sys per year if lifetime output if ((as_integer("system_use_lifetime_output") == 1) && ( idx < nrec_gen )) { -// e_sys[j] = p_sys[j] = 0.0; -// ts_power = (idx < nrec_gen) ? pgen[idx] : 0; -// e_sys[j] = ts_power * ts_hour_gen; -// p_sys[j] = ((ts_power > p_sys[j]) ? ts_power : p_sys[j]); e_sys_cy[j] = pgen[idx] * ts_hour_gen; p_sys_cy[j] = pgen[idx]; // until lifetime load fully implemented @@ -534,11 +515,7 @@ class cm_thermalrate_iph : public compute_module if (i == 0) { assign("year1_hourly_charge_with_system", var_data(&thermal_charge_w_sys[0], (int)m_num_rec_yearly)); - assign("thermal_revenue_with_system", var_data(&revenue_w_sys[0], (int)m_num_rec_yearly)); - assign("year1_monthly_load", var_data(&monthly_load[0], 12)); - assign("year1_monthly_system_generation", var_data(&monthly_system_generation[0], 12)); - assign("year1_monthly_thermal_bill_w_sys", var_data(&monthly_bill[0], 12)); // output and demand per Paul's email 9/10/10 // positive demand indicates system does not produce enough thermal to meet load @@ -598,49 +575,6 @@ class cm_thermalrate_iph : public compute_module assign("thermal_savings_year1", annual_thermal_cost_wo_sys[1] - annual_thermal_cost_w_sys[1]); } - void monthly_outputs(ssc_number_t *e_load, ssc_number_t *e_sys, ssc_number_t *e_grid, ssc_number_t *salespurchases, ssc_number_t monthly_load[12], ssc_number_t monthly_generation[12], ssc_number_t monthly_thermal_to_grid[12], ssc_number_t monthly_thermal_needed_from_grid[12], ssc_number_t monthly_salespurchases[12]) - { - // calculate the monthly net energy and monthly hours - int m,d,h,s; - ssc_number_t energy_use[12]; // 12 months - int c=0; - - size_t steps_per_hour = m_num_rec_yearly / 8760; - for (m=0;m<12;m++) - { - energy_use[m] = 0; - monthly_load[m] = 0; - monthly_generation[m] = 0; - monthly_thermal_to_grid[m] = 0; - monthly_salespurchases[m] = 0; - for (d=0;d<(int)util::nday[m];d++) - { - for(h=0;h<24;h++) - { - for (s = 0; s < (int)steps_per_hour && c < (int)m_num_rec_yearly; s++) - { - energy_use[m] += e_grid[c]; - monthly_load[m] -= e_load[c]; - monthly_generation[m] += e_sys[c]; // does not include first year sys_scale - monthly_thermal_to_grid[m] += e_grid[c]; - monthly_salespurchases[m] += salespurchases[c]; - c++; - } - } - } - } - // - - for (m=0;m<12;m++) - { - if (monthly_thermal_to_grid[m] > 0) - monthly_thermal_needed_from_grid[m] = monthly_thermal_to_grid[m]; - else - monthly_thermal_needed_from_grid[m]=0; - } - } - - void tr_calc_timestep(ssc_number_t *e_in, ssc_number_t *p_in, ssc_number_t *br_in, ssc_number_t *sr_in, ssc_number_t *revenue, ssc_number_t *payment, ssc_number_t *income, ssc_number_t *thermal_charge, From b58cf62da9d7c9ecbe1841e3fd04d5c98757565d Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:40:45 -0700 Subject: [PATCH 78/85] Add cash flow net heat output in MMBtu to IPH single owner and cashloan cmods. --- ssc/cmod_cashloan_heat.cpp | 9 ++++++++- ssc/cmod_singleowner_heat.cpp | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/ssc/cmod_cashloan_heat.cpp b/ssc/cmod_cashloan_heat.cpp index 30f613980..2414ee124 100644 --- a/ssc/cmod_cashloan_heat.cpp +++ b/ssc/cmod_cashloan_heat.cpp @@ -102,6 +102,7 @@ static var_info vtab_cashloan_heat[] = { { SSC_OUTPUT, SSC_ARRAY, "cf_energy_net", "Thermal energy", "kWht", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_net_heat_btu", "Thermal energy", "MMBtu", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales", "Electricity generation", "kWh", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_energy_purchases", "Electricity from grid to system", "kWh", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_energy_without_battery","Electricity generated without the battery or curtailment", "kWh", "", "Cash Flow Electricity", "", "LENGTH_EQUAL=cf_length", "" }, @@ -1563,7 +1564,13 @@ class cm_cashloan_heat : public compute_module save_cf(CF_itc_sta, nyears, "cf_itc_sta"); save_cf(CF_itc_total, nyears, "cf_itc_total"); - + // Save cf_energy_net_heat_btu (converted from cf_energy_net) + const double MMBTU_TO_KWh = 293.07107; // 1 MMBtu = 293.07107 kWh + std::vector cf_energy_net_vec = as_vector_double("cf_energy_net"); //[kWht] + int cf_energy_net_size = cf_energy_net_vec.size(); + ssc_number_t* cf_energy_net_heat_btu_arr = allocate("cf_energy_net_heat_btu", cf_energy_net_size); + for (int i = 0; i <= cf_energy_net_size; i++) + cf_energy_net_heat_btu_arr[i] = (ssc_number_t)(cf_energy_net_vec[i] / MMBTU_TO_KWh); //[MMBtu] } /* These functions can be placed in common financial library with matrix and constants passed? */ diff --git a/ssc/cmod_singleowner_heat.cpp b/ssc/cmod_singleowner_heat.cpp index e73f63d17..a739e1639 100644 --- a/ssc/cmod_singleowner_heat.cpp +++ b/ssc/cmod_singleowner_heat.cpp @@ -529,6 +529,7 @@ static var_info _cm_vtab_singleowner_heat[] = { /* Partial Income Statement: Project */ { SSC_OUTPUT, SSC_ARRAY, "cf_energy_net", "Thermal energy", "kWht", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, + { SSC_OUTPUT, SSC_ARRAY, "cf_energy_net_heat_btu", "Thermal energy", "MMBtu", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_energy_sales", "Thermal energy to grid", "kWht", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_energy_purchases", "Thermal energy from grid", "kWht", "", "Cash Flow Electricity", "*", "LENGTH_EQUAL=cf_length", "" }, { SSC_OUTPUT, SSC_ARRAY, "cf_energy_without_battery", "Thermal energy generated without storage", "kWht", "", "Cash Flow Electricity", "", "LENGTH_EQUAL=cf_length", "" }, @@ -3988,6 +3989,13 @@ class cm_singleowner_heat : public compute_module save_cf(CF_itc_sta, nyears, "cf_itc_sta"); save_cf(CF_itc_total, nyears, "cf_itc_total"); + + // Save cf_energy_net_heat_btu (converted from cf_energy_net) + std::vector cf_energy_net_vec = as_vector_double("cf_energy_net"); //[kWht] + int cf_energy_net_size = cf_energy_net_vec.size(); + ssc_number_t* cf_energy_net_heat_btu_arr = allocate("cf_energy_net_heat_btu", cf_energy_net_size); + for (int i = 0; i <= cf_energy_net_size; i++) + cf_energy_net_heat_btu_arr[i] = (ssc_number_t)(cf_energy_net_vec[i] / MMBTU_TO_KWh); //[MMBtu] } From 71e2ec06b8a1960918b1003a8188d8f95ef704bd Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Fri, 15 Nov 2024 00:09:11 -0700 Subject: [PATCH 79/85] Fix bug outputting energy output in MMBtu --- ssc/cmod_singleowner_heat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssc/cmod_singleowner_heat.cpp b/ssc/cmod_singleowner_heat.cpp index a739e1639..1454a9ff6 100644 --- a/ssc/cmod_singleowner_heat.cpp +++ b/ssc/cmod_singleowner_heat.cpp @@ -3994,7 +3994,7 @@ class cm_singleowner_heat : public compute_module std::vector cf_energy_net_vec = as_vector_double("cf_energy_net"); //[kWht] int cf_energy_net_size = cf_energy_net_vec.size(); ssc_number_t* cf_energy_net_heat_btu_arr = allocate("cf_energy_net_heat_btu", cf_energy_net_size); - for (int i = 0; i <= cf_energy_net_size; i++) + for (int i = 0; i < cf_energy_net_size; i++) cf_energy_net_heat_btu_arr[i] = (ssc_number_t)(cf_energy_net_vec[i] / MMBTU_TO_KWh); //[MMBtu] } From d77fb883acb9e2854a7d4ce9f5fdd4b377b8cda9 Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon, 18 Nov 2024 13:28:45 -0700 Subject: [PATCH 80/85] Fix bug assigning array in cashloan_heat --- ssc/cmod_cashloan_heat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssc/cmod_cashloan_heat.cpp b/ssc/cmod_cashloan_heat.cpp index 2414ee124..f457332cb 100644 --- a/ssc/cmod_cashloan_heat.cpp +++ b/ssc/cmod_cashloan_heat.cpp @@ -1569,7 +1569,7 @@ class cm_cashloan_heat : public compute_module std::vector cf_energy_net_vec = as_vector_double("cf_energy_net"); //[kWht] int cf_energy_net_size = cf_energy_net_vec.size(); ssc_number_t* cf_energy_net_heat_btu_arr = allocate("cf_energy_net_heat_btu", cf_energy_net_size); - for (int i = 0; i <= cf_energy_net_size; i++) + for (int i = 0; i < cf_energy_net_size; i++) cf_energy_net_heat_btu_arr[i] = (ssc_number_t)(cf_energy_net_vec[i] / MMBTU_TO_KWh); //[MMBtu] } From a008f8f3cefc01d91c10a09eb27653eb376b8eaa Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:02:34 -0700 Subject: [PATCH 81/85] Add commercial model to IPH fresnel. Add check for timestep fraction type in IPH trough. --- ssc/cmod_fresnel_physical_iph.cpp | 181 ++++++++++++++++++++---------- ssc/cmod_trough_physical_iph.cpp | 4 + 2 files changed, 127 insertions(+), 58 deletions(-) diff --git a/ssc/cmod_fresnel_physical_iph.cpp b/ssc/cmod_fresnel_physical_iph.cpp index 8eee6609e..07d721520 100644 --- a/ssc/cmod_fresnel_physical_iph.cpp +++ b/ssc/cmod_fresnel_physical_iph.cpp @@ -177,8 +177,18 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { // System Control - { SSC_INPUT, SSC_NUMBER, "is_timestep_load_fractions", "Use turbine load fraction for each timestep instead of block dispatch?", "", "", "tou", "?=0", "", "SIMULATION_PARAMETER" }, - { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "is_timestep_load_fractions=1", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "is_timestep_load_fractions", "0: block dispatch, 1: hourly load fraction, 2: absolute load", "", "", "tou", "?=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "is_timestep_load_fractions=1", "", "" }, + { SSC_INPUT, SSC_MATRIX, "weekday_schedule", "12x24 Time of Use Values for week days", "", "", "tou", "is_timestep_load_fractions=0", "", "" }, + { SSC_INPUT, SSC_MATRIX, "weekend_schedule", "12x24 Time of Use Values for week end days", "", "", "tou", "is_timestep_load_fractions=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "f_turb_tou_periods", "Dispatch logic for turbine load fraction", "-", "", "tou", "is_timestep_load_fractions=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "timestep_load_abs", "Heat sink hourly load (not normalized)", "kWt", "", "tou", "is_timestep_load_fractions=2", "", "" }, + { SSC_INPUT, SSC_NUMBER, "timestep_load_abs_factor", "Heat sink hourly load scale factor", "", "", "tou", "?=1", "", "" }, + + + { SSC_INPUT, SSC_NUMBER, "is_tod_pc_target_also_pc_max","Is the TOD target cycle heat input also the max cycle heat input?", "", "", "tou", "?=0", "", "" }, + + { SSC_INPUT, SSC_NUMBER, "pb_fixed_par", "Fixed parasitic load - runs at all times", "", "", "Sys_Control", "*", "", "" }, { SSC_INPUT, SSC_ARRAY, "bop_array", "Balance of plant parasitic power fraction", "", "", "Sys_Control", "*", "", "" }, @@ -218,15 +228,6 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { { SSC_INPUT, SSC_ARRAY, "dispatch_tod_factors", "TOD factors for periods 1 through 9", "", "", "Time of Delivery Factors", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1&sim_type=1", "", "SIMULATION_PARAMETER" }, { SSC_INPUT, SSC_ARRAY, "ppa_price_input_heat_btu", "PPA prices - yearly", "$/MMBtu", "", "Revenue", "ppa_multiplier_model=0&csp_financial_model<5&is_dispatch=1", "", "SIMULATION_PARAMETER" }, - - - // System Control - { SSC_INPUT, SSC_MATRIX, "weekday_schedule", "12x24 Time of Use Values for week days", "", "", "Sys_Control", "*", "", "" }, - { SSC_INPUT, SSC_MATRIX, "weekend_schedule", "12x24 Time of Use Values for week end days", "", "", "Sys_Control", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "is_tod_pc_target_also_pc_max","Is the TOD target cycle heat input also the max cycle heat input?", "", "", "tou", "?=0", "", "" }, - { SSC_INPUT, SSC_ARRAY, "f_turb_tou_periods", "Dispatch logic for turbine load fraction", "-", "", "tou", "*", "", "" }, - - // Capital Costs // Direct Capital Costs { SSC_INPUT, SSC_NUMBER, "site_improvements_spec_cost", "Site Improvement Cost per m2", "$/m2", "", "Capital_Costs", "?=0", "", "" }, @@ -359,8 +360,13 @@ static var_info _cm_vtab_fresnel_physical_iph[] = { // System Control { SSC_OUTPUT, SSC_NUMBER, "W_dot_bop_design", "BOP parasitics at design", "MWe", "", "Power Cycle", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "W_dot_fixed", "Fixed parasitic at design", "MWe", "", "Power Cycle", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "aux_design", "Aux parasitics at design", "MWe", "", "System Control", "*", "", "" }, - + { SSC_OUTPUT, SSC_NUMBER, "aux_design", "Aux parasitics at design", "MWe", "", "System Control", "*", "", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "timestep_load_fractions_calc", "Calculated timestep load fractions", "", "", "System Control", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "timestep_load_abs_calc", "Calculated timestep load data", "kWt", "", "System Control", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "thermal_load_heat_btu", "Thermal load (year 1)", "MMBtu/hr", "", "Thermal Rate", "csp_financial_model=5", "", "" }, + + // Capital Costs // Direct Capital Costs @@ -600,6 +606,7 @@ class cm_fresnel_physical_iph : public compute_module add_var_info(_cm_vtab_fresnel_physical_iph); add_var_info(vtab_adjustment_factors); add_var_info(vtab_technology_outputs); + add_var_info(vtab_utility_rate_common); // Required for dispatch w/ utility rates } void exec() @@ -813,35 +820,6 @@ class cm_fresnel_physical_iph : public compute_module } - // Heat Sink - C_pc_heat_sink c_heat_sink; - { - size_t n_f_turbine1 = 0; - ssc_number_t* p_f_turbine1 = as_array("f_turb_tou_periods", &n_f_turbine1); // heat sink, not turbine - double f_turbine_max1 = 1.0; - for (size_t i = 0; i < n_f_turbine1; i++) { - f_turbine_max1 = max(f_turbine_max1, p_f_turbine1[i]); - } - - c_heat_sink.ms_params.m_T_htf_hot_des = T_htf_hot_des; //[C] FIELD design outlet temperature - c_heat_sink.ms_params.m_T_htf_cold_des = T_htf_cold_des; //[C] FIELD design inlet temperature - c_heat_sink.ms_params.m_q_dot_des = q_dot_pc_des; //[MWt] HEAT SINK design thermal power (could have field solar multiple...) - // 9.18.2016 twn: assume for now there's no pressure drop though heat sink - c_heat_sink.ms_params.m_htf_pump_coef = as_double("pb_pump_coef"); //[kWe/kg/s] - c_heat_sink.ms_params.m_max_frac = f_turbine_max1; - - c_heat_sink.ms_params.m_pc_fl = as_integer("Fluid"); - c_heat_sink.ms_params.m_pc_fl_props = as_matrix("field_fl_props"); - - - // Allocate heat sink outputs - c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_Q_DOT_HEAT_SINK, allocate("q_dot_to_heat_sink", n_steps_fixed), n_steps_fixed); - c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_W_DOT_PUMPING, allocate("W_dot_pc_pump", n_steps_fixed), n_steps_fixed); - c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_M_DOT_HTF, allocate("m_dot_htf_heat_sink", n_steps_fixed), n_steps_fixed); - c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_T_HTF_IN, allocate("T_heat_sink_in", n_steps_fixed), n_steps_fixed); - c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_T_HTF_OUT, allocate("T_heat_sink_out", n_steps_fixed), n_steps_fixed); - } - // TES C_csp_two_tank_tes storage; { @@ -899,16 +877,69 @@ class cm_fresnel_physical_iph : public compute_module // Off-taker schedule C_timeseries_schedule_inputs offtaker_schedule; - bool is_timestep_load_fractions = as_boolean("is_timestep_load_fractions"); - if (is_timestep_load_fractions) { + int is_timestep_load_fractions = as_integer("is_timestep_load_fractions"); + std::vector timestep_load_fractions_calc; + std::vector timestep_load_abs_calc; + + // Block schedules + if (is_timestep_load_fractions == 0) { + C_timeseries_schedule_inputs offtaker_block = C_timeseries_schedule_inputs(as_matrix("weekday_schedule"), + as_matrix("weekend_schedule"), as_vector_double("f_turb_tou_periods"), std::numeric_limits::quiet_NaN()); + offtaker_schedule = offtaker_block; + } + else if (is_timestep_load_fractions == 1) { auto vec = as_vector_double("timestep_load_fractions"); C_timeseries_schedule_inputs offtaker_series = C_timeseries_schedule_inputs(vec, std::numeric_limits::quiet_NaN()); offtaker_schedule = offtaker_series; } - else { // Block schedules - C_timeseries_schedule_inputs offtaker_block = C_timeseries_schedule_inputs(as_matrix("weekday_schedule"), - as_matrix("weekend_schedule"), as_vector_double("f_turb_tou_periods"), std::numeric_limits::quiet_NaN()); - offtaker_schedule = offtaker_block; + else if (is_timestep_load_fractions == 2) { + std::vector vec_abs = as_vector_double("timestep_load_abs"); //[kWt] + double scale_factor = as_double("timestep_load_abs_factor"); + std::vector vec_abs_scaled; + for (double abs_val : vec_abs) + vec_abs_scaled.push_back(abs_val * scale_factor); //[kWt] + std::vector vec_norm; + double q_pb_design_kW = q_dot_pc_des * 1.e3; //[kWt] + for (double abs_val_scaled : vec_abs_scaled) + vec_norm.push_back(abs_val_scaled / q_pb_design_kW); + C_timeseries_schedule_inputs offtaker_series = C_timeseries_schedule_inputs(vec_norm, std::numeric_limits::quiet_NaN()); + offtaker_schedule = offtaker_series; + } + else + { + throw exec_error("fresnel_physical_iph", "Variable is_timestep_load_fractions must be 0-2"); + } + + // Heat Sink + C_pc_heat_sink c_heat_sink; + { + //size_t n_f_turbine1 = 0; + //ssc_number_t* p_f_turbine1 = as_array("f_turb_tou_periods", &n_f_turbine1); // heat sink, not turbine + //double f_turbine_max1 = 1.0; + //for (size_t i = 0; i < n_f_turbine1; i++) { + // f_turbine_max1 = max(f_turbine_max1, p_f_turbine1[i]); + //} + double f_turbine_max1 = 1.0; + for (S_timeseries_schedule_data data : offtaker_schedule.mv_timeseries_schedule_data) + f_turbine_max1 = max(f_turbine_max1, data.nondim_value); + + c_heat_sink.ms_params.m_T_htf_hot_des = T_htf_hot_des; //[C] FIELD design outlet temperature + c_heat_sink.ms_params.m_T_htf_cold_des = T_htf_cold_des; //[C] FIELD design inlet temperature + c_heat_sink.ms_params.m_q_dot_des = q_dot_pc_des; //[MWt] HEAT SINK design thermal power (could have field solar multiple...) + // 9.18.2016 twn: assume for now there's no pressure drop though heat sink + c_heat_sink.ms_params.m_htf_pump_coef = as_double("pb_pump_coef"); //[kWe/kg/s] + c_heat_sink.ms_params.m_max_frac = f_turbine_max1; + + c_heat_sink.ms_params.m_pc_fl = as_integer("Fluid"); + c_heat_sink.ms_params.m_pc_fl_props = as_matrix("field_fl_props"); + + + // Allocate heat sink outputs + c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_Q_DOT_HEAT_SINK, allocate("q_dot_to_heat_sink", n_steps_fixed), n_steps_fixed); + c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_W_DOT_PUMPING, allocate("W_dot_pc_pump", n_steps_fixed), n_steps_fixed); + c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_M_DOT_HTF, allocate("m_dot_htf_heat_sink", n_steps_fixed), n_steps_fixed); + c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_T_HTF_IN, allocate("T_heat_sink_in", n_steps_fixed), n_steps_fixed); + c_heat_sink.mc_reported_outputs.assign(C_pc_heat_sink::E_T_HTF_OUT, allocate("T_heat_sink_out", n_steps_fixed), n_steps_fixed); } // Electricity pricing schedule @@ -983,13 +1014,23 @@ class cm_fresnel_physical_iph : public compute_module } } + else if (csp_financial_model == 5) { // Commercial + + bool is_ur_assigned = is_assigned("ur_en_ts_sell_rate"); + + // rate data setup from ~ line 1336 in cmod_battery.cpp + rate_data* util_rate_data = new rate_data(); + rate_setup::setup(m_vartab, 8760, 1, *util_rate_data, "cmod_fresnel_physical_iph"); + + // Need to figure out dispatch, but for now, just use something so that annual simulation solves + elec_pricing_schedule = C_timeseries_schedule_inputs(-1.0, std::numeric_limits::quiet_NaN()); + } else { - throw exec_error("fresnel_physical_iph", "csp_financial_model must be 1, 7, or 8"); + throw exec_error("fresnel_physical_iph", "csp_financial_model must be 1, 5, 7, or 8"); } } else if (sim_type == 2) { elec_pricing_schedule = C_timeseries_schedule_inputs(-1.0, std::numeric_limits::quiet_NaN()); - } // Set dispatch model type @@ -1361,15 +1402,35 @@ class cm_fresnel_physical_iph : public compute_module } // System Control - // temporary fix vector aux_vec = as_vector_double("aux_array"); double W_dot_cycle_des = 0; double aux_design = aux_vec[0] * aux_vec[1] * (aux_vec[2] + aux_vec[3] + aux_vec[4]) * W_dot_cycle_des; + assign("aux_design", aux_design); - // Assign + std::vector timestep_load_fractions_calc; + std::vector timestep_load_abs_calc; + for (S_timeseries_schedule_data data : offtaker_schedule.mv_timeseries_schedule_data) { - assign("aux_design", aux_design); + double frac_val = data.nondim_value; + double abs_val = q_dot_pc_des * frac_val * 1.e3; //[kWt] + timestep_load_fractions_calc.push_back(frac_val); + timestep_load_abs_calc.push_back(abs_val); } + + set_vector("timestep_load_fractions_calc", timestep_load_fractions_calc); + set_vector("timestep_load_abs_calc", timestep_load_abs_calc); + + // Need to assign thermal load in Btu for thermalrate_iph cmod if commercial + if (csp_financial_model == 5) + { + std::vector load_abs_MMBtu; + for (double val_kW : timestep_load_abs_calc) + { + load_abs_MMBtu.push_back(val_kW / MMBTU_TO_KWh); + } + set_vector("thermal_load_heat_btu", load_abs_MMBtu); + } + } // Calculate Costs and assign outputs @@ -1696,13 +1757,17 @@ class cm_fresnel_physical_iph : public compute_module ssc_number_t* p_pipe_tes_P_dsn = allocate("pipe_tes_P_dsn", storage.pipe_P_des.ncells()); std::copy(storage.pipe_P_des.data(), storage.pipe_P_des.data() + storage.pipe_P_des.ncells(), p_pipe_tes_P_dsn); + } - - - - - + template + void set_vector(const std::string& name, const vector vec) + { + int size = vec.size(); + ssc_number_t* alloc_vals = allocate(name, size); + for (int i = 0; i < size; i++) + alloc_vals[i] = vec[i]; // [] } + }; DEFINE_MODULE_ENTRY(fresnel_physical_iph, "Physical Fresnel IPH applications", 1) diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index c1e1fcac7..f49177504 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -1490,6 +1490,10 @@ class cm_trough_physical_iph : public compute_module C_timeseries_schedule_inputs offtaker_series = C_timeseries_schedule_inputs(vec_norm, std::numeric_limits::quiet_NaN()); offtaker_schedule = offtaker_series; } + else + { + throw exec_error("trough_physical_iph", "Variable is_timestep_load_fractions must be 0-2"); + } // Heat Sink C_pc_heat_sink c_heat_sink; From 08b2aa7e4673d9e7a040bd3b543465cf2256bb62 Mon Sep 17 00:00:00 2001 From: Paul Gilman Date: Mon, 18 Nov 2024 14:52:34 -0800 Subject: [PATCH 82/85] Fix 6par sanity check that causes SAM to crash (#1245) --- ssc/cmod_6parsolve.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssc/cmod_6parsolve.cpp b/ssc/cmod_6parsolve.cpp index b695173f3..4e2cd669f 100644 --- a/ssc/cmod_6parsolve.cpp +++ b/ssc/cmod_6parsolve.cpp @@ -108,7 +108,7 @@ class cm_6parsolve : public compute_module int err = m.solve_with_sanity_and_heuristics(300, 1e-7); int err_keys[10] = { -1, -2, -3, -4, -5, -6, -7, -33, -44, -55 }; - int x; + int x=0; for (int i = 0; i < 10; i++) if (err_keys[i] == err) x = i; From 444fda31cd5c2e0ce0186a9dd511a62f9e2f5e0f Mon Sep 17 00:00:00 2001 From: Taylor Brown <60201147+taylorbrown75@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:30:04 -0700 Subject: [PATCH 83/85] Delete duplicate vartable variables. --- ssc/cmod_trough_physical_iph.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ssc/cmod_trough_physical_iph.cpp b/ssc/cmod_trough_physical_iph.cpp index f49177504..e44fb6502 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -100,10 +100,6 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "f_htfmin", "Minimum loop mass flow rate fraction of design", "", "", "solar_field", "use_abs_or_rel_mdot_limit=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "f_htfmax", "Maximum loop mass flow rate fraction of design", "", "", "solar_field", "use_abs_or_rel_mdot_limit=1", "", "" }, - { SSC_INPUT, SSC_NUMBER, "T_startup", "Required temperature of the system before the power block can be switched on", "C", "", "solar_field", "", "", "" }, - { SSC_INPUT, SSC_NUMBER, "T_shutdown", "Temperature when solar field begins recirculating", "C", "", "solar_field", "", "", "" }, - - { SSC_INPUT, SSC_MATRIX, "field_fl_props", "User defined field fluid property data", "-", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "T_fp", "Freeze protection temperature (heat trace activation temperature)", "none", "", "solar_field", "*", "", "" }, { SSC_INPUT, SSC_NUMBER, "I_bn_des", "Solar irradiation at design", "C", "", "solar_field", "*", "", "" }, From 80538e58592ca59e871581390eb6b2a93650be9c Mon Sep 17 00:00:00 2001 From: Steven Janzou Date: Thu, 21 Nov 2024 00:24:32 -0700 Subject: [PATCH 84/85] 2024.12.12.ssc.296.beta expires 1/31/2025 --- ssc/sscapi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssc/sscapi.cpp b/ssc/sscapi.cpp index 12386e71d..2844d1cf6 100644 --- a/ssc/sscapi.cpp +++ b/ssc/sscapi.cpp @@ -51,7 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SSCEXPORT int ssc_version() { - return 295; + return 296; } SSCEXPORT const char *ssc_build_info() From 2992a19a6dbf3a208f4859a14fe2039d7baf6067 Mon Sep 17 00:00:00 2001 From: Steven Janzou Date: Sun, 24 Nov 2024 03:14:46 -0700 Subject: [PATCH 85/85] Fix SAM issue 1927 --- ssc/cmod_singleowner_heat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssc/cmod_singleowner_heat.cpp b/ssc/cmod_singleowner_heat.cpp index 1454a9ff6..82fb922c2 100644 --- a/ssc/cmod_singleowner_heat.cpp +++ b/ssc/cmod_singleowner_heat.cpp @@ -3496,7 +3496,7 @@ class cm_singleowner_heat : public compute_module // save_cf(CF_energy_curtailed, nyears, "cf_energy_curtailed"); // const double MMBTU_TO_KWh = 293.07107; // 1 save_cf(CF_ppa_price, nyears, "cf_ppa_price"); - for (size_t i = 0; i < nyears; i++) + for (size_t i = 0; i <= nyears; i++) cf.at(CF_ppa_price_heat_btu, i) = cf.at(CF_ppa_price, i) * MMBTU_TO_KWh; save_cf( CF_ppa_price_heat_btu, nyears, "cf_ppa_price_heat_btu" ); save_cf( CF_om_fixed_expense, nyears, "cf_om_fixed_expense" );