initial implementation

This commit is contained in:
Roland Conybeare 2023-10-18 12:06:07 -04:00
commit 1078c49269
30 changed files with 2131 additions and 0 deletions

52
CMakeLists.txt Normal file
View 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
View 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
```

View 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@")

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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)

View 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 */

View file

@ -0,0 +1,5 @@
/* Realization.cpp */
#include "Realization.hpp"
/* end Realization.cpp */

45
src/process/UpxEvent.cpp Normal file
View 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 */

View 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*/

View 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
View 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

View 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 */

View 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 */

View file

@ -0,0 +1,6 @@
/* @file process_utest_main.cpp */
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
/* end process_utest_main.cpp */