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) {
99 return "normal:" + std::to_string(d.p1) + "," + std::to_string(d.p2);
101 return "uniform:" + std::to_string(d.p1) + "," + std::to_string(d.p2);
103 return "exponential:" + std::to_string(d.p1);
104 case DistKind::Erlang:
105 /* k is integer-valued; print as integer so the textual form
106 * matches what the SQL constructor writes. */
107 return "erlang:" + std::to_string(static_cast<long long>(d.p1))
108 + "," + std::to_string(d.p2);
109 }
110 return {};
111}
112
114{
115 switch (d.kind) {
116 case DistKind::Normal: return d.p1;
117 case DistKind::Uniform: return 0.5 * (d.p1 + d.p2);
118 case DistKind::Exponential: return 1.0 / d.p1;
119 case DistKind::Erlang: return d.p1 / d.p2;
120 }
121 return 0.0;
122}
123
125{
126 switch (d.kind) {
127 case DistKind::Normal: {
128 const double sigma = d.p2;
129 return sigma * sigma;
130 }
131 case DistKind::Uniform: {
132 const double w = d.p2 - d.p1;
133 return (w * w) / 12.0;
134 }
136 const double lambda = d.p1;
137 return 1.0 / (lambda * lambda);
138 }
139 case DistKind::Erlang: {
140 const double k = d.p1, lambda = d.p2;
141 return k / (lambda * lambda);
142 }
143 }
144 return 0.0;
145}
146
147namespace {
148
149double factorial(unsigned k)
150{
151 double r = 1.0;
152 for (unsigned i = 2; i <= k; ++i) r *= static_cast<double>(i);
153 return r;
154}
155
156double binomial_coeff(unsigned n, unsigned k)
157{
158 if (k > n) return 0.0;
159 if (k > n - k) k = n - k;
160 double r = 1.0;
161 for (unsigned i = 1; i <= k; ++i) {
162 r *= static_cast<double>(n - i + 1);
163 r /= static_cast<double>(i);
164 }
165 return r;
166}
167
168// (j-1)!! with the empty-product convention (-1)!! = 1.
169// j = 0 -> 1
170// j = 2 -> 1!! = 1
171// j = 4 -> 3!! = 3
172// j = 6 -> 5!! = 15
173double double_factorial_minus_one(unsigned j)
174{
175 if (j == 0) return 1.0;
176 double r = 1.0;
177 for (unsigned i = 1; i < j; i += 2) r *= static_cast<double>(i);
178 return r;
179}
180
181} // namespace
182
183double analytical_raw_moment(const DistributionSpec &d, unsigned k)
184{
185 if (k == 0) return 1.0;
186 if (k == 1) return analytical_mean(d);
187 switch (d.kind) {
188 case DistKind::Normal: {
189 const double mu = d.p1;
190 const double sigma = d.p2;
191 // E[X^k] = sum_{j=0,2,...}^{k} C(k,j) mu^(k-j) sigma^j (j-1)!!
192 double total = 0.0;
193 for (unsigned j = 0; j <= k; j += 2) {
194 total += binomial_coeff(k, j)
195 * std::pow(mu, static_cast<double>(k - j))
196 * std::pow(sigma, static_cast<double>(j))
197 * double_factorial_minus_one(j);
198 }
199 return total;
200 }
201 case DistKind::Uniform: {
202 const double a = d.p1;
203 const double b = d.p2;
204 const double kp1 = static_cast<double>(k + 1);
205 return (std::pow(b, kp1) - std::pow(a, kp1)) / (kp1 * (b - a));
206 }
208 const double lambda = d.p1;
209 return factorial(k) / std::pow(lambda, static_cast<double>(k));
210 }
211 case DistKind::Erlang: {
212 /* E[X^k] = Gamma(s + k) / (Gamma(s) lambda^k)
213 * = s (s+1) ... (s+k-1) / lambda^k
214 * for integer shape s ≥ 1; the rising factorial is built as a
215 * plain double loop to keep the routine free of <cmath>'s tgamma
216 * (which is fine but unnecessary here since s is integer). */
217 const double s = d.p1, lambda = d.p2;
218 double rising = 1.0;
219 for (unsigned i = 0; i < k; ++i) rising *= (s + static_cast<double>(i));
220 return rising / std::pow(lambda, static_cast<double>(k));
221 }
222 }
223 return 0.0;
224}
225
226} // 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.
std::string format_distribution_spec(const DistributionSpec &d)
Format a spec back into its on-disk text encoding.
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 λ).