diff --git a/aspects/electrical/LosslessSource/GunnsLosslessSource.cpp b/aspects/electrical/LosslessSource/GunnsLosslessSource.cpp new file mode 100644 index 00000000..f1765e10 --- /dev/null +++ b/aspects/electrical/LosslessSource/GunnsLosslessSource.cpp @@ -0,0 +1,246 @@ +#include "GunnsLosslessSource.hh" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @param[in] name (--) Link name +/// @param[in] nodes (--) Network nodes array +/// +/// @details Constructs the Lossless Source Config data +//////////////////////////////////////////////////////////////////////////////////////////////////// +GunnsLosslessSourceConfigData::GunnsLosslessSourceConfigData( + const std::string& name, + GunnsNodeList* nodes, + TsLinearInterpolator* efficiencyTable +) + : GunnsBasicSourceConfigData(name, nodes) + , mEfficiencyTable(efficiencyTable) +{ + // nothing to do + return; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @param[in] that (--) Object to copy +/// +/// @details Copy Constructs the Lossless Source Config data +//////////////////////////////////////////////////////////////////////////////////////////////////// +GunnsLosslessSourceConfigData::GunnsLosslessSourceConfigData(const GunnsLosslessSourceConfigData& that) + : GunnsBasicSourceConfigData(that) +{ + // nothing to do +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Destructs the Lossless Source Config Data Object +//////////////////////////////////////////////////////////////////////////////////////////////////// +GunnsLosslessSourceConfigData::~GunnsLosslessSourceConfigData() +{ + // nothing to do +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @param[in] malfBlockageFlag (--) Blockage malfunction flag +/// @param[in] malfBlockageValue (--) Blockage malfunction fractional value (0-1) +/// @param[in] sourceFlux (--) Initial demanded flux of the link +/// +/// @details Default constructs this Basic Source input data. +//////////////////////////////////////////////////////////////////////////////////////////////////// +GunnsLosslessSourceInputData::GunnsLosslessSourceInputData( + const bool malfBlockageFlag, + const double malfBlockageValue, + const double sourceFlux, + const double efficiency, + const double referencePower, + const bool staticEfficiency +) + : GunnsBasicSourceInputData(malfBlockageFlag, malfBlockageValue, sourceFlux) + , mEfficiency(efficiency) + , mReferencePower(referencePower) + , mStaticEfficiency(staticEfficiency) +{ + // nothing to do +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @param[in] that (--) Object to copy +/// +/// @details Copy constructs this Lossless Source input data. +//////////////////////////////////////////////////////////////////////////////////////////////////// +GunnsLosslessSourceInputData::GunnsLosslessSourceInputData(const GunnsLosslessSourceInputData& that) + : GunnsBasicSourceInputData(that) +{ + // nothing to do +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Default destructs this Lossless Source input data. +//////////////////////////////////////////////////////////////////////////////////////////////////// +GunnsLosslessSourceInputData::~GunnsLosslessSourceInputData() +{ +// nothing to do +} + + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Default constructs link. +//////////////////////////////////////////////////////////////////////////////////////////////////// +GunnsLosslessSource::GunnsLosslessSource() + : GunnsBasicSource() + , mEfficiency(1.0) + , mWastePower(0.0) +{ + +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Validates the link initialization +//////////////////////////////////////////////////////////////////////////////////////////////////// +void GunnsLosslessSource::validate(const GunnsLosslessSourceConfigData &configData, const GunnsLosslessSourceInputData &inputData) +{ + if (inputData.mSourceFlux < 0.0) { + GUNNS_ERROR(TsInitializationException, "Invalid Initialization Data", "GunnsLosslessSource sourceFlux must be > 0"); + } + if (inputData.mEfficiency <= 0.0 || inputData.mEfficiency > 1.0) { + GUNNS_ERROR(TsInitializationException, "Invalid Initialization Data", "GunnsLosslessSource Efficiency must be in range (0.0, 1.0]"); + } + if (inputData.mReferencePower <= 0.0) { + GUNNS_ERROR(TsInitializationException, "Invalid Initialization Data", "GunnsLosslessSource reference power must be > 0"); + } + if (!inputData.mStaticEfficiency && !configData.mEfficiencyTable) { + GUNNS_ERROR(TsInitializationException, "Invalid Initialization Data", "GunnsLosslessSource must use static efficiency if no efficiencyTable is provided (mStaticEfficiency = false && mEfficiencyTable = nullptr -> 🙅‍♂️)"); + } + + // Copied from GunnsElectConverterInput + if (configData.mEfficiencyTable) { + /// - Issue an error on table limits out of bounds of valid efficiency (DBL_EPSILON-1). + /// Check at every 10% power fraction. + for (int i=0; i<11; ++i) { + if (not MsMath::isInRange(DBL_EPSILON, configData.mEfficiencyTable->get(0.1 * i), 1.0)) { + GUNNS_ERROR(TsInitializationException, "Invalid Configuration Data", + "some of the efficiency table is not in valid range (DBL_EPSILON-1)"); + } + } + } + +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @param[in] configData (--) Link Config Data +/// @param[in] inputData (--) Link Input Data +/// @param[in,out] networkLinks (--) Reference to the Solver Links +/// @param[in] port0 (--) Port 0 Node Mapping +/// @param[in] port1 (--) Port 1 Node Mapping +/// +/// @throws TsInitializationException +/// +/// @details This initializes the link and sets up its connectivity to the network. +//////////////////////////////////////////////////////////////////////////////////////////////////// +void GunnsLosslessSource::initialize( + const GunnsLosslessSourceConfigData& configData, + const GunnsLosslessSourceInputData& inputData, + std::vector& networkLinks, + const int port0, + const int port1 +) +{ + GunnsBasicSource::initialize(configData, inputData, networkLinks, port0, port1); + this->mSourceFlux = inputData.mSourceFlux; + this->mEfficiency = inputData.mEfficiency; + this->mReferencePower = inputData.mReferencePower; + this->mStaticEfficiency = inputData.mStaticEfficiency; + + this->mEfficiencyTable = configData.mEfficiencyTable; + this->mInitFlag = true; +} + +/// @brief Computes mPower for the aspect-specific implementation +void GunnsLosslessSource::computePower() { + // mPotentialVector should always be (+) + double powerOut = mPotentialVector[1]*mSourceVector[1]; // mSourceVector[1] (I_out) should always be (+) + double powerIn = mPotentialVector[0]*mSourceVector[0]; // mSourceVector[0] (I_in) should always be (-) + mPower = powerIn + powerOut; + mWastePower = mPower; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Makes calls to accumulate flux in the input & output terms of the receiving and +/// sending nodes, respectively. +//////////////////////////////////////////////////////////////////////////////////////////////////// +void GunnsLosslessSource::transportFlux() { + /** Ignore the params because we only go from 0 to 1, but want to override the GunnsBasicLink::transportFlux */ + /** + * mNodes[0].flux = mNodes[1].flux*mNodes[1].potential/mNodes[0].potential + */ + // I think I can just use the mSourceVector instead of working from mSourceFlux here + // double fluxOnInputNode = -1*mSourceFlux*mNodes[1]->getPotential()/mNodes[0]->getPotential(); + double fluxOnInputNode = this->mSourceVector[0]; + if (mSourceFlux > 0.0) { + mNodes[0]->collectOutflux(std::fabs(fluxOnInputNode)); + mNodes[1]->collectInflux (std::fabs(mSourceFlux)); + + } else if (mSourceFlux < 0.0) { + mNodes[1]->collectOutflux(std::fabs(mSourceFlux)); + mNodes[0]->collectInflux (std::fabs(fluxOnInputNode)); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Ignores inputs and calls normal `transportFlux` +//////////////////////////////////////////////////////////////////////////////////////////////////// +void GunnsLosslessSource::transportFlux(const int, const int) +{ + this->transportFlux(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Computes the flows across the link +//////////////////////////////////////////////////////////////////////////////////////////////////// +void GunnsLosslessSource::computeFlows(const double dt) +{ + mPotentialDrop = getDeltaPotential(); + computePower(); + transportFlux(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Builds the source vector terms of the links contribution to the network. This sign +/// convention creates positive flow from port 0 to port 1. +//////////////////////////////////////////////////////////////////////////////////////////////////// +void GunnsLosslessSource::buildSource() +{ + /** + * Unlike GunnsBasicSource, this link tries to satisfy the eqn + * Pot_in*Flux_in = Pot_out*Flux_out + * If the potential on input is 0, we divide by DBL_EPSILON instead + */ + + // First update efficiency if we're supposed to and have a proper eff table + this->mOutputPower = this->mSourceFlux * this->mPotentialVector[1]; + this->mEfficiency = this->estimateEfficiencyAtLoad(this->mOutputPower); + this->mInputPower = this->mOutputPower / this->mEfficiency; + double inputFlux = this->mInputPower / this->mPotentialVector[0]; + + if (mPotentialVector[0] == 0.0) { + mPotentialVector[0] = __DBL_EPSILON__; + mOverrideVector[0] = true; + } + // Apply mEfficiency to mSourceVector[0] + // I_in*V_in = I_out*V_out/mEfficiency + if (mSourceFlux != 0.0) { + mSourceVector[0] = -1 * inputFlux; + mSourceVector[1] = mSourceFlux; + } else { + mSourceVector[0] = 0.0; + mSourceVector[1] = 0.0; + } + +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Dtor +//////////////////////////////////////////////////////////////////////////////////////////////////// +GunnsLosslessSource::~GunnsLosslessSource() +{ + // nothing to do +} diff --git a/aspects/electrical/LosslessSource/GunnsLosslessSource.hh b/aspects/electrical/LosslessSource/GunnsLosslessSource.hh new file mode 100644 index 00000000..ebe4f4cc --- /dev/null +++ b/aspects/electrical/LosslessSource/GunnsLosslessSource.hh @@ -0,0 +1,219 @@ +#ifndef GunnsLosslessSource_EXISTS +#define GunnsLosslessSource_EXISTS + +/** +@file +@brief GUNNS Lossless Source Link declarations + +@defgroup TSM_GUNNS_ELECT_LINK_LOSSLESS_SOURCE_LINK GUNNS Lossless Source Link +@ingroup TSM_GUNNS_ELECT_LINK_LOSSLESS_SOURCE + +@copyright Copyright 2019 United States Government as represented by the Administrator of the + National Aeronautics and Space Administration. All Rights Reserved. + +@details +PURPOSE: +- (Classes for the GUNNS Lossless Source Model.) + +REFERENCE: +- (TBD) + +ASSUMPTIONS AND LIMITATIONS: +- (TBD) + +LIBRARY_DEPENDENCY: +- ( + (./GunnsLosslessSource.o) + ) + +PROGRAMMERS: +- ( + (Tristan Mansfield) (Axiom Space) (2025-03) (Specialized Source to allow for power conservation and an efficiency term) + ) + +@{ +*/ + +#include "math/approximation/TsLinearInterpolator.hh" +#include "software/SimCompatibility/TsSimCompatibility.hh" +#include "software/exceptions/TsInitializationException.hh" +#include "core/GunnsBasicSource.hh" +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Lossless Source Configuration Data +/// +/// @details The sole purpose of this class is to provide a data structure for the Lossless Source +/// configuration data. +//////////////////////////////////////////////////////////////////////////////////////////////////// +class GunnsLosslessSourceConfigData : public GunnsBasicSourceConfigData +{ + public: + /// @brief Default constructs this Lossless Source configuration data. + GunnsLosslessSourceConfigData( + const std::string& name = "", + GunnsNodeList* nodes = 0, + TsLinearInterpolator* efficiencyTable = 0 + ); + + /// @brief Default destructs this Lossless Source configuration data. + virtual ~GunnsLosslessSourceConfigData(); + + /// @brief Copy constructs this Lossless Source configuration data. + GunnsLosslessSourceConfigData(const GunnsLosslessSourceConfigData& that); + TsLinearInterpolator* mEfficiencyTable; /**< (1) trick_chkpnt_io(**) Pointer to the converter efficiency vs. power fraction table. */ + protected: + + private: + /// @brief Assignment operator unavailable since declared private and not implemented. + GunnsLosslessSourceConfigData& operator =(const GunnsLosslessSourceConfigData&); +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Lossless Source Input Data +/// +/// @details The sole purpose of this class is to provide a data structure for the Lossless Source +/// input data. +//////////////////////////////////////////////////////////////////////////////////////////////////// +class GunnsLosslessSourceInputData : public GunnsBasicSourceInputData +{ + public: + /// @brief Default constructs this Lossless Source input data. + GunnsLosslessSourceInputData( + const bool malfBlockageFlag = false, + const double malfBlockageValue = 0.0, + const double sourceFlux = 0.0, + const double efficiency = 0.0, + const double referencePower = 0.0, + const bool staticEfficiency = true + ); + + /// @brief Default destructs this Lossless Source input data. + virtual ~GunnsLosslessSourceInputData(); + + /// @brief Copy constructs this Lossless Source input data. + GunnsLosslessSourceInputData(const GunnsLosslessSourceInputData& that); + double mEfficiency; + double mReferencePower; /**< (W) trick_chkpnt_io(**) Initial reference power load for efficiency calculation. */ + bool mStaticEfficiency; + protected: + + private: + /// @details Assignment operator unavailable since declared private and not implemented. + GunnsLosslessSourceInputData& operator =(const GunnsLosslessSourceInputData&); + +}; + +class GunnsLosslessSource : public GunnsBasicSource +{ + /** + * TODO_: Make this inherit from GunnsBasicLink instead of GunnsBasicSource + * && write all logic, instead of doing this weird inheritance dance between + * BasicSource and BasicLink -- It's not worth the headache of tracing nested + * virtual calls + * + * For now, the call-graph is: + * GunnsBasicSource::step { + * GunnsBasicLink::processUserPortCommand + * GunnsBasicLink::updateState + * ... Apply mMalfBlockageFlag ... + * GunnsLosslessSource::buildSource { + * ... + * } + * Much later.. + * GunnsLosslessSource::computeFlows{ + * GunnsLosslessSource::computePower + * GunnsLosslessSource::transportFlux + * } + * ... Some more stuff + * } + */ + TS_MAKE_SIM_COMPATIBLE(GunnsLosslessSource); + + public: + /// @brief Default Constructor + GunnsLosslessSource(); + + /// @brief Default Destructor + virtual ~GunnsLosslessSource(); + + /// @brief Initializes the link + void initialize( + const GunnsLosslessSourceConfigData& configData, + const GunnsLosslessSourceInputData& inputData, + std::vector& networkLinks, + const int port0, + const int port1 + ); + + inline double getEfficiency() { + return this->mEfficiency; + }; + + inline bool setEfficiency(double newEfficiency) { + if (newEfficiency <= 1.0 && newEfficiency > 0.0) { + this->mEfficiency = newEfficiency; + return true; + } + return false; + } + + void validate( + const GunnsLosslessSourceConfigData& configData, + const GunnsLosslessSourceInputData& inputData + ); + + inline double getWastePower() { + return this->mWastePower; + } + + inline double estimateEfficiencyAtLoad(double power) { + // If we're using dynamic efficiency, return the eff at given load, otherwise return current efficiency + double eff = this->mEfficiency; + if (!this->mStaticEfficiency && this->mSourceFlux > 0.0 && this->mEfficiencyTable != nullptr && this->mEfficiencyTable->isInitialized()) { + eff = this->mEfficiencyTable->get(power); + } + return eff; + } + + // Member vars + double mReferencePower; /**< (1) Reference power load for efficiency calculation. Please just set this to 1. */ + + protected: + /// @brief Calculates flow across the link + /// - This was only written to make the link use GunnsLossessSource::transportFlux + virtual void computeFlows(const double dt) override; + + /// @brief Computes mPower given potentials and fluxes at each port (should be near 0.0) + virtual void computePower() override; + + /// @brief Influx / Outflux on nodes does not = mFlux + void transportFlux(); + void transportFlux(const int, const int) override; + + bool mStaticEfficiency; /**< (--) cio(*io) io(*io) If true, don't update efficiency based on power throughput */ + double mEfficiency; /**< (%) trick_chkpnt_io(*io) io(*io) Efficiency of source; if not mStaticEfficiency, then calculated from mEfficiencyTable each timestep -- I_in = I_out / mEfficiency ; I_out = mSourceFlux */ + // Copying GunnsElectConverterInput 's 'power fraction' approach, but recommend to use mReferencePower to 1.0 for simplicity + TsLinearInterpolator* mEfficiencyTable; /**< (1) trick_chkpnt_io(**) Pointer to the converter efficiency vs. power fraction table. */ + double mWastePower; /**< (W) trick_chkpnt_io(*io) io(*o) How much power is wasted from mEfficiency; mWasteHeat = Power_in - Power_out */ + + double mOutputPower; /**< (W) cio(*io) io(*io) power to output node */ + double mInputPower; /**< (W) cio(*io) io(*io) power from input node */ + + private: + /// @details Define the number of ports this link class has. All objects of the same link + /// class always have the same number of ports. We use an enum rather than a + /// static const int so that we can reuse the NPORTS name and allow each class to + /// define its own value. + enum {NPORTS = 2}; + /// @brief Builds the source vector terms of the links contribution to the network + virtual void buildSource() override; + + /// @brief Copy constructor unavailable since declared private and not implemented. + GunnsLosslessSource(const GunnsLosslessSource& that); + + /// @brief Assignment operator unavailable since declared private and not implemented. + GunnsLosslessSource& operator =(const GunnsLosslessSource& that); +}; + +#endif \ No newline at end of file diff --git a/aspects/electrical/LosslessSource/test/Makefile b/aspects/electrical/LosslessSource/test/Makefile new file mode 100644 index 00000000..9006d744 --- /dev/null +++ b/aspects/electrical/LosslessSource/test/Makefile @@ -0,0 +1,5 @@ +# Copyright 2019 United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +no_TRICK_ENV = 1 +include ${GUNNS_HOME}/test/utils/Makefile.default + diff --git a/aspects/electrical/LosslessSource/test/UtGunnsLosslessSource.cpp b/aspects/electrical/LosslessSource/test/UtGunnsLosslessSource.cpp new file mode 100755 index 00000000..fac1865b --- /dev/null +++ b/aspects/electrical/LosslessSource/test/UtGunnsLosslessSource.cpp @@ -0,0 +1,436 @@ +/************************** TRICK HEADER *********************************************************** +@copyright Copyright 2019 United States Government as represented by the Administrator of the + National Aeronautics and Space Administration. All Rights Reserved. + + LIBRARY DEPENDENCY: + ( + (../GunnsLosslessSource.o) + ) +***************************************************************************************************/ +#include "UtGunnsLosslessSource.hh" + +int UtGunnsLosslessSource::TEST_ID = 0; +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details This is the default constructor for the UtGunnsLosslessSource class. +//////////////////////////////////////////////////////////////////////////////////////////////////// +UtGunnsLosslessSource::UtGunnsLosslessSource() + : + mConfigData(), + mInputData(), + mArticle(), + mLinkName(), + mInitialDemand(), + mNodes(), + mNodeList(), + mLinks(), + mPort0(), + mPort1(), + mTimeStep(), + mTolerance() +{ + //do nothing +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details This is the default destructor for the UtGunnsLosslessSource class. +//////////////////////////////////////////////////////////////////////////////////////////////////// +UtGunnsLosslessSource::~UtGunnsLosslessSource() +{ + //do nothing +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Executed after each unit test. +//////////////////////////////////////////////////////////////////////////////////////////////////// +void UtGunnsLosslessSource::tearDown() +{ + /// - Deletes for news in setUp + delete mArticle; + delete mInputData; + delete mConfigData; + delete mEffTable; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Executed before each unit test. +//////////////////////////////////////////////////////////////////////////////////////////////////// +void UtGunnsLosslessSource::setUp() +{ + mLinkName = "Test Lossless Source"; + mNodeList.mNumNodes = 2; + mNodeList.mNodes = mNodes; + mInitialDemand = 0.5; + mEfficiency = 1.0; + mPort0 = 0; + mPort1 = 1; + + this->xArr[0] = 1.0; + this->xArr[1] = 2.0; + this->yArr[0] = 0.1; + this->yArr[1] = 0.9; + mEffTable = new TsLinearInterpolator(xArr, yArr, this->nArr, xArr[0], xArr[1]); + + mDefReferencePower = 1.0; + mDefStaticEff = true; + + /// - Define nominal configuration data + mConfigData = new GunnsLosslessSourceConfigData(mLinkName, &mNodeList, this->mEffTable); + + /// - Define nominal input data + mInputData = new GunnsLosslessSourceInputData(true, 0.5, mInitialDemand, mEfficiency, mDefReferencePower, mDefStaticEff); + + /// - Create the test article + mArticle = new FriendlyGunnsLosslessSource; + + /// - Declare the nominal test data + mTolerance = 1.0e-08; + mTimeStep = 0.1; + + TEST_ID ++; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Tests for construction of config data. +//////////////////////////////////////////////////////////////////////////////////////////////////// +void UtGunnsLosslessSource::testConfig() +{ + UT_RESULT; + // std::cout << "\n -----------------------------------------------------------------------------"; + // std::cout << "\n UtGunnsLosslessSource ..... 01: testConfig ............................"; + + /// - Check nominal config construction + CPPUNIT_ASSERT(mLinkName == mConfigData->mName); + CPPUNIT_ASSERT(mNodes == mConfigData->mNodeList->mNodes); + + /// - Check default config construction + GunnsLosslessSourceConfigData defaultConfig; + CPPUNIT_ASSERT("" == defaultConfig.mName); + CPPUNIT_ASSERT(0 == defaultConfig.mNodeList); + + /// - Check copy config construction + GunnsLosslessSourceConfigData copyConfig(*mConfigData); + CPPUNIT_ASSERT(mLinkName == copyConfig.mName); + CPPUNIT_ASSERT(mNodes == copyConfig.mNodeList->mNodes); + + UT_PASS; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Tests for construction of input data. +//////////////////////////////////////////////////////////////////////////////////////////////////// +void UtGunnsLosslessSource::testInput() +{ + UT_RESULT; + + /// - Check nominal input construction + CPPUNIT_ASSERT(mInputData->mMalfBlockageFlag); + CPPUNIT_ASSERT(0.5 == mInputData->mMalfBlockageValue); + CPPUNIT_ASSERT(mInitialDemand == mInputData->mSourceFlux); + + /// - Check default input construction + GunnsLosslessSourceInputData defaultInput; + CPPUNIT_ASSERT(!defaultInput.mMalfBlockageFlag); + CPPUNIT_ASSERT(0.0 == defaultInput.mMalfBlockageValue); + CPPUNIT_ASSERT(0.0 == defaultInput.mSourceFlux); + + /// - Check copy input construction + GunnsLosslessSourceInputData copyInput(*mInputData); + CPPUNIT_ASSERT(mInputData->mMalfBlockageFlag == copyInput.mMalfBlockageFlag); + CPPUNIT_ASSERT(mInputData->mMalfBlockageValue == copyInput.mMalfBlockageValue); + CPPUNIT_ASSERT(mInitialDemand == copyInput.mSourceFlux); + + UT_PASS; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Test for default construction without exceptions. +//////////////////////////////////////////////////////////////////////////////////////////////////// +void UtGunnsLosslessSource::testDefaultConstruction() +{ + UT_RESULT; + + /// @test config data + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, + mArticle->mSourceFlux, + mTolerance); + + /// @test init flag + CPPUNIT_ASSERT(!mArticle->mInitFlag); + + /// @test new/delete for code coverage + GunnsLosslessSource* article = new GunnsLosslessSource(); + delete article; + + UT_PASS; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Test for nominal initialization without exceptions. +//////////////////////////////////////////////////////////////////////////////////////////////////// +void UtGunnsLosslessSource::testNominalInitialization() +{ + UT_RESULT; + + /// - Default construct and initialize (with nominal data) a test article + FriendlyGunnsLosslessSource article; + article.initialize(*mConfigData, *mInputData, mLinks, mPort0, mPort1); + + /// @test config data + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, article.mMalfBlockageValue, 0.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mInitialDemand, article.mSourceFlux, 0.0); + + /// @test init flag + CPPUNIT_ASSERT(article.mInitFlag); + + UT_PASS; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Test for accessors. +//////////////////////////////////////////////////////////////////////////////////////////////////// +void UtGunnsLosslessSource::testStep() +{ + UT_RESULT; + + /// - Initialize default test article with nominal initialization data + mArticle->initialize(*mConfigData, *mInputData, mLinks, mPort0, mPort1); + + /// - Initialize the nodes potentials + mNodes[0].setPotential(100.0); + mNodes[1].setPotential( 0.0); + mArticle->mPotentialVector[0] = 100.0; + mArticle->mPotentialVector[1] = 0.0; + + mArticle->step(mTimeStep); + + /// - Not using the malfunction flag now, may change this in the future + // /// - during step the source Vector will be equal to the source flux times the blockage malf. + // CPPUNIT_ASSERT_DOUBLES_EQUAL(mArticle->mSourceFlux * 0.5, + // mArticle->mSourceVector[1], DBL_EPSILON); + + /** This is the change from GunnsBasicSource + * Potential*flux = "Power" + * For conservation of power we expect Pot_in*flux_in = Pot_out*flux_out + * We set flux_out, so solving for flux_in: + * flux_in = (Pot_out/Pot_in)*flux_out + */ + double expected_influx = std::fabs(mNodes[1].getPotential()/mNodes[0].getPotential()*mArticle->mSourceFlux); + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected_influx, + mArticle->mSourceVector[0], + DBL_EPSILON + ); + + + mNodes[0].resetFlows(); + mNodes[1].resetFlows(); + + /// - Initialize the nodes potentials at something more interesting + mNodes[0].setPotential(600.0); + mNodes[1].setPotential(135.0); + mArticle->mPotentialVector[0] = 600.0; + mArticle->mPotentialVector[1] = 135.0; + + mArticle->step(mTimeStep); + + expected_influx = std::fabs(mNodes[1].getPotential()/mNodes[0].getPotential()*mArticle->mSourceFlux); + CPPUNIT_ASSERT_DOUBLES_EQUAL(-1*expected_influx, + mArticle->mSourceVector[0], + DBL_EPSILON + ); + + UT_PASS; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Test for Compute Flows. +//////////////////////////////////////////////////////////////////////////////////////////////////// +void UtGunnsLosslessSource::testLosslessComputeFlows() +{ + UT_RESULT; + + /// - Initialize default test article with nominal initialization data + mArticle->initialize(*mConfigData, *mInputData, mLinks, mPort0, mPort1); + + // NOTE_ What should the link do if there's 0V on the output? Seems like there should be 0 current too? + { + /// - Test 1: 100V input, 0V output + /// - Initialize the nodes potentials + mNodes[0].setPotential(100.0); + mNodes[1].setPotential( 0.0); + mArticle->mPotentialVector[0] = 100.0; + mArticle->mPotentialVector[1] = 0.0; + mArticle->mMalfBlockageFlag = false; + + mArticle->step(mTimeStep); + mArticle->computeFlows(mTimeStep); + + /// - Check potential drop and power across the link is updated + CPPUNIT_ASSERT_DOUBLES_EQUAL(mNodes[0].getPotential() - mNodes[1].getPotential(), + mArticle->mPotentialDrop, + DBL_EPSILON + ); + + /// - The power across this link should always be near 0.0 + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, mArticle->mPower, DBL_EPSILON); + + /// - Check flux is transported to/from the nodes + double expected_influx = std::fabs(mNodes[1].getPotential()/mNodes[0].getPotential()*mInitialDemand); + double expected_outflux = std::fabs(mInitialDemand); + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected_outflux, mNodes[1].getInflux(), 0.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected_influx, mNodes[0].getOutflux(), 0.0); + } + + mNodes[0].resetFlows(); + mNodes[1].resetFlows(); + + + { + /// - Test 2: 100V input, 13V output + /// - Initialize the nodes potentials + mNodes[0].setPotential(100.0); + mNodes[1].setPotential( 13.0); + mArticle->mPotentialVector[0] = 100.0; + mArticle->mPotentialVector[1] = 13.0; + mArticle->mMalfBlockageFlag = false; + + mArticle->step(mTimeStep); + mArticle->computeFlows(mTimeStep); + + /// - Check potential drop and power across the link is updated + CPPUNIT_ASSERT_DOUBLES_EQUAL(mNodes[0].getPotential() - mNodes[1].getPotential(), + mArticle->mPotentialDrop, + DBL_EPSILON + ); + + /// - The power across this link should always be near 0.0 + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, mArticle->mPower, DBL_EPSILON); + + /** + * 0.5A*13V = 6.5W + * 6.5W/100V = 0.0065A + * mNodes[0].getOutflux() should give us 0.0065 + */ + + /// - Check flux is transported to/from the nodes + double expected_influx = std::fabs(mNodes[1].getPotential()/mNodes[0].getPotential()*mInitialDemand); + double expected_outflux = std::fabs(mInitialDemand); + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected_outflux, mNodes[1].getInflux(), 0.0); // Exact Assignment + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected_influx, mNodes[0].getOutflux(), DBL_EPSILON); // Calculated Value + } + + UT_PASS; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Tests setter methods. +//////////////////////////////////////////////////////////////////////////////////////////////////// +void UtGunnsLosslessSource::testSetters() +{ + UT_RESULT; + + /// - Initialize default test article with nominal initialization data + mArticle->initialize(*mConfigData, *mInputData, mLinks, mPort0, mPort1); + + /// - Set the source flux demand using the setter method and verify. + mArticle->setFluxDemand(1.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, mArticle->mSourceFlux, 0.0); + + UT_PASS; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @details Tests access methods. +//////////////////////////////////////////////////////////////////////////////////////////////////// +void UtGunnsLosslessSource::testAccessMethods() +{ + UT_RESULT; + + /// - Initialize default test article with nominal initialization data + mArticle->initialize(*mConfigData, *mInputData, mLinks, mPort0, mPort1); + + CPPUNIT_ASSERT(mArticle->isInitialized()); + /// - Get the source flux demand using the getter method and verify. + mArticle->setFluxDemand(5.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0, mArticle->getFluxDemand(), 0.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, mArticle->mEfficiency, 0.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, mArticle->getEfficiency(), 0.0); + + UT_PASS; +} + +void UtGunnsLosslessSource::testInefficiencyAndWaste() +{ + UT_RESULT; + + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, mInputData->mEfficiency, 0.0); + mArticle->initialize(*mConfigData, *mInputData, mLinks, mPort0, mPort1); + double startEff = this->mArticle->getEfficiency(); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, startEff, 0.0); + double lowerEff = 0.9; + this->mArticle->setEfficiency(lowerEff); + CPPUNIT_ASSERT_DOUBLES_EQUAL(lowerEff, this->mArticle->getEfficiency(), 0.0); + + this->mInputData->mEfficiency = lowerEff; + /// - Initialize default test article with nominal initialization data + mArticle->initialize(*mConfigData, *mInputData, mLinks, mPort0, mPort1); + + { + /// - Test 1: 100V input, 10V output + /// - Initialize the nodes potentials + /// - Test initialDemand = 0.5 + mNodes[0].setPotential(100.0); + mNodes[1].setPotential( 10.0); + mArticle->mPotentialVector[0] = 100.0; + mArticle->mPotentialVector[1] = 10.0; + mArticle->mMalfBlockageFlag = false; + + mArticle->step(mTimeStep); + mArticle->computeFlows(mTimeStep); + + /// - Check potential drop and power across the link is updated + CPPUNIT_ASSERT_DOUBLES_EQUAL(mNodes[0].getPotential() - mNodes[1].getPotential(), + mArticle->mPotentialDrop, + DBL_EPSILON + ); + + /// - The power across this link should be PowerThrough*(1-Efficiency) + double powerIn = mNodes[0].getPotential() * mArticle->getSourceVector()[0]; + double powerOut = mNodes[1].getPotential() * mArticle->getSourceVector()[1]; + double expectedWastePower = powerIn*(1.0 - lowerEff); + + /// - Check "Power values" + CPPUNIT_ASSERT_DOUBLES_EQUAL(expectedWastePower, mArticle->mPower, DBL_EPSILON*10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(expectedWastePower, powerIn + powerOut, DBL_EPSILON*10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(mArticle->mPower, mArticle->getWastePower(), DBL_EPSILON); + CPPUNIT_ASSERT_GREATER(0.0, std::fabs(mArticle->mPower)); + + /// - Check correct flux is transported to/from the nodes + /// This uses efficiency term now + double expected_influx = std::fabs(mNodes[1].getPotential()/mNodes[0].getPotential()) *mInitialDemand/mArticle->getEfficiency(); + double expected_outflux = std::fabs(mInitialDemand); + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected_outflux, mNodes[1].getInflux(), DBL_EPSILON); + CPPUNIT_ASSERT_DOUBLES_EQUAL(expected_influx, mNodes[0].getOutflux(), DBL_EPSILON); + } + + mNodes[0].resetFlows(); + mNodes[1].resetFlows(); + + + { // Now test dynamic efficiency, by set static efficiency = false + this->mArticle->mStaticEfficiency = false; + mArticle->step(mTimeStep); + mArticle->computeFlows(mTimeStep); + + /// - The power across this link should be PowerThrough*(1-Efficiency) + double powerIn = mNodes[0].getPotential() * mArticle->getSourceVector()[0]; + double powerOut = mNodes[1].getPotential() * mArticle->getSourceVector()[1]; + double expectedEff = mArticle->mEfficiencyTable->get(powerOut); + double expectedWastePower = powerIn*(1.0 - expectedEff); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(expectedEff, mArticle->getEfficiency(), DBL_EPSILON); + CPPUNIT_ASSERT_DOUBLES_EQUAL(expectedWastePower , mArticle->mWastePower, DBL_EPSILON); + CPPUNIT_ASSERT_DOUBLES_EQUAL(expectedWastePower, powerIn+powerOut, DBL_EPSILON); + } + + UT_PASS; +} \ No newline at end of file diff --git a/aspects/electrical/LosslessSource/test/UtGunnsLosslessSource.hh b/aspects/electrical/LosslessSource/test/UtGunnsLosslessSource.hh new file mode 100755 index 00000000..1a81abe7 --- /dev/null +++ b/aspects/electrical/LosslessSource/test/UtGunnsLosslessSource.hh @@ -0,0 +1,134 @@ +#ifndef UtGunnsLosslessSource_EXISTS +#define UtGunnsLosslessSource_EXISTS + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @defgroup UT_GUNNS_LOSSLESS_SOURCE Gunns Lossless Source Unit Test +/// @ingroup UT_GUNNS +/// +/// @copyright Copyright 2019 United States Government as represented by the Administrator of the +/// National Aeronautics and Space Administration. All Rights Reserved. +/// +/// @details Unit Tests for the Gunns Lossless Source +/// @{ +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "strings/UtResult.hh" +#include "aspects/electrical/LosslessSource/GunnsLosslessSource.hh" +#include "math/approximation/TsLinearInterpolator.hh" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Inherit from GunnsLosslessSource and befriend UtGunnsLosslessSource. +/// +/// @details Class derived from the unit under test. It just has a constructor with the same +/// arguments as the parent and a default destructor, but it befriends the unit test case +/// driver class to allow it access to protected data members. +//////////////////////////////////////////////////////////////////////////////////////////////////// +class FriendlyGunnsLosslessSource : public GunnsLosslessSource +{ + public: + FriendlyGunnsLosslessSource(); + virtual ~FriendlyGunnsLosslessSource(); + friend class UtGunnsLosslessSource; +}; +inline FriendlyGunnsLosslessSource::FriendlyGunnsLosslessSource() : GunnsLosslessSource() {}; +inline FriendlyGunnsLosslessSource::~FriendlyGunnsLosslessSource() {} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Gunns Lossless Source unit tests. +//// +/// @details This class provides the unit tests for the GunnsLosslessSource class within the CPPUnit +/// framework. +//////////////////////////////////////////////////////////////////////////////////////////////////// +class UtGunnsLosslessSource: public CppUnit::TestFixture +{ + private: + /// @brief Copy constructor unavailable since declared private and not implemented. + UtGunnsLosslessSource(const UtGunnsLosslessSource& that); + /// @brief Assignment operator unavailable since declared private and not implemented. + UtGunnsLosslessSource& operator =(const UtGunnsLosslessSource& that); + + CPPUNIT_TEST_SUITE(UtGunnsLosslessSource); + CPPUNIT_TEST(testConfig); + CPPUNIT_TEST(testInput); + CPPUNIT_TEST(testDefaultConstruction); + CPPUNIT_TEST(testNominalInitialization); + CPPUNIT_TEST(testStep); + CPPUNIT_TEST(testLosslessComputeFlows); + CPPUNIT_TEST(testSetters); + CPPUNIT_TEST(testAccessMethods); + CPPUNIT_TEST(testInefficiencyAndWaste); + CPPUNIT_TEST_SUITE_END(); + + /// -- Pointer to nominal configuration data + GunnsLosslessSourceConfigData* mConfigData; + + /// -- Pointer to nominal input data + GunnsLosslessSourceInputData* mInputData; + + /// -- Test Article + FriendlyGunnsLosslessSource* mArticle; + + /// -- Link Name + std::string mLinkName; + + /// -- Link flux demand + double mInitialDemand; + + /// -- Link Default efficiency + double mEfficiency; + + double xArr[2] = {0}; + double yArr[2] = {0}; + const int nArr = 2; + TsLinearInterpolator* mEffTable; + + double mDefReferencePower; + bool mDefStaticEff; + + /// -- Network Nodes + GunnsBasicNode mNodes[2]; + + /// -- Node List + GunnsNodeList mNodeList; + + /// -- Network Links + std::vector mLinks; + + /// -- Nominal inlet port index + int mPort0; + + /// -- Nominal outlet port index + int mPort1; + + /// (s) Nominal time step + double mTimeStep; + + /// -- Nominal tolerance for comparison of expected and returned values + double mTolerance; + + public: + /// -- + static int TEST_ID; + UtGunnsLosslessSource(); + virtual ~UtGunnsLosslessSource(); + void tearDown(); + void setUp(); + void testConfig(); + void testInput(); + void testDefaultConstruction(); + void testNominalInitialization(); + void testStep(); + void testLosslessComputeFlows(); + void testSetters(); + void testAccessMethods(); + void testInefficiencyAndWaste(); +}; + +///@} + +#endif diff --git a/aspects/electrical/LosslessSource/test/main.cpp b/aspects/electrical/LosslessSource/test/main.cpp new file mode 100644 index 00000000..fea8fbd7 --- /dev/null +++ b/aspects/electrical/LosslessSource/test/main.cpp @@ -0,0 +1,28 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @defgroup UT_GUNNS_ELECT_AX Ax Elect Unit tests +/// @ingroup UT_GUNNS_ELECT +/// @details Unit test classes for Axiom GUNNS elect link models. +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "UtGunnsLosslessSource.hh" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// @param int -- not used +/// @param char** -- not used +/// +/// @return -- status (always 0) +/// +/// @details Main for GUNNS elect conductor link model unit tests in the CPPUNIT framework. +//////////////////////////////////////////////////////////////////////////////////////////////////// +int main(int, char**) +{ + CppUnit::TextTestRunner runner; + + runner.addTest(UtGunnsLosslessSource::suite()); + + runner.run(); + + return 0; +} diff --git a/bin/bashrc b/bin/bashrc index afa91131..3d174f98 100644 --- a/bin/bashrc +++ b/bin/bashrc @@ -54,12 +54,18 @@ function isNotInExcludeList { # Get the full environment from a tcsh shell that sourced the bin/cshrc, then # add quotes to the env lines so that var=bleh blah becomes var="bleh blah" # and replace every line ending with a ? as a delimeter -fullTcshEnv=`tcsh -f -c "source bin/cshrc $@; env | sed 's/=/=\"/' | sed 's/$/\"?/'"`; + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +# echo $SCRIPT_DIR +GUNNS_HOME=$( dirname -- ${SCRIPT_DIR}) +# echo "THIS IS MY ${GUNNS_HOME}" + +fullTcshEnv=`tcsh -f -c "source ${SCRIPT_DIR}/cshrc $@; env | sed 's/=/=\"/' | sed 's/$/\"?/'"`; # Collapse newlines to make all variables on one line -#echo "$fullTcshEnv" +# echo "$fullTcshEnv" allOneLine=`echo $fullTcshEnv` -#echo "alloneline $allOneLine" +# echo "alloneline $allOneLine" # Split environment by newline and match to VAR= form # Then store the variable as an export line to be executed later @@ -77,7 +83,7 @@ for line in "${ADDR[@]}"; do #echo "regex is $regex" if [[ $line =~ $regex ]]; then if [[ $included == 1 ]]; then - #echo "STORING line $line" + # echo "STORING line $line" bashExportLines[j]="export $line" j=`expr $j + 1` fi @@ -89,7 +95,7 @@ unset IFS # Now bashExportLines contains every 'export VAR="value"' line we need # just eval each line and walk away in slow motion like a boss for line in "${bashExportLines[@]}"; do - #echo "Evaling $line" + # echo "Evaling $line" eval $line done diff --git a/bin/cshrc b/bin/cshrc index 37172a44..51b32395 100644 --- a/bin/cshrc +++ b/bin/cshrc @@ -2,7 +2,12 @@ # Copyright 2019 United States Government as represented by the Administrator of the # National Aeronautics and Space Administration. All Rights Reserved. -setenv GUNNS_HOME `pwd` +if ( $?GUNNS_HOME ) then + # echo "GUNNS_HOME already defined as $GUNNS_HOME" +else + setenv GUNNS_HOME `pwd` +endif + # For maintainability, we prefer that this script not source any other scripts. # Simply determine what needs to be set, prepended, or appended, and place it here. diff --git a/bin/makefile.trickless_lib b/bin/makefile.trickless_lib index cc1e7a9e..9b692799 100644 --- a/bin/makefile.trickless_lib +++ b/bin/makefile.trickless_lib @@ -204,3 +204,4 @@ ./aspects/electrical/Diode/DiodeElect.o \ ./aspects/electrical/FetSwitch/FetSwitch.o \ ./aspects/electrical/FetSwitch/FetSwitchElect.o \ + ./aspects/electrical/LosslessSource/GunnsLosslessSource.o \ diff --git a/lib/sources.mk b/lib/sources.mk index 536712a0..6d48c679 100644 --- a/lib/sources.mk +++ b/lib/sources.mk @@ -17,6 +17,7 @@ SOURCES := \ $(wildcard $(GUNNS_HOME)/aspects/electrical/UserLoad/*.cpp) \ $(wildcard $(GUNNS_HOME)/aspects/electrical/resistive/*.cpp) \ $(wildcard $(GUNNS_HOME)/aspects/electrical/TripLogic/*.cpp) \ + $(wildcard $(GUNNS_HOME)/aspects/electrical/LosslessSource/*.cpp) \ $(wildcard $(GUNNS_HOME)/aspects/fluid/capacitor/*.cpp) \ $(wildcard $(GUNNS_HOME)/aspects/fluid/conductor/*.cpp) \ $(wildcard $(GUNNS_HOME)/aspects/fluid/fluid/*.cpp) \ diff --git a/test/clean_all_ut.sh b/test/clean_all_ut.sh index 0cbde77f..17c49ffd 100755 --- a/test/clean_all_ut.sh +++ b/test/clean_all_ut.sh @@ -69,6 +69,9 @@ cd $GUNNS_HOME/$FOLDER; make clean; set FOLDER = aspects/electrical/UserLoad/test cd $GUNNS_HOME/$FOLDER; make clean; +set FOLDER = aspects/electrical/LosslessSource/test +cd $GUNNS_HOME/$FOLDER; make clean; + set FOLDER = math/test cd $MS_UTILS_HOME/$FOLDER; make clean; diff --git a/test/make_all_ut.sh b/test/make_all_ut.sh index 11b51d23..907ab633 100755 --- a/test/make_all_ut.sh +++ b/test/make_all_ut.sh @@ -253,6 +253,15 @@ else echo $FOLDER\: NO TEST OUTPUT, possibly failed to build! >> $OUT endif +# +set FOLDER = aspects/electrical/LosslessSource/test + +cd $GUNNS_HOME/$FOLDER; make clean; make; +if ( -f $GUNNS_HOME/$FOLDER/output/unit-tests-valgrind.log ) then + echo $FOLDER\: `grep -E 'OK \(*|FAILURES\!|Failures \!' $GUNNS_HOME/$FOLDER/output/unit-tests-valgrind.log` `grep 'ERROR SUMMARY' $GUNNS_HOME/$FOLDER/output/unit-tests-valgrind.log | grep -v ' 0 errors'` >> $OUT +else + echo $FOLDER\: NO TEST OUTPUT, possibly failed to build! >> $OUT +endif ######################################################## # test relevent folders in ms-utils diff --git a/test/make_all_ut_asan.sh b/test/make_all_ut_asan.sh index 3fddc978..473d63fc 100755 --- a/test/make_all_ut_asan.sh +++ b/test/make_all_ut_asan.sh @@ -274,6 +274,17 @@ else endif echo `grep -s 'SUMMARY' $GUNNS_HOME/$FOLDER/output/asan.log*` >> $OUT +# +set FOLDER = aspects/electrical/LosslessSource/test + +cd $GUNNS_HOME/$FOLDER; make clean; $UT_RECIPE; +if ( -f $GUNNS_HOME/$FOLDER/output/unit-tests.log ) then + echo $FOLDER\: `grep -s -E 'OK \(*|FAILURES\!|Failures \!' $GUNNS_HOME/$FOLDER/output/unit-tests.log` >> $OUT +else + echo $FOLDER\: NO TEST OUTPUT, possibly failed to build! >> $OUT +endif +echo `grep -s 'SUMMARY' $GUNNS_HOME/$FOLDER/output/asan.log*` >> $OUT + ######################################################## # test relevent folders in ms-utils