From bc4b8ad326ff26d11c2609008668be7b287a09cf Mon Sep 17 00:00:00 2001 From: Adam Boesky Date: Tue, 2 Jan 2024 02:32:34 +0900 Subject: [PATCH] Implement configs (#18) * made progress on octree class; working on building the octree class * finished implementing octree class and members/functions; need to fix segmentation fault in testing * update testing and previous implementation; debug octree implementation and makefile configuration * add inclusion of to octree files * add tests for body implementation; slightly edit body.cpp implementation * Implement config file functionality. * Fix everything * Add yaml-cpp install to workflows * Debug --------- Co-authored-by: jdinovi --- .github/workflows/coverage.yml | 5 + .github/workflows/test.yml | 5 + Makefile | 5 +- configs/default.yaml | 29 ++++++ include/environment.h | 53 +++++----- include/octree.h | 40 ++++++++ include/statistics.h | 18 ++++ src/body.cpp | 2 +- src/environment.cpp | 159 +++++++++++++++++++++++++++-- src/octree.cpp | 176 +++++++++++++++++++++++++++++++++ src/simulation.cpp | 24 +---- src/statistics.cpp | 4 + test/test_body.cpp | 67 +++++++++++++ test/test_environment.cpp | 88 +++++++++++++++-- test/test_statistics.cpp | 66 +++++++++++++ 15 files changed, 676 insertions(+), 65 deletions(-) create mode 100644 configs/default.yaml create mode 100644 include/octree.h create mode 100644 include/statistics.h create mode 100644 src/octree.cpp create mode 100644 src/statistics.cpp create mode 100644 test/test_body.cpp create mode 100644 test/test_statistics.cpp diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 0c1cff5..a283431 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -11,6 +11,11 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 + + - name: Install requirements + run: | + sudo apt-get update -q + sudo apt-get install libyaml-cpp-dev -y - name: Evaluate coverage run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d0495e6..2d22f7c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 + - name: Install requirements + run: | + sudo apt-get update -q + sudo apt-get install libyaml-cpp-dev -y + - name: Run tests run: | result=$(make test) diff --git a/Makefile b/Makefile index 1b6a71e..b93894e 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ CXX = g++ CXXFLAGS = -std=c++17 -Wall --coverage +LDFLAGS = -lyaml-cpp SRC_DIR = src INC_DIR = include OBJ_DIR = obj @@ -22,7 +23,7 @@ INC_DIRS = -I $(INC_DIR) # Linking step for src files $(BIN_DIR)/$(TARGET): $(OBJS) - $(CXX) $(CXXFLAGS) -o $@ $^ + $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) # Compiling step for src files $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp @@ -30,7 +31,7 @@ $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp # Linking step for test files $(BIN_DIR)/$(TEST_TARGET): $(filter-out $(OBJ_DIR)/simulation.o, $(OBJS)) $(TEST_OBJS) - $(CXX) $(CXXFLAGS) -o $@ $^ + $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) # Compiling step for test files $(TEST_OBJ_DIR)/%.o: $(TEST_DIR)/%.cpp diff --git a/configs/default.yaml b/configs/default.yaml new file mode 100644 index 0000000..f8bb1a4 --- /dev/null +++ b/configs/default.yaml @@ -0,0 +1,29 @@ +global: + nParticles: 1000 +mass: + dist: constant + val: 100000000 +x: + dist: uniform + min: 0 + max: 4 +y: + dist: uniform + min: -2 + max: 4 +z: + dist: uniform + min: 1 + max: 10 +vx: + dist: uniform + min: 0 + max: 4 +vy: + dist: normal + mu: -2 + sigma: 4 +vz: + dist: uniform + min: -10 + max: 10 diff --git a/include/environment.h b/include/environment.h index 9bfcb79..6816bdc 100644 --- a/include/environment.h +++ b/include/environment.h @@ -1,37 +1,40 @@ #pragma once #include #include -#include +#include +#include // Include for smart pointers +#include "particle.h" -#include "./particle.h" template -class GravitationalEnvironment{ - - public: - // Constructors - // GravitationalEnvironment(const std::vector>& particlePtrs, const bool log); - GravitationalEnvironment(const std::vector>& particlePtrs, const bool log, std::string logFilePrefix="run"); +class GravitationalEnvironment { +public: + // Constructors + GravitationalEnvironment(const std::vector>& particlePtrs, const bool log, std::string logFilePrefix = "run"); + GravitationalEnvironment(const std::string configFileName, const bool log, std::string logFilePrefix = "run"); - // Define member functions - std::vector> getForces(const double timestep); - void updateAll(const std::vector>& forces, const double timestep); - void step(const double timestep); - void simulate(const double duration, const double timestep); - std::string getStepLog() const; - std::string getLogHeader() const; - void reset(); + // Define member functions + void loadParticlesFromConfig(std::string configFileName); + std::vector> getForces(const double timestep); + void updateAll(const std::vector>& forces, const double timestep); + void step(const double timestep); + void simulate(const double duration, const double timestep); + std::string getStepLog() const; + std::string getLogHeader() const; + void reset(); - // Instantiation of the physical members - std::vector> particlePtrs; // Changed to std::shared_ptr - bool log; - double time; - int nParticles; - std::string logFileName; + // Instantiation of the physical members + std::vector> particlePtrs; // Changed to std::shared_ptr + bool log; + double time; + int nParticles; // This should be calculated in the constructor. + std::string logFileName; + +private: + // Instantiation of the physical members + std::string logFilePrefix; }; // Helper functions int getLargestLabelNumber(const std::vector& filenames, const std::string logFilePrefix); - - -typedef GravitationalEnvironment ParticleEnvironment; +std::map> loadConfig(const std::string& fileName); diff --git a/include/octree.h b/include/octree.h new file mode 100644 index 0000000..86bb29c --- /dev/null +++ b/include/octree.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include "body.h" + +template +class Octree { + public: + + // Octree constructor + Octree(std::array& xCoords, std::array& yCoords, std::array& zCoords); + + // Member functions + void clear(); + void insert(std::shared_ptr objPtr); + void build(std::vector>& objPtrs); + + // Members + std::vector> objPtrs; + std::array centerOfMass; + float* totalMass; + + // Dimensions of the current octant + std::array xCoords; + std::array yCoords; + std::array zCoords; + + // Octree children --> 0-7 based on 2D convention in postive z, and then 2D convention in negative z, observing from above + Octree* child0; + Octree* child1; + Octree* child2; + Octree* child3; + Octree* child4; + Octree* child5; + Octree* child6; + Octree* child7; + +}; diff --git a/include/statistics.h b/include/statistics.h new file mode 100644 index 0000000..b9a5591 --- /dev/null +++ b/include/statistics.h @@ -0,0 +1,18 @@ +#pragma once +#include +#include + +// Assuming you have the random device and generator declared somewhere +extern std::random_device rd; +extern std::mt19937 GENERATOR; + +// Distribution template used to sample from random distributions +template +std::vector sampleFromDistribution(size_t n, Distribution& distribution) { + // Generate samples + std::vector samples(n); + for (size_t i = 0; i < n; ++i) { + samples[i] = distribution(GENERATOR); + } + return samples; +} diff --git a/src/body.cpp b/src/body.cpp index 67eeeab..d594ba7 100644 --- a/src/body.cpp +++ b/src/body.cpp @@ -12,4 +12,4 @@ float DENSITY = 4E3; // Constructor definition Body::Body(const std::array* initial_position, const std::array* initial_velocity, double mass, float radius) - : Particle(initial_position, initial_velocity, mass), radius(radius == 0 ? pow(3 * mass / (4 * M_PI * DENSITY), 1/3) : radius) {}; + : Particle(initial_position, initial_velocity, mass), radius((radius == 0) ? pow(3 * mass / (4 * M_PI * DENSITY), 1./3.) : radius) {}; diff --git a/src/environment.cpp b/src/environment.cpp index 6243e34..bc132c1 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -1,17 +1,72 @@ #include +#include #include #include +#include +#include #include #include #include +#include #include "../include/environment.h" #include "../include/body.h" #include "../include/particle.h" +#include "../include/statistics.h" namespace fs = std::filesystem; double G = 6.6743e-11; +std::string REPOPATH = std::string(std::getenv("HOOTSIM_PATH")); + + +// Load config file to a hashmap. These files are of the form (key: param, value: the distribution parameters). +std::map> loadConfig(const std::string& fileName) { + + std::map> configMap; // a 2D hashmap of the configuration + std::string fullPath = std::string(REPOPATH) + "/configs/" + fileName; // the full path to the configfile + + try { + + // Get file and parse like a map + YAML::Node config = YAML::LoadFile(fullPath); + if (config.IsMap()) { + + // Fill in 2D hashmap + for (const auto& element : config) { + std::string outerKey = element.first.as(); + const YAML::Node& innerNode = element.second; + + if (innerNode.IsMap()) { // If there is a 2nd dim for the specific element + std::map innerMap; + for (const auto& innerElement : innerNode) { + std::string innerKey = innerElement.first.as(); + std::string innerValue = innerElement.second.as(); + innerMap[innerKey] = innerValue; + } + configMap[outerKey] = innerMap; + } + } + } + } catch (const YAML::BadFile& e) { // fail to open + std::cerr << "Failed to open YAML file: " << fullPath << std::endl; + throw; + } catch (const YAML::Exception& e) { // issue parsing + std::cerr << "YAML parsing error: " << e.what() << std::endl; + throw; + } + + // Check if the map contains the key "dist" is assigned for each parameter + for (auto const& [property, propertyDist] : configMap) { + if (property != "global") { + if (propertyDist.find("dist") == propertyDist.end()) { + throw std::runtime_error("Key 'dist' not found in configuration for property " + property + "."); + } + } + } + + return configMap; +} // Get the largest number from a vector of filenames @@ -52,22 +107,106 @@ GravitationalEnvironment::GravitationalEnvironment(const std::vector lastLogFileNames; const char* repoPath = std::getenv("HOOTSIM_PATH"); - std::string dataPath = std::string(repoPath) + "/data"; + std::string dataPath = repoPath == nullptr ? "./data" : std::string(repoPath) + "/data"; if (!fs::exists(dataPath)) { fs::create_directory(dataPath); std::cout << "data directory created successfully.\n"; } else { std::cout << "data directory already exists.\n"; } - for (const auto& entry : fs::directory_iterator(dataPath)) { - if (fs::is_regular_file(entry.status())) { - lastLogFileNames.push_back(entry.path().filename().string()); + for (const auto& entry : fs::directory_iterator(dataPath)) { + if (fs::is_regular_file(entry.status())) { + lastLogFileNames.push_back(entry.path().filename().string()); + } } + + // Get the largest log file number and create new log file + int lastLogNum = getLargestLabelNumber(lastLogFileNames, logFilePrefix); + logFileName = dataPath + "/" + logFilePrefix + std::to_string(lastLogNum + 1) + ".csv"; } + } +template +GravitationalEnvironment::GravitationalEnvironment(const std::string configFileName, const bool log, std::string logFilePrefix) + : log(log), time(0) { + + // Get particles + loadParticlesFromConfig(configFileName); + + // Declare the number of particles + nParticles = particlePtrs.size(); - // Get the largest log file number and create new log file - int lastLogNum = getLargestLabelNumber(lastLogFileNames, logFilePrefix); - logFileName = dataPath + "/" + logFilePrefix + std::to_string(lastLogNum + 1) + ".csv"; + // Create a log file if we want one + if (log == true) { + + // Get a vector of the filenames in the data directory + std::vector lastLogFileNames; + std::string dataPath = REPOPATH + "/data"; + for (const auto& entry : fs::directory_iterator(dataPath)) { + if (fs::is_regular_file(entry.status())) { + lastLogFileNames.push_back(entry.path().filename().string()); + } + } + + // Get the largest log file number and create new log file + int lastLogNum = getLargestLabelNumber(lastLogFileNames, logFilePrefix); + logFileName = dataPath + "/" + logFilePrefix + std::to_string(lastLogNum + 1) + ".csv"; + } + } + + +// Load a full environment from the configuration file +template +void GravitationalEnvironment::loadParticlesFromConfig(const std::string configFileName) { + + // Get configuration map + std::map> configMap = loadConfig(configFileName); + + // Grab the gloabl config params for the environment + std::map globalConfigMap = configMap.at("global"); + int nParticles = std::stoi(globalConfigMap.at("nParticles")); + + // Generate distributions for each param + std::map> envParams; + + // Iterate through the configuration and sample particles according to config prescirption + for (auto const& [property, propertyDist] : configMap) { + + if (property != "global") { // not the global configuarion field + + // The type of distribution for the property + std::string distType = propertyDist.at("dist"); + + // Sample from the distribution for the property + if (distType == "constant") { // Constant dist + envParams[property] = std::vector (nParticles, std::stod(propertyDist.at("val"))); + } else if (distType == "normal") { // Normal dist + double mu = std::stod(propertyDist.at("mu")); + double sigma = std::stod(propertyDist.at("sigma")); + std::normal_distribution<> normalDist(mu, sigma); + envParams[property] = sampleFromDistribution(nParticles, normalDist); + } else if (distType == "uniform") { // Uniform dist + double min = std::stod(propertyDist.at("min")); + double max = std::stod(propertyDist.at("max")); + std::uniform_real_distribution<> uniformDist(min, max); + envParams[property] = sampleFromDistribution(nParticles, uniformDist); + } else { + throw std::invalid_argument("Property " + property + " has an invalid distribution."); + } + } + } + + // Declare particle pointer vector + std::vector> positions(nParticles); + std::vector> velocities(nParticles); + for (int i = 0; i < nParticles; i++) { + + // Populate positions and velocities + positions[i] = {envParams.at("x")[i], envParams.at("y")[i], envParams.at("z")[i]}; + velocities[i] = {envParams.at("vx")[i], envParams.at("vy")[i], envParams.at("vz")[i]}; + double mass = envParams.at("mass")[i]; + + // Create Particle instance with pointers to elements in the positions and velocities vectors + particlePtrs.push_back(std::make_shared(&positions[i], &velocities[i], mass)); } } @@ -181,7 +320,7 @@ void GravitationalEnvironment::simulate(const double duration, const double t // If logging, append to log string if (log == true) { - logStr += std::to_string(i * timestep) + ","; + logStr += std::to_string(i * timestep); logStr += getStepLog() + "\n"; } std::cout << i * timestep << ",\t" << getStepLog() << "\n"; @@ -191,6 +330,7 @@ void GravitationalEnvironment::simulate(const double duration, const double t } // end state if (log == true) { + logStr += std::to_string(nTimesteps * timestep) + ","; logStr += getStepLog() + "\n"; } std::cout << nTimesteps * timestep << ",\t" << getStepLog() << "\n"; @@ -203,7 +343,7 @@ void GravitationalEnvironment::simulate(const double duration, const double t std::cerr << "Failed to open the file: " << logFileName << std::endl; } else { logFile << logStr; - std::cout << "Successfully logged to " + logFileName; + std::cout << "Successfully logged to " + logFileName + "\n"; } logFile.close(); } @@ -212,7 +352,6 @@ void GravitationalEnvironment::simulate(const double duration, const double t template // Reset the environment void GravitationalEnvironment::reset() { - time = 0; } diff --git a/src/octree.cpp b/src/octree.cpp new file mode 100644 index 0000000..1292458 --- /dev/null +++ b/src/octree.cpp @@ -0,0 +1,176 @@ +#include "./../include/octree.h" +#include +#include + +template +Octree::Octree(std::array& xCoords, std::array& yCoords, std::array& zCoords) + : totalMass(0), xCoords(xCoords), yCoords(yCoords), zCoords(zCoords) {}; + +// Recursively set every child to null in the tree, but preserving the tree +template +void Octree::clear() { + + if (child0 != nullptr) { + child0->clear(); + child0 = nullptr; + } + + if (child1 != nullptr) { + child1->clear(); + child1 = nullptr; + } + + if (child2 != nullptr) { + child2->clear(); + child2 = nullptr; + } + + if (child3 != nullptr) { + child3->clear(); + child3 = nullptr; + } + + if (child4 != nullptr) { + child4->clear(); + child4 = nullptr; + } + + if (child5 != nullptr) { + child5->clear(); + child5 = nullptr; + } + + if (child6 != nullptr) { + child6->clear(); + child6 = nullptr; + } + + if (child7 != nullptr) { + child7->clear(); + child7 = nullptr; + } +} + +template +void Octree::insert(std::shared_ptr objPtr) { + + // Append objPtr to the vector of objects + objPtrs.push_back(objPtr); + + // Instantiate the center of mass if it doesn't exist; update it otherwise + float newTotalMass = *totalMass + objPtr->mass; + if (*totalMass == 0) { + std::copy(std::begin(objPtr->position), std::end(objPtr->position), std::begin(centerOfMass)); + } else { + for (int i = 0; i < 3; i++) { + centerOfMass[i] = (((centerOfMass[i] * (*totalMass))) + ((objPtr->position[i]) * (objPtr->mass))) / (newTotalMass); + } + } + + // Update the total mass + *totalMass = newTotalMass; + + // Midpoints of coordinates + float mX = (xCoords[0] + xCoords[1]) / 2.; + float mY = (yCoords[0] + yCoords[1]) / 2.; + float mZ = (zCoords[0] + zCoords[1]) / 2.; + + + // Check for recursive insertion --> if greater than 1 pointer, then need to recursively insert + if (objPtrs.size() > 1) { + + // Initialize flags for octant location + bool xFlag = objPtr->position[0] > mX; + bool yFlag = objPtr->position[1] > mY; + bool zFlag = objPtr->position[2] > mZ; + + std::array xCoordsNew; + std::array yCoordsNew; + std::array zCoordsNew; + + + // Get the X coordinates + if (xFlag) { + xCoordsNew[0] = mX; + xCoordsNew[1] = xCoords[1]; + } else { + xCoordsNew[0] = xCoords[0]; + xCoordsNew[1] = mX; + } + + // Get the Y coordinates + if (yFlag) { + yCoordsNew[0] = mY; + yCoordsNew[1] = yCoords[1]; + } else { + yCoordsNew[0] = yCoords[0]; + yCoordsNew[1] = mY; + } + + // Get the Z coordinates + if (zFlag) { + zCoordsNew[0] = mZ; + zCoordsNew[1] = zCoords[1]; + } else { + zCoordsNew[0] = zCoords[0]; + zCoordsNew[1] = mZ; + } + + // Instantiate a new octree with the calculated coordinates + Octree* newOctreePtr = new Octree(xCoordsNew, yCoordsNew, zCoordsNew); + + // Insert the new octree into the correct child with if statements (both location, and if child exists) + if (xFlag & yFlag & zFlag) { + if (child0 == nullptr) { + child0 = newOctreePtr; + } + child0->insert(objPtr); + } else if (!xFlag & yFlag & zFlag) { + if (child1 == nullptr) { + child1 = newOctreePtr; + } + child1->insert(objPtr); + } else if (!xFlag & !yFlag & zFlag) { + if (child2 == nullptr) { + child2 = newOctreePtr; + } + child2->insert(objPtr); + } else if (xFlag & !yFlag & zFlag) { + if (child3 == nullptr) { + child3 = newOctreePtr; + } + child3->insert(objPtr); + } else if (xFlag & yFlag & !zFlag) { + if (child4 == nullptr) { + child4 = newOctreePtr; + } + child4->insert(objPtr); + } else if (!xFlag & yFlag & !zFlag) { + if (child5 == nullptr) { + child5 = newOctreePtr; + } + child5->insert(objPtr); + } else if (!xFlag & !yFlag & !zFlag) { + if (child6 == nullptr) { + child6 = newOctreePtr; + } + child6->insert(objPtr); + } else if (xFlag & !yFlag & !zFlag) { + if (child7 == nullptr) { + child7 = newOctreePtr; + } + child7->insert(objPtr); + } + } +} + +// Build the octree +template +void Octree::build(std::vector>& objPtrs) { + for (int i = 0; i < objPtrs.size(); i++) { + this->insert(objPtrs[i]); + } +} + +template class Octree; +template class Octree; diff --git a/src/simulation.cpp b/src/simulation.cpp index 2be365a..726f814 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -8,28 +8,10 @@ #include "../include/environment.h" int main() { - + GravitationalEnvironment defaultEnv("default.yaml", true); - // // Parameters for simulation - // float timestep = 0.5; - - // Initialize particle specs - double mass = 1E9; - std::array initial_position1 = {0, 0, 0}; - std::array initial_position2 = {4, 0, 0}; - std::array initial_velocity1 = {0, 0, 0}; - std::array initial_velocity2 = {0, 0, 0}; - - - // Define the particles - auto particle1Ptr = std::make_shared(&initial_position1, &initial_velocity1, mass); - auto particle2Ptr = std::make_shared(&initial_position2, &initial_velocity2, mass); - std::vector> particles = {particle1Ptr, particle2Ptr}; - - GravitationalEnvironment env1(particles, true); - - env1.simulate(3, 0.5); + // Simulate + defaultEnv.simulate(3, 0.5); return 0; - }; diff --git a/src/statistics.cpp b/src/statistics.cpp new file mode 100644 index 0000000..4988d38 --- /dev/null +++ b/src/statistics.cpp @@ -0,0 +1,4 @@ +#include "../include/statistics.h" + +std::random_device rd; +std::mt19937 GENERATOR(rd()); diff --git a/test/test_body.cpp b/test/test_body.cpp new file mode 100644 index 0000000..81e4a9b --- /dev/null +++ b/test/test_body.cpp @@ -0,0 +1,67 @@ +#define _USE_MATH_DEFINES + +#include +#include + +#include "../include/doctest.h" +#include "../include/body.h" + +TEST_CASE("Body Initialization - WITH Radius") { + + // Initialize particle specs + double mass = 1; + std::array initial_position1 = {0, 0, 0}; + std::array initial_velocity1 = {0, 0, 0}; + float radius = 1E5; + + // Define the particle + Body body1(&initial_position1, &initial_velocity1, mass, radius); + + CHECK(body1.position[0] == 0); + CHECK(body1.position[1] == 0); + CHECK(body1.position[2] == 0); + CHECK(body1.mass == 1); + CHECK(body1.radius == 1E5); +} + +TEST_CASE("Body Initialization - WITHOUT Radius") { + + // Initialize particle specs + double mass = 1; + std::array initial_position1 = {0, 0, 0}; + std::array initial_velocity1 = {0, 0, 0}; + + // Define the particle + Body body1(&initial_position1, &initial_velocity1, mass); + + CHECK(body1.position[0] == 0); + CHECK(body1.position[1] == 0); + CHECK(body1.position[2] == 0); + CHECK(body1.mass == 1); + CHECK((body1.radius - pow(3 * mass / (4 * M_PI * 4E3), 1./3.)) < 0.000001); +} + + +TEST_CASE("Body Update") { + + // Parameters for a timestep + double timestep = 1; + + // Initialize particle specs + double mass = 1; + float radius = 1E5; + std::array initial_position1 = {0, 0, 0}; + std::array initial_velocity1 = {0, 0, 0}; + + // Define the force + std::array force = {3, 0, 0}; + + // Define the particle + Body body1(&initial_position1, &initial_velocity1, mass, radius); + + // Update particle + body1.update(&force, timestep); + + // Check the update works + CHECK(body1.position[0] == 1.5); +} \ No newline at end of file diff --git a/test/test_environment.cpp b/test/test_environment.cpp index 3b65dc1..3fd40eb 100644 --- a/test/test_environment.cpp +++ b/test/test_environment.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -14,7 +15,8 @@ double _G = 6.6743e-11; // Data path const char* repoPath = std::getenv("HOOTSIM_PATH"); -std::string dataPath = std::string(repoPath) + "/data"; +std::string dataPath = repoPath == nullptr ? "./data" : std::string(repoPath )+ "/data"; +std::string configPath = std::string(repoPath) + "/config"; // Initialize particle specs double mass = 1E10; @@ -28,11 +30,9 @@ auto particle1Ptr = std::make_shared(&initial_position1, &initial_velo auto particle2Ptr = std::make_shared(&initial_position2, &initial_velocity2, mass); std::vector> particles = {particle1Ptr, particle2Ptr}; -// Initialize an environment -GravitationalEnvironment env1(particles, true); -GravitationalEnvironment env2(particles, true, "funPrefix"); - - +// Initialize a particle environment +GravitationalEnvironment env1(particles, true); +GravitationalEnvironment env2(particles, true, "funPrefix"); @@ -50,6 +50,11 @@ TEST_CASE("Environment Initialization") { CHECK(env2.logFileName == dataPath + "/funPrefix0.csv"); } +TEST_CASE("Error Handling for Load Config") { + // Test error handling when loading an invalid configuration file + CHECK_THROWS(loadConfig("invalid_config.yaml")); +} + void test_normalCase() { std::vector filenames = {"run0.csv", "run1.csv", "run10.csv"}; CHECK(getLargestLabelNumber(filenames, "run") == 10); @@ -161,5 +166,76 @@ TEST_CASE("Reset Environment") { // Reset environment env1.reset(); + // Check that it resets CHECK(env1.time == 0); } + + +////////// CONFIG FILE TESTS ////////// +TEST_CASE("Load Config File") { + + // Get the default map + std::map> defaultConfig = loadConfig("default.yaml"); + + // Check some values + CHECK(defaultConfig["vy"]["dist"] == "normal"); + CHECK(defaultConfig["vy"]["mu"] == "-2"); + CHECK(defaultConfig["vy"]["sigma"] == "4"); + CHECK(defaultConfig["global"]["nParticles"] == "1000"); +} + +void checkDefaultEnv(GravitationalEnvironment& env) { + // Check that it all looks good... that default vals are: +// mass: +// dist: constant +// val: 100000000 +// x: +// dist: uniform +// min: 0 +// max: 4 +// y: +// dist: uniform +// min: -2 +// max: 4 +// z: +// dist: uniform +// min: 1 +// max: 10 +// vx: +// dist: uniform +// min: 0 +// max: 4 +// vy: +// dist: uniform +// min: -2 +// max: 4 +// vz: +// dist: uniform +// min: -10 +// max: 10 + bool massCheck = true; + bool xCheck = true; + bool yCheck = true; + bool zCheck = true; + for (auto pPtr : env.particlePtrs) { + massCheck &= pPtr->mass == 100000000; + xCheck &= (pPtr->position[0] <= 4 & pPtr->position[0] >= 0); + yCheck &= (pPtr->position[1] <= 4 & pPtr->position[1] >= -2); + zCheck &= (pPtr->position[2] <= 10 & pPtr->position[2] >= 1); + } + CHECK(env.nParticles == 1000); + CHECK(xCheck); + CHECK(yCheck); + CHECK(zCheck); +} +TEST_CASE("Load Particles From Config") { + + // Get a defualt environment class + GravitationalEnvironment defaultEnv("default.yaml", true); + GravitationalEnvironment defaultEnv2("default.yaml", true, "prefixyprefix"); + + // Check 'em + checkDefaultEnv(defaultEnv); + checkDefaultEnv(defaultEnv2); + CHECK(defaultEnv2.logFileName == dataPath + "/prefixyprefix0.csv"); +} diff --git a/test/test_statistics.cpp b/test/test_statistics.cpp new file mode 100644 index 0000000..8c06c97 --- /dev/null +++ b/test/test_statistics.cpp @@ -0,0 +1,66 @@ +#include +#include +#include + +#include "../include/doctest.h" +#include "../include/statistics.h" + +TEST_CASE("Constant") { + // Sample + std::vector valVec(12, std::stod("6.32")); + + // Check that all the values are equal + bool allEq = true; + for (double v : valVec) { + allEq &= (v == 6.32); + } + CHECK(allEq); + CHECK(valVec.size() == 12); +} + +TEST_CASE("Normal") { + // Sample + int n = 1000; + std::normal_distribution<> normalDist(0, 1); + std::vector valVec = sampleFromDistribution(n, normalDist); + + // Check the mean is near 0 + double mean = 0; + for (double v : valVec) { + mean += v; + } + mean /= n; + CHECK(abs(mean) < 0.1); + + // Check std is near 1 + double std = 0; + for (double v : valVec) { + std += v*v; + } + std = pow(std / n, 0.5); + CHECK(abs(std - 1) < 0.1); + CHECK(valVec.size() == n); +} + +TEST_CASE("Uniform") { + // Sample + int n = 1000; + std::uniform_real_distribution<> uniformDis(-5, 5); + std::vector valVec = sampleFromDistribution(n, uniformDis); + + // Check the mean is near 0 + double mean = 0; + for (double v : valVec) { + mean += v; + } + mean /= n; + CHECK(abs(mean) < 0.25); + + // Check they're in the sampling range + bool inRange = true; + for (double v : valVec) { + inRange &= (v <= 5 & v >= -5); + } + CHECK(inRange); + CHECK(valVec.size() == n); +}