diff --git a/CMakeLists.txt b/CMakeLists.txt index aa4d627a29caef0aaabd661c585642a36eea3915..2d1a7620816952cd634be65861202b8eef4a48ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,6 +186,7 @@ endif() add_subdirectory(mechanisms) add_subdirectory(src) +add_subdirectory(proto/symge) add_subdirectory(tests) add_subdirectory(miniapp) diff --git a/proto/symge/CMakeLists.txt b/proto/symge/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..0762039e2fa915bc4692caf0a13de3463adf3fa9 --- /dev/null +++ b/proto/symge/CMakeLists.txt @@ -0,0 +1,7 @@ +add_executable(symge-demo symge-demo.cpp) + +set_target_properties(symge-demo + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/proto" +) + diff --git a/proto/symge/msparse.hpp b/proto/symge/msparse.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d1752381f9a4c1f134621600092da40b9270313e --- /dev/null +++ b/proto/symge/msparse.hpp @@ -0,0 +1,207 @@ +#pragma once + +#include <algorithm> +#include <utility> +#include <initializer_list> +#include <iterator> +#include <vector> + +#include <util/compat.hpp> +#include <util/iterutil.hpp> +#include <util/rangeutil.hpp> + +namespace msparse { + +namespace util = nest::mc::util; + +struct msparse_error: std::runtime_error { + msparse_error(const std::string &what): std::runtime_error(what) {} +}; + +constexpr unsigned row_npos = unsigned(-1); + +template <typename X> +class row { +public: + struct entry { + unsigned col; + X value; + }; + static constexpr unsigned npos = row_npos; + +private: + // entries must have strictly monotonically increasing col numbers. + std::vector<entry> data_; + + bool check_invariant() const { + for (unsigned i = 1; i<data_.size(); ++i) { + if (data_[i].col<=data_[i-1].col) return false; + } + return true; + } + +public: + row() = default; + row(const row&) = default; + + row(std::initializer_list<entry> il): data_(il) { + if (!check_invariant()) + throw msparse_error("improper row element list"); + } + + template <typename InIter> + row(InIter b, InIter e): data_(b, e) { + if (!check_invariant()) + throw msparse_error("improper row element list"); + } + + unsigned size() const { return data_.size(); } + bool empty() const { return size()==0; } + + auto begin() -> decltype(data_.begin()) { return data_.begin(); } + auto begin() const -> decltype(data_.cbegin()) { return data_.cbegin(); } + auto end() -> decltype(data_.end()) { return data_.end(); } + auto end() const -> decltype(data_.cend()) { return data_.cend(); } + + unsigned mincol() const { + return empty()? npos: data_.front().col; + } + + unsigned mincol_after(unsigned c) const { + auto i = std::upper_bound(data_.begin(), data_.end(), c, + [](unsigned a, const entry& b) { return a<b.col; }); + + return i==data_.end()? npos: i->col; + } + + unsigned maxcol() const { + return empty()? npos: data_.back().col; + } + + const entry& get(unsigned i) const { + return data_[i]; + } + + void push_back(const entry& e) { + if (!empty() && e.col <= data_.back().col) + throw msparse_error("cannot push_back row elements out of order"); + data_.push_back(e); + } + + unsigned index(unsigned c) const { + auto i = std::lower_bound(data_.begin(), data_.end(), c, + [](const entry& a, unsigned b) { return a.col<b; }); + + return (i==data_.end() || i->col!=c)? npos: std::distance(data_.begin(), i); + } + + // remove all entries from column c onwards + void truncate(unsigned c) { + auto i = std::lower_bound(data_.begin(), data_.end(), c, + [](const entry& a, unsigned b) { return a.col<b; }); + data_.erase(i, data_.end()); + } + + X operator[](unsigned c) const { + auto i = index(c); + return i==npos? X{}: data_[i].value; + } + + struct assign_proxy { + row<X>& row_; + unsigned c; + + assign_proxy(row<X>& r, unsigned c): row_(r), c(c) {} + + operator X() const { return const_cast<const row<X>&>(row_)[c]; } + assign_proxy& operator=(const X& x) { + auto i = std::lower_bound(row_.data_.begin(), row_.data_.end(), c, + [](const entry& a, unsigned b) { return a.col<b; }); + + if (i==row_.data_.end() || i->col!=c) { + row_.data_.insert(i, {c, x}); + } + else if (x == X{}) { + row_.data_.erase(i); + } + else { + i->value = x; + } + + return *this; + } + }; + + assign_proxy operator[](unsigned c) { + return assign_proxy{*this, c}; + } + + template <typename RASeq> + auto dot(const RASeq& v) const -> decltype(X{}*util::front(v)) { + using result_type = decltype(X{}*util::front(v)); + result_type s{}; + + auto nv = util::size(v); + for (const auto& e: data_) { + if (e.col>=nv) throw msparse_error("right multiplicand too short"); + s += e.value*v[e.col]; + } + return s; + } +}; + +template <typename X> +class matrix { +private: + std::vector<row<X>> rows; + unsigned cols = 0; + unsigned aug = row_npos; + +public: + static constexpr unsigned npos = row_npos; + + matrix() = default; + matrix(unsigned n, unsigned c): rows(n), cols(c) {} + + row<X>& operator[](unsigned i) { return rows[i]; } + const row<X>& operator[](unsigned i) const { return rows[i]; } + + unsigned size() const { return rows.size(); } + unsigned nrow() const { return size(); } + unsigned ncol() const { return cols; } + unsigned augcol() const { return aug; } + + bool empty() const { return size()==0; } + bool augmented() const { return aug!=npos; } + + template <typename Seq> + void augment(const Seq& col_dense) { + unsigned r = 0; + for (const auto& v: col_dense) { + if (r>=rows.size()) throw msparse_error("augmented column size mismatch"); + rows[r++].push_back({cols, v}); + } + if (aug==npos) aug=cols; + ++cols; + } + + void diminish() { + if (aug==npos) return; + for (auto& row: rows) row.truncate(aug); + cols = aug; + aug = npos; + } +}; + +// sparse * dense vector muliply: writes A*x to b. +template <typename AT, typename RASeqX, typename SeqB> +void mul_dense(const matrix<AT>& A, const RASeqX& x, SeqB& b) { + auto bi = std::begin(b); + unsigned n = A.nrow(); + for (unsigned i = 0; i<n; ++i) { + if (bi==compat::end(b)) throw msparse_error("output sequence b too short"); + *bi++ = A[i].dot(x); + } +} + +} // namespace msparse diff --git a/proto/symge/symbolic.hpp b/proto/symge/symbolic.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3801bd74a35986e2c23b67696102cbf586b84842 --- /dev/null +++ b/proto/symge/symbolic.hpp @@ -0,0 +1,222 @@ +#pragma once + +#include <iosfwd> +#include <string> +#include <stdexcept> +#include <vector> + +#include <util/optional.hpp> + +namespace sym { + +template <typename X> +using optional = nest::mc::util::optional<X>; + +using nest::mc::util::nothing; +using nest::mc::util::just; + +struct symbol_error: public std::runtime_error { + symbol_error(const std::string& what): std::runtime_error(what) {} +}; + +// Note impl definitions below over template argument S are used to resolve +// class declaration dependencies. + +namespace impl { + // Represents product of two symbols, or zero if either symbol is null. + template <typename S> + struct symbol_term_ { + S a, b; + + symbol_term_() = default; + + // true iff representing non-zero + operator bool() const { + return a && b; + } + }; + + // Represents the difference between two product terms. + template <typename S> + struct symbol_term_diff_ { + symbol_term_<S> left, right; + + symbol_term_diff_() = default; + symbol_term_diff_(const symbol_term_<S>& left): left(left), right{} {} + symbol_term_diff_(const symbol_term_<S>& left, const symbol_term_<S>& right): + left(left), right(right) {} + }; + + // A symbol can be primitive (has no expansion) or is defined as a product term difference. + template <typename S> + using symbol_def_ = optional<symbol_term_diff_<S>>; + + // A symbol table represents a set of symbol names and definitions, referenced + // by symbol index. + template <typename S> + class symbol_table_ { + public: + struct table_entry { + std::string name; + symbol_def_<S> def; + }; + + S define(const std::string& name, const symbol_def_<S>& definition = nothing) { + unsigned idx = size(); + entries.push_back({name, definition}); + return S{idx, this}; + } + + S operator[](unsigned i) const { + if (i>=size()) throw symbol_error("no such symbol"); + return S{i, this}; + } + + std::size_t size() const { + return entries.size(); + } + + const symbol_def_<S>& def(S s) const { + if (!valid(s)) throw symbol_error("symbol not present in this table"); + return entries[s.idx].def; + } + + const std::string& name(S s) const { + if (!valid(s)) throw symbol_error("symbol not present in this table"); + return entries[s.idx].name; + } + + private: + std::vector<table_entry> entries; + bool valid(S s) const { + return s.tbl==this && s.idx<entries.size(); + } + }; +} // namespace impl + +class symbol { +public: + using symbol_table = impl::symbol_table_<symbol>; + using symbol_def = impl::symbol_def_<symbol>; + +private: + friend class impl::symbol_table_<symbol>; + unsigned idx = 0; + const symbol_table* tbl = nullptr; + + // Symbols are created through a symbol table, or are + // default-constructed 'null' symbols. + symbol(unsigned idx, const symbol_table* tbl): + idx(idx), tbl(tbl) {} + +public: + symbol() = default; + symbol(const symbol&) = default; + symbol& operator=(const symbol&) = default; + + // A symbol is 'null' if it has no corresponding symbol table. + operator bool() const { return (bool)tbl; } + + std::string str() const { + return tbl? tbl->name(*this): ""; + } + + symbol_def def() const { + return tbl? tbl->def(*this): symbol_def{}; + } + + bool primitive() const { return (bool)def(); } + + optional<unsigned> index(const symbol_table& in_table) const { + return tbl==&in_table? just(idx): nothing; + } +}; + +using symbol_term = impl::symbol_term_<symbol>; +using symbol_term_diff = impl::symbol_term_diff_<symbol>; +using symbol_def = impl::symbol_def_<symbol>; +using symbol_table = impl::symbol_table_<symbol>; + +inline symbol_term_diff operator-(const symbol_term& left, const symbol_term& right) { + return symbol_term_diff{left, right}; +} + +inline symbol_term_diff operator-(const symbol_term& right) { + return symbol_term_diff{symbol_term{}, right}; +} + +inline symbol_term operator*(const symbol& a, const symbol& b) { + return symbol_term{a, b}; +} + +inline std::ostream& operator<<(std::ostream& o, const symbol& s) { + return o << s.str(); +} + +inline std::ostream& operator<<(std::ostream& o, const symbol_term& term) { + if (term) return o << term.a.str() << '*' << term.b.str(); + else return o << '0'; +} + +inline std::ostream& operator<<(std::ostream& o, const symbol_term_diff& diff) { + if (!diff.right) return o << diff.left; + else { + if (diff.left) o << diff.left; + o << '-'; + return o << diff.right; + } +} + +// A store represents a map from symbols (all from one table) to values. +class store { +private: + const symbol_table& table; + std::vector<optional<double>> data; + +public: + explicit store(const symbol_table& table): table(table) {} + + optional<double>& operator[](const symbol& s) { + if (auto idx = s.index(table)) { + if (*idx>=data.size()) data.resize(1+*idx); + return data[*idx]; + } + throw symbol_error("symbol not associated with store table"); + } + + optional<double> operator[](const symbol& s) const { + auto idx = s.index(table); + if (idx && *idx<data.size()) return data[*idx]; + else return nothing; + } + + optional<double> evaluate(const symbol& s) { + auto& value = (*this)[s]; + if (!value) { + if (auto def = s.def()) { + value = evaluate(*def); + } + } + return value; + } + + optional<double> evaluate(const symbol_term& t) { + if (!t) return 0; + auto a = evaluate(t.a); + auto b = evaluate(t.b); + return a && b? ++mul_count, just((*a)*(*b)): nothing; + } + + optional<double> evaluate(const symbol_term_diff& d) { + auto l = evaluate(d.left); + auto r = evaluate(d.right); + return l && r? ++sub_count, just((*l)-(*r)): nothing; + } + + // stat collection... + unsigned mul_count = 0; + unsigned sub_count = 0; +}; + +} // namespace sym + diff --git a/proto/symge/symge-demo.cpp b/proto/symge/symge-demo.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e832264c1b666e45f50a8791987fcabdef8f2e5c --- /dev/null +++ b/proto/symge/symge-demo.cpp @@ -0,0 +1,472 @@ +// bloody CMake +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include <cassert> +#include <cstring> +#include <iomanip> +#include <iostream> +#include <queue> +#include <random> +#include <sstream> +#include <string> +#include <unordered_set> +#include <utility> + +#include <util/optional.hpp> + +#include "symbolic.hpp" +#include "msparse.hpp" + +using namespace sym; + +// Identifier name picking helper routines + +void join_impl(std::ostream& ss, const std::string& sep) {} + +template <typename Head, typename... Args> +void join_impl(std::ostream& ss, const std::string& sep, Head&& head, Args&&... tail) { + if (sizeof...(tail)==0) + ss << std::forward<Head>(head); + else + join_impl(ss << std::forward<Head>(head) << sep, sep, std::forward<Args>(tail)...); +} + +// Return string representation of all arguments, separated by `sep`. +template <typename... Args> +std::string join(const std::string& sep, Args&&... items) { + std::stringstream ss; + join_impl(ss, sep, std::forward<Args>(items)...); + return ss.str(); +} + +// Maintain a collection of unique identifiers. +class id_maker { +private: + std::unordered_set<std::string> ids; + +public: + // Find the next string lexicographically after argument, allowing only + // ASCII letters and digits to change. + static std::string next_id(std::string s) { + unsigned l = s.size(); + if (l==0) return "a"; + + unsigned i = l-1; + + constexpr char l0='a', l1='z'; + constexpr char u0='A', u1='Z'; + constexpr char d0='0', d1='9'; + for (;;) { + char& c = s[i]; + if ((c>=l0 && c<l1) || (c>=u0 && c<u1) || (c>=d0 && c<d1)) { + ++c; + return s; + } + if (c==l1) c=l0; + if (c==u1) c=u0; + if (c==d1) c=d0; + if (i==0) break; + --i; + } + + // prepend a character based on class of first + if (s[0]==u0) return u0+s; + if (s[0]==d0) return d0+s; + return l0+s; + } + + // Make a new identifier based on the given arguments. + template <typename... Args> + std::string operator()(Args&&... elements) { + std::string name = join("",std::forward<Args>(elements)...); + if (name.empty()) name = "a"; + + while (ids.count(name)) { + name = next_id(name); + } + ids.insert(name); + return name; + } + + void reserve(std::string name) { + ids.insert(std::move(name)); + } +}; + +// Output helper functions + +template <typename X> +std::ostream& operator<<(std::ostream& o, const optional<X>& x) { + return x? o << *x: o << "nothing"; +} + +template <typename X> +std::ostream& operator<<(std::ostream& o, const msparse::matrix<X>& m) { + for (unsigned r = 0; r<m.nrow(); ++r) { + o << '|'; + for (unsigned c = 0; c<m.ncol(); ++c) { + if (c==m.augcol()) o << " | "; + o << std::setw(12) << m[r][c]; + } + o << " |\n"; + } + return o; +} + +template <typename Sep, typename V> +struct sepval_proxy { + const Sep& sep; + const V& v; + + sepval_proxy(const Sep& sep, const V& v): sep(sep), v(v) {} + + friend std::ostream& operator<<(std::ostream& O, const sepval_proxy& sv) { + bool first = true; + for (const auto& x: sv.v) { + if (!first) O << sv.sep; + first = false; + O << x; + } + return O; + } +}; + +template <typename Sep, typename V> +sepval_proxy<Sep,V> sepval(const Sep& sep, const V& v) { return sepval_proxy<Sep,V>(sep, v); } + +std::ostream& operator<<(std::ostream& o, const symbol_table& syms) { + for (unsigned i = 0; i<syms.size(); ++i) { + symbol s = syms[i]; + if (s.def()) o << s << ": " << s.def() << "\n"; + } + return o; +} + +// Symbolic GE + +using sym_row = msparse::row<symbol>; +using sym_matrix = msparse::matrix<symbol>; + +// Returns q[c]*p - p[c]*q +template <typename DefineSym> +sym_row row_reduce(unsigned c, const sym_row& p, const sym_row& q, DefineSym& define_sym) { + if (p.index(c)==p.npos || q.index(c)==q.npos) throw std::runtime_error("improper row GE"); + + sym_row u; + symbol x = q[c]; + symbol y = p[c]; + + auto piter = p.begin(); + auto qiter = q.begin(); + unsigned pj = piter->col; + unsigned qj = qiter->col; + + while (piter!=p.end() || qiter!=q.end()) { + unsigned j = std::min(pj, qj); + symbol_term t1, t2; + + if (j==pj) { + t1 = x*piter->value; + ++piter; + pj = piter==p.end()? p.npos: piter->col; + } + if (j==qj) { + t2 = y*qiter->value; + ++qiter; + qj = qiter==q.end()? q.npos: qiter->col; + } + if (j!=c) { + u.push_back({j, define_sym(t1-t2)}); + } + } + return u; +} + +// Actual GE reduction. +template <typename DefineSym> +void gj_reduce_simple(sym_matrix& A, unsigned ncol, const DefineSym& define_sym) { + // Expect A to be a (possibly column-augmented) diagonally dominant + // matrix; take diagonal elements as pivots. + if (A.nrow()>A.ncol()) throw std::runtime_error("improper matrix for reduction"); + + for (unsigned pivrow = 0; pivrow<A.nrow(); ++pivrow) { + unsigned pivcol = pivrow; + + for (unsigned i = 0; i<A.nrow(); ++i) { + if (i==pivrow || A[i].index(pivcol)==msparse::row_npos) continue; + + A[i] = row_reduce(pivcol, A[i], A[pivrow], define_sym); + } + } +} + +// simple greedy estimate based on immediate fill cost +double estimate_cost(const sym_matrix& A, unsigned p) { + struct count_sym_mul { + mutable unsigned nmul = 0; + mutable unsigned nfill = 0; + symbol operator()(symbol_term_diff t) { + bool l = t.left, r = t.right; + nmul += l+r; + nfill += r&!l; + return symbol{}; + } + }; + + count_sym_mul counter; + for (unsigned i = 0; i<A.nrow(); ++i) { + if (i==p || A[i].index(p)==msparse::row_npos) continue; + + row_reduce(p, A[i], A[p], counter); + } + return counter.nfill; +} + +template <typename DefineSym> +void gj_reduce(sym_matrix& A, unsigned ncol, const DefineSym define_sym) { + // Expect A to be a (possibly column-augmented) diagonally dominant + // matrix; take diagonal elements as pivots. + if (A.nrow()>A.ncol()) throw std::runtime_error("improper matrix for reduction"); + + std::vector<unsigned> pivots; + for (unsigned r = 0; r<A.nrow(); ++r) { + pivots.push_back(r); + } + + std::vector<double> cost(pivots.size()); + + while (!pivots.empty()) { + for (unsigned i = 0; i<pivots.size(); ++i) { + cost[pivots[i]] = estimate_cost(A, pivots[i]); + } + + std::sort(pivots.begin(), pivots.end(), + [&](unsigned r1, unsigned r2) { return cost[r1]>cost[r2]; }); + + unsigned pivrow = pivots.back(); + pivots.erase(std::prev(pivots.end())); + + unsigned pivcol = pivrow; + + for (unsigned i = 0; i<A.nrow(); ++i) { + if (i==pivrow || A[i].index(pivcol)==msparse::row_npos) continue; + + A[i] = row_reduce(pivcol, A[i], A[pivrow], define_sym); + } + } +} + +// Validation + +template <typename Rng> +msparse::matrix<double> make_random_matrix(unsigned n, double row_nnz, Rng& R) { + std::uniform_real_distribution<double> U; + msparse::matrix<double> M(n, n); + + row_nnz = std::min(row_nnz, n-1.5); // always allow some chance of zeroes. + double density = n==1? 1: row_nnz/(n-1); + for (unsigned i = 0; i<n; ++i) { + for (unsigned j = 0; j<n; ++j) { + if (i!=j && U(R)>density) continue; + double u = U(R); + M[i][j] = i==j? 1+0.2*u: (u-0.5)/n; + } + } + return M; +} + +struct symge_stats { + unsigned n; + unsigned nnz; + unsigned nmul; + unsigned nsub; + unsigned nsym; + double relerr; +}; + +template <typename Rng> +symge_stats run_symge_validation(Rng& R, unsigned n, double row_nnz, bool use_estimator, bool debug = false) { + std::uniform_real_distribution<double> U; + symge_stats stats = { n, 0, 0, 0, 0, 0. }; + + msparse::matrix<double> A = make_random_matrix(n, row_nnz, R); + sym_matrix S(n, n); + + symbol_table syms; + store values(syms); + id_maker make_id; + + // make symbolic matrix with same sparsity pattern as M + for (unsigned i = 0; i<A.nrow(); ++i) { + sym_row srow; + for (const auto& a: A[i]) { + unsigned j = a.col; + auto s = syms.define(make_id("a", i, j)); + values[s] = a.value; + srow.push_back({j, s}); + ++stats.nnz; + } + S[i] = srow; + } + + // pick x and compute b = Ax + std::vector<double> x(n); + for (auto& elem: x) elem = 10*U(R); + + std::vector<double> b(n); + mul_dense(A, x, b); + + // augment M and S with rhs + A.augment(b); + std::vector<symbol> rhs; + for (unsigned i = 0; i<n; ++i) { + auto s = syms.define(make_id("b", i)); + rhs.push_back(s); + values[s] = b[i]; + } + S.augment(rhs); + + if (debug) { + std::cerr << "A|b:\n" << A << "\n"; + std::cerr << "S:\n" << S << "\n"; + } + + // perform GE + auto nprim_sym = syms.size(); + auto define_sym = [&](const symbol_def& def) { return syms.define(make_id("t00"), def); }; + if (use_estimator) { + gj_reduce(S, n, define_sym); + } + else { + gj_reduce_simple(S, n, define_sym); + } + stats.nsym = syms.size()-nprim_sym; + if (debug) { + std::cerr << "reduced S:\n" << S << "\n"; + std::cerr << "definitions:\n" << syms << "\n"; + } + + // validate: compute solution y from reduced S and symbol defs + std::vector<double> y(n); + double maxerr = 0; + double maxx = 0; + for (unsigned i = 0; i<n; ++i) { + const sym_row& row = S[i]; + if (row.size()!=2 || row.maxcol()!=n) + throw std::runtime_error("unexpected matrix layout!"); + + unsigned idx = row.get(0).col; + symbol coeff = row.get(0).value; + symbol rhs = row.get(1).value; + + if (debug) { + std::cerr << "y" << idx << " = " << rhs << "/" << coeff << "\n"; + std::cerr << " = " << values.evaluate(rhs).get() << "/" << values.evaluate(coeff).get() << "\n"; + } + y[idx] = values.evaluate(rhs).get()/values.evaluate(coeff).get(); + maxerr = std::max(maxerr, std::abs(y[idx]-x[idx])); + maxx = std::max(maxx, std::abs(x[idx])); + } + stats.relerr = maxerr/maxx; + stats.nsub = values.sub_count; + stats.nmul = values.mul_count; + if (debug) { + std::cerr << "x:\n" << sepval(", ", x) << "\n"; + std::cerr << "computed solution:\n" << sepval(", ", y) << "\n"; + std::cerr << "operation count: " << values.mul_count << " mul; " << values.sub_count << " sub\n"; + } + return stats; +} + +void usage() { + std::cout << "Usage: symge-demo [-v] [-d] [-e]\n" + << "Options:\n" + << " -v Verbose output: display statistics as table\n" + << " -d Debug output to stderr\n" + << " -e Use a simple cost estimator to select pivots\n" + << " -n N Specify max matrix size (default 10)\n" + << " -k N Number of matrices to test of each size (default 100)\n" + << " -r R Mean number of off-diagonal nonzero elements per row (default 2.0)\n"; +} + +void usage_error(const std::string& msg) { + std::cerr << "symge-demo: " << msg << "\n" + << "Try 'symge-demo -h' for more information.\n"; +} + +int main(int argc, const char** argv) { + bool debug = false; + bool verbose = false; + bool use_estimator = false; + int max_n = 10; + int count = 100; + double row_mean_nnz = 2.0; + + for (const char** arg=argv+1; arg!=argv+argc; ++arg) { + if (!std::strcmp("-v", *arg)) { + verbose = true; + } + else if (!std::strcmp("-d", *arg)) { + debug = true; + } + else if (!std::strcmp("-e", *arg)) { + use_estimator = true; + } + else if (!std::strcmp("-n", *arg)) { + if (!arg[1]) { + usage_error("expected argument for '-n'"); + return 2; + } + max_n = std::atoi(*++arg); + } + else if (!std::strcmp("-r", *arg)) { + if (!arg[1]) { + usage_error("expected argument for '-r'"); + return 2; + } + row_mean_nnz = std::stod(*++arg); + } + else if (!std::strcmp("-k", *arg)) { + if (!arg[1]) { + usage_error("expected argument for '-k'"); + return 2; + } + count = std::atoi(*++arg); + } + else if (!std::strcmp("-h", *arg)) { + usage(); + return 0; + } + else { + std::cerr << "symge-demo: unrecognized argument\n" + << "Try 'symge-demo -h' for more information.\n"; + return 2; + } + } + + if (verbose) { + char line[80]; + std::snprintf(line, sizeof(line), "%10s%10s%10s%10s%10s%10s\n", + "n", "nnz", "nmul", "nsub", "nsym", "relerr"); + std::cout << line; + } + + std::minstd_rand R; + for (unsigned n = 1; n<=max_n; ++n) { + for (unsigned k = 1; k<=count; ++k) { + auto stats = run_symge_validation(R, n, row_mean_nnz, use_estimator, debug); + if (verbose) { + char line[80]; + std::snprintf(line, sizeof(line), "%10d%10d%10d%10d%10d%10lf\n", + stats.n, stats.nnz, stats.nmul, stats.nsub, stats.nsym, stats.relerr); + + std::cout << line; + } + assert(stats.relerr<1e-6); + } + } + + return 0; +}