diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index d70a9a5..acf1e67 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -10,7 +10,8 @@ "/home/GHo/Documents/Code/Theron++/Communication", "/home/GHo/Documents/Code/Theron++/Communication/AMQ", "/opt/AMPL/amplapi/include/ampl", - "${workspaceFolder}/**" + "${workspaceFolder}/**", + "/home/GHo/Documents/Code/Theron++" ], "defines": [], "cStandard": "c23", @@ -23,7 +24,6 @@ "-I/opt/AMPL/amplapi/include/ampl" ], "cppStandard": "c++23", - "compilerPathInCppPropertiesJson": "/usr/bin/g++", "mergeConfigurations": false, "configurationProvider": "ms-vscode.makefile-tools", "browse": { diff --git a/AMPLSolver.cpp b/AMPLSolver.cpp index 84aee4f..f80a455 100644 --- a/AMPLSolver.cpp +++ b/AMPLSolver.cpp @@ -15,6 +15,8 @@ License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/) #include <stdexcept> // Standard exceptions #include <system_error> // Error codes +#include "Utility/ConsolePrint.hpp" + #include "AMPLSolver.hpp" namespace NebulOuS @@ -92,7 +94,12 @@ std::string AMPLSolver::SaveFile( const JSON & TheMessage, void AMPLSolver::DefineProblem(const Solver::OptimisationProblem & TheProblem, const Address TheOracle) { - ProblemDefinition.read( SaveFile( TheProblem ) ); + Theron::ConsoleOutput Output; + Output << "AMPL Solver received the AMPL problem: " << TheProblem.dump(2) + << std::endl; + + ProblemDefinition.read( SaveFile( TheProblem ) ); + Output << "Problem loaded!" << std::endl; } // The data file(s) corresponding to the current optimisation problem will be @@ -215,7 +222,8 @@ void AMPLSolver::SolveProblem( AMPLSolver::AMPLSolver( const std::string & TheActorName, const ampl::Environment & InstallationDirectory, - const std::filesystem::path & ProblemPath ) + const std::filesystem::path & ProblemPath, + const std::string TheSolverType ) : Actor( TheActorName ), StandardFallbackHandler( Actor::GetAddress().AsString() ), NetworkingActor( Actor::GetAddress().AsString() ), @@ -225,6 +233,8 @@ AMPLSolver::AMPLSolver( const std::string & TheActorName, { RegisterHandler( this, &LSolver::DataFileUpdate ); + ProblemDefinition.setOption( "solver", TheSolverType ); + Send( Theron::AMQ::NetworkLayer::TopicSubscription( Theron::AMQ::NetworkLayer::TopicSubscription::Action::Subscription, Theron::AMQ::TopicName( DataFileTopic ) diff --git a/AMPLSolver.hpp b/AMPLSolver.hpp index 9a03df6..80e6094 100644 --- a/AMPLSolver.hpp +++ b/AMPLSolver.hpp @@ -223,23 +223,26 @@ public: explicit AMPLSolver( const std::string & TheActorName, const ampl::Environment & InstallationDirectory, - const std::filesystem::path & ProblemPath ); + const std::filesystem::path & ProblemPath, + std::string TheSolverType ); // 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 ) + const ampl::Environment & InstallationDirectory, + std::string TheSolverType ) : AMPLSolver( TheActorName, InstallationDirectory, - std::filesystem::temp_directory_path() ) + std::filesystem::temp_directory_path(), TheSolverType ) {} // 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 ) + const std::filesystem::path & ProblemPath, + std::string TheSolverType ) + : AMPLSolver( TheActorName, ampl::Environment(), ProblemPath, TheSolverType ) {} // Finally, it is just the standard constructor taking only the name of the @@ -247,7 +250,7 @@ public: AMPLSolver( const std::string & TheActorName ) : AMPLSolver( TheActorName, ampl::Environment(), - std::filesystem::temp_directory_path() ) + std::filesystem::temp_directory_path(), "couenne" ) {} // The solver will just close the open connections for listening to data file diff --git a/ExecutionControl.cpp b/ExecutionControl.cpp index fb358f6..b02e39a 100644 --- a/ExecutionControl.cpp +++ b/ExecutionControl.cpp @@ -11,6 +11,7 @@ License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/) #include "Actor.hpp" #include "Communication/NetworkEndpoint.hpp" +#include "Communication/AMQ/AMQEndpoint.hpp" #include "ExecutionControl.hpp" namespace NebulOuS @@ -56,6 +57,9 @@ void ExecutionControl::StopMessageHandler( const StopMessage & Command, { std::lock_guard< std::mutex > Lock( TerminationLock ); + Send( StatusMessage( StatusMessage::State::Stopped ), + Address( std::string( StatusTopic ) ) ); + Send( Theron::Network::ShutDown(), Theron::Network::GetAddress( Theron::Network::Layer::Session ) ); @@ -67,14 +71,24 @@ void ExecutionControl::StopMessageHandler( const StopMessage & Command, // Constructor // ----------------------------------------------------------------------------- // -// The only action taken by the constructor is to register the handler for the -// stop message. +// The constructor registers the stop message handler and sets up a publisher +// for the status topic, and then post a message that the solver is starting. ExecutionControl::ExecutionControl( const std::string & TheActorName ) : Actor( TheActorName ), - StandardFallbackHandler( Actor::GetAddress().AsString() ) + StandardFallbackHandler( Actor::GetAddress().AsString() ), + NetworkingActor( Actor::GetAddress().AsString() ) { RegisterHandler( this, &ExecutionControl::StopMessageHandler ); + + Send( Theron::AMQ::NetworkLayer::TopicSubscription( + Theron::AMQ::NetworkLayer::TopicSubscription::Action::Publisher, + std::string( StatusTopic ) + ), GetSessionLayerAddress() ); + + Send( StatusMessage( StatusMessage::State::Starting ), + Address( std::string( StatusTopic ) ) ); + } } // namespace NebulOuS \ No newline at end of file diff --git a/ExecutionControl.hpp b/ExecutionControl.hpp index 2986579..6296031 100644 --- a/ExecutionControl.hpp +++ b/ExecutionControl.hpp @@ -16,6 +16,9 @@ to call the function to wait for termination. The threads calling the function to wait for termination will block until the required message is received. +The Agent is also involved with the general component status messages to be +sent to the Solver's status topic. + 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/) @@ -26,6 +29,10 @@ License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/) // Standard headers +#include <string_view> // For constant strings +#include <map> // Standard maps +#include <sstream> // Stream conversion +#include <chrono> // For standard time points #include <condition_variable> // Execution stop management #include <mutex> // Lock the condtion variable @@ -34,6 +41,12 @@ License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/) #include "Actor.hpp" // Actor base class #include "Utility/StandardFallbackHandler.hpp" // Exception unhanded messages +// AMQ communication + +#include "Communication/NetworkingActor.hpp" // The networking actor +#include "Communication/AMQ/AMQMessage.hpp" +#include "Communication/AMQ/AMQjson.hpp" // JSON messages to be sent + namespace NebulOuS { @@ -45,7 +58,9 @@ namespace NebulOuS class ExecutionControl : virtual public Theron::Actor, - virtual public Theron::StandardFallbackHandler + virtual public Theron::StandardFallbackHandler, + virtual public Theron::NetworkingActor< + typename Theron::AMQ::Message::PayloadType > { // The mechanism used for blocking other threads will be to make them wait // for a condition variable until the message handler for the exit message @@ -57,6 +72,57 @@ private: static std::mutex TerminationLock; static std::condition_variable ReadyToTerminate; +protected: + + // There is a status message class that can be used to send the status to + // other components. + + class StatusMessage + : virtual public Theron::AMQ::JSONMessage + { + public: + + enum class State + { + Starting, + Started, + Stopping, + Stopped + }; + + private: + + std::string ToString( State TheSituation ) + { + static const std::map< State, std::string > StateString { + {State::Starting, "starting"}, {State::Started, "started"}, + {State::Stopping, "stopping"}, {State::Stopped, "stopped"} }; + + return StateString.at( TheSituation ); + } + + std::string UTCNow( void ) + { + std::ostringstream TimePoint; + TimePoint << std::chrono::system_clock::now(); + return TimePoint.str(); + } + + public: + + StatusMessage( State TheSituation, + std::string AdditionalInformation = std::string() ) + : JSONMessage( std::string( StatusTopic ), + { {"when", UTCNow() }, {"state", ToString( TheSituation ) }, + {"message", AdditionalInformation } } ) + {} + }; + + // The status of the solver is communicated on the dedicated status topic + + static constexpr std::string_view StatusTopic + = "eu.nebulouscloud.solver.state"; + public: // The function used to wait for the termination message simply waits on the diff --git a/SolverComponent.cpp b/SolverComponent.cpp index 2945dd8..ff2fa54 100644 --- a/SolverComponent.cpp +++ b/SolverComponent.cpp @@ -17,6 +17,7 @@ The command line arguments that can be givne to the Solver Component are -M ir --ModelDir <directory> for model and data files -N or --name The AMQ identity of the solver (see below) -P or --port <n> the port to use on the AMQ broker URL +-S or --Solver <label> The back-end solver used by AMPL -U or --user <user> the user to authenticate for the AMQ broker -Pw or --password <password> the AMQ broker password for the user -? or --Help prints a help message for the options @@ -29,6 +30,7 @@ Default values: -M <temporary directory created by the OS> -N "NebulOuS::Solver" -P 5672 +-S couenne -U admin -Pw admin @@ -38,6 +40,20 @@ will be established as "name@endpoint" and so if there are several solver components running, the endpoint is the only way for the AMQ solvers to distinguish the different solver component subscriptions. +Notes on use: + +The path to the AMPL API shared libray must be in the LIB path environment +variable. For instance, the installation of AMPL on the author's machine is in +/opt/AMPL and so the first thing to ensure is that the path to the API library +directory is added to the link library path, e.g., + + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/AMPL/amplapi/lib + +The AMPL directory also needs to be in the path variable, and the path must +be extended with the AMPL execution file path, e.g., + + export PATH=$PATH:/opt/AMPL + 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/) @@ -100,20 +116,22 @@ int main( int NumberOfCLIOptions, char ** CLIOptionStrings ) CLIOptions.add_options() ("A,AMPLDir", "The AMPL installation path", cxxopts::value<std::string>()->default_value("") ) - ("B,broker", "The URL of the AMQ broker", + ("B,Broker", "The URL of the AMQ broker", cxxopts::value<std::string>()->default_value("localhost") ) - ("E,endpoint", "The endpoint name", cxxopts::value<std::string>() ) + ("E,Endpoint", "The endpoint name", cxxopts::value<std::string>() ) ("M,ModelDir", "Directory to store the model and its data", cxxopts::value<std::string>()->default_value("") ) - ("N,name", "The name of the Solver Component", + ("N,Name", "The name of the Solver Component", cxxopts::value<std::string>()->default_value("NebulOuS::Solver") ) - ("P,port", "TCP port on AMQ Broker", + ("P,Port", "TCP port on AMQ Broker", cxxopts::value<unsigned int>()->default_value("5672") ) - ("U,user", "The user name used for the AMQ Broker connection", + ("S,Solver", "Solver to use, devault Couenne", + cxxopts::value<std::string>()->default_value("couenne") ) + ("U,User", "The user name used for the AMQ Broker connection", cxxopts::value<std::string>()->default_value("admin") ) - ("Pw,password", "The password for the AMQ Broker connection", + ("Pw,Password", "The password for the AMQ Broker connection", cxxopts::value<std::string>()->default_value("admin") ) - ("?,help", "Print help information"); + ("h,help", "Print help information"); CLIOptions.allow_unrecognised_options(); @@ -170,17 +188,17 @@ int main( int NumberOfCLIOptions, char ** CLIOptionStrings ) proton::connection_options AMQOptions; - AMQOptions.user( CLIValues["user"].as< std::string >() ); - AMQOptions.password( CLIValues["password"].as< std::string >() ); + AMQOptions.user( CLIValues["User"].as< std::string >() ); + AMQOptions.password( CLIValues["Password"].as< std::string >() ); // Then the network endpoint cna be constructed using the default names for // the various network endpoint servers in order to pass the defined // connection options. Theron::AMQ::NetworkEndpoint AMQNetWork( - CLIValues["endpoint"].as< std::string >(), - CLIValues["broker"].as< std::string >(), - CLIValues["port"].as< unsigned int >(), + CLIValues["Endpoint"].as< std::string >(), + CLIValues["Broker"].as< std::string >(), + CLIValues["Port"].as< unsigned int >(), Theron::AMQ::Network::NetworkLayerLabel, Theron::AMQ::Network::SessionLayerLabel, Theron::AMQ::Network::PresentationLayerLabel, @@ -203,11 +221,12 @@ int main( int NumberOfCLIOptions, char ** CLIOptionStrings ) // the root solver name. NebulOuS::SolverManager< NebulOuS::AMPLSolver > - WorkloadMabager( "WorkloadManager", + WorkloadMabager( CLIValues["Name"].as<std::string>(), std::string( NebulOuS::Solver::Solution::MessageIdentifier ), std::string( NebulOuS::Solver::ApplicationExecutionContext::MessageIdentifier ), 1, "AMPLSolver", - ampl::Environment( TheAMPLDirectory.native() ), ModelDirectory ); + ampl::Environment( TheAMPLDirectory.native() ), ModelDirectory, + CLIValues["Solver"].as<std::string>() ); NebulOuS::MetricUpdater ContextMabager( "MetricUpdater", WorkloadMabager.GetAddress() ); diff --git a/SolverManager.hpp b/SolverManager.hpp index 6392f1c..69f5165 100644 --- a/SolverManager.hpp +++ b/SolverManager.hpp @@ -299,6 +299,10 @@ public: Send( Theron::AMQ::NetworkLayer::TopicSubscription( Theron::AMQ::NetworkLayer::TopicSubscription::Action::Subscription, ContextPublisherTopic ), GetSessionLayerAddress() ); + + Send( ExecutionControl::StatusMessage( + ExecutionControl::StatusMessage::State::Started + ), Address( std::string( ExecutionControl::StatusTopic ) ) ); } else {