From e730917516cd5e00d54a52f34b0f43f3a76f957b Mon Sep 17 00:00:00 2001 From: Geir Horn Date: Wed, 10 Jan 2024 16:26:09 +0100 Subject: [PATCH] First release of the solver component Change-Id: Ie016703ce389b97a849ba7c0a56abad89885b5b2 --- .gitignore | 2 ++ AMPLSolver.cpp | 5 ++++- Bin/.gitignore | 2 ++ MetricUpdater.cpp | 10 ++++++++-- MetricUpdater.hpp | 2 +- SolverComponent.cpp | 18 +++++++++--------- SolverManager.hpp | 44 ++++++++++++++++++++++++++++++++------------ makefile | 43 ++++++++++++++++++++++++++++++------------- 8 files changed, 88 insertions(+), 38 deletions(-) create mode 100644 Bin/.gitignore diff --git a/.gitignore b/.gitignore index e0e9b8b..ad9d7f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ __pycache__/ .nox/ +*.d +/SolverComponent diff --git a/AMPLSolver.cpp b/AMPLSolver.cpp index 198afe4..84aee4f 100644 --- a/AMPLSolver.cpp +++ b/AMPLSolver.cpp @@ -161,8 +161,11 @@ void AMPLSolver::SolveProblem( // objective functions as 'dropped'. Note that this is experimental code // as the multi-objective possibilities in AMPL are not well documented. + std::string + OptimisationGoal = TheContext.at( Solver::ObjectiveFunctionLabel ); + for( auto TheObjective : ProblemDefinition.getObjectives() ) - if( TheObjective.name() == TheContext.at( Solver::ObjectiveFunctionLabel ) ) + if( TheObjective.name() == OptimisationGoal ) TheObjective.restore(); else TheObjective.drop(); diff --git a/Bin/.gitignore b/Bin/.gitignore new file mode 100644 index 0000000..31dc307 --- /dev/null +++ b/Bin/.gitignore @@ -0,0 +1,2 @@ +*.d +*.o diff --git a/MetricUpdater.cpp b/MetricUpdater.cpp index 3af6e15..19f6623 100644 --- a/MetricUpdater.cpp +++ b/MetricUpdater.cpp @@ -87,6 +87,12 @@ void MetricUpdater::AddMetricSubscription( const MetricTopic & TheMetrics, // The sender address will contain the metric topic, but this will contain the // generic metric prediction root string, and this string must be removed // before the metric name can be updated. +// +// Note that the map's [] operator cannot be used to look up the topic in the +// current map because it assumes the implicit creation of non-existing keys, +// which means that an empty metric value record should be constructed first +// and then used. To modify the existing record, the 'at' function must be +// used. void MetricUpdater::UpdateMetricValue( const MetricValueUpdate & TheMetricValue, const Address TheMetricTopic) @@ -96,8 +102,8 @@ void MetricUpdater::UpdateMetricValue( if( MetricValues.contains( TheTopic ) ) { - MetricValues[ TheTopic ].Value = TheMetricValue[ NebulOuS::ValueLabel ]; - + MetricValues.at( TheTopic ).Value = TheMetricValue[ NebulOuS::ValueLabel ]; + ValidityTime = std::max( ValidityTime, TheMetricValue[ NebulOuS::TimePoint ].get< Solver::TimePointType >() ); } diff --git a/MetricUpdater.hpp b/MetricUpdater.hpp index 85d0215..5eada0e 100644 --- a/MetricUpdater.hpp +++ b/MetricUpdater.hpp @@ -164,7 +164,7 @@ private: const std::string OptimisationName; JSON Value; - MetricValueRecord( const std::string & TheName, JSON InitialValue ) + MetricValueRecord( const std::string & TheName, const JSON InitialValue ) : OptimisationName( TheName ), Value( InitialValue ) {} diff --git a/SolverComponent.cpp b/SolverComponent.cpp index e63e34f..8f85fe9 100644 --- a/SolverComponent.cpp +++ b/SolverComponent.cpp @@ -46,16 +46,10 @@ License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/) // Standard headers #include // For standard strings -// #include // For smart pointers #include // Making informative error messages #include // To format error messages #include // standard exceptions #include // Access to the file system -// #include // To unpack variable arguments -// #include // To constrain types -// #include // To store subscribed topics -// #include // To sleep while waiting for termination -// #include // To have a concept of fime // Theron++ headers @@ -89,10 +83,9 @@ License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/) /*============================================================================== - Main file + Main ==============================================================================*/ -// int main( int NumberOfCLIOptions, char ** CLIOptionStrings ) { @@ -201,12 +194,19 @@ int main( int NumberOfCLIOptions, char ** CLIOptionStrings ) // a parameter to the constructor of the Metric Updater so the latter actor // knows where to send application execution contexts whenever a new solution // is requested by the SLO Violation Detector through the Optimzer Controller. + // Then follows the number of solvers to use in the solver pool and the root + // name of the solvers. This root name string will be extended with _n where n + // where n is a sequence number from 1.As all solvers are of the same type + // given by the template parameter (here AMPLSolver), they are assumed to need + // the same set of constructor arguments and the constructor arguments follow + // the root solver name. NebulOuS::SolverManager< NebulOuS::AMPLSolver > WorkloadMabager( "WorkloadManager", std::string( NebulOuS::Solver::Solution::MessageIdentifier ), std::string( NebulOuS::Solver::ApplicationExecutionContext::MessageIdentifier ), - "AMPLSolver", ampl::Environment( TheAMPLDirectory.native() ), ModelDirectory ); + 1, "AMPLSolver", + ampl::Environment( TheAMPLDirectory.native() ), ModelDirectory ); NebulOuS::MetricUpdater ContextMabager( "MetricUpdater", WorkloadMabager.GetAddress() ); diff --git a/SolverManager.hpp b/SolverManager.hpp index 7f91358..6392f1c 100644 --- a/SolverManager.hpp +++ b/SolverManager.hpp @@ -49,11 +49,13 @@ License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/) #include // Pool of local solvers #include // Range based views #include // Standard algorithms +#include // For inserters #include // For nice error messages #include // Standard exceptions #include // Error location reporting #include // Execution stop management #include // Lock the condtion variable +#include // For constructing solvers // Other packages @@ -143,7 +145,7 @@ private: if( !PassiveSolvers.empty() && !ContextExecutionQueue.empty() ) { for( const auto & [ SolverAddress, ContextElement ] : - ranges::views::zip( PassiveSolvers, ContextExecutionQueue ) ) + std::ranges::views::zip( PassiveSolvers, ContextExecutionQueue ) ) Send( Contexts.at( ContextElement.second ), SolverAddress ); // The number of contexts dispatched must equal the minimum of the @@ -156,13 +158,15 @@ private: std::ranges::move( std::ranges::subrange( PassiveSolvers.begin(), - PassiveSolvers.begin() + DispatchedContexts ), - std::inserter( ActiveSolvers ) ); + std::ranges::next( PassiveSolvers.begin(), DispatchedContexts, + PassiveSolvers.end() ) ), + std::inserter( ActiveSolvers, ActiveSolvers.end() ) ); // Then the dispatched context identifiers are removed from queue ContextExecutionQueue.erase( ContextExecutionQueue.begin(), - ContextExecutionQueue.begin() + DispatchedContexts ); + std::ranges::next( ContextExecutionQueue.begin(), DispatchedContexts, + ContextExecutionQueue.end() ) ); } } @@ -216,7 +220,7 @@ private: // contexts, if any. void PublishSolution( const Solver::Solution & TheSolution, - const Addres TheSolver ) + const Address TheSolver ) { Send( TheSolution, SolutionReceiver ); PassiveSolvers.insert( ActiveSolvers.extract( TheSolver ) ); @@ -243,10 +247,13 @@ private: public: + template< typename ...SolverArgTypes > SolverManager( const std::string & TheActorName, - const Theron::AMQ::TopicName & SolutionTopic, - const Theron::AMQ::TopicName & ContextPublisherTopic, - const auto & ...SolverArguments ) + const Theron::AMQ::TopicName & SolutionTopic, + const Theron::AMQ::TopicName & ContextPublisherTopic, + const unsigned int NumberOfSolvers, + const std::string SolverRootName, + SolverArgTypes && ...SolverArguments ) : Actor( TheActorName ), StandardFallbackHandler( Actor::GetAddress().AsString() ), NetworkingActor( Actor::GetAddress().AsString() ), @@ -256,10 +263,22 @@ public: Contexts(), ContextExecutionQueue() { // The solvers are created by expanding the arguments for the solvers - // one by one creating new elements in the solver pool + // one by one creating new elements in the solver pool. The solvers + // will be named with a sequence number from 1 and up added to the + // root solver name, e.g., if the root name is "MySolver" the solvers + // will have names "MySolver_1", "MySolver_2",... and so forth. Since + // all solvers are of the same type they should take the same arguments + // and so the given arguments are just fowarded to each solver constructor. - ( SolverPool.emplace_back( std::forward( SolverArguments ) ), ... ); + for( unsigned int i = 1; i <= NumberOfSolvers; i++ ) + { + std::ostringstream TheSolverName; + TheSolverName << SolverRootName << "_" << i; + SolverPool.emplace_back( TheSolverName.str(), + std::forward< SolverArgTypes >(SolverArguments)... ); + } + // If the solvers were successfully created, their addresses are recorded as // passive servers, and a publisher is made for the solution channel, and // optionally, a subscritpion is made for the alternative context publisher @@ -268,8 +287,9 @@ public: if( !SolverPool.empty() ) { - std::ranges::transform( ServerPool, std::inserter( PassiveSolvers ), - [](const SolverType & TheSolver){ return TheSolver.GetAddress(); } ); + std::ranges::transform( SolverPool, + std::inserter( PassiveSolvers, PassiveSolvers.end() ), + [](const SolverType & TheSolver){ return TheSolver.GetAddress(); } ); Send( Theron::AMQ::NetworkLayer::TopicSubscription( Theron::AMQ::NetworkLayer::TopicSubscription::Action::Publisher, diff --git a/makefile b/makefile index 8e661de..db988a2 100644 --- a/makefile +++ b/makefile @@ -22,6 +22,17 @@ RM = rm -f THERON = /home/GHo/Documents/Code/Theron++ +# Location of the AMPL API directory + +AMPL_INCLUDE = /opt/AMPL/amplapi/include + +# The solver component uses the CxxOpts class for parsing the command line +# options since it is header only and lighter than the Options library of +# boost, which seems to have lost the most recent C++ features. The CxxOpts +# library can be cloned from https://github.com/jarro2783/cxxopts + +CxxOpts_DIR = /home/GHo/Documents/Code/CxxOpts/include + # Optimisation -O3 is the highest level of optimisation and should be used # with production code. -Og is the code optimising and offering debugging # transparency and should be use while the code is under development @@ -40,7 +51,8 @@ DEPENDENCY_FLAGS = -MMD -MP # Options GENERAL_OPTIONS = -Wall -std=c++23 -ggdb -D_DEBUG -INCLUDE_DIRECTORIES = -I. -I/usr/include -I$(THERON) +INCLUDE_DIRECTORIES = -I. -I/usr/include -I$(THERON) -I$(AMPL_INCLUDE) \ + -I$(CxxOpts_DIR) CXXFLAGS = $(GENERAL_OPTIONS) $(INCLUDE_DIRECTORIES) $(DEPENDENCY_FLAGS) \ $(OPTIMISATION_FLAG) @@ -55,7 +67,7 @@ CXXFLAGS = $(GENERAL_OPTIONS) $(INCLUDE_DIRECTORIES) $(DEPENDENCY_FLAGS) \ CFLAGS = $(DEPENDENCY_FLAGS) $(OPTIMISATION_FLAG) $(GENERAL_OPTIONS) LDFLAGS = -fuse-ld=gold -ggdb -D_DEBUG -pthread -l$(THERON)/Theron++.a \ - -lqpid-proton-cpp + -lqpid-proton-cpp -l/opt/AMPL/amplapi/lib/libampl.so #------------------------------------------------------------------------------ # Theron library @@ -82,29 +94,34 @@ OBJECTS_DIR = Bin # Listing the actors' source files and expected object files SOLVER_SOURCE = $(wildcard *.cpp) -SOLVER_OBJECTS = $(addprefix $(OBJECTS_DIR)/, $(SOLVER_SOURCE:.cpp=.o) +SOLVER_OBJECTS = $(addprefix $(OBJECTS_DIR)/, $(SOLVER_SOURCE:.cpp=.o) ) # Since all source files are in the same directory as the make file and the # component's objective file, they can be built by a general rule $(OBJECTS_DIR)/%.o : %.cpp - $(CXX) $(CXXFLAGS) $< -o $@ $(INCLUDE_DIRECTORIES) + $(CXX) $(CXXFLAGS) -c $< -o $@ $(INCLUDE_DIRECTORIES) #------------------------------------------------------------------------------ # Solver component #------------------------------------------------------------------------------ # -# The solver component uses the CxxOpts class for parsing the command line -# options since it is header only and lighter than the Options library of -# boost, which seems to have lost the most recent C++ features. The CxxOpts -# library can be cloned from https://github.com/jarro2783/cxxopts - -CxxOpts_DIR = /home/GHo/Documents/Code/CxxOpts/include # The only real target is to build the solver component whenever some of # the object files or the solver actors. -SolverComponent: SolverComponent.cpp $(SOLVER_OBJECTS) $(THERON)/Theron++.a - $(CXX) SolverComponent.cpp -o SolverComponent $(CXXFLAGS) \ - -I$(CxxOpts_DIR) $(LDFLAGS) +SolverComponent: $(SOLVER_OBJECTS) $(THERON)/Theron++.a + $(CXX) -o SolverComponent $(CXXFLAGS) $(SOLVER_OBJECTS) $(LDFLAGS) +# There is also a standard target to clean the automatically generated build +# files + +clean: + $(RM) $(OBJECTS_DIR)/*.o $(OBJECTS_DIR)/*.d + +#------------------------------------------------------------------------------ +# Dependencies +#------------------------------------------------------------------------------ +# + +-include $(SOLVER_OBJECTS:.o=.d) \ No newline at end of file