Implemented AMPL Solver class and interaction with the other Actors

Change-Id: I37bb164b60bf1888b0ce99665486f13552681737
This commit is contained in:
Geir Horn 2024-01-02 18:41:49 +01:00
parent 285b64a8fe
commit 5db3a5e865
8 changed files with 630 additions and 89 deletions

29
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,29 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${default}",
"/home/GHo/Documents/Code/CxxOpts/include",
"/usr/include",
"/home/GHo/Documents/Code/Theron++",
"/home/GHo/Documents/Code/Theron++/Utility",
"/home/GHo/Documents/Code/Theron++/Communication",
"/home/GHo/Documents/Code/Theron++/Communication/AMQ",
"/opt/AMPL/amplapi/include",
"${workspaceFolder}/**"
],
"defines": [],
"compilerArgs": [
"--std=c++23",
"-I/opt/AMPL/amplapi/include",
"-I/usr/include"
],
"cStandard": "c23",
"cppStandard": "c++23",
"intelliSenseMode": "linux-gcc-x64",
"compilerPath": "/usr/bin/g++"
}
],
"version": 4
}

243
AMPLSolver.cpp Normal file
View File

@ -0,0 +1,243 @@
/*==============================================================================
AMPL Solver
This file provides the implementation of the methods of the AMLP Solver actor
that is instantiated by the Solution Manager and used to obtain solutions for
optimisation problems in the queue managed by the Solution Manager.
Author and Copyright: Geir Horn, University of Oslo
Contact: Geir.Horn@mn.uio.no
License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/)
==============================================================================*/
#include <fstream> // For file I/O
#include <sstream> // For formatted errors
#include <stdexcept> // Standard exceptions
#include <system_error> // Error codes
#include "AMPLSolver.hpp"
namespace NebulOuS
{
// -----------------------------------------------------------------------------
// Utility function
// -----------------------------------------------------------------------------
//
std::string AMPLSolver::SaveFile( const JSON & TheMessage,
const std::source_location & Location )
{
if( TheMessage.is_object() )
{
// Writing the problem file based on the message content that should be
// only a single key-value pair. If the file could not be opened, a run
// time exception is thrown.
std::string TheFileName
= ProblemFileDirectory / TheMessage.begin().key();
std::fstream ProblemFile( TheFileName, std::ios::out );
if( ProblemFile.is_open() )
{
ProblemFile << TheMessage.begin().value();
ProblemFile.close();
return TheFileName;
}
else
{
std::ostringstream ErrorMessage;
ErrorMessage << "[" << Location.file_name() << " at line "
<< Location.line()
<< "in function " << Location.function_name() <<"] "
<< "The AMPL file at "
<< TheFileName
<< " could not be opened for output!";
throw std::system_error( static_cast< int >( std::errc::io_error ),
std::system_category(), ErrorMessage.str() );
}
}
else
{
std::ostringstream ErrorMessage;
ErrorMessage << "[" << Location.file_name() << " at line "
<< Location.line()
<< "in function " << Location.function_name() <<"] "
<< "The JSON message is not an object. The received "
<< "message is " << std::endl
<< TheMessage.dump(2)
<< std::endl;
throw std::system_error( static_cast< int >( std::errc::io_error ),
std::system_category(), ErrorMessage.str() );
}
}
// -----------------------------------------------------------------------------
// Optimisation
// -----------------------------------------------------------------------------
//
// The first step in solving an optimisation problem is to define the problme
// involving the decision variables, the parameters, and the constraints over
// these entities. The problem is received as an AMQ JSON message where where
// the only key is the file name and the value is the AMPL model file. This file
// is first saved, and if there is no exception thrown form the save file
// function, the filename will be returned and read back into the problem
// definition.
void AMPLSolver::DefineProblem(const Solver::OptimisationProblem & TheProblem,
const Address TheOracle)
{
ProblemDefinition.read( SaveFile( TheProblem ) );
}
// The data file(s) corresponding to the current optimisation problem will be
// sent in the same way and separately file by file. The logic is the same as
// the Define Problem message handler: The save file is used to store the
// received file, which is then loaded as the data problem.
void AMPLSolver::DataFileUpdate( const DataFileMessage & TheDataFile,
const Address TheOracle )
{
ProblemDefinition.readData( SaveFile( TheDataFile ) );
}
// The solver function is more involved as must set the metric values received
// in the application execution context message as parameter values for the
// optimisation problem, then solve for the optimal objective value, and finally
// report the solution back to the entity requesting the solution, typically an
// instance of the Solution Manager actor.
void AMPLSolver::SolveProblem(
const ApplicationExecutionContext & TheContext, const Address TheRequester )
{
// Setting the metric values one by one. In the setting of NebulOuS a metric
// is either a numerical value or a string. Vectors are currently not
// supported as values.
for( const auto & [ TheName, MetricValue ] :
Solver::MetricValueType( TheContext.at( Solver::ExecutionContext ) ) )
{
ampl::Parameter TheParameter = ProblemDefinition.getParameter( TheName );
switch ( MetricValue.type() )
{
case JSON::value_t::number_integer :
case JSON::value_t::number_unsigned :
case JSON::value_t::boolean :
TheParameter.set( MetricValue.get< long >() );
break;
case JSON::value_t::number_float :
TheParameter.set( MetricValue.get< double >() );
break;
case JSON::value_t::string :
TheParameter.set( MetricValue.get< std::string >() );
break;
default:
{
std::source_location Location = std::source_location::current();
std::ostringstream ErrorMessage;
ErrorMessage << "[" << Location.file_name() << " at line "
<< Location.line()
<< "in function " << Location.function_name() <<"] "
<< "The JSON value " << MetricValue
<< " has JSON type "
<< static_cast< int >( MetricValue.type() )
<< " which is not supported"
<< std::endl;
throw std::invalid_argument( ErrorMessage.str() );
}
break;
}
}
// Setting the given objective as the active objective and all other
// objective functions as 'dropped'. Note that this is experimental code
// as the multi-objective possibilities in AMPL are not well documented.
for( auto TheObjective : ProblemDefinition.getObjectives() )
if( TheObjective.name() == TheContext.at( Solver::ObjectiveFunctionLabel ) )
TheObjective.restore();
else
TheObjective.drop();
// The problem can then be solved.
Optimize();
// Once the problem has been optimised, the objective values can be
// be obtained from the objectives
Solver::Solution::ObjectiveValuesType ObjectiveValues;
for( auto TheObjective : ProblemDefinition.getObjectives() )
ObjectiveValues.emplace( TheObjective.name(), TheObjective.value() );
// The variable values are obtained in the same way
Solver::Solution::VariableValuesType VariableValues;
for( auto Variable : ProblemDefinition.getVariables() )
VariableValues.emplace( Variable.name(), Variable.value() );
// The found solution can then be returned to the requesting actor or topic
Send( Solver::Solution(
TheContext.at( Solver::ContextIdentifier ),
TheContext.at( Solver::TimeStamp ).get< Solver::TimePointType >(),
TheContext.at( Solver::ObjectiveFunctionLabel ),
ObjectiveValues, VariableValues
), TheRequester );
}
// -----------------------------------------------------------------------------
// Constructor and destructor
// -----------------------------------------------------------------------------
//
// The constructor initialises the base classes and sets the AMPL installation
// directory and the path for the problem related files. The message handlers
// for the data file updates must be registered since the inherited handlers
// for the application execution context and the problem definition were already
// defined by the generic solver. Note that no publisher is defined for the
// solution since the solution message is just returned to the requester actor,
// which is assumed to be a Solution Manager on the local endpoint because
// multiple solvers may run in parallel. The external publication of solutions
// will be made by the Solution Manager for all solvers on this endpoint.
AMPLSolver::AMPLSolver( const std::string & TheActorName,
const ampl::Environment & InstallationDirectory,
const std::filesystem::path & ProblemPath )
: Actor( TheActorName ),
StandardFallbackHandler( Actor::GetAddress().AsString() ),
NetworkingActor( Actor::GetAddress().AsString() ),
Solver( Actor::GetAddress().AsString() ),
ProblemFileDirectory( ProblemPath ),
ProblemDefinition( InstallationDirectory )
{
RegisterHandler( this, &AMPLSolver::DataFileUpdate );
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::Subscription,
Theron::AMQ::TopicName( DataFileTopic )
), GetSessionLayerAddress() );
}
// In case the network is still running when the actor is closing, the data file
// subscription should be closed.
AMPLSolver::~AMPLSolver()
{
if( HasNetwork() )
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::CloseSubscription,
Theron::AMQ::TopicName( DataFileTopic )
), GetSessionLayerAddress() );
}
} // namespace NebulOuS

