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 <array>
8#include <cctype>
9#include <charconv>
10#include <cmath>
11#include <cstddef>
12#include <exception>
13#include <iomanip>
14#include <sstream>
15#include <string>
16#include <system_error>
17
18#include "Circuit.h" // CircuitException
19#include "distributions/Distribution.h" // makeDistribution (per-family closed forms)
20
21namespace provsql {
22
23double parseDoubleStrict(const std::string &s)
24{
25 if (s.empty())
26 throw CircuitException("Empty gate_value extra");
27 std::size_t idx = 0;
28 double v;
29 try {
30 v = std::stod(s, &idx);
31 } catch (const std::exception &) {
32 throw CircuitException("Cannot parse gate_value extra as double: " + s);
33 }
34 if (idx != s.size())
35 throw CircuitException("Trailing characters in gate_value extra: " + s);
36 return v;
37}
38
39/* std::ostringstream is used rather than std::snprintf in the fallback
40 * because including <cstdio> after PostgreSQL's port.h would expand
41 * std::snprintf to the non-existent std::pg_snprintf via the
42 * #define snprintf macro. */
43std::string double_to_text(double v)
44{
45 std::array<char, 32> buf;
46 auto [ptr, ec] = std::to_chars(buf.data(), buf.data() + buf.size(), v);
47 if (ec == std::errc{}) return std::string(buf.data(), ptr);
48 std::ostringstream oss;
49 oss << std::setprecision(17) << v;
50 return oss.str();
51}
52
53namespace {
54
55void strip(std::string &s)
56{
57 while (!s.empty() && std::isspace(static_cast<unsigned char>(s.front())))
58 s.erase(s.begin());
59 while (!s.empty() && std::isspace(static_cast<unsigned char>(s.back())))
60 s.pop_back();
61}
62
63bool parse_double(const std::string &raw, double &out)
64{
65 std::string s = raw;
66 strip(s);
67 if (s.empty()) return false;
68 try {
69 std::size_t idx = 0;
70 out = std::stod(s, &idx);
71 return idx == s.size();
72 } catch (const std::exception &) {
73 return false;
74 }
75}
76
77/* Parse one parameter slot: a decimal literal or a wire reference "$i".
78 * A wired slot stores its 0-based wire index in wire_slot (literal
79 * unused); a literal slot keeps literal and leaves wire_slot < 0. */
80bool parse_param(const std::string &raw, DistributionParam &out)
81{
82 std::string s = raw;
83 strip(s);
84 if (s.empty()) return false;
85 if (s.front() == '$') {
86 std::string idx_str = s.substr(1);
87 strip(idx_str);
88 if (idx_str.empty()) return false;
89 try {
90 std::size_t idx = 0;
91 const long slot = std::stol(idx_str, &idx);
92 if (idx != idx_str.size() || slot < 0) return false;
93 out.literal = 0.0;
94 out.wire_slot = static_cast<int>(slot);
95 return true;
96 } catch (const std::exception &) {
97 return false;
98 }
99 }
100 if (!parse_double(s, out.literal)) return false;
101 out.wire_slot = -1;
102 return true;
103}
104
105} // namespace
106
107std::optional<DistributionTemplate>
108parse_distribution_template(const std::string &s)
109{
110 const auto colon = s.find(':');
111 if (colon == std::string::npos) return std::nullopt;
112
113 std::string kind_str = s.substr(0, colon);
114 std::string params = s.substr(colon + 1);
115 strip(kind_str);
116 strip(params);
117
118 /* Resolve the family name through the DistributionRegistry so a new
119 * family's token is recognised without touching this parser. */
120 const DistributionFamily *family = lookupDistributionFamily(kind_str);
121 if (!family) return std::nullopt;
122
124 out.family = family;
125 if (family->nparams == 2) {
126 const auto comma = params.find(',');
127 if (comma == std::string::npos) return std::nullopt;
128 if (!parse_param(params.substr(0, comma), out.p1)) return std::nullopt;
129 if (!parse_param(params.substr(comma + 1), out.p2)) return std::nullopt;
130 } else {
131 if (!parse_param(params, out.p1)) return std::nullopt;
132 out.p2 = DistributionParam{0.0, -1};
133 }
134 return out;
135}
136
137std::optional<DistributionSpec> parse_distribution_spec(const std::string &s)
138{
139 /* The resolved (all-literal) form: parse as a template, then require
140 * every parameter be a literal. A parametric (wired) leaf has no
141 * constant-parameter closed form, so it deliberately declines here and
142 * every analytic call site falls through to the Monte Carlo path. */
143 auto tmpl = parse_distribution_template(s);
144 if (!tmpl || tmpl->parametric()) return std::nullopt;
145 DistributionSpec out{};
146 out.family = tmpl->family;
147 out.p1 = tmpl->p1.literal;
148 out.p2 = tmpl->p2.literal;
149 return out;
150}
151
152/* analytical_mean / analytical_variance / analytical_raw_moment are thin
153 * wrappers over the per-family Distribution closed forms (src/Distribution.*).
154 * The family-specific formulas live in the Distribution subclasses; these
155 * free functions stay as the stable call surface for existing consumers. */
157{
158 return makeDistribution(d)->mean();
159}
160
162{
163 return makeDistribution(d)->variance();
164}
165
166double analytical_raw_moment(const DistributionSpec &d, unsigned k)
167{
168 return makeDistribution(d)->rawMoment(k);
169}
170
171} // namespace provsql
Generic directed-acyclic-graph circuit template and gate identifier.
Per-family polymorphic view over a continuous gate_rv distribution (§F.1 class hierarchy).
Continuous random-variable helpers (distribution parsing, moments).
Exception type thrown by circuit operations on invalid input.
Definition Circuit.h:206
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::unique_ptr< Distribution > makeDistribution(const DistributionSpec &spec)
Construct the per-family Distribution for a parsed spec.
std::optional< DistributionSpec > parse_distribution_spec(const std::string &s)
Parse the on-disk text encoding of a gate_rv distribution.
std::optional< DistributionTemplate > parse_distribution_template(const std::string &s)
Parse the on-disk text encoding of a gate_rv distribution, keeping wired (token) parameters as wire r...
double analytical_mean(const DistributionSpec &d)
Closed-form expectation E[X] for a basic distribution.
const DistributionFamily * lookupDistributionFamily(const std::string &name)
Look up a family by its on-disk name token.
double analytical_raw_moment(const DistributionSpec &d, unsigned k)
Closed-form raw moment for a basic distribution.
std::string double_to_text(double v)
Format a double back into the canonical text form used by gate_value extras and gate_rv distribution ...
A registered family's descriptor: its complete identity.
unsigned nparams
1 or 2 (a 1-parameter family leaves p2 = 0)
One parameter slot of a gate_rv, either a literal or a wire.
Parsed distribution spec (family + up to two parameters).
const DistributionFamily * family
double p2
Second parameter (σ, b, or λ; unused for 1-parameter families).
double p1
First parameter (μ, a, k, or λ).
A gate_rv distribution spec that may carry wired (token) parameters – the parse-time counterpart of D...
const DistributionFamily * family