Skip to content

Commit be3e7c1

Browse files
author
Alexander Robbins
committed
added multi strat functions
1 parent 40e45ab commit be3e7c1

13 files changed

Lines changed: 421 additions & 9 deletions

include/lfmc/estimator/control_variate_estimator.hpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
#include "lfmc/core/types.hpp"
44
#include "lfmc/estimator/estimator.hpp"
5-
5+
#include <cmath>
6+
#include <limits>
67
#include <expected>
78
#include <string>
89
#include <vector>
@@ -18,6 +19,7 @@ class ControlVariateEstimator : public Estimator {
1819
double sum_y = 0.0; // Sum of control variate values
1920
double sum_xy = 0.0; // Sum of products of payoffs and control variate
2021
double sum_yy = 0.0; // Sum of squares of control variate values
22+
double sum_xx = 0.0;
2123

2224
double control_expectation_ = 0.0; // Expected value of control variate (known analytically)
2325

@@ -29,6 +31,11 @@ class ControlVariateEstimator : public Estimator {
2931
bool converged() const override;
3032
std::expected<double, std::string> result() const override;
3133
std::expected<void, std::string> merge(Estimator const& other) override;
34+
35+
double mean() const noexcept;
36+
double variance() const noexcept;
37+
double std_error() const noexcept;
38+
std::size_t sample_count() const noexcept;
3239
};
3340

3441
} // namespace lfmc

include/lfmc/estimator/monte_carlo_estimator.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,19 @@ namespace lfmc {
1212
class MonteCarloEstimator : public Estimator {
1313
private:
1414
double sum = 0.0;
15+
double sum_sq = 0.0;
1516
std::size_t count = 0;
1617

1718
public:
1819
std::expected<void, std::string> add_payoffs(const std::vector<Payoffs>& payoffs) override;
1920
bool converged() const override;
2021
std::expected<double, std::string> result() const override;
2122
std::expected<void, std::string> merge(Estimator const& other) override;
23+
24+
double mean() const noexcept;
25+
double variance() const noexcept;
26+
double std_error() const noexcept;
27+
std::size_t sample_count() const noexcept;
2228
};
2329

2430
} // namespace lfmc

