commit 0e9ecec2ed83c94bd3a3cb4a638d724e0a6d1185 Author: Roland Conybeare Date: Wed Oct 18 17:10:23 2023 -0400 initial implementation diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..af0bae5b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,44 @@ +# xo-pyprocess/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pyprocess VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see github.com:Rconybea/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/pyprocess) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/README.md b/README.md new file mode 100644 index 00000000..2ad68ffb --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# python bindings for c++ stochastic process library (xo-process) + +# build + install +``` +$ cd xo-pyprocess +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +# build for unit test coverage +``` +$ cd xo-pyprocess +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +# LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pyprocess +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` + +# Examples + +Assumes `xo-pyprocess` installed to `~/local2/lib` + +``` +PYTHONPATH=~/local2/lib python +>>> import pyprocess +>>> dir(pyprocess) +``` diff --git a/cmake/cmake b/cmake/cmake new file mode 100644 index 00000000..b49b4828 --- /dev/null +++ b/cmake/cmake @@ -0,0 +1,4 @@ + /home/roland/proj/xo-pyprocess/cmake: + drwxr-xr-x 2 roland roland 4096 Oct 12 21:49 . + drwxr-xr-x 6 roland roland 4096 Oct 12 21:49 .. + -rw-r--r-- 1 roland roland 125 Oct 12 21:49 xo_pyprocessConfig.cmake.in diff --git a/cmake/xo_pyprocessConfig.cmake.in b/cmake/xo_pyprocessConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pyprocessConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..aca8853c --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder for future pyprocess #include files diff --git a/src/pyprocess/CMakeLists.txt b/src/pyprocess/CMakeLists.txt new file mode 100644 index 00000000..1b6c5347 --- /dev/null +++ b/src/pyprocess/CMakeLists.txt @@ -0,0 +1,8 @@ +# xo_pyprocess/src/pyprocess/CMakeLists.txt + +set(SELF_LIB pyprocess) +set(SELF_SRCS pyprocess.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) + +xo_pybind11_dependency(${SELF_LIB} process) diff --git a/src/pyprocess/pyprocess.cpp b/src/pyprocess/pyprocess.cpp new file mode 100644 index 00000000..21c2f287 --- /dev/null +++ b/src/pyprocess/pyprocess.cpp @@ -0,0 +1,183 @@ +/* @file pyprocess.cpp */ + +// note: need pyprocess/ here bc pyprocess.hpp is generated, located in build directory +#include "src/pyprocess/pyprocess.hpp" +#include "xo/pywebutil/pywebutil.hpp" +#include "xo/process/init_process.hpp" +#include "xo/process/UpxToConsole.hpp" +#include "xo/process/StochasticProcess.hpp" +#include "xo/process/BrownianMotion.hpp" +#include "xo/process/ExpProcess.hpp" +#include "xo/process/RealizationSource.hpp" +#include "xo/pyreactor/pyreactor.hpp" +#include "xo/reactor/EventStore.hpp" +#include "xo/reactor/PolyAdapterSink.hpp" +#include "xo/randomgen/random_seed.hpp" +#include "xo/randomgen/xoshiro256.hpp" +#include +#include +#include + +/* xo::ref::intrusive_ptr is an intrusively-reference-counted pointer. + * always safe to create one from a T* p + * (since refcount is directly accessible from p) + */ +PYBIND11_DECLARE_HOLDER_TYPE(T, xo::ref::intrusive_ptr, true); + +namespace xo { + using xo::reactor::AbstractSink; + using xo::reactor::AbstractEventStore; + using xo::reactor::StructEventStore; + using xo::reactor::PolyAdapterSink; + using xo::json::PrintJsonSingleton; + using xo::time::utc_nanos; + using xo::rng::Seed; + using xo::rng::xoshiro256ss; + using xo::ref::rp; + namespace py = pybind11; + + namespace process { + PYBIND11_MODULE(PYPROCESS_MODULE_NAME(), m) { + /* ensure process/ will be initialized */ + InitSubsys::require(); + /* ..and immediately perform init steps */ + Subsystem::initialize_all(); + + /* e.g. py wrapper for xo::reactor::ReactorSource */ + PYREACTOR_IMPORT_MODULE(); + /* e.g. py wrapper for xo::web::EndpointDescr */ + PYWEBUTIL_IMPORT_MODULE(); + + m.doc() = "pybind11 plugin for xo.process"; + + m.def("make_brownian_motion", + [](utc_nanos start_tm, + double annual_volatility) { + Seed seed; + + return BrownianMotion::make(start_tm, + annual_volatility, + seed); + }, + "create new BrownianMotion instance"); + + m.def("make_exponential_brownian_motion", + [](utc_nanos start_tm, + double start_value, + double annual_volatility) { + Seed seed; + + return ExpProcess::make(start_value /*scale*/, + BrownianMotion::make(start_tm, + annual_volatility, + seed)); + }, + py::arg("start_tm"), py::arg("start_value"), py::arg("annual_volatility")); + + py::class_, + xo::ref::rp>>(m, "StochasticProcess") + .def_property_readonly("t0", &StochasticProcess::t0) + .def_property_readonly("t0_value", &StochasticProcess::t0_value) + .def("exterior_sample", &StochasticProcess::exterior_sample) + .def("__repr__", &StochasticProcess::display_string); + + py::class_, + StochasticProcess, + xo::ref::rp>>(m, "BrownianMotion"); + //.def("exterior_sample", &BrownianMotion::exterior_sample) + //.def("__repr__", &BrownianMotion::display_string); + + py::class_, + xo::ref::rp>(m, "ExpProcess") + .def_property_readonly("exponent_process", + [](ExpProcess & self) { + return self.exponent_process().promote(); + }); + + m.def("make_tracer", + &RealizationTracer::make); + + py::class_, + xo::ref::rp>>(m, "RealizationTracer"); + + /* e.g. + * import datetime as dt + * t0=dt.datetime.now() + * ebm=pyprocess.make_exponential_brownian_motion(t0, 0.5) + * s=pyprocess.make_realization_source(ebm, dt.timedelta(seconds=1)) + */ + m.def("make_realization_source", + [](xo::ref::rp> p, + xo::time::nanos sample_dt) + { + auto tracer = RealizationTracer::make(p); + + return RealizationSource::make(tracer, + sample_dt); + }); + + /* note: providing __repr__ changes printing behavior, + * but uses default printer for inherited std::pair<..> + */ + py::class_(m, "UpxEvent") + .def_property_readonly("tm", &UpxEvent::tm) + .def_property_readonly("upx", &UpxEvent::upx) + .def("__repr__", &UpxEvent::display_string); + + py::class_, + reactor::ReactorSource, + xo::ref::rp>>(m, "RealizationSource") + .def_property_readonly("current_ev", &RealizationSource::current_ev); + + py::class_> + (m, "UpxToConsole"); + + using UpxEventStore = StructEventStore; + + /* see also: KalmanFilterStateEventStore in [pyfilter/pyfilter.cpp] + */ + py::class_> + (m, "UpxEventStore") + .def_static("make", &UpxEventStore::make) + .def_property_readonly("empty", &UpxEventStore::empty) + .def_property_readonly("size", &UpxEventStore::size) + .def("last_n", &UpxEventStore::last_n, py::arg("n")) + .def("last_dt", &UpxEventStore::last_dt, py::arg("dt")); + //.def("__repr__", &UpxEventStore::display_string); + + /* temporary -- to reveal compiler errors */ + using UpxAdapterSink = PolyAdapterSink; + + py::class_>(m, "UpxAdapterSink") + .def_static("make", &UpxAdapterSink::make); + + /* prints + * std::pair + * pairs + */ + m.def("make_realization_printer", &UpxToConsole::make); + +#ifdef OBSOLETE + /* this implementation fails -- looks like .so libraries + * have separate typeinfo for std::pair + * and don't find each other. + */ + m.def("make_realization_printer2", + [] + { + return reactor::TemporaryTest::realization_printer(); + }); +#endif + + } /*pyprocess*/ + } /*namespace process*/ +} /*namespace xo*/ + +/* end pyprocess.cpp */ diff --git a/src/pyprocess/pyprocess.hpp.in b/src/pyprocess/pyprocess.hpp.in new file mode 100644 index 00000000..d2f5cec1 --- /dev/null +++ b/src/pyprocess/pyprocess.hpp.in @@ -0,0 +1,25 @@ +/* @file pyprocess.hpp + * + * automatically generated from src/pyprocess/pyprocess.hpp.in + * see src/pyprocess/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYPROCESS_MODULE_NAME(), m) { ... } + */ +#define PYPROCESS_MODULE_NAME() @SELF_LIBRARY_NAME@ + +/* example: + * py::module_::import(PYPROCESS_MODULE_NAME_STR) + */ +#define PYPROCESS_MODULE_NAME_STR "@SELF_LIBRARY_NAME@" + +/* example: + * PYPROCESS_IMPORT_MODULE() + * replaces + * py::module_::import("pyprocess") + */ +#define PYPROCESS_IMPORT_MODULE() py::module_::import("@SELF_LIBRARY_NAME@") + +/* end pyprocess.hpp */