View File

@ -26,8 +26,232 @@ License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/)
#ifndef NEBULOUS_AMPL_SOLVER #ifndef NEBULOUS_AMPL_SOLVER
#define NEBULOUS_AMPL_SOLVER #define NEBULOUS_AMPL_SOLVER
// Standard headers
#include <string_view> // Constant strings
#include <string> // Standard strings
#include <list> // To store names
#include <filesystem> // For problem files
#include <source_location> // For better errors
// Other packages
#include <nlohmann/json.hpp> // JSON object definition
using JSON = nlohmann::json; // Short form name space
// Theron++ files
#include "Actor.hpp" // Actor base class
#include "Utility/StandardFallbackHandler.hpp" // Exception unhanded messages
#include "Communication/NetworkingActor.hpp" // Actor to receive messages
#include "Communication/PolymorphicMessage.hpp" // The network message type
// AMQ communication files
#include "Communication/AMQ/AMQjson.hpp" // For JSON metric messages
#include "Communication/AMQ/AMQEndpoint.hpp" // AMQ endpoint
#include "Communication/AMQ/AMQSessionLayer.hpp" // For topic subscriptions
// NebulOuS files
#include "Solver.hpp" // The generic solver base
// AMPL Application Programmer Interface (API)
#include "ampl/ampl.h"
namespace NebulOuS namespace NebulOuS
{ {
/*==============================================================================
AMPL Solver actor
==============================================================================*/
//
// The AMPL solver is an Actor and a Solver. It provides handlers for messages
// defining the problem file and data file(s), and responds to an application
// execution context message by optimising the saved problem for the given
// context parameters.
class AMPLSolver
: virtual public Theron::Actor,
virtual public Theron::StandardFallbackHandler,
virtual public Theron::NetworkingActor<
typename Theron::AMQ::Message::PayloadType >,
virtual public Solver
{
// --------------------------------------------------------------------------
// Utility methods
// --------------------------------------------------------------------------
//
// Since both the optimisation problem file and the data file(s) will be sent
// as JSON messages with a single key-value pair where the key is the filename
// and the value is the file content, there is a common dfinition of the
// problem file directory and a function to read the file. The function will
// throw errors if the JSON message given is not an object, or of there are
// issues opening the file name given. If the file could be successfully
// saved, the functino will close the file and return the file name for
// further processing.
private:
const std::filesystem::path ProblemFileDirectory;
std::string SaveFile( const JSON & TheMessage,
const std::source_location & Location
= std::source_location::current() );
// --------------------------------------------------------------------------
// The optimisation problem
// --------------------------------------------------------------------------
//
// The problem is received as an AMPL file in a message. However, the AMPL
// interface allows the loading of problem and data files on an existing
// AMPL object, and the AMPL API object is therefore reused when a new
// problem file is received.
ampl::AMPL ProblemDefinition;
// The problem is loaded by the handler defining the problem. This receives
// the standard optimisation problem definition. Essentially, this message
// contains one tag, the name of the AMPL file and the body is a big string
// containing the file content.
virtual void DefineProblem( const Solver::OptimisationProblem & TheProblem,
const Address TheOracle ) override;
// The topic on which the problem file is posted is currently defined as a
// constant string
static constexpr std::string_view AMPLProblemTopic
= "AMPL::OptimisationProblem";
// --------------------------------------------------------------------------
// Data file updates
// --------------------------------------------------------------------------
//
// The data files are assumed to be published on a dedicated topic for the
// optimiser
public:
static constexpr std::string_view DataFileTopic = "AMPL::DataFileUpdates";
// The message defining the data file is a JSON topic message with the same
// structure as the optimisation problem message: It contains only one
// attribute, which is the name of the data file, and the data file
// content as the value. This content is just saved to the problem file
// directory before it is read back to the AMPL problem definition.
class DataFileMessage
: public Theron::AMQ::JSONTopicMessage
{
public:
DataFileMessage( const std::string & TheDataFileName,
const JSON & DataFileContent )
: JSONTopicMessage( std::string( DataFileTopic ),
{ TheDataFileName, DataFileContent } )
{}
DataFileMessage( const DataFileMessage & Other )
: JSONTopicMessage( Other )
{}
DataFileMessage()
: JSONTopicMessage( std::string( DataFileTopic ) )
{}
virtual ~DataFileMessage() = default;
};
// The handler for this message saves the received file and uploads the file
// to the AMPL problem definition.
private:
void DataFileUpdate( const DataFileMessage & TheDataFile,
const Address TheOracle );
// --------------------------------------------------------------------------
// Solving the problem
// --------------------------------------------------------------------------
//
// The real action happens when an Application Execution Context message is
// received. This defines the values of the independent metrics used in the
// objective functions and in the problem constraints, and one objective
// function name indicating which objective to optimise. The actual solution
// is provided by a small helper function. The reason is that this may
// use the AMPL problem but not the solver, and as such other solvers can
// be build on this class. The standard definition just asks AMPL to call
// the solver.
protected:
virtual void Optimize( void )
{ ProblemDefinition.solve(); }
// The handler for the application execution context will first set all the
// parameter values for the contex metrics to the received values, and then
// optimise the problem. When a solution is found it will be sent back to
// the Agent providing the application execution context as a solution value
// message. The message format is defined in the Solver base class.
virtual void SolveProblem( const ApplicationExecutionContext & TheContext,
const Address TheRequester ) override;
// --------------------------------------------------------------------------
// Constructor and destructor
// --------------------------------------------------------------------------
//
// The AMPL solver requires the name of the actor, an AMPL environment class
// pointing to the AMPL installation directory. If this is given as empty,
// then the path is taken from the corresponding environment variables. There
// is also a path to the directory where the optimisation problem file will
// be stored together with any required data files.
//
// Note that the constructors are declared as explicit because in theory
// a string could be converted to an Environment class or a Path and so to
// be able to distinquish what a string means, the actual classes must be
// given constructed on the content string.
public:
explicit AMPLSolver( const std::string & TheActorName,
const ampl::Environment & InstallationDirectory,
const std::filesystem::path & ProblemPath );
// If the path to the problem directory is omitted, it will be initialised to
// a temporary directory.
explicit AMPLSolver( const std::string & TheActorName,
const ampl::Environment & InstallationDirectory )
: AMPLSolver( TheActorName, InstallationDirectory,
std::filesystem::temp_directory_path() )
{}
// If the AMPL installation environment is omitted, the installation directory
// will be taken form the environment variables.
explicit AMPLSolver( const std::string & TheActorName,
const std::filesystem::path & ProblemPath )
: AMPLSolver( TheActorName, ampl::Environment(), ProblemPath )
{}
// Finally, it is just the standard constructor taking only the name of the
// actor
AMPLSolver( const std::string & TheActorName )
: AMPLSolver( TheActorName, ampl::Environment(),
std::filesystem::temp_directory_path() )
{}
// The solver will just close the open connections for listening to data file
// updates since the subscriptions for the problem definition will be closed
// by the generic solver
virtual ~AMPLSolver();
};
} // namespace NebulOuS } // namespace NebulOuS
#endif // NEBULOUS_AMPL_SOLVER #endif // NEBULOUS_AMPL_SOLVER

View File

@ -101,6 +101,10 @@ void MetricUpdater::UpdateMetricValue(
} }
} }
// --------------------------------------------------------------------------
// SLO Violation Events
// --------------------------------------------------------------------------
//
// When an SLO Violation is predicted a message is received from the SLO // When an SLO Violation is predicted a message is received from the SLO
// violation detector and this will trigger the definition of a new // violation detector and this will trigger the definition of a new
// application execution context and a request to the Solution Manager to // application execution context and a request to the Solution Manager to
@ -150,6 +154,10 @@ void MetricUpdater::SLOViolationHandler(
), TheSolutionManger ); ), TheSolutionManger );
} }
// --------------------------------------------------------------------------
// Constructor and destructor
// --------------------------------------------------------------------------
//
// The constructor initialises the base classes and sets the validity time // The constructor initialises the base classes and sets the validity time
// to zero so that it will be initialised by the first metric values received. // to zero so that it will be initialised by the first metric values received.
// The message handlers are registered, and the the updater will then subscribe // The message handlers are registered, and the the updater will then subscribe
@ -170,13 +178,42 @@ MetricUpdater::MetricUpdater( const std::string UpdaterName,
Send( Theron::AMQ::NetworkLayer::TopicSubscription( Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::Subscription, Theron::AMQ::NetworkLayer::TopicSubscription::Action::Subscription,
std::string( MetricSubscriptions ) ), std::string( NebulOuS::MetricSubscriptions ) ),
Theron::Network::GetAddress( Theron::Network::Layer::Session ) ); GetSessionLayerAddress() );
Send( Theron::AMQ::NetworkLayer::TopicSubscription( Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::Subscription, Theron::AMQ::NetworkLayer::TopicSubscription::Action::Subscription,
std::string( SLOViolationTopic ) ), std::string( NebulOuS::SLOViolationTopic ) ),
Theron::Network::GetAddress( Theron::Network::Layer::Session ) ); GetSessionLayerAddress() );
}
// The destructor is closing the established subscription if the network is
// still running. If this is called when the application is closing the network
// connection should be stopped, and in that case all subscriptions will be
// automatically cancelled.
MetricUpdater::~MetricUpdater()
{
if( HasNetwork() )
{
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::CloseSubscription,
std::string( NebulOuS::MetricSubscriptions ) ),
GetSessionLayerAddress() );
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::CloseSubscription,
std::string( NebulOuS::SLOViolationTopic ) ),
GetSessionLayerAddress() );
std::ranges::for_each( std::views::keys( MetricValues ),
[this]( const Theron::AMQ::TopicName & TheMetricTopic ){
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::CloseSubscription,
std::string( MetricValueRootString ) + TheMetricTopic ),
GetSessionLayerAddress() );
});
}
} }
} // End name space NebulOuS } // End name space NebulOuS

