ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
RandomVariable.cpp
Go to the documentation of this file.
1/**
2 * @file RandomVariable.cpp
3 * @brief Implementation of distribution parsing/formatting/moments.
4 */
5#include "RandomVariable.h"
6
7#include <cctype>
8#include <cmath>
9#include <cstddef>
10#include <exception>
11#include <string>
12
13#include "Circuit.h" // CircuitException
14
15namespace provsql {
16
17double parseDoubleStrict(const std::string &s)
18{
19 if (s.empty())
20 throw CircuitException("Empty gate_value extra");
21 std::size_t idx = 0;
22 double v;
23 try {
24 v = std::stod(s, &idx);
25 } catch (const std::exception &) {
26 throw CircuitException("Cannot parse gate_value extra as double: " + s);
27 }
28 if (idx != s.size())
29 throw CircuitException("Trailing characters in gate_value extra: " + s);
30 return v;
31}
32
33namespace {
34
35void strip(std::string &s)
36{
37 while (!s.empty() && std::isspace(static_cast<unsigned char>(s.front())))
38 s.erase(s.begin());
39 while (!s.empty() && std::isspace(static_cast<unsigned char>(s.back())))
40 s.pop_back();
41}
42
43bool parse_double(const std::string &raw, double &out)
44{
45 std::string s = raw;
46 strip(s);
47 if (s.empty()) return false;
48 try {
49 std::size_t idx = 0;
50 out = std::stod(s, &idx);
51 return idx == s.size();
52 } catch (const std::exception &) {
53 return false;
54 }
55}
56
57} // namespace
58
59std::optional<DistributionSpec> parse_distribution_spec(const std::string &s)
60{
61 const auto colon = s.find(':');
62 if (colon == std::string::npos) return std::nullopt;
63
64 std::string kind_str = s.substr(0, colon);
65 std::string params = s.substr(colon + 1);
66 strip(kind_str);
67 strip(params);
68
69 DistributionSpec out{};
70 if (kind_str == "normal" || kind_str == "uniform") {
71 const auto comma = params.find(',');
72 if (comma == std::string::npos) return std::nullopt;
73 if (!parse_double(params.substr(0, comma), out.p1)) return std::nullopt;
74 if (!parse_double(params.substr(comma + 1), out.p2)) return std::nullopt;
75 out.kind = (kind_str == "normal") ? DistKind::Normal : DistKind::Uniform;
76 return out;
77 }
78 if (kind_str == "exponential") {
79 if (!parse_double(params, out.p1)) return std::nullopt;
80 out.p2 = 0.0;
82 return out;
83 }
84 if (kind_str == "erlang") {
85 const auto comma = params.find(',');
86 if (comma == std::string::npos) return std::nullopt;
87 if (!parse_double(params.substr(0, comma), out.p1)) return std::nullopt;
88 if (!parse_double(params.substr(comma + 1), out.p2)) return std::nullopt;
90 return out;
91 }
92 return std::nullopt;
93}
94
96{
97 switch (d.kind) {
98 case DistKind::Normal: return d.p1;
99 case DistKind::Uniform: return 0.5 * (d.p1 + d.p2);
100 case DistKind::Exponential: return 1.0 / d.p1;
101 case DistKind::Erlang: return d.p1 / d.p2;
102 }
103 return 0.0;
104}
105
107{
108 switch (d.kind) {
109 case DistKind::Normal: {
110 const double sigma = d.p2;
111 return sigma * sigma;
112 }
113 case DistKind::Uniform: {
114 const double w = d.p2 - d.p1;
115 return (w * w) / 12.0;
116 }
118 const double lambda = d.p1;
119 return 1.0 / (lambda * lambda);
120 }
121 case DistKind::Erlang: {
122 const double k = d.p1, lambda = d.p2;
123 return k / (lambda * lambda);
124 }
125 }
126 return 0.0;
127}
128
129namespace {
130
131double factorial(unsigned k)
132{
133 double r = 1.0;
134 for (unsigned i = 2; i <= k; ++i) r *= static_cast<double>(i);
135 return r;
136}
137
138double binomial_coeff(unsigned n, unsigned k)
139{
140 if (k > n) return 0.0;
141 if (k > n - k) k = n - k;
142 double r = 1.0;
143 for (unsigned i = 1; i <= k; ++i) {
144 r *= static_cast<double>(n - i + 1);
145 r /= static_cast<double>(i);
146 }
147 return r;
148}
149
150// (j-1)!! with the empty-product convention (-1)!! = 1.
151// j = 0 -> 1
152// j = 2 -> 1!! = 1
153// j = 4 -> 3!! = 3
154// j = 6 -> 5!! = 15
155double double_factorial_minus_one(unsigned j)
156{
157 if (j == 0) return 1.0;
158 double r = 1.0;
159 for (unsigned i = 1; i < j; i += 2) r *= static_cast<double>(i);
160 return r;
161}
162
163} // namespace
164
165double analytical_raw_moment(const DistributionSpec &d, unsigned k)
166{
167 if (k == 0) return 1.0;
168 if (k == 1) return analytical_mean(d);
169 switch (d.kind) {
170 case DistKind::Normal: {
171 const double mu = d.p1;
172 const double sigma = d.p2;
173 // E[X^k] = sum_{j=0,2,...}^{k} C(k,j) mu^(k-j) sigma^j (j-1)!!
174 double total = 0.0;
175 for (unsigned j = 0; j <= k; j += 2) {
176 total += binomial_coeff(k, j)
177 * std::pow(mu, static_cast<double>(k - j))
178 * std::pow(sigma, static_cast<double>(j))
179 * double_factorial_minus_one(j);
180 }
181 return total;
182 }
183 case DistKind::Uniform: {
184 const double a = d.p1;
185 const double b = d.p2;
186 const double kp1 = static_cast<double>(k + 1);
187 return (std::pow(b, kp1) - std::pow(a, kp1)) / (kp1 * (b - a));
188 }
190 const double lambda = d.p1;
191 return factorial(k) / std::pow(lambda, static_cast<double>(k));
192 }
193 case DistKind::Erlang: {
194 /* E[X^k] = Gamma(s + k) / (Gamma(s) lambda^k)
195 * = s (s+1) ... (s+k-1) / lambda^k
196 * for integer shape s ≥ 1; the rising factorial is built as a
197 * plain double loop to keep the routine free of <cmath>'s tgamma
198 * (which is fine but unnecessary here since s is integer). */
199 const double s = d.p1, lambda = d.p2;
200 double rising = 1.0;
201 for (unsigned i = 0; i < k; ++i) rising *= (s + static_cast<double>(i));
202 return rising / std::pow(lambda, static_cast<double>(k));
203 }
204 }
205 return 0.0;
206}
207
208} // namespace provsql
Generic directed-acyclic-graph circuit template and gate identifier.
Continuous random-variable helpers (distribution parsing, moments).
Exception type thrown by circuit operations on invalid input.
Definition Circuit.h:206
@ Normal
Normal (Gaussian): p1=μ, p2=σ
@ Exponential
Exponential: p1=λ, p2 unused.
@ Uniform
Uniform on [a,b]: p1=a, p2=b.
@ Erlang
Erlang: p1=k (positive integer), p2=λ.
double analytical_variance(const DistributionSpec &d)
Closed-form variance Var(X) for a basic distribution.
double parseDoubleStrict(const std::string &s)
Strictly parse s as a double.
std::optional< DistributionSpec > parse_distribution_spec(const std::string &s)
Parse the on-disk text encoding of a gate_rv distribution.
double analytical_mean(const DistributionSpec &d)
Closed-form expectation E[X] for a basic distribution.
double analytical_raw_moment(const DistributionSpec &d, unsigned k)
Closed-form raw moment for a basic distribution.
Parsed distribution spec (kind + up to two parameters).
double p2
Second parameter (σ or b; unused for Exponential).
double p1
First parameter (μ, a, or λ).