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/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/CMakeLists.txt b/ssc/CMakeLists.txt index ba479a77c..f688508eb 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_custom_generation.cpp cmod_etes_electric_resistance.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 @@ -104,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_cashloan_heat.cpp b/ssc/cmod_cashloan_heat.cpp new file mode 100644 index 000000000..f457332cb --- /dev/null +++ b/ssc/cmod_cashloan_heat.cpp @@ -0,0 +1,1804 @@ +/* +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", "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", "" }, + + { 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_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", "" }, + { 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_heat[], + vtab_depreciation[], + vtab_battery_replacement_cost[], + 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[]; + + +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_om_elec_price_for_heat_techs, + + + 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_heat ); + 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); + 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, true); + + // 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_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; + 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"); + + // 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"); + 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_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) + + 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_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" ); + 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 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 + pvElec_price_for_heat_techs))); + 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"); + + // 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? */ + + 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_csp_subcomponent.cpp b/ssc/cmod_csp_subcomponent.cpp index 12c0057a1..a348af8bc 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_piston_cylinder_tes.h" +#include "csp_solver_packedbed_tes.h" // Forward declarations double C_to_K(double T); @@ -48,30 +50,37 @@ 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", "*", "", "" }, - { 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, "h_tank", "Total height of tank (height of HTF when tank is full", "m", "", "TES", "*", "", "" }, - { 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 (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=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=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>1", "", "" }, + + // 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", "*", "", "" }, @@ -87,11 +96,68 @@ 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", "*", "", "" }, + // TES Two Tank Specific + { 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=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=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 { 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=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=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=2", "", "" }, + var_info_invalid }; @@ -105,6 +171,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 +181,219 @@ 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_piston_cylinder_tes storage_cyl; + 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 == 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"); + + 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; + } + // 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; + + } + // Piston Cylinder + else if (tes_type == C_csp_tes::csp_tes_types::E_TES_CYL) + { + + int nstep = as_integer("tes_n_tsteps"); + + 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_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"); + 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_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 + //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_cyl_tank_cp") * 1000, // convert to J/kgK + dens_w_insulation, // Tank Wall density + as_double("tes_cyl_tank_thick"), // Tank wall thickness + nstep, // Number subtimesteps + 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 + 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_cyl; + } + else + { + throw exec_error("csp_subcomponent", "tes_type must be 1-3"); + } // 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 +413,65 @@ 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 == 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); + } + 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_CYL) + { + 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); + } + // 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 == C_csp_tes::csp_tes_types::E_TES_PACKED_BED) + { + 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 +479,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 +491,101 @@ 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 == C_csp_tes::csp_tes_types::E_TES_CYL) + { + double 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_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); + 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_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 + 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++) + { + tes_T_grad_mat.at(i, j) = T_prev_vec[j] - 273.15; // [C] Convert from K + } + } + + } + + 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); + 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 == C_csp_tes::csp_tes_types::E_TES_PACKED_BED) + { + 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 cb41416a6..ce7f1327d 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"), @@ -709,10 +711,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"); // ***************************************************** // ***************************************************** @@ -737,16 +735,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; - } // ***************************************************** // ***************************************************** @@ -883,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 @@ -1162,18 +1156,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 9609658bc..9040d9152 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] @@ -807,10 +811,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"); // ***************************************************** // ***************************************************** @@ -835,16 +835,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; - } // ***************************************************** // ***************************************************** @@ -1010,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 @@ -1276,18 +1272,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 b1ab000a1..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", "", "" }, @@ -203,16 +203,16 @@ 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, "is_dispatch_series", "Use time-series dispatch factors", "", "", "Sys_Control", "?=1", "", "" }, - { SSC_INPUT, SSC_ARRAY, "dispatch_series", "Time series dispatch factors", "", "", "Sys_Control", "", "", "" }, { 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", "", "" }, @@ -222,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, "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, "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" }, @@ -235,8 +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_ARRAY, "timestep_load_fractions", "Turbine load fraction for each timestep, alternative to block dispatch", "", "", "tou", "?", "", "SIMULATION_PARAMETER" }, + { SSC_INPUT, SSC_NUMBER, "q_rec_heattrace", "Receiver heat trace energy consumption during startup", "kWhe", "", "tou", "?=0.0", "", "SIMULATION_PARAMETER" }, @@ -294,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", "", "" }, @@ -432,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): @@ -544,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 electricity production 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", "", "" }, @@ -599,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 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", "", "" }, @@ -969,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"), @@ -1036,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()); @@ -1192,9 +1185,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; { @@ -1202,11 +1192,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 @@ -1222,10 +1212,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] @@ -1233,9 +1222,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; - } } @@ -1480,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(); @@ -1606,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 @@ -1639,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); + } } @@ -1891,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); @@ -1902,25 +1892,15 @@ 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 - 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 5e8c8c2d4..07d721520 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", "", "" }, @@ -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", "?", "", "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", "*", "", "" }, @@ -194,10 +204,6 @@ static var_info _cm_vtab_fresnel_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", "", "" }, - /*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" }, @@ -206,7 +212,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" }, @@ -219,16 +225,8 @@ 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" }, - - - // 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", "*", "", "" }, - + { 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" }, // Capital Costs // Direct Capital Costs @@ -256,26 +254,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 @@ -283,6 +281,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", "*", "", "" }, @@ -349,7 +348,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", "*", "", "" }, @@ -361,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 @@ -388,25 +392,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_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): @@ -563,18 +568,24 @@ 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", "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 - { 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 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 electricity production 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_electricity_consumption", "Annual electricity consumption with availability derate", "kWe-hr", "", "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", "", "" }, - { 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", "", "" }, @@ -595,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() @@ -610,7 +622,27 @@ 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 + { + 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; @@ -788,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; { @@ -830,7 +833,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"), @@ -872,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 @@ -956,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 @@ -979,9 +1047,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; { @@ -989,11 +1054,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 @@ -1005,10 +1070,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; @@ -1019,9 +1083,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, @@ -1153,7 +1214,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] @@ -1262,13 +1323,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*/, - Q_tes_des_calc /*MWt-hr*/; + 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 /*MWht*/; 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(); @@ -1324,7 +1388,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); @@ -1338,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 @@ -1386,16 +1470,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 @@ -1419,69 +1503,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); + } } @@ -1535,7 +1620,9 @@ 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_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) @@ -1543,6 +1630,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) @@ -1550,43 +1639,39 @@ 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_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 + 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); + 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", "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("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); //[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 - 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) { @@ -1598,7 +1683,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); @@ -1611,7 +1696,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; }); @@ -1672,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_linear_fresnel_dsg_iph.cpp b/ssc/cmod_linear_fresnel_dsg_iph.cpp index ca678cd3d..22fadc941 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 @@ -179,7 +182,11 @@ 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, "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 { SSC_OUTPUT, SSC_ARRAY, "op_mode_1", "1st operating mode", "", "", "Controller", "*", "", "" }, @@ -187,14 +194,23 @@ 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 with availability derate", "kWt-hr", "", "Post-process", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_field_energy", "Annual gross thermal energy production with availability 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 consumption with availability derate", "kWe-hr", "", "Post-process", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_total_water_use", "Total annual water usage", "m^3", "", "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", "*", "", "" }, + { 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", "*", "", "" }, + // Financing + { 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 }; @@ -211,6 +227,24 @@ 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")) + { + 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")) { @@ -482,7 +516,6 @@ class cm_linear_fresnel_dsg_iph : public compute_module // ***************************************************** // System dispatch csp_dispatch_opt dispatch; - dispatch.solver_params.dispatch_optimize = false; // ******************************** // ******************************** @@ -587,38 +620,53 @@ 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_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 + 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_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]; } - 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_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] - accumulate_annual_for_year("gen", "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] + 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] - 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_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"); 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.)); 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] } }; diff --git a/ssc/cmod_mspt_iph.cpp b/ssc/cmod_mspt_iph.cpp index f8d3ac5cf..992361564 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", "*", "", ""}, @@ -164,8 +164,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"}, @@ -210,7 +209,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", "*", "", ""}, @@ -240,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", "*", "", ""}, @@ -248,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 @@ -265,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" }, @@ -279,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_heat_btu", "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", "*", "", "" }, @@ -309,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 @@ -393,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", "*", "", "" }, @@ -402,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", "*", "", "" }, @@ -438,25 +434,26 @@ 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" }, @@ -603,28 +600,32 @@ 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", "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 thermal energy to heat sink with availability derate", "kWt-hr", "", "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 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 w/ avail. derate", "MMBtu", "", "Post-process", "sim_type=1", "", ""}, -{ SSC_OUTPUT, SSC_NUMBER, "annual_electricity_consumption", "Annual electricity consumption with availability 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", "", ""}, { 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", "", ""}, @@ -659,6 +660,24 @@ 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")) + { + 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]; // [] + } + // ***************************************************** // System Design Parameters @@ -668,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 @@ -1393,7 +1412,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 @@ -1542,7 +1561,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"), @@ -1596,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 @@ -1683,8 +1704,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 // ***************************************************** // @@ -1705,10 +1724,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; @@ -1717,10 +1735,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 @@ -1914,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*/, - 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*/; + 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 /*MWht*/; 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("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] @@ -2189,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 @@ -2294,9 +2310,12 @@ 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_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 ssc_number_t* p_time_final_hr = as_array("time_hr", &count); @@ -2304,23 +2323,27 @@ 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_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]; } - 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); //[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] - 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] // 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.)); @@ -2373,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); @@ -2394,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_singleowner_heat.cpp b/ssc/cmod_singleowner_heat.cpp new file mode 100644 index 000000000..1454a9ff6 --- /dev/null +++ b/ssc/cmod_singleowner_heat.cpp @@ -0,0 +1,4542 @@ +/* +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", "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", "*", "", "" }, + + { 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", "", "", "" }, + // 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 + 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", "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", "" }, + + { 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", "" }, + + { 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", "" }, + /* 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", "" }, + { 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_heat[], +vtab_standard_financial[], +vtab_oandm_heat[], +vtab_equip_reserve[], +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_heat[], +// 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_ppa_price_heat_btu, + + 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_heat ); + 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_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 ); + 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); +// 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_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 ); + 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 ); + + 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, true); + + // 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 + + // convert to $/kWht done in iph models + + // 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); + // 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]; + } + } + } + } + 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]; + } + + // 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; + }; + + 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"); + 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"); +// 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" ); + + 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"); + + + // 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] + } + + + // 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)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*/, - 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*/; + 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 /*MWht*/; 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("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] @@ -2998,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); @@ -3022,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,19 +3013,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; @@ -3063,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); @@ -3136,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) { @@ -3153,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) { @@ -3184,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_thermalrate_iph.cpp b/ssc/cmod_thermalrate_iph.cpp new file mode 100644 index 000000000..fb61f479a --- /dev/null +++ b/ssc/cmod_thermalrate_iph.cpp @@ -0,0 +1,678 @@ +/* +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", "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", "", "", "" }, + + { 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_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", "", "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-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", "*", "", "" }, + { 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)", "$", "", "", "*", "", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "year1_monthly_load_heat", "Thermal load", "kWht/mo", "", "Monthly", "*", "LENGTH=12", "" }, + + + + 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_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; + + + // 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 + + // Convert thermal load units + 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)); + if ((nrec_load != m_num_rec_yearly) && (nrec_load != 8760)) + 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; + + + + + + + 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) + { + size_t nbuyrate,step_per_hour_br; + ssc_number_t br; + 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_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_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 / 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)) + idx++; + } + } + } + // 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_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; + 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] / eff_buy_frac; // account for heat conversion efficiency + hr_count++; + } + } + } + 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 / eff_buy_frac; // account for heat conversion efficiency + } + + // 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_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_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_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++) + { + 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++; + } + } + } + // 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_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; + 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"); + 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_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); + 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); + + 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 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.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.cpp b/ssc/cmod_trough_physical.cpp index 76edb8f70..408036d75 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_piston_cylinder_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" @@ -56,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"}, @@ -90,7 +90,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", "", "" }, @@ -98,6 +99,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", "*", "", "" }, @@ -164,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", "*", "", "" }, @@ -197,23 +201,49 @@ 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, "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 (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, "h_tank", "Total height of tank (height of HTF when tank is full", "m", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "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=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", "*", "", "" }, - { 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=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", "*", "", "" }, + { 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>1", "", "" }, + + // TES Two Tank Specific + { 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=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=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) // Trough field @@ -264,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", "" }, @@ -277,7 +307,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", "?", "", "SIMULATION_PARAMETER" }, + { 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" }, @@ -315,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", "*", "", "" }, @@ -368,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 @@ -436,6 +488,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","*", "", "" }, @@ -447,7 +500,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", "", "" }, @@ -493,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", "", "" }, @@ -596,6 +668,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=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=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=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", "*", "", "" }, // SYSTEM @@ -607,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 production 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", "", "" }, @@ -660,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 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", "", "" }, @@ -669,13 +774,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=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", "*", "", "" }, @@ -701,8 +806,6 @@ static var_info _cm_vtab_trough_physical[] = { var_info_invalid }; - - class cm_trough_physical : public compute_module { public: @@ -721,6 +824,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) { @@ -884,8 +989,24 @@ 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 + + double T_startup = T_startup_old; + if (is_assigned("T_startup")) + { + T_startup = as_double("T_startup"); + } + + double T_shutdown = T_startup; + if (is_assigned("T_shutdown")) + { + T_shutdown = as_double("T_shutdown"); + } + + + 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 c_trough.m_m_dot_htfmin_in = as_double("m_dot_htfmin"); //[kg/s] Minimum loop HTF flow rate @@ -1077,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 @@ -1261,15 +1382,18 @@ 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_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) { bool custom_tes_pipe_sizes = as_boolean("custom_tes_pipe_sizes"); @@ -1294,7 +1418,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"), @@ -1302,7 +1429,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"), @@ -1335,29 +1464,197 @@ 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); + } + // 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); + } + // 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"); + + 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_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_cyl = C_csp_piston_cylinder_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"), + // 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_cyl_tank_cp") * 1000, // convert to J/kgK + dens_w_insulation, + as_double("tes_cyl_tank_thick"), + nstep, + as_vector_double("tes_cyl_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_cyl; + + // Set storage outputs + 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 + { + throw exec_error("trough_physical", "tes_type must be 1-3"); } - // ************************************************************************* // 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()); @@ -1445,6 +1742,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 @@ -1521,10 +1820,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); @@ -1571,8 +1866,6 @@ class cm_trough_physical : public compute_module } } - - } @@ -1599,10 +1892,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] @@ -1610,9 +1903,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; - } } @@ -1620,7 +1910,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, @@ -1743,6 +2033,7 @@ class cm_trough_physical : public compute_module // ******************************** // ******************************** double nameplate_des; + double Q_tes = std::numeric_limits::quiet_NaN(); { // System Design { @@ -1809,25 +2100,52 @@ 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; + 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_CYL) + { + 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_cyl.get_hot_tank_vol_frac(); + } + + 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 = 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 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", 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 @@ -2014,7 +2332,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 @@ -2026,15 +2343,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; @@ -2060,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 @@ -2181,11 +2564,18 @@ 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 + 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_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; }); 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); ssc_number_t *p_pipe_runner_wallthk = allocate("pipe_runner_wallthk", c_trough.m_WallThk_runner.size()); @@ -2225,21 +2615,24 @@ 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 == 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 if (!as_boolean("vacuum_arrays")) { @@ -2254,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); @@ -2264,25 +2657,15 @@ 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 - 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 31390ef75..e44fb6502 100644 --- a/ssc/cmod_trough_physical_iph.cpp +++ b/ssc/cmod_trough_physical_iph.cpp @@ -43,14 +43,21 @@ 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_solver_piston_cylinder_tes.h" +#include "csp_solver_packedbed_tes.h" #include "csp_system_costs.h" //#include "cmod_csp_common_eqns.h" +#include "csp_solver_cr_electric_resistance.h" + #include #include #include +// for utility rates +#include "cmod_utilityrate5.h" + // signed/unsigned mismatch #pragma warning (disable : 4388) @@ -58,12 +65,13 @@ 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"}, + { 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", "" }, { 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 @@ -75,8 +83,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", "*", "", "" }, @@ -84,7 +90,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", "", "" }, @@ -93,7 +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_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", "*", "", "" }, @@ -160,59 +166,100 @@ 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 - - /*Heat Sink*/{ SSC_INPUT, SSC_NUMBER, "pb_pump_coef", "Pumping power to move 1kg of HTF through PB loop", "kW/kg", "", "Heat Sink", "*", "", "" }, - - - - // 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", "*", "", "" }, + { 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 (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, "h_tank", "Total height of tank (height of HTF when tank is full", "m", "", "TES", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "u_tank", "Loss coefficient from the tank", "W/m2-K", "", "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=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", "*", "", "" }, - { 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, "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, "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>1", "", "" }, + + // TES Two Tank Specific + { 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=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=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", "", "" }, - // 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, "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", "*", "", "" }, + + // Dispatch optimization + { 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, "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_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", "", - "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_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_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" }, + { 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","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 { SSC_INPUT, SSC_NUMBER, "pb_fixed_par", "Fraction of rated gross power constantly consumed", "MWe/MWcap", "", "system", "*", "", "" }, @@ -220,8 +267,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", "*", "", "" }, @@ -238,10 +283,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", "*", "", "" }, @@ -268,17 +309,6 @@ static var_info _cm_vtab_trough_physical_iph[] = { { SSC_INPUT, SSC_NUMBER, "non_solar_field_land_area_multiplier", "non_solar_field_land_area_multiplier", "-", "", "controller", "*", "", "" }, { 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" }, - - // Direct Capital Costs { SSC_INPUT, SSC_NUMBER, "csp.dtr.cost.site_improvements.cost_per_m2", "Site Improvement Cost per m2", "$/m2", "", "Capital_Costs", "?=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "csp.dtr.cost.solar_field.cost_per_m2", "Solar Field Cost per m2", "$/m2", "", "Capital_Costs", "?=0", "", "" }, @@ -304,26 +334,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 @@ -331,7 +361,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", "*", "", "" }, @@ -382,11 +412,16 @@ 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", "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","*", "", "" }, { 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","*", "", "" }, @@ -396,7 +431,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", "", "" }, @@ -412,6 +448,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 @@ -440,25 +481,27 @@ 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","", "" }, + // Simulation Kernel { SSC_OUTPUT, SSC_ARRAY, "time_hr", "Time at end of timestep", "hr", "", "solver", "sim_type=1", "", "" }, @@ -530,19 +573,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", "", "" }, @@ -551,7 +581,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", "", "" }, @@ -571,6 +600,34 @@ 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=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=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=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", "*", "", "" }, @@ -586,21 +643,26 @@ 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 AC energy in Year 1", "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 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 consumption with availability derate", "kWe-hr", "", "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", "", "" }, + { 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", "", "" }, { 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", "", "" }, @@ -609,6 +671,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", "", "" }, @@ -617,46 +683,56 @@ 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", "", "" }, - - //{ 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", "", "" }, + + // 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", "", "" }, + { 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", "", "" }, { 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", "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", "", "" }, { 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", "", "" }, { 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", "", "" }, - { 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=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", "*", "", "" }, @@ -673,6 +749,8 @@ 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( ) @@ -687,9 +765,27 @@ 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] - int is_dispatch = 0; + double q_dot_hs_des = as_double("q_pb_design"); //[MWt] HEAT SINK design thermal power + double Q_tes = q_dot_hs_des * tshours; //[MWht] + int is_dispatch = as_boolean("is_dispatch"); + + int tes_type = as_integer("tes_type"); + const double MMBTU_TO_KWh = 293.07107; // 1 MMBtu = 293.07107 kWh + + // Convert IPH Input Units + { + 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); + } + set_vector("ppa_price_input", ppa_price_input_vec); + } + } // ***************************************************** // System Design Parameters @@ -741,7 +837,6 @@ class cm_trough_physical_iph : public compute_module sim_setup.m_report_step = 3600.0 / (double)steps_per_hour; //[s] } - // ******************************** // ******************************** // Solar field, trough @@ -782,13 +877,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")) + { + T_startup = as_double("T_startup"); + } + + double T_shutdown = T_startup; + if (is_assigned("T_shutdown")) + { + T_shutdown = as_double("T_shutdown"); + } + + 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 c_trough.m_m_dot_htfmin_in = as_double("m_dot_htfmin"); //[kg/s] Minimum loop HTF flow rate @@ -815,7 +926,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"); //[] @@ -961,7 +1072,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 @@ -1033,45 +1144,55 @@ class cm_trough_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]); + // 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"); + } } - 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; + double heater_mult = as_double("heater_mult"); //[-] + heater_spec_cost = as_double("heater_spec_cost"); //[$/kWt] - 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"); + 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 - // 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); - } - + 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; // ******************************** // ******************************** // TES // ******************************** // ******************************** - C_csp_two_tank_tes storage; + C_csp_tes* storage_pointer; + C_csp_two_tank_tes storage_two_tank; + 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) { bool custom_tes_pipe_sizes = as_boolean("custom_tes_pipe_sizes"); @@ -1096,15 +1217,20 @@ class cm_trough_physical_iph : 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"), as_matrix("store_fl_props"), - q_dot_pc_des, + 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"), @@ -1114,8 +1240,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"), @@ -1135,17 +1262,190 @@ class cm_trough_physical_iph : public compute_module as_double("HDR_rough") ); + 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_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_hs_des, // [MWt] Design heat rate in and out of tes + 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 + 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); + } + // 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"); + + 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_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_cyl = C_csp_piston_cylinder_tes( + c_trough.m_Fluid, + c_trough.m_field_fl_props, + //as_integer("store_fluid"), + //as_matrix("store_fl_props"), + q_dot_hs_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_cyl_tank_cp") * 1000, // convert to J/kgK + dens_w_insulation, + as_double("tes_cyl_tank_thick"), + nstep, + as_vector_double("tes_cyl_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_cyl; + + // Set storage outputs + 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 + { + throw exec_error("trough_physical", "tes_type must be 1-3"); + } + // ******************************** @@ -1156,16 +1456,72 @@ 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; + } + 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; + { + //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 @@ -1173,9 +1529,9 @@ 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 || 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 @@ -1196,7 +1552,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 { ppa_price_year1 = 1.0; //[-] don't need ppa multiplier if not optimizing } @@ -1240,8 +1596,19 @@ 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, 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) @@ -1261,7 +1628,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; //[$/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; { @@ -1269,51 +1641,36 @@ 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]; } // ***************************************************** // System dispatch - csp_dispatch_opt dispatch; - - if (is_dispatch) { - - double heater_startup_cost = 0.0; + cst_iph_dispatch_opt dispatch; - double q_dot_rec_des = q_dot_pc_des * c_trough.m_solar_mult; //[MWt] + if (is_dispatch && sim_type == 1) { - 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; - 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; + dispatch.params.set_user_params(as_double("disp_time_weighting"), 0.0); } // Instantiate Solver C_csp_solver csp_solver(weather_reader, c_trough, c_heat_sink, - storage, + *storage_pointer, tou, dispatch, system, - NULL, + p_heater, nullptr, ssc_cmod_update, (void*)(this)); @@ -1359,6 +1716,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); @@ -1372,6 +1730,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); @@ -1386,11 +1747,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); @@ -1434,7 +1791,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] @@ -1493,33 +1850,83 @@ 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; //[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); //[MWhr] + } + + // 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*/, - Q_tes_des_calc /*MWt-hr*/; + 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 /*MWht*/; + + 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_CYL) + { + 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_cyl.get_hot_tank_vol_frac(); + } - 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); + 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 = 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("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 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 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 @@ -1627,17 +2034,39 @@ 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_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 + + 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); + } + } } @@ -1670,31 +2099,32 @@ 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"); // 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); @@ -1714,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 @@ -1830,9 +2264,13 @@ 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_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 + ssc_number_t *p_W_dot_parasitic_tot = as_array("W_dot_parasitic_tot", &count); if (count != n_steps_fixed) @@ -1846,6 +2284,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"); @@ -1853,20 +2295,37 @@ 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]); - 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_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] //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; //[$] + 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 = 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_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; }); 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; }); @@ -1910,28 +2369,32 @@ 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); - + 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", "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("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); @@ -1941,27 +2404,19 @@ 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"); //[kWhe] - 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 - 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) { @@ -1986,6 +2441,22 @@ 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"); + 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 < 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; + tot_heat_load += heat_load; + 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); } template diff --git a/ssc/common.cpp b/ssc/common.cpp index d931a83e1..eaecf85d6 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*/ @@ -263,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" , ""}, @@ -291,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" , ""}, @@ -457,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", "*", "", "" }, @@ -485,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", "" }, @@ -603,7 +732,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); @@ -611,7 +740,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); @@ -1200,14 +1334,20 @@ 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_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", "" }, @@ -1216,50 +1356,53 @@ var_info vtab_utility_rate_common[] = { { SSC_INPUT, SSC_NUMBER, "ur_nb_apply_credit_current_month", "Apply earned credits to balance before rolling over excess ", "0/1", "0=disable,1=enable", "Electricity Rates", "?=0", "INTEGER,MIN=0,MAX=1", "" }, // 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", "" }, - - - // 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", "", "", "" }, + { 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 diff --git a/ssc/common.h b/ssc/common.h index c6a9e2575..44724d9f0 100644 --- a/ssc/common.h +++ b/ssc/common.h @@ -52,9 +52,13 @@ 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[]; +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[]; @@ -76,7 +80,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); 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; } diff --git a/ssc/sscapi.cpp b/ssc/sscapi.cpp index e8ad9f96d..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() @@ -87,14 +87,16 @@ extern module_entry_info cm_entry_utilityrate4, cm_entry_utilityrate5, cm_entry_utilityrateforecast, - cm_entry_cashloan, - cm_entry_thirdpartyownership, + cm_entry_cashloan, + cm_entry_cashloan_heat, + cm_entry_thirdpartyownership, cm_entry_ippppa, cm_entry_timeseq, cm_entry_levpartflip, cm_entry_equpartflip, cm_entry_saleleaseback, cm_entry_singleowner, + cm_entry_singleowner_heat, cm_entry_communitysolar, cm_entry_merchantplant, cm_entry_host_developer, @@ -153,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, @@ -187,14 +190,16 @@ static module_entry_info *module_table[] = { &cm_entry_utilityrate4, &cm_entry_utilityrate5, &cm_entry_utilityrateforecast, - &cm_entry_cashloan, - &cm_entry_thirdpartyownership, + &cm_entry_cashloan, + &cm_entry_cashloan_heat, + &cm_entry_thirdpartyownership, &cm_entry_ippppa, &cm_entry_timeseq, &cm_entry_levpartflip, &cm_entry_equpartflip, &cm_entry_saleleaseback, &cm_entry_singleowner, + &cm_entry_singleowner_heat, &cm_entry_communitysolar, &cm_entry_merchantplant, &cm_entry_host_developer, @@ -253,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, diff --git a/tcs/CMakeLists.txt b/tcs/CMakeLists.txt index 6ae3fd0dc..3e75609b5 100644 --- a/tcs/CMakeLists.txt +++ b/tcs/CMakeLists.txt @@ -17,13 +17,15 @@ 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 csp_solver_mspt_collector_receiver.cpp csp_solver_mspt_receiver.cpp csp_solver_mspt_receiver_222.cpp + csp_solver_piston_cylinder_tes.cpp + csp_solver_packedbed_tes.cpp csp_solver_pc_gen.cpp csp_solver_pc_heat_sink.cpp csp_solver_pc_ptes.cpp @@ -32,12 +34,14 @@ 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 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 +52,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,12 +114,14 @@ 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 csp_solver_mspt_receiver.h csp_solver_mspt_receiver_222.h + csp_solver_piston_cylinder_tes.h + csp_solver_packedbed_tes.h csp_solver_pc_gen.h csp_solver_pc_heat_sink.h csp_solver_pc_ptes.h @@ -124,10 +130,12 @@ 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 csp_system_costs.h + cst_iph_dispatch.h direct_steam_receivers.h dispatch_builder.h etes_dispatch.h diff --git a/tcs/base_dispatch.h b/tcs/base_dispatch.h index 9f424fedf..22e014bc2 100644 --- a/tcs/base_dispatch.h +++ b/tcs/base_dispatch.h @@ -184,6 +184,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 3e52c6269..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 /* @@ -174,33 +163,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; } @@ -1592,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 54373f04f..ce321c32f 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 @@ -467,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); } @@ -614,6 +613,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 +1075,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] @@ -1481,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")); } @@ -2386,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 d483ade0c..0469d5cf9 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, @@ -347,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; @@ -718,6 +728,13 @@ class C_csp_tes public: + enum csp_tes_types + { + E_TES_TWO_TANK = 1, + E_TES_PACKED_BED, + E_TES_CYL + }; + // Class to save messages for up stream classes C_csp_messages mc_csp_messages; @@ -844,6 +861,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 @@ -1853,6 +1871,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 diff --git a/tcs/csp_solver_packedbed_tes.cpp b/tcs/csp_solver_packedbed_tes.cpp new file mode 100644 index 000000000..fdf6a46bc --- /dev/null +++ b/tcs/csp_solver_packedbed_tes.cpp @@ -0,0 +1,1033 @@ +/* +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_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 + 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, // [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_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_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] + + // Convert Specific heat unit + m_cp_solid = cp_solid * 1e3; //[J/kg 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_grad_init.push_back(T_C + 273.15); + 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; + } + } + + // 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() +{ + 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() +{ + //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() +{ + //MWh + return 0.; +} + +double C_csp_packedbed_tes::get_max_charge_energy() +{ + //MWh + return m_q_pb_design * m_ts_hours / 1.e6; +} + +double C_csp_packedbed_tes::get_degradation_rate() +{ + // 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; +} + +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; +} diff --git a/tcs/csp_solver_packedbed_tes.h b/tcs/csp_solver_packedbed_tes.h new file mode 100644 index 000000000..7798d5f94 --- /dev/null +++ b/tcs/csp_solver_packedbed_tes.h @@ -0,0 +1,236 @@ +/* +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_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 + 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 + 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, + 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_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 + 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, // [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 + ); + + 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*/); + + std::vector get_T_prev_vec() { return m_T_prev_vec; }; +}; + + +#endif diff --git a/tcs/csp_solver_piston_cylinder_tes.cpp b/tcs/csp_solver_piston_cylinder_tes.cpp new file mode 100644 index 000000000..02c0d99fd --- /dev/null +++ b/tcs/csp_solver_piston_cylinder_tes.cpp @@ -0,0 +1,2096 @@ +/* +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_piston_cylinder_tes.h" + +static C_csp_reported_outputs::S_output_info S_output_info[] = +{ + {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_cyl::C_storage_tank_dynamic_cyl() +{ + 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_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*/, + 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_cyl::calc_mass_at_prev() +{ + return m_V_prev * mc_htf.dens(m_T_prev, 1.0); //[kg] +} + +double C_storage_tank_dynamic_cyl::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_cyl::get_m_T_prev() +{ + return m_T_prev; //[K] +} + +double C_storage_tank_dynamic_cyl::get_m_T_calc() +{ + return m_T_calc; +} + +double C_storage_tank_dynamic_cyl::get_m_m_calc() // Get Current FLUID mass +{ + return m_m_calc; //[kg] +} + +double C_storage_tank_dynamic_cyl::get_m_m_prev() // Get Previous FLUID mass +{ + return m_m_prev; //[kg] +} + +double C_storage_tank_dynamic_cyl::get_m_E_prev() +{ + return m_E_prev; //[MJ] +} + +double C_storage_tank_dynamic_cyl::get_m_E_calc() +{ + return m_E_calc; //[MJ] +} + +double C_storage_tank_dynamic_cyl::get_vol_frac() +{ + return (m_V_prev - m_V_inactive) / m_V_active; +} + +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_cyl::get_fluid_vol() +{ + return m_V_calc; +} + +double C_storage_tank_dynamic_cyl::get_fluid_vol_prev() +{ + return m_V_prev; +} + +double C_storage_tank_dynamic_cyl::get_radius() +{ + return m_radius; +} + +double C_storage_tank_dynamic_cyl::get_SA_calc() +{ + return m_SA_calc; +} + +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 + 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_cyl::get_m_m_wall_prev() +{ + return m_m_wall_prev; +} + +double C_storage_tank_dynamic_cyl::get_m_m_wall_calc() +{ + return m_m_wall_calc; +} + +double C_storage_tank_dynamic_cyl::get_m_L_calc() +{ + return m_L_calc; +} + +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) + //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_cyl::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_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*/, + 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_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*/ + 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_cyl::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_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); + + 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_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); + 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_cyl::calc_SA(double volume /*m3*/) +{ + return 2.0 * volume / m_radius; +} + +double C_storage_tank_dynamic_cyl::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_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 + //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_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 = + 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_piston_cylinder_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")); + } + + // For Piston Cylinder, 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")); + //} + + /*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) + { + 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; + } + // Size Tank with Fixed Diameter + else + { + 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; + } + + // 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_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_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_cyl.get_radius(); + double cold_radius = mc_cold_tank_cyl.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_piston_cylinder_tes::does_tes_exist() +{ + return m_is_tes; +} + +bool C_csp_piston_cylinder_tes::is_cr_to_cold_allowed() +{ + return m_is_cr_to_cold_tank_allowed; +} + +double C_csp_piston_cylinder_tes::get_hot_temp() +{ + return mc_hot_tank_cyl.get_m_T_prev(); //[K] +} + +double C_csp_piston_cylinder_tes::get_cold_temp() +{ + return mc_cold_tank_cyl.get_m_T_prev(); //[K] +} + +double C_csp_piston_cylinder_tes::get_hot_tank_vol_frac() +{ + return mc_hot_tank_cyl.get_vol_frac(); +} + +double C_csp_piston_cylinder_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_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_piston_cylinder_tes::get_max_charge_energy() +{ + return m_q_pb_design * m_ts_hours / 1.e6; +} + +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)); + 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_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 + 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_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_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_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_cyl.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_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] + m_dot_external_est = m_dot_tank_disch_avail; + T_hot_external_est = T_hot_ini; + +} + +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 + 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_cyl.m_dot_available(f_ch_storage, step_s); //[kg/s] + + double T_cold_ini = mc_cold_tank_cyl.get_m_T_prev(); //[K] + + // for debugging + 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_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 + 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_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*/, + 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_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_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 + } + + + // 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_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; + + 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_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 + 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_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_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] + mc_reported_outputs.value(E_EXP_LENGTH, expansion_wall_L); //[m] + + return 0; +} + +void C_csp_piston_cylinder_tes::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_cyl.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_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_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_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) +{ + // 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] + + return htf_pump_power; +} + + +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*/) +{ + 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_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*/, + 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_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) + { + 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_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, + 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_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] + 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_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*/, + 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_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 + { + 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_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, + 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_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] + 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_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*/, + 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_cyl.get_m_m_prev(); + double mass_calc_inner_cold = std::numeric_limits::quiet_NaN(); + 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; + 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_cyl.get_m_m_prev(); + double mass_calc_inner_hot = std::numeric_limits::quiet_NaN(); + 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; + 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_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_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); + + // 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_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 + 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_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 + 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_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 + 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_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 + 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_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] + } + // 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_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] + } + } + + // 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_piston_cylinder_tes::get_min_storage_htf_temp() +{ + return mc_store_htfProps.min_temp(); +} + +double C_csp_piston_cylinder_tes::get_max_storage_htf_temp() +{ + return mc_store_htfProps.max_temp(); +} + +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_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_piston_cylinder_tes::calc_piston_location(double &piston_loc, double &piston_frac) +{ + 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)); + 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_piston_cylinder_tes.h b/tcs/csp_solver_piston_cylinder_tes.h new file mode 100644 index 000000000..acacc3d13 --- /dev/null +++ b/tcs/csp_solver_piston_cylinder_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_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_cyl +{ +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_cyl(); + + 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_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_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; + + // 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 Piston cylinder model + 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] + + // Piston Cylinder 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_piston_cylinder_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_piston_cylinder_tes(); + + ~C_csp_piston_cylinder_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_tes_core.cpp b/tcs/csp_solver_tes_core.cpp new file mode 100644 index 000000000..ba2fc3613 --- /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 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*/) +{ + 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 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*/) +{ + 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..9f9b9dd50 --- /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 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 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*/); + + + +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_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; } diff --git a/tcs/csp_solver_trough_collector_receiver.cpp b/tcs/csp_solver_trough_collector_receiver.cpp index 869114648..a543728e3 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 @@ -882,7 +887,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; @@ -1284,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; @@ -4101,7 +4112,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 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/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 new file mode 100644 index 000000000..20ffc71da --- /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 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 * q_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(); +}; 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 99b721eea..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 @@ -114,17 +111,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.); - - 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; + params.buy_price.resize(num_steps, 1.); - 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; @@ -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/input_cases/trough_physical_defaults.h b/test/input_cases/trough_physical_defaults.h index 38e3268a2..7a6a42098 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); @@ -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; } 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/main.cpp b/test/main.cpp index 20d32bf4f..00ec0fd14 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) = "Solesca*"; 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); } }; 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.);