diff --git a/xo-pydistribution/.gitignore b/xo-pydistribution/.gitignore new file mode 100644 index 00000000..13c0afb7 --- /dev/null +++ b/xo-pydistribution/.gitignore @@ -0,0 +1,6 @@ +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/xo-pydistribution/CMakeLists.txt b/xo-pydistribution/CMakeLists.txt new file mode 100644 index 00000000..3389b1a2 --- /dev/null +++ b/xo-pydistribution/CMakeLists.txt @@ -0,0 +1,28 @@ +# xo-pydistribution/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pydistribution VERSION 1.0) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/pydistribution) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/xo-pydistribution/README.md b/xo-pydistribution/README.md new file mode 100644 index 00000000..98199fef --- /dev/null +++ b/xo-pydistribution/README.md @@ -0,0 +1,77 @@ +# python bindings for c++ reflection library (xo-distribution) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-reflect](https://github.com/Rconybea/xo-distribution) + +### build + install +``` +$ cd xo-pydistribution +$ 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-pydistribution +$ 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-pydistribution +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` + +## Examples + +Assumes `xo-pydistribution` installed to `~/local2/lib` + +``` +PYTHONPATH=~/local2/lib python +>>> import xo_pydistribution +>>> dir(xo_pydistribution) +['Distribution', 'ExplicitDist', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'normalcdf'] +>>> from xo_pydistribution import * +``` + +normal distribution +``` +>>> normalcdf(0.0) +0.5 +>>> normalcdf(3.0) +0.9986501019683699 +``` + +explicit distribution (online implementation). +intended to model empirically a Bayesian prior. +``` +>>> d=ExplicitDist.make(bucket_dx=0.01, ref_value=1e-6) +>>> d +]"> +>>> d.cdf(0.0) +0.0 +>>> d.cdf(0.01) +1.0 +``` diff --git a/xo-pydistribution/cmake/xo-bootstrap-macros.cmake b/xo-pydistribution/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..aba31169 --- /dev/null +++ b/xo-pydistribution/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/xo-pydistribution/cmake/xo_pydistributionConfig.cmake.in b/xo-pydistribution/cmake/xo_pydistributionConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/xo-pydistribution/cmake/xo_pydistributionConfig.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/xo-pydistribution/include/README.md b/xo-pydistribution/include/README.md new file mode 100644 index 00000000..ec349995 --- /dev/null +++ b/xo-pydistribution/include/README.md @@ -0,0 +1 @@ +placeholder for future pydistribution #include files diff --git a/xo-pydistribution/src/pydistribution/CMakeLists.txt b/xo-pydistribution/src/pydistribution/CMakeLists.txt new file mode 100644 index 00000000..389ae251 --- /dev/null +++ b/xo-pydistribution/src/pydistribution/CMakeLists.txt @@ -0,0 +1,10 @@ +# xo_pydistribution/src/pydistribution/CMakeLists.txt + +set(SELF_LIB xo_pydistribution) +set(SELF_SRCS pydistribution.cpp) + +# ---------------------------------------------------------------- + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +xo_pybind11_dependency(${SELF_LIB} xo_distribution) +xo_pybind11_dependency(${SELF_LIB} xo_pyutil) diff --git a/xo-pydistribution/src/pydistribution/pydistribution.cpp b/xo-pydistribution/src/pydistribution/pydistribution.cpp new file mode 100644 index 00000000..58793950 --- /dev/null +++ b/xo-pydistribution/src/pydistribution/pydistribution.cpp @@ -0,0 +1,73 @@ +/* @file pydistribution.cpp */ + +#include "pydistribution.hpp" +#include "xo/distribution/Normal.hpp" +#include "xo/distribution/ExplicitDist.hpp" +//#include "xo/reflect/SelfTagging.hpp" +#include "xo/pyutil/pyutil.hpp" +#include +#include + +namespace xo { + using xo::distribution::Normal; + using xo::distribution::Distribution; + using xo::distribution::ExplicitDist; + + namespace sim { + namespace py = pybind11; + + PYBIND11_MODULE(PYDISTRIBUTION_MODULE_NAME(), m) { + m.doc() = "pybind11 distribution plugin"; // optional module docstring + + m.def("normalcdf", + &Normal::cdf_impl, + "cumulative normal distribution", + py::arg("x")); + + py::class_, + rp>>(m, "Distribution") + .def("cdf", &Distribution::cdf, + "return cumulative distribution function at x", + py::arg("x")); + + py::class_, + Distribution, + rp>>(m, "ExplicitDist") + .def_static("make", &ExplicitDist::make, + "create instance", + py::arg("bucket_dx"), py::arg("ref_value")) + .def_static("make_n", &ExplicitDist::make_n, + "create instance with n buckets", + py::arg("n"), py::arg("bucket_dx"), py::arg("ref_value")) + .def("n_bucket", &ExplicitDist::n_bucket, + "return number of explicitly-represented buckets in distribution") + .def("lo", &ExplicitDist::lo, + "return least upper bound x: cdf(x)=0") + .def("hi", &ExplicitDist::hi, + "return greatest lower bound x: cdf(x)=1") + .def("density", &ExplicitDist::density, + "return probability density at x", + py::arg("x")) + .def("density_v", &ExplicitDist::density_v, + "return probability density vector for all explicit buckets." + " each member is pair {lh bucket edge, density}") + .def("signed_bucket_index", &ExplicitDist::signed_bucket_index, + "signed index to probability bucket. ref_value -> 0", + py::arg("x")) + .def("scale_bucket", &ExplicitDist::scale_bucket, + "scale probability weight in bucket containing x by k", + py::arg("x"), py::arg("k")) + .def("scale_by_normal_cdf", &ExplicitDist::scale_by_normal_cdf, + "scale by normal cumulative distribution N(sign.(x-mean)/sigma)." + " expect sign in {+1, -1}", + py::arg("sign"), py::arg("mean"), py::arg("sigma")) + .def("renormalize", &ExplicitDist::renormalize, + "renormalize to ensure sum of weights=1") + .def("check_renormalize", &ExplicitDist::check_renormalize, + "renormalize if needed, otherwise do nothing") + .def("__repr__", &ExplicitDist::display_string); + } + } /*namespace sim*/ +} /*namespace xo*/ + +/* end pydistribution.cpp */ diff --git a/xo-pydistribution/src/pydistribution/pydistribution.hpp.in b/xo-pydistribution/src/pydistribution/pydistribution.hpp.in new file mode 100644 index 00000000..bf94e785 --- /dev/null +++ b/xo-pydistribution/src/pydistribution/pydistribution.hpp.in @@ -0,0 +1,25 @@ +/* @file pydistribution.hpp + * + * automatically generated from src/pydistribution/pydistribution.hpp.in + * see src/pydistribution/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYDISTRIBUTION_MODULE_NAME(), m) { ... } + */ +#define PYDISTRIBUTION_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(PYDISTRIBUTION_MODULE_NAME_STR) + */ +#define PYDISTRIBUTION_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * PYDISTRIBUTION_IMPORT_MODULE() + * replaces + * py::module_::import("pydistribution") + */ +#define PYDISTRIBUTION_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pydistribution.hpp */