diff --git a/.gitignore b/.gitignore index d36e03d..c28e83e 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,6 @@ build .cproject .project .settings/ -.vscode/ \ No newline at end of file + +# Vscode +.vscode/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 09df300..339d4f2 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,6 +111,16 @@ if (BUILD_SV_INSTALLER) add_subdirectory("${CMAKE_SOURCE_DIR}/Distribution") ENDIF() +# NLOHMANN JSON FOR SERIALIZATION +include(FetchContent) +FetchContent_Declare( + nlohmann_json + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG v3.11.2 # You can specify a version or use the latest tag +) +FetchContent_MakeAvailable(nlohmann_json) +target_include_directories(${PROJECT_NAME} PUBLIC ${nlohmann_json_SOURCE_DIR}/single_include) + # Unit tests and Google Test if(ENABLE_UNIT_TEST) @@ -164,6 +174,13 @@ if(ENABLE_UNIT_TEST) # handle both use cases. target_link_libraries(UnitTestsExecutable gtest gtest_main pthread) + # We need to include Nlohmann when compiling the unit test executable + # A better strategy would be to build up a list of what we're going to + # include and reuse it. + # Another option: we could simply build the library of cvOneD functionality + # and link against that both here and in the main executable. + target_include_directories(UnitTestsExecutable PRIVATE ${nlohmann_json_SOURCE_DIR}/single_include) + # It's likely we'll need to include additional directories for some # versions of the unit tests. That can be updated when/if we update # the general strategy. diff --git a/Code/Source/cvOneDOptions.cxx b/Code/Source/cvOneDOptions.cxx index d654e20..7fadbb2 100644 --- a/Code/Source/cvOneDOptions.cxx +++ b/Code/Source/cvOneDOptions.cxx @@ -31,362 +31,161 @@ #include "cvOneDOptions.h" -// CONSTRUCTOR -cvOneDOptions::cvOneDOptions(){ - modelNameDefined = false; - solverOptionDefined = false; -} +namespace cvOneD{ -// DESTRUCTOR -cvOneDOptions::~cvOneDOptions(){ -} +// ===================== +// PERFORM DATA CHECKING +// ===================== -// PRINT MODEL NAME -void cvOneDOptions::printModelName(FILE* f){ - fprintf(f,"--- \n"); - fprintf(f,"MODEL NAME: %s\n",modelName.c_str()); -} +namespace{ -// PRINT NODE DATA -void cvOneDOptions::printNodeData(FILE* f){ - fprintf(f,"--- \n"); - fprintf(f,"NODE DATA\n"); - for(long int loopA=0;loopA + int checkForDuplicateEntry(vector vec){ + for(int loopA=0;loopA + void checkForDuplicateValues(const vector& vec, const string& type) { + if (int dblIDX = checkForDuplicateEntry(vec); dblIDX >= 0) { + throw cvException(string("ERROR: Duplicate " + type + ": " + vec[dblIDX] + "\n").c_str()); + } } -} -// PRINT SEGMEMENT DATA -void cvOneDOptions::printSegmentData(FILE* f){ - fprintf(f,"--- \n"); - fprintf(f,"SEGMENT DATA\n"); - for(long int loopA=0;loopA= 0) { + throw cvException(string("ERROR: Duplicate " + type + ": " + to_string(vec[dblIDX]) + "\n").c_str()); + } } -} -// PRINT SOLVER OPTION DATA -void cvOneDOptions::printSolverOptions(FILE* f){ - fprintf(f,"--- \n"); - fprintf(f,"PRINT SOLVER OPTION DATA\n"); - fprintf(f,"TIME STEP: %f\n",timeStep); - fprintf(f,"STEP SIZE: %ld\n",stepSize); - fprintf(f,"MAX STEP: %ld\n",maxStep); - fprintf(f,"QUADRATURE POINTS: %ld\n",quadPoints); - fprintf(f,"INLET DATA TABLE: %s\n",inletDataTableName.c_str()); - fprintf(f,"BOUNDARY TYPE: %s\n",boundaryType.c_str()); - fprintf(f,"CONVERGENCE TOLERANCE: %f\n",convergenceTolerance); - fprintf(f,"USE IV: %d\n",useIV); - fprintf(f,"USE STABILIZATION: %d\n",useStab); -} - -// PRINT MATERIAL DATA -void cvOneDOptions::printMaterialData(FILE* f){ - fprintf(f,"--- \n"); - // MATERIAL - for(long int loopA=0;loopA 0){ - fprintf(f,"LIST ITEMS\n"); - for(size_t loopB=0;loopB 0){ - fprintf(f,"LIST ITEMS\n"); - for(size_t loopB=0;loopB + void checkForNegativeValues(const vector& vec, const vector& names, const string& type) { + if (int chkIDX = checkForPositiveVal(vec); chkIDX >= 0) { + throw cvException(string("ERROR: Negative " + type + " in segment: " + names[chkIDX] + "\n").c_str()); } - } - fprintf(f,"\n"); } -} -// PRINT DATA TABLES -void cvOneDOptions::printDataTables(FILE* f){ - fprintf(f,"--- \n"); - fprintf(f,"DATA TABLES\n"); - for(long int loopA=0;loopA -int checkForDoubleEntry(vector vec){ - for(int loopA=0;loopA + bool checkContains(T value, vector vec){ + for(int loopA=0;loopA -bool checkContains(T value, vector vec){ - for(int loopA=0;loopA= opts.nodeName.size())){ + throw cvException(string("ERROR: Missing Node in Segment: " + opts.segmentName[loopA] + "\n").c_str()); + } + if((outNode < 0)||(outNode >= opts.nodeName.size())){ + throw cvException(string("ERROR: Missing Node in Segment: " + opts.segmentName[loopA] + "\n").c_str()); + } } } - return false; -} -// CHECK FOR POSITIVE VALUES -int checkForPositiveVal(cvDoubleVec vec){ - for(int loopA=0;loopA= 0){ - throw cvException(string("ERROR: Double Node Name: " + nodeName[dblIDX] + "\n").c_str()); - } - // Check for double joint name - dblIDX = checkForDoubleEntry(jointName); - if(dblIDX >= 0){ - throw cvException(string("ERROR: Double Joint Name: " + jointName[dblIDX] + "\n").c_str()); - } - // Check for double jointInlet name - dblIDX = checkForDoubleEntry(jointInletListNames); - if(dblIDX >= 0){ - throw cvException(string("ERROR: Double JointInlet Name: " + jointInletListNames[dblIDX] + "\n").c_str()); - } - // Check for double jointOutlet name - dblIDX = checkForDoubleEntry(jointOutletListNames); - if(dblIDX >= 0){ - throw cvException(string("ERROR: Double JointOutlet Name: " + jointOutletListNames[dblIDX] + "\n").c_str()); - } - // Check for double material name - dblIDX = checkForDoubleEntry(materialName); - if(dblIDX >= 0){ - throw cvException(string("ERROR: Double Material Name: " + materialName[dblIDX] + "\n").c_str()); - } - // Check for double data table name - dblIDX = checkForDoubleEntry(dataTableName); - if(dblIDX >= 0){ - throw cvException(string("ERROR: Double Data Table Name: " + dataTableName[dblIDX] + "\n").c_str()); - } - // Check for double segment Name - dblIDX = checkForDoubleEntry(segmentName); - if(dblIDX >= 0){ - throw cvException(string("ERROR: Double Segment Name: " + segmentName[dblIDX] + "\n").c_str()); - } - // Check for double segment ID - dblIDX = checkForDoubleEntry(segmentID); - if(dblIDX >= 0){ - throw cvException(string("ERROR: Double Segment ID: " + to_string(segmentID[dblIDX]) + "\n").c_str()); - } - // Check for negative area in input - chkIDX = checkForPositiveVal(segmentInInletArea); - if(chkIDX >= 0){ - throw cvException(string("ERROR: Negative Inlet area in segment : " + segmentName[chkIDX] + "\n").c_str()); - } - chkIDX = checkForPositiveVal(segmentInOutletArea); - if(chkIDX >= 0){ - throw cvException(string("ERROR: Negative Outlet area in segment : " + segmentName[chkIDX] + "\n").c_str()); - } + // Check if there are duplicate options + checkForDuplicates(opts); + + // Check for negative values when there shouldn't be + checkForNegatives(opts); // Check the consistency between node coords and segment lengths - checkSegmentLengthConsistency(); + checkSegmentLengthConsistency(opts); // Check if the segments refer to a node that is not there - checkSegmentHasNodes(); + checkSegmentHasNodes(opts); // Check if the joints refer to a node that is not there - checkJointHasNodes(); + checkJointHasNodes(opts); } -void cvOneDOptions::checkSegmentHasNodes(){ - int inNode = 0; - int outNode = 0; - for(int loopA=0;loopA= nodeName.size())){ - throw cvException(string("ERROR: Missing Node in Segment: " + segmentName[loopA] + "\n").c_str()); - } - if((outNode < 0)||(outNode >= nodeName.size())){ - throw cvException(string("ERROR: Missing Node in Segment: " + segmentName[loopA] + "\n").c_str()); - } - } -} - -void cvOneDOptions::checkJointHasNodes(){ - string currNodeName; - for(int loopA=0;loopA -# include -# include "cvOneDTypes.h" -# include "cvOneDException.h" +#include +#include +#include +#include "cvOneDTypes.h" +#include "cvOneDException.h" using namespace std; -class cvOneDOptions{ - public: - // CONSTRUCTOR - cvOneDOptions(); - // DESTRUCTOR - ~cvOneDOptions(); +namespace cvOneD{ + +struct options{ // DATA string modelName; - bool modelNameDefined; // NODE DATA cvStringVec nodeName; + // Note that these coordinates are also used + // for the joints through an implicit mapping cvDoubleVec nodeXcoord; cvDoubleVec nodeYcoord; cvDoubleVec nodeZcoord; // JOINT DATA cvStringVec jointName; - cvStringVec jointNode; - cvDoubleVec jointXcoord; - cvDoubleVec jointYcoord; - cvDoubleVec jointZcoord; + + // "jointNode" isn't used currently -- but we want + // to start integrating it. It can be used to make + // the mapping from joint->node explicit. Right + // now, the mapping is implicit: each joint is + // associated with the node of the same index in + // the list of nodes. + cvStringVec jointNode; + cvStringVec jointInletName; cvStringVec jointOutletName; - // JOINT INLET AND OUTLET LIST cvStringVec jointInletListNames; cvLongVec jointInletListNumber; cvLongMat jointInletList; + cvStringVec jointOutletListNames; cvLongVec jointOutletListNumber; cvLongMat jointOutletList; @@ -118,26 +122,11 @@ class cvOneDOptions{ int useIV; int useStab; string outputType; - bool solverOptionDefined; - - // PRINTING - void printToFile(string fileName); - void printModelName(FILE* f); - void printNodeData(FILE* f); - void printJointData(FILE* f); - void printSegmentData(FILE* f); - void printSolverOptions(FILE* f); - void printMaterialData(FILE* f); - void printJointInletData(FILE* f); - void printJointOutletData(FILE* f); - void printDataTables(FILE* f); - - // CHECKING - void check(); - void checkSegmentLengthConsistency(); - void checkSegmentHasNodes(); - void checkJointHasNodes(); }; +void validateOptions(options const& opts); + +} // namespace cvOneD + #endif // CVONEDOPTIONS_H diff --git a/Code/Source/cvOneDOptionsJsonParser.cxx b/Code/Source/cvOneDOptionsJsonParser.cxx new file mode 100644 index 0000000..a165af8 --- /dev/null +++ b/Code/Source/cvOneDOptionsJsonParser.cxx @@ -0,0 +1,247 @@ +#include +#include +#include + +#include + +#include "cvOneDOptionsJsonSerializer.h" + +using json = nlohmann::ordered_json; + +namespace cvOneD{ + +namespace { + +std::string readFileContents(const std::string& filePath) { + std::ifstream file(filePath); + if (!file.is_open()) { + throw std::runtime_error("Could not open file: " + filePath); + } + + std::stringstream buffer; + buffer << file.rdbuf(); + return buffer.str(); +} + +// Helper function for error handling +template +void wrapParsingErrors(const std::string& sectionName, Func&& func) { + try { + func(); + } catch (const std::exception& e) { + throw std::runtime_error("Error parsing '" + sectionName + "': " + std::string(e.what())); + } +} + +void parseNodeData(const nlohmann::ordered_json& jsonData, options& opts) try { + // Ensure "nodes" exists and is an array + if (!jsonData.contains("nodes") || !jsonData["nodes"].is_array()) { + throw std::invalid_argument("Expected 'nodes' to be an array in JSON input."); + } + + const auto& nodes = jsonData["nodes"]; + + for (const auto& node : nodes) { + if (!node.is_object()) { + throw std::invalid_argument("Each entry in 'nodes' must be a JSON object."); + } + + // Ensure required fields exist + opts.nodeName.push_back(node.at("name").get()); + opts.nodeXcoord.push_back(node.at("x").get()); + opts.nodeYcoord.push_back(node.at("y").get()); + opts.nodeZcoord.push_back(node.at("z").get()); + } +} catch (const std::exception& e) { + throw std::runtime_error("Error parsing 'nodes': " + std::string(e.what())); +} + +void parseJointData(const nlohmann::ordered_json& jsonData, options& opts) try { + // Ensure "joints" exists and is an array + if (!jsonData.contains("joints") || !jsonData["joints"].is_array()) { + throw std::invalid_argument("Expected 'joints' to be an array in JSON input."); + } + + const auto& joints = jsonData["joints"]; + + for (const auto& jointEntry : joints) { + if (!jointEntry.is_object()) { + throw std::invalid_argument("Each entry in 'joints' must be a JSON object."); + } + + // Parse JOINT data + const auto& joint = jointEntry.at("joint"); // Ensures "joint" exists + opts.jointName.push_back(joint.at("id").get()); + opts.jointNode.push_back(joint.at("associatedNode").get()); + opts.jointInletName.push_back(joint.at("inletName").get()); + opts.jointOutletName.push_back(joint.at("outletName").get()); + + // Parse JOINTINLET data + const auto& jointInlet = jointEntry.at("jointInlet"); // Ensures "jointInlet" exists + opts.jointInletListNames.push_back(jointInlet.at("name").get()); + opts.jointInletListNumber.push_back(jointInlet.at("totalSegments").get()); + opts.jointInletList.emplace_back(jointInlet.at("segments").get>()); + + // Parse JOINTOUTLET data + const auto& jointOutlet = jointEntry.at("jointOutlet"); // Ensures "jointOutlet" exists + opts.jointOutletListNames.push_back(jointOutlet.at("name").get()); + opts.jointOutletListNumber.push_back(jointOutlet.at("totalSegments").get()); + opts.jointOutletList.emplace_back(jointOutlet.at("segments").get>()); + } + +} catch (const std::exception& e) { + throw std::runtime_error("Error parsing 'joints': " + std::string(e.what())); +} + +void parse_material_data(const nlohmann::json& json, options& opts) try { + if (!json.contains("materials") || !json["materials"].is_array()) { + throw std::invalid_argument("Expected 'materials' to be an array in JSON input."); + } + + const auto& materials = json["materials"]; + + for (const auto& material : materials) { + if (!material.is_object()) { + throw std::invalid_argument("Each material in 'materials' must be a JSON object."); + } + + opts.materialName.push_back(material.at("name").get()); + opts.materialType.push_back(material.at("type").get()); + opts.materialDensity.push_back(material.at("density").get()); + opts.materialViscosity.push_back(material.at("viscosity").get()); + opts.materialPRef.push_back(material.at("pRef").get()); + opts.materialExponent.push_back(material.at("exponent").get()); + opts.materialParam1.push_back(material.at("param1").get()); + opts.materialParam2.push_back(material.at("param2").get()); + opts.materialParam3.push_back(material.at("param3").get()); + } + +} catch (const std::exception& e) { + throw std::runtime_error("Error parsing material data: " + std::string(e.what())); +} + +void parseDataTables(const nlohmann::ordered_json& jsonData, options& opts) try { + if (!jsonData.contains("dataTables") || !jsonData["dataTables"].is_array()) { + throw std::invalid_argument("Expected 'dataTables' to be an array in JSON input."); + } + + const auto& dataTables = jsonData["dataTables"]; + + // Parse each data table entry + for (const auto& table : dataTables) { + if (!table.is_object()) { + throw std::invalid_argument("Each entry in 'dataTables' must be a JSON object."); + } + + // Ensure required fields exist and have the correct types + const auto& name = table.at("name").get(); + const auto& type = table.at("type").get(); + + // Parse "values" array + if (!table.contains("values") || !table["values"].is_array()) { + throw std::invalid_argument("Expected 'values' to be an array in each data table."); + } + + cvDoubleVec values; + for (const auto& val : table["values"]) { + if (!val.is_number()) { + throw std::invalid_argument("Each 'values' entry must be a number."); + } + values.push_back(val.get()); + } + + // Append parsed data to the options object + opts.dataTableName.push_back(name); + opts.dataTableType.push_back(type); + opts.dataTableVals.push_back(values); + } +} catch (const std::exception& e) { + throw std::runtime_error("Error parsing 'dataTables': " + std::string(e.what())); +} + +void parseSegmentData(const nlohmann::ordered_json& jsonData, options& opts) try { + const auto& segments = jsonData.at("segments"); // Throws if "segments" does not exist + if (!segments.is_array()) { + throw std::invalid_argument("'segments' must be an array."); + } + + for (const auto& segment : segments) { + if (!segment.is_object()) { + throw std::invalid_argument("Each entry in 'segments' must be a JSON object."); + } + + // Parse required fields without defaults; throw if missing + opts.segmentName.push_back(segment.at("name").get()); + opts.segmentID.push_back(segment.at("id").get()); + opts.segmentLength.push_back(segment.at("length").get()); + opts.segmentTotEls.push_back(segment.at("totalElements").get()); + opts.segmentInNode.push_back(segment.at("inNode").get()); + opts.segmentOutNode.push_back(segment.at("outNode").get()); + opts.segmentInInletArea.push_back(segment.at("inletArea").get()); + opts.segmentInOutletArea.push_back(segment.at("outletArea").get()); + opts.segmentInFlow.push_back(segment.at("flow").get()); + opts.segmentMatName.push_back(segment.at("materialName").get()); + opts.segmentLossType.push_back(segment.at("lossType").get()); + opts.segmentBranchAngle.push_back(segment.at("branchAngle").get()); + opts.segmentUpstreamSegment.push_back(segment.at("upstreamSegment").get()); + opts.segmentBranchSegment.push_back(segment.at("branchSegment").get()); + opts.segmentBoundType.push_back(segment.at("boundaryType").get()); + opts.segmentDataTableName.push_back(segment.at("dataTableName").get()); + } +} catch (const std::exception& e) { + throw std::runtime_error("Error parsing segment data: " + std::string(e.what())); +} + +void parseSolverOptions(const nlohmann::ordered_json& jsonData, options& opts) try { + // Ensure "solverOptions" section exists + const auto& solverOptions = jsonData.at("solverOptions"); + + opts.timeStep = solverOptions.at("timeStep").get(); + opts.stepSize = solverOptions.at("stepSize").get(); + opts.maxStep = solverOptions.at("maxStep").get(); + opts.quadPoints = solverOptions.at("quadPoints").get(); + opts.inletDataTableName = solverOptions.at("inletDataTableName").get(); + opts.boundaryType = solverOptions.at("boundaryType").get(); + opts.convergenceTolerance = solverOptions.at("convergenceTolerance").get(); + opts.useIV = solverOptions.at("useIV").get(); + opts.useStab = solverOptions.at("useStab").get(); + + // Doesn't seem like this is used...but we'll provide a default anyway + opts.outputType = solverOptions.value("outputType", "None"); + +} catch (const std::exception& e) { + throw std::runtime_error("Error parsing 'solverOptions': " + std::string(e.what())); +} + +options parseJsonOptions(const std::string& jsonString) { + json jsonData; + options opts; + + try{ + jsonData = json::parse(jsonString); + } catch (const std::exception& e) { + throw std::runtime_error("Error parsing json file: " + std::string(e.what())); + } + + opts.modelName = jsonData.value("modelName", ""); + parseNodeData(jsonData,opts); + parseJointData(jsonData,opts); + parse_material_data(jsonData,opts); + parseDataTables(jsonData,opts); + parseSegmentData(jsonData, opts); + parseSolverOptions(jsonData, opts); + + return opts; +} + +} // namespace + +options readJsonOptions(std::string const& inputFile){ + auto const jsonStr = readFileContents(inputFile); + + return parseJsonOptions(jsonStr); +} + +} // namespace cvOneD + + diff --git a/Code/Source/main.h b/Code/Source/cvOneDOptionsJsonParser.h similarity index 75% rename from Code/Source/main.h rename to Code/Source/cvOneDOptionsJsonParser.h index edf0712..da57030 100644 --- a/Code/Source/main.h +++ b/Code/Source/cvOneDOptionsJsonParser.h @@ -1,3 +1,4 @@ + /* Copyright (c) Stanford University, The Regents of the University of * California, and others. * @@ -29,23 +30,20 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -# include -# include -# include +#ifndef CVONEDOPTIONSJSONPARSER_H +#define CVONEDOPTIONSJSONPARSER_H -# include "cvOneDGlobal.h" -# include "cvOneDUtility.h" -# include "cvOneDModelManager.h" -# include "cvOneDOptions.h" -# include "cvOneDDataTable.h" -# include "cvOneDException.h" +#include "cvOneDOptions.h" using namespace std; -void WriteHeader(); -int getDataTableIDFromStringKey(string key); -void createAndRunModel(cvOneDOptions* opts); -void readModelFile(string inputFile, cvOneDOptions* opts, cvStringVec includedFiles); -void readModel(string inputFile, cvOneDOptions* opts); -void runOneDSolver(string inputFile); +namespace cvOneD{ + +options readJsonOptions(string const& inputFile); + +} // namespace cvOneD + +#endif // CVONEDOPTIONSJSONPARSER_H + + diff --git a/Code/Source/cvOneDOptionsJsonSerializer.cxx b/Code/Source/cvOneDOptionsJsonSerializer.cxx new file mode 100644 index 0000000..be9bf29 --- /dev/null +++ b/Code/Source/cvOneDOptionsJsonSerializer.cxx @@ -0,0 +1,272 @@ +#include +#include +#include + +#include "cvOneDOptionsJsonSerializer.h" + +using json = nlohmann::ordered_json; + +namespace cvOneD{ + +namespace { + +// Serialize cvStringVec into a JSON array (as a plain array) +nlohmann::ordered_json to_json(const cvStringVec& vec) { + nlohmann::ordered_json j = nlohmann::ordered_json::array(); + for (const auto& str : vec) { + j.push_back(str); + } + return j; +} + +// Serialize cvLongVec into a JSON array (as a plain array) +nlohmann::ordered_json to_json(const cvLongVec& vec) { + nlohmann::ordered_json j = nlohmann::ordered_json::array(); + for (const auto& num : vec) { + j.push_back(num); + } + return j; +} + +// Serialize cvDoubleVec into a JSON array (as a plain array) +nlohmann::ordered_json to_json(const cvDoubleVec& vec) { + nlohmann::ordered_json j = nlohmann::ordered_json::array(); + for (const auto& num : vec) { + j.push_back(num); + } + return j; +} + +// Serialize cvLongMat into a JSON array of arrays (matrix) +nlohmann::ordered_json to_json(const cvLongMat& mat) { + nlohmann::ordered_json j = nlohmann::ordered_json::array(); + for (const auto& row : mat) { + nlohmann::ordered_json jRow = nlohmann::ordered_json::array(); + for (const auto& val : row) { + jRow.push_back(val); + } + j.push_back(jRow); + } + return j; +} + +template +void check_consistent_size(const std::string& dataName, const Containers&... containers) { + auto size = [](const auto& container) { return container.size(); }; + if (!((size(containers) == size(std::get<0>(std::tie(containers...)))) && ...)) { + throw std::runtime_error(dataName + ": All containers must have the same size."); + } +} + +// Serialize Node data into a JSON array, returning only the array +nlohmann::ordered_json serialize_node_data(const cvStringVec& nodeName, const cvDoubleVec& nodeXcoord, const cvDoubleVec& nodeYcoord, const cvDoubleVec& nodeZcoord) { + nlohmann::ordered_json nodes = nlohmann::ordered_json::array(); + + check_consistent_size("nodes", nodeName, nodeXcoord, nodeYcoord, nodeZcoord); + + size_t n = nodeName.size(); + for (size_t i = 0; i < n; ++i) { + nlohmann::ordered_json node; + node["name"] = nodeName.at(i); + node["x"] = nodeXcoord.at(i); + node["y"] = nodeYcoord.at(i); + node["z"] = nodeZcoord.at(i); + nodes.push_back(node); + } + + return nodes; // Just return the array of nodes +} + +nlohmann::ordered_json serialize_joint_data(const options& opts) { + nlohmann::ordered_json j = nlohmann::ordered_json::array(); + + // Check consistency of the joint inlet and outlet vectors + check_consistent_size("joints", opts.jointName, + opts.jointInletListNames, opts.jointInletListNumber, + opts.jointOutletListNames, opts.jointOutletListNumber, + opts.jointInletList, opts.jointOutletList); + + size_t jointCount = opts.jointName.size(); + + // There must be enough nodes for us to make associations. This is + // an implicit assumption made in the current joint creation code. + if( opts.nodeName.size() < jointCount ){ + throw std::runtime_error("The implicit joint-node association requires that there be at least as many nodes as there are joints."); + } + + for (size_t i = 0; i < jointCount; ++i) { + nlohmann::ordered_json joint; + + // Add JOINT data + // We're adding the node based on the index so we + // can start incorporating the explicit association + // from the implicit one currently used in the code. + joint["joint"] = { + {"id", "J" + std::to_string(i + 1)}, + {"associatedNode", opts.nodeName.at(i)}, + {"inletName", opts.jointInletListNames.at(i)}, + {"outletName", opts.jointOutletListNames.at(i)} + }; + + // Add JOINTINLET data + joint["jointInlet"] = { + {"name", opts.jointInletListNames.at(i)}, + {"totalSegments", opts.jointInletListNumber.at(i)}, + {"segments", to_json(opts.jointInletList.at(i))} + }; + + // Add JOINTOUTLET data + joint["jointOutlet"] = { + {"name", opts.jointOutletListNames.at(i)}, + {"totalSegments", opts.jointOutletListNumber.at(i)}, + {"segments", to_json(opts.jointOutletList.at(i))} + }; + + j.push_back(joint); + } + + return j; +} + +nlohmann::ordered_json serialize_material_data(const options& opts) { + nlohmann::ordered_json materials = nlohmann::ordered_json::array(); + + // Ensure all material-related vectors are the same size + check_consistent_size("materials", + opts.materialName, opts.materialType, opts.materialDensity, + opts.materialViscosity, opts.materialPRef, opts.materialExponent, + opts.materialParam1, opts.materialParam2, opts.materialParam3); + + size_t n = opts.materialName.size(); + for (size_t i = 0; i < n; ++i) { + nlohmann::ordered_json material; + material["name"] = opts.materialName.at(i); + material["type"] = opts.materialType.at(i); + material["density"] = opts.materialDensity.at(i); + material["viscosity"] = opts.materialViscosity.at(i); + material["pRef"] = opts.materialPRef.at(i); + material["exponent"] = opts.materialExponent.at(i); + material["param1"] = opts.materialParam1.at(i); + material["param2"] = opts.materialParam2.at(i); + material["param3"] = opts.materialParam3.at(i); + materials.push_back(material); + } + + return materials; // Just return the array of materials +} + +// Serialize data tables into a JSON array +nlohmann::ordered_json serialize_data_tables(const cvStringVec& dataTableName, + const cvStringVec& dataTableType, + const std::vector& dataTableVals) { + nlohmann::ordered_json dataTables = nlohmann::ordered_json::array(); + + check_consistent_size("data tables", dataTableName, dataTableType, dataTableVals); + + size_t n = dataTableName.size(); + for (size_t i = 0; i < n; ++i) { + nlohmann::ordered_json table; + table["name"] = dataTableName.at(i); + table["type"] = dataTableType.at(i); + table["values"] = to_json(dataTableVals.at(i)); // Use the existing `to_json` for cvDoubleVec + dataTables.push_back(table); + } + + return dataTables; // Return the array of data tables +} + +nlohmann::ordered_json serializeSegmentData(const options& opts) { + nlohmann::ordered_json segments = nlohmann::ordered_json::array(); + + // Ensure all segment-related vectors are the same size + check_consistent_size("segments", opts.segmentName, opts.segmentID, opts.segmentLength, + opts.segmentTotEls, opts.segmentInNode, opts.segmentOutNode, + opts.segmentInInletArea, opts.segmentInOutletArea, opts.segmentInFlow, + opts.segmentMatName, opts.segmentLossType, opts.segmentBranchAngle, + opts.segmentUpstreamSegment, opts.segmentBranchSegment, + opts.segmentBoundType, opts.segmentDataTableName); + + size_t n = opts.segmentName.size(); + for (size_t i = 0; i < n; ++i) { + nlohmann::ordered_json segment; + + segment["name"] = opts.segmentName.at(i); + segment["id"] = opts.segmentID.at(i); + segment["length"] = opts.segmentLength.at(i); + segment["totalElements"] = opts.segmentTotEls.at(i); + segment["inNode"] = opts.segmentInNode.at(i); + segment["outNode"] = opts.segmentOutNode.at(i); + segment["inletArea"] = opts.segmentInInletArea.at(i); + segment["outletArea"] = opts.segmentInOutletArea.at(i); + segment["flow"] = opts.segmentInFlow.at(i); + segment["materialName"] = opts.segmentMatName.at(i); + segment["lossType"] = opts.segmentLossType.at(i); + segment["branchAngle"] = opts.segmentBranchAngle.at(i); + segment["upstreamSegment"] = opts.segmentUpstreamSegment.at(i); + segment["branchSegment"] = opts.segmentBranchSegment.at(i); + segment["boundaryType"] = opts.segmentBoundType.at(i); + segment["dataTableName"] = opts.segmentDataTableName.at(i); + + segments.push_back(segment); + } + + return segments; +} + +nlohmann::ordered_json serializeSolverOptions(options const& opts) { + nlohmann::ordered_json solverOptions; + + solverOptions["timeStep"] = opts.timeStep; + solverOptions["stepSize"] = opts.stepSize; + solverOptions["maxStep"] = opts.maxStep; + solverOptions["quadPoints"] = opts.quadPoints; + solverOptions["inletDataTableName"] = opts.inletDataTableName; + solverOptions["boundaryType"] = opts.boundaryType; + solverOptions["convergenceTolerance"] = opts.convergenceTolerance; + solverOptions["useIV"] = opts.useIV; + solverOptions["useStab"] = opts.useStab; + solverOptions["outputType"] = opts.outputType; + + return solverOptions; +} + +// Serialize all options into a single JSON object +nlohmann::ordered_json serialize_options(const options& opts) { + nlohmann::ordered_json j; + + // Serialize and add each part of the options data + j["modelName"] = opts.modelName; + + j["solverOptions"] = serializeSolverOptions(opts); + + j["materials"] = serialize_material_data(opts); + + j["nodes"] = serialize_node_data(opts.nodeName, opts.nodeXcoord, opts.nodeYcoord, opts.nodeZcoord); + + j["joints"] = serialize_joint_data(opts); + + j["segments"] = serializeSegmentData(opts); + + j["dataTables"] = serialize_data_tables(opts.dataTableName, opts.dataTableType, opts.dataTableVals); + + return j; +} + +} // namespace + +void writeJsonOptions(options const& opts, std::string const& fileName){ + // Serialize options into JSON + nlohmann::ordered_json jsonData = cvOneD::serialize_options(opts); + + // Write JSON to the specified file + std::ofstream outFile(fileName); + if (!outFile) { + throw std::runtime_error("Unable to open file for writing: " + fileName); + } + outFile << jsonData.dump(2); // Write with 2-space indentation + outFile.close(); +} + +} // namespace cvOneD + + diff --git a/Code/Source/cvOneDOptionsJsonSerializer.h b/Code/Source/cvOneDOptionsJsonSerializer.h new file mode 100644 index 0000000..b0dd4b3 --- /dev/null +++ b/Code/Source/cvOneDOptionsJsonSerializer.h @@ -0,0 +1,49 @@ + +/* Copyright (c) Stanford University, The Regents of the University of + * California, and others. + * + * All Rights Reserved. + * + * See Copyright-SimVascular.txt for additional details. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject + * to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * 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 OWNER + * 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 CVONEDOPTIONSJSONSERIALIZER_H +#define CVONEDOPTIONSJSONSERIALIZER_H + +#include "cvOneDOptions.h" + +using namespace std; + +namespace cvOneD{ + +void writeJsonOptions(options const& opts, string const& fileName); + +} // namespace cvOneD + +#endif // CVONEDOPTIONSJSONSERIALIZER_H + + + diff --git a/Code/Source/cvOneDOptionsLegacySerializer.cxx b/Code/Source/cvOneDOptionsLegacySerializer.cxx index 5b00767..f938995 100644 --- a/Code/Source/cvOneDOptionsLegacySerializer.cxx +++ b/Code/Source/cvOneDOptionsLegacySerializer.cxx @@ -43,7 +43,7 @@ namespace cvOneD{ // ====================== // READ SINGLE MODEL FILE // ====================== -void readOptionsLegacyFormat(string inputFile, cvOneDOptions* opts){ +void readOptionsLegacyFormat(string inputFile, options* opts){ // Message printf("\n"); @@ -54,7 +54,9 @@ void readOptionsLegacyFormat(string inputFile, cvOneDOptions* opts){ cvLongVec tempIntVec; string matType; cvDoubleVec temp; - bool doInclude = false; + + bool solverOptionsDefined = false; + bool modelNameDefined = false; // Declare input File ifstream infile; @@ -84,7 +86,7 @@ void readOptionsLegacyFormat(string inputFile, cvOneDOptions* opts){ // CHECK THE ELEMENT TYPE if(upper_string(tokenizedString[0]) == "MODEL"){ //printf("Found Model.\n"); - if(opts->modelNameDefined){ + if(modelNameDefined){ throw cvException("ERROR: Model name already defined\n"); } if(tokenizedString.size() > 2){ @@ -97,7 +99,7 @@ void readOptionsLegacyFormat(string inputFile, cvOneDOptions* opts){ }catch(...){ throw cvException(string("ERROR: Invalid Model Name. Line " + to_string(lineCount) + "\n").c_str()); } - opts->modelNameDefined = true; + modelNameDefined = true; }else if(upper_string(tokenizedString[0]) == "NODE"){ // printf("Found Joint.\n"); if(tokenizedString.size() > 5){ @@ -219,7 +221,7 @@ void readOptionsLegacyFormat(string inputFile, cvOneDOptions* opts){ } }else if(upper_string(tokenizedString[0]) == "SOLVEROPTIONS"){ // printf("Found Solver Options.\n"); - if(opts->solverOptionDefined){ + if(solverOptionsDefined){ throw cvException("ERROR: SOLVEROPTIONS already defined\n"); } if(tokenizedString.size() > 10){ @@ -249,7 +251,7 @@ void readOptionsLegacyFormat(string inputFile, cvOneDOptions* opts){ }catch(...){ throw cvException(string("ERROR: Invalid SOLVEROPTIONS Format. Line " + to_string(lineCount) + "\n").c_str()); } - opts->solverOptionDefined = true; + solverOptionsDefined = true; }else if(upper_string(tokenizedString[0]) == std::string("OUTPUT")){ if(tokenizedString.size() > 3){ throw cvException(string("ERROR: Too many parameters for OUTPUT token. Line " + to_string(lineCount) + "\n").c_str()); @@ -363,4 +365,195 @@ void readOptionsLegacyFormat(string inputFile, cvOneDOptions* opts){ infile.close(); } +namespace{ + +// PRINT MODEL NAME +void printModelName(options const& opts,FILE* f){ + fprintf(f,"--- \n"); + fprintf(f,"MODEL NAME: %s\n",opts.modelName.c_str()); +} + +// PRINT NODE DATA +void printNodeData(options const& opts,FILE* f){ + fprintf(f,"--- \n"); + fprintf(f,"NODE DATA\n"); + for(long int loopA=0;loopA 0){ + fprintf(f,"LIST ITEMS\n"); + for(size_t loopB=0;loopB 0){ + fprintf(f,"LIST ITEMS\n"); + for(size_t loopB=0;loopB +#include +#include "cvOneDGlobal.h" +#include "cvOneDModelManager.h" +#include "cvOneDOptions.h" +#include "cvOneDOptionsJsonParser.h" +#include "cvOneDOptionsJsonSerializer.h" #include "cvOneDOptionsLegacySerializer.h" using namespace std; @@ -70,116 +75,116 @@ int getDataTableIDFromStringKey(string key){ // =============================== // CREATE MODEL AND RUN SIMULATION // =============================== -void createAndRunModel(cvOneDOptions* opts){ +void createAndRunModel(const cvOneD::options& opts) { // MESSAGE printf("\n"); printf("Creating and Running Model ...\n"); // CREATE MODEL MANAGER - cvOneDModelManager* oned = new cvOneDModelManager((char*)opts->modelName.c_str()); + cvOneDModelManager* oned = new cvOneDModelManager((char*)opts.modelName.c_str()); // CREATE NODES printf("Creating Nodes ... \n"); - int totNodes = opts->nodeName.size(); + int totNodes = opts.nodeName.size(); int nodeError = CV_OK; - for(int loopA=0;loopACreateNode((char*)opts->nodeName[loopA].c_str(), - opts->nodeXcoord[loopA], opts->nodeYcoord[loopA], opts->nodeZcoord[loopA]); - if(nodeError == CV_ERROR){ + nodeError = oned->CreateNode((char*)opts.nodeName[loopA].c_str(), + opts.nodeXcoord[loopA], opts.nodeYcoord[loopA], opts.nodeZcoord[loopA]); + if(nodeError == CV_ERROR) { throw cvException(string("ERROR: Error Creating NODE " + to_string(loopA) + "\n").c_str()); } } // CREATE JOINTS printf("Creating Joints ... \n"); - int totJoints = opts->jointName.size(); + int totJoints = opts.jointName.size(); int jointError = CV_OK; - int* asInlets = NULL; - int* asOutlets = NULL; + int* asInlets = nullptr; + int* asOutlets = nullptr; string currInletName; string currOutletName; int jointInletID = 0; int jointOutletID = 0; int totJointInlets = 0; int totJointOutlets = 0; - for(int loopA=0;loopAjointInletName[loopA]; - currOutletName = opts->jointOutletName[loopA]; + currInletName = opts.jointInletName[loopA]; + currOutletName = opts.jointOutletName[loopA]; // FIND JOINTINLET INDEX - jointInletID = getListIDWithStringKey(currInletName,opts->jointInletListNames); - if(jointInletID < 0){ + jointInletID = getListIDWithStringKey(currInletName, opts.jointInletListNames); + if(jointInletID < 0) { throw cvException(string("ERROR: Cannot Find JOINTINLET for key " + currInletName).c_str()); } - totJointInlets = opts->jointInletListNumber[jointInletID]; + totJointInlets = opts.jointInletListNumber[jointInletID]; // FIND JOINTOUTLET INDEX - jointOutletID = getListIDWithStringKey(currOutletName,opts->jointOutletListNames); - if(jointInletID < 0){ + jointOutletID = getListIDWithStringKey(currOutletName, opts.jointOutletListNames); + if(jointInletID < 0) { throw cvException(string("ERROR: Cannot Find JOINTOUTLET for key " + currOutletName).c_str()); } // GET TOTALS - totJointInlets = opts->jointInletListNumber[jointInletID]; - totJointOutlets = opts->jointOutletListNumber[jointOutletID]; + totJointInlets = opts.jointInletListNumber[jointInletID]; + totJointOutlets = opts.jointOutletListNumber[jointOutletID]; // ALLOCATE INLETS AND OUTLET LIST - asInlets = NULL; - asOutlets = NULL; - if(totJointInlets > 0){ + asInlets = nullptr; + asOutlets = nullptr; + if(totJointInlets > 0) { asInlets = new int[totJointInlets]; - for(int loopB=0;loopBjointInletList[jointInletID][loopB]; + for(int loopB = 0; loopB < totJointInlets; loopB++) { + asInlets[loopB] = opts.jointInletList[jointInletID][loopB]; } } - if(totJointOutlets > 0){ + if(totJointOutlets > 0) { asOutlets = new int[totJointOutlets]; - for(int loopB=0;loopBjointOutletList[jointOutletID][loopB]; + for(int loopB = 0; loopB < totJointOutlets; loopB++) { + asOutlets[loopB] = opts.jointOutletList[jointOutletID][loopB]; } } // Finally Create Joint - jointError = oned->CreateJoint((char*)opts->jointName[loopA].c_str(), - opts->nodeXcoord[loopA], opts->nodeYcoord[loopA], opts->nodeZcoord[loopA], - totJointInlets, totJointOutlets,asInlets,asOutlets); - if(jointError == CV_ERROR){ + jointError = oned->CreateJoint((char*)opts.jointName[loopA].c_str(), + opts.nodeXcoord[loopA], opts.nodeYcoord[loopA], opts.nodeZcoord[loopA], + totJointInlets, totJointOutlets, asInlets, asOutlets); + if(jointError == CV_ERROR) { throw cvException(string("ERROR: Error Creating JOINT " + to_string(loopA) + "\n").c_str()); } // Deallocate - delete [] asInlets; - delete [] asOutlets; - asInlets = NULL; - asOutlets = NULL; + delete[] asInlets; + delete[] asOutlets; + asInlets = nullptr; + asOutlets = nullptr; } // CREATE MATERIAL printf("Creating Materials ... \n"); - int totMaterials = opts->materialName.size(); + int totMaterials = opts.materialName.size(); int matError = CV_OK; double doubleParams[3]; int matID = 0; string currMatType = "MATERIAL_OLUFSEN"; int numParams = 0; - for(int loopA=0;loopAmaterialType[loopA]) == "OLUFSEN"){ + for(int loopA = 0; loopA < totMaterials; loopA++) { + if(upper_string(opts.materialType[loopA]) == "OLUFSEN") { currMatType = "MATERIAL_OLUFSEN"; numParams = 3; - }else{ + } else { currMatType = "MATERIAL_LINEAR"; numParams = 1; } - doubleParams[0] = opts->materialParam1[loopA]; - doubleParams[1] = opts->materialParam2[loopA]; - doubleParams[2] = opts->materialParam3[loopA]; + doubleParams[0] = opts.materialParam1[loopA]; + doubleParams[1] = opts.materialParam2[loopA]; + doubleParams[2] = opts.materialParam3[loopA]; // CREATE MATERIAL - matError = oned->CreateMaterial((char*)opts->materialName[loopA].c_str(), + matError = oned->CreateMaterial((char*)opts.materialName[loopA].c_str(), (char*)currMatType.c_str(), - opts->materialDensity[loopA], - opts->materialViscosity[loopA], - opts->materialExponent[loopA], - opts->materialPRef[loopA], + opts.materialDensity[loopA], + opts.materialViscosity[loopA], + opts.materialExponent[loopA], + opts.materialPRef[loopA], numParams, doubleParams, &matID); - if(matError == CV_ERROR){ + if(matError == CV_ERROR) { throw cvException(string("ERROR: Error Creating MATERIAL " + to_string(loopA) + "\n").c_str()); } @@ -187,11 +192,11 @@ void createAndRunModel(cvOneDOptions* opts){ // CREATE DATATABLES printf("Creating Data Tables ... \n"); - int totCurves = opts->dataTableName.size(); + int totCurves = opts.dataTableName.size(); int curveError = CV_OK; - for(int loopA=0;loopACreateDataTable((char*)opts->dataTableName[loopA].c_str(),(char*)opts->dataTableType[loopA].c_str(),opts->dataTableVals[loopA]); - if(curveError == CV_ERROR){ + for(int loopA = 0; loopA < totCurves; loopA++) { + curveError = oned->CreateDataTable((char*)opts.dataTableName[loopA].c_str(),(char*)opts.dataTableType[loopA].c_str(), opts.dataTableVals[loopA]); + if(curveError == CV_ERROR) { throw cvException(string("ERROR: Error Creating DATATABLE " + to_string(loopA) + "\n").c_str()); } } @@ -199,68 +204,68 @@ void createAndRunModel(cvOneDOptions* opts){ // SEGMENT DATA printf("Creating Segments ... \n"); int segmentError = CV_OK; - int totalSegments = opts->segmentName.size(); + int totalSegments = opts.segmentName.size(); int curveTotals = 0; - double* curveTime = NULL; - double* curveValue = NULL; + double* curveTime = nullptr; + double* curveValue = nullptr; string matName; string curveName; int currMatID = 0; int dtIDX = 0; - for(int loopA=0;loopAsegmentMatName[loopA]; - currMatID = getListIDWithStringKey(matName,opts->materialName); - if(currMatID < 0){ + matName = opts.segmentMatName[loopA]; + currMatID = getListIDWithStringKey(matName, opts.materialName); + if(currMatID < 0) { throw cvException(string("ERROR: Cannot Find Material for key " + matName).c_str()); } // GET CURVE DATA - curveName = opts->segmentDataTableName[loopA]; + curveName = opts.segmentDataTableName[loopA]; if(upper_string(curveName) != "NONE") { dtIDX = getDataTableIDFromStringKey(curveName); curveTotals = cvOneDGlobal::gDataTables[dtIDX]->getSize(); curveTime = new double[curveTotals]; curveValue = new double[curveTotals]; - for(int loopA=0;loopAgetTime(loopA); curveValue[loopA] = cvOneDGlobal::gDataTables[dtIDX]->getValues(loopA); } - }else{ + } else { curveTotals = 1; curveTime = new double[curveTotals]; curveValue = new double[curveTotals]; curveTime[0] = 0.0; curveValue[0] = 0.0; } - segmentError = oned->CreateSegment((char*)opts->segmentName[loopA].c_str(), - (long)opts->segmentID[loopA], - opts->segmentLength[loopA], - (long)opts->segmentTotEls[loopA], - (long)opts->segmentInNode[loopA], - (long)opts->segmentOutNode[loopA], - opts->segmentInInletArea[loopA], - opts->segmentInOutletArea[loopA], - opts->segmentInFlow[loopA], + segmentError = oned->CreateSegment((char*)opts.segmentName[loopA].c_str(), + (long)opts.segmentID[loopA], + opts.segmentLength[loopA], + (long)opts.segmentTotEls[loopA], + (long)opts.segmentInNode[loopA], + (long)opts.segmentOutNode[loopA], + opts.segmentInInletArea[loopA], + opts.segmentInOutletArea[loopA], + opts.segmentInFlow[loopA], currMatID, - (char*)opts->segmentLossType[loopA].c_str(), - opts->segmentBranchAngle[loopA], - opts->segmentUpstreamSegment[loopA], - opts->segmentBranchSegment[loopA], - (char*)opts->segmentBoundType[loopA].c_str(), + (char*)opts.segmentLossType[loopA].c_str(), + opts.segmentBranchAngle[loopA], + opts.segmentUpstreamSegment[loopA], + opts.segmentBranchSegment[loopA], + (char*)opts.segmentBoundType[loopA].c_str(), curveValue, curveTime, curveTotals); - if(segmentError == CV_ERROR){ + if(segmentError == CV_ERROR) { throw cvException(string("ERROR: Error Creating SEGMENT " + to_string(loopA) + "\n").c_str()); } // Deallocate - delete [] curveTime; - curveTime = NULL; - delete [] curveValue; - curveValue = NULL; + delete[] curveTime; + curveTime = nullptr; + delete[] curveValue; + curveValue = nullptr; } double* vals; @@ -269,57 +274,143 @@ void createAndRunModel(cvOneDOptions* opts){ // SOLVE MODEL printf("Solving Model ... \n"); int solveError = CV_OK; - string inletCurveName = opts->inletDataTableName; + string inletCurveName = opts.inletDataTableName; int inletCurveIDX = getDataTableIDFromStringKey(inletCurveName); int inletCurveTotals = cvOneDGlobal::gDataTables[inletCurveIDX]->getSize(); double* inletCurveTime = new double[inletCurveTotals]; double* inletCurveValue = new double[inletCurveTotals]; - for(int loopB=0;loopBgetTime(loopB); inletCurveValue[loopB] = cvOneDGlobal::gDataTables[inletCurveIDX]->getValues(loopB); } // Solve Model - solveError = oned->SolveModel(opts->timeStep, - opts->stepSize, - opts->maxStep, - opts->quadPoints, + solveError = oned->SolveModel(opts.timeStep, + opts.stepSize, + opts.maxStep, + opts.quadPoints, inletCurveTotals, - (char*)opts->boundaryType.c_str(), + (char*)opts.boundaryType.c_str(), inletCurveValue, inletCurveTime, - opts->convergenceTolerance, + opts.convergenceTolerance, // Formulation Type - opts->useIV, + opts.useIV, // Stabilization - opts->useStab); - if(solveError == CV_ERROR){ + opts.useStab); + if(solveError == CV_ERROR) { throw cvException(string("ERROR: Error Solving Model\n").c_str()); } - delete [] inletCurveTime; - delete [] inletCurveValue; + delete[] inletCurveTime; + delete[] inletCurveValue; } // ============== // RUN ONEDSOLVER // ============== -void runOneDSolver(string inputFile){ - - // Create options from legacy format file - cvOneDOptions* opts = new cvOneDOptions(); - cvOneD::readOptionsLegacyFormat(inputFile,opts); +void runOneDSolver(const cvOneD::options& opts){ // Model Checking - opts->check(); + cvOneD::validateOptions(opts); // Print Input Data Echo string fileName("echo.out"); - opts->printToFile(fileName); + cvOneD::printToLegacyFile(opts,fileName); + + // For now, we'll just duplicate the printing of + // the echo of the options to JSON as well + string jsonFilename("echo.json"); + cvOneD::writeJsonOptions(opts, jsonFilename); // Create Model and Run Simulation createAndRunModel(opts); - // Delete Options - delete opts; +} + +void convertOptions(const std::string& legacyFilename, const std::string& jsonFilename){ + // Convert legacy format to JSON and print it to file + cvOneD::options opts{}; + cvOneD::readOptionsLegacyFormat(legacyFilename,&opts); + + cvOneD::writeJsonOptions(opts, jsonFilename); + + std::cout << "Converted legacy file " << legacyFilename << + "to json file " << jsonFilename << std::endl; +} + +struct ArgOptions{ + std::optional jsonInput = std::nullopt; + + std::optional legacyConversionInput = std::nullopt; + std::optional jsonConversionOutput = std::nullopt; +}; + +ArgOptions parseInputArgs(int argc, char** argv) { + // Right now, we don't check for bad arguments but we could + // do that in here if we want more coherent behavior. + ArgOptions options; + + for (int i = 1; i < argc; i++) { + std::string arg = argv[i]; + + if (arg == "-jsonInput" && i + 1 < argc) { + options.jsonInput = argv[i + 1]; + i++; + } + + if (arg == "-legacyToJson" && i + 2 < argc) { + options.legacyConversionInput = argv[i + 1]; + options.jsonConversionOutput = argv[i + 2]; + i++; + } + + } + + return options; +} + +// Parse the incoming arguments and read the options file. +// Also performs option file conversions if requested by user. +// +// Updated behavior: +// parse input arg pairs as +// "-argName argValue -anotherArg anotherValue" +// Current options: +// * Run simulation with JSON input file: +// -jsonInput inputFilename +// * Convert legacy input to JSON input: +// -legacyToJson legacyInput.in jsonInput.json +// +// Preserved legacy behavior: +// Single input that is a legacy input file, e.g.: +// ./svOneDSolver inputFilename.in +// +// Legacy behavior is used only if exactly one input is provided. +// +std::optional parseArgsAndHandleOptions(int argc, char** argv){ + + // Legacy behavior + if(argc == 2){ + string inputFile(argv[1]); + cvOneD::options opts{}; + cvOneD::readOptionsLegacyFormat(inputFile,&opts); + return opts; + } + + // Default behavior + auto const argOptions = parseInputArgs(argc,argv); + + // Conversion + if(argOptions.legacyConversionInput && argOptions.jsonConversionOutput){ + convertOptions(*argOptions.legacyConversionInput, + *argOptions.jsonConversionOutput); + } + + // Only return the input args if the user provided them + if(argOptions.jsonInput){ + return cvOneD::readJsonOptions(*argOptions.jsonInput); + } + + return std::nullopt; } // ============= @@ -332,9 +423,17 @@ int main(int argc, char** argv){ try{ - // Run Simulation - string inputFile(argv[1]); - runOneDSolver(inputFile); + auto const simulationOptions = parseArgsAndHandleOptions(argc,argv); + if(simulationOptions){ + // The simulation options were defined so we can run the simulation + runOneDSolver(*simulationOptions); + } else{ + // The user could just want to convert legacy input *.in -> *.json + // so we don't error but we notify the user that no simulation + // is run. + std::cout << "The simulation was not run because" + " no input file was provided." << std::endl; + } }catch(exception& e){ // Print Exception Message diff --git a/Tests/UnitTests/OptionsTestHelpers.cxx b/Tests/UnitTests/OptionsTestHelpers.cxx new file mode 100644 index 0000000..dc12768 --- /dev/null +++ b/Tests/UnitTests/OptionsTestHelpers.cxx @@ -0,0 +1,95 @@ + +#include +#include +#include + +#include + +#include "OptionsTestHelpers.hpp" + +void expectEqOptions(const cvOneD::options& actual, const cvOneD::options& expected) { + // Compare modelName + EXPECT_EQ(expected.modelName, actual.modelName); + + // Compare node data + EXPECT_EQ(expected.nodeName, actual.nodeName); + EXPECT_EQ(expected.nodeXcoord, actual.nodeXcoord); + EXPECT_EQ(expected.nodeYcoord, actual.nodeYcoord); + EXPECT_EQ(expected.nodeZcoord, actual.nodeZcoord); + + // Compare joint data + EXPECT_EQ(expected.jointName, actual.jointName); + EXPECT_EQ(expected.jointNode, actual.jointNode); + EXPECT_EQ(expected.jointInletName, actual.jointInletName); + EXPECT_EQ(expected.jointOutletName, actual.jointOutletName); + + // Compare joint inlet and outlet lists + EXPECT_EQ(expected.jointInletListNames, actual.jointInletListNames); + EXPECT_EQ(expected.jointInletListNumber, actual.jointInletListNumber); + EXPECT_EQ(expected.jointInletList, actual.jointInletList); + EXPECT_EQ(expected.jointOutletListNames, actual.jointOutletListNames); + EXPECT_EQ(expected.jointOutletListNumber, actual.jointOutletListNumber); + EXPECT_EQ(expected.jointOutletList, actual.jointOutletList); + + // Compare material data + EXPECT_EQ(expected.materialName, actual.materialName); + EXPECT_EQ(expected.materialType, actual.materialType); + EXPECT_EQ(expected.materialDensity, actual.materialDensity); + EXPECT_EQ(expected.materialViscosity, actual.materialViscosity); + EXPECT_EQ(expected.materialPRef, actual.materialPRef); + EXPECT_EQ(expected.materialExponent, actual.materialExponent); + EXPECT_EQ(expected.materialParam1, actual.materialParam1); + EXPECT_EQ(expected.materialParam2, actual.materialParam2); + EXPECT_EQ(expected.materialParam3, actual.materialParam3); + + // Compare data table information + EXPECT_EQ(expected.dataTableName, actual.dataTableName); + EXPECT_EQ(expected.dataTableType, actual.dataTableType); + EXPECT_EQ(expected.dataTableVals, actual.dataTableVals); + + // Compare segment data + EXPECT_EQ(expected.segmentName, actual.segmentName); + EXPECT_EQ(expected.segmentID, actual.segmentID); + EXPECT_EQ(expected.segmentLength, actual.segmentLength); + EXPECT_EQ(expected.segmentTotEls, actual.segmentTotEls); + EXPECT_EQ(expected.segmentInNode, actual.segmentInNode); + EXPECT_EQ(expected.segmentOutNode, actual.segmentOutNode); + EXPECT_EQ(expected.segmentInInletArea, actual.segmentInInletArea); + EXPECT_EQ(expected.segmentInOutletArea, actual.segmentInOutletArea); + EXPECT_EQ(expected.segmentInFlow, actual.segmentInFlow); + EXPECT_EQ(expected.segmentMatName, actual.segmentMatName); + EXPECT_EQ(expected.segmentLossType, actual.segmentLossType); + EXPECT_EQ(expected.segmentBranchAngle, actual.segmentBranchAngle); + EXPECT_EQ(expected.segmentUpstreamSegment, actual.segmentUpstreamSegment); + EXPECT_EQ(expected.segmentBranchSegment, actual.segmentBranchSegment); + EXPECT_EQ(expected.segmentBoundType, actual.segmentBoundType); + EXPECT_EQ(expected.segmentDataTableName, actual.segmentDataTableName); + + // Compare solver options + EXPECT_EQ(expected.timeStep, actual.timeStep); + EXPECT_EQ(expected.stepSize, actual.stepSize); + EXPECT_EQ(expected.maxStep, actual.maxStep); + EXPECT_EQ(expected.quadPoints, actual.quadPoints); + EXPECT_EQ(expected.inletDataTableName, actual.inletDataTableName); + EXPECT_EQ(expected.boundaryType, actual.boundaryType); + EXPECT_EQ(expected.convergenceTolerance, actual.convergenceTolerance); + EXPECT_EQ(expected.useIV, actual.useIV); + EXPECT_EQ(expected.useStab, actual.useStab); + // For now, we're not going to verify the outputType. Why not? Because, currently + // the legacy serializer does not record the outputType. Instead, it stores it + // in the global settings. + // + // A more consistent behavior would be to store it within the options like the + // other settings and update clients to use it. Once that's done, we can test it here + // in a consistent way. +} + +std::string readFileContents(const std::string& filePath) { + std::ifstream inputFile(filePath); + if (!inputFile) { + throw std::runtime_error("Could not open the file: " + filePath); + } + + return std::string((std::istreambuf_iterator(inputFile)), + std::istreambuf_iterator()); +} \ No newline at end of file diff --git a/Tests/UnitTests/OptionsTestHelpers.hpp b/Tests/UnitTests/OptionsTestHelpers.hpp new file mode 100644 index 0000000..7829f2f --- /dev/null +++ b/Tests/UnitTests/OptionsTestHelpers.hpp @@ -0,0 +1,7 @@ +#include "cvOneDOptions.h" + +// Compares two sets of options using gtest macros +void expectEqOptions(const cvOneD::options& actual, const cvOneD::options& expected); + +// Read the file contents to a string +std::string readFileContents(const std::string& filePath); \ No newline at end of file diff --git a/Tests/UnitTests/TestFiles/TestInput.json b/Tests/UnitTests/TestFiles/TestInput.json new file mode 100644 index 0000000..e4adfa6 --- /dev/null +++ b/Tests/UnitTests/TestFiles/TestInput.json @@ -0,0 +1,215 @@ +{ + "modelName": "MyModel", + "solverOptions": { + "timeStep": 0.001087, + "stepSize": 10, + "maxStep": 100, + "quadPoints": 2, + "inletDataTableName": "STEADY_FLOW", + "boundaryType": "FLOW", + "convergenceTolerance": 1e-06, + "useIV": 1, + "useStab": 0, + "outputType": "NONE" + }, + "materials": [ + { + "name": "Material1", + "type": "Type1", + "density": 1000.0, + "viscosity": 0.3, + "pRef": 1.0, + "exponent": 0.5, + "param1": 10.0, + "param2": 30.0, + "param3": 50.0 + }, + { + "name": "Material2", + "type": "Type2", + "density": 1200.0, + "viscosity": 0.4, + "pRef": 1.2, + "exponent": 0.6, + "param1": 20.0, + "param2": 40.0, + "param3": 60.0 + } + ], + "nodes": [ + { + "name": "Node1", + "x": 1.1, + "y": 4.4, + "z": 7.7 + }, + { + "name": "Node2", + "x": 2.2, + "y": 5.5, + "z": 8.8 + }, + { + "name": "Node3", + "x": 3.3, + "y": 6.6, + "z": 9.9 + } + ], + "joints": [ + { + "joint": { + "id": "J1", + "associatedNode": "Node1", + "inletName": "IN1", + "outletName": "OUT1" + }, + "jointInlet": { + "name": "IN1", + "totalSegments": 1, + "segments": [ + 0 + ] + }, + "jointOutlet": { + "name": "OUT1", + "totalSegments": 1, + "segments": [ + 1 + ] + } + }, + { + "joint": { + "id": "J2", + "associatedNode": "Node2", + "inletName": "IN2", + "outletName": "OUT2" + }, + "jointInlet": { + "name": "IN2", + "totalSegments": 1, + "segments": [ + 1 + ] + }, + "jointOutlet": { + "name": "OUT2", + "totalSegments": 1, + "segments": [ + 2 + ] + } + }, + { + "joint": { + "id": "J3", + "associatedNode": "Node3", + "inletName": "IN3", + "outletName": "OUT3" + }, + "jointInlet": { + "name": "IN3", + "totalSegments": 1, + "segments": [ + 2 + ] + }, + "jointOutlet": { + "name": "OUT3", + "totalSegments": 1, + "segments": [ + 3 + ] + } + } + ], + "segments": [ + { + "name": "Aorta", + "id": 0, + "length": 17.670671, + "totalElements": 50, + "inNode": 0, + "outNode": 1, + "inletArea": 5.027254990390394, + "outletArea": 1.6068894493599328, + "flow": 0.0, + "materialName": "MAT1", + "lossType": "NONE", + "branchAngle": 0.0, + "upstreamSegment": 0, + "branchSegment": 0, + "boundaryType": "NOBOUND", + "dataTableName": "NONE" + }, + { + "name": "iliacR", + "id": 1, + "length": 12.997461, + "totalElements": 50, + "inNode": 1, + "outNode": 3, + "inletArea": 1.55, + "outletArea": 0.3525652531134944, + "flow": 0.0, + "materialName": "MAT1", + "lossType": "NONE", + "branchAngle": 0.0, + "upstreamSegment": 0, + "branchSegment": 0, + "boundaryType": "RCR", + "dataTableName": "RCR_VALS" + }, + { + "name": "iliacL", + "id": 2, + "length": 12.997461, + "totalElements": 50, + "inNode": 1, + "outNode": 2, + "inletArea": 1.55, + "outletArea": 0.3525652531134944, + "flow": 0.0, + "materialName": "MAT1", + "lossType": "NONE", + "branchAngle": 0.0, + "upstreamSegment": 0, + "branchSegment": 0, + "boundaryType": "RCR", + "dataTableName": "RCR_VALS" + } + ], + "dataTables": [ + { + "name": "R_VALS", + "type": "LIST", + "values": [ + 0.0, + 991.36 + ] + }, + { + "name": "STEADY_FLOW", + "type": "LIST", + "values": [ + 0.0, + 7.985, + 1.0, + 7.985 + ] + }, + { + "name": "PULS_FLOW", + "type": "LIST", + "values": [ + 0.0, + 0.0, + 0.019668108360095, + -4.11549971450822, + 0.055247073448669, + -7.16517105402019 + ] + } + ] +} \ No newline at end of file diff --git a/Tests/UnitTests/TestJsonSerialization.cxx b/Tests/UnitTests/TestJsonSerialization.cxx new file mode 100644 index 0000000..e8de928 --- /dev/null +++ b/Tests/UnitTests/TestJsonSerialization.cxx @@ -0,0 +1,108 @@ +#include +#include + +#include + +#include "cvOneDOptions.h" +#include "cvOneDOptionsJsonParser.h" +#include "cvOneDOptionsJsonSerializer.h" + +#include "OptionsTestHelpers.hpp" + +// Test function to demonstrate the serialization +cvOneD::options test_input_options(){ + cvOneD::options opts; + + opts.modelName = "MyModel"; + opts.nodeName = {"Node1", "Node2", "Node3"}; + opts.nodeXcoord = {1.1, 2.2, 3.3}; + opts.nodeYcoord = {4.4, 5.5, 6.6}; + opts.nodeZcoord = {7.7, 8.8, 9.9}; + + // Joint Data + opts.jointName = {"J1", "J2", "J3"}; + opts.jointNode = {"Node1", "Node2", "Node3"}; + opts.jointInletName = {"IN1", "IN2", "IN3"}; + opts.jointOutletName = {"OUT1", "OUT2", "OUT3"}; + + opts.jointInletListNames = {"IN1", "IN2", "IN3"}; + opts.jointInletListNumber = {1, 1, 1}; + opts.jointInletList = {{0}, {1}, {2}}; + + opts.jointOutletListNames = {"OUT1", "OUT2", "OUT3"}; + opts.jointOutletListNumber = {1, 1, 1}; + opts.jointOutletList = {{1}, {2}, {3}}; + + // Material Data + opts.materialName = {"Material1", "Material2"}; + opts.materialType = {"Type1", "Type2"}; + opts.materialDensity = {1000.0, 1200.0}; + opts.materialViscosity = {0.3, 0.4}; + opts.materialPRef = {1.0, 1.2}; + opts.materialExponent = {0.5, 0.6}; + opts.materialParam1 = {10.0, 20.0}; + opts.materialParam2 = {30.0, 40.0}; + opts.materialParam3 = {50.0, 60.0}; + + // Data tables + opts.dataTableName = {"R_VALS", "STEADY_FLOW", "PULS_FLOW"}; + opts.dataTableType = {"LIST", "LIST", "LIST"}; + opts.dataTableVals = { + {0.0, 991.36}, + {0.0, 7.985, 1.0, 7.985}, + {0.0, 0.0, 0.019668108360095, -4.11549971450822, 0.055247073448669, -7.16517105402019} + }; + + // Segment Data + opts.segmentName = {"Aorta", "iliacR", "iliacL"}; + opts.segmentID = {0, 1, 2}; + opts.segmentLength = {17.670671, 12.997461, 12.997461}; + opts.segmentTotEls = {50, 50, 50}; + opts.segmentInNode = {0, 1, 1}; + opts.segmentOutNode = {1, 3, 2}; + opts.segmentInInletArea = {5.027254990390394, 1.55, 1.55}; + opts.segmentInOutletArea = {1.6068894493599328, 0.3525652531134944, 0.3525652531134944}; + opts.segmentInFlow = {0.0, 0.0, 0.0}; + opts.segmentMatName = {"MAT1", "MAT1", "MAT1"}; + opts.segmentLossType = {"NONE", "NONE", "NONE"}; + opts.segmentBranchAngle = {0.0, 0.0, 0.0}; + opts.segmentUpstreamSegment = {0, 0, 0}; + opts.segmentBranchSegment = {0, 0, 0}; + opts.segmentBoundType = {"NOBOUND", "RCR", "RCR"}; + opts.segmentDataTableName = {"NONE", "RCR_VALS", "RCR_VALS"}; + + // Solver Options + opts.timeStep = 0.001087; + opts.stepSize = 10; + opts.maxStep = 100; + opts.quadPoints = 2; + opts.inletDataTableName = "STEADY_FLOW"; + opts.boundaryType = "FLOW"; + opts.convergenceTolerance = 1.0e-6; + opts.useIV = 1; + opts.useStab = 0; + opts.outputType = "NONE"; + + return opts; +} + +TEST(JsonParser, deserialize){ + std::string inputFilename {"TestFiles/TestInput.json"}; + auto const expOptions = test_input_options(); + + auto const actOptions = cvOneD::readJsonOptions(inputFilename); + expectEqOptions(actOptions, expOptions); +} + +TEST(JsonParser, serialize){ + // For now, we're just going to verify that the JSON output + // exactly matches the contents of the file. + std::string jsonFilename {"TestFiles/TestInput.json"}; + auto const expJsonStr = readFileContents(jsonFilename); + std::string actFilename{"output.json"}; + + cvOneD::writeJsonOptions(test_input_options(),actFilename); + auto const actJsonStr = readFileContents(actFilename); + + EXPECT_EQ(actJsonStr, expJsonStr); +} \ No newline at end of file diff --git a/Tests/UnitTests/TestLegacySerializer.cxx b/Tests/UnitTests/TestLegacySerializer.cxx index 48f27e8..5f1957a 100644 --- a/Tests/UnitTests/TestLegacySerializer.cxx +++ b/Tests/UnitTests/TestLegacySerializer.cxx @@ -5,12 +5,13 @@ #include "cvOneDOptions.h" #include "cvOneDOptionsLegacySerializer.h" -cvOneDOptions simpleArteryOptions() { - cvOneDOptions options; +#include "OptionsTestHelpers.hpp" + +cvOneD::options simpleArteryOptions() { + cvOneD::options options; // MODEL options.modelName = "simpleArtery_Res_"; - options.modelNameDefined = true; // NODE options.nodeName = {"0", "1"}; @@ -80,17 +81,15 @@ cvOneDOptions simpleArteryOptions() { options.useIV = 1; options.useStab = 1; options.outputType = "TEXT"; - options.solverOptionDefined = true; return options; } -cvOneDOptions bifurcationOptions() { - cvOneDOptions options; +cvOneD::options bifurcationOptions() { + cvOneD::options options; // MODEL options.modelName = "results_bifurcation_R_"; - options.modelNameDefined = true; // NODE options.nodeName = {"0", "1", "2", "3"}; @@ -101,14 +100,7 @@ cvOneDOptions bifurcationOptions() { // JOINT DATA options.jointName = {"JOINT1"}; options.jointNode = {"1"}; - - // The joint XYZ coords are not recorded. - // TODO: it's likely going to make sense to refacotr - // these in the future. - options.jointXcoord = {}; - options.jointYcoord = {}; - options.jointZcoord = {}; - + // Inlet and Outlet Connections for the Joint options.jointInletName = {"INSEGS"}; options.jointOutletName = {"OUTSEGS"}; @@ -209,96 +201,13 @@ cvOneDOptions bifurcationOptions() { options.useIV = 1; options.useStab = 1; options.outputType = "TEXT"; - options.solverOptionDefined = true; return options; } -void expectEqOptions(const cvOneDOptions& actual, const cvOneDOptions& expected) { - // Compare modelName - EXPECT_EQ(expected.modelName, actual.modelName); - EXPECT_EQ(expected.modelNameDefined, actual.modelNameDefined); - - // Compare node data - EXPECT_EQ(expected.nodeName, actual.nodeName); - EXPECT_EQ(expected.nodeXcoord, actual.nodeXcoord); - EXPECT_EQ(expected.nodeYcoord, actual.nodeYcoord); - EXPECT_EQ(expected.nodeZcoord, actual.nodeZcoord); - - // Compare joint data - EXPECT_EQ(expected.jointName, actual.jointName); - EXPECT_EQ(expected.jointNode, actual.jointNode); - EXPECT_EQ(expected.jointXcoord, actual.jointXcoord); - EXPECT_EQ(expected.jointYcoord, actual.jointYcoord); - EXPECT_EQ(expected.jointZcoord, actual.jointZcoord); - EXPECT_EQ(expected.jointInletName, actual.jointInletName); - EXPECT_EQ(expected.jointOutletName, actual.jointOutletName); - - // Compare joint inlet and outlet lists - EXPECT_EQ(expected.jointInletListNames, actual.jointInletListNames); - EXPECT_EQ(expected.jointInletListNumber, actual.jointInletListNumber); - EXPECT_EQ(expected.jointInletList, actual.jointInletList); - EXPECT_EQ(expected.jointOutletListNames, actual.jointOutletListNames); - EXPECT_EQ(expected.jointOutletListNumber, actual.jointOutletListNumber); - EXPECT_EQ(expected.jointOutletList, actual.jointOutletList); - - // Compare material data - EXPECT_EQ(expected.materialName, actual.materialName); - EXPECT_EQ(expected.materialType, actual.materialType); - EXPECT_EQ(expected.materialDensity, actual.materialDensity); - EXPECT_EQ(expected.materialViscosity, actual.materialViscosity); - EXPECT_EQ(expected.materialPRef, actual.materialPRef); - EXPECT_EQ(expected.materialExponent, actual.materialExponent); - EXPECT_EQ(expected.materialParam1, actual.materialParam1); - EXPECT_EQ(expected.materialParam2, actual.materialParam2); - EXPECT_EQ(expected.materialParam3, actual.materialParam3); - - // Compare data table information - EXPECT_EQ(expected.dataTableName, actual.dataTableName); - EXPECT_EQ(expected.dataTableType, actual.dataTableType); - EXPECT_EQ(expected.dataTableVals, actual.dataTableVals); - - // Compare segment data - EXPECT_EQ(expected.segmentName, actual.segmentName); - EXPECT_EQ(expected.segmentID, actual.segmentID); - EXPECT_EQ(expected.segmentLength, actual.segmentLength); - EXPECT_EQ(expected.segmentTotEls, actual.segmentTotEls); - EXPECT_EQ(expected.segmentInNode, actual.segmentInNode); - EXPECT_EQ(expected.segmentOutNode, actual.segmentOutNode); - EXPECT_EQ(expected.segmentInInletArea, actual.segmentInInletArea); - EXPECT_EQ(expected.segmentInOutletArea, actual.segmentInOutletArea); - EXPECT_EQ(expected.segmentInFlow, actual.segmentInFlow); - EXPECT_EQ(expected.segmentMatName, actual.segmentMatName); - EXPECT_EQ(expected.segmentLossType, actual.segmentLossType); - EXPECT_EQ(expected.segmentBranchAngle, actual.segmentBranchAngle); - EXPECT_EQ(expected.segmentUpstreamSegment, actual.segmentUpstreamSegment); - EXPECT_EQ(expected.segmentBranchSegment, actual.segmentBranchSegment); - EXPECT_EQ(expected.segmentBoundType, actual.segmentBoundType); - EXPECT_EQ(expected.segmentDataTableName, actual.segmentDataTableName); - - // Compare solver options - EXPECT_EQ(expected.timeStep, actual.timeStep); - EXPECT_EQ(expected.stepSize, actual.stepSize); - EXPECT_EQ(expected.maxStep, actual.maxStep); - EXPECT_EQ(expected.quadPoints, actual.quadPoints); - EXPECT_EQ(expected.inletDataTableName, actual.inletDataTableName); - EXPECT_EQ(expected.boundaryType, actual.boundaryType); - EXPECT_EQ(expected.convergenceTolerance, actual.convergenceTolerance); - EXPECT_EQ(expected.useIV, actual.useIV); - EXPECT_EQ(expected.useStab, actual.useStab); - EXPECT_EQ(expected.solverOptionDefined, actual.solverOptionDefined); - // For now, we're not going to verify the outputType. Why not? Because, currently - // the legacy serializer does not record the outputType. Instead, it stores it - // in the global settings. - // - // A more consistent behavior would be to store it within the options like the - // other settings and update clients to use it. Once that's done, we can test it here - // in a consistent way. -} - struct LegacySerializerTestParams { std::string filePath; - cvOneDOptions expOptions; + cvOneD::options expOptions; }; class LegacySerializerTest : public ::testing::TestWithParam {}; @@ -317,7 +226,7 @@ TEST_P(LegacySerializerTest, ParseSimpleFile) { const auto& params = GetParam(); // Execute and verify - cvOneDOptions actOptions; + cvOneD::options actOptions; cvOneD::readOptionsLegacyFormat(params.filePath, &actOptions); expectEqOptions(actOptions, params.expOptions); }