initial implementation
This commit is contained in:
commit
1078c49269
30 changed files with 2131 additions and 0 deletions
52
CMakeLists.txt
Normal file
52
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# xo-process/CMakeLists.txt
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(process VERSION 1.0)
|
||||
enable_language(CXX)
|
||||
|
||||
# common XO cmake macros (see proj/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
|
||||
|
||||
# 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})
|
||||
|
||||
xo_toplevel_compile_options()
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
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)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# install project .hpp files
|
||||
|
||||
xo_install_include_tree()
|
||||
|
||||
# end CMakeLists.txt
|
||||
57
README.md
Normal file
57
README.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# stochastic process library
|
||||
|
||||
constructive, simulation-aware models for stochastic processes
|
||||
|
||||
## Getting Started
|
||||
|
||||
### build + install dependencies
|
||||
|
||||
build+install these first
|
||||
|
||||
- xo-reactor [github.com/Rconybea/xo-reactor](https://github.com/Rconybea/xo-reactor)
|
||||
- randomgen [github.com/Rconybea/randomgen](https://github.com/Rconybea/randomgen)
|
||||
|
||||
# build + install
|
||||
|
||||
## build
|
||||
```
|
||||
$ cd 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
|
||||
```
|
||||
13
cmake/processConfig.cmake.in
Normal file
13
cmake/processConfig.cmake.in
Normal file
|
|
@ -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(reflect)
|
||||
#find_dependency(callback)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
|
||||
check_required_components("@PROJECT_NAME@")
|
||||
20
include/xo/process/AbstractRealization.hpp
Normal file
20
include/xo/process/AbstractRealization.hpp
Normal file
|
|
@ -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 ref::rp<AbstractStochasticProcess> stochastic_process() const = 0;
|
||||
}; /*AbstractRealization*/
|
||||
} /*namespace process*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end AbstractRealization.hpp */
|
||||
19
include/xo/process/AbstractStochasticProcess.hpp
Normal file
19
include/xo/process/AbstractStochasticProcess.hpp
Normal file
|
|
@ -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 */
|
||||
321
include/xo/process/BrownianMotion.hpp
Normal file
321
include/xo/process/BrownianMotion.hpp
Normal file
|
|
@ -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 <memory>
|
||||
#include <chrono>
|
||||
|
||||
namespace xo {
|
||||
namespace process {
|
||||
class BrownianMotionBase : public Realizable2Process<double> {
|
||||
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<double> -----
|
||||
|
||||
virtual ref::rp<Realization2<double>> make_realization() override = 0;
|
||||
|
||||
// ----- inherited from StochasticProcess<double> -----
|
||||
|
||||
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<utc_nanos, double>
|
||||
* value_type: double
|
||||
*/
|
||||
template<class RngEngine>
|
||||
class BrownianMotion : public BrownianMotionBase {
|
||||
public:
|
||||
using self_type = BrownianMotion<RngEngine>;
|
||||
using rstate_type = StochasticProcess<double>::event_type;
|
||||
using TaggedRcptr = reflect::TaggedRcptr;
|
||||
using normalgen_type = xo::rng::normalgen<RngEngine>;
|
||||
using nanos = xo::time::nanos;
|
||||
|
||||
public:
|
||||
/* t0. start time,
|
||||
* sdev. annual sqrt volatility
|
||||
* seed. initialize pseudorandom-number generator
|
||||
*/
|
||||
template<class Seed>
|
||||
static ref::rp<BrownianMotion<RngEngine>> make(utc_nanos t0,
|
||||
double sdev,
|
||||
Seed const & seed)
|
||||
{
|
||||
return new BrownianMotion<RngEngine>(t0, sdev, seed);
|
||||
} /*make*/
|
||||
|
||||
/* reflect BrownianMotion object representation */
|
||||
static void reflect_self() {
|
||||
reflect::StructReflector<BrownianMotion<RngEngine>> 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 ref::rp<Realization2<double>> make_realization() override {
|
||||
rstate_type rs0 = std::make_pair(this->t0(),
|
||||
this->t0_value());
|
||||
|
||||
return new ProcessRealization2<double, rstate_type, self_type>(rs0, this);
|
||||
} /*make_realization*/
|
||||
|
||||
virtual std::unique_ptr<AbstractRealizationState> make_rstate() override {
|
||||
rstate_type rs0 = std::make_pair(this->t0(),
|
||||
this->t0_value());
|
||||
|
||||
return std::unique_ptr<AbstractRealizationState>(new RealizationState<rstate_type>(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_lo<t<t_hi
|
||||
*/
|
||||
virtual value_type interior_sample(utc_nanos t, event_type const &lo,
|
||||
event_type const &hi) override;
|
||||
|
||||
#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(double const &a,
|
||||
event_type const &lo) override;
|
||||
#endif
|
||||
|
||||
/* return human-readable string identifying this process */
|
||||
virtual std::string display_string() const override {
|
||||
return "<BrownianMotion>";
|
||||
}
|
||||
|
||||
// ----- Inherited from SelfTagging -----
|
||||
|
||||
virtual TaggedRcptr self_tp() override { return reflect::Reflect::make_rctp(this); }
|
||||
|
||||
private:
|
||||
template<class Seed>
|
||||
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<RngEngine>::reflect_self();
|
||||
}
|
||||
|
||||
private:
|
||||
/* generates normally-distributed pseudorandom numbers,
|
||||
* distributed according to N(0,1)
|
||||
*/
|
||||
normalgen_type rng_;
|
||||
}; /*BrownianMotion*/
|
||||
|
||||
template<typename RngEngine>
|
||||
double
|
||||
BrownianMotion<RngEngine>::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<typename RngEngine>
|
||||
double
|
||||
BrownianMotion<RngEngine>::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 */
|
||||
103
include/xo/process/ExpProcess.hpp
Normal file
103
include/xo/process/ExpProcess.hpp
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/* @file ExpProcess.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
//#include "time/Time.hpp"
|
||||
#include "StochasticProcess.hpp"
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
|
||||
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<double> {
|
||||
public:
|
||||
using TaggedRcptr = reflect::TaggedRcptr;
|
||||
|
||||
public:
|
||||
static ref::rp<ExpProcess> make(double scale,
|
||||
ref::brw<StochasticProcess<double>> exp_proc) {
|
||||
return new ExpProcess(scale, exp_proc);
|
||||
} /*make*/
|
||||
|
||||
/* reflect ExpProcess object representation */
|
||||
static void reflect_self();
|
||||
|
||||
ref::brw<StochasticProcess<double>> 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("<ExpProcess ", exponent_process_->display_string(), ">");
|
||||
|
||||
return "<ExpProcess>";
|
||||
} /*display_string*/
|
||||
|
||||
// ----- Inherited from SelfTagging -----
|
||||
|
||||
virtual TaggedRcptr self_tp() override;
|
||||
|
||||
private:
|
||||
ExpProcess(double scale, ref::brw<StochasticProcess> 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 */
|
||||
ref::rp<StochasticProcess<double>> exponent_process_;
|
||||
}; /*ExpProcess*/
|
||||
} /*namespace process*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end ExpProcess.hpp */
|
||||
33
include/xo/process/LogNormalProcess.hpp
Normal file
33
include/xo/process/LogNormalProcess.hpp
Normal file
|
|
@ -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<typename RngEngine, typename Seed>
|
||||
static ref::rp<ExpProcess> make(utc_nanos t0, double x0,
|
||||
double sdev, Seed const & seed) {
|
||||
|
||||
ref::rp<BrownianMotion<RngEngine>> bm
|
||||
= BrownianMotion<RngEngine>::make(t0, sdev, seed);
|
||||
|
||||
return ExpProcess::make(x0 /*scale*/, bm);
|
||||
} /*make*/
|
||||
}; /*LogNormalProcess*/
|
||||
|
||||
} /*namespace process*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end LogNormalProcess.hpp */
|
||||
58
include/xo/process/Realizable2Process.hpp
Normal file
58
include/xo/process/Realizable2Process.hpp
Normal file
|
|
@ -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<T, Rstate>
|
||||
* 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<T>
|
||||
* 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<t2,
|
||||
* sample process at time t1, with t0<=t1<=t2
|
||||
*
|
||||
* Require:
|
||||
* - Rstate.last() :: (time x T) last process sample represented by Rstate
|
||||
* if p is a markov process, this is also sufficient to drive
|
||||
* process unfolding
|
||||
* - Rstate.tn() :: time
|
||||
* location in time represented by a particular Rstate (same as .last().first)
|
||||
*/
|
||||
template<typename T>
|
||||
class Realizable2Process : public StochasticProcess<T> {
|
||||
public:
|
||||
virtual ref::rp<Realization2<T>> 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<AbstractRealizationState> 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 */
|
||||
72
include/xo/process/Realization.hpp
Normal file
72
include/xo/process/Realization.hpp
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/* @file Realization.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "StochasticProcess.hpp"
|
||||
//#include "time/Time.hpp"
|
||||
//#include <boost/range.hpp>
|
||||
#include <ranges>
|
||||
#include <map>
|
||||
|
||||
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<typename T>
|
||||
class Realization : public ref::Refcount {
|
||||
public:
|
||||
using utc_nanos = xo::time::utc_nanos;
|
||||
using KnownMap = std::map<utc_nanos, T>;
|
||||
using KnownIterator = typename KnownMap::const_iterator;
|
||||
//using KnownRange = boost::iterator_range<KnownIterator>;
|
||||
using KnownRange = decltype(std::views::all(KnownMap()));
|
||||
|
||||
public:
|
||||
static ref::rp<Realization> make(ref::brw<StochasticProcess<T>> p) {
|
||||
return new Realization(p);
|
||||
} /*make*/
|
||||
|
||||
ref::brw<StochasticProcess<T>> 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<IT>
|
||||
|
||||
private:
|
||||
Realization(ref::brw<StochasticProcess<T>> p) : process_{p} {}
|
||||
|
||||
private:
|
||||
/* stochastic process from which this realization is sampled */
|
||||
ref::rp<StochasticProcess<T>> 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 */
|
||||
91
include/xo/process/Realization2.hpp
Normal file
91
include/xo/process/Realization2.hpp
Normal file
|
|
@ -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<typename T>
|
||||
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<T, Rstate>
|
||||
*/
|
||||
template<typename T, typename Rstate, typename Process>
|
||||
class ProcessRealization2 : public Realization2<T> {
|
||||
public:
|
||||
using TaggedRcptr = reflect::TaggedRcptr;
|
||||
using nanos = xo::time::nanos;
|
||||
|
||||
public:
|
||||
ProcessRealization2(Rstate const & rstate, ref::rp<Process> const & process)
|
||||
: rstate_{rstate}, process_{process} {}
|
||||
ProcessRealization2(Rstate && rstate, ref::rp<Process> const & process)
|
||||
: rstate_{std::move(rstate)}, process_{process} {}
|
||||
|
||||
Rstate const & rstate() const { return rstate_; }
|
||||
ref::rp<Process> 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 ref::rp<AbstractStochasticProcess> 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
|
||||
*/
|
||||
ref::rp<Process> process_;
|
||||
}; /*ProcessRealization2*/
|
||||
} /*namespace process*/
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
|
||||
/* end Realization2.hpp */
|
||||
31
include/xo/process/RealizationCallback.hpp
Normal file
31
include/xo/process/RealizationCallback.hpp
Normal file
|
|
@ -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 <utility>
|
||||
|
||||
namespace xo {
|
||||
namespace process {
|
||||
/* callback for consuming stochastic process realizations */
|
||||
template<typename T>
|
||||
class RealizationCallback : public reactor::Sink1<std::pair<xo::time::utc_nanos, T>> {
|
||||
public:
|
||||
using utc_nanos = xo::time::utc_nanos;
|
||||
|
||||
public:
|
||||
/* notification with process event (std::pair<utc_nanos, T>)
|
||||
* see StochasticProcess<T>::event_type
|
||||
*/
|
||||
virtual void notify_ev(std::pair<utc_nanos, T> 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 */
|
||||
314
include/xo/process/RealizationSource.hpp
Normal file
314
include/xo/process/RealizationSource.hpp
Normal file
|
|
@ -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 <functional>
|
||||
|
||||
namespace xo {
|
||||
namespace process {
|
||||
/* use a discrete realization of a continuous stochastic process,
|
||||
* as a simulation source.
|
||||
*
|
||||
* 1. Realization is developed lazily, (see RealizationTracer<T>)
|
||||
* 2. Use a fixed discretization interval to develop realization
|
||||
* 3. events are consumed by Sink
|
||||
*
|
||||
* Require:
|
||||
* - std::pair<utc_nanos, T> --convertible-to--> EventType
|
||||
* - EventSink.notify_source_exhausted()
|
||||
* - invoke EventSink(x), with x :: EventType
|
||||
*/
|
||||
template <typename EventType, typename T, typename EventSink>
|
||||
class RealizationSourceBase : public xo::reactor::ReactorSource {
|
||||
public:
|
||||
using event_type = typename RealizationTracer<T>::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 ref::rp<RealizationSourceBase>
|
||||
make(ref::rp<RealizationTracer<T>> 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 ref::rp<RealizationSimSource> make(ref::rp<RealizationTracer<T>> 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<RealizationSourceBase *>(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<EventType>();
|
||||
}
|
||||
|
||||
/* 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(ref::rp<reactor::AbstractSink> 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 << "<RealizationSourceBase"
|
||||
<< xtag("name", this->name())
|
||||
<< xtag("n_out_ev", this->n_out_ev())
|
||||
//<< xtag("ev_interval_dt", ev_interval_dt_)
|
||||
<< ">";
|
||||
} /*display*/
|
||||
|
||||
virtual void visit_direct_consumers(std::function<void (ref::brw<xo::reactor::AbstractEventProcessor>)> const &) override {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
protected:
|
||||
RealizationSourceBase(ref::rp<RealizationTracer<T>> 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(ref::rp<RealizationTracer<T>> 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<RealizationSourceBase<EventType, T, EventSink>>();
|
||||
|
||||
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 */
|
||||
ref::rp<RealizationTracer<T>> 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<typename EventType, typename T>
|
||||
class RealizationSource
|
||||
: public RealizationSourceBase<EventType, T,
|
||||
xo::fn::NotifyCallbackSet<reactor::Sink1<EventType>,
|
||||
decltype(&reactor::Sink1<EventType>::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 ref::rp<RealizationSource<EventType, T>> make(ref::rp<RealizationTracer<T>> const & tracer,
|
||||
nanos ev_interval_dt)
|
||||
{
|
||||
return new RealizationSource<EventType, T>(tracer, ev_interval_dt);
|
||||
} /*make*/
|
||||
|
||||
CallbackId add_callback(ref::rp<reactor::Sink1<EventType>> 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(ref::rp<reactor::AbstractSink> 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<std::pair<utc_nanos,T>>
|
||||
* and RealizationSource<T> 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<T>()
|
||||
* 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<T>()));
|
||||
|
||||
ref::rp<reactor::Sink1<EventType>> event_sink
|
||||
= reactor::Sink1<EventType>::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 << "<RealizationSource"
|
||||
<< xtag("name", this->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<void (ref::brw<xo::reactor::AbstractEventProcessor>)> const & fn) override {
|
||||
|
||||
for(auto const & x : *(this->ev_sink_addr()))
|
||||
fn(x.fn_.borrow());
|
||||
} /*visit_direct_consumers*/
|
||||
|
||||
private:
|
||||
RealizationSource(ref::rp<RealizationTracer<T>> const & tracer,
|
||||
nanos ev_interval_dt)
|
||||
: RealizationSourceBase
|
||||
<EventType, T,
|
||||
xo::fn::NotifyCallbackSet<reactor::Sink1<EventType>,
|
||||
decltype(&reactor::Sink1<EventType>::notify_ev)>
|
||||
>(tracer,
|
||||
ev_interval_dt,
|
||||
fn::make_notify_cbset(&reactor::Sink1<EventType>::notify_ev))
|
||||
{}
|
||||
}; /*RealizationSource*/
|
||||
|
||||
} /*namespace process*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end RealizationSource.hpp */
|
||||
42
include/xo/process/RealizationState.hpp
Normal file
42
include/xo/process/RealizationState.hpp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/* file RealizationState.hpp
|
||||
*
|
||||
* author: Roland Conybeare, Nov 2022
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
/* 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<typename Rstate>
|
||||
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 */
|
||||
112
include/xo/process/RealizationTracer.hpp
Normal file
112
include/xo/process/RealizationTracer.hpp
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/* @file RealizationTracer.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "StochasticProcess.hpp"
|
||||
#include "xo/refcnt/Refcounted.hpp"
|
||||
|
||||
namespace xo {
|
||||
namespace process {
|
||||
//template<typename T, typename EventSink> 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<xoshiro256ss> seed;
|
||||
* auto process = LogNormalProcess::make(t0, sdev, seed);
|
||||
* auto tracer = RealizationTracer<double>::make(process.get());
|
||||
*/
|
||||
template <typename T>
|
||||
class RealizationTracer : public ref::Refcount {
|
||||
public:
|
||||
using Process = xo::process::StochasticProcess<T>;
|
||||
using process_type = Process;
|
||||
/* something like std::pair<utc_nanos, T> */
|
||||
using event_type = typename Process::event_type;
|
||||
using utc_nanos = xo::time::utc_nanos;
|
||||
using nanos = xo::time::nanos;
|
||||
|
||||
public:
|
||||
static ref::rp<RealizationTracer> make(ref::rp<Process> 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; }
|
||||
ref::rp<Process> 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<utc_nanos, T> next_dt(nanos dt) {
|
||||
this->advance_dt(dt);
|
||||
|
||||
return this->current_;
|
||||
} /*next_dt*/
|
||||
|
||||
std::pair<utc_nanos, T> 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(ref::rp<Process> 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 */
|
||||
ref::rp<Process> process_;
|
||||
}; /*RealizationTracer*/
|
||||
|
||||
} /*namespace process*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end RealizationTracer.hpp */
|
||||
73
include/xo/process/StochasticProcess.hpp
Normal file
73
include/xo/process/StochasticProcess.hpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/* @file StochasticProcess.hpp */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AbstractStochasticProcess.hpp"
|
||||
//#include "refcnt/Refcounted.hpp"
|
||||
//#include "time/Time.hpp"
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
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<typename T>
|
||||
class StochasticProcess : public AbstractStochasticProcess {
|
||||
public:
|
||||
using value_type = T;
|
||||
using utc_nanos = xo::time::utc_nanos;
|
||||
using event_type = std::pair<utc_nanos, T>;
|
||||
|
||||
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 */
|
||||
54
include/xo/process/UpxEvent.hpp
Normal file
54
include/xo/process/UpxEvent.hpp
Normal file
|
|
@ -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<utc_nanos, double> const & x) : contents_{x} {}
|
||||
UpxEvent(std::pair<utc_nanos, double> 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<utc_nanos, double> 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 */
|
||||
25
include/xo/process/UpxToConsole.hpp
Normal file
25
include/xo/process/UpxToConsole.hpp
Normal file
|
|
@ -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<UpxEvent>.
|
||||
* hoping to workaroudn a typeinfo problem by getting typeinfo for Sink1<UpxEvent>
|
||||
* to appear in the process/ library instead of the process_py/ library.
|
||||
*
|
||||
* See FAQ "dynamic_cast<Foo<T> *> fails unexpectedly for a template class"
|
||||
*/
|
||||
class UpxToConsole : public xo::reactor::SinkToConsole<UpxEvent> {
|
||||
public:
|
||||
UpxToConsole();
|
||||
|
||||
static ref::rp<UpxToConsole> make();
|
||||
}; /*UpxToConsole*/
|
||||
} /*namespace process*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end UpxToConsole.hpp */
|
||||
21
include/xo/process/init_process.hpp
Normal file
21
include/xo/process/init_process.hpp
Normal file
|
|
@ -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<S_process_tag> {
|
||||
static void init();
|
||||
static InitEvidence require();
|
||||
};
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end init_process.hpp */
|
||||
102
src/process/BrownianMotion.cpp
Normal file
102
src/process/BrownianMotion.cpp
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/* @file BrownianMotion.cpp */
|
||||
|
||||
#include "xo/reflect/TaggedPtr.hpp"
|
||||
//#include "time/Time.hpp"
|
||||
#include "BrownianMotion.hpp"
|
||||
#include <cmath>
|
||||
|
||||
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<double>(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<std::chrono::microseconds>(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 */
|
||||
19
src/process/CMakeLists.txt
Normal file
19
src/process/CMakeLists.txt
Normal file
|
|
@ -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_library3(${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} reflect)
|
||||
#xo_dependency(${SELF_LIB} webutil)
|
||||
#xo_dependency(${SELF_LIB} callback)
|
||||
69
src/process/ExpProcess.cpp
Normal file
69
src/process/ExpProcess.cpp
Normal file
|
|
@ -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<ExpProcess> 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 */
|
||||
5
src/process/Realization.cpp
Normal file
5
src/process/Realization.cpp
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
/* Realization.cpp */
|
||||
|
||||
#include "Realization.hpp"
|
||||
|
||||
/* end Realization.cpp */
|
||||
45
src/process/UpxEvent.cpp
Normal file
45
src/process/UpxEvent.cpp
Normal file
|
|
@ -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<UpxEvent> 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 << "<UpxEvent"
|
||||
<< xtag("tm", this->tm())
|
||||
<< xtag("x", this->upx())
|
||||
<< ">";
|
||||
} /*display*/
|
||||
|
||||
std::string
|
||||
UpxEvent::display_string() const {
|
||||
return tostr(*this);
|
||||
} /*display_string*/
|
||||
|
||||
} /*namespace process*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end UpxEvent.cpp */
|
||||
15
src/process/UpxToConsole.cpp
Normal file
15
src/process/UpxToConsole.cpp
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/* @file UpxToConsole.cpp */
|
||||
|
||||
#include "UpxToConsole.hpp"
|
||||
|
||||
namespace xo {
|
||||
namespace process {
|
||||
ref::rp<UpxToConsole>
|
||||
UpxToConsole::make()
|
||||
{
|
||||
return new UpxToConsole();
|
||||
} /*make*/
|
||||
|
||||
UpxToConsole::UpxToConsole() = default;
|
||||
} /*namespace process*/
|
||||
} /*namespace xo*/
|
||||
40
src/process/init_process.cpp
Normal file
40
src/process/init_process.cpp
Normal file
|
|
@ -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<S_process_tag>::init()
|
||||
{
|
||||
UpxEvent::reflect_self();
|
||||
} /*init*/
|
||||
|
||||
InitEvidence
|
||||
InitSubsys<S_process_tag>::require()
|
||||
{
|
||||
InitEvidence retval;
|
||||
|
||||
/* direct subsystem dependencies for process/
|
||||
*
|
||||
* UpxEventStore --uses-> printjson (via reactor/EventStore.hpp)
|
||||
*/
|
||||
retval ^= InitSubsys<S_printjson_tag>::require();
|
||||
|
||||
/* process/'s own initialization code */
|
||||
retval ^= Subsystem::provide<S_process_tag>("process", &init);
|
||||
|
||||
return retval;
|
||||
} /*require*/
|
||||
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end init_process.cpp */
|
||||
28
utest/CMakeLists.txt
Normal file
28
utest/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# 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)
|
||||
|
||||
add_executable(${SELF_EXE} ${SELF_SRCS})
|
||||
xo_include_options2(${SELF_EXE})
|
||||
|
||||
add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE})
|
||||
target_code_coverage(${SELF_EXE} AUTO ALL)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# internal dependencies (on this codebase)
|
||||
|
||||
xo_self_dependency(${SELF_EXE} process)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# external dependencies
|
||||
|
||||
xo_dependency(${SELF_EXE} simulator)
|
||||
xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2)
|
||||
|
||||
# end CMakeLists.txt
|
||||
30
utest/ProcessReflect.test.cpp
Normal file
30
utest/ProcessReflect.test.cpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/* @file ProcessReflect.test.cpp */
|
||||
|
||||
#include "xo/process/init_process.hpp"
|
||||
#include "xo/reflect/Reflect.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
namespace xo {
|
||||
using xo::reflect::TypeDescrBase;
|
||||
|
||||
namespace ut {
|
||||
static InitEvidence s_init = (InitSubsys<S_process_tag>::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 */
|
||||
261
utest/RealizationSource.test.cpp
Normal file
261
utest/RealizationSource.test.cpp
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
/* @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 <catch2/catch.hpp>
|
||||
|
||||
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::ref::rp;
|
||||
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<Simulator> 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<Simulator> 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'..");
|
||||
|
||||
ref::rp<BrownianMotion<xoshiro256ss>> bm
|
||||
= BrownianMotion<xoshiro256ss>::make(t0,
|
||||
0.30 /*sdev -- annualized volatility*/,
|
||||
12345678UL /*seed*/);
|
||||
|
||||
log && log("..done");
|
||||
|
||||
|
||||
log && log("create realization tracer..");
|
||||
|
||||
rp<RealizationTracer<double>> tracer
|
||||
= RealizationTracer<double>::make(bm);
|
||||
|
||||
log && log("..done");
|
||||
|
||||
std::vector<std::pair<utc_nanos,double>> sample_v;
|
||||
|
||||
auto sink
|
||||
= ([&sample_v]
|
||||
(std::pair<utc_nanos,double> const & ev)
|
||||
{ sample_v.push_back(ev); });
|
||||
|
||||
log && log("create sim source from tracer..");
|
||||
|
||||
/* what is step dt? */
|
||||
rp<RealizationSourceBase<std::pair<utc_nanos, double>, double, decltype(sink)>>
|
||||
sim_source
|
||||
= RealizationSourceBase<std::pair<utc_nanos, double>, 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<xoshiro256ss>::make(t0,
|
||||
0.50 /*annualized volatility*/,
|
||||
65431123UL /*seed*/);
|
||||
|
||||
auto tracer
|
||||
= RealizationTracer<double>::make(bm);
|
||||
|
||||
auto realization
|
||||
= RealizationSource<std::pair<utc_nanos, double>, double>::make(tracer,
|
||||
std::chrono::seconds(1) /*ev_interval_dt*/);
|
||||
|
||||
rp<SinkToConsole<std::pair<utc_nanos, double>>> sink
|
||||
= new SinkToConsole<std::pair<utc_nanos, double>>();
|
||||
|
||||
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<Simulator> sim = Simulator::make(t0);
|
||||
sim->set_loglevel(c_logging_enabled
|
||||
? log_level::chatty
|
||||
: log_level::error);
|
||||
|
||||
REQUIRE(sim->is_exhausted());
|
||||
|
||||
rp<ExpProcess> ebm
|
||||
(LogNormalProcess::make<xoshiro256ss, uint64_t>
|
||||
(t0,
|
||||
1.0 /*x0*/,
|
||||
0.30 /*sdev -- annualized volatility*/,
|
||||
12345678UL /*seed*/));
|
||||
|
||||
/* recover the exponentiated process, for testing */
|
||||
//StochasticProcess<double> * bm = ebm->exponent_process();
|
||||
|
||||
rp<RealizationTracer<double>> tracer
|
||||
= RealizationTracer<double>::make(ebm.get());
|
||||
|
||||
/* will be: samples from log-normal brownian motion */
|
||||
std::vector<std::pair<utc_nanos,double>> sample_v;
|
||||
|
||||
/* collect process samples as sim runs */
|
||||
auto sink
|
||||
= ([&sample_v]
|
||||
(std::pair<utc_nanos,double> const & ev)
|
||||
{ sample_v.push_back(ev); });
|
||||
|
||||
rp<RealizationSourceBase<std::pair<utc_nanos, double>, double, decltype(sink)>>
|
||||
sim_source
|
||||
= RealizationSourceBase<std::pair<utc_nanos, double>, 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 */
|
||||
6
utest/process_utest_main.cpp
Normal file
6
utest/process_utest_main.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/* @file process_utest_main.cpp */
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
/* end process_utest_main.cpp */
|
||||
Loading…
Add table
Add a link
Reference in a new issue