include/lfmc/path_generator/path_generator.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ class PathGenerator {
2727

2828
std::vector<Path> paths;
2929
for (const auto& norm : normals) {
30-
Path path(steps + 1);
31-
30+
Path path;
31+
path.reserve(steps + 1);
3232
double t = 0.0;
3333
double x = process_.initial();
3434
path.push_back(x);

include/lfmc/pipeline/pipeline.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ template <StochasticProcess SP, NumericalScheme<SP> NS> class Pipeline {
4949

5050
return estimator_->result();
5151
}
52+
53+
const Estimator* get_estimator() const noexcept {
54+
return estimator_.get();
55+
}
56+
57+
Estimator* get_estimator() noexcept {
58+
return estimator_.get();
59+
}
60+
5261
};
5362

5463
} // namespace lfmc
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#pragma once
2+
3+
#include "lfmc/strategy/strategy_metrics.hpp"
4+
#include "lfmc/strategy/strategy_runner.hpp"
5+
6+
#include <algorithm>
7+
#include <memory>
8+
#include <thread>
9+
#include <vector>
10+
11+
namespace lfmc {
12+
13+
template <StochasticProcess SP, NumericalScheme<SP> NS>
14+
class MultiStrategyRunner {
15+
private:
16+
std::vector<std::unique_ptr<StrategyRunner<SP, NS>>> strategies;
17+
std::vector<std::thread> threads;
18+
19+
public:
20+
void add_strategy(std::unique_ptr<StrategyRunner<SP, NS>> strategy) {
21+
strategies.push_back(std::move(strategy));
22+
}
23+
24+
// Run all strategies for a warmup period
25+
std::expected<void, std::string> run_warmup(size_t steps, double T,
26+
size_t warmup_iterations) {
27+
threads.clear();
28+
threads.reserve(strategies.size());
29+
30+
for (auto& strategy : strategies) {
31+
threads.emplace_back([&strategy, steps, T, warmup_iterations]() {
32+
for (size_t i = 0; i < warmup_iterations; ++i) {
33+
auto result = strategy->run_batch(steps, T);
34+
if (!result) {
35+
// Error occurred - need this to get log
36+
return;
37+
}
38+
}
39+
});
40+
}
41+
42+
for (auto& thread : threads) {
43+
if (thread.joinable()) {
44+
thread.join();
45+
}
46+
}
47+
48+
return {};
49+
}
50+
51+
// Get current metricss
52+
std::vector<StrategyMetrics> get_all_metrics() const {
53+
std::vector<StrategyMetrics> metrics;
54+
metrics.reserve(strategies.size());
55+
56+
for (const auto& strategy : strategies) {
57+
metrics.push_back(strategy->get_metrics());
58+
}
59+
60+
return metrics;
61+
}
62+
63+
// Find best strat
64+
size_t best_strategy_index() const {
65+
auto metrics = get_all_metrics();
66+
if (metrics.empty()) {
67+
return 0;
68+
}
69+
return std::min_element(metrics.begin(), metrics.end()) - metrics.begin();
70+
}
71+
72+
size_t strategy_count() const noexcept {
73+
return strategies.size();
74+
}
75+
};
76+
77+
} // namespace lfmc
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#pragma once
2+
3+
#include "lfmc/estimator/control_variate_estimator.hpp"
4+
#include "lfmc/estimator/monte_carlo_estimator.hpp"
5+
#include "lfmc/numerical_scheme/euler_maruyama.hpp"
6+
#include "lfmc/path_generator/path_generator.hpp"
7+
#include "lfmc/payoff/control_variate_payoffs.hpp"
8+
#include "lfmc/payoff/payoff.hpp"
9+
#include "lfmc/pipeline/pipeline.hpp"
10+
#include "lfmc/random_source/antithetic_random_source.hpp"
11+
#include "lfmc/random_source/pseudo_random_source.hpp"
12+
#include "lfmc/strategy/strategy_runner.hpp"
13+
#include "lfmc/stochastic_process/stochastic_process.hpp"
14+
15+
#include <memory>
16+
#include <random>
17+
#include <string>
18+
19+
namespace lfmc {
20+
21+
template <StochasticProcess SP, NumericalScheme<SP> NS>
22+
class StrategyFactory {
23+
public:
24+
static std::unique_ptr<StrategyRunner<SP, NS>>
25+
create_pseudo_random_strategy(SP process, NS scheme,
26+
std::unique_ptr<Payoff> payoff,
27+
unsigned seed = std::random_device{}()) {
28+
auto rs = std::make_unique<PseudoRandomSource>(seed);
29+
auto pg = std::make_unique<PathGenerator<SP, NS>>(process, scheme);
30+
auto est = std::make_unique<MonteCarloEstimator>();
31+
32+
Pipeline<SP, NS> pipeline(std::move(rs), std::move(pg),
33+
std::move(payoff), std::move(est));
34+
35+
return std::make_unique<StrategyRunner<SP, NS>>(
36+
std::move(pipeline), "PseudoRandom");
37+
}
38+
39+
static std::unique_ptr<StrategyRunner<SP, NS>>
40+
create_antithetic_strategy(SP process, NS scheme,
41+
std::unique_ptr<Payoff> payoff,
42+
unsigned seed = std::random_device{}()) {
43+
auto rs = std::make_unique<AntitheticRandomSource>(seed);
44+
auto pg = std::make_unique<PathGenerator<SP, NS>>(process, scheme);
45+
auto est = std::make_unique<MonteCarloEstimator>();
46+
47+
Pipeline<SP, NS> pipeline(std::move(rs), std::move(pg),
48+
std::move(payoff), std::move(est));
49+
50+
return std::make_unique<StrategyRunner<SP, NS>>(
51+
std::move(pipeline), "Antithetic");
52+
}
53+
54+
static std::unique_ptr<StrategyRunner<SP, NS>>
55+
create_control_variate_strategy(SP process, NS scheme,
56+
std::unique_ptr<Payoff> target_payoff,
57+
std::unique_ptr<Payoff> control_payoff,
58+
double control_expectation,
59+
unsigned seed = std::random_device{}()) {
60+
auto rs = std::make_unique<PseudoRandomSource>(seed);
61+
auto pg = std::make_unique<PathGenerator<SP, NS>>(process, scheme);
62+
auto po = std::make_unique<ControlVariatePayoff>(
63+
std::move(target_payoff), std::move(control_payoff));
64+
auto est = std::make_unique<ControlVariateEstimator>(control_expectation);
65+
66+
Pipeline<SP, NS> pipeline(std::move(rs), std::move(pg),
67+
std::move(po), std::move(est));
68+
69+
return std::make_unique<StrategyRunner<SP, NS>>(
70+
std::move(pipeline), "ControlVariate");
71+
}
72+
73+
// Add more factory methods as we make/implement more VR strategies:
74+
// - create_quasi_monte_carlo_strategy()
75+
// - create_importance_sampling_strategy()
76+
// etc.
77+
};
78+
79+
} // namespace lfmc
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
#include <string>
5+
6+
namespace lfmc {
7+
8+
struct StrategyMetrics {
9+
std::string name;
10+
double mean;
11+
double variance;
12+
double std_error;
13+
std::size_t samples;
14+
long long elapsed_ms;
15+
16+
bool operator<(const StrategyMetrics& other) const noexcept {
17+
return variance < other.variance;
18+
}
19+
};
20+
21+
} // namespace lfmc
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#pragma once
2+
3+
#include "lfmc/estimator/estimator.hpp"
4+
#include "lfmc/estimator/monte_carlo_estimator.hpp"
5+
#include "lfmc/estimator/control_variate_estimator.hpp"
6+
#include "lfmc/pipeline/pipeline.hpp"
7+
#include "lfmc/strategy/strategy_metrics.hpp"
8+
#include "lfmc/timing/timing.hpp"
9+
10+
#include <atomic>
11+
#include <memory>
12+
#include <string>
13+
14+
namespace lfmc {
15+
16+
template <StochasticProcess SP, NumericalScheme<SP> NS>
17+
class StrategyRunner {
18+
private:
19+
Pipeline<SP, NS> pipeline;
20+
std::string name;
21+
Timer timer;
22+
23+
std::atomic<double> current_mean{0.0};
24+
std::atomic<double> current_variance{std::numeric_limits<double>::max()};
25+
std::atomic<size_t> samples_processed{0};
26+
27+
public:
28+
StrategyRunner(Pipeline<SP, NS> pipeline, std::string name)
29+
: pipeline(std::move(pipeline)), name(std::move(name)) {}
30+
31+
// Run a single batch of simulations and update metrics I made in the other metrics file
32+
std::expected<void, std::string> run_batch(size_t steps, double T) {
33+
auto result = pipeline.run(steps, T);
34+
if (!result) {
35+
return std::unexpected(result.error());
36+
}
37+
38+
39+
auto* estimator = pipeline.get_estimator();
40+
41+
// had to implement this, it wasn't in the base class, but need it to update the metrics
42+
if (auto* mc_est = dynamic_cast<MonteCarloEstimator*>(estimator)) {
43+
current_mean.store(mc_est->mean(), std::memory_order_relaxed);
44+
current_variance.store(mc_est->variance(), std::memory_order_relaxed);
45+
samples_processed.store(mc_est->sample_count(), std::memory_order_relaxed);
46+
}
47+
// Try ControlVariateEstimator, same as above
48+
else if (auto* cv_est = dynamic_cast<ControlVariateEstimator*>(estimator)) {
49+
current_mean.store(cv_est->mean(), std::memory_order_relaxed);
50+
current_variance.store(cv_est->variance(), std::memory_order_relaxed);
51+
samples_processed.store(cv_est->sample_count(), std::memory_order_relaxed);
52+
}
53+
54+
return {};
55+
}
56+
57+
StrategyMetrics get_metrics() const noexcept {
58+
return StrategyMetrics{
59+
.name = name,
60+
.mean = current_mean.load(std::memory_order_relaxed),
61+
.variance = current_variance.load(std::memory_order_relaxed),
62+
.std_error = std::sqrt(current_variance.load(std::memory_order_relaxed) /
63+
samples_processed.load(std::memory_order_relaxed)),
64+
.samples = samples_processed.load(std::memory_order_relaxed),
65+
.elapsed_ms = timer.elapsedMilliseconds()
66+
};
67+
}
68+
69+
const std::string& get_name() const noexcept { return name; }
70+
};
71+
72+
} // namespace lfmc

