diff --git a/python/bindings.cpp b/python/bindings.cpp index 7b7b1b740aecbe37d12bc254b77ce63a3a26e976..46092643ba79a95d3488635ed1620b9e94aa7ec9 100644 --- a/python/bindings.cpp +++ b/python/bindings.cpp @@ -50,6 +50,7 @@ PYBIND11_MODULE(pygin, m) { .def("filterLeq", &Dist::filterLeq) .def("filterGreater", &Dist::filterGreater) .def("filterGeq", &Dist::filterGeq) + .def("coefficient_iterator", &Dist::coefficientIterator) .def(py::self == py::self) .def(py::self != py::self) //.def(string() * py::self) @@ -71,6 +72,10 @@ PYBIND11_MODULE(pygin, m) { .value("true", troolean::TRUE) .value("unknown", troolean::UNKNOWN); + py::class_<Dist::CoefficientIterator>(m, "CoefficientIterator") + .def("next", &Dist::CoefficientIterator::next) + .def("rest", &Dist::CoefficientIterator::rest); + #ifdef VERSION_INFO m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO); diff --git a/python/test.py b/python/test.py index 3aec377a6a64a801e30a48e4b2ff8319c4232b9c..2392a9ddbb99ac59b7b3cfa7781d858036eaa82f 100644 --- a/python/test.py +++ b/python/test.py @@ -25,3 +25,7 @@ if __name__ == "__main__": check_if_zero(dist) check_if_zero(pg.Dist("0")) + + it = dist.coefficient_iterator(var) + print(f"first terms of dist are {it.next()}, {it.next()}, and {it.next()}") + print(f"remaining terms not consumed by iterator: {it.rest()}") diff --git a/src/Dist.cpp b/src/Dist.cpp index 964a7a6042bc5b5f107175f01e259b001047202d..e9fc9f3c7cc163418d33900dbcf3c15178a4d7a7 100644 --- a/src/Dist.cpp +++ b/src/Dist.cpp @@ -370,6 +370,7 @@ namespace prodigy { EXPECTS(eExp.info(info_flags::nonnegint), "given expression must be non-neg int"); int order = ex_to<numeric>(eExp).to_int(); ex xExp; + // todo why not use parseWithKnownSymbols? if(!isKnownAsVar(x)) { xExp = symbol {x}; Dist::registerAsVar(ex_to<symbol>(xExp)); @@ -380,6 +381,18 @@ namespace prodigy { return Dist{series_to_poly(series)}; } + Dist Dist::CoefficientIterator::next() { + ex resGf = _rest.subs(_var == 0); + _rest = (_rest - resGf) * pow(_var, -1); + _rest = _rest.normal(); // for canceling common factors + return Dist(resGf); + } + + Dist::CoefficientIterator Dist::coefficientIterator(const std::string &var) const { + EXPECTS(isKnownAsVar(var), "can only expand series in known variables"); + return CoefficientIterator(*this, ex_to<symbol>(_vars.at(var))); + } + Dist Dist::substituteParam(const std::string &p, const std::string &val) const { EXPECTS(!isKnownAsVar(p), "given symbol is considered a variable"); if (!isKnownAsParam(p)) { diff --git a/src/Dist.h b/src/Dist.h index 79f12f890b6e8d8366f7b081e4ae801c1d417e15..7ac5bfb5aac55e13236532fa16d5b479f3dd121f 100644 --- a/src/Dist.h +++ b/src/Dist.h @@ -132,11 +132,25 @@ namespace prodigy { const ex& gf() const; + /* + * returns the specified raw moment (possibly non-numeric due to presence of parameters) + * if *this is a distribution, then the raw moment is the expected value of the given monomial + * this function does not modify the global state (//todo why?) + */ + ex moment(const std::map<std::string, int>& monomial) const; + /* * returns total probability mass, possibly non-numeric due to params + * // todo implement in terms of moment */ ex mass() const; + /* + * returns expected value, possibly non-numeric due to params + * // todo implement in terms of moment + */ + ex E(const std::string& var) const; + /* * approximate size of underlying gf representation */ @@ -180,10 +194,9 @@ namespace prodigy { troolean areIndependent(const std::string& x, const std::string& y) const; /* - * returns expected value, possibly non-numeric due to params - * does not modify global state + * attempts to prove that *this is finite-support, i.e., just finitely many coefficients are non-zero */ - ex E(const std::string& var) const; + troolean isFiniteSupport() const; /* * update distribution according to assignment x := e @@ -217,6 +230,29 @@ namespace prodigy { Dist filterEq(const std::string& x, const std::string& e) const { return *this - filterLess(x, e) - filterGreater(x, e); } Dist filterNeq(const std::string& x, const std::string& e) const { return *this - filterEq(x, e); } + /* + * this iterator regards a k-dimensional distribution as 1-dimensional in the given variable with coefficients + * that are k-1 dimensional, and it iterates of these coefficients + * + * in other words, the iterator ranges over the coefficients in the series expansion wrt. the given of the gf + * underlying the given distribution + */ + class CoefficientIterator { + private: + ex _rest; + symbol _var; + public: + CoefficientIterator(Dist dist, symbol var) : _rest(std::move(dist._gf)), _var(std::move(var)) {} + Dist next(); + Dist rest() const { return Dist(_rest); } + }; + + /* + * returns a coefficient iterator for the dimension specified by var + * throws an exception if var is not registered as a variable + */ + CoefficientIterator coefficientIterator(const std::string& var) const; + /* * replaces given parameter with given value or parametric expression */ diff --git a/test/TestDist.cpp b/test/TestDist.cpp index 13babff0f33d727e77d2837d11dfa3c349637feb..09379b0d5787c3c8e83601ce51126eb3e5be9c49 100644 --- a/test/TestDist.cpp +++ b/test/TestDist.cpp @@ -69,3 +69,10 @@ TEST(Dist, gfSize) { ASSERT_EQ(Dist("x").gfSize(), 1); ASSERT_EQ(Dist("x+y").gfSize(), 3); } + +TEST(Dist, coefficientIterator) { + Dist::resetSymbolCache(); + Dist dist = Fac::geometric("x", "q"); + Dist::CoefficientIterator it = dist.coefficientIterator("x"); + ASSERT_EQ(it.next() + it.next() + it.next(), Dist("1-q - q^2 + q + q^2 - q^3", "q")); +} \ No newline at end of file