First release of the solver component

Change-Id: Ie016703ce389b97a849ba7c0a56abad89885b5b2
This commit is contained in:
Geir Horn 2024-01-10 16:26:09 +01:00
parent c83d0dedd5
commit e730917516
8 changed files with 88 additions and 38 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
__pycache__/
.nox/
*.d
/SolverComponent

View File

@ -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();

2
Bin/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.d
*.o

View File

@ -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 >() );
}

View File

@ -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 )
{}

View File

@ -46,16 +46,10 @@ License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/)
// Standard headers
#include <string> // For standard strings
// #include <memory> // For smart pointers
#include <source_location> // Making informative error messages
#include <sstream> // To format error messages
#include <stdexcept> // standard exceptions
#include <filesystem> // Access to the file system
// #include <initializer_list> // To unpack variable arguments
// #include <concepts> // To constrain types
// #include <vector> // To store subscribed topics
// #include <thread> // To sleep while waiting for termination
// #include <chrono> // 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() );

View File

@ -49,11 +49,13 @@ License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/)
#include <list> // Pool of local solvers
#include <ranges> // Range based views
#include <algorithm> // Standard algorithms
#include <iterator> // For inserters
#include <sstream> // For nice error messages
#include <stdexcept> // Standard exceptions
#include <source_location> // Error location reporting
#include <condition_variable> // Execution stop management
#include <mutex> // Lock the condtion variable
#include <tuple> // 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,

View File

@ -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)