diff --git a/xo-process/.github/workflows/main.yml b/xo-process/.github/workflows/main.yml new file mode 100644 index 00000000..618a85e4 --- /dev/null +++ b/xo-process/.github/workflows/main.yml @@ -0,0 +1,269 @@ +name: build xo-process + xo dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Clone webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/webutil + + - name: Configure webutil + # configure cmake for webutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil + + - name: Build webutil + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Install webutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_webutil + + # ---------------------------------------------------------------- + + - name: Clone printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/printjson + + - name: Configure printjson + # configure cmake for printjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson + + - name: Build printjson + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Install printjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_printjson + + # ---------------------------------------------------------------- + + - name: Clone randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/randomgen + + - name: Configure randomgen + # configure cmake for randomgen in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen + + - name: Build randomgen + run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} + + - name: Install randomgen + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_randomgen + + # ---------------------------------------------------------------- + + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + + - name: Clone reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/reactor + + - name: Configure reactor + # configure cmake for reactor in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor + + - name: Build reactor + run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} + + - name: Install reactor + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reactor + + # ---------------------------------------------------------------- + + - name: Clone simulator + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-simulator + path: repo/simulator + + - name: Configure simulator + # configure cmake for simulator in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_simulator -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/simulator + + - name: Build simulator + run: cmake --build ${{github.workspace}}/build_simulator --config ${{env.BUILD_TYPE}} + + - name: Install simulator + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_simulator + + # ---------------------------------------------------------------- + + - name: Configure self (process) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_process -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (process) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_process --config ${{env.BUILD_TYPE}} + + - name: Test self (process) + working-directory: ${{github.workspace}}/build_process + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} diff --git a/xo-process/.gitignore b/xo-process/.gitignore new file mode 100644 index 00000000..13c0afb7 --- /dev/null +++ b/xo-process/.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-process/CMakeLists.txt b/xo-process/CMakeLists.txt new file mode 100644 index 00000000..3cf358bd --- /dev/null +++ b/xo-process/CMakeLists.txt @@ -0,0 +1,30 @@ +# xo-process/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(process VERSION 1.0) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- + +add_subdirectory(src/process) +add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support for reactor customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/xo-process/README.md b/xo-process/README.md new file mode 100644 index 00000000..fe4f6263 --- /dev/null +++ b/xo-process/README.md @@ -0,0 +1,57 @@ +# stochastic process library + +constructive, simulation-aware models for stochastic processes + +## Getting Started + +### build + install dependencies + +build+install these first + +- xo-simulator [github.com/Rconybea/xo-simulator](https://github.com/Rconybea/xo-simulator) +- randomgen [github.com/Rconybea/randomgen](https://github.com/Rconybea/randomgen) + +# build + install + +## build +``` +$ cd xo-process +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ 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-process +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +## development + +### LSP support + +LSP looks for compile commands in the root of the source tree; +cmake creates them in the root of its build directory. + +``` +$ cd xo-process +$ ln -s build/compile_commands.json +``` + +## display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-process/build +$ cmake -LAH +``` diff --git a/xo-process/cmake/processConfig.cmake.in b/xo-process/cmake/processConfig.cmake.in new file mode 100644 index 00000000..dc6207c5 --- /dev/null +++ b/xo-process/cmake/processConfig.cmake.in @@ -0,0 +1,13 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-process/src/process/CMakeLists.txt +# +find_dependency(reactor) +find_dependency(printjson) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/xo-process/cmake/xo-bootstrap-macros.cmake b/xo-process/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..aba31169 --- /dev/null +++ b/xo-process/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-process/include/xo/process/AbstractRealization.hpp b/xo-process/include/xo/process/AbstractRealization.hpp new file mode 100644 index 00000000..b87e20ac --- /dev/null +++ b/xo-process/include/xo/process/AbstractRealization.hpp @@ -0,0 +1,20 @@ +/* file AbstractRealization.hpp + * + * author: Roland Conybeare, Nov 2022 + */ + +#pragma once + +#include "xo/reflect/SelfTagging.hpp" +#include "AbstractStochasticProcess.hpp" + +namespace xo { + namespace process { + class AbstractRealization : public reflect::SelfTagging { + public: + virtual rp stochastic_process() const = 0; + }; /*AbstractRealization*/ + } /*namespace process*/ +} /*namespace xo*/ + +/* end AbstractRealization.hpp */ diff --git a/xo-process/include/xo/process/AbstractStochasticProcess.hpp b/xo-process/include/xo/process/AbstractStochasticProcess.hpp new file mode 100644 index 00000000..b21cc8d6 --- /dev/null +++ b/xo-process/include/xo/process/AbstractStochasticProcess.hpp @@ -0,0 +1,19 @@ +/* file AbstractStochasticProcess.hpp + * + * author: Roland Conybeare, Nov 2022 + */ + +#pragma once + +#include "xo/reflect/SelfTagging.hpp" + +namespace xo { + namespace process { + class AbstractStochasticProcess : public reflect::SelfTagging { + }; /*AbstractStochasticProcess*/ + } /*namespace process*/ + +} /*namespace xo*/ + + +/* end AbstractStochasticProcess.hpp */ diff --git a/xo-process/include/xo/process/BrownianMotion.hpp b/xo-process/include/xo/process/BrownianMotion.hpp new file mode 100644 index 00000000..1ec1f5cf --- /dev/null +++ b/xo-process/include/xo/process/BrownianMotion.hpp @@ -0,0 +1,321 @@ +/* @file BrownianMotion.hpp */ + +#pragma once + +#include "Realizable2Process.hpp" +#include "Realization2.hpp" +#include "RealizationState.hpp" +#include "xo/randomgen/normalgen.hpp" +#include "xo/reflect/StructReflector.hpp" +#include "xo/reflect/TaggedPtr.hpp" +#include +#include + +namespace xo { + namespace process { + class BrownianMotionBase : public Realizable2Process { + public: + using nanos = xo::time::nanos; + + public: + BrownianMotionBase(utc_nanos t0, + double volatility) + : t0_{t0}, + volatility_(volatility), + vol2_day_{(volatility * volatility) * (1.0 / 365.25)} + {} + + /* brownian motion with constant volatility at this level */ + double volatility() const { return volatility_; } + double vol2_day() const { return vol2_day_; } + + /* compute variance that accumulates over time interval dt + * for this brownian motion + */ + double variance_dt(nanos dt) const; + + // ----- needed by Realization2 ----- + + virtual rp> make_realization() override = 0; + + // ----- inherited from StochasticProcess ----- + + virtual utc_nanos t0() const override { return t0_; } + + protected: + /* generate sample given a random number from N(0,1) */ + double exterior_sample_impl(utc_nanos t, + event_type const & lo, + double x0); + + protected: + /* starting time for this process */ + utc_nanos t0_; + /* annual volatility (1-year := 365.25 days) for this process */ + double volatility_ = 0.0; + /* daily variance for this brownian motion */ + double vol2_day_ = 0.0; + }; /*BrownianMotionBase*/ + + /* representation for brownian motion. + * + * starting value of zero at time t0. + * for process with volatility s, variance for horizon dt is + * V = (s^2).dt + * + * ofc this means volatility has units 1/sqrt(t) + * + * event_type: something like std::pair + * value_type: double + */ + template + class BrownianMotion : public BrownianMotionBase { + public: + using self_type = BrownianMotion; + using rstate_type = StochasticProcess::event_type; + using TaggedRcptr = reflect::TaggedRcptr; + using normalgen_type = xo::rng::normalgen; + using nanos = xo::time::nanos; + + public: + /* t0. start time, + * sdev. annual sqrt volatility + * seed. initialize pseudorandom-number generator + */ + template + static rp> make(utc_nanos t0, + double sdev, + Seed const & seed) + { + return new BrownianMotion(t0, sdev, seed); + } /*make*/ + + /* reflect BrownianMotion object representation */ + static void reflect_self() { + reflect::StructReflector> sr; + + if (sr.is_incomplete()) { +#ifdef NOT_USING + REFLECT_MEMBER(sr, t0); + REFLECT_MEMBER(sr, volatility); + REFLECT_MEMBER(sr, vol2_day); +#endif + REFLECT_MEMBER(sr, rng); + } + } /*reflect_self*/ + + virtual ~BrownianMotion() = default; + + /* see .make_realization(); coordinates with that */ + void rstate_sample_inplace(nanos dt, rstate_type * p_rstate) { + utc_nanos t0 = p_rstate->first; + utc_nanos t1 = t0 + dt; + double x1_n01 = this->rng_(); + + value_type x1 = this->exterior_sample(t1, *p_rstate, x1_n01); + + *p_rstate = std::make_pair(t1, x1); + } /*rstate_sample_inplace*/ + + // ----- inherited from BrownianMotionBase ----- + + // ----- inherited from Realizable2Process<> ----- + + virtual rp> make_realization() override { + rstate_type rs0 = std::make_pair(this->t0(), + this->t0_value()); + + return new ProcessRealization2(rs0, this); + } /*make_realization*/ + + virtual std::unique_ptr make_rstate() override { + rstate_type rs0 = std::make_pair(this->t0(), + this->t0_value()); + + return std::unique_ptr(new RealizationState(rs0)); + } /*make_rstate*/ + + // ----- inherited from StochasticProcess<> ----- + + virtual double t0_value() const override { return 0.0; } + + /* sample this process at time t, + * given glb sample for this process lo={t_lo, x_lo}, t>t_lo + */ + virtual value_type exterior_sample(utc_nanos t, + event_type const &lo) override; + /* sample this process at time t, + * given glb sample for this process lo={t_lo, x_lo}, + * and lub sample for this process of hi={t_hi, x_hi}, + * t_lot1} for process hitting value a, + * given preceding known value + * {t1, v1} = {lo.first, lo.second} + */ + virtual utc_nanos hitting_time(double const &a, + event_type const &lo) override; +#endif + + /* return human-readable string identifying this process */ + virtual std::string display_string() const override { + return ""; + } + + // ----- Inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override { return reflect::Reflect::make_rctp(this); } + + private: + template + BrownianMotion(utc_nanos t0, double sdev, Seed const & seed) + : BrownianMotionBase(t0, sdev), + rng_{normalgen_type::make(RngEngine(seed), + std::normal_distribution(0.0 /*mean*/, 1.0 /*sdev*/))} { + BrownianMotion::reflect_self(); + } + + private: + /* generates normally-distributed pseudorandom numbers, + * distributed according to N(0,1) + */ + normalgen_type rng_; + }; /*BrownianMotion*/ + + template + double + BrownianMotion::interior_sample(utc_nanos ts, + event_type const & lo, + event_type const & hi) + { + /* suppose we know values of a brownian motion + * at two points t1, t2: + * x1 = B(t1) + * x2 = B(t2) + * + * Want to sample B for some particular time ts in (t1,t2). + * + * First step is to de-drift B: + * + * considered sheared process B'(t): + * B'(dt) = -B(t1) + B(t1+dt) - u.dt, + * where u = (x2-x1).(t2-t1), t in (t1,t2) + * then + * B'(0) = 0 + * E[B'(dt)] = 0 + * V[B'(dt)] ~ dt + * + * so B' is also a brownian motion. + * We want to sample the conditional process + * B''(dt) = {B'(dt) | B'(t2-t1)=0} at some point ts in (0, t2-t1). + * The condition B'(t2-t1)=0 gives us: + * B(t1) + B''(t-t1) = B(t1), t=t1 + * B(t1) + B''(t-t1) = B(t2), t=t2 + * + * At ts: + * - the increment x = B(ts) - B(t1) is normally distributed, + * with variance proportional to (ts - t1). + * - the increment y = B(t2) - B(ts) is normally distributed, + * with variance proportional to (t2 - ts). + * + * Using bivariate normal prob density of two vars x,y + * with sdev sx, sy: + * + * 1 / x^2 y^2 \ + * p(x,y) = ---------- . exp | -(1/2) (------ + ------) | + * 2.pi.sx.sy \ sx^2 sy^2 / + * + * we can condition on y=-x to get conditional probability distribution + * + * 1 / x^2 . sy^2 + y^2 . sx^2 \ + * p(x) = ---------- . exp | -(1/2) --------------------------- | + * 2.pi.sx.sy \ sx^2 . sy^2 / + * + * + * 1 / x^2 . sy^2 + y^2 . sx^2 \ + * = ---------- . exp | -(1/2) --------------------------- | + * 2.pi.sx.sy \ sx^2 . sy^2 / + * + * 1 / x^2 . (sy^2 + sx^2) \ + * = ---------- . exp | -(1/2) --------------------------- | + * 2.pi.sx.sy \ sx^2 . sy^2 / + * + * / sx^2 . sy^2 \ + * let sxy = sqrt | ------------- | + * \ sx^2 + sy^2 / + * + * then + * 1 / x^2 \ + * p(x) = ---------- . exp | -(1/2) ----- | + * 2.pi.sx.sy \ sxy^2 / + * + * which is density for normal distribution with variance sxy^2, + * (scaled by constant 1 / sqrt(sx^2 + sy^2)); + * + * e.g. at midpoint between t1 and t2, is sx^2 = sy^2 = 1/2 : + * sxy^2 = 1/4 + * + * which is 1/2 the variance we'd see at midpoint if not constrained + * to B(t2)=x2 + */ + + utc_nanos lo_tm = lo.first; + double lo_x = lo.second; + utc_nanos hi_tm = hi.first; + double hi_x = hi.second; + + double t_frac = (ts - lo_tm) / (hi_tm - lo_tm); + + /* compute mean value, at t, relative to B(lo), + * of all brownian motions on [lo, hi] that + * start from B(lo) and end at B(hi), + * + * i.e. applying drift u = (x2 - x1)/(t2 - t1) two stationary BM + */ + double mean_dx = (hi_x - lo_x) * t_frac; + + /* t splits the interval [t1,t2] into two subintervals + * [t1,t] and [t,t2]. compute variances of brownian motion + * increments [t1,t], [t,t2]: + */ + double var1 = this->variance_dt(ts - lo_tm); + double var2 = this->variance_dt(hi_tm - ts); + + /* variance for B(ts) is (var1 * var2 / (var1 + var2)) */ + double vars = var1 * var2 / (var1 + var2); + + /* sample from N(0,1) */ + double xs = this->rng_(); + + /* scale for variance of B(ts) */ + double dx = ::sqrt(vars) * xs; + + double x = lo_x + mean_dx + dx; + + return x; + } /*interior_sample*/ + + template + double + BrownianMotion::exterior_sample(utc_nanos t, + event_type const & lo) + { + /* sample brownian motion starting at t0; + * offset by lo.second + */ + + double x0 = this->rng_(); + + return this->exterior_sample_impl(t, lo, x0); + } /*exterior_sample*/ + + + } /*namespace process*/ +} /*namespace xo*/ + +/* end BrownianMotion.hpp */ diff --git a/xo-process/include/xo/process/ExpProcess.hpp b/xo-process/include/xo/process/ExpProcess.hpp new file mode 100644 index 00000000..8cf6f526 --- /dev/null +++ b/xo-process/include/xo/process/ExpProcess.hpp @@ -0,0 +1,103 @@ +/* @file ExpProcess.hpp */ + +#pragma once + +//#include "time/Time.hpp" +#include "StochasticProcess.hpp" +#include +#include + +namespace xo { + namespace process { + // a stochastic process + // + // S(t) + // P(t) = m.e + // + // where + // - m is a constant scale factor + // - S(t) is some already-defined-and-represented process + // + // In particular, if S(t) is brownian motion, + // then P(t) is log-normal + // + class ExpProcess : public StochasticProcess { + public: + using TaggedRcptr = reflect::TaggedRcptr; + + public: + static rp make(double scale, + ref::brw> exp_proc) { + return new ExpProcess(scale, exp_proc); + } /*make*/ + + /* reflect ExpProcess object representation */ + static void reflect_self(); + + ref::brw> exponent_process() const { return exponent_process_.borrow(); } + + // ----- inherited from StochasticProcess<...> ----- + + virtual ~ExpProcess() = default; + + virtual utc_nanos t0() const override { return this->exponent_process_->t0(); } + + virtual double t0_value() const override { + return this->scale_ * ::exp(this->exponent_process_->t0_value()); + } + + /* note: lo is a sample from the exponentiated process; + * must take log to get sample from the exponent process + */ + virtual value_type exterior_sample(utc_nanos t, + event_type const & lo) override; + + /* note: lo, hi are samples from the exponentiated process; + * must take logs to get samples from the exponent process + */ + virtual value_type interior_sample(utc_nanos t, + event_type const & lo, + event_type const & hi) override { + double m + = this->scale_; + double e + = (this->exponent_process_->interior_sample + (t, + event_type(lo.first, ::log(lo.second)), + event_type(hi.first, ::log(hi.second)))); + + return m * ::exp(e); + } /*interior_sample*/ + + virtual std::string display_string() const override { + // return tostr("display_string(), ">"); + + return ""; + } /*display_string*/ + + // ----- Inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override; + + private: + ExpProcess(double scale, ref::brw exp_proc) + : scale_(scale), + exponent_process_{exp_proc.get()} { + ExpProcess::reflect_self(); + } + + private: + /* modeling + * P(t) = m.exp(E(t)) + * where: + * - m is .scale + * - E(t) is .exponent_process + */ + double scale_ = 1.0; + /* exponentiate this process */ + rp> exponent_process_; + }; /*ExpProcess*/ + } /*namespace process*/ +} /*namespace xo*/ + +/* end ExpProcess.hpp */ diff --git a/xo-process/include/xo/process/LogNormalProcess.hpp b/xo-process/include/xo/process/LogNormalProcess.hpp new file mode 100644 index 00000000..36d6cf49 --- /dev/null +++ b/xo-process/include/xo/process/LogNormalProcess.hpp @@ -0,0 +1,33 @@ +/* LogNormalProcess.hpp */ + +#pragma once + +#include "BrownianMotion.hpp" +#include "ExpProcess.hpp" + +namespace xo { + namespace process { + + /* log-normal process -- i.e. logs follow brownian motion + */ + class LogNormalProcess { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + /* log-normal process starting at (t0, x0) */ + template + static rp make(utc_nanos t0, double x0, + double sdev, Seed const & seed) { + + rp> bm + = BrownianMotion::make(t0, sdev, seed); + + return ExpProcess::make(x0 /*scale*/, bm); + } /*make*/ + }; /*LogNormalProcess*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end LogNormalProcess.hpp */ diff --git a/xo-process/include/xo/process/Realizable2Process.hpp b/xo-process/include/xo/process/Realizable2Process.hpp new file mode 100644 index 00000000..5a3effd3 --- /dev/null +++ b/xo-process/include/xo/process/Realizable2Process.hpp @@ -0,0 +1,58 @@ +/* file Realizable2Process.hpp + * + * author: Roland Conybeare, Nov 2022 + */ + +#pragma once + +#include "StochasticProcess.hpp" +#include "Realization2.hpp" +#include "RealizationState.hpp" + +namespace xo { + namespace process { + /* a stochastic process p that interacts with a Realization2 + * This means: + * - p defines state (Rstate) sufficient to constructively unroll/unfold + * one of its own paths + * - p provides methods to implement such unfolding: + * - .make_realization() :: Realization2 + * create new realization of p. + * - .rstate_sample(t1,rs0) :: time x Rstate -> Rstate + * given runstate rs0 representing process state at some time t0, + * sample process at time t1, with t0<=t1 + * - in general can only sample process at a bounded set of points; + * sometimes useful to be able to generate samples consistently in + * non-monotonically-increasing time order. Algorithm to do this available + * for some, but not all p + * - .rstate_insample(t1,rs0,rs2) :: time x Rstate x Rstate -> Rstate + * given runstates rs0, rs2 representing process state at two times t0 + class Realizable2Process : public StochasticProcess { + public: + virtual rp> make_realization() = 0; + /* make_rstate() will be used to establish nested state when a process is used + * as input to a transforming process (ex: ExpProcess). + * in that context the outer process' realization state will + * need to hold an abstract pointer to nested process' realization state, + * and use this method to establish that state. + */ + virtual std::unique_ptr make_rstate() = 0; + // Rstate rstate_init() const; + // void rstate_sample_implace(utc_nanos t1, Rstate * p_rs0 const; + }; /*Realizable2Process*/ + + } /*namespace process*/ +} /*namespace xo*/ + + +/* end Realizable2Process.hpp */ diff --git a/xo-process/include/xo/process/Realization.hpp b/xo-process/include/xo/process/Realization.hpp new file mode 100644 index 00000000..35ced2df --- /dev/null +++ b/xo-process/include/xo/process/Realization.hpp @@ -0,0 +1,72 @@ +/* @file Realization.hpp */ + +#pragma once + +#include "StochasticProcess.hpp" +//#include "time/Time.hpp" +//#include +#include +#include + +namespace xo { + namespace process { + +// realization of a stochastic process. +// interface designed to allow for lazy evaluation. +// +// since a process connects a family of random variables, +// a single process can have a generally unbounded number of distinct realizations. +// +// implications: +// - can only realize (or observe) a finite set of instants. +// - given process evolves continuously, +// want ability to revisit intervals that may already contain some realized instants. +// - achieve this by allowing for caching behavior +// + template + class Realization : public ref::Refcount { + public: + using utc_nanos = xo::time::utc_nanos; + using KnownMap = std::map; + using KnownIterator = typename KnownMap::const_iterator; + //using KnownRange = boost::iterator_range; + using KnownRange = decltype(std::views::all(KnownMap())); + + public: + static rp make(ref::brw> p) { + return new Realization(p); + } /*make*/ + + ref::brw> process() const { return process_; } + + utc_nanos t0() const { return process_->t0(); } + + size_t n_known() const { return this->known_map_.size(); } + + /* require: .n_known() > 0 */ + utc_nanos lo_tm() const { return this->known_map_.begin().first(); } + utc_nanos hi_tm() const { return this->known_map_.rbegin().first(); } + + //KnownRange known_range() const { return boost::make_iterator_range(this->known_map_); } + KnownRange known_range() const { return std::views::all(this->known_map_); } + + // concept: + // realized_range() -> iterator_range + + private: + Realization(ref::brw> p) : process_{p} {} + + private: + /* stochastic process from which this realization is sampled */ + rp> process_; + + /* process value (for this realization) has been established (sampled) + * at each time t in {.known_map[].first} + */ + KnownMap known_map_; + }; /*Realization*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end Realization.hpp */ diff --git a/xo-process/include/xo/process/Realization2.hpp b/xo-process/include/xo/process/Realization2.hpp new file mode 100644 index 00000000..1edae93a --- /dev/null +++ b/xo-process/include/xo/process/Realization2.hpp @@ -0,0 +1,91 @@ +/* file Realization2.hpp + * + * author: Roland Conybeare, Nov 2022 + */ + +#pragma once + +#include "AbstractRealization.hpp" +#include "xo/reflect/Reflect.hpp" +//#include "time/Time.hpp" + +namespace xo { + namespace process { + template + class Realization2 : public AbstractRealization { + }; /*Realization2*/ + + /* Rstate: state needed to trace unfolding of a process + * realization; will be process-specific. + * + * Pattern like: + * StochasticProcess p + * Rstate rs + * Realization2 rz + * + * +----+ +----+ + * | rz +----| rs | + * +--+-+ +----+ + * | ^ + * +----+ | + * | p | <----/ + * +----+ + * + * rz owns rs, sends it to p to be modified as p needs + * p knows type Rstate, initially creates it + * therefore also knows how to create its own realizations + * + * + * Require: + * - Process -isa-> Realizable2Process + */ + template + class ProcessRealization2 : public Realization2 { + public: + using TaggedRcptr = reflect::TaggedRcptr; + using nanos = xo::time::nanos; + + public: + ProcessRealization2(Rstate const & rstate, rp const & process) + : rstate_{rstate}, process_{process} {} + ProcessRealization2(Rstate && rstate, rp const & process) + : rstate_{std::move(rstate)}, process_{process} {} + + Rstate const & rstate() const { return rstate_; } + rp const & process() const { return process_; } + + /* sample process at point .rstate.tk + dt + * Require: + * - dt >= 0 + */ + void advance_dt(nanos dt) { + this->process_->rstate_sample_inplace(dt, &(this->rstate_)); + } /*advance_dt*/ + + // ----- inherited from AbstractRealization ----- + + virtual rp stochastic_process() const override { + return process_; + } /*stochastic_process*/ + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override { return reflect::Reflect::make_rctp(this); } + + private: + /* realization state + * this type is determined by .process; + * sufficient state to develop faithful realization + */ + Rstate rstate_; + /* process (set of paths + probability measure); + * *this coordinates with .process to constructively samples one such path + */ + rp process_; + }; /*ProcessRealization2*/ + } /*namespace process*/ + +} /*namespace xo*/ + + +/* end Realization2.hpp */ diff --git a/xo-process/include/xo/process/RealizationCallback.hpp b/xo-process/include/xo/process/RealizationCallback.hpp new file mode 100644 index 00000000..314bdc2e --- /dev/null +++ b/xo-process/include/xo/process/RealizationCallback.hpp @@ -0,0 +1,31 @@ +/* @file RealizationCallback.hpp */ + +#pragma once + +#include "xo/reactor/Sink.hpp" +#include "xo/indentlog/print/pair.hpp" +//#include "time/Time.hpp" +#include + +namespace xo { + namespace process { + /* callback for consuming stochastic process realizations */ + template + class RealizationCallback : public reactor::Sink1> { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + /* notification with process event (std::pair) + * see StochasticProcess::event_type + */ + virtual void notify_ev(std::pair const & ev) override; + + /* CallbackSet invokes these on add/remove events */ + virtual void notify_add_callback() override {} + virtual void notify_remove_callback() override {} + }; /*RealizationCallback*/ + } /*namespace process*/ +} /*namespace xo*/ + +/* end RealizationCallback.hpp */ diff --git a/xo-process/include/xo/process/RealizationSource.hpp b/xo-process/include/xo/process/RealizationSource.hpp new file mode 100644 index 00000000..932da915 --- /dev/null +++ b/xo-process/include/xo/process/RealizationSource.hpp @@ -0,0 +1,314 @@ +/* @file RealizationSimSource.hpp */ + +#pragma once + +#include "xo/reactor/ReactorSource.hpp" +#include "RealizationTracer.hpp" +#include "RealizationCallback.hpp" +#include "xo/callback/CallbackSet.hpp" +#include "xo/indentlog/scope.hpp" +#include + +namespace xo { + namespace process { + /* use a discrete realization of a continuous stochastic process, + * as a simulation source. + * + * 1. Realization is developed lazily, (see RealizationTracer) + * 2. Use a fixed discretization interval to develop realization + * 3. events are consumed by Sink + * + * Require: + * - std::pair --convertible-to--> EventType + * - EventSink.notify_source_exhausted() + * - invoke EventSink(x), with x :: EventType + */ + template + class RealizationSourceBase : public xo::reactor::ReactorSource { + public: + using event_type = typename RealizationTracer::event_type; + using nanos = xo::time::nanos; + + public: + ~RealizationSourceBase() { + //constexpr char const * c_self = "RealizationSimSource<>::dtor"; + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled), + "delete instance", + xtag("p", this)); + } /*dtor*/ + + static rp + make(rp> const & tracer, + nanos ev_interval_dt, + EventSink const & ev_sink) + { + using xo::scope; + using xo::xtag; + + constexpr bool c_logging_enabled = false; + + auto p = new RealizationSourceBase(tracer, ev_interval_dt, ev_sink); + + scope log(XO_DEBUG(c_logging_enabled), + "create instance", + xtag("p", p), + xtag("bytes", sizeof(RealizationSourceBase))); + + return p; + } /*make*/ + +#ifdef NOT_IN_USE + static rp make(rp> tracer, + nanos ev_interval_dt, + EventSink && ev_sink) + { + return new RealizationSimSource(tracer, ev_interval_dt, ev_sink); + } /*make*/ +#endif + + event_type const & current_ev() const { return this->tracer_->current_ev(); } + nanos ev_interval_dt() const { return ev_interval_dt_; } + + /* supplying this to allow for setting up cyclic pointer references */ + EventSink * ev_sink_addr() { return &(this->ev_sink_); } + + /* deliver current event to sink */ + void sink_one() const { + /* calling .ev_sink() can modify the callback set reentrantly + * (i.e. adding/removing callbacks) + * although this changes the state of .ev_sink, + * we want to treat this as not changing the state of *this + */ + RealizationSourceBase * self = const_cast(this); + + self->ev_sink_(this->tracer_->current_ev()); + } /*sink_one*/ + + // ----- inherited from ReactorSource ----- + + /* process realizations are always primed (at least for now) */ + virtual bool is_empty() const override { return false; } + /* stochastic process api doesn't have an end time; + * will need simulator to impose one + */ + virtual bool is_exhausted() const override { return false; } + + virtual utc_nanos sim_current_tm() const override { return this->tracer_->current_tm(); } + + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { this->name_ = x; } + virtual bool debug_sim_flag() const override { return debug_sim_flag_; } + virtual void set_debug_sim_flag(bool x) override { this->debug_sim_flag_ = x; } + + /* note: + * with replay_flag=true, treats tm as lower bound + */ + virtual std::uint64_t sim_advance_until(utc_nanos tm, bool replay_flag) override { + std::uint64_t retval = 0ul; + + if(replay_flag) { + while(this->sim_current_tm() < tm) { + retval += this->deliver_one(); + } + } else { + this->tracer_->advance_until(tm); + } + + return retval; + } /*advance_until*/ + + // ----- Inherited from AbstractSource ----- + + virtual TypeDescr source_ev_type() const override { + return reflect::Reflect::require(); + } + + /* Tracer is intended always to deliver non-volatile events */ + virtual bool is_volatile() const override { return false; } + + virtual uint32_t n_out_ev() const override { return n_out_ev_; } + /* no mechanism in RealizationSource to hold onto an outgoing event + * see reactor::SecondarySource for contrary example + */ + virtual uint32_t n_queued_out_ev() const override { return 0; } + + virtual std::uint64_t deliver_one() override { + ++(this->n_out_ev_); + this->sink_one(); + this->tracer_->advance_dt(this->ev_interval_dt_); + + return 1; + } /*deliver_one*/ + + virtual CallbackId attach_sink(rp const & /*sink*/) override { + /* see RealizationSource */ + assert(false); + return CallbackId(); + } + + virtual void detach_sink(CallbackId /*id*/) override { + /* see RealizationSource */ + assert(false); + } + + virtual void display(std::ostream & os) const override { + using xo::xtag; + + os << "name()) + << xtag("n_out_ev", this->n_out_ev()) + //<< xtag("ev_interval_dt", ev_interval_dt_) + << ">"; + } /*display*/ + + virtual void visit_direct_consumers(std::function)> const &) override { + assert(false); + } + + protected: + RealizationSourceBase(rp> const & tracer, + nanos ev_interval_dt, + EventSink const & ev_sink) + : tracer_{tracer}, + ev_sink_{std::move(ev_sink)}, + ev_interval_dt_{ev_interval_dt} {} + RealizationSourceBase(rp> const & tracer, + nanos ev_interval_dt, + EventSink && ev_sink) + : tracer_{tracer}, + ev_sink_{std::move(ev_sink)}, + ev_interval_dt_(ev_interval_dt) {} + + private: + static constexpr std::string_view sc_self_type = xo::reflect::type_name>(); + + private: + /* reporting name for this source -- use when .debug_sim_flag is set */ + std::string name_; + /* if true reactor/simulator to log interaction with this source */ + bool debug_sim_flag_ = false; + /* counts lifetime #of events */ + uint32_t n_out_ev_ = 0; + /* produces events representing realized stochastic-process values */ + rp> tracer_; + /* send stochastic-process events to this sink */ + EventSink ev_sink_; + /* discretize process using this interval: + * consecutive events from this simulation source will be at least + * .ev_interval_dt apart + */ + nanos ev_interval_dt_; + }; /*RealizationSourceBase*/ + + // ----- RealizationSource ----- + + template + class RealizationSource + : public RealizationSourceBase, + decltype(&reactor::Sink1::notify_ev)>> + + { + public: + using TypeDescr = reflect::TypeDescr; + using CallbackId = fn::CallbackId; + using utc_nanos = xo::time::utc_nanos; + using nanos = xo::time::nanos; + + public: + static rp> make(rp> const & tracer, + nanos ev_interval_dt) + { + return new RealizationSource(tracer, ev_interval_dt); + } /*make*/ + + CallbackId add_callback(rp> const & cb) { + return this->ev_sink_addr()->add_callback(cb); + } /*add_callback*/ + + void remove_callback(CallbackId id) { + this->ev_sink_addr()->remove_callback(id); + } /*remove_callback*/ + + // ----- inherited from AbstractSource ----- + + /* alternative naming: + * .add_callback(sink) <--> .attach_sink(sink) + * .remove_callback(sink) <--> .detach_sink(sink) + */ + virtual CallbackId attach_sink(rp const & sink) override { + /* ------- + * WARNING + * ------- + * spent some time chasing down clang behavior here. + * the call to + * reactor::Sink1<...>::require_native() + * fails unexpectedly because the template + * Sink1> + * and RealizationSource may come from different modules. + */ + + //using xo::scope; + //using xo::xtag; + + /* checking that sink handles events of type T + * This is quick-n-dirty. Want reflection here, so we can write + * a runtime type test + * sink->can_consume() + * w/out exploding vtable size + */ + constexpr std::string_view c_self_name + = "RealizationSource::attach_sink"; + + //scope lscope(c_self_name); + //lscope.log(xtag("T", reflect::type_name())); + + rp> event_sink + = reactor::Sink1::require_native(c_self_name, sink); + + return this->add_callback(event_sink); + } /*attach_sink*/ + + virtual void detach_sink(CallbackId id) override { + /* see comment on .attach_sink() */ + + this->remove_callback(id); + } /*detach_sink*/ + + virtual void display(std::ostream & os) const override { + using xo::xtag; + + os << "name()) + << xtag("n_out_ev", this->n_out_ev()) + //<< xtag("ev_interval_dt", this->ev_interval_dt()) + << ">"; + } /*display*/ + + // ----- Inherited from AbstractEventProcessor ----- + + virtual void visit_direct_consumers(std::function)> const & fn) override { + + for(auto const & x : *(this->ev_sink_addr())) + fn(x.fn_.borrow()); + } /*visit_direct_consumers*/ + + private: + RealizationSource(rp> const & tracer, + nanos ev_interval_dt) + : RealizationSourceBase + , + decltype(&reactor::Sink1::notify_ev)> + >(tracer, + ev_interval_dt, + fn::make_notify_cbset(&reactor::Sink1::notify_ev)) + {} + }; /*RealizationSource*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end RealizationSource.hpp */ diff --git a/xo-process/include/xo/process/RealizationState.hpp b/xo-process/include/xo/process/RealizationState.hpp new file mode 100644 index 00000000..0d43c581 --- /dev/null +++ b/xo-process/include/xo/process/RealizationState.hpp @@ -0,0 +1,42 @@ +/* file RealizationState.hpp + * + * author: Roland Conybeare, Nov 2022 + */ + +#pragma once + +#include + +/* opaque type representing state of an unfolding + * realization, for a StochasticProcess. + * Needs runtime polymorphism here so we can stack states + * e.g. to represent realization state for a process + * defined by transformation of another process. + * For example see ExpProcess. + * For now we don't refcount these; expect each process-realization + * to create its own stack, managed with unique_ptr<> + * + * See also: + * - ProcessRealization2 + * - Realizable2Process + */ +class AbstractRealizationState { +public: + AbstractRealizationState() = default; + + virtual ~AbstractRealizationState() = default; +}; /*RealizationState*/ + +template +class RealizationState : public AbstractRealizationState { +public: + RealizationState(Rstate const & rs) : rstate_{rs} {} + RealizationState(Rstate && rs) : rstate_{std::move(rs)} {} + + Rstate * p_rstate() { return &rstate_; } + +private: + Rstate rstate_; +}; /*RealizationState*/ + +/* end RealizationState.hpp */ diff --git a/xo-process/include/xo/process/RealizationTracer.hpp b/xo-process/include/xo/process/RealizationTracer.hpp new file mode 100644 index 00000000..d427f5d6 --- /dev/null +++ b/xo-process/include/xo/process/RealizationTracer.hpp @@ -0,0 +1,112 @@ +/* @file RealizationTracer.hpp */ + +#pragma once + +#include "StochasticProcess.hpp" +#include "xo/refcnt/Refcounted.hpp" + +namespace xo { + namespace process { + //template class RealizationSimSource; + + /* One-way iteration over a realization (i.e. sampled path) + * belonging to a stochastic process. + * has a monotonically increasing 'current time'. + * can be adapted as a simulation source + * + * Example: + * utc_nanos t0 = ...; + * double sdev = 1.0; + * Seed seed; + * auto process = LogNormalProcess::make(t0, sdev, seed); + * auto tracer = RealizationTracer::make(process.get()); + */ + template + class RealizationTracer : public ref::Refcount { + public: + using Process = xo::process::StochasticProcess; + using process_type = Process; + /* something like std::pair */ + using event_type = typename Process::event_type; + using utc_nanos = xo::time::utc_nanos; + using nanos = xo::time::nanos; + + public: + static rp make(rp const & p) { + return new RealizationTracer(p); + } + + event_type const & current_ev() const { return current_; } + utc_nanos current_tm() const { return current_.first; } + /* value of this path at time t */ + T const & current_value() const { return current_.second; } + rp const & process() const { return process_; } + + /* sample with fixed time: + * - advance to time t+dt, where t=.current_tm() + * - return new time and process value + * + * can use .advance_dt(dt) to avoid copying T + */ + std::pair next_dt(nanos dt) { + this->advance_dt(dt); + + return this->current_; + } /*next_dt*/ + + std::pair next_eps(double eps) { + this->advance_eps(eps); + + return this->current_; + } /*next_eps*/ + + /* sample with fixed time: + * - advance to point t+dt, with dt specified. + */ + void advance_dt(nanos dt) { + utc_nanos t1 = this->current_.first + dt; + + this->advance_until(t1); + } /*advance_dt*/ + + void advance_until(utc_nanos t1) { + event_type ev0 = this->current_; + + if(t1 <= ev0.first) { + /* tracer state already past t1 */ + } else { + T x1 = this->process_->exterior_sample(t1, ev0); + + /* careful! may not alter .current until after call to exterior_sample() + * returns + */ + + this->current_.first = t1; + this->current_.second = x1; + } + } /*advance_until*/ + +#ifdef NOT_IN_USE // need StochasticProcess.hitting_time() for this + /* sample with max change in process value eps. + * requires that T defines a norm under which eps + * can be interpreted + */ + virtual void advance_eps(double eps) = 0; +#endif + + private: + RealizationTracer(rp const & p) + : current_(event_type(p->t0(), p->t0_value())), process_(p) {} + + private: + /* current (time, processvalue) associated with this realization */ + event_type current_; + + /* develop a sampled realization of this stochastic process */ + rp process_; + }; /*RealizationTracer*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end RealizationTracer.hpp */ diff --git a/xo-process/include/xo/process/StochasticProcess.hpp b/xo-process/include/xo/process/StochasticProcess.hpp new file mode 100644 index 00000000..3c6a4101 --- /dev/null +++ b/xo-process/include/xo/process/StochasticProcess.hpp @@ -0,0 +1,73 @@ +/* @file StochasticProcess.hpp */ + +#pragma once + +#include "AbstractStochasticProcess.hpp" +//#include "refcnt/Refcounted.hpp" +//#include "time/Time.hpp" +#include +#include + +namespace xo { + namespace process { + +// abstraction for a stochastic process. +// - represents a probability space: +// - a collection of paths +// - an associated probability measure on path sapce +// - paths may vary continuously with time +// - need not be continuous +// - want to be able to use in simulation, +// in which case will likely require some discretization +// + template + class StochasticProcess : public AbstractStochasticProcess { + public: + using value_type = T; + using utc_nanos = xo::time::utc_nanos; + using event_type = std::pair; + + public: + virtual ~StochasticProcess() = default; + + /* starting time for this process */ + virtual utc_nanos t0() const = 0; + + /* starting value of this process */ + virtual T t0_value() const = 0; + + /* sample this process at time t, + * given preceding known value + * {t1, v1} + * with t1 < t + */ + virtual value_type exterior_sample(utc_nanos t, + event_type const & lo) = 0; + + /* sample this process at time t, + * given surrounding known values + * {t1, v1}, {t2, v2} + * with t1 < t < t2 + */ + virtual value_type interior_sample(utc_nanos t, + event_type const & lo, + event_type const & hi) = 0; + +#ifdef NOT_IN_USE + /* sample hitting time + * T(a) = inf{t : P(t)=a, t>t1} for process hitting value a, + * given preceding known value + * {t1, v1} = {lo.first, lo.second} + */ + virtual utc_nanos hitting_time(T const & a, + event_type const & lo) = 0; +#endif + + /* human-readable string identifying this process */ + virtual std::string display_string() const = 0; + }; /*StochasticProcess*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end StochasticProcess.hpp */ diff --git a/xo-process/include/xo/process/UpxEvent.hpp b/xo-process/include/xo/process/UpxEvent.hpp new file mode 100644 index 00000000..51ad768b --- /dev/null +++ b/xo-process/include/xo/process/UpxEvent.hpp @@ -0,0 +1,54 @@ +/* @file UpxEvent.hpp */ + +#pragma once + +#include "xo/indentlog/timeutil/timeutil.hpp" + +namespace xo { + namespace process { + /* typical representation for events emitted by a stochastic process + * writing this as a non-template class (instead of just template alias) + * because we want typeinfo to be generated + */ + class UpxEvent { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + UpxEvent(); + //UpxEvent(std::pair const & x) : contents_{x} {} + UpxEvent(std::pair const & x) : tm_{x.first}, upx_{x.second} {} + //UpxEvent(utc_nanos tm, double x) : contents_{tm, x} {} + UpxEvent(utc_nanos tm, double x) : tm_{tm}, upx_{x} {} + + /* reflect UpxEvent object representation */ + static void reflect_self(); + + /* convenience -- e.g. so we can use with EventTimeFn */ + //utc_nanos tm() const { return contents_.first; } + utc_nanos tm() const { return tm_; } + //double upx() const { return contents_.second; } + double upx() const { return upx_; } + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* note: earlier version inherited std::pair<>, but this exposed + * pybind11 problem when we tried to control printing + */ + utc_nanos tm_; + double upx_; + //std::pair contents_; + }; /*UpxEvent*/ + + inline std::ostream & + operator<<(std::ostream & os, UpxEvent const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end UpxEvent.hpp */ diff --git a/xo-process/include/xo/process/UpxToConsole.hpp b/xo-process/include/xo/process/UpxToConsole.hpp new file mode 100644 index 00000000..650de79c --- /dev/null +++ b/xo-process/include/xo/process/UpxToConsole.hpp @@ -0,0 +1,25 @@ +/* @file UpxToConsole.hpp */ + +#pragma once + +#include "UpxEvent.hpp" +#include "xo/reactor/Sink.hpp" + +namespace xo { + namespace process { + /* trivial extension of SinkToConsole. + * hoping to workaroudn a typeinfo problem by getting typeinfo for Sink1 + * to appear in the process/ library instead of the process_py/ library. + * + * See FAQ "dynamic_cast *> fails unexpectedly for a template class" + */ + class UpxToConsole : public xo::reactor::SinkToConsole { + public: + UpxToConsole(); + + static rp make(); + }; /*UpxToConsole*/ + } /*namespace process*/ +} /*namespace xo*/ + +/* end UpxToConsole.hpp */ diff --git a/xo-process/include/xo/process/init_process.hpp b/xo-process/include/xo/process/init_process.hpp new file mode 100644 index 00000000..5fcba67d --- /dev/null +++ b/xo-process/include/xo/process/init_process.hpp @@ -0,0 +1,21 @@ +/* file init_process.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + /* tag to represent the process/ subsystem within ordered initialization */ + enum S_process_tag {}; + + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + +/* end init_process.hpp */ diff --git a/xo-process/src/process/BrownianMotion.cpp b/xo-process/src/process/BrownianMotion.cpp new file mode 100644 index 00000000..0c000696 --- /dev/null +++ b/xo-process/src/process/BrownianMotion.cpp @@ -0,0 +1,102 @@ +/* @file BrownianMotion.cpp */ + +#include "xo/reflect/TaggedPtr.hpp" +//#include "time/Time.hpp" +#include "BrownianMotion.hpp" +#include + +namespace xo { + using xo::time::utc_nanos; + using xo::scope; + using xo::xtag; + + namespace process { + double + BrownianMotionBase::variance_dt(nanos dt) const + { + constexpr uint64_t c_sec_per_day = (24L * 3600L); + constexpr double c_day_per_sec = (1.0 / c_sec_per_day); + + /* time-to-horizon in nanos */ + double dt_sec = std::chrono::duration(dt).count(); + double dt_day = dt_sec * c_day_per_sec; + + return this->vol2_day_ * dt_day; + } /*variance_dt*/ + + double + BrownianMotionBase::exterior_sample_impl(utc_nanos t, + BrownianMotionBase::event_type const & lo, + double x0) + { + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + /* sample brownian motion starting at t0; + * offset by lo.second + */ + + utc_nanos lo_tm = lo.first; + double lo_x = lo.second; + + nanos dt = (t - lo_tm); + + /* variance at horizon t, relative to value at lo.first */ + double var = this->variance_dt(dt); + + /* scale for variance of B(t) - B(lo) */ + double dx = ::sqrt(var) * x0; + + double sample = lo_x + dx; + + log && log("result", + xtag("start-time", this->t0()), + xtag("vol2-day", this->vol2_day()), + xtag("lo.tm", lo_tm), + xtag("lo.x", lo_x), + xtag("dt-us", std::chrono::duration_cast(dt).count()), + xtag("var", var), + xtag("dx", dx)); + + return sample; + } /*exterior_sample_impl*/ + + // ----- BrownianMotion ----- + +#ifdef NOT_IN_USE + utc_nanos + BrownianMotion::hitting_time(double const & a, + event_type const & lo) + { + /* (1) + * probability density function p1(s) + * giving hitting time for brownian motion starting at 0, + * first time to reach a constant barrier a: + * + * a^2 + * - --- + * a 2.s + * p1(s) = ------------- . e + * sqrt(2.pi.s^3) + * + * (2) + * we also know probability density function p2(s) + * giving hitting time for brownian motion starting at 0, + * first time to reach expanding barrier a + ct: + * (i.e. T2 = inf{t : B(t) = c.t + a, t > 0}) + * + * (c.s + a)^2 + * - ----------- + * a 2.s + * p2(s) = -------------- . e + * sqrt(2.pi.s^3) + * + */ + } /*hitting_time*/ +#endif + + } /*namespace process*/ +} /*namespace xo*/ + +/* end BrownianMotion.cpp */ diff --git a/xo-process/src/process/CMakeLists.txt b/xo-process/src/process/CMakeLists.txt new file mode 100644 index 00000000..c7bb3abe --- /dev/null +++ b/xo-process/src/process/CMakeLists.txt @@ -0,0 +1,19 @@ +# xo-process/src/process/CMakeLists.txt + +set(SELF_LIB process) +set(SELF_SRCS + BrownianMotion.cpp ExpProcess.cpp Realization.cpp UpxEvent.cpp UpxToConsole.cpp + init_process.cpp) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# external dependencies + +# note: changes to xo_dependency() calls here +# must coordinate with find_dependency() calls +# in xo-process/cmake/processConfig.cmake.in +# +xo_dependency(${SELF_LIB} reactor) +xo_dependency(${SELF_LIB} printjson) +#xo_dependency(${SELF_LIB} callback) diff --git a/xo-process/src/process/ExpProcess.cpp b/xo-process/src/process/ExpProcess.cpp new file mode 100644 index 00000000..7b5d36f7 --- /dev/null +++ b/xo-process/src/process/ExpProcess.cpp @@ -0,0 +1,69 @@ +/* @file ExpProcess.cpp */ + +#include "xo/reflect/TaggedPtr.hpp" +#include "xo/reflect/StructReflector.hpp" +//#include "time/Time.hpp" +#include "ExpProcess.hpp" + +namespace xo { + using reflect::Reflect; + using reflect::StructReflector; + using reflect::TaggedRcptr; + using xo::scope; + using xo::xtag; + + namespace process { + void + ExpProcess::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + REFLECT_MEMBER(sr, scale); + REFLECT_MEMBER(sr, exponent_process); + } + } /*self_reflect*/ + + /* note: lo is a sample from the exponentiated process; + * must take log to get sample from the exponent process + */ + ExpProcess::value_type + ExpProcess::exterior_sample(utc_nanos t, + event_type const & lo) + { + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + double lo_value = lo.second; + double log_lo_value = ::log(lo.second / this->scale_); + + double e + = (this->exponent_process_->exterior_sample + (t, + event_type(lo.first, log_lo_value))); + + double retval = this->scale_ * ::exp(e); + + log && log("result", + xtag("t", t), + xtag("lo.tm", lo.first), + xtag("lo.value", lo_value), + xtag("log(lo.value/m)", log_lo_value), + xtag("m", this->scale_), + xtag("e", e), + xtag("retval", retval)); + + return retval; + } /*exterior_sample*/ + + TaggedRcptr + ExpProcess::self_tp() + { + return Reflect::make_rctp(this); + } /*self_tp*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end ExpProcess.cpp */ diff --git a/xo-process/src/process/Realization.cpp b/xo-process/src/process/Realization.cpp new file mode 100644 index 00000000..0697fc39 --- /dev/null +++ b/xo-process/src/process/Realization.cpp @@ -0,0 +1,5 @@ +/* Realization.cpp */ + +#include "Realization.hpp" + +/* end Realization.cpp */ diff --git a/xo-process/src/process/UpxEvent.cpp b/xo-process/src/process/UpxEvent.cpp new file mode 100644 index 00000000..5faa97b8 --- /dev/null +++ b/xo-process/src/process/UpxEvent.cpp @@ -0,0 +1,45 @@ +/* @file UpxEvent.cpp */ + +#include "UpxEvent.hpp" +#include "xo/reflect/StructReflector.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/tag.hpp" + +namespace xo { + using xo::reflect::StructReflector; + using xo::tostr; + using xo::xtag; + + namespace process { + UpxEvent::UpxEvent() = default; + + void + UpxEvent::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + //REFLECT_MEMBER(sr, contents); + REFLECT_MEMBER(sr, tm); + REFLECT_MEMBER(sr, upx); + } + } /*reflect_self*/ + + void + UpxEvent::display(std::ostream & os) const + { + os << "tm()) + << xtag("x", this->upx()) + << ">"; + } /*display*/ + + std::string + UpxEvent::display_string() const { + return tostr(*this); + } /*display_string*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end UpxEvent.cpp */ diff --git a/xo-process/src/process/UpxToConsole.cpp b/xo-process/src/process/UpxToConsole.cpp new file mode 100644 index 00000000..b6bf08ef --- /dev/null +++ b/xo-process/src/process/UpxToConsole.cpp @@ -0,0 +1,15 @@ +/* @file UpxToConsole.cpp */ + +#include "UpxToConsole.hpp" + +namespace xo { + namespace process { + rp + UpxToConsole::make() + { + return new UpxToConsole(); + } /*make*/ + + UpxToConsole::UpxToConsole() = default; + } /*namespace process*/ +} /*namespace xo*/ diff --git a/xo-process/src/process/init_process.cpp b/xo-process/src/process/init_process.cpp new file mode 100644 index 00000000..fead726d --- /dev/null +++ b/xo-process/src/process/init_process.cpp @@ -0,0 +1,40 @@ +/* file init_process.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "init_process.hpp" +#include "xo/printjson/init_printjson.hpp" + +#include "UpxEvent.hpp" +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + using xo::process::UpxEvent; + + void + InitSubsys::init() + { + UpxEvent::reflect_self(); + } /*init*/ + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* direct subsystem dependencies for process/ + * + * UpxEventStore --uses-> printjson (via reactor/EventStore.hpp) + */ + retval ^= InitSubsys::require(); + + /* process/'s own initialization code */ + retval ^= Subsystem::provide("process", &init); + + return retval; + } /*require*/ + +} /*namespace xo*/ + +/* end init_process.cpp */ diff --git a/xo-process/utest/CMakeLists.txt b/xo-process/utest/CMakeLists.txt new file mode 100644 index 00000000..a3d5585b --- /dev/null +++ b/xo-process/utest/CMakeLists.txt @@ -0,0 +1,16 @@ +# build unittest 'process/unittest' + +# These tests can use the Catch2-provided main + +set(SELF_EXE utest.process) +set(SELF_SRCS + ProcessReflect.test.cpp + RealizationSource.test.cpp + process_utest_main.cpp) + +xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS}) +xo_self_dependency(${SELF_EXE} process) +xo_dependency(${SELF_EXE} simulator) +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# end CMakeLists.txt diff --git a/xo-process/utest/ProcessReflect.test.cpp b/xo-process/utest/ProcessReflect.test.cpp new file mode 100644 index 00000000..11ac995b --- /dev/null +++ b/xo-process/utest/ProcessReflect.test.cpp @@ -0,0 +1,30 @@ +/* @file ProcessReflect.test.cpp */ + +#include "xo/process/init_process.hpp" +#include "xo/reflect/Reflect.hpp" +#include + +namespace xo { + using xo::reflect::TypeDescrBase; + + namespace ut { + static InitEvidence s_init = (InitSubsys::require()); + + TEST_CASE("process-reflect", "[reflect]") { + Subsystem::initialize_all(); + + char const * c_self = "TEST_CASE:process-reflect"; + constexpr bool c_logging_enabled = true; + + scope log(XO_DEBUG2(c_logging_enabled, c_self)); + + // this ought to work but doesn't (too much output?)... + //log && log(xo::reflect::reflected_types_printer()); + + xo::reflect::TypeDescrBase::print_reflected_types(std::cout); + } /*TEST_CASE(process-reflect)*/ + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end ProcessReflect.test.cpp */ diff --git a/xo-process/utest/RealizationSource.test.cpp b/xo-process/utest/RealizationSource.test.cpp new file mode 100644 index 00000000..ae6aeb0c --- /dev/null +++ b/xo-process/utest/RealizationSource.test.cpp @@ -0,0 +1,260 @@ +/* @file RealizationSource.test.cpp */ + +//#include "time/Time.hpp" +#include "xo/process/RealizationSource.hpp" +#include "xo/process/LogNormalProcess.hpp" +#include "xo/process/BrownianMotion.hpp" +#include "xo/randomgen/xoshiro256.hpp" +#include "xo/simulator/Simulator.hpp" +#include "xo/indentlog/print/printer.hpp" +#include "xo/indentlog/scope.hpp" +#include + +namespace xo { + using xo::sim::Simulator; + using xo::process::RealizationSourceBase; + using xo::process::RealizationSource; + using xo::process::RealizationTracer; + using xo::process::LogNormalProcess; + using xo::process::ExpProcess; + using xo::process::BrownianMotion; + using xo::rng::xoshiro256ss; + using xo::reactor::SinkToConsole; + using xo::time::timeutil; + using xo::time::seconds; + using xo::time::utc_nanos; + //using xo::print::printer; + using xo::scope; + using xo::xtag; + using std::chrono::hours; + using std::chrono::minutes; + + namespace ut { + /* TODO: move this to time/utest/ */ + TEST_CASE("time-formatting", "[time][print]") { + /* TODO: unit test for time conversions */ + + constexpr char const * c_self = "TEST_CASE:time-formatting"; + constexpr bool c_logging_enabled = true; + + utc_nanos t0 = timeutil::ymd_hms_usec(20220610 /*ymd*/, + 162905 /*hms*/, + 123456 /*usec*/); + + std::stringstream ss; + xo::timeutil::print_utc_ymd_hms_usec(t0, ss); + + REQUIRE(ss.str() == "20220610:16:29:05.123456"); + +#ifdef NOT_IN_USE + BrownianMotion bm = BrownianMotion::make(xxx t0, + xxx dev, + xxx seed); +#endif + } /*TEST_CASE(time-formatting)*/ + + /* TODO: move this to simulator/utest/ */ + TEST_CASE("empty-simulation", "[simulation][trivial]") { + constexpr char const * c_self = "TEST_CASE:empty-simulation"; + constexpr bool c_logging_enabled = true; + + /* arbitrary 'starting time' */ + utc_nanos t0 = timeutil::ymd_hms_usec(20220610 /*ymd*/, + 162905 /*hms*/, + 123456 /*usec*/); + + rp sim = Simulator::make(t0); + sim->set_loglevel(log_level::chatty); + + REQUIRE(sim->is_exhausted()); + + utc_nanos t1 = t0 + hours(1); + + sim->run_until(t1); + + REQUIRE((sim->is_exhausted() || (sim->next_tm() > t1))); + } /*TEST_CASE(empty-simulation)*/ + + /* test simulator with a single source */ + TEST_CASE("sim-brownian-motion", "[process][simulation]") { + constexpr char const * c_self = "TEST_CASE:sim-brownian-motion"; + + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG2(c_logging_enabled, c_self)); + + /* arbitrary 'starting time' */ + utc_nanos t0 = timeutil::ymd_hms_usec(20220610 /*ymd*/, + 162905 /*hms*/, + 123456 /*usec*/); + + rp sim = Simulator::make(t0); + sim->set_loglevel(c_logging_enabled + ? log_level::chatty + : log_level::error); + + REQUIRE(sim->is_exhausted()); + + log && log("create brownian motion process 'bm'.."); + + rp> bm + = BrownianMotion::make(t0, + 0.30 /*sdev -- annualized volatility*/, + 12345678UL /*seed*/); + + log && log("..done"); + + + log && log("create realization tracer.."); + + rp> tracer + = RealizationTracer::make(bm); + + log && log("..done"); + + std::vector> sample_v; + + auto sink + = ([&sample_v] + (std::pair const & ev) + { sample_v.push_back(ev); }); + + log && log("create sim source from tracer.."); + + /* what is step dt? */ + rp, double, decltype(sink)>> + sim_source + = RealizationSourceBase, double, decltype(sink)>::make(tracer, + std::chrono::seconds(1) /*ev_interval_dt*/, + sink); + + log && log("..done"); + + log && log("add sim source to simulator.."); + + sim->add_source(sim_source); + + log&& log("..done"); + + utc_nanos t1 = t0 + minutes(1); + + log && log("run sim.."); + + sim->run_until(t1); + + log && log("..done"); + + log && log("verify sample_v.."); + + /* 1-minute simulation with 1-second samples */ + REQUIRE(sample_v.size() == 61); + + utc_nanos sample_t0 = sample_v[0].first; + + for(size_t i = 0; i < sample_v.size(); ++i) { + REQUIRE(sample_v[i].first == t0 + seconds(i)); + } + + log && log("..done"); + + //lscope.log(xtag("sample_v.size", sample_v.size())); + + } /*TEST_CASE("sim-brownian-motion")*/ + + TEST_CASE("sim-brownian-motion-with-sink", "[process][simulation]") { + constexpr char const * c_self = "TEST_CASE:sim-brownian-motion-with-sink"; + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG2(c_logging_enabled, c_self)); + + utc_nanos t0 = timeutil::ymd_hms_usec(20220718 /*ymd*/, + 120000 /*hms*/, + 0 /*usec*/); + + auto bm + = BrownianMotion::make(t0, + 0.50 /*annualized volatility*/, + 65431123UL /*seed*/); + + auto tracer + = RealizationTracer::make(bm); + + auto realization + = RealizationSource, double>::make(tracer, + std::chrono::seconds(1) /*ev_interval_dt*/); + + rp>> sink + = new SinkToConsole>(); + + realization->attach_sink(sink); + } /*TEST_CASE(sim-brownian-motion-with-sink)*/ + + TEST_CASE("sim-lognormal", "[process][simulation]") { + constexpr char const * c_self = "TEST_CASE:sim-lognormal"; + constexpr bool c_logging_enabled = false; + + scope log(XO_LITERAL(log_level::never, c_self, "")); + + /* arbitrary 'starting time' */ + utc_nanos t0 = timeutil::ymd_hms_usec(20220610 /*ymd*/, + 162905 /*hms*/, + 123456 /*usec*/); + + rp sim = Simulator::make(t0); + sim->set_loglevel(c_logging_enabled + ? log_level::chatty + : log_level::error); + + REQUIRE(sim->is_exhausted()); + + rp ebm + (LogNormalProcess::make + (t0, + 1.0 /*x0*/, + 0.30 /*sdev -- annualized volatility*/, + 12345678UL /*seed*/)); + + /* recover the exponentiated process, for testing */ + //StochasticProcess * bm = ebm->exponent_process(); + + rp> tracer + = RealizationTracer::make(ebm.get()); + + /* will be: samples from log-normal brownian motion */ + std::vector> sample_v; + + /* collect process samples as sim runs */ + auto sink + = ([&sample_v] + (std::pair const & ev) + { sample_v.push_back(ev); }); + + rp, double, decltype(sink)>> + sim_source + = RealizationSourceBase, double, decltype(sink)>::make(tracer, + std::chrono::seconds(1) /*ev_interval_dt*/, + sink); + + sim->add_source(sim_source); + + utc_nanos t1 = t0 + minutes(1); + + sim->run_until(t1); + + /* 1-minute simulation with 1-second samples */ + REQUIRE(sample_v.size() == 61); + + utc_nanos sample_t0 = sample_v[0].first; + + for(size_t i = 0; i < sample_v.size(); ++i) { + REQUIRE(sample_v[i].first == t0 + seconds(i)); + /* exponentiated process will have strictly +ve values */ + REQUIRE(sample_v[i].second > 0.0); + } + + log && log(xtag("sample_v.size", sample_v.size())); + } /*TEST_CASE("sim-lognormal")*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end RealizationSource.test.cpp */ diff --git a/xo-process/utest/process_utest_main.cpp b/xo-process/utest/process_utest_main.cpp new file mode 100644 index 00000000..b1338709 --- /dev/null +++ b/xo-process/utest/process_utest_main.cpp @@ -0,0 +1,6 @@ +/* @file process_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end process_utest_main.cpp */