From b3654bd1ac7d38aaac0550de063158dbdf96522f Mon Sep 17 00:00:00 2001 From: mcarrier Date: Tue, 24 Jan 2017 17:06:57 +0000 Subject: added headers and examples for Nerve GIC git-svn-id: svn+ssh://scm.gforge.inria.fr/svnroot/gudhi/branches/Nerve_GIC@2002 636b058d-ea47-450e-bf9e-a15bfbe3eedb Former-commit-id: 587a3e6748bb2b7313dbd1bd4e81c4a3b01a5efc --- src/Nerve_GIC/example/bottleneckBootstrap.cpp | 274 ++++++++++++++++++ src/Nerve_GIC/example/density_smoother.cpp | 155 ++++++++++ src/Nerve_GIC/example/graph_off.cpp | 49 ++++ src/Nerve_GIC/example/km.py | 390 ++++++++++++++++++++++++++ src/Nerve_GIC/example/mapper.cpp | 179 ++++++++++++ src/Nerve_GIC/example/mapper_parameter.cpp | 165 +++++++++++ src/Nerve_GIC/example/quantile.cpp | 48 ++++ src/Nerve_GIC/example/visu.py | 43 +++ 8 files changed, 1303 insertions(+) create mode 100644 src/Nerve_GIC/example/bottleneckBootstrap.cpp create mode 100644 src/Nerve_GIC/example/density_smoother.cpp create mode 100644 src/Nerve_GIC/example/graph_off.cpp create mode 100755 src/Nerve_GIC/example/km.py create mode 100755 src/Nerve_GIC/example/mapper.cpp create mode 100644 src/Nerve_GIC/example/mapper_parameter.cpp create mode 100644 src/Nerve_GIC/example/quantile.cpp create mode 100755 src/Nerve_GIC/example/visu.py (limited to 'src/Nerve_GIC/example') diff --git a/src/Nerve_GIC/example/bottleneckBootstrap.cpp b/src/Nerve_GIC/example/bottleneckBootstrap.cpp new file mode 100644 index 00000000..eac94e5e --- /dev/null +++ b/src/Nerve_GIC/example/bottleneckBootstrap.cpp @@ -0,0 +1,274 @@ + #include "mapper.h" +#define ETA 0.001 +#define SHIFT 1 +#define DELTA 0 +#define CONSTANT 10 + +double GetUniform(){ + static default_random_engine re; + static uniform_real_distribution Dist(0,1); + return Dist(re); +} + +void SampleWithReplacement(int populationSize, int sampleSize, vector & samples){ + int m = 0; double u; + while (m < sampleSize){ + u = GetUniform(); + samples[m] = floor(u*populationSize); + m++; + } +} + +void SampleWithoutReplacement(int populationSize, int sampleSize, vector & samples){ + int& n = sampleSize; int& N = populationSize; + int t = 0; int m = 0; double u; + while (m < n){ + u = GetUniform(); + if ( (N - t)*u >= n - m ) + t++; + else{samples[m] = t; t++; m++;} + } +} + +void compute_estimator(Cloud & C, char* const & cloud, const bool & VNE, const int & Nsubs, \ + const double & gain){ + + int n = C.size(); + double** dist = new double*[n]; + for(int i = 0; i < n; i++) dist[i] = new double[n]; + double d; + Cover I; AdjacencyMatrix G; MapperElements M; + double maxf = C[0].func; double minf = C[0].func; + for(int i = 1; i < n; i++){ maxf = max(maxf,C[i].func); minf = min(minf,C[i].func);} + + char name1[100]; + if(VNE) sprintf(name1, "%s_VNdist", cloud); + else sprintf(name1, "%s_dist", cloud); + char* const name2 = name1; + ifstream input(name2, ios::out | ios::binary); + + if(input.good()){ + cout << " Reading distances for parameter selection..." << endl; + for(int i = 0; i < n; i++){ + for (int j = 0; j < n; j++){ + input.read((char*) &d,8); dist[i][j] = d; + } + } + input.close(); + } + else{ + cout << " Computing distances for parameter selection..." << endl; vector V; + input.close(); ofstream output(name2, ios::out | ios::binary); + if(VNE){ + int dim = C[0].coord.size(); + for(int i = 0; i < dim; i++){ + double meani = 0; + for (int j = 0; j < n; j++) meani += C[j].coord[i]/n; + double vari = 0; + for (int j = 0; j < n; j++) vari += pow(C[j].coord[i]-meani,2)/n; + V.push_back(vari); + } + } + + for(int i = 0; i < n; i++){ + if( (int) floor( 100*((double) i)/((double) n)+1 ) %10 == 0 ){ + cout << "\r" << floor( 100*((double) i)/((double) n) +1) << "%" << flush;} + for (int j = 0; j < n; j++){ + if(!VNE){d = C[i].EuclideanDistance(C[j]); dist[i][j] = d;} + else{d = C[i].VarianceNormalizedEuclideanDistance(C[j],V); dist[i][j] = d;} + output.write((char*) &d,8); + } + } + output.close(); + } + + int m = floor(n/pow(log(n)/log(CONSTANT),1+ETA)); + double lip = 0; double delta = 0; vector samples(m); + #pragma omp parallel for + for (int i = 0; i < Nsubs; i++){ + SampleWithoutReplacement(n,m,samples); + double hausdorff_dist = 0; + for (int j = 0; j < n; j++){ + Point p = C[j]; double mj = dist[j][samples[0]]; double Mi = abs(p.func-C[0].func)/dist[j][0]; + for (int k = 1; k < m; k++) mj = min(mj, dist[j][samples[k]]); + if(j < n-1) + for (int k = j+1; k < n; k++) Mi = max(Mi, abs(p.func-C[k].func)/dist[j][k]); + hausdorff_dist = max(hausdorff_dist, mj); lip = max(lip,Mi); + } + delta += hausdorff_dist; + } + delta /= Nsubs; double resolution; if(DELTA) resolution = lip*delta; else resolution = lip*delta/gain; + //cout << delta << " " << lip << " " << resolution << endl; + cout << " Done." << endl; + + G = build_neighborhood_graph_from_scratch_with_parameter(C,delta,name2,VNE); + + sort(C.begin(),C.end()); + I = Cover(C.begin()->func, (C.end()-1)->func, resolution, gain, SHIFT, 1); + I.proper_value(); + + map colors; colors.clear(); map num; num.clear(); + map > clusters; vector dum; dum.clear(); + for(int i = 0; i < C.size(); i++) clusters.insert(pair >(C[i],dum)); vector col(G.size()); + + M = MapperElts(G,I,clusters,col); + SparseGraph MG; + if (DELTA) MG = build_mapperDelta_graph(M,G,clusters,colors,num); else MG = build_mapper_graph(M,colors,num); + + char mappg[100]; sprintf(mappg,"%s_mapper.txt",cloud); + ofstream graphicg(mappg); + + double maxv, minv; maxv = numeric_limits::min(); minv = numeric_limits::max(); + for (map::iterator iit = colors.begin(); iit != colors.end(); iit++){ + if(iit->second > maxv) maxv = iit->second; + if(minv > iit->second) minv = iit->second; + } + + int k = 0; int ke = 0; + + for (SparseGraph::iterator it = MG.begin(); it != MG.end(); it++){ + k++; for (int j = 0; j < it->second.size(); j++) ke++; + } + + graphicg << k << " " << ke << endl; int kk = 0; + for (SparseGraph::iterator it = MG.begin(); it != MG.end(); it++){graphicg << kk << " " << colors[it->first] << endl; kk++;} + for (SparseGraph::iterator it = MG.begin(); it != MG.end(); it++) + for (int j = 0; j < it->second.size(); j++) + graphicg << it->first << " " << it->second[j] << endl; +} + +void compute_bootstrapped_estimator(const vector & samp, char* const & cloud, const int & Nboot, double** dist, Cloud & Cboot, \ + const int & Nsubs, const double & gain){ + + int n = Cboot.size(); + Cover I; MapperElements M; + double maxf = Cboot[0].func; double minf = Cboot[0].func; + for(int i = 1; i < n; i++){ maxf = max(maxf,Cboot[i].func); minf = min(minf,Cboot[i].func);} + int m = floor(n/pow(log(n)/log(CONSTANT),1+ETA)); + double lip = 0; double delta = 0; vector samples(m); + #pragma omp parallel for + for (int i = 0; i < Nsubs; i++){ + SampleWithoutReplacement(n,m,samples); + double hausdorff_dist = 0; + for (int j = 0; j < n; j++){ + Point p = Cboot[j]; double mj = dist[samp[j]][samp[samples[0]]]; double Mi = abs(p.func-Cboot[0].func)/dist[samp[j]][samp[0]]; + for (int k = 1; k < m; k++) mj = min(mj, dist[samp[j]][samp[samples[k]]]); + if(j < n-1) + for (int k = j+1; k < n; k++) Mi = max(Mi, abs(p.func-Cboot[k].func)/dist[samp[j]][samp[k]]); + hausdorff_dist = max(hausdorff_dist, mj); lip = max(lip,Mi); + } + delta += hausdorff_dist; + } + delta /= Nsubs; double resolution; + if(DELTA) resolution = lip*delta; else resolution = lip*delta/gain; + //cout << delta << " " << lip << " " << resolution << endl; + + AdjacencyMatrix G; G.clear(); vector adj; + + for(int i = 0; i < n; i++){ + adj.clear(); + for(int j = 0; j < n; j++) + if(dist[samp[i]][samp[j]] <= delta) + adj.push_back(Cboot[j]); + G.insert(pair >(Cboot[i],adj)); + } + + sort(Cboot.begin(),Cboot.end()); + I = Cover(Cboot.begin()->func, (Cboot.end()-1)->func, resolution, gain, SHIFT, 1); + I.proper_value(); + + map colors; colors.clear(); map num; num.clear(); + map > clusters; vector dum; dum.clear(); + for(int i = 0; i < Cboot.size(); i++) clusters.insert(pair >(Cboot[i],dum)); vector col(n); + + M = MapperElts(G,I,clusters,col); + SparseGraph MG; + if(DELTA) MG = build_mapperDelta_graph(M,G,clusters,colors,num); else MG = build_mapper_graph(M,colors,num); + + char mappg[100]; sprintf(mappg,"%s_mapper_%d.txt",cloud,Nboot); + ofstream graphicg(mappg); + + double maxv, minv; maxv = numeric_limits::min(); minv = numeric_limits::max(); + for (map::iterator iit = colors.begin(); iit != colors.end(); iit++){ + if(iit->second > maxv) maxv = iit->second; + if(minv > iit->second) minv = iit->second; + } + + int k = 0; int ke = 0; + + for (SparseGraph::iterator it = MG.begin(); it != MG.end(); it++){ + k++; for (int j = 0; j < it->second.size(); j++) ke++; + } + + graphicg << k << " " << ke << endl; int kk = 0; + for (SparseGraph::iterator it = MG.begin(); it != MG.end(); it++){graphicg << kk << " " << colors[it->first] << endl; kk++;} + for (SparseGraph::iterator it = MG.begin(); it != MG.end(); it++) + for (int j = 0; j < it->second.size(); j++) + graphicg << it->first << " " << it->second[j] << endl; + graphicg.close(); + +} + + +int main(int argc, char** argv){ + + if(argc <= 7 || argc >= 9){ + cout << "./bottleBoot " << endl; + return 0;} + + char* const cloud = argv[1]; bool normalized = atoi(argv[2]); char* const funct = argv[3]; double gain = atof(argv[5]); + int Nsubs = atoi(argv[6]); int Nboot = atoi(argv[7]); + + Cloud C, D, Cboot; C = read_cloud(cloud); int n = C.size(); + + if (strcmp(funct, "function:") == 0){ + char* const func = argv[4]; read_function_from_file(func,C); + } + if (strcmp(funct, "coordinate:") == 0){ + int number = atoi(argv[4]); read_coordinate(number,C); + } + if (strcmp(funct, "eccentricity") == 0){ + char* const matrix = argv[4]; compute_eccentricity(C,matrix); + } + + D = C; + + cout << "Computing estimator..." << endl; + compute_estimator(C,cloud,normalized,Nsubs,gain); + cout << "Estimator computed." << endl; + + char nam[100]; sprintf(nam,"%s_mapper.txt",cloud); char dg[100]; sprintf(dg,"%s_ExDg",cloud); + char command[100]; + sprintf(command,"cat %s | ../persistence/filtgraph 1 | ../persistence/pers 1 >> %s",nam,dg); system(command); + char list[100]; sprintf(list,"%s_quantile",cloud); + + char name[100]; + if(normalized) sprintf(name, "%s_VNdist", cloud); + else sprintf(name, "%s_dist", cloud); + ifstream input(name, ios::out | ios::binary); double d; + double** dist = new double*[n]; for(int i = 0; i < n; i++) dist[i] = new double[n]; + for(int i = 0; i < n; i++) for (int j = 0; j < n; j++){input.read((char*) &d,8); dist[i][j] = d;} + input.close(); + + for(int i = 0; i < Nboot; i++){ + + Cboot.clear(); + vector samples(n); SampleWithReplacement(n,n,samples); sort(samples.begin(), samples.end()); + for (int i = 0; i < n; i++) Cboot.push_back(D[samples[i]]); + + cout << "Computing " << i << "th bootstrapped estimator..." << endl; + compute_bootstrapped_estimator(samples,cloud,i,dist,Cboot,Nsubs,gain); + cout << "Done." << endl; + + char namei[100]; sprintf(namei,"%s_mapper_%d.txt",cloud,i); char dgi[100]; sprintf(dgi,"%s_ExDg_%d",cloud,i); + char command[100]; + sprintf(command,"cat %s | ../persistence/filtgraph 1 | ../persistence/pers 1 >> %s",namei,dgi); system(command); + sprintf(command,"../persistence/bottleneck_dist %s %s >> %s",dg,dgi,list); system(command); + sprintf(command, "rm %s %s",namei, dgi); system(command); + + } + + delete [] dist; + + return 0; +} diff --git a/src/Nerve_GIC/example/density_smoother.cpp b/src/Nerve_GIC/example/density_smoother.cpp new file mode 100644 index 00000000..4f3c4137 --- /dev/null +++ b/src/Nerve_GIC/example/density_smoother.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +double EuclideanDistance(const vector & V1, const vector & V2){ + assert(V1.size() == V2.size()); + double res = 0; + for(int i = 0; i < V1.size(); i++) res += pow(V1[i]-V2[i],2); + return sqrt(res); +} + +int main(int argc, char** argv){ + + char* const cloud = argv[1]; + char* const method = argv[2]; + + if(argc <= 4){cout << "Usage: " << endl; return 0;} + if(argc >= 6){cout << "Too many arguments !" << endl; return 0;} + + ifstream Cloud(cloud); + vector > C, distances; C.clear(); double d; + string line; + + int nb = 0; cout << "Reading cloud..." << endl; + while(getline(Cloud,line)){ + if(nb%100000 == 0) cout << " " << nb << endl; + vector P; + stringstream stream(line); + while(stream >> d) P.push_back(d); + C.push_back(P); nb++; + } + cout << endl; + + for(int i = 0; i < nb; i++){vector vect(nb); distances.push_back(vect);} + + char name[100]; sprintf(name,"%s_dist",cloud); + ifstream input(name, ios::out | ios::binary); + + if(input.good()){ + + cout << " Reading distances..." << endl; + + for(int i = 0; i < nb; i++){ + for (int j = 0; j < nb; j++){ + input.read((char*) &d,8); distances[i][j] = d; + } + } + + input.close(); + cout << " Done." << endl; + + } + + else{ + + cout << " Computing distances..." << endl; + input.close(); ofstream output(name, ios::out | ios::binary); + + for(int i = 0; i < nb-1; i++){ + if( (int) floor( 100*((double) i)/((double) nb)+1 ) %10 == 0 ){cout << "\r" << floor( 100*((double) i)/((double) nb) +1) << "%" << flush;} + for (int j = i+1; j < nb; j++){ + d = EuclideanDistance(C[i],C[j]); distances[i][j] = d; distances[j][i] = d; + output.write((char*) &d,8); + } + } + + output.close(); + cout << endl << " Done." << endl; + + } + + if(strcmp(method,"DTM") == 0){ + + int k = atoi(argv[3]); + double thresh = atof(argv[4]); + + char nameo[100]; sprintf(nameo,"%s_DTM_%d_%g",cloud,k,thresh); + ofstream output(nameo); vector densities; double M = 0; + + cout << "Computing DTM..." << endl; + for(int i = 0; i < nb; i++){ + if( (int) floor( 100*((double) i)/((double) nb)+1 ) %10 == 0 ){cout << "\r" << floor( 100*((double) i)/((double) nb) +1) << "%" << flush;} + double density = 0; + sort(distances[i].begin(),distances[i].end()); + for(int j = 0; j < k; j++) + density += pow(distances[i][j],2); + density = sqrt(k)/sqrt(density); + if(density >= M) M = density; + densities.push_back(density); + + } + cout << endl << " Done." << endl; + + for(int i = 0; i < nb; i++){ + if(densities[i] >= thresh*M){ + for(int j = 0; j < C[i].size(); j++) output << C[i][j] << " "; + output << endl; + } + } + + output.close(); + + } + + if(strcmp(method,"GaussianDE") == 0){ + + double bandwidth = atof(argv[3]); + double thresh = atof(argv[4]); + + char nameo[100]; sprintf(nameo,"%s_GDE_%g_%g",cloud,bandwidth,thresh); + ofstream output(nameo); vector densities; double M = 0; + + cout << "Computing GDE..." << endl; + for(int i = 0; i < nb; i++){ + if( (int) floor( 100*((double) i)/((double) nb)+1 ) %10 == 0 ){cout << "\r" << floor( 100*((double) i)/((double) nb) +1) << "%" << flush;} + double density = 0; + for(int j = 0; j < nb; j++) + density += exp(-pow(distances[i][j],2)/(2*pow(bandwidth,2))); + density /= sqrt(2*3.1415)*nb*bandwidth; + if(density >= M) M = density; + densities.push_back(density); + + } + cout << endl << " Done." << endl; + + for(int i = 0; i < nb; i++){ + if(densities[i] >= thresh*M){ + for(int j = 0; j < C[i].size(); j++) output << C[i][j] << " "; + output << endl; + } + } + + output.close(); + + } + + return 0; + +} diff --git a/src/Nerve_GIC/example/graph_off.cpp b/src/Nerve_GIC/example/graph_off.cpp new file mode 100644 index 00000000..574d9fc6 --- /dev/null +++ b/src/Nerve_GIC/example/graph_off.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +int main (int argc, char **argv) { + + char* const fileoff = argv[1]; ifstream input(fileoff); char ngoff[100]; sprintf(ngoff,"%s_NG", fileoff); ofstream output(ngoff); + char buf[256]; + vector > NG; + + input.getline(buf, 255); // skip "OFF" + int n, m; + input >> n >> m; + input.getline(buf, 255); // skip "0" + + // read vertices + double x,y,z; vector ng; int nn = n; + while(nn-->0) { + input >> x >> z >> y; NG.push_back(ng); + } + + // read triangles + int d, p, q, s; + while (m-->0) { + input >> d >> p >> q >> s; + NG[p].push_back(q); NG[p].push_back(s); + NG[q].push_back(p); NG[q].push_back(s); + NG[s].push_back(q); NG[s].push_back(p); + } + + for(int i = 0; i < n; i++){ + vector ng = NG[i]; + sort(ng.begin(),ng.end()); vector::iterator iter = unique(ng.begin(),ng.end()); ng.resize(distance(ng.begin(),iter)); + int size = ng.size(); + for(int j = 0; j < size; j++) + output << ng[j] << " "; + output << endl; + } + + return 0; + +} diff --git a/src/Nerve_GIC/example/km.py b/src/Nerve_GIC/example/km.py new file mode 100755 index 00000000..d3cfad59 --- /dev/null +++ b/src/Nerve_GIC/example/km.py @@ -0,0 +1,390 @@ +from __future__ import division +import numpy as np +from collections import defaultdict +import json +import itertools +from sklearn import cluster, preprocessing, manifold +from datetime import datetime +import sys + +class KeplerMapper(object): + # With this class you can build topological networks from (high-dimensional) data. + # + # 1) Fit a projection/lens/function to a dataset and transform it. + # For instance "mean_of_row(x) for x in X" + # 2) Map this projection with overlapping intervals/hypercubes. + # Cluster the points inside the interval + # (Note: we cluster on the inverse image/original data to lessen projection loss). + # If two clusters/nodes have the same members (due to the overlap), then: + # connect these with an edge. + # 3) Visualize the network using HTML and D3.js. + # + # functions + # --------- + # fit_transform: Create a projection (lens) from a dataset + # map: Apply Mapper algorithm on this projection and build a simplicial complex + # visualize: Turns the complex dictionary into a HTML/D3.js visualization + + def __init__(self, verbose=2): + self.verbose = verbose + + self.chunk_dist = [] + self.overlap_dist = [] + self.d = [] + self.nr_cubes = 0 + self.overlap_perc = 0 + self.clusterer = False + + def fit_transform(self, X, projection="sum", scaler=preprocessing.MinMaxScaler()): + # Creates the projection/lens from X. + # + # Input: X. Input features as a numpy array. + # Output: projected_X. original data transformed to a projection (lens). + # + # parameters + # ---------- + # projection: Projection parameter is either a string, + # a scikit class with fit_transform, like manifold.TSNE(), + # or a list of dimension indices. + # scaler: if None, do no scaling, else apply scaling to the projection + # Default: Min-Max scaling + + self.scaler = scaler + self.projection = str(projection) + + # Detect if projection is a class (for scikit-learn) + if str(type(projection))[1:6] == "class": #TODO: de-ugly-fy + reducer = projection + if self.verbose > 0: + try: + projection.set_params(**{"verbose":self.verbose}) + except: + pass + print("\n..Projecting data using: \n\t%s\n"%str(projection)) + X = reducer.fit_transform(X) + + # Detect if projection is a string (for standard functions) + if isinstance(projection, str): + if self.verbose > 0: + print("\n..Projecting data using: %s"%(projection)) + # Stats lenses + if projection == "sum": # sum of row + X = np.sum(X, axis=1).reshape((X.shape[0],1)) + if projection == "mean": # mean of row + X = np.mean(X, axis=1).reshape((X.shape[0],1)) + if projection == "median": # mean of row + X = np.median(X, axis=1).reshape((X.shape[0],1)) + if projection == "max": # max of row + X = np.max(X, axis=1).reshape((X.shape[0],1)) + if projection == "min": # min of row + X = np.min(X, axis=1).reshape((X.shape[0],1)) + if projection == "std": # std of row + X = np.std(X, axis=1).reshape((X.shape[0],1)) + + if projection == "dist_mean": # Distance of x to mean of X + X_mean = np.mean(X, axis=0) + X = np.sum(np.sqrt((X - X_mean)**2), axis=1).reshape((X.shape[0],1)) + + # Detect if projection is a list (with dimension indices) + if isinstance(projection, list): + if self.verbose > 0: + print("\n..Projecting data using: %s"%(str(projection))) + X = X[:,np.array(projection)] + + # Scaling + if scaler is not None: + if self.verbose > 0: + print("\n..Scaling with: %s\n"%str(scaler)) + X = scaler.fit_transform(X) + + return X + + def map(self, projected_X, inverse_X=None, clusterer=cluster.DBSCAN(eps=0.5,min_samples=3), nr_cubes=10, overlap_perc=0.1): + # This maps the data to a simplicial complex. Returns a dictionary with nodes and links. + # + # Input: projected_X. A Numpy array with the projection/lens. + # Output: complex. A dictionary with "nodes", "links" and "meta information" + # + # parameters + # ---------- + # projected_X projected_X. A Numpy array with the projection/lens. Required. + # inverse_X Numpy array or None. If None then the projection itself is used for clustering. + # clusterer Scikit-learn API compatible clustering algorithm. Default: DBSCAN + # nr_cubes Int. The number of intervals/hypercubes to create. + # overlap_perc Float. The percentage of overlap "between" the intervals/hypercubes. + + start = datetime.now() + + # Helper function + def cube_coordinates_all(nr_cubes, nr_dimensions): + # Helper function to get origin coordinates for our intervals/hypercubes + # Useful for looping no matter the number of cubes or dimensions + # Example: if there are 4 cubes per dimension and 3 dimensions + # return the bottom left (origin) coordinates of 64 hypercubes, + # as a sorted list of Numpy arrays + # TODO: elegance-ify... + l = [] + for x in range(nr_cubes): + l += [x] * nr_dimensions + return [np.array(list(f)) for f in sorted(set(itertools.permutations(l,nr_dimensions)))] + + nodes = defaultdict(list) + links = defaultdict(list) + complex = {} + self.nr_cubes = nr_cubes + self.clusterer = clusterer + self.overlap_perc = overlap_perc + + if self.verbose > 0: + print("Mapping on data shaped %s using dimensions\n"%(str(projected_X.shape))) + + # If inverse image is not provided, we use the projection as the inverse image (suffer projection loss) + if inverse_X is None: + inverse_X = projected_X + + # We chop up the min-max column ranges into 'nr_cubes' parts + self.chunk_dist = (np.max(projected_X, axis=0) - np.min(projected_X, axis=0))/nr_cubes + + # We calculate the overlapping windows distance + self.overlap_dist = self.overlap_perc * self.chunk_dist + + # We find our starting point + self.d = np.min(projected_X, axis=0) + + # Use a dimension index array on the projected X + # (For now this uses the entire dimensionality, but we keep for experimentation) + di = np.array([x for x in range(projected_X.shape[1])]) + + # Prefix'ing the data with ID's + ids = np.array([x for x in range(projected_X.shape[0])]) + projected_X = np.c_[ids,projected_X] + inverse_X = np.c_[ids,inverse_X] + + # Subdivide the projected data X in intervals/hypercubes with overlap + if self.verbose > 0: + total_cubes = len(cube_coordinates_all(nr_cubes,projected_X.shape[1])) + print("Creating %s hypercubes."%total_cubes) + + for i, coor in enumerate(cube_coordinates_all(nr_cubes,di.shape[0])): + # Slice the hypercube + hypercube = projected_X[ np.invert(np.any((projected_X[:,di+1] >= self.d[di] + (coor * self.chunk_dist[di])) & + (projected_X[:,di+1] < self.d[di] + (coor * self.chunk_dist[di]) + self.chunk_dist[di] + self.overlap_dist[di]) == False, axis=1 )) ] + + if self.verbose > 1: + print("There are %s points in cube_%s / %s with starting range %s"% + (hypercube.shape[0],i,total_cubes,self.d[di] + (coor * self.chunk_dist[di]))) + + # If at least one sample inside the hypercube + if hypercube.shape[0] > 0: + # Cluster the data point(s) in the cube, skipping the id-column + # Note that we apply clustering on the inverse image (original data samples) that fall inside the cube. + inverse_x = inverse_X[[int(nn) for nn in hypercube[:,0]]] + + clusterer.fit(inverse_x[:,1:]) + + if self.verbose > 1: + print("Found %s clusters in cube_%s\n"%(np.unique(clusterer.labels_[clusterer.labels_ > -1]).shape[0],i)) + + #Now for every (sample id in cube, predicted cluster label) + for a in np.c_[hypercube[:,0],clusterer.labels_]: + if a[1] != -1: #if not predicted as noise + cluster_id = str(coor[0])+"_"+str(i)+"_"+str(a[1])+"_"+str(coor)+"_"+str(self.d[di] + (coor * self.chunk_dist[di])) # TODO: de-rudimentary-ify + nodes[cluster_id].append( int(a[0]) ) # Append the member id's as integers + else: + if self.verbose > 1: + print("Cube_%s is empty.\n"%(i)) + + # Create links when clusters from different hypercubes have members with the same sample id. + candidates = itertools.combinations(nodes.keys(),2) + for candidate in candidates: + # if there are non-unique members in the union + if len(nodes[candidate[0]]+nodes[candidate[1]]) != len(set(nodes[candidate[0]]+nodes[candidate[1]])): + links[candidate[0]].append( candidate[1] ) + + # Reporting + if self.verbose > 0: + nr_links = 0 + for k in links: + nr_links += len(links[k]) + print("\ncreated %s edges and %s nodes in %s."%(nr_links,len(nodes),str(datetime.now()-start))) + + complex["nodes"] = nodes + complex["links"] = links + complex["meta"] = self.projection + + return complex + + def visualize(self, complex, color_function="", path_html="mapper_visualization_output.html", title="My Data", + graph_link_distance=30, graph_gravity=0.1, graph_charge=-120, custom_tooltips=None, width_html=0, + height_html=0, show_tooltips=True, show_title=True, show_meta=True, res=0,gain=0,minimum=0,maximum=0): + # Turns the dictionary 'complex' in a html file with d3.js + # + # Input: complex. Dictionary (output from calling .map()) + # Output: a HTML page saved as a file in 'path_html'. + # + # parameters + # ---------- + # color_function string. Not fully implemented. Default: "" (distance to origin) + # path_html file path as string. Where to save the HTML page. + # title string. HTML page document title and first heading. + # graph_link_distance int. Edge length. + # graph_gravity float. "Gravity" to center of layout. + # graph_charge int. charge between nodes. + # custom_tooltips None or Numpy Array. You could use "y"-label array for this. + # width_html int. Width of canvas. Default: 0 (full width) + # height_html int. Height of canvas. Default: 0 (full height) + # show_tooltips bool. default:True + # show_title bool. default:True + # show_meta bool. default:True + + # Format JSON for D3 graph + json_s = {} + json_s["nodes"] = [] + json_s["links"] = [] + k2e = {} # a key to incremental int dict, used for id's when linking + + for e, k in enumerate(complex["nodes"]): + # Tooltip and node color formatting, TODO: de-mess-ify + if custom_tooltips is not None: + tooltip_s = "

