From 44f9c0af84bc5b0c80322f42f26716c1df8e7f15 Mon Sep 17 00:00:00 2001 From: Matt Prilliman Date: Thu, 21 Sep 2023 13:24:44 -0500 Subject: [PATCH] Add modeling option for tidal timeseries modeling, tidal file reader --- ssc/CMakeLists.txt | 1 + ssc/cmod_mhk_tidal.cpp | 183 ++++++++++++-------- ssc/cmod_tidalfile.cpp | 380 +++++++++++++++++++++++++++++++++++++++++ ssc/sscapi.cpp | 2 + 4 files changed, 496 insertions(+), 70 deletions(-) create mode 100644 ssc/cmod_tidalfile.cpp diff --git a/ssc/CMakeLists.txt b/ssc/CMakeLists.txt index e7a1e2ba7..a530fb0b8 100644 --- a/ssc/CMakeLists.txt +++ b/ssc/CMakeLists.txt @@ -120,6 +120,7 @@ set(SSC_SRC cmod_utilityrate5.cpp cmod_utilityrate5.h cmod_wavefile.cpp + cmod_tidalfile.cpp cmod_utilityrate5_eqns.cpp cmod_utilityrate5_eqns.h cmod_utilityrateforecast.cpp diff --git a/ssc/cmod_mhk_tidal.cpp b/ssc/cmod_mhk_tidal.cpp index 9cb9cce66..ea63d54c7 100644 --- a/ssc/cmod_mhk_tidal.cpp +++ b/ssc/cmod_mhk_tidal.cpp @@ -46,6 +46,8 @@ static var_info _cm_vtab_mhk_tidal[] = { { SSC_INPUT, SSC_NUMBER, "financial_cost_total", "Financial costs", "$", "", "MHKTidal", "?=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "total_operating_cost", "O&M costs", "$", "", "MHKTidal", "?=1", "", "" }, { SSC_INPUT, SSC_NUMBER, "system_capacity", "System Nameplate Capacity", "kW", "", "MHKTidal", "?=0", "", "" }, + { SSC_INPUT, SSC_NUMBER, "tidal_resource_model_choice", "Resource distribution or time series tidal resource data", "0/1", "", "MHKTidal", "?=0", "INTEGER", "" }, + { SSC_INPUT, SSC_ARRAY, "tidal_velocity", "Tidal velocity", "m/s", "", "MHKTidal", "?", "", "" }, // losses @@ -60,9 +62,11 @@ static var_info _cm_vtab_mhk_tidal[] = { { SSC_OUTPUT, SSC_NUMBER, "device_rated_capacity", "Rated capacity of device", "kW", "", "MHKTidal", "", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "device_average_power", "Average power production of a single device", "kW", "", "MHKTidal", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual energy production of array", "kWh", "", "MHKTidal", "*", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "MHKTidal", "*", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "annual_energy_distribution", "Annual energy production of array as function of speed", "kWh", "", "MHKTidal", "*", "", "" }, - { SSC_OUTPUT, SSC_ARRAY, "annual_cumulative_energy_distribution","Cumulative annual energy production of array as function of speed", "kWh", "", "MHKTidal", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "gen", "System power generated", "kW", "", "MHKTidal", "", "", "" }, + + { SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "MHKTidal", "*", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "annual_energy_distribution", "Annual energy production of array as function of speed", "kWh", "", "MHKTidal", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "annual_cumulative_energy_distribution","Cumulative annual energy production of array as function of speed", "kWh", "", "MHKTidal", "", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "tidal_resource_start_velocity", "First tidal velocity where probability distribution is greater than 0 ", "m/s", "", "MHKTidal", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "tidal_resource_end_velocity", "Last tidal velocity where probability distribution is greater than 0 ", "m/s", "", "MHKTidal", "*", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "tidal_power_start_velocity", "First tidal velocity where power curve is greater than 0 ", "m/s", "", "MHKTidal", "*", "", "" }, @@ -149,93 +153,132 @@ class cm_mhk_tidal : public compute_module + as_double("loss_downtime") + as_double("loss_additional"); - - //Storing each column of the tidal_resource_matrix and tidal_power_curve as vectors: + int tidal_resource_model_choice = as_integer("tidal_resource_model_choice"); double tidal_resource_start_velocity = 0; double tidal_power_start_velocity = 0; double tidal_resource_end_velocity = 0; double tidal_power_end_velocity = 0; - double min_velocity, max_velocity = 0; - min_velocity = tidal_resource_matrix.at(0, 0); - max_velocity = tidal_resource_matrix.at(size_t(number_rows) - 1, 0); - - for (int i = 0; i < number_rows; i++) { - size_t n = i; - if (i != 0) { - if (tidal_resource_matrix.at(n, 1) != 0 && tidal_resource_matrix.at(n - 1, 1) == 0) - { - tidal_resource_start_velocity = tidal_resource_matrix.at(n, 0); + if (tidal_resource_model_choice == 0) { + //Storing each column of the tidal_resource_matrix and tidal_power_curve as vectors: + double min_velocity, max_velocity = 0; + min_velocity = tidal_resource_matrix.at(0, 0); + max_velocity = tidal_resource_matrix.at(size_t(number_rows) - 1, 0); + + for (int i = 0; i < number_rows; i++) { + size_t n = i; + if (i != 0) { + if (tidal_resource_matrix.at(n, 1) != 0 && tidal_resource_matrix.at(n - 1, 1) == 0) + { + tidal_resource_start_velocity = tidal_resource_matrix.at(n, 0); + } + if (tidal_power_curve.at(n, 1) != 0 && tidal_power_curve.at(n - 1, 1) == 0 && n != 0) + { + tidal_power_start_velocity = tidal_power_curve.at(n, 0); + } } - if (tidal_power_curve.at(n, 1) != 0 && tidal_power_curve.at(n - 1, 1) == 0 && n != 0) - { - tidal_power_start_velocity = tidal_power_curve.at(n, 0); + else { + if (tidal_resource_matrix.at(n, 1) != 0) + { + tidal_resource_start_velocity = tidal_resource_matrix.at(n, 0); + } + if (tidal_power_curve.at(n, 1) != 0) + { + tidal_power_start_velocity = tidal_power_curve.at(n, 0); + } } - } - else { - if (tidal_resource_matrix.at(n, 1) != 0) - { - tidal_resource_start_velocity = tidal_resource_matrix.at(n, 0); + if (i != number_rows - 1) { + if (tidal_resource_matrix.at(n, 1) != 0 && tidal_resource_matrix.at(n + 1, 1) == 0 && n != 0) + { + tidal_resource_end_velocity = tidal_resource_matrix.at(n, 0); + } + if (tidal_power_curve.at(n, 1) != 0 && tidal_power_curve.at(n + 1, 1) == 0 && n != 0) + { + tidal_power_end_velocity = tidal_power_curve.at(n, 0); + } } - if (tidal_power_curve.at(n, 1) != 0) - { - tidal_power_start_velocity = tidal_power_curve.at(n, 0); + else { + if (i == number_rows - 1 && tidal_resource_end_velocity == 0) + { + tidal_resource_end_velocity = tidal_resource_matrix.at(n, 0); + } + if (i == number_rows - 1 && tidal_power_end_velocity == 0) + { + tidal_power_end_velocity = tidal_power_curve.at(n, 0); + } } + + _speed_vect[i] = tidal_resource_matrix.at(i, 0); + _probability_vect[i] = tidal_resource_matrix.at(i, 1); //*******************again need to modify to handle different depths + _power_vect[i] = tidal_power_curve.at(i, 1); + + //Store max power if not set in UI: + /*if (as_boolean("calculate_capacity")) */ + if (_power_vect[i] > device_rated_capacity) + device_rated_capacity = _power_vect[i]; + + //Checker to ensure probability distribution adds to >= 99.5%: + _probability_vect_checker += _probability_vect[i]; + + //Calculate annual energy production at each stream speed bin: + p_annual_energy_dist[i] = _power_vect[i] * _probability_vect[i] * number_devices * 8760 * (1 - total_loss/100.0); + + //Add current annual energy bin to total annual energy + annual_energy += p_annual_energy_dist[i]; + + + p_annual_cumulative_energy_dist[i] = p_annual_energy_dist[i] + p_annual_cumulative_energy_dist[i - 1]; + + //Contribution to Average Power from this speed bin + device_average_power += _power_vect[i] * _probability_vect[i]; } - if (i != number_rows - 1) { - if (tidal_resource_matrix.at(n, 1) != 0 && tidal_resource_matrix.at(n + 1, 1) == 0 && n != 0) - { - tidal_resource_end_velocity = tidal_resource_matrix.at(n, 0); + } + else { + size_t number_records; + ssc_number_t* tidal_velocity = as_array("tidal_velocity", &number_records); + ssc_number_t* p_gen = allocate("gen", number_records); + int power_bin = 0; + //Find velocity bin of power array + for (int i = 0; i < number_records; i++) { + if (tidal_velocity[i] > tidal_power_curve.at(tidal_power_curve.nrows() - 1, 0)) { + power_bin = tidal_power_curve.nrows() - 1; + continue; } - if (tidal_power_curve.at(n, 1) != 0 && tidal_power_curve.at(n + 1, 1) == 0 && n != 0) - { - tidal_power_end_velocity = tidal_power_curve.at(n, 0); + else if (tidal_velocity[i] < tidal_power_curve.at(0, 0)) { + power_bin = 0; + continue; } - } - else { - if (i == number_rows - 1 && tidal_resource_end_velocity == 0) - { - tidal_resource_end_velocity = tidal_resource_matrix.at(n, 0); + else { + for (int j = 1; j < tidal_power_curve.nrows(); j++) { + if (tidal_velocity[i] - tidal_power_curve.at(j - 1, 0) > 0 && tidal_velocity[i] - tidal_power_curve.at(j, 0) < 0) { + power_bin = j; + } + + } } - if (i == number_rows - 1 && tidal_power_end_velocity == 0) - { - tidal_power_end_velocity = tidal_power_curve.at(n, 0); + p_gen[i] = tidal_power_curve.at(power_bin, 1) * (1 - total_loss/100.0) * number_devices; //kW + //p_annual_energy_dist[i] = p_gen[i] * 8760.0 / number_records; + annual_energy += p_gen[i] * 8760.0 / number_records; + //p_annual_cumulative_energy_dist[i] = p_annual_energy_dist[i] + p_annual_cumulative_energy_dist[i - 1]; + device_average_power += p_gen[i] / number_devices; + for (int k = 0; k < tidal_power_curve.nrows(); k++) { + if (tidal_power_curve.at(k, 1) > device_rated_capacity) + device_rated_capacity = tidal_power_curve.at(k, 1); } + } - - _speed_vect[i] = tidal_resource_matrix.at(i, 0); - _probability_vect[i] = tidal_resource_matrix.at(i, 1); //*******************again need to modify to handle different depths - _power_vect[i] = tidal_power_curve.at(i, 1); - - //Store max power if not set in UI: - /*if (as_boolean("calculate_capacity")) */ - if (_power_vect[i] > device_rated_capacity) - device_rated_capacity = _power_vect[i]; - - //Checker to ensure probability distribution adds to >= 99.5%: - _probability_vect_checker += _probability_vect[i]; - - //Calculate annual energy production at each stream speed bin: - p_annual_energy_dist[i] = _power_vect[i] * _probability_vect[i] * number_devices * 8760; - - //Add current annual energy bin to total annual energy - annual_energy += p_annual_energy_dist[i]; - - - p_annual_cumulative_energy_dist[i] = p_annual_energy_dist[i] + p_annual_cumulative_energy_dist[i - 1]; - - //Contribution to Average Power from this speed bin - device_average_power += _power_vect[i] * _probability_vect[i]; - } + } //Throw exception if frequency distribution vector sums to < 99.5% - double probability_tolerance = 0.005; - if (std::abs(1.0 - _probability_vect_checker) > probability_tolerance) - throw exec_error("mhk_tidal", "Probability distribution vector does not add up to 100%."); + if (tidal_resource_model_choice == 0) { + double probability_tolerance = 0.005; + if (std::abs(1.0 - _probability_vect_checker) > probability_tolerance) + throw exec_error("mhk_tidal", "Probability distribution vector does not add up to 100%."); + } //Factoring in losses in total annual energy production: - annual_energy *= (1 - (total_loss / 100 )); + //annual_energy *= (1 - (total_loss / 100 )); // leave device power without losses if (is_assigned("device_costs_total")) { //TEST cost metrics in tidal page rather than cost page diff --git a/ssc/cmod_tidalfile.cpp b/ssc/cmod_tidalfile.cpp new file mode 100644 index 000000000..1d4747bfd --- /dev/null +++ b/ssc/cmod_tidalfile.cpp @@ -0,0 +1,380 @@ +/* +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_util.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include "lib_weatherfile.cpp" + +static std::string trimboth(std::string& buf) +{ + const auto strBegin = buf.find_first_not_of(" \t"); + if (strBegin == std::string::npos) + return std::string(); + + const auto strEnd = buf.find_last_not_of(" \t\r\n"); + const auto strRange = strEnd - strBegin + 1; + return buf.substr(strBegin, strRange); +} + +static std::vector split(const std::string& buf, char delim = ',') +{ + std::string token; + std::vector tokens; + std::istringstream tokenStream(buf); + while (std::getline(tokenStream, token, delim)) + tokens.push_back(token); + return tokens; +} + +static var_info _cm_tidal_file_reader[] = { +/* VARTYPE DATATYPE NAME LABEL UNITS META GROUP REQUIRED_IF CONSTRAINTS UI_HINTS*/ + { SSC_INPUT, SSC_NUMBER, "tidal_resource_model_choice", "Resource distribution or time series tidal resource data", "0/1", "", "Weather Reader", "?=1", "INTEGER", "" }, + { SSC_INPUT, SSC_STRING, "tidal_resource_filename", "File path with tidal resource data", "", "", "Weather Reader", "tidal_resource_model_choice=0", "LOCAL_FILE", "" }, + + { SSC_INPUT, SSC_NUMBER, "use_specific_wf_tidal", "user specified file", "0/1", "", "Weather Reader", "?=0", "INTEGER,MIN=0,MAX=1", "" }, + +// header data + { SSC_OUTPUT, SSC_STRING, "name", "Name", "", "", "Weather Reader", "tidal_resource_model_choice=0", "", "" }, + { SSC_OUTPUT, SSC_STRING, "city", "City", "", "", "Weather Reader", "tidal_resource_model_choice=0", "", "" }, + { SSC_OUTPUT, SSC_STRING, "state", "State", "", "", "Weather Reader", "tidal_resource_model_choice=0", "", "" }, + { SSC_OUTPUT, SSC_STRING, "country", "Country", "", "", "Weather Reader", "tidal_resource_model_choice=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "lat", "Latitude", "deg", "", "Weather Reader", "", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "lon", "Longitude", "deg", "", "Weather Reader", "", "", "" }, + { SSC_OUTPUT, SSC_STRING, "nearby_buoy_number", "Nearby buoy number", "", "", "Weather Reader", "tidal_resource_model_choice=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "average_power_flux", "Average power flux", "kW/m", "", "Weather Reader", "tidal_resource_model_choice=0", "", "" }, + { SSC_OUTPUT, SSC_STRING, "bathymetry", "Bathymetry", "", "", "Weather Reader", "tidal_resource_model_choice=0", "", "" }, + { SSC_OUTPUT, SSC_STRING, "sea_bed", "Sea bed", "", "", "Weather Reader", "tidal_resource_model_choice=0", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "tz", "Time zone", "", "", "Weather Reader", "", "", "" }, + { SSC_OUTPUT, SSC_STRING, "data_source", "Data source", "", "", "Weather Reader", "", "", "" }, + { SSC_OUTPUT, SSC_STRING, "notes", "Notes", "", "", "Weather Reader", "", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "location_id", "Location ID", "", "", "Weather Reader", "tidal_resource_model_choice=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "distance_to_shore_file", "Distance to shore", "m", "", "Weather Reader", "tidal_resource_model_choice=1", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "water_depth_file", "Water depth", "m", "", "Weather Reader", "tidal_resource_model_choice=1", "", "" }, + + //timestamps + + { SSC_OUTPUT, SSC_ARRAY, "year", "Year", "yr", "", "Weather Reader", "tidal_resource_model_choice=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "month", "Month", "mn", "1-12", "Weather Reader", "tidal_resource_model_choice=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "day", "Day", "dy", "1-365", "Weather Reader", "tidal_resource_model_choice=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "hour", "Hour", "hr", "0-23", "Weather Reader", "tidal_resource_model_choice=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "minute", "Minute", "min", "0-59", "Weather Reader", "tidal_resource_model_choice=1", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "tidal_velocity", "Tidal velocity", "m/s", "", "Weather Reader", "?", "", "" }, + +// weather data records + // { SSC_OUTPUT, SSC_ARRAY, "time_check", "Time check", "", "", "Weather Reader", "?", "", "" }, + // { SSC_OUTPUT, SSC_ARRAY, "month", "Month", "", "", "Weather Reader", "?", "", "" }, + + { SSC_OUTPUT, SSC_ARRAY, "significant_wave_height", "Wave height time series data", "m", "", "Weather Reader", "?", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "number_records", "Number of records in wave time series", "", "", "Weather Reader", "?", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "number_hours", "Number of hours in wave time series", "", "", "Weather Reader", "?", "", "" }, + +var_info_invalid }; + + +class cm_tidal_file_reader : public compute_module +{ +public: + + cm_tidal_file_reader() + { + add_var_info(_cm_tidal_file_reader); + } + + void exec() + { + + std::string file; + if (is_assigned("tidal_resource_filename") && as_integer("tidal_resource_model_choice")==1) + { + file = as_string("tidal_resource_filename"); + } + else + { + throw exec_error("tidal_file_reader", "Model choice and tidal resource file do not match"); + } + if (file.empty()) + { + throw exec_error("tidal_file_reader", "File name missing."); + } + + + std::string buf, buf1; + std::ifstream ifs(file); + + if (!ifs.is_open()) + { + throw exec_error("tidal_file_reader", "could not open file for reading: " + file); + } + + std::vector values; + std::vector value_0; + std::vector value_1; + // header if not use_specific_wf_file + if (as_integer("use_specific_wf_tidal") == 0) + { + getline(ifs, buf); + getline(ifs, buf1); + + // header name value pairs + std::vector keys = split(buf); + values = split(buf1); + int ncols = (int)keys.size(); + int ncols1 = (int)values.size(); + //Do we need to require all + if (ncols != ncols1 || ncols < 13) + { + throw exec_error("tidal_file_reader", "Number of header column labels does not match number of values. There are " + std::to_string(ncols) + "keys and " + std::to_string(ncols1) + "values."); + } + if (as_integer("tidal_resource_model_choice") == 0) { + assign("name", var_data(values[0])); + assign("city", var_data(values[1])); + assign("state", var_data(values[2])); + assign("country", var_data(values[3])); + // lat with S is negative + ssc_number_t dlat = std::numeric_limits::quiet_NaN(); + std::vector slat = split(values[4], ' '); + if (slat.size() > 0) + { + dlat = std::stod(slat[0]); + if (slat.size() > 1) + { + if (slat[1] == "S") dlat = 0.0 - dlat; + } + } + assign("lat", var_data(dlat)); + // lon with W is negative + ssc_number_t dlon = std::numeric_limits::quiet_NaN(); + std::vector slon = split(values[5], ' '); + if (slon.size() > 0) + { + dlon = std::stod(slon[0]); + if (slon.size() > 1) + { + if (slon[1] == "W") dlon = 0.0 - dlon; + } + } + assign("lon", var_data(dlon)); + assign("nearby_buoy_number", var_data(values[6])); + assign("average_power_flux", var_data(std::stod(values[7]))); + assign("bathymetry", var_data(values[8])); + assign("sea_bed", var_data(values[9])); + assign("tz", var_data(std::stod(values[10]))); + assign("data_source", var_data(values[11])); + assign("notes", var_data(values[12])); + } + else { + assign("location_id", var_data(std::stod(values[1]))); + assign("distance_to_shore_file", var_data(std::stod(values[7]))); + assign("water_depth_file", var_data(std::stod(values[18]))); + assign("lat", var_data(std::stod(values[3]))); + assign("lon", var_data(std::stod(values[4]))); + assign("tz", var_data(std::stod(values[6]))); + assign("data_source", var_data(values[0])); + assign("notes", var_data(values[19])); + + } + } + + if (as_integer("tidal_resource_model_choice") == 1) + { + size_t numberRecords = 0; + double numberRecords_mat = 0; + int year_index = -1, month_index = -1, day_index = -1, hour_index = -1, minute_index = -1, period_index = -1, height_index = -1; + int vel_index = -1; + getline(ifs, buf); //Skip past column labels for record counting + while (getline(ifs, buf)) { + numberRecords++; + numberRecords_mat++; + } + //if (numberRecords < 2920) throw exec_error("tidal_file_reader", "Number of records in the wave file must = 2920 (8760 h / 3 h interval)"); + assign("number_records", (int)numberRecords); + // rewind the file and reposition right after the header information + ifs.clear(); + ifs.seekg(0); + for (size_t i = 0; i < 3; i++) + getline(ifs, buf); + if (ifs.eof()) + { + throw exec_error("tidal_file_reader", "Could not read column names"); + } + + auto cols = split(buf); + int num_cols = (int)cols.size(); + for (int i = 0; i < num_cols; i++) + { + const std::string col_name = trimboth(cols[i]); + if (name.length() > 0) + { + std::string lowname = util::lower_case(col_name); + + if (lowname == "yr" || lowname == "year") year_index = i; + else if (lowname == "mo" || lowname == "month") month_index = i; + else if (lowname == "day") day_index = i; + else if (lowname == "hour" || lowname == "hr") hour_index = i; + else if (lowname == "min" || lowname == "minute") minute_index = i; + //else if (lowname == "wave height" || lowname == "wave heights" || lowname == "heights" || lowname == "height" || lowname == "hs" || lowname == "significant wave height") height_index = i; + //else if (lowname == "wave period" || lowname == "wave periods" || lowname == "energy period" || lowname == "energy periods" || lowname == "tp") period_index = i; + else if (lowname == "velocity" || lowname == "speed") vel_index = i; + } + } + if (year_index == -1 || month_index == -1 || day_index == -1 || hour_index == -1 || minute_index == -1 || + vel_index == -1) + throw exec_error("tidal_file_reader", "Data values could not be identified from column headings. Please check for year, month, day, hour, minute, wave period, and wave height columns headings"); + ssc_number_t hour0 = 0, hour1 = 0, hourdiff = 0; + ssc_number_t ts_significant_wave_height; + ssc_number_t ts_energy_period; + size_t ncols = 22; + size_t nrows = 21; + size_t sig_wave_height_index = 0; + size_t energy_period_index = 0; + ssc_number_t* p_year = allocate("year", numberRecords); + ssc_number_t* p_day = allocate("day", numberRecords); + ssc_number_t* p_hour = allocate("hour", numberRecords); + ssc_number_t* p_minute = allocate("minute", numberRecords); + ssc_number_t* mat = allocate("wave_resource_matrix", nrows, ncols); + + for (size_t j = 0; j < 21; j++) { + mat[j * ncols] = (0.25 + (j-1) * 0.5); + } + for (size_t m = 0; m < 22; m++) { + mat[m] = m - 0.5; + } + ssc_number_t* month = allocate("month", numberRecords); + std::vector timecheck(numberRecords); + timecheck[0] = 0; + //ssc_number_t* wave_heights = allocate("significant_wave_height", numberRecords); + //ssc_number_t* wave_periods = allocate("energy_period", numberRecords); + ssc_number_t* tidal_velocity = allocate("tidal_velocity", numberRecords); + for (size_t r = 0; r < numberRecords; r++) { + getline(ifs, buf); + values.clear(); + values = split(buf); + + if (r == 0) { + //value_0 = split(buf); + hour0 = (ssc_number_t)std::stod(values[hour_index]); + } + if (r == 1) { + //value_1 = split(buf); + hour1 = (ssc_number_t)std::stod(values[hour_index]); + hourdiff = hour1 - hour0; + } + month[r] = (ssc_number_t)std::stod(values[month_index]); + timecheck[r] = (ssc_number_t)std::stod(values[hour_index]); + if (r > 0) { + if (timecheck[r] - timecheck[r - 1] != hourdiff && timecheck[r] != 0) { + throw exec_error("tidal_file_reader", "Time steps are nonuniform"); + } + } + p_year[r] = (ssc_number_t)std::stod(values[year_index]); + p_hour[r] = (ssc_number_t)std::stod(values[hour_index]); + p_day[r] = (ssc_number_t)std::stod(values[day_index]); + p_minute[r] = (ssc_number_t)std::stod(values[minute_index]); + //wave_heights[r] = (ssc_number_t)std::stod(values[height_index]); + //wave_periods[r] = (ssc_number_t)std::stod(values[period_index]); + tidal_velocity[r] = (ssc_number_t)std::stod(values[vel_index]); + + //Make JPD from time series data + /* + ts_significant_wave_height = wave_heights[r]; + ts_energy_period = wave_periods[r]; + for (size_t j = 0; j < 21; j++) { + if (std::abs(ts_significant_wave_height - (0.25 + (j - 1) * 0.5)) <= 0.25 && j != 0) { + sig_wave_height_index = j; + break; + } + } + for (size_t m = 0; m < 22; m++) { + if (std::abs(ts_energy_period - (0.5 + (m - 1))) <= 0.5 && m != 0) { + energy_period_index = m; + break; + } + } + //Add percentage point to resource matrix for matcing wave height and energy period bins + double mat_incr = 100 / numberRecords_mat; + mat[sig_wave_height_index * ncols + energy_period_index] = mat[sig_wave_height_index * ncols + energy_period_index] + mat_incr; //1/numberRecords * 100 to make a percentage at each time step + */ + } + //Set decimals to 2 places in resource matrix for easier reading in output + /* + for (size_t r2 = 0; r2 < 21; r2++) { + for (size_t c2 = 0; c2 < 22; c2++) { + if (r2 != 0 && c2 != 0) mat[r2 * 22 + c2] = round(mat[r2 * 22 + c2] * 100) / 100; + } + }*/ + double test_value = mat[28]; + double test_value2 = mat[187]; + mat[0] = 0; + assign("number_hours", int(numberRecords * hourdiff)); + } + else if (as_integer("tidal_resource_model_choice") == 0) { + ssc_number_t* mat = allocate("wave_resource_matrix", 21, 22); + for (size_t r = 0; r < 21; r++) + { + getline(ifs, buf); + values.clear(); + values = split(buf); + if (values.size() != 22) + { + throw exec_error("tidal_file_reader", "Wave period columns must span 0.5s to 20.5s with increments of 1s. Incorrect number of wave period (s) columns: " + std::to_string(values.size())); + } + for (size_t c = 0; c < 22; c++) + { + if (r == 0 && c == 0) + mat[r * 22 + c] = 0.0; + else + mat[r * 22 + c] = std::stod(values[c]); + } + } + + } + else { + throw exec_error("tidal_file_reader", "Resource data type needs to be defined "); + } + + return; + } +}; + +DEFINE_MODULE_ENTRY(tidal_file_reader, "SAM Tidal Resource File Reader", 1) diff --git a/ssc/sscapi.cpp b/ssc/sscapi.cpp index ff4d307f0..ea6bc9dd5 100644 --- a/ssc/sscapi.cpp +++ b/ssc/sscapi.cpp @@ -158,6 +158,7 @@ extern module_entry_info cm_entry_mhk_wave, cm_entry_mhk_costs, cm_entry_wave_file_reader, + cm_entry_tidal_file_reader, cm_entry_grid, cm_entry_battery_stateful, cm_entry_csp_subcomponent, @@ -260,6 +261,7 @@ static module_entry_info *module_table[] = { &cm_entry_mhk_wave, &cm_entry_mhk_costs, &cm_entry_wave_file_reader, + &cm_entry_tidal_file_reader, &cm_entry_grid, &cm_entry_battery_stateful, &cm_entry_csp_subcomponent,