mirror of
https://github.com/zenorogue/hyperrogue.git
synced 2024-08-25 11:06:47 +00:00
1387 lines
33 KiB
C++
1387 lines
33 KiB
C++
// define all our manifolds, and perform tests on them
|
|
// Copyright (C) 2011-2022 Tehora and Zeno Rogue, see 'hyper.cpp' for details
|
|
|
|
#include "kohonen.h"
|
|
#include <unordered_map>
|
|
|
|
namespace rogueviz {
|
|
|
|
transmatrix& memo_relative_matrix(cell *c1, cell *c2);
|
|
|
|
namespace kohonen_test {
|
|
|
|
using namespace kohonen;
|
|
|
|
void equal_weights() {
|
|
alloc(weights);
|
|
for(auto& w: weights) w = 1;
|
|
}
|
|
|
|
void show_all() {
|
|
samples_to_show.clear();
|
|
for(int i=0; i<samples; i++)
|
|
samples_to_show.push_back(i);
|
|
}
|
|
|
|
bool check(bool deb);
|
|
|
|
vector<pair<int, int> > voronoi_edges;
|
|
|
|
bool nmap;
|
|
|
|
vector<cell*> where;
|
|
int max_distance = 999;
|
|
|
|
bool using_subdata;
|
|
vector<sample> orig_data;
|
|
vector<int> sub_indices;
|
|
vector<int> inv_sub_indices;
|
|
|
|
void ideal() {
|
|
for(int i=0; i<isize(net); i++)
|
|
for(auto& v: net[i].net) v = 1000;
|
|
|
|
for(int i=0; i<isize(net); i++)
|
|
for(int j=0; j<isize(where); j++)
|
|
if(where[j] == net[i].where)
|
|
net[i].net = (using_subdata ? orig_data : data)[j].val;
|
|
|
|
println(hlog, make_pair(isize(net), isize(where)));
|
|
}
|
|
|
|
bool sphere_data = false;
|
|
|
|
bool edge_data;
|
|
|
|
vector<bool> is_special;
|
|
vector<int> ctrdist;
|
|
int ctrdist_max;
|
|
|
|
voronoi::manifold data_manifold;
|
|
|
|
using measures::manidata;
|
|
manidata test_emb, test_orig;
|
|
|
|
void create_manidata(manidata& mdata) {
|
|
auto ac = gen_neuron_cells();
|
|
auto& edges = mdata.edges;
|
|
edges.clear();
|
|
mdata.size = isize(ac);
|
|
mdata.distances.clear();
|
|
for(int i=0; i<mdata.size; i++) {
|
|
for(int j=0; j<i; j++)
|
|
if(isNeighbor(ac[i], ac[j]))
|
|
edges.emplace_back(i, j);
|
|
}
|
|
}
|
|
|
|
void create_data() {
|
|
if(sphere_data) return;
|
|
sphere_data = true;
|
|
|
|
initialize_rv();
|
|
|
|
cell *c0 = currentmap->gamestart();
|
|
where.clear();
|
|
is_special.clear();
|
|
ctrdist.clear();
|
|
ctrdist_max = 0;
|
|
edge_data = false;
|
|
|
|
drawthemap();
|
|
data.clear(); samples_to_show.clear();
|
|
auto ac = gen_neuron_cells();
|
|
|
|
for(auto c: ac) {
|
|
if(celldistance(c, c0) > max_distance) continue;
|
|
where.push_back(c);
|
|
sample s;
|
|
embeddings::get_coordinates(s.val, c, c0);
|
|
data.push_back(std::move(s));
|
|
}
|
|
samples = isize(data);
|
|
test_orig.size = samples;
|
|
colnames.resize(columns);
|
|
for(int i=0; i<columns; i++) colnames[i] = "Coordinate " + its(i);
|
|
equal_weights();
|
|
show_all();
|
|
data_manifold = voronoi::build_manifold(ac);
|
|
|
|
for(int i=0; i<samples; i++) {
|
|
bool sign = false;
|
|
using embeddings::signposts;
|
|
for(cell *c: signposts) if(c == where[i]) sign = true;
|
|
if(signposts.empty()) sign = where[i]->type != (S3 == 3 ? 6 : 4);
|
|
is_special.push_back(sign);
|
|
ctrdist.push_back(celldist(where[i]));
|
|
ctrdist_max = max(ctrdist_max, ctrdist.back());
|
|
}
|
|
|
|
create_manidata(test_orig);
|
|
}
|
|
|
|
void create_subdata(int qty) {
|
|
if(!using_subdata)
|
|
orig_data = data;
|
|
using_subdata = true;
|
|
int N = isize(orig_data);
|
|
sub_indices.resize(N);
|
|
for(int i=0; i<N; i++) sub_indices[i] = i;
|
|
hrandom_shuffle(sub_indices);
|
|
sub_indices.resize(qty);
|
|
data.clear();
|
|
|
|
for(int i=0; i<isize(vdata); i++)
|
|
if(vdata[i].m) vdata[i].m->dead = true;
|
|
vdata.clear();
|
|
sample_vdata_id.clear();
|
|
state &= ~KS_SAMPLES;
|
|
|
|
for(int idx: sub_indices) data.push_back(orig_data[idx]);
|
|
samples = isize(data);
|
|
|
|
inv_sub_indices.clear();
|
|
inv_sub_indices.resize(N, -1);
|
|
for(int i=0; i<samples; i++) inv_sub_indices[sub_indices[i]] = i;
|
|
|
|
show_all();
|
|
edge_data = false;
|
|
}
|
|
|
|
bool colorless = false;
|
|
|
|
void create_edgedata() {
|
|
if(edge_data) return;
|
|
edge_data = true;
|
|
create_data();
|
|
|
|
if(!colorless) for(int i=0; i<samples; i++) {
|
|
if(is_special[i])
|
|
vdata[i].cp.color1 = gradient(0xC0C000FF, 0xC00000FF, 0, ctrdist[i], ctrdist_max);
|
|
else
|
|
vdata[i].cp.color1 = gradient(0x0000FFFF, 0x101010FF, 0, ctrdist[i], ctrdist_max);
|
|
}
|
|
|
|
auto& edges = test_orig.edges;
|
|
|
|
for(int i=0; i<samples; i++) {
|
|
vector<int> ids;
|
|
for(auto e: edges) {
|
|
if(e.first == i) ids.push_back(e.second);
|
|
if(e.second == i) ids.push_back(e.first);
|
|
}
|
|
vdata[i].name = lalign(0, "#", i, " ", ids);
|
|
}
|
|
|
|
auto any = add_edgetype("adjacent");
|
|
|
|
if(!using_subdata) {
|
|
for(auto e: edges)
|
|
addedge(e.first, e.second, 1, false, any);
|
|
}
|
|
else {
|
|
vector<int> nearest(isize(orig_data), -1);
|
|
set<pair<int, int>> subedges;
|
|
for(int i=0; i<samples; i++)
|
|
nearest[sub_indices[i]] = i;
|
|
while(true) {
|
|
vector<pair<int, int> > changes;
|
|
for(auto e: edges) {
|
|
if(nearest[e.first] == -1 && nearest[e.second] >= 0) changes.emplace_back(e.first, nearest[e.second]);
|
|
if(nearest[e.second] == -1 && nearest[e.first] >= 0) changes.emplace_back(e.second, nearest[e.first]);
|
|
}
|
|
if(changes.empty()) break;
|
|
// hrandom_shuffle(changes);
|
|
for(auto ch: changes) nearest[ch.first] = ch.second;
|
|
}
|
|
for(auto e: edges)
|
|
if(nearest[e.first] != nearest[e.second])
|
|
subedges.emplace(nearest[e.first], nearest[e.second]);
|
|
// for(auto se: subedges) println(hlog, "subedges = ", se);
|
|
for(auto sube: subedges)
|
|
addedge(sube.first, sube.second, 1, false, any);
|
|
}
|
|
|
|
println(hlog, "edgedata created, ", using_subdata);
|
|
}
|
|
|
|
void sphere_test() {
|
|
|
|
create_data();
|
|
|
|
initialize_dispersion();
|
|
initialize_neurons_initial();
|
|
|
|
analyze();
|
|
|
|
create_edgedata();
|
|
|
|
ideal();
|
|
analyze();
|
|
}
|
|
|
|
void sphere_test_no_disp() {
|
|
|
|
create_data();
|
|
|
|
initialize_neurons_initial();
|
|
analyze();
|
|
create_edgedata();
|
|
ideal();
|
|
analyze();
|
|
}
|
|
|
|
void check_energy() {
|
|
vector<int> dlist;
|
|
|
|
vector<cell*> win_cells(samples);
|
|
for(int i=0; i<samples; i++)
|
|
win_cells[i] = winner(i).where;
|
|
|
|
vector<int> distlist;
|
|
for(int i=0; i<samples; i++)
|
|
for(int j=0; j<i; j++)
|
|
distlist.push_back(celldistance(where[i], where[j]));
|
|
|
|
for(int i=0; i<samples; i++) {
|
|
shiftmatrix M = ggmatrix(where[i]);
|
|
println(hlog, i, ": ", M * C0);
|
|
}
|
|
// println(hlog, distlist);
|
|
|
|
for(auto e: test_orig.edges) {
|
|
cell *w1 = win_cells[e.first];
|
|
cell *w2 = win_cells[e.second];
|
|
int d = celldistance(w1, w2);
|
|
dlist.push_back(d);
|
|
}
|
|
println(hlog, dlist);
|
|
}
|
|
|
|
void evaluate() {
|
|
create_manidata(test_emb);
|
|
test_orig.distances = measures::build_distance_matrix(test_orig.size, test_orig.edges);
|
|
test_emb.distances = measures::build_distance_matrix(test_emb.size, test_emb.edges);
|
|
vector<int> mapp(test_orig.size, 0);
|
|
map<cell*, int> id;
|
|
for(int i=0; i<isize(net); i++) id[net[i].where] = i;
|
|
for(int i=0; i<samples; i++) mapp[i] = id[winner(i).where];
|
|
vector<pair<int, int>> edo_recreated = measures::recreate_topology(mapp, test_orig.edges);
|
|
for(int k=0; k<measures::MCOUNT; k++) {
|
|
print(hlog, measures::catnames[k], " = ", measures::evaluate_measure(test_emb, test_orig, mapp, voronoi_edges, edo_recreated, k), " ");
|
|
}
|
|
println(hlog);
|
|
}
|
|
|
|
bool kst_key(int sym, int uni) {
|
|
if((cmode & sm::NORMAL) && uni == 'l') {
|
|
set_neuron_initial();
|
|
t = tmax;
|
|
analyze();
|
|
return true;
|
|
}
|
|
else if((cmode & sm::NORMAL) && uni == 'd') {
|
|
for(int i=0; i<samples; i++)
|
|
println(hlog, i, ": ", data[i].val);
|
|
return true;
|
|
}
|
|
else if((cmode & sm::NORMAL) && uni == 'v') {
|
|
voronoi_edges = voronoi::compute_voronoi_edges(data_manifold);
|
|
println(hlog, "voronoi edges computed");
|
|
evaluate();
|
|
return true;
|
|
}
|
|
else if((cmode & sm::NORMAL) && uni == 'r') {
|
|
set_neuron_initial();
|
|
t = tmax;
|
|
dynamicval ks(qpct, 0);
|
|
while(!finished()) kohonen::step();
|
|
println(hlog, "check result = ", check(true));
|
|
check_energy();
|
|
return true;
|
|
}
|
|
if((cmode & sm::NORMAL) && uni == 't') {
|
|
tmax /= 2;
|
|
println(hlog, "tmax = ", tmax);
|
|
return true;
|
|
}
|
|
if((cmode & sm::NORMAL) && uni == 'x') {
|
|
int t = clock();
|
|
println(hlog, "weights = ", weights, " w0 = ", weights[0]);
|
|
check(true);
|
|
println(hlog, "time = ", int(clock() - t), " power = ", kohonen::ttpower, " dea = ", kohonen::dispersion_end_at, " dm = ", kohonen::distmul);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int ks_empty, ks_nonadj, ks_distant;
|
|
|
|
int qenergy = 0;
|
|
double tot_energy = 0;
|
|
|
|
bool check(bool deb) {
|
|
dynamicval dd(debugflags, 0);
|
|
set_neuron_initial();
|
|
t = tmax;
|
|
dynamicval dp(qpct, 0);
|
|
while(!finished()) kohonen::step();
|
|
analyze();
|
|
int empty = 0, nonadj = 0, distant = 0;
|
|
for(int i=0; i<cells; i++)
|
|
if(net[i].csample == 0)
|
|
empty++;
|
|
|
|
vector<cell*> win_cells(samples);
|
|
for(int i=0; i<samples; i++)
|
|
win_cells[i] = winner(i).where;
|
|
|
|
int energy = 0;
|
|
|
|
for(auto e: test_orig.edges) {
|
|
cell *w1 = win_cells[e.first];
|
|
cell *w2 = win_cells[e.second];
|
|
int rho = celldistance(w1, w2);
|
|
energy += rho * rho;
|
|
if(!isNeighbor(w1, w2)) {
|
|
nonadj++;
|
|
bool dist2 = false;
|
|
forCellEx(w3, w1)
|
|
if(isNeighbor(w3, w2)) dist2 = true;
|
|
if(!dist2) distant++;
|
|
}
|
|
}
|
|
|
|
tot_energy += energy;
|
|
qenergy++;
|
|
|
|
if(deb) println(hlog, "empty = ", empty, " nonadj = ", nonadj, " distant = ", distant, " energy = ", energy, " avg ", tot_energy / qenergy);
|
|
bool res = empty <= ks_empty && distant <= ks_distant && nonadj <= ks_nonadj;
|
|
return res;
|
|
}
|
|
|
|
vector<ld> get_parameters() {
|
|
return vector<ld> { ttpower, learning_factor, gaussian ? distmul : dispersion_end_at-1 };
|
|
}
|
|
|
|
void set_parameters(const vector<ld>& v) {
|
|
ttpower = v[0];
|
|
learning_factor = v[1];
|
|
if(gaussian) distmul = v[2];
|
|
else dispersion_end_at = v[2] + 1;
|
|
}
|
|
|
|
void som_table() {
|
|
sphere_test();
|
|
|
|
map<array<int, 3>, pair<int, int> > tries;
|
|
|
|
map<array<int, 3>, string> sucorder;
|
|
|
|
auto bttpower = ttpower;
|
|
auto blearning = learning_factor;
|
|
auto bdist = distmul;
|
|
auto bdispe = dispersion_end_at - 1;
|
|
|
|
ld last_distmul = -1;
|
|
|
|
auto set_parameters = [&] (array<int, 3>& u) {
|
|
distmul = bdist * exp(u[0] / 5.);
|
|
dispersion_end_at = 1 + bdispe * exp(u[0] / 5.);
|
|
ttpower = bttpower * exp(u[2] / 5.);
|
|
learning_factor = blearning * exp(u[1] / 5.);
|
|
|
|
if(last_distmul != distmul) {
|
|
last_distmul = distmul, state &=~ KS_DISPERSION;
|
|
}
|
|
};
|
|
|
|
array<int, 3> best = {0, 0, 0};
|
|
|
|
int maxtry = 20;
|
|
|
|
while(true) {
|
|
|
|
array<int, 3> best_at;
|
|
ld bestval = -1;
|
|
|
|
vector<ld> vals;
|
|
|
|
for(int k=0; k<27; k++) {
|
|
array<int, 3> cnt;
|
|
int k1 = k;
|
|
for(int i=0; i<3; i++) cnt[2-i] = best[2-i] + (k1%3-1), k1 /= 3;
|
|
set_parameters(cnt);
|
|
do {
|
|
tries[cnt].second++;
|
|
dynamicval dd(debugflags, 0);
|
|
bool chk = check(false);
|
|
if(chk)
|
|
tries[cnt].first++;
|
|
sucorder[cnt] += (chk ? 'y' : 'n');
|
|
}
|
|
while(tries[cnt].second < maxtry);
|
|
ld val_here = tries[cnt].first * 1. / tries[cnt].second;
|
|
if(val_here > bestval) bestval = val_here, best_at = cnt;
|
|
vals.push_back(val_here);
|
|
}
|
|
|
|
sort(vals.begin(), vals.end());
|
|
|
|
best = best_at;
|
|
set_parameters(best);
|
|
println(hlog, "score ", bestval, " at ", best_at, " : ", tie(distmul, dispersion_end_at, learning_factor, ttpower), " x", tries[best].second, " s=", vals[vals.size()-2]);
|
|
if(tries[best].second > maxtry)
|
|
maxtry = tries[best].second;
|
|
|
|
if(tries[best].second >= 1000) {
|
|
println(hlog, "suc ", best, " :\n", sucorder[best]);
|
|
|
|
for(int vv=10; vv>=0; vv--) {
|
|
dynamicval ks(qpct, 0);
|
|
t = vv ? tmax * vv / 10 : 1;
|
|
step();
|
|
println(hlog, "t=", t);
|
|
println(hlog, "dispersion_count = ", dispersion_count);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
vector<string> shapelist;
|
|
map<string, reaction_t> shapes;
|
|
map<string, reaction_t> embeddings;
|
|
|
|
int data_scale = 1;
|
|
int embed_scale = 1;
|
|
int current_scale = 1;
|
|
|
|
void set_gp(int a, int b) {
|
|
a *= current_scale; b *= current_scale;
|
|
if(a == 1 && b == 1)
|
|
set_variation(eVariation::bitruncated);
|
|
else if(a == 1 && b == 0)
|
|
set_variation(eVariation::pure);
|
|
else {
|
|
set_variation(eVariation::goldberg);
|
|
gp::param = gp::loc(a, b);
|
|
}
|
|
}
|
|
|
|
void set_restrict() {
|
|
kohonen::kqty = 5000;
|
|
kohonen::kohrestrict = 520 * current_scale * current_scale;
|
|
}
|
|
|
|
void set_torus2(int a, int b, int c, int d, int e) {
|
|
using namespace euc;
|
|
auto& T0 = eu_input.user_axes;
|
|
T0[0][0] = a;
|
|
T0[0][1] = b;
|
|
T0[1][0] = c;
|
|
T0[1][1] = d;
|
|
eu_input.twisted = e;
|
|
build_torus3();
|
|
}
|
|
|
|
int dim = 10;
|
|
|
|
string emb;
|
|
|
|
void add(string name, reaction_t embed, reaction_t set) {
|
|
shapelist.push_back(name);
|
|
shapes[name] = set;
|
|
embeddings[name] = embed;
|
|
}
|
|
|
|
void set_euclid3(int x, int y, int z, int twist) {
|
|
using namespace euc;
|
|
auto& T0 = eu_input.user_axes;
|
|
for(int i=0; i<3; i++)
|
|
for(int j=0; j<3; j++) T0[i][j] = 0;
|
|
|
|
T0[0][0] = x;
|
|
T0[1][1] = y;
|
|
T0[2][2] = z;
|
|
|
|
eu_input.twisted = twist;
|
|
build_torus3();
|
|
}
|
|
|
|
void klein_signposts() {
|
|
embeddings::etype = embeddings::eSignpost;
|
|
println(hlog, "marking klein signposts");
|
|
embeddings::signposts.clear();
|
|
|
|
for(cell *c: currentmap->allcells()) setdist(c, 7, nullptr);
|
|
|
|
for(int x=0; x<4; x++)
|
|
for(int y=0; y<13; y++) {
|
|
cellwalker cw = cellwalker(currentmap->gamestart(), 0);
|
|
for(int i=0; i<x*5*current_scale; i++) {
|
|
cw += wstep;
|
|
cw += 3;
|
|
}
|
|
cw+=2;
|
|
for(int j=0; j<y*current_scale; j++) {
|
|
cw += wstep;
|
|
cw += 2;
|
|
cw += wstep;
|
|
cw -= 2;
|
|
}
|
|
embeddings::signposts.push_back(cw.at);
|
|
}
|
|
println(hlog, embeddings::signposts);
|
|
}
|
|
|
|
flagtype flags;
|
|
|
|
const static flagtype m_symmetric = Flag(0);
|
|
|
|
int landscape_dim = 60;
|
|
|
|
void init_shapes() {
|
|
|
|
auto signpost = [] { emb = "signpost"; embeddings::mark_signposts(false, gen_neuron_cells()); };
|
|
auto signpost_subg = [] (int a, int b) { return [a,b] { emb = "signpost" + its(a) + its(b); embeddings::mark_signposts_subg(a, b, gen_neuron_cells()); }; };
|
|
// auto signpost_full = [] { emb = "signpost-full"; embeddings::mark_signposts(true, gen_neuron_cells()); };
|
|
auto signpost_klein = [] { emb = "signpost-klein"; klein_signposts(); };
|
|
auto landscape = [] {
|
|
emb = "landscape";
|
|
if(landscape_dim)
|
|
embeddings::init_landscape(landscape_dim);
|
|
else
|
|
embeddings::init_landscape_det(gen_neuron_cells());
|
|
};
|
|
auto natural = [] { emb = "natural"; embeddings::etype = embeddings::eNatural; };
|
|
|
|
/* disks */
|
|
|
|
add("disk10", landscape, [] {
|
|
set_geometry(gNormal);
|
|
set_gp(1,0);
|
|
set_restrict();
|
|
flags = 0;
|
|
});
|
|
|
|
add("disk11", landscape, [] {
|
|
set_geometry(gNormal);
|
|
set_gp(1,1);
|
|
set_restrict();
|
|
flags = 0;
|
|
});
|
|
|
|
add("disk20", landscape, [] {
|
|
set_geometry(gNormal);
|
|
set_gp(2,0);
|
|
set_restrict();
|
|
flags = 0;
|
|
});
|
|
|
|
add("disk21", landscape, [] {
|
|
set_geometry(gNormal);
|
|
set_gp(2,1);
|
|
set_restrict();
|
|
flags = 0;
|
|
});
|
|
|
|
add("disk40", landscape, [] {
|
|
set_geometry(gNormal);
|
|
set_gp(4,0);
|
|
set_restrict();
|
|
flags = 0;
|
|
});
|
|
|
|
add("disk43", landscape, [] {
|
|
set_geometry(gNormal);
|
|
set_gp(4,3);
|
|
set_restrict();
|
|
flags = 0;
|
|
});
|
|
|
|
add("disk-euclid", landscape, [] {
|
|
set_geometry(gEuclid);
|
|
set_gp(1,0);
|
|
set_restrict();
|
|
set_torus2(0,0,0,0,0);
|
|
flags = 0;
|
|
});
|
|
|
|
/* spheres */
|
|
|
|
add("elliptic", signpost, [] {
|
|
set_geometry(gElliptic);
|
|
set_gp(6,6);
|
|
flags = m_symmetric;
|
|
});
|
|
|
|
add("sphere", natural, [] {
|
|
set_geometry(gSphere);
|
|
set_gp(6,2);
|
|
flags = m_symmetric;
|
|
});
|
|
|
|
add("sphere4", natural, [] {
|
|
set_geometry(gSmallSphere);
|
|
set_gp(7,6);
|
|
flags = m_symmetric;
|
|
});
|
|
|
|
/* tori */
|
|
|
|
add("torus-hex", natural, [] {
|
|
set_geometry(gEuclid);
|
|
set_gp(1,0);
|
|
dim = 23;
|
|
set_torus2(dim, 0, 0, dim, 0);
|
|
flags = m_symmetric;
|
|
});
|
|
|
|
add("torus-sq", natural, [] {
|
|
set_geometry(gEuclid);
|
|
set_gp(1,0);
|
|
dim = 13;
|
|
set_torus2(dim*1.6,0,-dim,dim*2,0);
|
|
});
|
|
|
|
add("torus-rec", natural, [] {
|
|
set_geometry(gEuclid);
|
|
set_gp(1,0);
|
|
dim = 9;
|
|
set_torus2(dim*3+2,0,-dim,dim*2,0);
|
|
flags = 0;
|
|
});
|
|
|
|
if(0) add("torus-sq-sq", natural, [] {
|
|
set_geometry(gEuclidSquare);
|
|
set_gp(1,0);
|
|
set_torus2(23,0,0,23,0);
|
|
flags = m_symmetric;
|
|
});
|
|
|
|
add("klein-sq", signpost_klein, [] {
|
|
set_geometry(gEuclid);
|
|
set_gp(1,0);
|
|
dim = 13;
|
|
set_torus2(dim*1.6,0,-dim,dim*2,8);
|
|
flags = 0;
|
|
});
|
|
|
|
/* hyperbolic 8 */
|
|
|
|
add("Bolza", signpost_subg(1, 1), [] {
|
|
set_geometry(gBolza);
|
|
set_gp(6,3);
|
|
flags = m_symmetric;
|
|
});
|
|
|
|
add("Bolza2", signpost, [] {
|
|
set_geometry(gBolza2);
|
|
set_gp(5,1);
|
|
flags = m_symmetric;
|
|
});
|
|
|
|
/* hyperbolic 7 */
|
|
|
|
add("minimal", signpost, [] {
|
|
set_geometry(gMinimal);
|
|
set_gp(5,5);
|
|
flags = 0;
|
|
});
|
|
|
|
add("Zebra", signpost, [] {
|
|
set_geometry(gZebraQuotient);
|
|
set_gp(4,3);
|
|
flags = 0;
|
|
});
|
|
|
|
add("KQ", signpost, [] {
|
|
set_geometry(gKleinQuartic);
|
|
set_gp(3,2);
|
|
flags = m_symmetric;
|
|
});
|
|
|
|
add("Macbeath", signpost, [] {
|
|
set_geometry(gMacbeath);
|
|
set_gp(2,1);
|
|
flags = m_symmetric;
|
|
});
|
|
|
|
add("triplet1", signpost, [] {
|
|
field_quotient_2d(0, 1, 0);
|
|
set_gp(1, 1);
|
|
flags = m_symmetric;
|
|
});
|
|
|
|
add("triplet2", signpost, [] {
|
|
field_quotient_2d(0, 1, 1);
|
|
set_gp(1, 1);
|
|
flags = m_symmetric;
|
|
});
|
|
|
|
add("triplet3", signpost, [] {
|
|
field_quotient_2d(0, 1, 2);
|
|
set_gp(1, 1);
|
|
flags = m_symmetric;
|
|
});
|
|
|
|
}
|
|
|
|
void shot_settings() {
|
|
View = Id;
|
|
|
|
brm_limit = GDIM == 2 ? 1000 : 0;
|
|
|
|
if(GDIM == 3)
|
|
View = cspin(0, 2, 30 * degree) * cspin(1, 2, 30._deg) * View;
|
|
|
|
shift_view(ctangent(2, -0.5));
|
|
|
|
vid.creature_scale = GDIM == 2 ? 4.5 : 1;
|
|
if(geometry == gCell600) vid.creature_scale = 0.5;
|
|
pconf.alpha = 1;
|
|
pmodel = mdDisk;
|
|
|
|
if(GDIM == 3 && hyperbolic) {
|
|
sightranges[geometry] = 8;
|
|
vid.creature_scale = 2;
|
|
}
|
|
|
|
shot::shotformat = -1;
|
|
shot::shotx = 1000;
|
|
shot::shoty = 1000;
|
|
shot::transparent = GDIM == 2;
|
|
pconf.scale = 0.9;
|
|
|
|
modelcolor = 0xFF;
|
|
|
|
drawthemap();
|
|
|
|
if(sphere) pconf.scale = 0.6;
|
|
|
|
if(euclid) {
|
|
ld maxs = 0;
|
|
auto& cd = current_display;
|
|
for(auto n: net) {
|
|
auto w = n.where;
|
|
shiftmatrix g = ggmatrix(w);
|
|
for(int i=0; i<w->type; i++) {
|
|
shiftpoint h = tC0(g * currentmap->adj(w, i));
|
|
hyperpoint onscreen;
|
|
applymodel(h, onscreen);
|
|
maxs = max(maxs, onscreen[0] / cd->xsize);
|
|
maxs = max(maxs, onscreen[1] / cd->ysize);
|
|
}
|
|
}
|
|
pconf.alpha = 1;
|
|
pconf.scale = pconf.scale / 2 / maxs / cd->radius;
|
|
pconf.scale /= 1.2;
|
|
if(closed_manifold) pconf.scale = WDIM == 3 ? 0.2 : 0.07;
|
|
}
|
|
|
|
if(GDIM == 3) pmodel = mdPerspective;
|
|
if(nil || sol) pmodel = mdGeodesic;
|
|
|
|
vid.use_smart_range = 2;
|
|
vid.smart_range_detail = 7;
|
|
vid.cells_generated_limit = 999999;
|
|
vid.cells_drawn_limit = 200000;
|
|
}
|
|
|
|
void shot_settings_png() {
|
|
vid.use_smart_range = 2;
|
|
vid.smart_range_detail = 0.5;
|
|
|
|
shot::shotx = 500;
|
|
shot::shoty = 500;
|
|
}
|
|
|
|
bool more = true;
|
|
|
|
void create_index() {
|
|
|
|
hr::ignore(system(("mkdir " + som_test_dir).c_str()));
|
|
|
|
fhstream f(som_test_dir + "index-" + its(current_scale) + ".html", "wt");
|
|
|
|
fhstream csv(som_test_dir + "manifold-data-" + its(current_scale) + ".csv", "wt");
|
|
|
|
fhstream tex(som_test_dir + "manifold-data-" + its(current_scale) + ".tex", "wt");
|
|
println(f, "<html><body>");
|
|
|
|
// fhstream distf(som_test_dir + "distlists-" + its(current_scale) + ".txt", "wt");
|
|
|
|
bool add_header = true;
|
|
|
|
for(auto s: shapelist) {
|
|
|
|
sphere_data = false;
|
|
println(hlog, "building ", s);
|
|
kohonen::kqty = kohonen::krad = 0;
|
|
kohonen::kohrestrict = 999999999;
|
|
stop_game();
|
|
shapes[s]();
|
|
// if(!euclid) continue;
|
|
start_game();
|
|
|
|
initialize_rv();
|
|
embeddings[s]();
|
|
|
|
println(hlog, "create_data");
|
|
create_data();
|
|
|
|
println(hlog, "sphere_test");
|
|
sphere_test_no_disp();
|
|
|
|
println(hlog, "building disttable");
|
|
|
|
vector<int> disttable(100, 0);
|
|
int pairs = 0;
|
|
|
|
test_orig.distances = measures::build_distance_matrix(test_orig.size, test_orig.edges);
|
|
int N = test_orig.size;
|
|
for(int i=0; i<N; i++)
|
|
for(int j=0; j<i; j++) {
|
|
disttable[test_orig.distances[i][j]]++;
|
|
pairs++;
|
|
}
|
|
|
|
println(hlog, "render");
|
|
shot_settings();
|
|
shot_settings_png();
|
|
shot::take(som_test_dir + s + "-" + its(current_scale) + ".png");
|
|
|
|
println(f, "<img src=\"" + s + "-" + its(current_scale) + ".png\"/><br/>");
|
|
|
|
println(f, "shape ", s, " : ", samples, " items, ", isize(test_orig.edges), " edges, dim ", columns, " (", emb, "), ", full_geometry_name());
|
|
|
|
println(f, "<hr/>");
|
|
fflush(f.f);
|
|
|
|
again:
|
|
if(add_header) print(csv, "name"); else print(csv, s);
|
|
if(add_header) print(tex, "name"); else print(tex, s);
|
|
#define Out(title,value) if(add_header) { print(csv, ";", title); print(tex, "&", title); } else { print(csv, ";", value); print(tex, "&", value); }
|
|
|
|
double avgdist = 0, avgdist2 = 0, sqsum = 0;
|
|
int maxdist = 0;
|
|
for(int i=0; i<100; i++) {
|
|
if(disttable[i] > 0) maxdist = i;
|
|
avgdist += i * disttable[i];
|
|
avgdist2 += i * i * disttable[i];
|
|
sqsum += disttable[i] * (disttable[i]-1.);
|
|
}
|
|
disttable.resize(maxdist + 1);
|
|
if(more) println(hlog, disttable, " pairs = ", pairs);
|
|
|
|
avgdist /= pairs;
|
|
avgdist2 /= pairs;
|
|
double kmax = 1 - sqsum / (pairs * (pairs-1.));
|
|
|
|
Out("samples", samples);
|
|
Out("edges", isize(test_orig.edges));
|
|
Out("columns", columns);
|
|
Out("embtype", emb);
|
|
Out("gpx", gp::univ_param().first);
|
|
Out("gpy", gp::univ_param().second);
|
|
Out("orientable", nonorientable ? 0 : 1);
|
|
Out("symmetric", (flags & m_symmetric) ? 1 : 0);
|
|
Out("closed", closed_manifold ? 1 : 0);
|
|
Out("quotient", quotient ? 1 : 0);
|
|
Out("dim", WDIM);
|
|
Out("valence", S3);
|
|
Out("tile", S7);
|
|
|
|
println(hlog, "gen neuron cells");
|
|
auto ac = gen_neuron_cells();
|
|
int sum = 0;
|
|
for(cell *c: ac) sum += c->type;
|
|
ld curvature = (S3 == 3 ? 6 : S3 >= OINF ? 2 : 4) - sum * 1. / isize(ac);
|
|
if(GDIM == 3) curvature = hyperbolic ? -1 : sphere ? 1 : 0;
|
|
Out("curvature", curvature);
|
|
|
|
println(hlog, "compute geometry data");
|
|
auto gd = compute_geometry_data();
|
|
Out("euler", gd.euler);
|
|
Out("area", gd.area);
|
|
|
|
Out("geometry",
|
|
hyperbolic ? "hyperbolic" :
|
|
euclid ? "euclidean" :
|
|
sphere ? "spherical" :
|
|
"other");
|
|
|
|
if(more) {
|
|
Out("maxdist", maxdist);
|
|
Out("avgdist", avgdist);
|
|
Out("avgdist2", avgdist2);
|
|
Out("kmax", kmax);
|
|
}
|
|
|
|
println(csv); println(tex, "\\\\");
|
|
fflush(csv.f); fflush(f.f); fflush(tex.f);
|
|
|
|
if(add_header) { add_header = false; goto again; }
|
|
|
|
println(hlog, "geom = ", s, " delta = ", isize(embeddings::delta_at));
|
|
}
|
|
|
|
println(f, "</body></html>");
|
|
}
|
|
|
|
unsigned hash(string s) {
|
|
unsigned res = 0;
|
|
for(char c: s) res = res * 37 + c;
|
|
return res;
|
|
}
|
|
|
|
int subdata_value;
|
|
|
|
bool only_landscape;
|
|
|
|
string cg() {
|
|
string s = "";
|
|
if(kohonen::gaussian == 1) s += "-cg";
|
|
if(kohonen::gaussian == 2) s += "-gg";
|
|
if(kohonen::dispersion_long) s += "-dl";
|
|
if(ttpower != 1) s += "-tt" + lalign(0, ttpower);
|
|
if(subdata_value) s += "-s" + its(subdata_value);
|
|
if(landscape_dim) s += "-l" + its(landscape_dim);
|
|
if(data_scale) s += "-d" + its(data_scale);
|
|
if(embed_scale) s += "-e" + its(embed_scale);
|
|
return s;
|
|
}
|
|
|
|
vector<vector<sample> > saved_data;
|
|
|
|
void all_pairs(bool one) {
|
|
|
|
string dir = som_test_dir + "pairs" + cg();
|
|
|
|
hr::ignore(system(("mkdir -p " + dir + "/img").c_str()));
|
|
|
|
int sid = 0;
|
|
for(auto s1: shapelist) {
|
|
for(auto s2: shapelist) {
|
|
sid++;
|
|
|
|
if(kohonen::gaussian == 2 && s2.substr(0, 4) != "disk" && s2.substr(0, 6) != "sphere") continue;
|
|
if(only_landscape && s1.substr(0, 4) != "disk") continue;
|
|
|
|
string fname_vor = dir + "/" + s1 + "-" + s2 + ".vor";
|
|
string fname = dir + "/" + s1 + "-" + s2 + ".txt";
|
|
if(file_exists(fname)) continue;
|
|
|
|
fhstream f(fname, "wt");
|
|
fhstream fvor(fname_vor, "wt");
|
|
|
|
println(hlog, "mapping ", s1, " to " ,s2);
|
|
|
|
shrand(hash(s1) ^ hash(s2));
|
|
|
|
sphere_data = false; using_subdata = false;
|
|
kohonen::kqty = kohonen::krad = 0;
|
|
kohonen::kohrestrict = 999999999;
|
|
current_scale = data_scale;
|
|
stop_game();
|
|
shapes[s1]();
|
|
start_game();
|
|
|
|
initialize_rv();
|
|
|
|
if(landscape_dim) {
|
|
saved_data.clear();
|
|
for(int i=0; i<100; i++) {
|
|
sphere_data = false;
|
|
data.clear();
|
|
embeddings[s1]();
|
|
create_data();
|
|
saved_data.push_back(data);
|
|
if(i < 5)
|
|
for(int j=0; j<20; j++)
|
|
println(hlog, "saved data ", i, ":", j, ": ", saved_data[i][j].val);
|
|
}
|
|
}
|
|
else {
|
|
embeddings[s1]();
|
|
create_data();
|
|
}
|
|
|
|
stop_game();
|
|
|
|
kohonen::kqty = kohonen::krad = 0;
|
|
kohonen::kohrestrict = 999999999;
|
|
|
|
current_scale = embed_scale;
|
|
shapes[s2]();
|
|
|
|
initialize_dispersion();
|
|
initialize_neurons_initial();
|
|
|
|
analyze();
|
|
|
|
create_edgedata();
|
|
|
|
int orig_samples = samples;
|
|
|
|
for(int i=0; i<100; i++) {
|
|
println(hlog, "iteration ", lalign(3, i), " of ", fname);
|
|
|
|
if(landscape_dim) { data = orig_data = saved_data[i]; }
|
|
|
|
if(subdata_value) create_subdata(subdata_value);
|
|
|
|
set_neuron_initial();
|
|
|
|
if(0) {
|
|
println(hlog, "initial neuron values:"); indenter ind(2);
|
|
for(auto& n: net) println(hlog, n.net);
|
|
}
|
|
|
|
t = tmax;
|
|
dynamicval ks(qpct, 0);
|
|
while(!finished()) kohonen::step();
|
|
|
|
for(int i=0; i<orig_samples; i++) {
|
|
if(i) print(f, " ");
|
|
int j = i;
|
|
if(using_subdata) {
|
|
j = inv_sub_indices[i];
|
|
if(j == -1) { print(f, "-1"); continue; }
|
|
}
|
|
auto& w = winner(j);
|
|
print(f, int((&w) - &net[0]));
|
|
}
|
|
|
|
print(f, "\n");
|
|
fflush(f.f);
|
|
|
|
voronoi::debug_str = lalign(0, fname_vor, " iteration ", i);
|
|
|
|
auto ve = voronoi::compute_voronoi_edges(data_manifold);
|
|
println(fvor, isize(ve));
|
|
for(auto e: ve) print(fvor, e.first, " ", e.second, " ");
|
|
println(fvor);
|
|
fflush(fvor.f);
|
|
|
|
if(i < 10) {
|
|
analyze();
|
|
if(i == 0) {
|
|
shot_settings();
|
|
shot_settings_png();
|
|
shot::shotx = 200;
|
|
shot::shoty = 200;
|
|
}
|
|
if(using_subdata) {
|
|
create_edgedata();
|
|
}
|
|
shot::take(dir + "/img/" + s1 + "-" + s2 + "-" + its(i) + ".png");
|
|
}
|
|
if(i == 10) brm_structure.clear();
|
|
fflush(stdout);
|
|
}
|
|
|
|
if(1) {
|
|
fhstream rndcheck(som_test_dir + "rndcheck" + cg(), "at");
|
|
vector<int> rnd;
|
|
for(int i=0; i<10; i++) rnd.push_back(hrand(1000));
|
|
println(hlog, "finished ", s1, "-", s2, " rnd = ", rnd);
|
|
println(rndcheck, "finished ", s1, "-", s2, " rnd = ", rnd);
|
|
}
|
|
|
|
if(one) exit(0);
|
|
}
|
|
}
|
|
|
|
hr::ignore(system("touch done"));
|
|
}
|
|
|
|
bool verify_distlists = false;
|
|
|
|
void create_edgelists() {
|
|
fhstream f("results/edgelists-" + its(current_scale) + ".txt", "wt");
|
|
for(auto s: shapelist) {
|
|
|
|
sphere_data = false;
|
|
kohonen::kqty = kohonen::krad = 0;
|
|
kohonen::kohrestrict = 999999999;
|
|
stop_game();
|
|
shapes[s]();
|
|
// if(!euclid) continue;
|
|
start_game();
|
|
|
|
initialize_rv();
|
|
embeddings[s]();
|
|
|
|
println(hlog, "create_data");
|
|
create_data();
|
|
|
|
println(hlog, "sphere_test");
|
|
sphere_test_no_disp();
|
|
|
|
auto ac = gen_neuron_cells();
|
|
|
|
int N = isize(ac);
|
|
int M = isize(test_orig.edges);
|
|
print(f, s, "\n", N, " ", M);
|
|
for(auto e: test_orig.edges) print(f, " ", e.first, " ", e.second);
|
|
|
|
if(verify_distlists) {
|
|
test_orig.distances = measures::build_distance_matrix(test_orig.size, test_orig.edges);
|
|
for(int i=0; i<N; i++)
|
|
for(int j=0; j<i; j++)
|
|
if(celldistance(ac[i], ac[j]) != test_orig.distances[i][j])
|
|
println(hlog, "failure on ", tie(i, j));
|
|
}
|
|
|
|
println(f);
|
|
fflush(f.f);
|
|
}
|
|
}
|
|
|
|
#if CAP_COMMANDLINE
|
|
int readArgs() {
|
|
using namespace arg;
|
|
|
|
if(0) ;
|
|
|
|
/* choose the embedding */
|
|
|
|
else if(argis("-som-landscape")) {
|
|
PHASE(3);
|
|
start_game();
|
|
initialize_rv();
|
|
shift(); embeddings::init_landscape(argi());
|
|
println(hlog, "landscape init, ", argi());
|
|
}
|
|
|
|
else if(argis("-som-landscape-det")) {
|
|
PHASE(3);
|
|
start_game();
|
|
initialize_rv();
|
|
embeddings::init_landscape_det(gen_neuron_cells());
|
|
println(hlog, "landscape init, ", argi());
|
|
}
|
|
|
|
else if(argis("-som-signposts")) {
|
|
PHASE(3);
|
|
start_game();
|
|
initialize_rv();
|
|
embeddings::mark_signposts(false, gen_neuron_cells());
|
|
}
|
|
|
|
else if(argis("-som-signposts-full")) {
|
|
PHASE(3);
|
|
start_game();
|
|
initialize_rv();
|
|
embeddings::mark_signposts(true, gen_neuron_cells());
|
|
}
|
|
|
|
else if(argis("-som-signposts-klein")) {
|
|
PHASE(3);
|
|
start_game();
|
|
klein_signposts();
|
|
}
|
|
|
|
else if(argis("-som-signposts-subg")) {
|
|
PHASE(3);
|
|
shift(); int a = argi();
|
|
shift(); int b = argi();
|
|
start_game();
|
|
initialize_rv();
|
|
embeddings::mark_signposts_subg(a, b, gen_neuron_cells());
|
|
}
|
|
|
|
else if(argis("-som-signposts-draw")) {
|
|
for(cell *c: embeddings::signposts) c->wall = waPlatform;
|
|
}
|
|
|
|
else if(argis("-som-rug")) {
|
|
PHASE(3);
|
|
start_game();
|
|
initialize_rv();
|
|
shift(); embeddings::generate_rug(argi(), true);
|
|
}
|
|
|
|
else if(argis("-som-rug-show")) {
|
|
PHASE(3);
|
|
start_game();
|
|
initialize_rv();
|
|
shift(); embeddings::generate_rug(argi(), false);
|
|
}
|
|
|
|
else if(argis("-som-proj")) {
|
|
PHASE(3); embeddings::etype = embeddings::eProjection;
|
|
}
|
|
|
|
else if(argis("-som-emb")) {
|
|
PHASE(3); embeddings::etype = embeddings::eNatural;
|
|
}
|
|
|
|
/* other stuff */
|
|
|
|
else if(argis("-som-test")) {
|
|
PHASE(3);
|
|
start_game();
|
|
sphere_test();
|
|
println(hlog, "data = ", isize(data), " neurons = ", isize(net));
|
|
}
|
|
|
|
else if(argis("-som-cdata")) {
|
|
PHASE(3);
|
|
start_game();
|
|
create_data();
|
|
}
|
|
|
|
else if(argis("-subdata")) {
|
|
shift();
|
|
create_subdata(argi());
|
|
}
|
|
|
|
else if(argis("-subdata-val")) {
|
|
shift();
|
|
subdata_value = argi();
|
|
}
|
|
|
|
else if(argis("-landscape-dim")) {
|
|
shift(); landscape_dim = argi();
|
|
}
|
|
|
|
else if(argis("-som-optimize")) {
|
|
PHASE(3);
|
|
start_game();
|
|
// som_optimize();
|
|
}
|
|
|
|
else if(argis("-som-table")) {
|
|
PHASE(3);
|
|
start_game();
|
|
som_table();
|
|
}
|
|
|
|
else if(argis("-som-scale-data")) {
|
|
PHASE(3);
|
|
shift(); current_scale = data_scale = argi();
|
|
}
|
|
|
|
else if(argis("-som-scale-embed")) {
|
|
PHASE(3);
|
|
shift(); current_scale = embed_scale = argi();
|
|
}
|
|
|
|
else if(argis("-by-name")) {
|
|
PHASE(3);
|
|
init_shapes();
|
|
land_structure = lsSingle;
|
|
shift(); string s = args();
|
|
|
|
if(shapes.count(s)) {
|
|
kohonen::kqty = kohonen::krad = 0;
|
|
kohonen::kohrestrict = 999999999;
|
|
stop_game();
|
|
shapes[s]();
|
|
start_game();
|
|
initialize_rv();
|
|
embeddings[s]();
|
|
println(hlog, "embedding used: ", emb, " for: ", s);
|
|
}
|
|
else {
|
|
println(hlog, "shape unknown: ", s);
|
|
}
|
|
}
|
|
|
|
else if(argis("-only-landscape")) {
|
|
only_landscape = true;
|
|
}
|
|
|
|
else if(argis("-som-experiment")) {
|
|
PHASE(3);
|
|
init_shapes();
|
|
land_structure = lsSingle;
|
|
all_pairs(false);
|
|
}
|
|
|
|
else if(argis("-som-experiment1")) {
|
|
PHASE(3);
|
|
init_shapes();
|
|
land_structure = lsSingle;
|
|
all_pairs(true);
|
|
}
|
|
|
|
else if(argis("-som-experiment-index")) {
|
|
PHASE(3);
|
|
init_shapes();
|
|
land_structure = lsSingle;
|
|
create_index();
|
|
}
|
|
|
|
else if(argis("-edgecheck")) {
|
|
PHASE(3);
|
|
unsigned x = 1;
|
|
for(auto e: test_orig.edges) {
|
|
x*= 7;
|
|
x += e.first;
|
|
x += 123 * e.second;
|
|
}
|
|
println(hlog, "x = ", x, " edges = ", isize(test_orig.edges));
|
|
}
|
|
|
|
else if(argis("-som-experiment-tables")) {
|
|
PHASE(3);
|
|
init_shapes();
|
|
land_structure = lsSingle;
|
|
create_edgelists();
|
|
}
|
|
|
|
else if(argis("-ex")) exit(0);
|
|
|
|
else return 1;
|
|
return 0;
|
|
}
|
|
|
|
auto hooks3 = addHook(hooks_args, 100, readArgs);
|
|
#endif
|
|
|
|
auto khook = arg::add3("-kst-keys", [] { rv_hook(hooks_handleKey, 150, kst_key); })
|
|
+ addHook(hooks_configfile, 100, [] {
|
|
param_i(ks_empty, "ks_empty", 0);
|
|
param_i(ks_distant, "ks_distant", 0);
|
|
param_i(ks_nonadj, "ks_nonadj", 0);
|
|
param_i(max_distance, "ks_max");
|
|
})
|
|
+ arg::add3("-kst-colorless", [] { colorless = true; })
|
|
+ addHook(hooks_markers, 100, [] () {
|
|
int N = isize(net);
|
|
bool multidraw = quotient;
|
|
bool use_brm = closed_manifold && isize(currentmap->allcells()) <= brm_limit;
|
|
vid.linewidth *= 3;
|
|
for(auto e: voronoi_edges)
|
|
if(e.first < N && e.second < N)
|
|
for(const shiftmatrix& V1 : hr::span_at(current_display->all_drawn_copies, net[e.first].where)) {
|
|
if(use_brm) {
|
|
auto V2 = brm_get(net[e.first].where, C0, net[e.second].where, C0);
|
|
queueline(V1*C0, V1*V2*C0, 0xC010C0FF, vid.linequality + 3);
|
|
}
|
|
else if(multidraw || elliptic) {
|
|
auto V2 = memo_relative_matrix(net[e.second].where, net[e.first].where);
|
|
queueline(V1*C0, V1*V2*C0, 0xC010C0FF, vid.linequality + 3);
|
|
}
|
|
else
|
|
for(const shiftmatrix& V2 : hr::span_at(current_display->all_drawn_copies, net[e.second].where))
|
|
queueline(V1*C0, V2*C0, 0xC010C0FF, vid.linequality + 3);
|
|
}
|
|
vid.linewidth /= 3;
|
|
})
|
|
+ arg::add3("-kst-animate", [] { rv_hook(anims::hooks_record_anim, 100, [] (int i, int noframes) {
|
|
bool steps = false;
|
|
ld nf = noframes;
|
|
while(t * nf > (nf - i) * tmax)
|
|
step(), steps = true;
|
|
if(steps) analyze();
|
|
}); });
|
|
|
|
}}
|
|
|