diff --git a/.gitignore b/.gitignore index 92eda074f..67ceeaffc 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ compile_commands.json *.plan results +.oppfeaturestate ### coverage files quisp/coverage* diff --git a/.gitmodules b/.gitmodules index 9fa54761c..0e09da8ac 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "quispr"] path = quispr url = https://github.com/sfc-aqua/quispr +[submodule "pybind11"] + path = pybind11 + url = https://github.com/pybind/pybind11 diff --git a/.nedexclusions b/.nedexclusions new file mode 100644 index 000000000..e69de29bb diff --git a/.oppfeatures b/.oppfeatures new file mode 100644 index 000000000..c02c2b1a1 --- /dev/null +++ b/.oppfeatures @@ -0,0 +1,12 @@ + + + + diff --git a/Makefile b/Makefile index 56339fe8e..01d426765 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ -QUISP_MAKEFILE = "./quisp/Makefile" +QUISP_SRC_DIR = "./quisp" +QUISP_MAKEFILE = "$(QUISP_SRC_DIR)/Makefile" +QUISP_FEATURE = "$(QUISP_SRC_DIR)/feature_defines.h" .PHONY: all tidy format ci makefile-exe makefile-lib checkmakefile googletest clean test coverage coverage-report help quispr @@ -47,12 +49,15 @@ eigen/CMakeLists.txt: eigen: eigen/CMakeLists.txt -makefile-exe: eigen +makefile-exe: eigen $(QUISP_FEATURE) cd quisp && opp_makemake -f --deep -O out -i ./makefrag -makefile-lib: eigen +makefile-lib: eigen $(QUISP_FEATURE) cd quisp && opp_makemake -f --deep -O out -i ./makefrag -M debug --make-so +$(QUISP_FEATURE): $(wildcard .oppfeaturestate) .oppfeatures + opp_featuretool defines > $(QUISP_SRC_DIR)/feature_defines.h + clean: @if [ -f "$(QUISP_MAKEFILE)" ]; then \ $(MAKE) -C quisp clean; \ diff --git a/pybind11 b/pybind11 new file mode 160000 index 000000000..ffa346860 --- /dev/null +++ b/pybind11 @@ -0,0 +1 @@ +Subproject commit ffa346860b306c9bbfb341aed9c14c067751feb8 diff --git a/quisp/.gitignore b/quisp/.gitignore index 2211d28a0..494aec9bf 100644 --- a/quisp/.gitignore +++ b/quisp/.gitignore @@ -10,6 +10,7 @@ out/ .oppfeaturestate quisp.dSYM/ Test* +feature_defines.h PhotonicQubit_m.cc PhotonicQubit_m.h diff --git a/quisp/.nedfolders b/quisp/.nedfolders index 2c61d0d51..811f76d58 100644 --- a/quisp/.nedfolders +++ b/quisp/.nedfolders @@ -1,4 +1,4 @@ . -./channels -./modules -./networks +channels +modules +networks diff --git a/quisp/app.py b/quisp/app.py new file mode 100644 index 000000000..c11839a44 --- /dev/null +++ b/quisp/app.py @@ -0,0 +1,16 @@ +import opp +import application + +def init_app_module(app): + num_resources = app.par("distant_measure_count").int() + pattern = app.par("TrafficPattern").int() + initiator_addr = app.par("LoneInitiatorAddress").int() + + if pattern == 1 and app.my_address == initiator_addr: + dest_addr = app.get_one_random_endnode_addr() + pk = app.create_conn_setup_req(dest_addr,num_resources) + app.schedule_at(opp.sim_time(), pk) + if pattern == 2: + dest_addr = app.get_one_random_endnode_addr() + pk = app.create_conn_setup_req(dest_addr,num_resources) + app.schedule_at(opp.sim_time() + 0.00001 * app.my_address, pk) diff --git a/quisp/makefrag b/quisp/makefrag index c666e05cc..27ef4e6de 100644 --- a/quisp/makefrag +++ b/quisp/makefrag @@ -25,7 +25,8 @@ else INCLUDE_PATH+=-I. $(shell pkg-config eigen3 --cflags) endif -INCLUDE_PATH+=-I. +INCLUDE_PATH+=-I$(PROJ_ROOT)/pybind11/include +INCLUDE_PATH+=-I$(OMNETPP_TOOLS_DIR)/lib/Python.framework/Headers ifneq (,$(ENABLE_COVERAGE)) CXXFLAGS+=-ftest-coverage -fprofile-instr-generate -fcoverage-mapping LDFLAGS+=-ftest-coverage -fprofile-instr-generate -fcoverage-mapping @@ -33,6 +34,7 @@ endif # include path for json INCLUDE_PATH+=-I$(PROJ_ROOT)/json/include/ +LDFLAGS+=-L$(OMNETPP_TOOLS_DIR)/lib -lpython3.8 default: eigen all diff --git a/quisp/modules/Application/Application.cc b/quisp/modules/Application/Application.cc index 9ab2462fe..24126dda1 100644 --- a/quisp/modules/Application/Application.cc +++ b/quisp/modules/Application/Application.cc @@ -5,15 +5,32 @@ * \brief Application */ #include "Application.h" +#include +#include +#include +#include +#include #include +#include "messages/base_messages_m.h" +#include "messages/connection_setup_messages_m.h" +#include "modules/Application/IApplication.h" #include "utils/ComponentProvider.h" using namespace omnetpp; using namespace quisp::messages; +namespace py = pybind11; namespace quisp { namespace modules { +PYBIND11_EMBEDDED_MODULE(application, m) { + py::class_, cSimpleModule> QuispApplication(m, "Application"); + QuispApplication.def("create_conn_setup_req", &Application::createConnectionSetupRequest); + QuispApplication.def_readonly("my_address", &Application::my_address); + QuispApplication.def("get_one_random_endnode_addr", &Application::getOneRandomEndNodeAddress); + py::class_, omnetpp::cMessage>(m, "ConnectionSetupRequest"); +} + Application::Application() : provider(utils::ComponentProvider{this}) {} /** @@ -29,19 +46,26 @@ void Application::initialize() { return; } + WATCH_VECTOR(other_end_node_addresses); + storeEndNodeAddresses(); + +#ifdef ENABLE_PYTHON + const char *app_py_path = par("app_py_path").stringValue(); + compile_code(app_py_path); + auto obj = py::cast(this); + execute_python("init_app_module", obj); + +#else + my_address = provider.getQNode()->par("address"); is_e2e_connection = par("EndToEndConnection"); num_measure = par("distant_measure_count"); - - WATCH_VECTOR(other_end_node_addresses); - storeEndNodeAddresses(); + traffic_pattern = par("TrafficPattern"); if (!is_e2e_connection) { return; } - traffic_pattern = par("TrafficPattern"); - if (traffic_pattern == 0) { EV_INFO << "EndToEndConnection is set true. but no traffic pattern specified; proceeding with no traffic\n"; return; @@ -72,6 +96,7 @@ void Application::initialize() { } error("Invalid TrafficPattern specified."); +#endif } /** diff --git a/quisp/modules/Application/Application.h b/quisp/modules/Application/Application.h index 3c2e539cf..f44dc40b2 100644 --- a/quisp/modules/Application/Application.h +++ b/quisp/modules/Application/Application.h @@ -8,6 +8,7 @@ #ifndef MODULES_APPLICATION_H_ #define MODULES_APPLICATION_H_ +#include #include "IApplication.h" #include "utils/ComponentProvider.h" @@ -20,12 +21,11 @@ namespace modules { * * \brief Application */ -class Application : public IApplication { +class Application : public IApplication, python_embeddable::PythonEmbeddable { public: Application(); ~Application() {} - protected: int my_address; std::vector other_end_node_addresses; diff --git a/quisp/modules/Application/Application.ned b/quisp/modules/Application/Application.ned index a1cb4912e..c18f9d796 100644 --- a/quisp/modules/Application/Application.ned +++ b/quisp/modules/Application/Application.ned @@ -8,6 +8,7 @@ simple Application { parameters: @display("i=block/app"); + string app_py_path = default("app"); int address; volatile double sendIaTime @unit(s) = default(exponential(1s)); // time between generating packets string Other_endnodes_table = ""; diff --git a/quisp/modules/Application/Application_test.cc b/quisp/modules/Application/Application_test.cc index 728f2fd3d..937e4c1a7 100644 --- a/quisp/modules/Application/Application_test.cc +++ b/quisp/modules/Application/Application_test.cc @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include namespace { @@ -35,6 +37,7 @@ class AppTestTarget : public quisp::modules::Application { explicit AppTestTarget(TestQNode *parent_qnode) : Application(), toRouterGate(new TestGate(this, "toRouter")) { this->provider.setStrategy(std::make_unique(parent_qnode)); setComponentType(new TestModuleType("test qnode")); + setParStr(this, "app_py_path", "app"); } virtual ~AppTestTarget() { EVCB.gateDeleted(toRouterGate); } std::vector getOtherEndNodeAdresses() { return this->other_end_node_addresses; } @@ -44,6 +47,8 @@ class AppTestTarget : public quisp::modules::Application { }; TEST(AppTest, InitSimple) { + pybind11::scoped_interpreter guard{}; + auto *sim = prepareSimulation(); auto *mock_qnode = new TestQNode{123}; auto *app = new AppTestTarget{mock_qnode}; @@ -61,6 +66,7 @@ TEST(AppTest, InitSimple) { } TEST(AppTest, Init_OneConnection_NoSender) { + pybind11::scoped_interpreter guard{}; auto *sim = prepareSimulation(); auto *mock_qnode = new TestQNode{123}; auto *app = new AppTestTarget{mock_qnode}; @@ -79,6 +85,7 @@ TEST(AppTest, Init_OneConnection_NoSender) { } TEST(AppTest, Init_OneConnection_Sender) { + pybind11::scoped_interpreter guard{}; auto *sim = prepareSimulation(); auto *mock_qnode = new TestQNode{123}; @@ -112,6 +119,7 @@ TEST(AppTest, Init_OneConnection_Sender) { } TEST(AppTest, Init_OneConnection_Sender_TrafficPattern2) { + pybind11::scoped_interpreter guard{}; auto *sim = prepareSimulation(); auto *mock_qnode = new TestQNode{123}; auto *mock_qnode2 = new TestQNode{456}; diff --git a/quisp/modules/Backend/Backend.cc b/quisp/modules/Backend/Backend.cc index a517e35f9..5c0c4dc5f 100644 --- a/quisp/modules/Backend/Backend.cc +++ b/quisp/modules/Backend/Backend.cc @@ -1,7 +1,14 @@ #include "Backend.h" namespace quisp::modules::backend { -BackendContainer::BackendContainer() {} + +BackendContainer::BackendContainer() +#ifdef ENABLE_PYTHON + : interpreter(pybind11::scoped_interpreter{}) +#endif +{ +} + BackendContainer::~BackendContainer() {} } // namespace quisp::modules::backend diff --git a/quisp/modules/Backend/Backend.h b/quisp/modules/Backend/Backend.h index 589c60acd..b1fec502c 100644 --- a/quisp/modules/Backend/Backend.h +++ b/quisp/modules/Backend/Backend.h @@ -1,8 +1,10 @@ #pragma once #include #include +#include #include #include "RNG.h" +#include "feature_defines.h" namespace quisp::modules::backend { using quisp::modules::common::ErrorTrackingBackend; @@ -15,6 +17,10 @@ class BackendContainer : public omnetpp::cSimpleModule { ~BackendContainer(); void initialize() override { +#ifdef ENABLE_PYTHON + python_enabled = true; +#endif + WATCH(python_enabled); auto backend_type = std::string(par("backendType").stringValue()); if (backend_type == "ErrorTrackingBackend") { backend = std::make_unique(std::make_unique(this)); @@ -33,7 +39,12 @@ class BackendContainer : public omnetpp::cSimpleModule { } protected: - std::unique_ptr backend; + void configureErrorTrackingBackend(); + std::unique_ptr backend = nullptr; + bool python_enabled = false; +#ifdef ENABLE_PYTHON + pybind11::scoped_interpreter interpreter; +#endif }; Define_Module(BackendContainer); diff --git a/quisp/modules/Backend/Backend_test.cc b/quisp/modules/Backend/Backend_test.cc index c8e2c517c..00dc16511 100644 --- a/quisp/modules/Backend/Backend_test.cc +++ b/quisp/modules/Backend/Backend_test.cc @@ -1,4 +1,5 @@ #include "Backend.h" +#include #include #include #include "modules/common_types.h" @@ -29,6 +30,7 @@ TEST(BackendContainer, callInitialize) { } TEST(BackendContainer, callInitializeWithInvalidBackend) { + Py_Finalize(); auto *sim = utils::prepareSimulation(); BackendContainer *backend = new BackendContainer(); sim->registerComponent(backend); @@ -37,7 +39,8 @@ TEST(BackendContainer, callInitializeWithInvalidBackend) { EXPECT_THROW(backend->callInitialize(), omnetpp::cRuntimeError); } -TEST(BackendContainer, getQuantumBackend) { +TEST(BackendConpainer, getQuantumBackend) { + Py_Finalize(); auto *sim = utils::prepareSimulation(); BackendContainer *backend = new BackendContainer(); sim->registerComponent(backend); diff --git a/quisp/modules/Common/PythonEmbeddedable.cc b/quisp/modules/Common/PythonEmbeddedable.cc new file mode 100644 index 000000000..8d36c8a9b --- /dev/null +++ b/quisp/modules/Common/PythonEmbeddedable.cc @@ -0,0 +1,50 @@ +#include "PythonEmbeddedable.h" +#include +#include +#include +#include +#include +#include +#include +#include "feature_defines.h" + +namespace py = pybind11; +namespace opp = omnetpp; +namespace fs = std::filesystem; +namespace quisp::modules::python_embeddable { + +#ifdef ENABLE_PYTHON +PYBIND11_EMBEDDED_MODULE(opp, m) { + py::class_> OppPar(m, "Par"); + OppPar.def("str", &omnetpp::cPar::str); + OppPar.def("int", &omnetpp::cPar::intValue); + + py::class_> OppSimpleModule(m, "SimpleModule"); + OppSimpleModule.def("par", py::overload_cast(&opp::cSimpleModule::par), py::return_value_policy::reference); + OppSimpleModule.def("schedule_at", &opp::cSimpleModule::scheduleAt); + m.def("sim_time", &opp::simTime, py::return_value_policy::reference); + + py::class_ OppSimTime(m, "SimTime"); + OppSimTime.def(py::self + float()); + OppSimTime.def(py::self += float()); + OppSimTime.def(float() + py::self); + + py::class_> OppCMessage(m, "Message"); +} +#endif + +void PythonEmbeddable::compile_code(const char* file_name) { + auto* env = omnetpp::getEnvir(); + auto* config = env->getConfigEx(); + fs::path exe_path(env->getArgVector()[0]); + fs::path conf_path(env->resolveResourcePath(config->getFileName())); + py::module sys = py::module::import("sys"); + py::cast(sys.attr("path")).append(fs::absolute(exe_path.parent_path()).string()); + py::cast(sys.attr("path")).append(fs::absolute(conf_path.parent_path()).string()); + py::print(sys.attr("path")); + module = pybind11::module_::import(file_name); +} + +void PythonEmbeddable::execute_python(const char* func_name, py::object& arg1) { py::object ret = module.attr(func_name)(arg1); } + +} // namespace quisp::modules::python_embeddable diff --git a/quisp/modules/Common/PythonEmbeddedable.h b/quisp/modules/Common/PythonEmbeddedable.h new file mode 100644 index 000000000..74251dc8f --- /dev/null +++ b/quisp/modules/Common/PythonEmbeddedable.h @@ -0,0 +1,16 @@ +#pragma once +#include + +namespace quisp::modules::python_embeddable { +namespace py = pybind11; + +class PythonEmbeddable { + public: + PythonEmbeddable() {} + ~PythonEmbeddable() {} + void compile_code(const char* file_name); + void execute_python(const char* func_name, py::object& arg1); + + pybind11::module_ module; +}; +} // namespace quisp::modules::python_embeddable diff --git a/quisp/test_utils/Configuration.h b/quisp/test_utils/Configuration.h index f478c44a8..aa7cd72fa 100644 --- a/quisp/test_utils/Configuration.h +++ b/quisp/test_utils/Configuration.h @@ -2,18 +2,19 @@ #include #include "KeyValue.h" +#include "omnetpp/cconfiguration.h" namespace quisp_test { namespace configuration { using key_value::TestKeyValue; -class Configuration : public omnetpp::cConfiguration { +class Configuration : public omnetpp::cConfigurationEx { private: std::vector kvs; public: - Configuration() { + Configuration() : omnetpp::cConfigurationEx() { kvs = std::vector(); auto kv = TestKeyValue{}; kvs.push_back(kv); @@ -23,7 +24,29 @@ class Configuration : public omnetpp::cConfiguration { virtual const char *getPerObjectConfigValue(const char *objectFullPath, const char *keySuffix) const override { return nullptr; }; virtual const KeyValue &getPerObjectConfigEntry(const char *objectFullPath, const char *keySuffix) const override { return kvs.at(0); }; virtual const char *substituteVariables(const char *value) const override { return nullptr; }; + virtual void initializeFrom(cConfiguration *bootConfig) override{}; + virtual const char *getFileName() const override { return "test-file-name"; }; + virtual void validate(const char *ignorableConfigKeys = nullptr) const override{}; + virtual std::vector getConfigNames() override { return {}; }; + virtual void activateConfig(const char *configName, int runNumber = 0) override{}; + virtual std::string getConfigDescription(const char *configName) const override {} + virtual std::vector getBaseConfigs(const char *configName) const override {} + virtual std::vector getConfigChain(const char *configName) const override {} + virtual int getNumRunsInConfig(const char *configName) const override {} + virtual std::vector unrollConfig(const char *configName) const override {} + virtual const char *getActiveConfigName() const override {} + virtual int getActiveRunNumber() const override {} + virtual const char *getVariable(const char *varname) const override {} + virtual std::vector getIterationVariableNames() const override {} + virtual std::vector getPredefinedVariableNames() const override {} + virtual const char *getVariableDescription(const char *varname) const override {} + virtual void dump() const override {} + virtual std::vector getMatchingConfigKeys(const char *pattern) const override {} + virtual const char *getParameterValue(const char *moduleFullPath, const char *paramName, bool hasDefaultValue) const override {} + virtual const KeyValue &getParameterEntry(const char *moduleFullPath, const char *paramName, bool hasDefaultValue) const override {} + virtual std::vector getKeyValuePairs(int flags = FILT_ALL) const override {} + virtual std::vector getMatchingPerObjectConfigKeySuffixes(const char *objectFullPath, const char *keySuffixPattern) const override {} }; } // namespace configuration -} // namespace quisp_test \ No newline at end of file +} // namespace quisp_test diff --git a/quisp/test_utils/StaticEnv.cc b/quisp/test_utils/StaticEnv.cc index 67632da6e..2c5289cf1 100644 --- a/quisp/test_utils/StaticEnv.cc +++ b/quisp/test_utils/StaticEnv.cc @@ -11,6 +11,7 @@ using configuration::Configuration; StaticEnv::StaticEnv() {} cConfiguration *StaticEnv::getConfig() { return new Configuration(); } +cConfigurationEx *StaticEnv::getConfigEx() { return new Configuration(); } std::string StaticEnv::gets(const char *prompt, const char *defaultreply) { unsupported(); return ""; diff --git a/quisp/test_utils/StaticEnv.h b/quisp/test_utils/StaticEnv.h index d76d94db6..8cce36090 100644 --- a/quisp/test_utils/StaticEnv.h +++ b/quisp/test_utils/StaticEnv.h @@ -70,6 +70,7 @@ class StaticEnv : public omnetpp::cEnvir { void flushXMLParsedContentCache() override {} unsigned getExtraStackForEnvir() const override { return 0; } cConfiguration *getConfig() override; + cConfigurationEx *getConfigEx() override; std::string resolveResourcePath(const char *fileName, cComponentType *context) override { return ""; } bool isGUI() const override { return false; } bool isExpressMode() const override { return false; }