src/estimator/control_variate_estimator.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ ControlVariateEstimator::add_payoffs(const std::vector<Payoffs>& payoffs) {
1616
sum_y += y;
1717
sum_xy += x * y;
1818
sum_yy += y * y;
19+
sum_xx += x * x;
1920
++count;
2021
}
2122

@@ -61,8 +62,53 @@ std::expected<void, std::string> ControlVariateEstimator::merge(Estimator const&
6162
sum_y += other_estimator->sum_y;
6263
sum_xy += other_estimator->sum_xy;
6364
sum_yy += other_estimator->sum_yy;
65+
sum_xx += other_estimator->sum_xx;
6466

6567
return {};
6668
}
6769

70+
double ControlVariateEstimator::mean() const noexcept {
71+
if (count == 0) return 0.0;
72+
73+
double n = static_cast<double>(count);
74+
double mean_x = sum_x / n;
75+
double mean_y = sum_y / n;
76+
77+
double cov_xy = (sum_xy / n) - mean_x * mean_y;
78+
double var_y = (sum_yy / n) - mean_y * mean_y;
79+
80+
if (var_y == 0.0) return mean_x;
81+
82+
double beta = cov_xy / var_y;
83+
return mean_x - beta * (mean_y - control_expectation_);
84+
}
85+
86+
double ControlVariateEstimator::variance() const noexcept {
87+
if (count < 2) return std::numeric_limits<double>::max();
88+
89+
double n = static_cast<double>(count);
90+
double mean_x = sum_x / n;
91+
double mean_y = sum_y / n;
92+
93+
double var_x = (sum_xx / n) - mean_x * mean_x;
94+
double var_y = (sum_yy / n) - mean_y * mean_y;
95+
double cov_xy = (sum_xy / n) - mean_x * mean_y;
96+
97+
if (var_y == 0.0) return var_x;
98+
99+
double beta = cov_xy / var_y;
100+
101+
// Var(X - β(Y - E[Y])) = Var(X) + β²Var(Y) - 2βCov(X,Y)
102+
return var_x + beta * beta * var_y - 2.0 * beta * cov_xy;
103+
}
104+
105+
double ControlVariateEstimator::std_error() const noexcept {
106+
if (count < 2) return std::numeric_limits<double>::max();
107+
return std::sqrt(variance() / static_cast<double>(count));
108+
}
109+
110+
std::size_t ControlVariateEstimator::sample_count() const noexcept {
111+
return count;
112+
}
113+
68114
} // namespace lfmc

0 commit comments

Comments
 (0)