diff --git a/src/cosmology/tabulated_dynamicalDE_EoS.cpp b/src/cosmology/tabulated_dynamicalDE_EoS.cpp index ef7c585d4..ca3a9255f 100644 --- a/src/cosmology/tabulated_dynamicalDE_EoS.cpp +++ b/src/cosmology/tabulated_dynamicalDE_EoS.cpp @@ -78,56 +78,67 @@ void TabulatedDynamicalDarkEnergyEoS::Set_DynamicalDE_Density() } } -void TabulatedDynamicalDarkEnergyEoS::Setup_DynamicalDE_EquationOfState_(const std::string& path) +static int count_lines(std::istream& in) { - chprintf("Loading wDE info... \n"); + int line_count = 0; + std::string line; + while (std::getline(in, line)) line_count++; + in.clear(); // <- we need to clear the failure state set by final std::getline call + in.seekg(0); // <- rewind the position of in + return line_count; +} + +void TabulatedDynamicalDarkEnergyEoS::Setup_DynamicalDE_EquationOfState_(std::istream& in, const std::string& path, + bool silent) +{ + if (not silent) chprintf("Loading wDE info... \n"); - std::fstream in(path); + int n_lines = count_lines(in); std::string line; std::vector> v; - int i = 0; - if (in.is_open()) { - while (std::getline(in, line)) { - if (line.find('#') == 0) continue; - - float value; - std::stringstream ss(line); - v.emplace_back(); - - while (ss >> value) { - v[i].push_back(value); - } - i += 1; + int lineno = 0; + while (std::getline(in, line)) { + lineno++; // <- increment the line number (it is 1-indexed) + if (line.find('#') == 0) continue; + if (line.empty()) { + if (lineno == n_lines) continue; // <- allow empty final line + CHOLLA_ERROR("%s:%d is empty", path.c_str(), lineno); } - in.close(); - } else { - chprintf(" Error: Unable to open DE equation of state file: %s\n", path.c_str()); - exit(1); + + float value; + std::stringstream ss(line); + std::vector& latest_pack = v.emplace_back(); + + while (ss >> value) { + latest_pack.push_back(value); + } + CHOLLA_ASSERT(latest_pack.size() == 2, "%s:%d doesn't specify 2 elements", path.c_str(), lineno); } - int n_lines = i; + int n_entries = v.size(); + CHOLLA_ASSERT(n_entries > 0, "%s doesn't contain any data", path.c_str()); - for (i = 0; i < n_lines; i++) { + for (int i = 0; i < n_entries; i++) { dynamicalDE_table_z.push_back(v[i][0]); dynamicalDE_table_w.push_back(v[i][1]); } - for (i = 0; i < n_lines - 1; i++) { + for (int i = 0; i < n_entries - 1; i++) { if (dynamicalDE_table_z[i] > dynamicalDE_table_z[i + 1]) { - chprintf( + CHOLLA_ERROR( " ERROR: equation of state must be ordered such that redshift is increasing " "as the rows increase in the file\n", path.c_str()); - exit(2); } } - chprintf(" Loaded DE equation of state file : \n"); - chprintf(" N redshift values: %d \n", dynamicalDE_table_z.size()); - chprintf(" z_min = %f z_max = %f \n", dynamicalDE_table_z.front(), dynamicalDE_table_z.back()); - chprintf(" w(z_min) = %f w(z_max) = %f \n", dynamicalDE_table_w.front(), dynamicalDE_table_w.back()); + if (not silent) { + chprintf(" Loaded DE equation of state file : \n"); + chprintf(" N redshift values: %d \n", dynamicalDE_table_z.size()); + chprintf(" z_min = %f z_max = %f \n", dynamicalDE_table_z.front(), dynamicalDE_table_z.back()); + chprintf(" w(z_min) = %f w(z_max) = %f \n", dynamicalDE_table_w.front(), dynamicalDE_table_w.back()); + } if (dynamicalDE_table_z[0] != 0.) { - chprintf("We require z_min = 0 so that w(z=0) is well defined \n"); - exit(1); + CHOLLA_ERROR("We require z_min = 0 so that w(z=0) is well defined \n"); } } diff --git a/src/cosmology/tabulated_dynamicalDE_EoS.h b/src/cosmology/tabulated_dynamicalDE_EoS.h index 76e7912ea..5caf6dcde 100644 --- a/src/cosmology/tabulated_dynamicalDE_EoS.h +++ b/src/cosmology/tabulated_dynamicalDE_EoS.h @@ -4,9 +4,13 @@ #pragma once +#include +#include +#include #include #include "../global/global.h" +#include "../utils/error_handling.h" class TabulatedDynamicalDarkEnergyEoS { @@ -18,18 +22,32 @@ class TabulatedDynamicalDarkEnergyEoS std::vector dynamicalDE_table_density; /*! Load redshift and dark energy equation of state table z, wDE(z), only called once to setup dynamical DE case */ - void Setup_DynamicalDE_EquationOfState_(const std::string& path); + void Setup_DynamicalDE_EquationOfState_(std::istream& in, const std::string& fname, bool silent); /*! Calculate dark energy density normalized to z=0, populate dynamicalDE_table_density */ void Set_DynamicalDE_Density(); + void Setup_Full(std::istream& in, const std::string& fname) {} + public: /*! Interpolate dynamicalDE_table_density to find rhoDE(z) / rhoDE(z=0) at z=1/a - 1 */ Real Get_DynamicalDE_Density_from_a(Real a); - explicit TabulatedDynamicalDarkEnergyEoS(const std::string& path) + /*! Construct a new instance + * + * By default, when \p f is a ``nulllptr, this function tries to open the file named + * \p path. Otherwise, this function treats \p f as if it's a newly openned stream + * associated with \p path (this secondary behavior is useful for testing purposes). + */ + explicit TabulatedDynamicalDarkEnergyEoS(const std::string& path, std::istream* f = nullptr, bool silent = false) { - Setup_DynamicalDE_EquationOfState_(path); + std::fstream tmp; + if (f == nullptr) { + tmp.open(path, std::ios_base::in); + CHOLLA_ASSERT(tmp.is_open(), "Unable to open DE equation of state file: %s\n", path.c_str()); + f = dynamic_cast(&tmp); + } + Setup_DynamicalDE_EquationOfState_(*f, path, silent); Set_DynamicalDE_Density(); } }; diff --git a/src/cosmology/tabulated_dynamicalDE_EoS_tests.cpp b/src/cosmology/tabulated_dynamicalDE_EoS_tests.cpp new file mode 100644 index 000000000..060f1635a --- /dev/null +++ b/src/cosmology/tabulated_dynamicalDE_EoS_tests.cpp @@ -0,0 +1,154 @@ +/*! \file + * Holds tests for \ref TabulatedDynamicalDarkEnergyEoS + */ +#include +#include + +// External Includes +#include // Include GoogleTest and related libraries/headers + +#include "tabulated_dynamicalDE_EoS.h" + +// this is the path we use when we construct a TabulatedDynamicalDarkEnergyEoS directly +// from a string +const char* string_file_path_("dummy-file-path"); + +/*! A helper function that constructs a \ref TabulatedDynamicalDarkEnergyEoS instance + * by treating \p contents as the contents of a file + */ +TabulatedDynamicalDarkEnergyEoS construct_from_string_(const std::string& contents) +{ + std::istringstream file_contents(contents); + // we pass silent=true to suppress informational messages summarizing properties of + // the file when we read it + TabulatedDynamicalDarkEnergyEoS dynamical_eos(string_file_path_, dynamic_cast(&file_contents), true); + return dynamical_eos; +} + +/*! Encapsulates a sample input file that will be used to read in + * \ref TabulatedDynamicalDarkEnergyEOS + */ +struct FileVariation { + std::string description; + std::string content; +}; + +// teach GoogleTest how to print File Variation +void PrintTo(const FileVariation& fv, std::ostream* os) { *os << fv.description; } + +// ------------------------------------------------------------------------------------- + +// we are going to run a simple test case where we try to parse variants of the +// following string +const std::string GOOD_CONTENTS_ = R"LITERAL(# z, w +0.000000000000000000e+00 -9.767616499273475972e-01 +6.938631476027579126e-03 -9.769941793369097960e-01 +# this is a random meaningless comment! +1.392540755881421788e-02 -9.772263520514015145e-01)LITERAL"; + +class tALLTabulatedDynamicalDarkEnergyEoS : public testing::TestWithParam +{ +}; + +TEST_P(tALLTabulatedDynamicalDarkEnergyEoS, SimpleOpen) +{ + TabulatedDynamicalDarkEnergyEoS dynamical_eos = construct_from_string_(GetParam().content); + + // the value at z=0 is always normalized to be 1.0 + EXPECT_EQ(dynamical_eos.Get_DynamicalDE_Density_from_a(1.0), 1.0); + + // check that the value before the earliest redshfift are all the same + EXPECT_EQ(dynamical_eos.Get_DynamicalDE_Density_from_a(0.1), dynamical_eos.Get_DynamicalDE_Density_from_a(0.01)); + + // it would be great to actually make some strong checks rather than just checking + // the bounds (but that would require a more detailed understanding of the + // calculation than I currently have) +} + +const FileVariation valid_variants_[3] = { + {"NoTerminalNewline", GOOD_CONTENTS_}, + {"WithTerminalNewline", GOOD_CONTENTS_ + '\n'}, + {"EmptyFinalLine", GOOD_CONTENTS_ + "\n\n"}, +}; + +INSTANTIATE_TEST_SUITE_P( + /* 1st arg intentionally empty */, tALLTabulatedDynamicalDarkEnergyEoS, testing::ValuesIn(valid_variants_), + testing::PrintToStringParamName()); + +// ------------------------------------------------------------------------------------- + +// if we ever decide that DeathTests are too expensive, we can transition to using exceptions +// in the following tests + +TEST(tALLTabulatedDynamicalDarkEnergyEoSDeathTest, InvalidPath) +{ + std::string path = "not/a/real/path.txt"; + ASSERT_DEATH({ TabulatedDynamicalDarkEnergyEoS dyanmical_eos(path); }, + "Unable to open DE equation of state file: " + path); +} + +TEST(tALLTabulatedDynamicalDarkEnergyEoSDeathTest, MissingRedshift0) +{ + const std::string contents = R"LITERAL(# z, w + 6.938631476027579126e-03 -9.769941793369097960e-01 + 1.392540755881421788e-02 -9.772263520514015145e-01)LITERAL"; + + ASSERT_DEATH({ TabulatedDynamicalDarkEnergyEoS dyanmical_eos = construct_from_string_(contents); }, + "We require z_min = 0 so that w\\(z=0\\) is well defined"); +} + +TEST(tALLTabulatedDynamicalDarkEnergyEoSDeathTest, OutOfOrder) +{ + const std::string contents = R"LITERAL(# z, w +0.000000000000000000e+00 -9.767616499273475972e-01 +1.392540755881421788e-02 -9.772263520514015145e-01 +6.938631476027579126e-03 -9.769941793369097960e-01)LITERAL"; + + ASSERT_DEATH( + { TabulatedDynamicalDarkEnergyEoS dyanmical_eos = construct_from_string_(contents); }, + "ERROR: equation of state must be ordered such that redshift is increasing as the rows increase in the file"); +} + +TEST(tALLTabulatedDynamicalDarkEnergyEoSDeathTest, SingleElemOnLine2) +{ + const std::string contents = R"LITERAL(# z, w +0.000000000000000000e+00 +1.392540755881421788e-02 -9.772263520514015145e-01)LITERAL"; + + ASSERT_DEATH({ TabulatedDynamicalDarkEnergyEoS dyanmical_eos = construct_from_string_(contents); }, + string_file_path_ + std::string(":2 doesn't specify 2 elements")); +} + +TEST(tALLTabulatedDynamicalDarkEnergyEoSDeathTest, ThreeElemsOnLine2) +{ + const std::string contents = R"LITERAL(# z, w +0.0 -0.97 342 +1.392540755881421788e-02 -9.772263520514015145e-01)LITERAL"; + + ASSERT_DEATH({ TabulatedDynamicalDarkEnergyEoS dyanmical_eos = construct_from_string_(contents); }, + string_file_path_ + std::string(":2 doesn't specify 2 elements")); +} + +// ------------------------------------------------------------------------------------- + +// define parametrized tests where we try to read files without real contents + +class tALLTabulatedDynamicalDarkEnergyEoSNoContentsDeathTest : public testing::TestWithParam +{ +}; + +TEST_P(tALLTabulatedDynamicalDarkEnergyEoSNoContentsDeathTest, Simple) +{ + ASSERT_DEATH({ TabulatedDynamicalDarkEnergyEoS dynamical_eos = construct_from_string_(GetParam().content); }, + string_file_path_ + std::string(" doesn't contain any data")); +} + +const FileVariation invalid_variants_[] = { + {"Empty", ""}, + {"SingleBlankLine", "\n"}, + {"SingleComment", "# this is a comment!"}, +}; + +INSTANTIATE_TEST_SUITE_P( + /* 1st arg intentionally empty */, tALLTabulatedDynamicalDarkEnergyEoSNoContentsDeathTest, + testing::ValuesIn(invalid_variants_), testing::PrintToStringParamName()); \ No newline at end of file