diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml new file mode 100644 index 0000000..13553a1 --- /dev/null +++ b/.github/workflows/cmake-multi-platform.yml @@ -0,0 +1,86 @@ +# This starter workflow is for a CMake project running on multiple platforms. There is a different +# starter workflow if you just want a single platform. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +name: CMake on multiple platforms + +on: + push: + branches: [ "development" ] + pull_request: + branches: [ "development" ] + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. + # Consider changing this to true when your workflow is stable. + fail-fast: false + + # Set up a matrix to run the following 3 configurations: + # 1. + # 2. + # 3. + # + # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. + matrix: + os: [ubuntu-latest, windows-latest] + build_type: [Release] + c_compiler: [gcc-13, clang, cl] + include: + - os: windows-latest + c_compiler: cl + cpp_compiler: cl + - os: ubuntu-latest + c_compiler: gcc-13 + cpp_compiler: g++-13 + #- os: ubuntu-latest + #c_compiler: clang + #cpp_compiler: clang++ + exclude: + - os: windows-latest + c_compiler: gcc-13 + - os: windows-latest + c_compiler: clang + - os: ubuntu-latest + c_compiler: cl + # Clang is broken on ubuntu-latest. C++20 brings in inconsistent libraries + - os: ubuntu-latest + c_compiler: clang + + steps: + - uses: actions/checkout@v3 + + - name: Install Qt + uses: jurplel/install-qt-action@v3 + with: + version: 6.6.2 + target: desktop + + - name: Set reusable strings + # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -S ${{ github.workspace }} + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} + + - name: Test + working-directory: ${{ steps.strings.outputs.build-output-dir }} + # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --build-config ${{ matrix.build_type }} -R 'qtrocket_*' diff --git a/.gitignore b/.gitignore index 045eed0..3575ea4 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,8 @@ build/ docs/doxygen/* # IDE +*.swp qtrocket.pro.user .qmake.stash +CMakeLists.txt.user diff --git a/CMakeLists.txt b/CMakeLists.txt index ece6ee2..b6cdec5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,14 +2,27 @@ cmake_minimum_required(VERSION 3.16) project(qtrocket VERSION 0.1 LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +enable_testing() include(FetchContent) +# Google Test framework +FetchContent_Declare(googletest + GIT_REPOSITORY https://github.com/google/googletest + GIT_TAG v1.13.0) +if(WIN32) + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +endif() +FetchContent_MakeAvailable(googletest) + # fmtlib dependency -FetchContent_Declare(fmt - GIT_REPOSITORY https://github.com/fmtlib/fmt - GIT_TAG 9.1.0) -FetchContent_MakeAvailable(fmt) +#FetchContent_Declare(fmt +# GIT_REPOSITORY https://github.com/fmtlib/fmt +# GIT_TAG 9.1.0) +#FetchContent_MakeAvailable(fmt) # jsoncpp dependency FetchContent_Declare(jsoncpp @@ -36,18 +49,30 @@ if(WIN32) endif() FetchContent_MakeAvailable(CURL) +# eigen dependency +FetchContent_Declare(Eigen + GIT_REPOSITORY https://gitlab.com/libeigen/eigen + GIT_TAG 3.4.0) +FetchContent_MakeAvailable(Eigen) + +# boost dependency + +FetchContent_Declare(Boost + GIT_REPOSITORY https://github.com/boostorg/boost + GIT_TAG boost-1.84.0) +set(BOOST_INCLUDE_LIBRARIES property_tree) +FetchContent_MakeAvailable(Boost) +# Add qtrocket subdirectories. These are components that will be linked in set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) if(WIN32) set(CMAKE_PREFIX_PATH $ENV{QTDIR}) - include_directories("C:\\boost\\boost_1_82_0\\") +# include_directories("C:\\boost\\boost_1_82_0\\") # find_package(Qt6Core REQUIRED) # find_package(Qt6Widgets REQUIRED) endif() @@ -84,61 +109,6 @@ set(PROJECT_SOURCES gui/ThrustCurveMotorSelector.ui gui/qcustomplot.cpp gui/qcustomplot.h - model/MotorCase.cpp - model/MotorCase.h - model/MotorModel.cpp - model/MotorModel.h - model/MotorModelDatabase.cpp - model/MotorModelDatabase.h - model/Rocket.cpp - model/Rocket.h - model/ThrustCurve.cpp - model/ThrustCurve.h - sim/AtmosphericModel.h - sim/ConstantAtmosphere.h - sim/ConstantGravityModel.cpp - sim/ConstantGravityModel.h - sim/DESolver.h - sim/Environment.h - sim/GeoidModel.h - sim/GravityModel.cpp - sim/GravityModel.h - sim/Propagator.cpp - sim/Propagator.h - sim/RK4Solver.h - sim/SphericalGeoidModel.cpp - sim/SphericalGeoidModel.h - sim/SphericalGravityModel.cpp - sim/SphericalGravityModel.h - sim/StateData.cpp - sim/StateData.h - sim/USStandardAtmosphere.cpp - sim/USStandardAtmosphere.h - sim/WindModel.cpp - sim/WindModel.h - utils/BinMap.cpp - utils/BinMap.h - utils/CurlConnection.cpp - utils/CurlConnection.h - utils/Logger.cpp - utils/Logger.h - utils/MotorModelDatabase.cpp - utils/MotorModelDatabase.h - utils/RSEDatabaseLoader.cpp - utils/RSEDatabaseLoader.h - utils/ThreadPool.cpp - utils/ThreadPool.h - utils/ThrustCurveAPI.cpp - utils/ThrustCurveAPI.h - utils/Triplet.h - utils/TSQueue.h - utils/math/Constants.h - utils/math/Quaternion.cpp - utils/math/Quaternion.h - utils/math/UtilityMathFunctions.cpp - utils/math/UtilityMathFunctions.h - utils/math/Vector3.cpp - utils/math/Vector3.h ${TS_FILES} ) @@ -167,11 +137,26 @@ else() ) endif() - #qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) endif() -#target_link_libraries(qtrocket PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${Qt_VERSION_MAJOR}::PrintSupport libcurl jsoncpp_static fmt::fmt-header-only) -target_link_libraries(qtrocket PRIVATE Qt6::Widgets Qt6::PrintSupport libcurl jsoncpp_static fmt::fmt-header-only) +add_subdirectory(utils) +add_subdirectory(sim) +add_subdirectory(model) + +#target_link_libraries(qtrocket PRIVATE +# Qt6::Widgets +# Qt6::PrintSupport +# libcurl +# jsoncpp_static +# fmt::fmt-header-only +# eigen) + +target_link_libraries(qtrocket PRIVATE + Qt6::Widgets + Qt6::PrintSupport + utils + sim + model) set_target_properties(qtrocket PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com diff --git a/CMakeLists.txt.user b/CMakeLists.txt.user deleted file mode 100644 index 5fc28a1..0000000 --- a/CMakeLists.txt.user +++ /dev/null @@ -1,416 +0,0 @@ - - - - - - EnvironmentId - {126c64d7-12e8-468a-ad3f-06f0fbdaeca1} - - - ProjectExplorer.Project.ActiveTarget - 0 - - - ProjectExplorer.Project.EditorSettings - - true - false - true - - Cpp - - CppGlobal - - - - QmlJS - - QmlJSGlobal - - - 2 - UTF-8 - false - 4 - false - 80 - true - true - 1 - false - true - false - 1 - true - true - 0 - 8 - true - false - 1 - true - true - true - *.md, *.MD, Makefile - false - true - true - - - - ProjectExplorer.Project.PluginSettings - - - true - false - true - true - true - true - - - 0 - true - - true - true - Builtin.DefaultTidyAndClazy - 6 - - - - true - - - - - ProjectExplorer.Project.Target.0 - - Desktop - Desktop Qt6 - Desktop Qt6 - {834bc66d-170a-454c-a2b0-c17798dc7b12} - 0 - 0 - 0 - - Debug - 2 - false - - -DCMAKE_GENERATOR:STRING=Ninja --DCMAKE_BUILD_TYPE:STRING=Debug --DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{buildDir}/.qtc/package-manager/auto-setup.cmake --DQT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} --DCMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} --DCMAKE_C_COMPILER:STRING=%{Compiler:Executable:C} --DCMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx} --DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} - 0 - /home/travis/build-qtrocket-Desktop_Qt6-Debug - - - - - all - - false - - true - Build - CMakeProjectManager.MakeStep - - 1 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - - - clean - - false - - true - Build - CMakeProjectManager.MakeStep - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - false - - Debug - CMakeProjectManager.CMakeBuildConfiguration - - - Release - 2 - false - - -DCMAKE_GENERATOR:STRING=Ninja --DCMAKE_BUILD_TYPE:STRING=Release --DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{buildDir}/.qtc/package-manager/auto-setup.cmake --DQT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} --DCMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} --DCMAKE_C_COMPILER:STRING=%{Compiler:Executable:C} --DCMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx} --DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} - /home/travis/build-qtrocket-Desktop_Qt6-Release - - - - - all - - false - - true - CMakeProjectManager.MakeStep - - 1 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - - - clean - - false - - true - CMakeProjectManager.MakeStep - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - false - - Release - CMakeProjectManager.CMakeBuildConfiguration - - - RelWithDebInfo - 2 - false - - -DCMAKE_GENERATOR:STRING=Ninja --DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo --DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{buildDir}/.qtc/package-manager/auto-setup.cmake --DQT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} --DCMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} --DCMAKE_C_COMPILER:STRING=%{Compiler:Executable:C} --DCMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx} --DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} - /home/travis/build-qtrocket-Desktop_Qt6-RelWithDebInfo - - - - - all - - false - - true - CMakeProjectManager.MakeStep - - 1 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - - - clean - - false - - true - CMakeProjectManager.MakeStep - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - false - - Release with Debug Information - CMakeProjectManager.CMakeBuildConfiguration - - - RelWithDebInfo - 2 - false - - -DCMAKE_GENERATOR:STRING=Ninja --DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo --DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{buildDir}/.qtc/package-manager/auto-setup.cmake --DQT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} --DCMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} --DCMAKE_C_COMPILER:STRING=%{Compiler:Executable:C} --DCMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx} --DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} - 0 - /home/travis/build-qtrocket-Desktop_Qt6-Profile - - - - - all - - false - - true - CMakeProjectManager.MakeStep - - 1 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - - - clean - - false - - true - CMakeProjectManager.MakeStep - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - false - - Profile - CMakeProjectManager.CMakeBuildConfiguration - - - MinSizeRel - 2 - false - - -DCMAKE_GENERATOR:STRING=Ninja --DCMAKE_BUILD_TYPE:STRING=MinSizeRel --DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{buildDir}/.qtc/package-manager/auto-setup.cmake --DQT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} --DCMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} --DCMAKE_C_COMPILER:STRING=%{Compiler:Executable:C} --DCMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx} --DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} - /home/travis/build-qtrocket-Desktop_Qt6-MinSizeRel - - - - - all - - false - - true - CMakeProjectManager.MakeStep - - 1 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - - - clean - - false - - true - CMakeProjectManager.MakeStep - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - false - - Minimum Size Release - CMakeProjectManager.CMakeBuildConfiguration - - 5 - - - 0 - Deploy - Deploy - ProjectExplorer.BuildSteps.Deploy - - 1 - - false - ProjectExplorer.DefaultDeployConfiguration - - 1 - - true - true - true - - 2 - - qtrocket - CMakeProjectManager.CMakeRunConfiguration.qtrocket - qtrocket - false - true - true - false - true - /home/travis/build-qtrocket-Desktop_Qt6-Debug - - 1 - - - - ProjectExplorer.Project.TargetCount - 1 - - - ProjectExplorer.Project.Updater.FileVersion - 22 - - - Version - 22 - - diff --git a/QtRocket.cpp b/QtRocket.cpp index eca81cb..b544ed9 100644 --- a/QtRocket.cpp +++ b/QtRocket.cpp @@ -1,4 +1,3 @@ - /// \cond // C headers // C++ headers @@ -45,7 +44,7 @@ void guiWorker(int argc, char* argv[], int& ret) // Go! MainWindow w(QtRocket::getInstance()); - logger->info("Showing MainWindow"); + logger->debug("Showing MainWindow"); w.show(); ret = a.exec(); @@ -65,7 +64,7 @@ void QtRocket::init() std::lock_guard lck(mtx); if(!initialized) { - utils::Logger::getInstance()->info("Instantiating new QtRocket"); + utils::Logger::getInstance()->debug("Instantiating new QtRocket"); instance = new QtRocket(); initialized = true; } @@ -81,10 +80,20 @@ QtRocket::QtRocket() setEnvironment(std::make_shared()); rocket.first = - std::make_shared(); + std::make_shared(); + + rocket.second = + std::make_shared(rocket.first); motorDatabase = std::make_shared(); + logger->debug("Initial states vector size: " + std::to_string(states.capacity()) ); + // Reserve at least 1024 spaces for StateData + if(states.capacity() < 1024) + { + states.reserve(1024); + } + logger->debug("New states vector size: " + std::to_string(states.capacity()) ); } int QtRocket::run(int argc, char* argv[]) @@ -101,12 +110,21 @@ int QtRocket::run(int argc, char* argv[]) return 0; } +void QtRocket::launchRocket() +{ + // initialize the propagator + rocket.first->clearStates(); + rocket.second->setCurrentTime(0.0); + + // start the rocket motor + rocket.first->launch(); + + // run the propagator until it terminates + rocket.second->runUntilTerminate(); +} + void QtRocket::addMotorModels(std::vector& m) { - for(const auto& i : m) - { - motorModels.push_back(i); - } - motorDatabase->addMotorModels(motorModels); + motorDatabase->addMotorModels(m); // TODO: Now clear any duplicates? } diff --git a/QtRocket.h b/QtRocket.h index ef21c17..a9efd7b 100644 --- a/QtRocket.h +++ b/QtRocket.h @@ -14,13 +14,12 @@ // qtrocket headers #include "model/MotorModel.h" -#include "model/Rocket.h" -#include "sim/AtmosphericModel.h" -#include "sim/GravityModel.h" +#include "model/RocketModel.h" #include "sim/Environment.h" #include "sim/Propagator.h" #include "utils/Logger.h" #include "utils/MotorModelDatabase.h" +#include "utils/math/MathTypes.h" /** * @brief The QtRocket class is the master controller for the QtRocket application. @@ -44,18 +43,29 @@ public: std::shared_ptr getEnvironment() { return environment; } void setTimeStep(double t) { rocket.second->setTimeStep(t); } - std::shared_ptr getRocket() { return rocket.first; } + std::shared_ptr getRocket() { return rocket.first; } std::shared_ptr getMotorDatabase() { return motorDatabase; } void addMotorModels(std::vector& m); - const std::vector& getMotorModels() const { return motorModels; } - - void addRocket(std::shared_ptr r) { rocket.first = r; } + void addRocket(std::shared_ptr r) { rocket.first = r; rocket.second = std::make_shared(r); } void setEnvironment(std::shared_ptr e) { environment = e; } + void launchRocket(); + /** + * @brief getStates returns a vector of time/state pairs generated during launch() + * @return vector of pairs of doubles, where the first value is a time and the second a state vector + */ + const std::vector>& getStates() const { return rocket.first->getStates(); } + + /** + * @brief setInitialState sets the initial state of the Rocket. + * @param initState initial state vector (x, y, z, xDot, yDot, zDot, pitch, yaw, roll, pitchDot, yawDot, rollDot) + */ + void setInitialState(const StateData& initState) { rocket.first->setInitialState(initState); } + private: QtRocket(); @@ -66,16 +76,21 @@ private: static std::mutex mtx; static QtRocket* instance; - // Motor "database(s)" - std::vector motorModels; - utils::Logger* logger; - std::pair, std::shared_ptr> rocket; + using Rocket = std::pair, std::shared_ptr>; + Rocket rocket; std::shared_ptr environment; std::shared_ptr motorDatabase; + // Launch site + // ECEF coordinates + Vector3 launchSitePosition{0.0, 0.0, 0.0}; + + // Table of state data + std::vector states; + }; #endif // QTROCKET_H diff --git a/gui/AnalysisWindow.cpp b/gui/AnalysisWindow.cpp index 3a47076..7d0e783 100644 --- a/gui/AnalysisWindow.cpp +++ b/gui/AnalysisWindow.cpp @@ -35,8 +35,8 @@ AnalysisWindow::~AnalysisWindow() void AnalysisWindow::onButton_plotAltitude_clicked() { - std::shared_ptr rocket = QtRocket::getInstance()->getRocket(); - const std::vector>>& res = rocket->getStates(); + QtRocket* qtRocket = QtRocket::getInstance(); + const std::vector>& res = qtRocket->getStates(); auto& plot = ui->plotWidget; plot->clearGraphs(); plot->setInteraction(QCP::iRangeDrag, true); @@ -46,7 +46,7 @@ void AnalysisWindow::onButton_plotAltitude_clicked() for (int i = 0; i < tData.size(); ++i) { tData[i] = res[i].first; - zData[i] = res[i].second[2]; + zData[i] = res[i].second.position[2]; } // create graph and assign data to it: plot->addGraph(); @@ -62,8 +62,8 @@ void AnalysisWindow::onButton_plotAltitude_clicked() void AnalysisWindow::onButton_plotVelocity_clicked() { - std::shared_ptr rocket = QtRocket::getInstance()->getRocket(); - const std::vector>>& res = rocket->getStates(); + QtRocket* qtRocket = QtRocket::getInstance(); + const std::vector>& res = qtRocket->getStates(); auto& plot = ui->plotWidget; plot->clearGraphs(); plot->setInteraction(QCP::iRangeDrag, true); @@ -74,7 +74,7 @@ void AnalysisWindow::onButton_plotVelocity_clicked() for (int i = 0; i < tData.size(); ++i) { tData[i] = res[i].first; - zData[i] = res[i].second[5]; + zData[i] = res[i].second.velocity[2]; } // create graph and assign data to it: plot->addGraph(); @@ -91,8 +91,8 @@ void AnalysisWindow::onButton_plotVelocity_clicked() void AnalysisWindow::onButton_plotMotorCurve_clicked() { - std::shared_ptr rocket = QtRocket::getInstance()->getRocket(); - model::MotorModel motor = rocket->getCurrentMotorModel(); + std::shared_ptr rocket = QtRocket::getInstance()->getRocket(); + model::MotorModel motor = rocket->getMotorModel(); ThrustCurve tc = motor.getThrustCurve(); @@ -122,4 +122,4 @@ void AnalysisWindow::onButton_plotMotorCurve_clicked() plot->yAxis->setRange(*std::min_element(std::begin(fData), std::end(fData)), *std::max_element(std::begin(fData), std::end(fData))); plot->replot(); -} \ No newline at end of file +} diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp index 0184fd0..12ec10a 100644 --- a/gui/MainWindow.cpp +++ b/gui/MainWindow.cpp @@ -20,7 +20,7 @@ #include "gui/MainWindow.h" #include "gui/ThrustCurveMotorSelector.h" #include "gui/SimOptionsWindow.h" -#include "model/Rocket.h" +#include "model/RocketModel.h" #include "utils/RSEDatabaseLoader.h" @@ -83,6 +83,7 @@ MainWindow::MainWindow(QtRocket* _qtRocket, QWidget *parent) this, SLOT(onButton_getTCMotorData_clicked())); + ui->calculateTrajectory_btn->setDisabled(true); } MainWindow::~MainWindow() @@ -122,12 +123,16 @@ void MainWindow::onButton_calculateTrajectory_clicked() double initialVelocityX = initialVelocity * std::cos(initialAngle / 57.2958); double initialVelocityZ = initialVelocity * std::sin(initialAngle / 57.2958); - std::vector initialState = {0.0, 0.0, 0.0, initialVelocityX, 0.0, initialVelocityZ, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + //std::vector initialState = {0.0, 0.0, 0.0, initialVelocityX, 0.0, initialVelocityZ, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + StateData initialState; + initialState.position = {0.0, 0.0, 0.0}; + initialState.velocity = {initialVelocityX, 0.0, initialVelocityZ}; auto rocket = QtRocket::getInstance()->getRocket(); - rocket->setInitialState(initialState); rocket->setMass(mass); rocket->setDragCoefficient(dragCoeff); - rocket->launch(); + + qtRocket->setInitialState(initialState); + qtRocket->launchRocket(); AnalysisWindow aWindow; aWindow.setModal(false); @@ -186,7 +191,13 @@ void MainWindow::onButton_setMotor_clicked() QString motorName = ui->engineSelectorComboBox->currentText(); model::MotorModel mm = rseDatabase->getMotorModelByName(motorName.toStdString()); QtRocket::getInstance()->getRocket()->setMotorModel(mm); - QtRocket::getInstance()->addMotorModels(rseDatabase->getMotors()); + + // Now that we have a motor selected, we can enable the calculateTrajectory button + ui->calculateTrajectory_btn->setDisabled(false); + + /// TODO: Figure out if this is the right place to populate the motor database + /// or from RSEDatabaseLoader where it currently is populated. + //QtRocket::getInstance()->addMotorModels(rseDatabase->getMotors()); } diff --git a/main.cpp b/main.cpp index e0d1885..379e1b5 100644 --- a/main.cpp +++ b/main.cpp @@ -1,11 +1,9 @@ - /// \cond // C headers // C++ headers // 3rd party headers /// \endcond - #include "QtRocket.h" #include "utils/Logger.h" @@ -14,54 +12,16 @@ int main(int argc, char *argv[]) // Instantiate logger utils::Logger* logger = utils::Logger::getInstance(); - logger->setLogLevel(utils::Logger::DEBUG_); + logger->setLogLevel(utils::Logger::PERF_); + logger->info("Logger instantiated at PERF level"); // instantiate QtRocket + logger->debug("Starting QtRocket"); QtRocket* qtrocket = QtRocket::getInstance(); // Run QtRocket. This'll start the GUI thread and block until the user // exits the program + logger->debug("QtRocket->run()"); int retVal = qtrocket->run(argc, argv); logger->debug("Returning"); return retVal; } - - - -/* - * This was the old main when it directly started the QtGui. It worked. The new way of - * starting the gui through QtRocket->run() also seems to work, but I'm leaving this here for - * now in case there are unforeseen issues with starting the gui in a separate thread via - * QtRocket::run() - * - -#include "gui/MainWindow.h" - -#include -#include -#include - -int main(int argc, char* argv[]) -{ - QApplication a(argc, argv); - a.setWindowIcon(QIcon(":/qtrocket.png")); - - // Start translation component. - // TODO: Only support US English at the moment. Anyone want to help translate? - QTranslator translator; - const QStringList uiLanguages = QLocale::system().uiLanguages(); - for (const QString &locale : uiLanguages) - { - const QString baseName = "qtrocket_" + QLocale(locale).name(); - if (translator.load(":/i18n/" + baseName)) - { - a.installTranslator(&translator); - break; - } - } - - // Go! - MainWindow w; - w.show(); - return a.exec(); -} -*/ diff --git a/model/CMakeLists.txt b/model/CMakeLists.txt new file mode 100644 index 0000000..afa0fd1 --- /dev/null +++ b/model/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library(model + MotorModel.cpp + MotorModel.h + MotorModelDatabase.cpp + MotorModelDatabase.h + Part.cpp + Part.h + Propagatable.cpp + Propagatable.h + RocketModel.cpp + RocketModel.h + ThrustCurve.cpp + ThrustCurve.h + InertiaTensors.h) + +target_link_libraries(model PRIVATE + utils) + +# Unit tests +add_subdirectory(tests) diff --git a/model/InertiaTensors.h b/model/InertiaTensors.h new file mode 100644 index 0000000..e0d09dc --- /dev/null +++ b/model/InertiaTensors.h @@ -0,0 +1,70 @@ +#ifndef INERTIATENSORS_H +#define INERTIATENSORS_H + +#include "utils/math/MathTypes.h" + +namespace model +{ + +/** + * @brief The InertiaTensors class provides a collection of methods to + * deliver some common inertia tensors centered about the center of mass + */ +class InertiaTensors +{ +public: + +/** + * @brief SolidSphere + * @param radius (meters) + * @return + */ +static Matrix3 SolidSphere(double radius) +{ + double xx = 0.4*radius*radius; + double yy = xx; + double zz = xx; + return Matrix3{{xx, 0, 0}, + {0, yy, 0}, + {0, 0, zz}}; +} + +/** + * @brief HollowSphere + * @param radius (meters) + * @return + */ +static Matrix3 HollowSphere(double radius) +{ + double xx = (2.0/3.0)*radius*radius; + double yy = xx; + double zz = xx; + return Matrix3{{xx, 0, 0}, + {0, yy, 0}, + {0, 0, zz}}; +} + +/** + * @brief Tube - The longitudinal axis is the z-axis. Can also be used for a solid cylinder + * when innerRadius = 0.0 + * @param innerRadius (meters) + * @param outerRadius (meters) + * @param length (meters) + * @return + */ +static Matrix3 Tube(double innerRadius, double outerRadius, double length) +{ + double xx = (1.0/12.0)*(3.0*(innerRadius*innerRadius + outerRadius*outerRadius) + length*length); + double yy = xx; + double zz = (1.0/2.0)*(innerRadius*innerRadius + outerRadius*outerRadius); + return Matrix3{{xx, 0.0, 0.0}, + {0.0, yy, 0.0}, + {0.0, 0.0, zz}}; + +} + +}; + +} + +#endif // INERTIATENSORS_H diff --git a/model/MotorCase.cpp b/model/MotorCase.cpp deleted file mode 100644 index bb394dd..0000000 --- a/model/MotorCase.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "MotorCase.h" \ No newline at end of file diff --git a/model/MotorCase.h b/model/MotorCase.h deleted file mode 100644 index 5e965fa..0000000 --- a/model/MotorCase.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef MODEL_MOTORCASE_H -#define MODEL_MOTORCASE_H - -namespace model -{ -class MotorCase -{ -public: - MotorCase(); - ~MotorCase(); - -private: - -}; - -} // namespace model -#endif // MODEL_MOTORCASE_H diff --git a/model/MotorModel.cpp b/model/MotorModel.cpp index 63c393e..a3aecbd 100644 --- a/model/MotorModel.cpp +++ b/model/MotorModel.cpp @@ -60,6 +60,7 @@ double MotorModel::getMass(double simTime) const double propMassEnd = i->second; double slope = (propMassEnd - propMassStart) / (tEnd - tStart); double currentMass = emptyMass + propMassStart + (thrustTime - tStart) * slope; + utils::Logger::getInstance()->info("simTime: " + std::to_string(simTime) + ": motor mass: " + std::to_string(currentMass)); return currentMass; } diff --git a/model/MotorModel.h b/model/MotorModel.h index 72b08af..b6ea62c 100644 --- a/model/MotorModel.h +++ b/model/MotorModel.h @@ -406,7 +406,7 @@ public: MetaData data; private: bool ignitionOccurred{false}; - bool burnOutOccurred{false}; + bool burnOutOccurred{false}; double emptyMass; double isp; double maxTime; diff --git a/model/Part.cpp b/model/Part.cpp new file mode 100644 index 0000000..dedb0c7 --- /dev/null +++ b/model/Part.cpp @@ -0,0 +1,109 @@ +#include "Part.h" +#include "utils/Logger.h" + +namespace model +{ + +Part::Part(const std::string& n, + const Matrix3& I, + double m, + const Vector3& centerMass) + : parent(nullptr), + name(n), + inertiaTensor(I), + compositeInertiaTensor(I), + mass(m), + compositeMass(m), + cm(centerMass), + needsRecomputing(false), + childParts() +{ } + +Part::~Part() +{} + +Part::Part(const Part& orig) + : parent(orig.parent), + name(orig.name), + inertiaTensor(orig.inertiaTensor), + compositeInertiaTensor(orig.compositeInertiaTensor), + mass(orig.mass), + compositeMass(orig.compositeMass), + cm(orig.cm), + needsRecomputing(orig.needsRecomputing), + childParts() +{ + + // We are copying the whole tree. If the part we're copying itself has child + // parts, we are also copying all of them! This may be inefficient and not what + // is desired, but it is less likely to lead to weird bugs with the same part + // appearing in multiple locations of the rocket + utils::Logger::getInstance()->debug("Calling model::Part copy constructor. Recursively copying all child parts. Check Part names for uniqueness"); + + + for(const auto& i : orig.childParts) + { + Part& x = *std::get<0>(i); + std::shared_ptr tempPart = std::make_shared(x); + childParts.emplace_back(tempPart, std::get<1>(i));; + } + +} + +double Part::getChildMasses(double t) +{ + double childMasses{0.0}; + for(const auto& i : childParts) + { + childMasses += std::get<0>(i)->getMass(t); + } + return childMasses; + +} + +void Part::addChildPart(const Part& childPart, Vector3 position) +{ + + double childMass = childPart.compositeMass; + Matrix3 childInertiaTensor = childPart.compositeInertiaTensor; + std::shared_ptr newChild = std::make_shared(childPart); + // Set the parent pointer + newChild->parent = this; + + // Recompute inertia tensor + + childInertiaTensor += childMass * ( position.dot(position) * Matrix3::Identity() - position*position.transpose()); + + compositeInertiaTensor += childInertiaTensor; + compositeMass += childMass; + + childParts.emplace_back(std::move(newChild), std::move(position)); + + if(parent) + { + parent->markAsNeedsRecomputing(); + parent->recomputeInertiaTensor(); + } + +} + +void Part::recomputeInertiaTensor() +{ + if(!needsRecomputing) + { + return; + } + // recompute the whole composite inertia tensor + // Reset the composite inertia tensor + compositeInertiaTensor = inertiaTensor; + compositeMass = mass; + for(auto& [child, pos] : childParts) + { + child->recomputeInertiaTensor(); + compositeInertiaTensor += child->compositeInertiaTensor + child->compositeMass * ( pos.dot(pos) * Matrix3::Identity() - pos*pos.transpose()); + compositeMass += child->compositeMass; + } + needsRecomputing = false; +} + +} // namespace model diff --git a/model/Part.h b/model/Part.h new file mode 100644 index 0000000..9e63cf2 --- /dev/null +++ b/model/Part.h @@ -0,0 +1,136 @@ +#ifndef MODEL_PART_H +#define MODEL_PART_H + +/// \cond +// C headers +// C++ headers +#include +#include + +// 3rd party headers +/// \endcond + +// qtrocket headers +#include "utils/math/MathTypes.h" + +namespace model +{ + +class Part +{ +public: + Part(const std::string& name, + const Matrix3& I, + double m, + const Vector3& centerMass); + + virtual ~Part(); + + Part(const Part&); + + Part& operator=(Part other) + { + if(this != &other) + { + std::swap(parent, other.parent); + std::swap(name, other.name); + std::swap(inertiaTensor, other.inertiaTensor); + std::swap(compositeInertiaTensor, other.compositeInertiaTensor); + std::swap(mass, other.mass); + std::swap(compositeMass, other.compositeMass); + std::swap(cm, other.cm); + std::swap(needsRecomputing, other.needsRecomputing); + std::swap(childParts, other.childParts); + } + return *this; + } + Part& operator=(Part&& other) + { + parent = std::move(other.parent); + name = std::move(other.name); + inertiaTensor = std::move(other.inertiaTensor); + compositeInertiaTensor = std::move(other.compositeInertiaTensor); + mass = std::move(other.mass); + compositeMass = std::move(other.compositeMass); + cm = std::move(other.cm); + needsRecomputing = std::move(other.needsRecomputing); + childParts = std::move(other.childParts); + + return *this; + } + + void setMass(double m) { mass = m; } + + // Set the inertia tensor + void setI(const Matrix3& I) { inertiaTensor = I; } + Matrix3 getI() { return inertiaTensor; } + Matrix3 getCompositeI() { return compositeInertiaTensor; } + + void setCm(const Vector3& x) { cm = x; } + // Special version of setCM that assumes the cm lies along the body x-axis + void setCm(double x) { cm = {x, 0.0, 0.0}; } + + double getMass(double t) + { + return mass; + } + + double getCompositeMass(double t) + { + return compositeMass; + } + + /** + * @brief Add a child part to this part. + * + * @param childPart Child part to add + * @param position Relative position of the child part's center-of-mass w.r.t the + * parent's center of mass + */ + void addChildPart(const Part& childPart, Vector3 position); + + /** + * @brief Recomputes the inertia tensor. If the change is due to the change in inertia + * of a child part, an optional name of the child part can be given to + * only recompute that change rather than recompute all child inertia + * tensors + * + * @param name Optional name of the child part to recompute. If empty, it will + * recompute all child inertia tensors + */ + //void recomputeInertiaTensor(std::string name = ""); + void recomputeInertiaTensor(); +private: + + // This is a pointer to the parent Part, if it has one. Purpose is to be able to + // tell the parent if it needs to recompute anything if this part changes. e.g. + // if a change to this part's inertia tensor occurs, the parent needs to recompute + // it's total inertia tensor. + Part* parent{nullptr}; + + std::string name; + + double getChildMasses(double t); + void markAsNeedsRecomputing() + { needsRecomputing = true; if(parent) { parent->markAsNeedsRecomputing(); }} + + // Because a part is both a simple part and the composite of itself with all of it's children, + // we will keep track of this object's inertia tensor (without children), and the composite + // one with all of it's children attached + Matrix3 inertiaTensor; // moment of inertia tensor with respect to the part's center of mass and + Matrix3 compositeInertiaTensor; + double mass; // The moment of inertia tensor also has this, so don't double compute + double compositeMass; // The mass of this part along with all attached parts + + Vector3 cm; // center of mass wrt middle of component + + bool needsRecomputing{false}; + + /// @brief child parts and the relative positions of their center of mass w.r.t. + /// the center of mass of this part + std::vector, Vector3>> childParts; +}; + +} + +#endif // MODEL_PART_H diff --git a/model/Propagatable.cpp b/model/Propagatable.cpp new file mode 100644 index 0000000..e69de29 diff --git a/model/Propagatable.h b/model/Propagatable.h new file mode 100644 index 0000000..d8a8a6e --- /dev/null +++ b/model/Propagatable.h @@ -0,0 +1,58 @@ +#ifndef MODEL_PROPAGATABLE_H +#define MODEL_PROPAGATABLE_H + +/// \cond +// C headers +// C++ headers +#include +// 3rd party headers +/// \endcond + +// qtrocket headers +#include "sim/Aero.h" +#include "sim/StateData.h" +#include "utils/math/MathTypes.h" + +namespace model +{ + +class Propagatable +{ +public: + Propagatable() {} + virtual ~Propagatable() {} + + virtual Vector3 getForces(double t) = 0; + virtual Vector3 getTorques(double t) = 0; + + virtual double getMass(double t) = 0; + virtual Matrix3 getInertiaTensor(double t) = 0; + + virtual bool terminateCondition(double t) = 0; + + void setCurrentState(const StateData& st) { currentState = st; } + const StateData& getCurrentState() { return currentState; } + + const StateData& getInitialState() { return initialState; } + void setInitialState(const StateData& init) { initialState = init; } + + void appendState(double t, const StateData& st) { states.emplace_back(t, st); } + + const std::vector>& getStates() { return states; } + + void clearStates() { states.clear(); } + +protected: + + sim::Aero aeroData; + + StateData initialState; + StateData currentState; + StateData nextState; + + std::vector> states; +}; + +} + +#endif // MODEL_PROPAGATABLE_H diff --git a/model/Rocket.cpp b/model/Rocket.cpp deleted file mode 100644 index 1ce0e14..0000000 --- a/model/Rocket.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "Rocket.h" -#include "QtRocket.h" - -Rocket::Rocket() : propagator(this) -{ - -} - -void Rocket::launch() -{ - propagator.clearStates(); - propagator.setCurrentTime(0.0); - mm.startMotor(0.0); - propagator.runUntilTerminate(); -} - -void Rocket::setMotorModel(const model::MotorModel& motor) -{ - mm = motor; -} - -bool Rocket::terminateCondition(const std::pair>& cond) -{ - // Terminate propagation when the z coordinate drops below zero - if(cond.second[2] < 0) - return true; - else - return false; -} - -double Rocket::getThrust(double t) -{ - return mm.getThrust(t); -} diff --git a/model/Rocket.h b/model/Rocket.h deleted file mode 100644 index 72345a4..0000000 --- a/model/Rocket.h +++ /dev/null @@ -1,119 +0,0 @@ -#ifndef ROCKET_H -#define ROCKET_H - -/// \cond -// C headers -// C++ headers -#include -#include -#include // std::move - -// 3rd party headers -/// \endcond - -// qtrocket headers -#include "model/ThrustCurve.h" -#include "model/MotorModel.h" -#include "sim/Propagator.h" - -/** - * @brief The Rocket class holds all rocket components - * - */ -class Rocket -{ -public: - /** - * @brief Rocket class constructor - */ - Rocket(); - - /** - * @brief launch Propagates the Rocket object until termination, - * normally when altitude crosses from positive to negative - */ - void launch(); - - /** - * @brief getStates returns a vector of time/state pairs generated during launch() - * @return vector of pairs of doubles, where the first value is a time and the second a state vector - */ - const std::vector>>& getStates() const { return propagator.getStates(); } - - /** - * @brief setInitialState sets the initial state of the Rocket. - * @param initState initial state vector (x, y, z, xDot, yDot, zDot, pitch, yaw, roll, pitchDot, yawDot, rollDot) - */ - void setInitialState(const std::vector& initState) { propagator.setInitialState(initState); } - - /** - * @brief getMass returns the current mass of the rocket. This is the sum of all components' masses - * @return total current mass of the Rocket - */ - double getMass(double simTime) const { return mass + mm.getMass(simTime); } - - /** - * @brief setMass sets the current total mass of the Rocket - * @param m total Rocket mass - * @todo This should be dynamically computed, not set. Fix this - */ - void setMass(double m) { mass = m;} - - /** - * @brief setDragCoefficient sets the current total drag coefficient of the Rocket - * @param d drag coefficient - * @todo This should be dynamically computed, not set. Fix this - */ - void setDragCoefficient(double d) { dragCoeff = d; } - - /** - * @brief getDragCoefficient returns the current drag coefficient - * - * This is intended to be called by the propagator during propagation. - * @return the coefficient of drag - */ - double getDragCoefficient() const { return dragCoeff; } - - /** - * @brief getThrust returns current motor thrust - * @param t current simulation time - * @return thrust in Newtons - */ - double getThrust(double t); - - /** - * @brief setMotorModel - * @param motor - */ - void setMotorModel(const model::MotorModel& motor); - - /** - * @brief Returns the current motor model. - * @return The current motor model - */ - const model::MotorModel& getCurrentMotorModel() const { return mm; } - - /** - * @brief terminateCondition returns true or false, whether the passed-in time/state matches the terminate condition - * @param cond time/state pair - * @return true if the passed-in time/state satisfies the terminate condition - */ - bool terminateCondition(const std::pair>& cond); - - /** - * @brief setName sets the rocket name - * @param n name to set the Rocket - */ - void setName(const std::string& n) { name = n; } -private: - - std::string name; /// Rocket name - sim::Propagator propagator; /// propagator - double dragCoeff; /// @todo get rid of this, should be dynamically calculated - double mass; /// @todo get rid of this, should be dynamically computed, but is the current rocket mass - - model::MotorModel mm; /// Current Motor Model - -}; - -#endif // ROCKET_H diff --git a/model/RocketModel.cpp b/model/RocketModel.cpp new file mode 100644 index 0000000..145f8a3 --- /dev/null +++ b/model/RocketModel.cpp @@ -0,0 +1,79 @@ + +// qtrocket headers +#include "RocketModel.h" +#include "QtRocket.h" +#include "InertiaTensors.h" + +namespace model +{ + +RocketModel::RocketModel() + : topPart("NoseCone", InertiaTensors::SolidSphere(1.0), 1.0, {0.0, 0.0, 1.0}) +{ + +} + + +double RocketModel::getMass(double t) +{ + double mass = mm.getMass(t); + mass += topPart.getCompositeMass(t); + return mass; +} + +Matrix3 RocketModel::getInertiaTensor(double) +{ + return topPart.getCompositeI(); +} + +bool RocketModel::terminateCondition(double) +{ + // Terminate propagation when the z coordinate drops below zero + if(currentState.position[2] < 0) + return true; + else + return false; +} + +Vector3 RocketModel::getForces(double t) +{ + // Get thrust + // Assume that thrust is always through the center of mass and in the rocket's Z-axis + Vector3 forces{0.0, 0.0, mm.getThrust(t)}; + + + // Get gravity + auto gravityModel = QtRocket::getInstance()->getEnvironment()->getGravityModel(); + + Vector3 gravity = gravityModel->getAccel(currentState.position)*getMass(t); + + forces += gravity; + + // Calculate aero forces + + + return forces; +} + +Vector3 RocketModel::getTorques(double t) +{ + return Vector3{0.0, 0.0, 0.0}; + +} + +double RocketModel::getThrust(double t) +{ + return mm.getThrust(t); +} + +void RocketModel::launch() +{ + mm.startMotor(0.0); +} + +void RocketModel::setMotorModel(const model::MotorModel& motor) +{ + mm = motor; +} + +} // namespace model diff --git a/model/RocketModel.h b/model/RocketModel.h new file mode 100644 index 0000000..c6be016 --- /dev/null +++ b/model/RocketModel.h @@ -0,0 +1,116 @@ +#ifndef ROCKETMODEL_H +#define ROCKETMODEL_H + +/// \cond +// C headers +// C++ headers +#include +#include +#include +#include // std::move + +// 3rd party headers +/// \endcond + +// qtrocket headers +#include "model/Part.h" +#include "sim/Propagator.h" +#include "model/MotorModel.h" + +#include "model/Propagatable.h" +// Not yet +//#include "model/Stage.h" + +namespace model +{ + +/** + * @brief The Rocket class holds all rocket components + * + */ +class RocketModel : public Propagatable +{ +public: + /** + * @brief Rocket class constructor + */ + RocketModel(); + + /** + * @brief Rocket class destructor + * + */ + virtual ~RocketModel() {} + + /** + * @brief launch Propagates the Rocket object until termination, + * normally when altitude crosses from positive to negative + */ + void launch(); + + Vector3 getForces(double t) override; + Vector3 getTorques(double t) override; + /** + * @brief getMass returns current rocket mass + * @param t current simulation time + * @return mass in kg + */ + double getMass(double t) override; + /** + * @brief terminateCondition returns true or false, whether the passed-in time/state matches the terminate condition + * @param cond time/state pair + * @return true if the passed-in time/state satisfies the terminate condition + */ + bool terminateCondition(double t) override; + + Matrix3 getInertiaTensor(double t) override; + + /** + * @brief getThrust returns current motor thrust + * @param t current simulation time + * @return thrust in Newtons + */ + double getThrust(double t); + + + /** + * @brief setMotorModel + * @param motor + */ + void setMotorModel(const model::MotorModel& motor); + + + /** + * @brief getMotorModel + */ + MotorModel getMotorModel() { return mm; } + + /** + * @brief Returns the current motor model. + * @return The current motor model + */ + //const model::MotorModel& getCurrentMotorModel() const { return mm; } + + + /** + * @brief setName sets the rocket name + * @param n name to set the Rocket + */ + void setName(const std::string& n) { name = n; } + + double getDragCoefficient() { return 1.0; } + void setDragCoefficient(double d) { } + void setMass(double m) { } + +private: + + std::string name; /// Rocket name + + model::MotorModel mm; /// Current Motor Model + + model::Part topPart; + +}; + +} // namespace model +#endif // ROCKETMODEL_H diff --git a/model/ThrustCurve.cpp b/model/ThrustCurve.cpp index fd7a803..8351d39 100644 --- a/model/ThrustCurve.cpp +++ b/model/ThrustCurve.cpp @@ -8,8 +8,6 @@ /// \endcond #include "model/ThrustCurve.h" -#include "utils/Logger.h" - ThrustCurve::ThrustCurve(std::vector>& tc) : thrustCurve(tc), diff --git a/model/tests/CMakeLists.txt b/model/tests/CMakeLists.txt new file mode 100644 index 0000000..294019a --- /dev/null +++ b/model/tests/CMakeLists.txt @@ -0,0 +1,16 @@ + +add_executable(model_tests + PartTests.cpp +) + +target_link_libraries(model_tests PRIVATE + model + utils + GTest::gtest_main +) + +include(GoogleTest) +gtest_discover_tests(model_tests) + +add_test(NAME qtrocket_model_tests COMMAND model_tests) + diff --git a/model/tests/PartTests.cpp b/model/tests/PartTests.cpp new file mode 100644 index 0000000..6e23d7d --- /dev/null +++ b/model/tests/PartTests.cpp @@ -0,0 +1,70 @@ +#include + +#include "model/Part.h" + +class PartTest : public testing::Test +{ +protected: + // Per-test-suite set-up. + // Called before the first test in this test suite. + // Can be omitted if not needed. + static void SetUpTestSuite() + { + //shared_resource_ = new ...; + + // If `shared_resource_` is **not deleted** in `TearDownTestSuite()`, + // reallocation should be prevented because `SetUpTestSuite()` may be called + // in subclasses of FooTest and lead to memory leak. + // + // if (shared_resource_ == nullptr) { + // shared_resource_ = new ...; + // } + } + + // Per-test-suite tear-down. + // Called after the last test in this test suite. + // Can be omitted if not needed. + static void TearDownTestSuite() + { + //delete shared_resource_; + //shared_resource_ = nullptr; + } + + // You can define per-test set-up logic as usual. + void SetUp() override { } + + // You can define per-test tear-down logic as usual. + void TearDown() override { } + + // Some expensive resource shared by all tests. + //static T* shared_resource_; +}; + +//T* FooTest::shared_resource_ = nullptr; + +TEST(PartTest, CreationTests) +{ + Matrix3 inertia; + inertia << 1, 0, 0, + 0, 1, 0, + 0, 0, 1; + Vector3 cm{1, 0, 0}; + model::Part testPart("testPart", + inertia, + 1.0, + cm); + + Matrix3 inertia2; + inertia2 << 1, 0, 0, + 0, 1, 0, + 0, 0, 1; + Vector3 cm2{1, 0, 0}; + model::Part testPart2("testPart2", + inertia2, + 1.0, + cm2); + Vector3 R{2.0, 2.0, 2.0}; + testPart.addChildPart(testPart2, R); + + +} diff --git a/sim/Aero.cpp b/sim/Aero.cpp new file mode 100644 index 0000000..e69de29 diff --git a/sim/Aero.h b/sim/Aero.h new file mode 100644 index 0000000..c57a778 --- /dev/null +++ b/sim/Aero.h @@ -0,0 +1,40 @@ +#ifndef SIM_AERO_H +#define SIM_AERO_H + +/// \cond +// C headers +// C++ headers + +// 3rd party headers +/// \endcond + +// qtrocket headers +#include "utils/math/MathTypes.h" + +namespace sim +{ + +class Aero +{ +public: + +private: + + Vector3 cp; /// center of pressure + + double Cx; /// longitudinal coefficient + double Cy; /// These are probably the same for axial symmetric + double Cz; /// rockets. The coeffients in the y and z body directions + + double Cl; // roll moment coefficient + double Cm; // pitch moment coefficient + double Cn; // yaw moment coefficient + + double baseCd; // coefficient of drag due to base drag + double Cd; // total coeffient of drag + + +}; +} + +#endif // SIM_AERO_H diff --git a/sim/AtmosphericModel.h b/sim/AtmosphericModel.h index deac8f5..0c61b14 100644 --- a/sim/AtmosphericModel.h +++ b/sim/AtmosphericModel.h @@ -13,6 +13,10 @@ public: virtual double getDensity(double altitude) = 0; virtual double getPressure(double altitude) = 0; virtual double getTemperature(double altitude) = 0; + + virtual double getSpeedOfSound(double altitude) = 0; + virtual double getDynamicViscosity(double altitude) = 0; + }; } // namespace sim diff --git a/sim/CMakeLists.txt b/sim/CMakeLists.txt new file mode 100644 index 0000000..b372b31 --- /dev/null +++ b/sim/CMakeLists.txt @@ -0,0 +1,28 @@ +add_library(sim + Aero.cpp + Aero.h + AtmosphericModel.h + ConstantAtmosphere.h + ConstantGravityModel.h + DESolver.h + Environment.h + GeoidModel.h + GravityModel.h + Propagator.cpp + Propagator.h + RK4Solver.h + SphericalGeoidModel.cpp + SphericalGeoidModel.h + SphericalGravityModel.cpp + SphericalGravityModel.h + StateData.h + USStandardAtmosphere.cpp + USStandardAtmosphere.h + WindModel.cpp + WindModel.h) + +target_link_libraries(sim PRIVATE + utils) + +# Unit tests +add_subdirectory(tests) diff --git a/sim/ConstantAtmosphere.h b/sim/ConstantAtmosphere.h index c37ad43..2b33762 100644 --- a/sim/ConstantAtmosphere.h +++ b/sim/ConstantAtmosphere.h @@ -15,6 +15,10 @@ public: double getDensity(double) override { return 1.225; } double getPressure(double) override { return 101325.0; } double getTemperature(double) override { return 288.15; } + + double getSpeedOfSound(double) override { return 340.294; } + + double getDynamicViscosity(double) override { return 1.78938e-5; } }; } // namespace sim diff --git a/sim/ConstantGravityModel.cpp b/sim/ConstantGravityModel.cpp deleted file mode 100644 index 9027ec7..0000000 --- a/sim/ConstantGravityModel.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "ConstantGravityModel.h" - -namespace sim { - -ConstantGravityModel::ConstantGravityModel() -{ - -} - -} // namespace sim diff --git a/sim/ConstantGravityModel.h b/sim/ConstantGravityModel.h index 74a83af..7d3a0fd 100644 --- a/sim/ConstantGravityModel.h +++ b/sim/ConstantGravityModel.h @@ -3,18 +3,21 @@ // qtrocket headers #include "sim/GravityModel.h" -#include "utils/Triplet.h" +#include "utils/math/MathTypes.h" namespace sim { class ConstantGravityModel : public GravityModel { public: - ConstantGravityModel(); + ConstantGravityModel() {} - virtual ~ConstantGravityModel() {} + virtual ~ConstantGravityModel() {} - TripletD getAccel(double, double, double) override { return TripletD(0.0, 0.0, -9.8); } + Vector3 getAccel(double, double, double) override + { + return Vector3(0.0, 0.0, -9.8); + } }; } // namespace sim diff --git a/sim/DESolver.h b/sim/DESolver.h index a20c2ad..f264e7c 100644 --- a/sim/DESolver.h +++ b/sim/DESolver.h @@ -4,14 +4,17 @@ /// \cond // C headers // C++ headers -#include +#include // 3rd party headers /// \endcond +// qtrocket headers + namespace sim { +template class DESolver { public: @@ -19,7 +22,17 @@ public: virtual ~DESolver() {} virtual void setTimeStep(double ts) = 0; - virtual void step(double t, const std::vector& curVal, std::vector& res ) = 0; + /** + * @brief + * + * @param curVal + * @param res + * @param t Optional parameter, but not used in QtRocket. Some generic solvers take time as + * a parameter to ODEs, but QtRocket's kinematic equations don't. Since I wrote + * the RK4 solver independently as a general tool, this interface is needed + * here unfortunately. + */ + virtual std::pair step(T& state, T& rate) = 0; }; } // namespace sim diff --git a/sim/Environment.h b/sim/Environment.h index b866420..d6d759e 100644 --- a/sim/Environment.h +++ b/sim/Environment.h @@ -17,7 +17,6 @@ #include "sim/ConstantAtmosphere.h" #include "sim/USStandardAtmosphere.h" -#include "sim/GeoidModel.h" namespace sim { diff --git a/sim/GravityModel.cpp b/sim/GravityModel.cpp deleted file mode 100644 index 36d4207..0000000 --- a/sim/GravityModel.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "GravityModel.h" - -namespace sim -{ - -GravityModel::GravityModel() -{ -} - -GravityModel::~GravityModel() -{ -} - -} // namespace sim \ No newline at end of file diff --git a/sim/GravityModel.h b/sim/GravityModel.h index da666ba..718dad1 100644 --- a/sim/GravityModel.h +++ b/sim/GravityModel.h @@ -2,7 +2,7 @@ #define SIM_GRAVITYMODEL_H // qtrocket headers -#include "utils/Triplet.h" +#include "utils/math/MathTypes.h" namespace sim { @@ -10,11 +10,11 @@ namespace sim class GravityModel { public: - GravityModel(); - virtual ~GravityModel(); + GravityModel() {} + virtual ~GravityModel() {} - virtual TripletD getAccel(double x, double y, double z) = 0; - TripletD getAccel(const TripletD& t) { return this->getAccel(t.x1, t.x2, t.x3); } + virtual Vector3 getAccel(double x, double y, double z) = 0; + Vector3 getAccel(const Vector3& t) { return this->getAccel(t.x(), t.y(), t.z()); } }; } // namespace sim diff --git a/sim/Propagator.cpp b/sim/Propagator.cpp index 619377a..2337a9e 100644 --- a/sim/Propagator.cpp +++ b/sim/Propagator.cpp @@ -2,7 +2,6 @@ /// \cond // C headers // C++ headers -#include #include #include #include @@ -14,47 +13,36 @@ // qtrocket headers #include "Propagator.h" -#include "QtRocket.h" -#include "model/Rocket.h" #include "sim/RK4Solver.h" #include "utils/Logger.h" - -namespace sim { - -Propagator::Propagator(Rocket* r) - : integrator(), - rocket(r) - +namespace sim { +Propagator::Propagator(std::shared_ptr r) + : linearIntegrator(), + object(r), + saveStates(true), + timeStep(0.01) +{ + // Linear velocity and acceleration + std::function(Vector3&, Vector3&)> linearODEs = [this](Vector3& state, Vector3& rate) -> std::pair + { + Vector3 dPosition; + Vector3 dVelocity; + // dx/dt + dPosition = rate; - // This is a little strange, but I have to populate the integrator unique_ptr - // with reset. make_unique() doesn't work because the compiler can't seem to - // deduce the template parameters correctly, and I don't want to specify them - // manually either. RK4Solver constructor is perfectly capable of deducing it's - // template types, and it derives from DESolver, so we can just reset the unique_ptr - // and pass it a freshly allocated RK4Solver pointer + // dvx/dt + dVelocity = object->getForces(currentTime) / object->getMass(currentTime); - // The state vector has components of the form: - // (x, y, z, xdot, ydot, zdot, pitch, yaw, roll, pitchRate, yawRate, rollRate) - integrator.reset(new RK4Solver( - /* dx/dt */ [](double, const std::vector& s) -> double {return s[3]; }, - /* dy/dt */ [](double, const std::vector& s) -> double {return s[4]; }, - /* dz/dt */ [](double, const std::vector& s) -> double {return s[5]; }, - /* dvx/dt */ [this](double, const std::vector& ) -> double { return getForceX() / getMass(); }, - /* dvy/dt */ [this](double, const std::vector& ) -> double { return getForceY() / getMass(); }, - /* dvz/dt */ [this](double, const std::vector& ) -> double { return getForceZ() / getMass(); }, - /* dpitch/dt */ [](double, const std::vector& s) -> double { return s[9]; }, - /* dyaw/dt */ [](double, const std::vector& s) -> double { return s[10]; }, - /* droll/dt */ [](double, const std::vector& s) -> double { return s[11]; }, - /* dpitchRate/dt */ [this](double, const std::vector& s) -> double { return (getTorqueP() - s[7] * s[8] * (getIroll() - getIyaw())) / getIpitch(); }, - /* dyawRate/dt */ [this](double, const std::vector& s) -> double { return (getTorqueQ() - s[6] * s[9] * (getIpitch() - getIroll())) / getIyaw(); }, - /* drollRate/dt */ [this](double, const std::vector& s) -> double { return (getTorqueR() - s[6] * s[7] * (getIyaw() - getIpitch())) / getIroll(); })); + return std::make_pair(dPosition, dVelocity); + }; + linearIntegrator.reset(new RK4Solver(linearODEs)); + linearIntegrator->setTimeStep(timeStep); - integrator->setTimeStep(timeStep); - saveStates = true; + saveStates = true; } Propagator::~Propagator() @@ -63,61 +51,41 @@ Propagator::~Propagator() void Propagator::runUntilTerminate() { - std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now(); - std::chrono::steady_clock::time_point endTime; + std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now(); + std::chrono::steady_clock::time_point endTime; - while(true) - { - // tempRes gets overwritten - integrator->step(currentTime, currentState, tempRes); + Vector3 currentPosition; + Vector3 currentVelocity; + Vector3 nextPosition; + Vector3 nextVelocity; + while(true) + { + currentPosition = object->getCurrentState().position; + currentVelocity = object->getCurrentState().velocity; - std::swap(currentState, tempRes); - if(saveStates) - { - states.push_back(std::make_pair(currentTime, currentState)); - } - if(rocket->terminateCondition(std::make_pair(currentTime, currentState))) - break; + std::tie(nextPosition, nextVelocity) = linearIntegrator->step(currentPosition, currentVelocity); - currentTime += timeStep; - } - endTime = std::chrono::steady_clock::now(); + StateData nextState; + nextState.position = nextPosition; + nextState.velocity = nextVelocity; + object->setCurrentState(nextState); - std::stringstream duration; - duration << "runUntilTerminate time (microseconds): "; - duration << std::chrono::duration_cast(endTime - startTime).count(); - utils::Logger::getInstance()->debug(duration.str()); + if(saveStates) + { + object->appendState(currentTime, nextState); + } + if(object->terminateCondition(currentTime)) + break; + + currentTime += timeStep; + } + endTime = std::chrono::steady_clock::now(); + + std::stringstream duration; + duration << "runUntilTerminate time (microseconds): "; + duration << std::chrono::duration_cast(endTime - startTime).count(); + utils::Logger::getInstance()->debug(duration.str()); } -double Propagator::getMass() -{ - return rocket->getMass(currentTime); -} - -double Propagator::getForceX() -{ - QtRocket* qtrocket = QtRocket::getInstance(); - return (currentState[3] >= 0 ? -1.0 : 1.0) * qtrocket->getEnvironment()->getAtmosphericModel()->getDensity(currentState[2])/ 2.0 * 0.008107 * rocket->getDragCoefficient() * currentState[3]* currentState[3]; -} - -double Propagator::getForceY() -{ - QtRocket* qtrocket = QtRocket::getInstance(); - return (currentState[4] >= 0 ? -1.0 : 1.0) * qtrocket->getEnvironment()->getAtmosphericModel()->getDensity(currentState[2]) / 2.0 * 0.008107 * rocket->getDragCoefficient() * currentState[4]* currentState[4]; -} - -double Propagator::getForceZ() -{ - QtRocket* qtrocket = QtRocket::getInstance(); - double gravity = (qtrocket->getEnvironment()->getGravityModel()->getAccel(currentState[0], currentState[1], currentState[2])).x3; - double airDrag = (currentState[5] >= 0 ? -1.0 : 1.0) * qtrocket->getEnvironment()->getAtmosphericModel()->getDensity(currentState[2]) / 2.0 * 0.008107 * rocket->getDragCoefficient() * currentState[5]* currentState[5]; - double thrust = rocket->getThrust(currentTime); - return gravity + airDrag + thrust; -} - -double Propagator::getTorqueP() { return 0.0; } -double Propagator::getTorqueQ() { return 0.0; } -double Propagator::getTorqueR() { return 0.0; } - } // namespace sim diff --git a/sim/Propagator.h b/sim/Propagator.h index 9e1151b..b9bcdcf 100644 --- a/sim/Propagator.h +++ b/sim/Propagator.h @@ -5,17 +5,22 @@ // C headers // C++ headers #include -#include // 3rd party headers /// \endcond // qtrocket headers -#include "sim/DESolver.h" +#include "sim/RK4Solver.h" +#include "utils/math/MathTypes.h" +#include "sim/StateData.h" +#include "model/Propagatable.h" // Forward declare +namespace model +{ class Rocket; +} class QtRocket; namespace sim @@ -24,22 +29,17 @@ namespace sim class Propagator { public: - Propagator(Rocket* r); + Propagator(std::shared_ptr o); ~Propagator(); - void setInitialState(const std::vector& initialState) + void setInitialState(const StateData& initialState) { - currentState.resize(initialState.size()); - for(std::size_t i = 0; i < initialState.size(); ++i) - { - currentState[i] = initialState[i]; - } - + object->setInitialState(initialState); } - const std::vector& getCurrentState() const + const StateData& getCurrentState() const { - return currentState; + return object->getCurrentState(); } void runUntilTerminate(); @@ -49,41 +49,21 @@ public: saveStates = s; } - const std::vector>>& getStates() const { return states; } - - void clearStates() { states.clear(); } void setCurrentTime(double t) { currentTime = t; } - void setTimeStep(double ts) { timeStep = ts; } - void setSaveStats(bool s) { saveStates = s; } private: - double getMass(); - double getForceX(); - double getForceY(); - double getForceZ(); - double getTorqueP(); - double getTorqueQ(); - double getTorqueR(); + std::unique_ptr> linearIntegrator; +// std::unique_ptr> orientationIntegrator; - double getIpitch() { return 1.0; } - double getIyaw() { return 1.0; } - double getIroll() { return 1.0; } + std::shared_ptr object; -//private: - - std::unique_ptr integrator; - - Rocket* rocket; - - std::vector currentState{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; - std::vector tempRes{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; bool saveStates{true}; double currentTime{0.0}; double timeStep{0.01}; - std::vector>> states; + }; } // namespace sim diff --git a/sim/RK4Solver.h b/sim/RK4Solver.h index 26a7e93..8365cad 100644 --- a/sim/RK4Solver.h +++ b/sim/RK4Solver.h @@ -4,10 +4,8 @@ /// \cond // C headers // C++ headers -#include #include #include -#include // 3rd party headers @@ -16,84 +14,80 @@ // qtrocket headers #include "sim/DESolver.h" #include "utils/Logger.h" +#include "utils/math/MathTypes.h" namespace sim { -template -class RK4Solver : public DESolver +/** + * @brief Runge-Kutta 4th order coupled ODE solver. + * @note This was written outside of the context of QtRocket, and it is very generic. There are + * some features of this solver that are note used by QtRocket, for example, it can solve + * and arbitrarily large system of coupled ODEs, but QtRocket only makes use of a system + * of size 6 (x, y, z, xDot, yDot, zDot) at a time. + * + * @tparam Ts + */ +template +class RK4Solver : public DESolver { public: - RK4Solver(Ts... funcs) + RK4Solver(std::function(T&, T&)> func) { - (odes.push_back(funcs), ...); - temp.resize(sizeof...(Ts)); - + // This only works for Eigen Vector types. + // TODO: Figure out how to make this slightly more generic, but for now + // we're only using this for Vector3 and Quaternion types + static_assert(std::is_same::value + || std::is_same::value, + "You can only use Vector3 or Quaternion valued functions in RK4Solver"); + + odes = func; } virtual ~RK4Solver() {} void setTimeStep(double inTs) override { dt = inTs; halfDT = dt / 2.0; } - void step(double t, const std::vector& curVal, std::vector& res) override + std::pair step(T& state, T& rate) override { + std::pair res; if(dt == std::numeric_limits::quiet_NaN()) { utils::Logger::getInstance()->error("Calling RK4Solver without setting dt first is an error"); - res[0] = std::numeric_limits::quiet_NaN(); + return res; } - for(size_t i = 0; i < len; ++i) - { - k1[i] = odes[i](t, curVal); - } + std::tie(k1State, k1Rate) = odes(state, rate); // compute k2 values. This involves stepping the current values forward a half-step // based on k1, so we do the stepping first - for(size_t i = 0; i < len; ++i) - { - temp[i] = curVal[i] + k1[i]*dt / 2.0; - } - for(size_t i = 0; i < len; ++i) - { - k2[i] = odes[i](t + halfDT, temp); - } - // repeat for k3 - for(size_t i = 0; i < len; ++i) - { - temp[i] = curVal[i] + k2[i]*dt / 2.0; - } - for(size_t i = 0; i < len; ++i) - { - k3[i] = odes[i](t + halfDT, temp); - } + std::tie(tempState, tempRate) = std::make_pair(state + k1State*halfDT, rate + k1Rate*halfDT); + std::tie(k2State, k2Rate) = odes(tempState, tempRate); - // now k4 - for(size_t i = 0; i < len; ++i) - { - temp[i] = curVal[i] + k3[i]*dt; - } - for(size_t i = 0; i < len; ++i) - { - k4[i] = odes[i](t + dt, temp); - } + std::tie(tempState, tempRate) = std::make_pair(state + k2State*halfDT, rate + k2Rate*halfDT); + std::tie(k3State, k3Rate) = odes(tempState, tempRate); - // now compute the result - for(size_t i = 0; i < len; ++i) - { - res[i] = curVal[i] + (dt / 6.0)*(k1[i] + 2.0*k2[i] + 2.0*k3[i] + k4[i]); - } + std::tie(tempState, tempRate) = std::make_pair(state + k3State*dt, rate + k3Rate*dt); + std::tie(k4State, k4Rate) = odes(tempState, tempRate); + res = std::make_pair(state + (dt / 6.0)*(k1State + 2.0*k2State + 2.0*k3State + k4State), + rate + (dt / 6.0)*(k1Rate + 2.0*k2Rate + 2.0*k3Rate + k4Rate)); + + return res; } private: - std::vector&)>> odes; + std::function(T&, T&)> odes; - static constexpr size_t len = sizeof...(Ts); - double k1[len]; - double k2[len]; - double k3[len]; - double k4[len]; + T k1State; + T k2State; + T k3State; + T k4State; + T k1Rate; + T k2Rate; + T k3Rate; + T k4Rate; - std::vector temp; + T tempState; + T tempRate; double dt = std::numeric_limits::quiet_NaN(); double halfDT = 0.0; diff --git a/sim/SphericalGravityModel.cpp b/sim/SphericalGravityModel.cpp index 24a9353..3ab355b 100644 --- a/sim/SphericalGravityModel.cpp +++ b/sim/SphericalGravityModel.cpp @@ -25,7 +25,7 @@ SphericalGravityModel::~SphericalGravityModel() } -TripletD SphericalGravityModel::getAccel(double x, double y, double z) +Vector3 SphericalGravityModel::getAccel(double x, double y, double z) { // Convert x, y, z from meters to km. This is to avoid potential precision losses // with using the earth's gravitation parameter in meters (14 digit number). @@ -43,7 +43,7 @@ TripletD SphericalGravityModel::getAccel(double x, double y, double z) double ay = accelFactor * y_km * 1000.0; double az = accelFactor * z_km * 1000.0; - return TripletD(ax, ay, az); + return Vector3(ax, ay, az); } diff --git a/sim/SphericalGravityModel.h b/sim/SphericalGravityModel.h index 8b28d31..90e7acb 100644 --- a/sim/SphericalGravityModel.h +++ b/sim/SphericalGravityModel.h @@ -2,7 +2,7 @@ #define SIM_SPHERICALGRAVITYMODEL_H // qtrocket headers -#include "GravityModel.h" +#include "sim/GravityModel.h" namespace sim { @@ -13,7 +13,7 @@ public: SphericalGravityModel(); virtual ~SphericalGravityModel(); - TripletD getAccel(double x, double y, double z) override; + Vector3 getAccel(double x, double y, double z) override; }; } // namespace sim diff --git a/sim/StateData.cpp b/sim/StateData.cpp deleted file mode 100644 index 2af9702..0000000 --- a/sim/StateData.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "StateData.h" - -StateData::StateData() -{ - -} diff --git a/sim/StateData.h b/sim/StateData.h index fc27e8a..c7a268b 100644 --- a/sim/StateData.h +++ b/sim/StateData.h @@ -1,9 +1,15 @@ #ifndef STATEDATA_H #define STATEDATA_H +/// \cond +// C headers +// C++ headers +#include +// 3rd party headers +/// \endcond + // qtrocket headers -#include "utils/math/Vector3.h" -#include "utils/math/Quaternion.h" +#include "utils/math/MathTypes.h" /** * @brief The StateData class holds physical state data. Things such as position, velocity, @@ -13,20 +19,76 @@ class StateData { public: - StateData(); + StateData() {} + ~StateData() {} + + StateData(const StateData&) = default; + StateData(StateData&&) = default; + + StateData& operator=(const StateData& rhs) + { + if(this != &rhs) + { + position = rhs.position; + velocity = rhs.velocity; + orientation = rhs.orientation; + orientationRate = rhs.orientationRate; + dcm = rhs.dcm; + eulerAngles = rhs.eulerAngles; + } + return *this; + } + StateData& operator=(StateData&& rhs) + { + if(this != &rhs) + { + position = std::move(rhs.position); + velocity = std::move(rhs.velocity); + orientation = std::move(rhs.orientation); + orientationRate = std::move(rhs.orientationRate); + dcm = std::move(rhs.dcm); + eulerAngles = std::move(rhs.eulerAngles); + } + return *this; + } + + std::vector getPosStdVector() const + { + return std::vector{position[0], position[1], position[2]}; + } + std::vector getVelStdVector() const + { + return std::vector{velocity[0], velocity[1], velocity[2]}; + } -private: - math::Vector3 position{0.0, 0.0, 0.0}; - math::Vector3 velocity{0.0, 0.0, 0.0}; +/// TODO: Put these behind an interface + //Vector3 getPosition() const + //{ + // return position; + //} - math::Quaternion orientation{0.0, 0.0, 0.0, 0.0}; // roll, pitch, yaw - math::Quaternion orientationRate{0.0, 0.0, 0.0, 0.0}; // roll-rate, pitch-rate, yaw-rate - // Necessary? - //math::Vector3 orientationAccel; + //Vector3 getVelocity() const + //{ + // return velocity; + //} +// private: - // This is an array because the integrator expects it - double data[6]; + // Intended to be used as world state data + Vector3 position{0.0, 0.0, 0.0}; + Vector3 velocity{0.0, 0.0, 0.0}; + + // Orientation of body coordinates w.r.t. world coordinates + Quaternion orientation{0.0, 0.0, 0.0, 0.0}; /// (vector, scalar) + Quaternion orientationRate{0.0, 0.0, 0.0, 0.0}; /// (vector, scalar) + + Matrix3 dcm{{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + + /// Euler angles are yaw-pitch-roll, and (3-2-1) order + /// yaw - psi + /// pitch - theta + /// roll - phi + Vector3 eulerAngles{0.0, 0.0, 0.0}; }; diff --git a/sim/USStandardAtmosphere.cpp b/sim/USStandardAtmosphere.cpp index c73d729..c6a0cf3 100644 --- a/sim/USStandardAtmosphere.cpp +++ b/sim/USStandardAtmosphere.cpp @@ -10,6 +10,7 @@ // qtrocket headers #include "sim/USStandardAtmosphere.h" #include "utils/math/Constants.h" +#include "utils/math/UtilityMathFunctions.h" using namespace utils::math; @@ -17,9 +18,9 @@ namespace sim { // Populate static data -utils::BinMap initTemps() +utils::Bin initTemps() { - utils::BinMap map; + utils::Bin map; map.insert(std::make_pair(0.0, 288.15)); map.insert(std::make_pair(11000.0, 216.65)); map.insert(std::make_pair(20000.0, 216.65)); @@ -32,9 +33,9 @@ utils::BinMap initTemps() } -utils::BinMap initLapseRates() +utils::Bin initLapseRates() { - utils::BinMap map; + utils::Bin map; map.insert(std::make_pair(0.0, 0.0065)); map.insert(std::make_pair(11000.0, 0.0)); map.insert(std::make_pair(20000.0, -0.001)); @@ -46,9 +47,9 @@ utils::BinMap initLapseRates() return map; } -utils::BinMap initDensities() +utils::Bin initDensities() { - utils::BinMap map; + utils::Bin map; map.insert(std::make_pair(0.0, 1.225)); map.insert(std::make_pair(11000.0, 0.36391)); map.insert(std::make_pair(20000.0, 0.08803)); @@ -60,11 +61,24 @@ utils::BinMap initDensities() return map; } -utils::BinMap USStandardAtmosphere::temperatureLapseRate(initLapseRates()); -utils::BinMap USStandardAtmosphere::standardTemperature(initTemps()); -utils::BinMap USStandardAtmosphere::standardDensity(initDensities()); +utils::Bin initPressures() +{ + utils::Bin map; + map.insert(std::make_pair(0.0, 101325)); + map.insert(std::make_pair(11000.0, 22632.1)); + map.insert(std::make_pair(20000.0, 5474.89)); + map.insert(std::make_pair(32000.0, 868.02)); + map.insert(std::make_pair(47000.0, 110.91)); + map.insert(std::make_pair(51000.0, 66.94)); + map.insert(std::make_pair(71000.0, 3.96)); + return map; +} +utils::Bin USStandardAtmosphere::temperatureLapseRate(initLapseRates()); +utils::Bin USStandardAtmosphere::standardTemperature(initTemps()); +utils::Bin USStandardAtmosphere::standardDensity(initDensities()); +utils::Bin USStandardAtmosphere::standardPressure(initPressures()); USStandardAtmosphere::USStandardAtmosphere() { @@ -78,31 +92,64 @@ USStandardAtmosphere::~USStandardAtmosphere() double USStandardAtmosphere::getDensity(double altitude) { - if(temperatureLapseRate[altitude] == 0.0) + if(utils::math::floatingPointEqual(temperatureLapseRate[altitude], 0.0)) { - return standardDensity[altitude] * std::exp((-Constants::g0 * Constants::airMolarMass * (altitude - standardDensity.getBinBase(altitude))) + return standardDensity[altitude] * std::exp( + (-Constants::g0 * Constants::airMolarMass * (altitude - standardDensity.getBinBase(altitude))) / (Constants::Rstar * standardTemperature[altitude])); } else { - double base = standardTemperature[altitude] / - (standardTemperature[altitude] + temperatureLapseRate[altitude] * (altitude - standardDensity.getBinBase(altitude))); + double base = (standardTemperature[altitude] - temperatureLapseRate[altitude] * + (altitude - standardDensity.getBinBase(altitude))) / standardTemperature[altitude]; - double exponent = 1 + (Constants::g0 * Constants::airMolarMass) / - (Constants::Rstar * temperatureLapseRate[altitude]); + double exponent = (Constants::g0 * Constants::airMolarMass) / + (Constants::Rstar * temperatureLapseRate[altitude]) - 1.0; return standardDensity[altitude] * std::pow(base, exponent); - } } -double USStandardAtmosphere::getTemperature(double /*altitude*/) +double USStandardAtmosphere::getTemperature(double altitude) { - return 0.0; + double baseTemp = standardTemperature[altitude]; + double baseAltitude = standardTemperature.getBinBase(altitude); + return baseTemp - (altitude - baseAltitude) * temperatureLapseRate[altitude]; + } -double USStandardAtmosphere::getPressure(double /* altitude */) +double USStandardAtmosphere::getPressure(double altitude) { - return 0.0; + if(utils::math::floatingPointEqual(temperatureLapseRate[altitude], 0.0)) + { + return standardPressure[altitude] * std::exp( + (-Constants::g0 * Constants::airMolarMass * (altitude - standardPressure.getBinBase(altitude))) + / (Constants::Rstar * standardTemperature[altitude])); + + } + else + { + double base = (standardTemperature[altitude] - temperatureLapseRate[altitude] * + (altitude - standardPressure.getBinBase(altitude))) / standardTemperature[altitude]; + + double exponent = (Constants::g0 * Constants::airMolarMass) / + (Constants::Rstar * temperatureLapseRate[altitude]); + + return standardPressure[altitude] * std::pow(base, exponent); + } + +} + +double USStandardAtmosphere::getSpeedOfSound(double altitude) +{ + return std::sqrt( (Constants::gamma * Constants::Rstar * getTemperature(altitude)) + / + Constants::airMolarMass); +} + +double USStandardAtmosphere::getDynamicViscosity(double altitude) +{ + double temperature = getTemperature(altitude); + return (Constants::beta * std::pow(temperature, 1.5)) / ( temperature + Constants::S); } } // namespace sim diff --git a/sim/USStandardAtmosphere.h b/sim/USStandardAtmosphere.h index 2835d81..3821295 100644 --- a/sim/USStandardAtmosphere.h +++ b/sim/USStandardAtmosphere.h @@ -3,7 +3,7 @@ // qtrocket headers #include "sim/AtmosphericModel.h" -#include "utils/BinMap.h" +#include "utils/Bin.h" namespace sim { @@ -28,10 +28,15 @@ public: double getPressure(double altitude) override; double getTemperature(double altitude) override; + double getSpeedOfSound(double altitude) override; + + double getDynamicViscosity(double altitude) override; + private: - static utils::BinMap temperatureLapseRate; - static utils::BinMap standardTemperature; - static utils::BinMap standardDensity; + static utils::Bin temperatureLapseRate; + static utils::Bin standardTemperature; + static utils::Bin standardDensity; + static utils::Bin standardPressure; }; diff --git a/sim/WindModel.cpp b/sim/WindModel.cpp index b98811c..ca5b51e 100644 --- a/sim/WindModel.cpp +++ b/sim/WindModel.cpp @@ -13,9 +13,9 @@ WindModel::~WindModel() { } -TripletD WindModel::getWindSpeed(double /* x */, double /* y */ , double /* z */) +Vector3 WindModel::getWindSpeed(double /* x */, double /* y */ , double /* z */) { - return TripletD(0.0, 0.0, 0.0); + return Vector3(0.0, 0.0, 0.0); } } // namespace sim diff --git a/sim/WindModel.h b/sim/WindModel.h index d01046b..7c584d7 100644 --- a/sim/WindModel.h +++ b/sim/WindModel.h @@ -2,7 +2,7 @@ #define SIM_WINDMODEL_H // qtrocket headers -#include "utils/Triplet.h" +#include "utils/math/MathTypes.h" namespace sim { @@ -13,7 +13,7 @@ public: WindModel(); virtual ~WindModel(); - virtual TripletD getWindSpeed(double x, double y, double z); + virtual Vector3 getWindSpeed(double x, double y, double z); }; diff --git a/sim/tests/CMakeLists.txt b/sim/tests/CMakeLists.txt new file mode 100644 index 0000000..aca0eb7 --- /dev/null +++ b/sim/tests/CMakeLists.txt @@ -0,0 +1,15 @@ + +add_executable(sim_tests + USStandardAtmosphereTests.cpp +) + +target_link_libraries(sim_tests + sim + GTest::gtest_main +) + +include(GoogleTest) +gtest_discover_tests(sim_tests) + +add_test(NAME qtrocket_sim_tests COMMAND sim_tests) + diff --git a/sim/tests/USStandardAtmosphereTests.cpp b/sim/tests/USStandardAtmosphereTests.cpp new file mode 100644 index 0000000..cafe226 --- /dev/null +++ b/sim/tests/USStandardAtmosphereTests.cpp @@ -0,0 +1,90 @@ +#include + +#include "sim/USStandardAtmosphere.h" + +TEST(USStandardAtmosphereTests, DensityTests) +{ + sim::USStandardAtmosphere atmosphere; + + // Test that the calucated values are with 0.1% of the published values in the NOAA report + EXPECT_NEAR(atmosphere.getDensity(0.0) / 1.225, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getDensity(1000.0) / 1.112, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getDensity(2000.0) / 1.007, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getDensity(3000.0) / 0.9093, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getDensity(4000.0) / 0.8194, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getDensity(5000.0) / 0.7364, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getDensity(6000.0) / 0.6601, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getDensity(7000.0) / 0.5900, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getDensity(15000.0) / 0.19367, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getDensity(20000.0) / 0.088035, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getDensity(25000.0) / 0.039466, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getDensity(30000.0) / 0.018012, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getDensity(40000.0) / 0.0038510, 1.0, 0.001); + + // These are generally accurate to ~0.5%. Slight deviation of calculated + // density from the given density in the report table + EXPECT_NEAR(atmosphere.getDensity(8000.0) / 0.52579, 1.0, 0.005); + EXPECT_NEAR(atmosphere.getDensity(9000.0) / 0.46706, 1.0, 0.005); + EXPECT_NEAR(atmosphere.getDensity(10000.0) / 0.41351, 1.0, 0.005); + EXPECT_NEAR(atmosphere.getDensity(50000.0) / 0.00097752, 1.0, 0.005); + EXPECT_NEAR(atmosphere.getDensity(60000.0) / 0.00028832, 1.0, 0.005); + EXPECT_NEAR(atmosphere.getDensity(70000.0) / 0.000074243, 1.0, 0.005); + EXPECT_NEAR(atmosphere.getDensity(80000.0) / 0.000015701, 1.0, 0.005); + +} + +TEST(USStandardAtmosphereTests, PressureTests) +{ + sim::USStandardAtmosphere atmosphere; + + // Test that the calucated values are with 0.1% of the published values in the NOAA report + EXPECT_NEAR(atmosphere.getPressure(0.0) / 101325.0, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(1000.0) / 89876.0, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(2000.0) / 79501.0, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(3000.0) / 70108.0, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(4000.0) / 61640.0, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(5000.0) / 54019.0, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(6000.0) / 47181.0, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(7000.0) / 41060.0, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(8000.0) / 35599.0, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(9000.0) / 30742.0, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(10000.0) / 26436.0, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(15000.0) / 12044.0, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(20000.0) / 5474.8, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(25000.0) / 2511.0, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(30000.0) / 1171.8, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(40000.0) / 277.52, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(50000.0) / 75.944, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(60000.0) / 20.314, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(70000.0) / 4.6342, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getPressure(80000.0) / 0.88627, 1.0, 0.001); + +} + +TEST(USStandardAtmosphereTests, TemperatureTests) +{ + sim::USStandardAtmosphere atmosphere; + + // Test that the calucated values are with 0.1% of the published values in the NOAA report + EXPECT_NEAR(atmosphere.getTemperature(0.0) / 288.15, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(1000.0) / 281.651, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(2000.0) / 275.154, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(3000.0) / 268.659, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(4000.0) / 262.166, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(5000.0) / 255.676, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(6000.0) / 249.187, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(7000.0) / 242.7, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(8000.0) / 236.215, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(9000.0) / 229.733, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(10000.0) / 223.252, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(15000.0) / 216.65, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(20000.0) / 216.65, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(25000.0) / 221.552, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(30000.0) / 226.509, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(40000.0) / 251.05, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(50000.0) / 270.65, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(60000.0) / 245.45, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(70000.0) / 217.450, 1.0, 0.001); + EXPECT_NEAR(atmosphere.getTemperature(80000.0) / 196.650, 1.0, 0.001); + +} \ No newline at end of file diff --git a/utils/BinMap.cpp b/utils/Bin.cpp similarity index 83% rename from utils/BinMap.cpp rename to utils/Bin.cpp index 95d17d6..8f9b15f 100644 --- a/utils/BinMap.cpp +++ b/utils/Bin.cpp @@ -3,15 +3,14 @@ // C headers // C++ headers #include -#include +#include #include // 3rd party headers -#include /// \endcond // qtrocket headers -#include "BinMap.h" +#include "Bin.h" // TODO: Check on the availability of this in Clang. // Replace libfmt with format when LLVM libc++ supports it @@ -20,33 +19,33 @@ namespace utils { -BinMap::BinMap() +Bin::Bin() : bins() { } -BinMap::BinMap(BinMap&& o) +Bin::Bin(Bin&& o) : bins(std::move(o.bins)) { } -BinMap::~BinMap() +Bin::~Bin() { } // TODO: Very low priority, but if anyone wants to make this more efficient it could be // interesting -void BinMap::insert(const std::pair& toInsert) +void Bin::insert(const std::pair& toInsert) { bins.push_back(toInsert); std::sort(bins.begin(), bins.end(), [](const auto& a, const auto& b){ return a.first < b.first; }); } -double BinMap::operator[](double key) +double Bin::operator[](double key) { auto iter = bins.begin(); // If the key is less than the lowest bin value, then it is out of range @@ -56,12 +55,12 @@ double BinMap::operator[](double key) if(key < iter->first) { throw std::out_of_range( - fmt::format("{} less than lower bound {} of BinMap", key, iter->first)); + std::format("{} less than lower bound {} of BinMap", key, iter->first)); } // Increment it and start searching If we reach the end without finding an existing key // greater than our search term, then we've just hit the last bin and return that iter++; - double retVal = bins.end()->second; + double retVal = bins.back().second; while(iter != bins.end()) { if(key < iter->first) @@ -74,7 +73,7 @@ double BinMap::operator[](double key) return retVal; } -double BinMap::getBinBase(double key) +double Bin::getBinBase(double key) { auto iter = bins.begin(); // If the key is less than the lowest bin value, then it is out of range @@ -84,7 +83,7 @@ double BinMap::getBinBase(double key) if(key < iter->first) { throw std::out_of_range( - fmt::format("{} less than lower bound {} of BinMap", key, iter->first)); + std::format("{} less than lower bound {} of BinMap", key, iter->first)); } // Increment it and start searching If we reach the end without finding an existing key // greater than our search term, then we've just hit the last bin and return that diff --git a/utils/BinMap.h b/utils/Bin.h similarity index 86% rename from utils/BinMap.h rename to utils/Bin.h index 216cd7d..86843b0 100644 --- a/utils/BinMap.h +++ b/utils/Bin.h @@ -1,5 +1,5 @@ -#ifndef UTILS_BINMAP_H -#define UTILS_BINMAP_H +#ifndef UTILS_BIN_H +#define UTILS_BIN_H /// \cond // C headers @@ -22,12 +22,12 @@ namespace utils { * @todo Make this class behave more like a proper STL container. Templetize it for one * */ -class BinMap +class Bin { public: - BinMap(); - BinMap(BinMap&& o); - ~BinMap(); + Bin(); + Bin(Bin&& o); + ~Bin(); void insert(const std::pair& toInsert); double operator[](double key); @@ -40,4 +40,4 @@ private: } // namespace utils -#endif // UTILS_BINMAP_H +#endif // UTILS_BIN_H diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt new file mode 100644 index 0000000..251b567 --- /dev/null +++ b/utils/CMakeLists.txt @@ -0,0 +1,30 @@ +add_library(utils + Bin.cpp + Bin.h + CurlConnection.cpp + CurlConnection.h + Logger.cpp + Logger.h + MotorModelDatabase.cpp + MotorModelDatabase.h + RSEDatabaseLoader.cpp + RSEDatabaseLoader.h + ThreadPool.cpp + ThreadPool.h + ThrustCurveAPI.cpp + ThrustCurveAPI.h + TSQueue.h + math/Constants.h + math/MathTypes.h + math/UtilityMathFunctions.h) + + +#target_include_directories(utils PRIVATE +# ${Boost_INCLUDE_DIRS}) + + +target_link_libraries(utils PUBLIC + libcurl + Boost::property_tree + jsoncpp_static + eigen) diff --git a/utils/Logger.cpp b/utils/Logger.cpp index bce72dc..2860874 100644 --- a/utils/Logger.cpp +++ b/utils/Logger.cpp @@ -40,6 +40,13 @@ void Logger::log(std::string_view msg, const LogLevel& lvl) // all levels at or lower than the current level. switch(currentLevel) { + case PERF_: + if(lvl == PERF_) + { + outFile << "[PERF] " << msg << std::endl; + std::cout << "[PERF] " << msg << "\n"; + } + [[fallthrough]]; case DEBUG_: if(lvl == DEBUG_) { diff --git a/utils/Logger.h b/utils/Logger.h index 18c7467..752baf8 100644 --- a/utils/Logger.h +++ b/utils/Logger.h @@ -25,7 +25,8 @@ public: ERROR_, WARN_, INFO_, - DEBUG_ + DEBUG_, + PERF_ }; static Logger* getInstance(); @@ -38,16 +39,11 @@ public: void setLogLevel(const LogLevel& lvl); - /* - std::function error; - std::function warn; - std::function info; - std::function debug; - */ inline void error(std::string_view msg) { log(msg, ERROR_); } inline void warn(std::string_view msg) { log(msg, WARN_); } inline void info(std::string_view msg) { log(msg, INFO_); } inline void debug(std::string_view msg) { log(msg, DEBUG_); } + inline void perf(std::string_view msg) { log(msg, PERF_); } void log(std::ostream& o, const std::string& msg); diff --git a/utils/MotorModelDatabase.cpp b/utils/MotorModelDatabase.cpp index 23ffa0d..cf958b7 100644 --- a/utils/MotorModelDatabase.cpp +++ b/utils/MotorModelDatabase.cpp @@ -68,20 +68,6 @@ std::optional MotorModelDatabase::getMotorModel(const std::st void MotorModelDatabase::saveMotorDatabase(const std::string& filename) { -/* - - - 1.0 - - - 10.0 - 123.4 - - - - -*/ - namespace pt = boost::property_tree; // top-level tree @@ -112,6 +98,15 @@ void MotorModelDatabase::saveMotorDatabase(const std::string& filename) motor.put("type", m.data.type.str()); motor.put("lastUpdated", m.data.lastUpdated); + // delays tag is in the form of a csv string + std::stringstream delays; + for (std::size_t i = 0; i < m.data.delays.size() - 1; ++i) + { + delays << std::to_string(m.data.delays[i]) << ","; + } + delays << std::to_string(m.data.delays[m.data.delays.size() - 1]); + motor.put("delays", delays.str()); + // thrust data { pt::ptree tc; diff --git a/utils/MotorModelDatabase.h b/utils/MotorModelDatabase.h index ea22557..3b734cb 100644 --- a/utils/MotorModelDatabase.h +++ b/utils/MotorModelDatabase.h @@ -74,4 +74,4 @@ private: } // namespace utils -#endif // UTILS_MOTORMODELDATABASE_H \ No newline at end of file +#endif // UTILS_MOTORMODELDATABASE_H diff --git a/utils/RSEDatabaseLoader.cpp b/utils/RSEDatabaseLoader.cpp index 111aa21..83b52d5 100644 --- a/utils/RSEDatabaseLoader.cpp +++ b/utils/RSEDatabaseLoader.cpp @@ -5,6 +5,7 @@ #include #include #include +#include // 3rd party headers #include @@ -58,6 +59,16 @@ void RSEDatabaseLoader::buildAndAppendMotorModel(boost::property_tree::ptree& v) mm.commonName = v.get(".code", ""); // mm.delays = extract vector from csv list + std::string delays = v.get(".delays", "1000"); + std::size_t pos{0}; + std::string tok; + while ((pos = delays.find(",")) != std::string::npos) + { + tok = delays.substr(0, pos); + mm.delays.push_back(std::atoi(tok.c_str())); + delays.erase(0, pos + 1); + } + mm.delays.push_back(std::atoi(delays.c_str())); // mm.designation = What is this? diff --git a/utils/ThrustCurveAPI.cpp b/utils/ThrustCurveAPI.cpp index de3ff83..04b7a01 100644 --- a/utils/ThrustCurveAPI.cpp +++ b/utils/ThrustCurveAPI.cpp @@ -4,6 +4,7 @@ // C++ headers // 3rd party headers #include +#include /// \endcond // qtrocket headers @@ -25,6 +26,58 @@ ThrustCurveAPI::~ThrustCurveAPI() } +std::optional ThrustCurveAPI::getThrustCurve(const std::string& id) +{ + std::stringstream endpoint; + endpoint << hostname << "api/v1/download.json?motorId=" << id << "&data=samples"; + std::vector extraHeaders = {}; + + std::string res = curlConnection.get(endpoint.str(), extraHeaders); + model::MotorModel mm; + + if(!res.empty()) + { + try + { + Json::Reader reader; + Json::Value jsonResult; + reader.parse(res, jsonResult); + + std::vector> samples; + for(Json::ValueConstIterator iter = jsonResult["results"].begin(); + iter != jsonResult["results"].end(); + ++iter) + { + // if there are more than 1 items in the results list, we only want the RASP data + // Otherwise just take whatever is there + if(std::next(iter) != jsonResult["results"].end()) + { + if( (*iter)["format"].asString() != "RASP") + continue; + } + for(Json::ValueConstIterator samplesIter = (*iter)["samples"].begin(); + samplesIter != (*iter)["samples"].end(); + ++samplesIter) + { + samples.push_back(std::make_pair((*samplesIter)["time"].asDouble(), + (*samplesIter)["thrust"].asDouble())); + + } + } + return ThrustCurve(samples); + } + catch(const std::exception& e) + { + std::string err("Unable to parse JSON from Thrustcurve motor data request. Error: "); + err += e.what(); + + Logger::getInstance()->error(err); + } + } + + return std::nullopt; + +} model::MotorModel ThrustCurveAPI::getMotorData(const std::string& motorId) { @@ -182,7 +235,9 @@ std::vector ThrustCurveAPI::searchMotors(const SearchCriteria { Json::Reader reader; Json::Value jsonResult; +Logger::getInstance()->debug("1"); reader.parse(result, jsonResult); +Logger::getInstance()->debug("2"); for(Json::ValueConstIterator iter = jsonResult["results"].begin(); iter != jsonResult["results"].end(); @@ -191,6 +246,7 @@ std::vector ThrustCurveAPI::searchMotors(const SearchCriteria model::MotorModel motorModel; model::MotorModel::MetaData mm; mm.commonName = (*iter)["commonName"].asString(); +Logger::getInstance()->debug("3"); std::string availability = (*iter)["availability"].asString(); if(availability == "regular") @@ -200,6 +256,7 @@ std::vector ThrustCurveAPI::searchMotors(const SearchCriteria mm.avgThrust = (*iter)["avgThrustN"].asDouble(); mm.burnTime = (*iter)["burnTimeS"].asDouble(); +Logger::getInstance()->debug("4"); // TODO fill in certOrg // TODO fill in delays mm.designation = (*iter)["designation"].asString(); @@ -226,7 +283,14 @@ std::vector ThrustCurveAPI::searchMotors(const SearchCriteria else mm.type = model::MotorModel::MotorType(model::MotorModel::MOTORTYPE::HYBRID); +Logger::getInstance()->debug("5"); + auto tc = getThrustCurve(mm.motorIdTC); motorModel.moveMetaData(std::move(mm)); +Logger::getInstance()->debug("6"); + if(tc) + { + motorModel.addThrustCurve(*tc); + } retVal.push_back(motorModel); } } diff --git a/utils/ThrustCurveAPI.h b/utils/ThrustCurveAPI.h index eb63946..af09ecb 100644 --- a/utils/ThrustCurveAPI.h +++ b/utils/ThrustCurveAPI.h @@ -7,6 +7,7 @@ // C++ headers #include #include +#include // 3rd party headers /// \endcond @@ -91,6 +92,8 @@ private: const std::string hostname; CurlConnection curlConnection; + std::optional getThrustCurve(const std::string& id); + // no extra headers, but CurlConnection library wants them const std::vector extraHeaders{}; }; diff --git a/utils/Triplet.h b/utils/Triplet.h deleted file mode 100644 index fd6a4cb..0000000 --- a/utils/Triplet.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef TRIPLET_H -#define TRIPLET_H - -/** - * The purpose of this class is to get rid of using std::tuple for coordinate triplets. - */ - -template -struct Triplet -{ - Triplet(const T& a, const T& b, const T& c) - : x1(a), x2(b), x3(c) - {} - T x1; - T x2; - T x3; -}; - -using TripletD = Triplet; - -#endif // TRIPLET_H diff --git a/utils/math/Constants.h b/utils/math/Constants.h index 13c0f18..cfc1bbe 100644 --- a/utils/math/Constants.h +++ b/utils/math/Constants.h @@ -6,9 +6,19 @@ namespace utils::math namespace Constants { - constexpr double Rstar = 8.3144598; + constexpr double Rstar = 8.31432; constexpr const double g0 = 9.80665; constexpr const double airMolarMass = 0.0289644; + + // gamma is the ratio of the specific heat of air at constant pressure to that at + // constant volume. Used in computing the speed of sound + constexpr const double gamma = 1.4; + + // beta is used in calculating the dynamic viscosity of air. Based on the 1976 US Standard + // Atmosphere report, it is empirically measured to be: + constexpr const double beta = 1.458e-6; + // Sutherland's constant + constexpr const double S = 110.4; constexpr const double standardTemperature = 288.15; constexpr const double standardDensity = 1.2250; constexpr const double meanEarthRadiusWGS84 = 6371008.8; diff --git a/utils/math/MathTypes.h b/utils/math/MathTypes.h new file mode 100644 index 0000000..9eba8a1 --- /dev/null +++ b/utils/math/MathTypes.h @@ -0,0 +1,74 @@ +#ifndef UTILS_MATH_MATHTYPES_H +#define UTILS_MATH_MATHTYPES_H + +#include +#include + +/// This is not in any namespace. These typedefs are intended to be used throughout QtRocket, +/// so keeping them in the global namespace seems to make sense. + +template +using MyMatrix = Eigen::Matrix; + +template +using MyVector = Eigen::Matrix; + +typedef Eigen::Quaterniond Quaternion; + +using Matrix3 = MyMatrix<3>; +using Matrix4 = MyMatrix<4>; + +using Vector3 = MyVector<3>; +using Vector6 = MyVector<6>; + +/* +namespace utils +{ + std::vector myVectorToStdVector(const Vector3& x) + { + return std::vector{x.coeff(0), x.coeff(1), x.coeff(2)}; + } + + std::vector myVectorToStdVector(const Vector6& x) + { + return std::vector{x.coeff(0), + x.coeff(1), + x.coeff(2), + x.coeff(3), + x.coeff(4), + x.coeff(5)}; + } +} + +class Vector3 : public MyVector<3> +{ +public: + template + Vector3(Args&&... args) : MyVector<3>(std::forward(args)...) + {} + operator std::vector() + { + return std::vector{this->coeff(0), this->coeff(1), this->coeff(2)}; + } +}; + +class Vector6 : public MyVector<6> +{ +public: + template + Vector6(Args&&... args) : MyVector<6>(std::forward(args)...) + {} + operator std::vector() + { + return std::vector{this->coeff(0), + this->coeff(1), + this->coeff(2), + this->coeff(3), + this->coeff(4), + this->coeff(5)}; + } +}; +*/ + + +#endif // UTILS_MATH_MATHTYPES_H \ No newline at end of file diff --git a/utils/math/Quaternion.cpp b/utils/math/Quaternion.cpp deleted file mode 100644 index c575d0f..0000000 --- a/utils/math/Quaternion.cpp +++ /dev/null @@ -1,25 +0,0 @@ - -// qtrocket headers -#include "utils/math/Quaternion.h" - -namespace math -{ - -Quaternion::Quaternion() -{ - -} - -Quaternion::Quaternion(double r, const Vector3& i) - : real(r), - imag(i) -{ -} - -Quaternion::Quaternion(double r, double i1, double i2, double i3) - : real(r), - imag(i1, i2, i3) -{ -} - -} // namespace math diff --git a/utils/math/Quaternion.h b/utils/math/Quaternion.h deleted file mode 100644 index 2d8a848..0000000 --- a/utils/math/Quaternion.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef MATH_QUATERNION_H -#define MATH_QUATERNION_H - -/// \cond -// C headers -// C++ headers -#include - -// 3rd party headers -/// \endcond - -// qtrocket headers -#include "Vector3.h" - - -namespace math -{ - -class Quaternion -{ -public: - Quaternion(); - ~Quaternion() {} - - Quaternion(double r, const Vector3& i); - Quaternion(double r, double i1, double i2, double i3); - - Quaternion(const Quaternion&) = default; - Quaternion(Quaternion&&) = default; - - Quaternion& operator=(const Quaternion& rhs) - { - if(this == &rhs) - return *this; - real = rhs.real; - imag = rhs.imag; - return *this; - } - Quaternion& operator=(Quaternion&& rhs) - { - if(this == &rhs) - return *this; - real = std::move(rhs.real); - imag = std::move(rhs.imag); - return *this; - } - - /* - Quaternion operator-() {} - Quaternion operator-(const Quaternion& ) {} - Quaternion operator+(const Quaternion& ) {} - Quaternion operator*(double ) {} - Quaternion operator*(const Quaternion& ) {} - */ - -private: - double real; - Vector3 imag; -}; - -} // namespace math - -#endif // MATH_QUATERNION_H diff --git a/utils/math/UtilityMathFunctions.cpp b/utils/math/UtilityMathFunctions.cpp deleted file mode 100644 index 21d5dfa..0000000 --- a/utils/math/UtilityMathFunctions.cpp +++ /dev/null @@ -1,15 +0,0 @@ - -/// \cond -// C headers -// C++ headers -// 3rd party headers -/// \endcond - - -namespace utils -{ -namespace math -{ - -} // namespace math -} // namespace utils diff --git a/utils/math/Vector3.cpp b/utils/math/Vector3.cpp deleted file mode 100644 index 90c7f1b..0000000 --- a/utils/math/Vector3.cpp +++ /dev/null @@ -1,68 +0,0 @@ - -// qtrocket headers -#include "utils/math/Vector3.h" - -namespace math -{ - -Vector3::Vector3() - : x1(0.0), - x2(0.0), - x3(0.0) -{ - -} - -Vector3::Vector3(const double& inX1, - const double& inX2, - const double& inX3) - : x1(inX1), - x2(inX2), - x3(inX3) -{ - -} - -Vector3::Vector3(const Vector3& o) - : x1(o.x1), - x2(o.x2), - x3(o.x3) -{ - -} - -Vector3::~Vector3() -{} - -Vector3 Vector3::operator=(const Vector3& rhs) -{ - return Vector3(rhs.x1, rhs.x2, rhs.x3); -} - -Vector3 Vector3::operator-() -{ - return Vector3(-x1, -x2, -x3); -} - -Vector3 Vector3::operator-(const Vector3& rhs) -{ - return Vector3(x1 - rhs.x1, - x2 - rhs.x2, - x3 - rhs.x3); -} - -Vector3 Vector3::operator+(const Vector3& rhs) -{ - return Vector3(x1 + rhs.x1, - x2 + rhs.x2, - x3 + rhs.x3); -} - -Vector3 Vector3::operator*(const double& s) -{ - return Vector3(x1 * s, - x2 * s, - x3 * s); -} - -} // namespace math diff --git a/utils/math/Vector3.h b/utils/math/Vector3.h deleted file mode 100644 index 6a42472..0000000 --- a/utils/math/Vector3.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef MATH_VECTOR3_H -#define MATH_VECTOR3_H - - -namespace math -{ - -class Vector3 -{ -public: - Vector3(); - Vector3(const double& inX1, - const double& inX2, - const double& inX3); - - Vector3(const Vector3& o); - - ~Vector3(); - - Vector3 operator=(const Vector3& rhs); - - Vector3 operator-(); - Vector3 operator-(const Vector3& rhs); - Vector3 operator+(const Vector3& rhs); - Vector3 operator*(const double& s); - - double getX1() { return x1; } - double getX2() { return x2; } - double getX3() { return x3; } - - -//private: - double x1; - double x2; - double x3; -}; - -} // namespace math - -#endif // MATH_VECTOR3_H