Cluster %s

"%k + " ".join(str(custom_tooltips[complex["nodes"][k][0]]).split(" ")) + if color_function == "Lens average": + tooltip_i = int(30*(custom_tooltips[complex["nodes"][k][0]]-minimum)/(maximum-minimum)) + json_s["nodes"].append({"name": str(k), "tooltip": tooltip_s, "group": 2 * int(np.log(complex["nodes"][k][2])), "color": tooltip_i}) + else: + json_s["nodes"].append({"name": str(k), "tooltip": tooltip_s, "group": 2 * int(np.log(len(complex["nodes"][k]))), "color": str(k.split("_")[0])}) + else: + tooltip_s = "

Cluster %s

Contains %s members."%(k,len(complex["nodes"][k])) + json_s["nodes"].append({"name": str(k), "tooltip": tooltip_s, "group": 2 * int(np.log(len(complex["nodes"][k]))), "color": str(k.split("_")[0])}) + k2e[k] = e + for k in complex["links"]: + for link in complex["links"][k]: + json_s["links"].append({"source": k2e[k], "target":k2e[link],"value":1}) + + # Width and height of graph in HTML output + if width_html == 0: + width_css = "100%" + width_js = 'document.getElementById("holder").offsetWidth-20' + else: + width_css = "%spx" % width_html + width_js = "%s" % width_html + if height_html == 0: + height_css = "100%" + height_js = 'document.getElementById("holder").offsetHeight-20' + else: + height_css = "%spx" % height_html + height_js = "%s" % height_html + + # Whether to show certain UI elements or not + if show_tooltips == False: + tooltips_display = "display: none;" + else: + tooltips_display = "" + + if show_meta == False: + meta_display = "display: none;" + else: + meta_display = "" + + if show_title == False: + title_display = "display: none;" + else: + title_display = "" + + with open(path_html,"wb") as outfile: + html = """ + + + %s | KeplerMapper + + + +
+