View File

@ -95,7 +95,7 @@ constexpr std::string_view MetricSubscriptions = "ApplicationContext";
// the Wiki-page [1] // the Wiki-page [1]
constexpr std::string_view MetricValueRootString constexpr std::string_view MetricValueRootString
= "eu.nebulouscloud.monitoring.predicted"; = "eu.nebulouscloud.monitoring.predicted.";
// The SLO violation detector will publish a message when a reconfiguration is // The SLO violation detector will publish a message when a reconfiguration is
// deamed necessary for a future time point called "Event type V" on the wiki // deamed necessary for a future time point called "Event type V" on the wiki
@ -201,57 +201,6 @@ private:
const Address TheSolutionManger; const Address TheSolutionManger;
// --------------------------------------------------------------------------
// JSON messages: Type by topic
// --------------------------------------------------------------------------
//
// The JSON message initialiser assumes that the content_type field of
// the message contains an unique label for the JSON message type to
// cover the situation where an actor may subscribe to multiple different
// messages all encoded as JSON messages. However, for this actor the type
// of the message will be decided by the topic on which the message is
// received. It is therefore necessary to set the message content type equal
// to the AMQ sender prior to decoding the AMQ message to the correct JSON
// object.
//
// The issue with the metric subscriptions is that the same type of message
// can come from any of the topics publishing metric values, and as such any
// topic name not being from the metric subscription command topic or the
// SLO Violation Event topic will be understood as a metric value update
// event The initialiser will check if the sender (topic) starts with the
// message identifier. This will allow the wildcard matching for metric
// values as well as an exact match for topic whose reply to address
// equals the message identifer.
class TypeByTopic
: public Theron::AMQ::JSONMessage
{
protected:
virtual bool
Initialize( const ProtocolPayload & ThePayload ) noexcept override
{
if( ThePayload->reply_to().starts_with( GetMessageIdentifier() ) )
{
ThePayload->content_type( GetMessageIdentifier() );
return JSONMessage::Initialize( ThePayload );
}
else return false;
}
public:
TypeByTopic( const std::string & TopicIdentifier )
: JSONMessage( TopicIdentifier )
{}
TypeByTopic( const TypeByTopic & Other )
: JSONMessage( Other.GetMessageIdentifier(), Other )
{}
virtual ~TypeByTopic() = default;
};
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// Subscribing to metric prediction values // Subscribing to metric prediction values
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
@ -263,16 +212,16 @@ private:
// the value publisher. // the value publisher.
class MetricTopic class MetricTopic
: public TypeByTopic : public Theron::AMQ::JSONTopicMessage
{ {
public: public:
MetricTopic( void ) MetricTopic( void )
: TypeByTopic( std::string( MetricSubscriptions ) ) : JSONTopicMessage( std::string( MetricSubscriptions ) )
{} {}
MetricTopic( const MetricTopic & Other ) MetricTopic( const MetricTopic & Other )
: TypeByTopic( Other ) : JSONTopicMessage( Other )
{} {}
virtual ~MetricTopic() = default; virtual ~MetricTopic() = default;
@ -296,16 +245,16 @@ private:
// with this string. // with this string.
class MetricValueUpdate class MetricValueUpdate
: public TypeByTopic : public Theron::AMQ::JSONWildcardMessage
{ {
public: public:
MetricValueUpdate( void ) MetricValueUpdate( void )
: TypeByTopic( std::string( MetricValueRootString ) ) : JSONWildcardMessage( std::string( MetricValueRootString ) )
{} {}
MetricValueUpdate( const MetricValueUpdate & Other ) MetricValueUpdate( const MetricValueUpdate & Other )
: TypeByTopic( Other ) : JSONWildcardMessage( Other )
{} {}
virtual ~MetricValueUpdate() = default; virtual ~MetricValueUpdate() = default;
@ -325,18 +274,24 @@ private:
// The SLO Violation detector publishes an event to indicate that at least // The SLO Violation detector publishes an event to indicate that at least
// one of the constraints for the application deployment will be violated in // one of the constraints for the application deployment will be violated in
// the predicted future, and that the search for a new solution should start. // the predicted future, and that the search for a new solution should start.
// This message is caught by the Optimisation Controller and republished
// adding a unique event identifier enabling the Optimisation Controller to
// match the produced solution with the event and deploy the right
// configuration.The message must also contain the name of the objective
// function to maximise. This name must match the name in the optimisation
// model sent to the solver.
class SLOViolation class SLOViolation
: public TypeByTopic : public Theron::AMQ::JSONTopicMessage
{ {
public: public:
SLOViolation( void ) SLOViolation( void )
: TypeByTopic( std::string( SLOViolationTopic ) ) : JSONTopicMessage( std::string( SLOViolationTopic ) )
{} {}
SLOViolation( const SLOViolation & Other ) SLOViolation( const SLOViolation & Other )
: TypeByTopic( Other ) : JSONTopicMessage( Other )
{} {}
virtual ~SLOViolation() = default; virtual ~SLOViolation() = default;
@ -362,9 +317,11 @@ public:
MetricUpdater( const std::string UpdaterName, MetricUpdater( const std::string UpdaterName,
const Address ManagerForSolutions ); const Address ManagerForSolutions );
// The destructor is just the default destructor // The destructor will unsubscribe from the control channels for the
// message defining metrics, and the channel for receiving SLO violation
// events.
virtual ~MetricUpdater() = default; virtual ~MetricUpdater();
}; // Class Metric Updater }; // Class Metric Updater
} // Name space NebulOuS } // Name space NebulOuS

View File

@ -95,7 +95,7 @@ class SolverManager
private: private:
const Address SolutionReceiver; const Theron::AMQ::TopicName SolutionReceiver;
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// Solver management // Solver management

View File

@ -49,10 +49,14 @@ using JSON = nlohmann::json; // Short form name space
#include "Actor.hpp" // Actor base class #include "Actor.hpp" // Actor base class
#include "Utility/StandardFallbackHandler.hpp" // Exception unhanded messages #include "Utility/StandardFallbackHandler.hpp" // Exception unhanded messages
#include "Communication/PolymorphicMessage.hpp" // The network message type
#include "Communication/NetworkingActor.hpp" // External communications
// AMQ communication headers // AMQ communication headers
#include "Communication/AMQ/AMQjson.hpp" // For JSON metric messages #include "Communication/AMQ/AMQjson.hpp" // For JSON metric messages
#include "Communication/AMQ/AMQEndpoint.hpp" // Enabling AMQ communication
#include "Communication/AMQ/AMQSessionLayer.hpp" // For topic subscriptions
namespace NebulOuS namespace NebulOuS
{ {
@ -64,7 +68,9 @@ namespace NebulOuS
class Solver class Solver
: virtual public Theron::Actor, : virtual public Theron::Actor,
virtual public Theron::StandardFallbackHandler virtual public Theron::StandardFallbackHandler,
virtual public Theron::NetworkingActor<
typename Theron::AMQ::Message::PayloadType >
{ {
public: public:
@ -127,10 +133,11 @@ public:
// The message is a simple JSON object where the various fields of the // The message is a simple JSON object where the various fields of the
// message struct are set by the constructor to ensure that all fields are // message struct are set by the constructor to ensure that all fields are
// given when the message is constructed. // given when the message is constructed. The message is a JSON Topic Message
// received on the topic with the same name as the message identifier.
class ApplicationExecutionContext class ApplicationExecutionContext
: public Theron::AMQ::JSONMessage : public Theron::AMQ::JSONTopicMessage
{ {
public: public:
@ -141,7 +148,7 @@ public:
const TimePointType MicroSecondTimePoint, const TimePointType MicroSecondTimePoint,
const std::string ObjectiveFunctionID, const std::string ObjectiveFunctionID,
const MetricValueType & TheContext ) const MetricValueType & TheContext )
: JSONMessage( std::string( MessageIdentifier ), : JSONTopicMessage( std::string( MessageIdentifier ),
{ { std::string( ContextIdentifier ), TheIdentifier }, { { std::string( ContextIdentifier ), TheIdentifier },
{ std::string( TimeStamp ), MicroSecondTimePoint }, { std::string( TimeStamp ), MicroSecondTimePoint },
{ std::string( ObjectiveFunctionLabel ), ObjectiveFunctionID }, { std::string( ObjectiveFunctionLabel ), ObjectiveFunctionID },
@ -149,10 +156,13 @@ public:
) {} ) {}
ApplicationExecutionContext( const ApplicationExecutionContext & Other ) ApplicationExecutionContext( const ApplicationExecutionContext & Other )
: JSONMessage( Other ) : JSONTopicMessage( Other )
{}
ApplicationExecutionContext()
: JSONTopicMessage( std::string( MessageIdentifier ) )
{} {}
ApplicationExecutionContext() = delete;
virtual ~ApplicationExecutionContext() = default; virtual ~ApplicationExecutionContext() = default;
}; };
@ -186,30 +196,36 @@ protected:
public: public:
using ObjectiveValuesType = MetricValueType;
static constexpr std::string_view ObjectiveValues = "ObjectiveValues";
class Solution class Solution
: public Theron::AMQ::JSONMessage : public Theron::AMQ::JSONTopicMessage
{ {
public: public:
using ObjectiveValuesType = MetricValueType;
using VariableValuesType = MetricValueType;
static constexpr std::string_view ObjectiveValues = "ObjectiveValues";
static constexpr std::string_view VariableValues = "VariableValues";
static constexpr std::string_view MessageIdentifier = "Solver::Solution"; static constexpr std::string_view MessageIdentifier = "Solver::Solution";
Solution( const ContextIdentifierType & TheIdentifier, Solution( const ContextIdentifierType & TheIdentifier,
const TimePointType MicroSecondTimePoint, const TimePointType MicroSecondTimePoint,
const std::string ObjectiveFunctionID, const std::string ObjectiveFunctionID,
const ObjectiveValuesType & TheObjectiveValues, const ObjectiveValuesType & TheObjectiveValues,
const MetricValueType & TheContext ) const VariableValuesType & TheVariables )
: JSONMessage( std::string( MessageIdentifier ) , : JSONTopicMessage( std::string( MessageIdentifier ) ,
{ { std::string( ContextIdentifier ), TheIdentifier }, { { std::string( ContextIdentifier ), TheIdentifier },
{ std::string( TimeStamp ), MicroSecondTimePoint }, { std::string( TimeStamp ), MicroSecondTimePoint },
{ std::string( ObjectiveFunctionLabel ), ObjectiveFunctionID }, { std::string( ObjectiveFunctionLabel ), ObjectiveFunctionID },
{ std::string( ObjectiveValues ) , TheObjectiveValues }, { std::string( ObjectiveValues ) , TheObjectiveValues },
{ std::string( ExecutionContext ), TheContext } } ) { std::string( VariableValues ), TheVariables } } )
{}
Solution()
: JSONTopicMessage( std::string( MessageIdentifier ) )
{} {}
Solution() = delete;
virtual ~Solution() = default; virtual ~Solution() = default;
}; };
@ -224,7 +240,7 @@ public:
// to implement this in a way appropriate for the algorithm. // to implement this in a way appropriate for the algorithm.
class OptimisationProblem class OptimisationProblem
: public Theron::AMQ::JSONMessage : public Theron::AMQ::JSONTopicMessage
{ {
public: public:
@ -232,10 +248,13 @@ public:
std::string_view MessageIdentifier = "Solver::OptimisationProblem"; std::string_view MessageIdentifier = "Solver::OptimisationProblem";
OptimisationProblem( const JSON & TheProblem ) OptimisationProblem( const JSON & TheProblem )
: JSONMessage( std::string( MessageIdentifier ), TheProblem ) : JSONTopicMessage( std::string( MessageIdentifier ), TheProblem )
{}
OptimisationProblem()
: JSONTopicMessage( std::string( MessageIdentifier ) )
{} {}
OptimisationProblem() = delete;
virtual ~OptimisationProblem() = default; virtual ~OptimisationProblem() = default;
}; };
@ -249,21 +268,43 @@ public:
// Constructor and destructor // Constructor and destructor
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// //
// The constructor defines the message handlers so that the derived soler // The constructor defines the message handlers so that the derived solver
// classes will not need to deal with the Actor specific details, and to // classes will not need to deal with the Actor specific details, and to
// ensure that the handlers are called when the Actor receives the various // ensure that the handlers are called when the Actor receives the various
// messages. The constructor requires an actor name as the only parameter. // messages. It should be noted that the problem definition can arrive from
// a remote actor on a topic corresponding to the message indentifier name.
// However, no subscription will be made for application execution contexts
// since these should be sorted and sent in order by the Solution Manager
// actor, and external communication should go throug the Solution Manager.
//
// The constructor requires an actor name as the only parameter, and the
// destructor unsubscribes from the topics previously subscribed to by
// the constuctor.
Solver( const std::string & TheSolverName ) Solver( const std::string & TheSolverName )
: Actor( TheSolverName ), : Actor( TheSolverName ),
StandardFallbackHandler( Actor::GetAddress().AsString() ) StandardFallbackHandler( Actor::GetAddress().AsString() ),
NetworkingActor( Actor::GetAddress().AsString() )
{ {
RegisterHandler( this, &Solver::SolveProblem ); RegisterHandler( this, &Solver::SolveProblem );
RegisterHandler( this, &Solver::DefineProblem ); RegisterHandler( this, &Solver::DefineProblem );
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::Subscription,
Theron::AMQ::TopicName( OptimisationProblem::MessageIdentifier )
), GetSessionLayerAddress() );
} }
Solver() = delete; Solver() = delete;
virtual ~Solver() = default;
virtual ~Solver()
{
if( HasNetwork() )
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::CloseSubscription,
Theron::AMQ::TopicName( OptimisationProblem::MessageIdentifier )
), GetSessionLayerAddress() );
}
}; };
/*============================================================================== /*==============================================================================

10
SolverComponent.cpp Normal file
View File

@ -0,0 +1,10 @@
/*==============================================================================
Solver Component
This is the main file for the Solver Component executable including the parsing
of command line arguments and the AMQ network interface.
Author and Copyright: Geir Horn, University of Oslo
Contact: Geir.Horn@mn.uio.no
License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/)
==============================================================================*/