1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2024-09-06 12:36:52 +00:00
hyperrogue/devmods/reps/tests.cpp
2023-06-21 15:10:42 +02:00

539 lines
19 KiB
C++

namespace reps {
constexpr int test_dim = 3;
constexpr bool in_hyperbolic = true;
int edges, valence;
void prepare_tests() {
hr::start_game();
if(MDIM != test_dim) throw hr::hr_exception("fix your dimension");
if(!(in_hyperbolic ? hyperbolic : sphere)) throw hr::hr_exception("fix your geometry");
if(hr::variation != hr::eVariation::pure) throw hr::hr_exception("fix your variation");
if(quotient) throw hr::hr_exception("fix your quotient");
if(test_dim == 4) {
if(cginf.tiling_name != "{4,3,5}") throw hr::hr_exception("only {4,3,5} implemented in 3D");
edges = 4;
valence = 5;
}
else {
edges = hr::cwt.at->type;
bool ok;
valence = hr::get_valence(hr::cwt.at, 1, ok);
}
}
struct data {
using Number = hr::ld;
static constexpr int Dim = test_dim;
static constexpr int Flipped = in_hyperbolic ? test_dim-1 : -1;
};
struct countdata {
using Number = countfloat;
static constexpr int Dim = data::Dim;
static constexpr int Flipped = data::Flipped;
};
struct bigdata {
using Number = big;
static constexpr int Dim = data::Dim;
static constexpr int Flipped = data::Flipped;
};
using good = rep_linear_nn<bigdata>;
int debug; // 0 -- never, 1 -- only errors, 2 -- always
vector<cell*> randomwalk(std::mt19937& gen, cell *from, int dist) {
vector<cell*> res = { from };
while(celldistance(from, res.back()) < dist) {
int i = gen() % res.back()->type;
res.push_back(res.back()->cmove(i));
}
return res;
}
template<class N> N rand01(std::mt19937& gen) { return N(((gen() & HRANDMAX) | 1) / (HRANDMAX+1.)); }
vector<cell*> random_return(std::mt19937& gen, cell *from, cell *to, ld peq, ld pbad) {
vector<cell*> res = { from };
ld d = celldistance(to, from);
while(to != res.back()) {
int i = gen() % res.back()->type;
cell *r1 = res.back()->cmove(i);
ld d1 = celldistance(to, r1);
bool ok = d1 < d ? true : d1 == d ? rand01<ld>(gen) < peq : rand01<ld>(gen) < pbad;
if(ok) { res.push_back(r1); d = d1; }
}
return res;
}
vector<cell*> vrev(vector<cell*> a) { reverse(a.begin(), a.end()); return a; }
vector<cell*> vcon(vector<cell*> a, vector<cell*> b) { for(auto bi: b) a.push_back(bi); return a; }
template<class N> N edge_of_triangle_with_angles(N alpha, N beta, N gamma) {
N of = (cos(alpha) + cos(beta) * cos(gamma)) / (sin(beta) * sin(gamma));
if(hyperbolic) return acosh(of);
return acos(of);
}
template<class N> N get_edgelen() {
N beta = get_deg<N>(360)/valence;
return edge_of_triangle_with_angles<N> (beta, get_deg<N>(180)/edges, get_deg<N>(180)/edges);
}
template<class T> typename T::isometry cpush(int c, typename T::data::Number distance) {
return T::lorentz(c, T::data::Dim-1, distance);
}
template<class T> struct cube_rotation_data_t {
std::vector<std::pair<hr::transmatrix, typename T::isometry>> mapping;
};
template<class T> cube_rotation_data_t<T> cube_rotation_data;
template<class T> cube_rotation_data_t<T>& build_cube_rotation() {
auto& crd = cube_rotation_data<T>;
auto& crdm = crd.mapping;
// using N = typename T::data::Number;
if(crdm.empty()) {
crdm.emplace_back(hr::Id, T::id());
for(int i=0; i<isize(crdm); i++)
for(int j=0; j<3; j++) for(int k=0; k<3; k++) if(j != k) {
hr::transmatrix U = crdm[i].first * hr::cspin90(j, k);
bool is_new = true;
for(int i0=0; i0<isize(crdm); i0++) if(hr::eqmatrix(U, crdm[i0].first)) is_new = false;
// if(is_new) crdm.emplace_back(U, T::apply(crdm[i].second, T::cspin(j, k, get_deg<N>(90))));
if(is_new) crdm.emplace_back(U, T::apply(crdm[i].second, T::cspin90(j, k)));
}
if(isize(crdm) != 24) {
println(hlog, "the number of rotations found: ", isize(crdm));
throw hr::hr_exception("wrong number of rotations");
}
}
return crd;
}
template<class T, class U> U apply_move(cell *a, cell *b, U to) {
if(a == b) return to;
using N = typename T::data::Number;
if constexpr(test_dim == 4) {
auto& crdm = build_cube_rotation<T>().mapping;
int ida = neighborId(a, b);
auto M = hr::currentmap->adj(a, ida);
for(int i0=0; i0<isize(crdm); i0++)
for(int i1=0; i1<isize(crdm); i1++)
if(hr::eqmatrix(M, crdm[i0].first * hr::xpush(1.06128) * crdm[i1].first)) {
to = T::apply(crdm[i1].second, to);
to = T::apply(cpush<T>(0, get_edgelen<N>()), to);
to = T::apply(crdm[i0].second, to);
return to;
}
println(hlog, "tessf = ", hr::cgip->tessf);
println(hlog, "len = ", get_edgelen<N>());
throw hr::hr_exception("rotation not found");
}
int ida = neighborId(a, b);
int idb = neighborId(b, a);
auto P1 = T::cspin(0, 1, idb * get_deg<N>(360) / edges);
to = T::apply(P1, to);
auto P2 = cpush<T>(0, get_edgelen<N>());
to = T::apply(P2, to);
auto P3 = T::cspin(1, 0, get_deg<N>(180) + ida * get_deg<N>(360) / edges);
to = T::apply(P3, to);
return to;
}
template<class T, class U> U apply_path(vector<cell*> path, U to) {
for(int i=hr::isize(path)-2; i>=0; i--)
to = apply_move<T, U> (path[i], path[i+1], to);
return to;
}
template<class T> double test_sanity(int i) {
hr::indenter in(2);
ld d1 = 1.25, d2 = 1.5;
typename good::point gp = good::center();
gp = good::apply(cpush<good>(0, d1), gp);
gp = good::apply(cpush<good>(1, d2), gp);
gp = good::apply(good::cspin(0, 1, get_deg<big>(72)), gp);
typename T::point p = T::center();
p = T::apply(cpush<T>(0, d1), p);
p = T::apply(cpush<T>(1, d2), p);
p = T::apply(T::cspin(0, 1, get_deg<typename T::data::Number>(72)), p);
double res = 0;
#define ADD(x, y) if(debug) println(hlog, "VS ", x, ",", y); res += pow( double(x-y), 2);
#define ADDA(x, y) if(debug) println(hlog, "VS ", x, ",", y); res += pow( cyclefix_on(double(x-y)), 2);
if(debug) println(hlog, "p=", T::print(p));
ADD(T::get_coord(p, 0), good::get_coord(gp, 0));
ADD(T::get_coord(p, 1), good::get_coord(gp, 1));
ADD(T::get_coord(p, 2), good::get_coord(gp, 2));
ADD(T::dist0(p), good::dist0(gp));
ADDA(T::angle(p), good::angle(gp));
return res;
}
template<class T> double test_consistency(int i) {
double res = 0;
using D = typename T::data;
auto a = cpush<T>(0, 1);
auto b = cpush<T>(1, 1);
auto c = cpush<T>(0, 1);
auto s = T::apply(T::apply(a, b), c);
auto sp = T::apply(s, T::center());
auto s1 = T::apply(a, T::apply(b, c));
auto sp1 = T::apply(s1, T::center());
auto sp2 = T::apply(a, T::apply(b, T::apply(c, T::center())));
ADD(T::dist0(sp), T::dist0(sp1));
ADD(T::dist0(sp), T::dist0(sp2));
for(int i=0; i<D::Dim; i++) { ADD(T::get_coord(sp, i), T::get_coord(sp1, i)); }
for(int i=0; i<D::Dim; i++) { ADD(T::get_coord(sp, i), T::get_coord(sp2, i)); }
if(test_dim == 3) {
ADDA(T::angle(sp), T::angle(sp1));
ADDA(T::angle(sp), T::angle(sp2));
}
return res;
}
template<class T> double test_tba(int id) {
std::mt19937 testr; testr.seed(id);
for(int i=0; i<hr::cwt.at->type; i++) {
vector<cell*> p = {hr::cwt.at, hr::cwt.at->cmove(i), hr::cwt.at};
auto h = apply_path<T>(p, T::center());
if(debug == 2) println(hlog, "i=", hr::lalign(3, i), " h = ", T::print(h), " distance =", T::dist0(h));
if(T::dist0(h) >= 0 && T::dist0(h) < 0.1) continue;
exit(1);
}
return 999;
}
template<class T> double test_loop_point(int id) {
std::mt19937 testr; testr.seed(id);
for(int i=0; i<100; i++) {
auto p1 = randomwalk(testr, hr::cwt.at, i);
auto p2 = random_return(testr, p1.back(), hr::cwt.at, 1/16., 1/32.);
auto p = vcon(p1, p2);
if(debug == 2) println(hlog, "path = ", p);
auto h = apply_path<T>(vrev(p), T::center());
if(debug == 2) println(hlog, "i=", hr::lalign(3, i), " h = ", T::print(h), " distance =", T::dist0(h));
if(T::dist0(h) >= 0 && T::dist0(h) < 0.1) continue;
return i;
}
return 999;
}
template<class T> double test_loop_iso(int id) {
std::mt19937 testr; testr.seed(id);
for(int i=0; i<100; i++) {
auto p1 = randomwalk(testr, hr::cwt.at, i);
auto p2 = random_return(testr, p1.back(), hr::cwt.at, 1/16., 1/32.);
auto p = vcon(p1, p2);
if(debug == 2) println(hlog, "path = ", p);
auto h = apply_path<T>(vrev(p), T::id());
auto hr = T::apply(h, T::center());
// println(hlog, "i=", hr::lalign(3, i), " h=", hr);
if(debug == 2) println(hlog, "i=", hr::lalign(3, i), " hr = ", T::print(hr), " distance = ", T::dist0(hr));
if(T::dist0(hr) >= 0 && T::dist0(hr) < 0.1) continue;
if(debug == 1) println(hlog, "i=", hr::lalign(3, i), " hr = ", T::print(hr), " distance = ", T::dist0(hr));
return i;
}
return 999;
}
template<class T, class F> vector<T> repeat_test(const F& f, int qty) {
vector<T> res;
for(int i=0; i<qty; i++) res.push_back(f(i));
return res;
}
template<class T> typename T::isometry random_rotation(std::mt19937& testr) {
using D = typename T::data;
using N = typename D::Number;
if(D::Dim == 3) {
auto alpha = rand01<N>(testr) * get_deg<N>(360);
return T::cspin(0, 1, alpha);
}
auto x = T::id();
for(int i=0; i<100; i++) {
int c0 = testr() % (D::Dim-1);
int c1 = testr() % (D::Dim-1);
if(c0 == c1) continue;
auto len = rand01<N>(testr) * get_deg<N>(360);
x = T::apply(T::cspin(c0, c1, len), x);
}
return x;
}
template<class T> typename T::isometry random_iso(std::mt19937& testr) {
auto x = T::id();
using D = typename T::data;
using N = typename D::Number;
for(int i=0; i<100; i++) {
int c0 = testr() % D::Dim;
int c1 = testr() % D::Dim;
if(c0 == c1) continue;
if(c0 == D::Flipped) std::swap(c0, c1);
N len = c1 < D::Flipped ? rand01<N>(testr) * get_deg<N>(360) : (rand01<N>(testr)-N(0.5)) * N(0.25);
if(c1 == D::Flipped) x = T::apply(T::lorentz(c0, c1, len), x);
else x = T::apply(T::cspin(c0, c1, len), x);
}
return x;
}
template<class T> std::string test_count(int id) {
std::mt19937 testr; testr.seed(id);
hr::shstream out;
auto A = random_iso<T>(testr);
auto B = random_iso<T>(testr);
auto C = random_iso<T>(testr);
auto P = T::apply(C, T::center());
for(int i=0; i<9; i++) {
counts.clear();
for(auto& i: cbc) i = 0;
std::string s;
switch(i) {
case 0: s = "spin"; T::cspin(0, 1, countfloat(.5)); break;
case 1: s = "L0"; T::lorentz(0, T::data::Dim-1, countfloat(.5)); break;
case 2: s = "L1"; T::lorentz(1, T::data::Dim-1, countfloat(.5)); break;
case 3: s = "ip"; T::apply(A, P); break;
case 4: s = "ii"; T::apply(A, B); break;
case 5: s = "d0"; T::dist0(P); break;
case 6: s = "angle"; T::angle(P); break;
case 7: s = "inverse"; T::inverse(A); break;
case 8: s = "push"; T::push(P); break;
}
if(i) print(out, " ");
if(1) {
print(out, s, "(");
bool nsp = false;
for(int i=1; i<5; i++) if(cbc[i]) {
if(nsp) print(out, " ");
print(out, cbc[i], hr::s0+".AMDF"[i]);
nsp = true;
}
print(out, ")");
}
}
return out.s;
}
template<class A, class B> bool closeto(A a, B b) { return abs(a-b) < 0.1; }
template<class A, class B> bool closeto_angle(A a, B b) { return abs(cyclefix_on(double(a-b))) < 0.1; }
template<class T> double test_angledist(int id) {
std::mt19937 testr; testr.seed(id);
for(int i=1; i<1000; i += (1 + i/5)) {
auto p = randomwalk(testr, hr::cwt.at, i);
auto h = apply_path<T>(vrev(p), T::center());
auto gh = apply_path<good>(vrev(p), good::center());
if(debug == 2) {
println(hlog, "good: ", good::print(gh), " dist = ", good::dist0(gh), " angle = ", good::angle(gh));
println(hlog, "test: ", T::print(h), " dist = ", T::dist0(h), " angle = ", T::angle(h), " [i=", i, "]");
}
if(closeto(good::dist0(gh), T::dist0(h)) && closeto_angle(good::angle(gh), T::angle(h))) continue;
if(debug == 1) {
println(hlog, "good: ", good::print(gh), " dist = ", good::dist0(gh), " angle = ", good::angle(gh));
println(hlog, "test: ", T::print(h), " dist = ", T::dist0(h), " angle = ", T::angle(h), " [i=", i, "]");
}
return i;
}
return 999;
}
#define TEST_VARIANTS(x,D,q,t,rn, r) \
nm = nmInvariant; println(hlog, rn, "invariant: ", repeat_test<t>(x<r<D>>, q)); \
nm = nmForced; println(hlog, rn, "forced : ", repeat_test<t>(x<r<D>>, q)); \
nm = nmWeak; println(hlog, rn, "weak : ", repeat_test<t>(x<r<D>>, q)); \
nm = nmFlatten; println(hlog, rn, "flatten : ", repeat_test<t>(x<r<D>>, q)); \
nm = nmCareless; println(hlog, rn, "careless : ", repeat_test<t>(x<r<D>>, q)); \
nm = nmBinary; println(hlog, rn, "binary : ", repeat_test<t>(x<r<D>>, q));
/*
#define TEST_ALL(x,D,q,t) \
println(hlog, "HyperRogue: ", repeat_test<t>(x<rep_hr<D>>, q)); \
polar_mod = polar_choose = false; println(hlog, "high polar: ", repeat_test<t>(x<rep_high_polar<D>>, q)); \
if(test_dim == 3) { polar_mod = polar_choose = false; println(hlog, "low polar : ", repeat_test<t>(x<rep_polar2<D>>, q)); }
*/
// println(hlog, "HyperRogue : ", repeat_test<t>(x<rep_hr<D>>, q));
#define TEST_ALL(x,D,q,t) \
fix_matrices = true; TEST_VARIANTS(x,D,q,t,"linear+F ", rep_linear) \
fix_matrices = false; TEST_VARIANTS(x,D,q,t,"linear-F ", rep_linear) \
TEST_VARIANTS(x,D,q,t,"mixed ", rep_mixed) \
TEST_VARIANTS(x,D,q,t,"Clifford ", rep_clifford) \
nm = nmFlatten; println(hlog, "Clifford gyro : ", repeat_test<t>(x<rep_half<D>>, q)); \
nm = nmInvariant; println(hlog, "halfplane invariant: ", repeat_test<t>(x<rep_half<D>>, q)); \
polar_choose = false; println(hlog, "polar basic : ", repeat_test<t>(x<rep_high_polar<D>>, q)); \
polar_choose = true; println(hlog, "polar improved : ", repeat_test<t>(x<rep_high_polar<D>>, q)); \
if(test_dim == 3) { \
polar_mod = false; polar_choose = false; println(hlog, "polar F/F : ", repeat_test<t>(x<rep_polar2<D>>, q)); \
polar_mod = false; polar_choose = true; println(hlog, "polar F/T : ", repeat_test<t>(x<rep_polar2<D>>, q)); \
polar_mod = true; polar_choose = false; println(hlog, "polar T/F : ", repeat_test<t>(x<rep_polar2<D>>, q)); \
polar_mod = true; polar_choose = true; println(hlog, "polar T/T : ", repeat_test<t>(x<rep_polar2<D>>, q)); \
}
template<class T> double test_distances(int id, int a) {
std::mt19937 testr; testr.seed(id);
using N = typename T::data::Number;
for(int i=1; i<1000; i ++) {
auto R = random_rotation<T>(testr);
auto dif = exp(N(-1) * i) + get_deg<N>(a);
auto p1 = T::apply(T::apply(R, cpush<T>(0, N(i))), T::center());
auto p2 = T::apply(T::apply(R, T::apply(T::cspin(0, 1, dif), cpush<T>(0, N(i)))), T::center());
auto pd = T::apply(T::inverse(T::push(p1)), p2);
auto d = T::dist0(pd);
// for good we do not need R actually
auto gp1 = good::apply(cpush<good>(0, N(i)), good::center());
auto gp2 = good::apply(good::apply(good::cspin(0, 1, dif), cpush<good>(0, N(i))), good::center());
auto gd = good::dist0(good::apply(good::inverse(good::push(gp1)), gp2));
if(debug == 2) println(hlog, T::print(p1), " ... ", T::print(p2), " = ", T::print(pd), " d=", d, " [i=", i, " dif=", dif, "]");
if(closeto(d, gd)) continue;
return i;
}
return 999;
}
template<class T> double test_similarity(int id) { return test_distances<T>(id, 0); }
template<class T> double test_dissimilarity(int id) { return test_distances<T>(id, 180); }
template<class T> double test_other(int id) { return test_distances<T>(id, 1); }
template<class T> double test_walk(int id) {
std::mt19937 testr; testr.seed(id);
ld step = 1/16.;
// mover-relative to cell-relative
auto R0 = random_rotation<T>(testr);
cell *c0 = hr::cwt.at;
auto R1 = T::apply(R0, cpush<T>(0, step/2));
cell *c1 = hr::cwt.at;
int i = 0;
int lastchange = 0;
while(i< lastchange + 1000 && i < 10000 && celldistance(c0, c1) < 3) {
// println(hlog, "iteration ", i, " in ", c0, " vs ", c1);
auto rebase = [&] (typename T::isometry& R, cell*& c, int id) {
ld d = T::dist0(T::apply(R, T::center()));
for(int dir=0; dir<c->type; dir++) {
cell *altc = c->cmove(dir);
auto altR = apply_move<T>(altc, c, R);
ld altd = T::dist0(T::apply(altR, T::center()));
if(altd < d + 1/256.) {
R = altR; c = altc; lastchange = i; return;
}
}
};
R0 = T::apply(R0, cpush<T>(0, step)); rebase(R0, c0, 0);
R1 = T::apply(R1, cpush<T>(0, step)); rebase(R1, c1, 1);
i++;
}
return i;
}
template<class T> double test_close(int id) {
std::mt19937 testr; testr.seed(id);
cell *c = hr::cwt.at;
int phase = 0;
auto p0 = T::apply(cpush<T>(0, 1/8.), T::center());
auto p = p0;
int steps = 0;
const int maxdist = id + 1;
int errors = 0;
while(steps < 10000) {
int d = testr() % c->type;
cell *c1 = c->cmove(d);
bool do_move = false;
switch(phase) {
case 0:
/* always move */
do_move = true;
if(celldistance(c1, hr::cwt.at) == maxdist) phase = 1;
break;
case 1:
/* move only towards the center */
int d0 = celldistance(c, hr::cwt.at);
int d1 = celldistance(c1, hr::cwt.at);
do_move = d1 < d0;
if(d1 == 0) phase = 0;
break;
}
if(do_move) {
p = apply_move<T>(c1, c, p); c = c1; steps++;
if(debug == 2) println(hlog, "dist = ", celldistance(c, hr::cwt.at), " dist = ", T::dist0(p));
if(c == hr::cwt.at) {
auto d = T::dist0(p);
auto a = T::angle(p);
if(!closeto(d, 1/8.) || !closeto_angle(a, 0)) {
errors++; phase = 0; c = hr::cwt.at; p = p0;
}
}
}
}
return errors;
}
void run_all_tests() {
prepare_tests();
// println(hlog, "test_sanity"); TEST_ALL(test_sanity, data, 1, ld);
// println(hlog, "test_consistency"); TEST_ALL(test_consistency, data, 1, ld);
println(hlog, "test_loop_iso"); TEST_ALL(test_loop_iso, data, 20, int);
println(hlog, "test_loop_point"); TEST_ALL(test_loop_point, data, 20, int);
println(hlog, "test_angledist"); TEST_ALL(test_angledist, data, 3, int);
println(hlog, "test_similarity"); TEST_ALL(test_similarity, data, 20, int);
println(hlog, "test_dissimilarity"); TEST_ALL(test_dissimilarity, data, 20, int);
println(hlog, "test_other"); TEST_ALL(test_other, data, 20, int);
println(hlog, "test_walk"); TEST_ALL(test_walk, data, 20, int);
println(hlog, "test_close"); TEST_ALL(test_close, data, 20, int);
println(hlog, "test_count"); TEST_ALL(test_count, countdata, 1, std::string);
}
}