%s

+

+ Lens
%s

+ Length of intervals
%s

+ Overlap percentage
%s%%

+ Color Function
%s +

+
+ + """%(title,width_css, height_css, title_display, meta_display, tooltips_display, title,complex["meta"],res,gain*100,color_function,width_js,height_js,graph_charge,graph_link_distance,graph_gravity,json.dumps(json_s)) + outfile.write(html.encode("utf-8")) + if self.verbose > 0: + print("\nWrote d3.js graph to '%s'"%path_html) \ No newline at end of file diff --git a/src/Nerve_GIC/example/mapper.cpp b/src/Nerve_GIC/example/mapper.cpp new file mode 100755 index 00000000..05dd87dd --- /dev/null +++ b/src/Nerve_GIC/example/mapper.cpp @@ -0,0 +1,179 @@ +#include "mapper.h" +#define SHIFT 1 +#define DELTA 1 +#define RESOLUTION 1 + +int main(int argc, char** argv){ + + if(argc <= 10 || argc >= 12){cout << "./mapper " <<\ + " " << endl; return 0;} + + char* const cloud = argv[1]; + char* const funct = argv[2]; + bool normalized = atoi(argv[6]); + char* const graph = argv[4]; + int mask; + char* const covering = argv[7]; + + Cover I; AdjacencyMatrix G; Cloud C; + MapperElements M; + + cout << "Reading input cloud from file " << cloud << "..." << endl; + C = read_cloud(cloud); + cout << " Done." << endl; + + double r,g; char namefunc[100]; + + if (strcmp(funct, "function:") == 0){ + char* const func = argv[3]; + cout << "Reading input filter from file " << func << "..." << endl; + read_function_from_file(func,C); sprintf(namefunc,"%s",func); + } + if (strcmp(funct, "coordinate:") == 0){ + int number = atoi(argv[3]); + cout << "Using coordinate " << number << " as a filter..." << endl; + read_coordinate(number,C); sprintf(namefunc,"Coordinate %d",number); + } + if (strcmp(funct, "eccentricity:") == 0){ + char* const matrix = argv[3]; + cout << "Computing eccentricity with distance matrix " << matrix << "..." << endl; + compute_eccentricity(C,matrix); sprintf(namefunc,"eccentricity"); + } + cout << " Done." << endl; + + if (strcmp(graph, "graph:") == 0){ + char* const graph_name = argv[5]; + cout << "Reading neighborhood graph from file " << graph_name << "..." << endl; + G = build_neighborhood_graph_from_file(C,graph_name); + } + if (strcmp(graph, "percentage:") == 0){ + double delta = atof(argv[5]); + char name1[100]; sprintf(name1, "%s_dist", cloud); char* const name2 = name1; + cout << "Computing neighborhood graph with delta percentage = " << delta << "..." << endl; + G = build_neighborhood_graph_from_scratch_with_percentage(C,delta,name2,normalized); + } + if (strcmp(graph, "parameter:") == 0){ + double delta = atof(argv[5]); + char name1[100]; sprintf(name1, "%s_dist", cloud); char* const name2 = name1; + cout << "Computing neighborhood graph with delta parameter = " << delta << "..." << endl; + G = build_neighborhood_graph_from_scratch_with_parameter(C,delta,name2,normalized); + } + cout << " Done." << endl; + + cout << "Sorting cloud..." << endl; + sort(C.begin(),C.end()); + cout << " Done." << endl; + + if(strcmp(covering,"cover:") == 0){ + char* const cover = argv[8]; mask = atoi(argv[9]); + cout << "Reading user-defined cover from file " << cover << "..." << endl; + I = Cover(cover); I.sort_covering(); + assert (I.intervals[0].first <= C.begin()->func && I.intervals[I.intervals.size()-1].second >= (C.end()-1)->func); + } + else{ + double resolution = atof(argv[8]); r = resolution; + double gain = atof(argv[9]); mask = atoi(argv[10]); g = gain; + cout << "Computing uniform cover with resolution " << resolution << " and gain " << gain << "..." << endl; + I = Cover(C.begin()->func, (C.end()-1)->func, resolution, gain, SHIFT, RESOLUTION); + } + I.proper_value(); + /*for (int i = 0; i < I.res; i++) + cout << " " << I.intervals[i].first << " " << I.intervals[i].second << " " << I.value[i] << endl; + cout << " Done." << endl;*/ + + map > clusters; vector dum; dum.clear(); + for(int i = 0; i < G.size(); i++) clusters.insert(pair >(C[i],dum)); vector col(G.size()); + cout << "Computing Mapper nodes..." << endl; + M = MapperElts(G,I,clusters,col); + cout << " Done." << endl; + + cout << "Computing intersections..." << endl; + map colors; colors.clear(); map num; num.clear(); SparseGraph MG; + if(!DELTA) MG = build_mapper_graph(M,colors,num); + else MG = build_mapperDelta_graph(M,G,clusters,colors,num); + cout << " Done." << endl; + + /*cout << "Computing Nerve..." << endl; + SimplicialComplex Nerve = compute_Nerve(C,clusters); + for(int i = 0; i < Nerve.size(); i++){ + for(int j = 0; j < Nerve[i].size(); j++) cout << Nerve[i][j] << " "; + cout << endl; + }*/ + + /*cout << "Computing GIC..." << endl; + SimplicialComplex GIC = compute_GIC(G,clusters); + for(int i = 0; i < GIC.size(); i++){ + for(int j = 0; j < GIC[i].size(); j++) cout << GIC[i][j] << " "; + cout << endl; + }*/ + + //cout << "Checking intersection crossings..." << endl; + //vector > error_pairs = check_intersection_crossing(M,G); + //cout << "Done." << endl; + + cout << "Writing outputs..." << endl; + + char mapp[11] = "mapper.dot"; + char mappg[11] = "mapper.txt"; + ofstream graphic(mapp); graphic << "graph Mapper {" << endl; + ofstream graphicg(mappg); + + double maxv, minv; maxv = numeric_limits::min(); minv = numeric_limits::max(); + for (map::iterator iit = colors.begin(); iit != colors.end(); iit++){ + if(iit->second > maxv){maxv = iit->second;} + if(minv > iit->second){minv = iit->second;} + } + + int k = 0; vector nodes; nodes.clear(); + for (SparseGraph::iterator iit = MG.begin(); iit != MG.end(); iit++){ + if(num[iit->first] > mask){ + nodes.push_back(iit->first); + graphic << iit->first << "[shape=circle fontcolor=black color=black label=\"" + << iit->first /*<< ":" << num[iit->first]*/ << "\" style=filled fillcolor=\"" + << (1-(maxv-colors[iit->first])/(maxv-minv))*0.6 << ", 1, 1\"]" << endl; + k++; + } + } + int ke = 0; + for (SparseGraph::iterator it = MG.begin(); it != MG.end(); it++) + for (int j = 0; j < it->second.size(); j++) + if(num[it->first] > mask && num[it->second[j]] > mask){ + graphic << " " << it->first << " -- " << it->second[j] << " [weight=15];" << endl; ke++;} + graphic << "}"; graphic.close(); + + graphicg << cloud << endl; + graphicg << namefunc << endl; + graphicg << r << " " << g << endl; + graphicg << k << " " << ke << endl; int kk = 0; + for (vector::iterator iit = nodes.begin(); iit != nodes.end(); iit++){ + graphicg << kk << " " << colors[*iit] << " " << num[*iit] << endl; kk++;} + for (SparseGraph::iterator it = MG.begin(); it != MG.end(); it++) + for (int j = 0; j < it->second.size(); j++) + if(num[it->first] > mask && num[it->second[j]] > mask) + graphicg << it->first << " " << it->second[j] << endl; + graphicg.close(); + + cout << " Done." << endl; + + char command[100]; + sprintf(command,"neato %s -Tpdf -o mapper.pdf",mapp); system(command); + sprintf(command,"python visu.py"); system(command); + sprintf(command,"firefox mapper_visualization_output.html"); system(command); + sprintf(command,"evince mapper.pdf"); system(command); + sprintf(command,"rm %s %s mapper_visualization_output.html mapper.pdf",mapp,mappg); system(command); + + /* + if (error_pairs.size() > 0) + for (vector >::iterator it = error_pairs.begin(); it != error_pairs.end(); it++) + if( find(nodes.begin(),nodes.end(),it->first)!=nodes.end() && find(nodes.begin(),nodes.end(),it->second)!=nodes.end() ) + graphic << " " << it->first << " -- " << it->second << " [weight=15];" << endl; + */ + + int dim = C[0].coord.size(); + if (dim <= 3){ + plotNG(dim,G); + plotCover(dim,G,col,I); + } + +return 0; +} diff --git a/src/Nerve_GIC/example/mapper_parameter.cpp b/src/Nerve_GIC/example/mapper_parameter.cpp new file mode 100644 index 00000000..2302d164 --- /dev/null +++ b/src/Nerve_GIC/example/mapper_parameter.cpp @@ -0,0 +1,165 @@ +#include "mapper.h" +#define ETA 0.001 +#define VERBOSE 1 +#define DELTA 1 +#define RESOLUTION 1 +#define CONSTANT 10 + +double GetUniform(){ + static default_random_engine re; + static uniform_real_distribution Dist(0,1); + return Dist(re); +} + +void SampleWithoutReplacement(int populationSize, int sampleSize, vector & samples){ + int& n = sampleSize; int& N = populationSize; + int t = 0; int m = 0; double u; + while (m < n){ + u = GetUniform(); + if ( (N - t)*u >= n - m ) + t++; + else{samples[m] = t; t++; m++;} + } +} + +int main(int argc, char** argv){ + + if(argc <= 7 || argc >= 9){cout << "./param " << \ + " " << endl; return 0;} + + char* const cloud = argv[1]; + char* const funct = argv[2]; + bool VNE = atoi(argv[4]); + double g = atof(argv[5]); + char* const method = argv[6]; + + Cloud C; + + if(VERBOSE) cout << "Reading input cloud from file " << cloud << "..." << endl; + C = read_cloud(cloud); int n = C.size(); + if(VERBOSE) cout << " Done." << endl; + + if (strcmp(funct, "function:") == 0){ + char* const func = argv[3]; + if(VERBOSE) cout << "Reading input filter from file " << func << "..." << endl; + read_function_from_file(func,C); + } + if (strcmp(funct, "coordinate:") == 0){ + int number = atoi(argv[3]); + if(VERBOSE) cout << "Using coordinate " << number << " as a filter..." << endl; + read_coordinate(number,C); + } + if (strcmp(funct, "eccentricity:") == 0){ + char* const matrix = argv[3]; + cout << "Computing eccentricity with distance matrix " << matrix << "..." << endl; + compute_eccentricity(C,matrix); + } + if(VERBOSE) cout << " Done." << endl; + + double maxf = C[0].func; double minf = C[0].func; + for(int i = 1; i < n; i++){ maxf = max(maxf,C[i].func); minf = min(minf,C[i].func);} + + double delta = 0; double lip = 0; + + if (strcmp(method, "subsampling:") == 0){ + + char name[100]; + if(VNE) sprintf(name,"%s_VNdist",cloud); + else sprintf(name,"%s_dist",cloud); + ifstream input(name, ios::out | ios::binary); + vector > dist; dist.clear(); double d; + + if(input.good()){ + cout << "Reading distances..." << endl; + for(int i = 0; i < n; i++){ + vector dis; dis.clear(); + for (int j = 0; j < n; j++){input.read((char*) &d,8); dis.push_back(d);} + dist.push_back(dis); + } + input.close(); + } + else{ + cout << "Computing distances..." << endl; vector V; + input.close(); ofstream output(name, ios::out | ios::binary); + if(VNE){ + int dim = C[0].coord.size(); + for(int i = 0; i < dim; i++){ + double meani = 0; + for (int j = 0; j < n; j++) meani += C[j].coord[i]/n; + double vari = 0; + for (int j = 0; j < n; j++) vari += pow(C[j].coord[i]-meani,2)/n; + V.push_back(vari); + } + } + + for(int i = 0; i < n; i++){ + if( (int) floor( 100*((double) i)/((double) n)+1 ) %10 == 0 ){cout << "\r" << floor( 100*((double) i)/((double) n) +1) << "%" << flush;} + vector dis; dis.clear(); + for (int j = 0; j < n; j++){ + if(!VNE){d = C[i].EuclideanDistance(C[j]); dis.push_back(d);} + else{d = C[i].VarianceNormalizedEuclideanDistance(C[j],V); dis.push_back(d);} + output.write((char*) &d,8); + } + dist.push_back(dis); + } + output.close(); + } + + cout << "Done." << endl; + + int m = floor(n/pow(log(n)/log(CONSTANT),1+ETA)); m = min(m,n-1); + + if(VERBOSE) cout << "dimension = " << C[0].coord.size() << endl << "n = " << n << " and s(n) = " << m << endl; + if(VERBOSE) cout << "range = [" << minf << ", " << maxf << "]" << endl; + + vector samples(m); int N = atoi(argv[7]); + #pragma omp parallel for + for (int i = 0; i < N; i++){ + SampleWithoutReplacement(n,m,samples); + double hausdorff_dist = 0; + for (int j = 0; j < n; j++){ + Point p = C[j]; + double mj = dist[j][samples[0]]; + double Mi = abs(p.func-C[0].func)/(dist[j][0]); + for (int k = 1; k < m; k++){mj = min(mj, dist[j][samples[k]]);} + for (int k = j+1; k < n; k++){Mi = max(Mi, abs(p.func-C[k].func)/(dist[j][k]));} + hausdorff_dist = max(hausdorff_dist, mj); lip = max(lip,Mi); + } + delta += hausdorff_dist; + } + delta /= N; + + } + + if (strcmp(method, "graph:") == 0){ + char* const graph_name = argv[7]; + cout << "Reading neighborhood graph from file " << graph_name << "..." << endl; + pair P = compute_delta_from_file(C,graph_name); + delta = P.first; lip = P.second; + } + + if(VERBOSE) cout << "lip = " << lip << endl; + + if(VERBOSE) cout << "delta = " << delta << endl; + else cout << delta << endl; + + if(VERBOSE) cout << "g = " << g << endl; + else cout << g << endl; + + if(!DELTA){ + if(VERBOSE){ + if(RESOLUTION) cout << "r = " << lip*delta/g << endl; + else cout << "r = " << floor(g*(maxf-minf)/(lip*delta*(1-g))) << endl;} + else{ + if(RESOLUTION) cout << lip*delta/g << endl; + else cout << floor(g*(maxf-minf)/(lip*delta*(1-g))) << endl;}} + else{ + if(VERBOSE){ + if(RESOLUTION) cout << "r = " << lip*delta << endl; + else cout << "r = " << floor((maxf-minf)/(lip*delta*(1-g))) << endl;} + else{ + if(RESOLUTION) cout << lip*delta << endl; + else cout << floor((maxf-minf)/(lip*delta*(1-g))) << endl;}} + + return 0; +} diff --git a/src/Nerve_GIC/example/quantile.cpp b/src/Nerve_GIC/example/quantile.cpp new file mode 100644 index 00000000..14dee5f2 --- /dev/null +++ b/src/Nerve_GIC/example/quantile.cpp @@ -0,0 +1,48 @@ +#include "mapper.h" + +bool myComp(const pair & P1, const pair & P2){ + if(P1.first == P2.first) return (P1.second <= P2.second); + else return P1.first < P2.first; +} + +int main(int argc, char** argv){ + + if(argc <= 3 || argc >= 5){cout << "./quant " << endl; return 0;} + + char* const file = argv[1]; + bool token = atoi(argv[2]); + double value = atof(argv[3]); + vector V; + vector > Q; + + double v; ifstream input(file); string line; + while(getline(input,line)){ + stringstream stream(line); stream >> v; V.push_back(v); + } + + int N = V.size(); + + if(!token){ + int count = 0; + for(int i = 0; i < N; i++) + if(V[i] <= value) + count++; + cout << count*1.0/N << endl; + } + else{ + for(int i = 0; i < N; i++){ + int counti = 0; + for(int j = 0; j < N; j++) + if(V[j] <= V[i]) + counti++; + Q.push_back(pair(counti*1.0/N,V[i])); + } + sort(Q.begin(),Q.end(),myComp); + int ind = 0; + while(ind < N && Q[ind].first <= value) ind++; + if(ind==N) cout << Q[ind-1].second << endl; + else cout << Q[ind].second << endl; + } + + return 0; +} diff --git a/src/Nerve_GIC/example/visu.py b/src/Nerve_GIC/example/visu.py new file mode 100755 index 00000000..1324e08b --- /dev/null +++ b/src/Nerve_GIC/example/visu.py @@ -0,0 +1,43 @@ +import km +import numpy as np +from collections import defaultdict + +network = {} +mapper = km.KeplerMapper(verbose=0) +data = np.zeros((3,3)) +projected_data = mapper.fit_transform( data, projection="sum", scaler=None ) + +f = open('mapper.txt','r') +nodes = defaultdict(list) +links = defaultdict(list) +custom = defaultdict(list) + +dat = f.readline() +lens = f.readline() +param = [float(i) for i in f.readline().split(" ")] + +nums = [int(i) for i in f.readline().split(" ")] +num_nodes = nums[0] +num_edges = nums[1] + +for i in range(0,num_nodes): + point = [float(j) for j in f.readline().split(" ")] + nodes[ str(int(point[0])) ] = [ int(point[0]), point[1], int(point[2]) ] + links[ str(int(point[0])) ] = [] + custom[ int(point[0]) ] = point[1] + +m = min([custom[i] for i in range(0,num_nodes)]) +M = max([custom[i] for i in range(0,num_nodes)]) + +for i in range(0,num_edges): + edge = [int(j) for j in f.readline().split(" ")] + links[ str(edge[0]) ].append( str(edge[1]) ) + links[ str(edge[1]) ].append( str(edge[0]) ) + +network["nodes"] = nodes +network["links"] = links +network["meta"] = lens + +mapper.visualize(network, color_function = "Lens average", path_html="mapper_visualization_output.html", title=dat, +graph_link_distance=30, graph_gravity=0.1, graph_charge=-120, custom_tooltips=custom, width_html=0, +height_html=0, show_tooltips=True, show_title=True, show_meta=True, res=param[0],gain=param[1], minimum=m,maximum=M) \ No newline at end of file -- cgit v1.2.3