From: erg Date: Wed, 13 Jul 2005 20:35:21 +0000 (+0000) Subject: Commit new files for hierarchical neato, plus changed makefiles. X-Git-Tag: LAST_LIBGRAPH~32^2~7459 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=58c611ed54cec49829f4fda2551b85c6f9063a27;p=graphviz Commit new files for hierarchical neato, plus changed makefiles. Note that unless DIGCOLA is defined, the .c files below will be empty compilation units. --- diff --git a/lib/neatogen/Makefile.am b/lib/neatogen/Makefile.am index be754761b..0353cf551 100644 --- a/lib/neatogen/Makefile.am +++ b/lib/neatogen/Makefile.am @@ -14,13 +14,15 @@ AM_CPPFLAGS = \ noinst_HEADERS = adjust.h edges.h geometry.h heap.h hedges.h info.h mem.h \ neato.h poly.h neatoprocs.h simple.h site.h voronoi.h \ bfs.h closest.h conjgrad.h defs.h dijkstra.h embed_graph.h kkutils.h \ - matrix_ops.h pca.h stress.h + matrix_ops.h pca.h stress.h quad_prog_solver.h noinst_LTLIBRARIES = libneatogen.la libneatogen_la_SOURCES = adjust.c circuit.c edges.c find_ints.c geometry.c \ heap.c hedges.c info.c neatoinit.c intersect.c legal.c lu.c matinv.c \ memory.c poly.c printvis.c site.c solve.c neatosplines.c stuff.c \ voronoi.c stress.c kkutils.c matrix_ops.c embed_graph.c dijkstra.c \ - conjgrad.c pca.c closest.c bfs.c constraint.c + conjgrad.c pca.c closest.c bfs.c constraint.c quad_prog_solve.c \ + smart_ini_x.c constrained_majorization.c opt_arrangement.c \ + compute_hierarchy.c EXTRA_DIST = Makefile.old diff --git a/lib/neatogen/Makefile.old b/lib/neatogen/Makefile.old index 20c58e04e..e054437d0 100644 --- a/lib/neatogen/Makefile.old +++ b/lib/neatogen/Makefile.old @@ -18,7 +18,8 @@ HDRS = adjust.h edges.h geometry.h heap.h hedges.h info.h mem.h \ neato.h neatoprocs.h poly.h simple.h site.h voronoi.h NOBJS = stress.o kkutils.o pca.o matrix_ops.o embed_graph.o dijkstra.o \ - conjgrad.o closest.o bfs.o + conjgrad.o closest.o bfs.o compute_hierarchy.o opt_arrangement.o \ + smart_ini_x.o constrained_majorization.o quad_prog_solve.o OBJS = adjust.o circuit.o edges.o find_ints.o geometry.o heap.o hedges.o \ info.o neatoinit.o intersect.o legal.o lu.o matinv.o memory.o poly.o \ printvis.o site.o solve.o neatosplines.o stuff.o voronoi.o \ diff --git a/lib/neatogen/compute_hierarchy.c b/lib/neatogen/compute_hierarchy.c new file mode 100644 index 000000000..683b3b286 --- /dev/null +++ b/lib/neatogen/compute_hierarchy.c @@ -0,0 +1,135 @@ +/********************************************************** +* This software is part of the graphviz package * +* http://www.graphviz.org/ * +* * +* Copyright (c) 1994-2004 AT&T Corp. * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Corp. * +* * +* Information and Software Systems Research * +* AT&T Research, Florham Park NJ * +**********************************************************/ + +#include +#ifdef DIGCOLA +#include "kkutils.h" + +static int* given_levels = NULL; +/* + * This function partitions the graph nodes into levels + * according to the minimizer of the hierarchy energy. + * + * To allow more flexibility we define a new level only + * when the hierarchy energy shows a significant jump + * (to compensate for noise). + * This is controlled by two parameters: 'abs_tol' and + * 'relative_tol'. The smaller these two are, the more + * levels we'll get. + * More speciffically: + * We never consider gaps smaller than 'abs_tol' + * Additionally, we never consider gaps smaller than 'abs_tol'* + * + * The output is an ordering of the nodes according to + * their levels, as follows: + * First level: + * ordering[0],ordering[1],...ordering[levels[0]-1] + * Second level: + * ordering[levels[0]],ordering[levels[0]+1],...ordering[levels[1]-1] + * ... + * Last level: + * ordering[levels[num_levels-1]],ordering[levels[num_levels-1]+1],...ordering[n-1] + * + * Hence, the nodes were partitioned into 'num_levels'+1 + * levels. + * + * Please note that 'ordering[levels[i]]' contains the first node at level i+1, + * and not the last node of level i. + */ +double +compute_hierarchy(vtx_data* graph, int n, double abs_tol, double relative_tol, + double* given_coords, int** orderingp, int** levelsp, int* num_levelsp) +{ + double* y; + int i; + double spread; + int use_given_levels=FALSE; + int* ordering; + int* levels; + double tol; /* node 'i' precedes 'j' in hierachy iff y[i]-y[j]>tol */ + double hierarchy_span; + int num_levels; + + /* compute optimizer of hierarchy energy: 'y' */ + if (given_coords) { + y = given_coords; + } + else { + y = N_GNEW(n,double); + compute_y_coords(graph, n, y, n); + } + + /* sort nodes accoridng to their y-ordering */ + *orderingp = ordering = N_NEW(n, int); + for (i=0; i=0; + } + } + if (use_given_levels) { + for (i=0; i tol) { + num_levels++; + } + } + *num_levelsp = num_levels; + if (num_levels==0) { + *levelsp = levels = N_GNEW(1, int); + levels[0] = n; + } + else { + *levelsp = levels = N_GNEW(num_levels, int); + int count=0; + for (i=1; i tol) { + levels[count++] = i; + } + } + } + if (!given_coords) { + free(y); + } + + return spread; +} + +#endif /* DIGCOLA */ + diff --git a/lib/neatogen/constrained_majorization.c b/lib/neatogen/constrained_majorization.c new file mode 100644 index 000000000..9d948aaa8 --- /dev/null +++ b/lib/neatogen/constrained_majorization.c @@ -0,0 +1,481 @@ +/********************************************************** +* This software is part of the graphviz package * +* http://www.graphviz.org/ * +* * +* Copyright (c) 1994-2004 AT&T Corp. * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Corp. * +* * +* Information and Software Systems Research * +* AT&T Research, Florham Park NJ * +**********************************************************/ + +#include "digcola.h" +#ifdef DIGCOLA +#include +#include +#include +#include +#include +#include "stress.h" +#include "dijkstra.h" +#include "bfs.h" +#include "matrix_ops.h" +#include "kkutils.h" +#include "conjgrad.h" +#include "quad_prog_solver.h" +#include "matrix_ops.h" + +#define localConstrMajorIterations 15 +#define levels_sep_tol 1e-1 + +int +stress_majorization_with_hierarchy( + vtx_data* graph, /* Input graph in sparse representation */ + int n, /* Number of nodes */ + int nedges_graph, /* Number of edges */ + double** d_coords, /* Coordinates of nodes (output layout) */ + int dim, /* Dimemsionality of layout */ + int smart_ini, /* smart initialization */ + int model, /* difference model */ + int maxi, /* max iterations */ + double levels_gap +) +{ + int iterations = 0; /* Output: number of iteration of the process */ + + /************************************************* + ** Computation of full, dense, unrestricted k-D ** + ** stress minimization by majorization ** + ** This function imposes HIERARCHY CONSTRAINTS ** + *************************************************/ + + int i,j,k; + bool directionalityExist = FALSE; + float * lap1 = NULL; + float * dist_accumulator = NULL; + float * tmp_coords = NULL; + float ** b = NULL; +#ifdef NONCORE + FILE * fp=NULL; +#endif + double * degrees = NULL; + float * lap2=NULL; + int lap_length; + float * f_storage=NULL; + float ** coords=NULL; + + double conj_tol=tolerance_cg; /* tolerance of Conjugate Gradient */ + float ** unpackedLap = NULL; + CMajEnv *cMajEnv = NULL; + clock_t start_time; + double y_0; + int length; + DistType diameter; + float * Dij=NULL; + /* to compensate noises, we never consider gaps smaller than 'abs_tol' */ + double abs_tol=1e-2; + /* Additionally, we never consider gaps smaller than 'abs_tol'* */ + double relative_tol=levels_sep_tol; + int *ordering=NULL, *levels=NULL; + double hierarchy_spread; + float constant_term; + int count; + double degree; + int step; + float val; + double old_stress, new_stress; + bool converged; + int len; + int num_levels; + float *hierarchy_boundaries; + + if (graph[0].edists!=NULL) { + for (i=0; i2) { + /* the dim==2 case is handled below */ + stress_majorization_kD_mkernel(graph, n, nedges_graph, d_coords+1, dim-1, smart_ini, model, 15); + /* now copy the y-axis into the (dim-1)-axis */ + for (i=0; i0) { + /* ensure that levels are separated in the initial layout */ + double displacement = 0; + int stop; + for (i=0; idiameter) { + diameter = (int)Dij[i]; + } + } + + if (!smart_ini) { + /* for numerical stability, scale down layout */ + /* No Jiggling, might conflict with constraints */ + double max=1; + for (i=0; imax) { + max=fabs(d_coords[i][j]); + } + } + } + for (i=0; i0) { + int length = n+n*(n-1)/2; + double sum1, sum2, scale_ratio; + int count; + sum1=(float)(n*(n-1)/2); + sum2=0; + for (count=0, i=0; imax_nodes_in_mem) { + #define FILENAME "tmp_Dij$$$.bin" + fp = fopen(FILENAME, "wb"); + fwrite(lap2, sizeof(float), lap_length, fp); + fclose(fp); + } +#endif + + /************************* + ** Layout optimization ** + *************************/ + + b = N_GNEW (dim, float*); + b[0] = N_GNEW (dim*n, float); + for (k=1; k=FLT_MAX || dist_accumulator[j]<0) { + dist_accumulator[j]=0; + } + } + + count++; /* save place for the main diagonal entry */ + degree=0; + for (j=0; jmax_nodes_in_mem) { + /* restore lap2 from disk */ + fsetpos(fp, &pos); + fread(lap2, sizeof(float), lap_length, fp); + } +#endif + for (k=0; k0.001) { + fprintf(stderr,"Diff stress vals: %lf %lf (iteration #%d)\n", mat_stress, new_stress,iterations); + } + } +#endif + /* check for convergence */ + converged = fabs(new_stress-old_stress)/fabs(old_stress+1e-10) < Epsilon; + converged = converged || (iterations>1 && new_stress>old_stress); + /* in first iteration we allowed stress increase, which + * might result ny imposing constraints + */ + old_stress = new_stress; + + for (k=0; k +#include +#include +#include +#include +#include +#include "matrix_ops.h" +#include "kkutils.h" +#include "quad_prog_solver.h" + +#define quad_prog_tol 1e-2 + +float** +unpackMatrix(float * packedMat, int n) +{ + float **mat; + int i,j,k; + + mat = N_GNEW(n, float*); + mat[0] = N_GNEW(n*n, float); + set_vector_valf(n*n, 0, mat[0]); + for (i=1; i=max_in_level) { + /* we are entering a new level */ + level++; + if (level==num_levels) { + /* last_level */ + max_in_level=n; + } + else { + max_in_level = levels[level]; + } + lower_bound = i>0 ? place[ordering[i-1]]+levels_gap : (float)-1e9; + quicksort_placef(place, ordering, i, max_in_level-1); + } + + node = ordering[i]; + if (place[node]n; + float *place=coords[cur_axis]; + float **lap=e->A; + int * ordering=e->ordering; + int * levels=e->levels; + int num_levels=e->num_levels; + int i,j; + float new_place_i; + bool converged=false; + float upper_bound, lower_bound; + int node; + int left, right; + float cur_place; + float des_place_block; + float block_deg; + float toBlockConnectivity; + float * lap_node; + int block_len; + int first_next_level; + int level=-1, max_in_level=0; + float* desired_place; + float* prefix_desired_place; + float* suffix_desired_place; + int* block; + int* lev; + int counter; + + if (max_iterations<=0) { + return 0; + } + if (levels_gap!=0) { + return constrained_majorization_new_with_gaps(e, b, coords,cur_axis,dims, max_iterations, hierarchy_boundaries, levels_gap); + } + + /* ensureMonotonicOrderingWithGaps(place, n, ordering, levels, num_levels); */ + ensureMonotonicOrdering(place, n, ordering); + /* it is important that in 'ordering' nodes are always sorted by layers, + * and within a layer by 'place' + */ + + /* the desired place of each individual node in the current block */ + desired_place = e->fArray1; + /* the desired place of each prefix of current block */ + prefix_desired_place = e->fArray2; + /* the desired place of each suffix of current block */ + suffix_desired_place = e->fArray3; + /* current block (nodes connected by active constraints) */ + block = e->iArray1; + + lev = e->iArray2; /* level of each node */ + for (i=0; i=max_in_level) { + /* we are entering a new level */ + level++; + if (level==num_levels) { + /* last_level */ + max_in_level=n; + } + else { + max_in_level = levels[level]; + } + } + node = ordering[i]; + lev[node]=level; + } + + for (counter=0; counter cur_place) { + block[block_len++] = node; + } + } + } + + /* loop through block and compute desired places of its prefixes */ + des_place_block = 0; + block_deg = 0; + for (i=0; i=0; i--) { + node = block[i]; + toBlockConnectivity = 0; + lap_node = lap[node]; + for (j=i+1; j0 ? prefix_desired_place[i-1] : suffix_des_place; + /* limit moves to ensure that the prefix is placed before the suffix */ + if (suffix_des_place cur_place) { + prefix_des_place = cur_place; + } + suffix_des_place = prefix_des_place; + } + else if (prefix_des_place>cur_place) { + prefix_des_place = suffix_des_place; + } + } + movement = (block_len-i)*fabs(suffix_des_place-cur_place) + i*fabs(prefix_des_place-cur_place); + if (movement>max_movement) { + max_movement = movement; + best_i = i; + } + } + /* Actually move prefix and suffix */ + if (best_i>=0) { + suffix_des_place = suffix_desired_place[best_i]; + prefix_des_place = best_i>0 ? prefix_desired_place[best_i-1] : suffix_des_place; + + /* compute right border of feasible move */ + if (right>=n) { + /* no nodes after current block */ + upper_bound = 1e9; + } + else { + upper_bound = place[ordering[right]]; + } + suffix_des_place = MIN(suffix_des_place, upper_bound); + prefix_des_place = MAX(prefix_des_place, lower_bound); + + /* limit moves to ensure that the prefix is placed before the suffix */ + if (suffix_des_place cur_place) { + prefix_des_place = cur_place; + } + suffix_des_place = prefix_des_place; + } + else if (prefix_des_place>cur_place) { + prefix_des_place = suffix_des_place; + } + } + + /* move prefix: */ + for (i=0; in; + float** lap = e->A; + int* ordering = e->ordering; + int* levels = e->levels; + int num_levels = e->num_levels; + float new_place_i; + bool converged=false; + float upper_bound, lower_bound; + int node; + int left, right; + float cur_place; + float des_place_block; + float block_deg; + float toBlockConnectivity; + float * lap_node; + float * desired_place; + float * prefix_desired_place; + float * suffix_desired_place; + int* block; + int block_len; + int first_next_level; + int * lev; + int level=-1, max_in_level=0; + int counter; + float* gap; + float total_gap, target_place; + + if (max_iterations<=0) { + return 0; + } + + ensureMonotonicOrderingWithGaps(place, n, ordering, levels, num_levels, levels_gap); + /* it is important that in 'ordering' nodes are always sorted by layers, + * and within a layer by 'place' + */ + + /* the desired place of each individual node in the current block */ + desired_place = e->fArray1; + /* the desired place of each prefix of current block */ + prefix_desired_place = e->fArray2; + /* the desired place of each suffix of current block */ + suffix_desired_place = e->fArray3; + /* current block (nodes connected by active constraints) */ + block = e->iArray1; + + lev = e->iArray2; /* level of each node */ + for (i=0; i=max_in_level) { + /* we are entering a new level */ + level++; + if (level==num_levels) { + /* last_level */ + max_in_level=n; + } + else { + max_in_level = levels[level]; + } + } + node = ordering[i]; + lev[node]=level; + } + + /* displacement of block's nodes from block's reference point */ + gap = e->fArray4; + + for (counter=0; counterlev[right-1]) { + /* we are entering a new level */ + target_place += levels_gap; /* note that 'levels_gap' may be negative */ + total_gap += levels_gap; + } + node = ordering[right]; +#if 0 + if (place[node]!=target_place) +#endif + /* not sure if this is better than 'place[node]!=target_place' */ + if (fabs(place[node]-target_place)>1e-9) + { + break; + } + gap[node] = place[node]-cur_place; + } + + /* compute desired place of block's reference point according + * to each node in the block: + */ + for (i=left; i cur_place) { + block[block_len++] = node; + } + } + } + + /* loop through block and compute desired places of its prefixes */ + des_place_block = 0; + block_deg = 0; + for (i=0; i=0; i--) { + node = block[i]; + toBlockConnectivity = 0; + lap_node = lap[node]; + for (j=i+1; j0 ? prefix_desired_place[i-1] : suffix_des_place; + /* limit moves to ensure that the prefix is placed before the suffix */ + if (suffix_des_place cur_place) { + prefix_des_place = cur_place; + } + suffix_des_place = prefix_des_place; + } + else if (prefix_des_place>cur_place) { + prefix_des_place = suffix_des_place; + } + } + movement = (block_len-i)*fabs(suffix_des_place-cur_place) + i*fabs(prefix_des_place-cur_place); + if (movement>max_movement) { + max_movement = movement; + best_i = i; + } + } + /* Actually move prefix and suffix */ + if (best_i>=0) { + suffix_des_place = suffix_desired_place[best_i]; + prefix_des_place = best_i>0 ? prefix_desired_place[best_i-1] : suffix_des_place; + + /* compute right border of feasible move */ + if (right>=n) { + /* no nodes after current block */ + upper_bound = 1e9; + } + else { + /* notice that we have to deduct 'gap[block[block_len-1]]' + * since all computations should be relative to + * the block's reference point + */ + if (lev[ordering[right]]>lev[ordering[right-1]]) { + upper_bound = place[ordering[right]]-levels_gap-gap[block[block_len-1]]; + } + else { + upper_bound = place[ordering[right]]-gap[block[block_len-1]]; + } + } + suffix_des_place = MIN(suffix_des_place, upper_bound); + prefix_des_place = MAX(prefix_des_place, lower_bound); + + /* limit moves to ensure that the prefix is placed before the suffix */ + if (suffix_des_place cur_place) { + prefix_des_place = cur_place; + } + suffix_des_place = prefix_des_place; + } + else if (prefix_des_place>cur_place) { + prefix_des_place = suffix_des_place; + } + } + + /* move prefix: */ + for (i=0; ilev[ordering[right-1]]) { + lower_bound = place[block[block_len-1]]+levels_gap; + } + else { + lower_bound = place[block[block_len-1]]; + } + + + /* reorder 'ordering' to reflect change of places + * Note that it is enough to reorder the level where + * the split was done + */ +#if 0 + int max_in_level, min_in_level; + + level = lev[best_i]; + if (level==num_levels) { + /* last_level */ + max_in_level = MIN(right,n); + } + else { + max_in_level = MIN(right,levels[level]); + } + if (level==0) { + /* first level */ + min_in_level = MAX(left,0); + } + else { + min_in_level = MAX(left,levels[level-1]); + } +#endif + for (i=left; ilev[ordering[right-1]]) { + lower_bound = place[block[block_len-1]]+levels_gap; + } + else { + lower_bound = place[block[block_len-1]]; + } + } + } + orthog1f(n, place); /* for numerical stability, keep ||place|| small */ + computeHierarchyBoundaries(place, n, ordering, levels, num_levels, hierarchy_boundaries); + } + + return counter; +} + +void +deleteCMajEnv(CMajEnv *e) +{ + free (e->A[0]); + free (e->A); + free (e->lev); + free (e->fArray1); + free (e->fArray2); + free (e->fArray3); + free (e->fArray4); + free (e->iArray1); + free (e->iArray2); + free (e->iArray3); + free (e->iArray4); + free (e); +} + +CMajEnv* +initConstrainedMajorization(float* packedMat, int n, int* ordering, + int* levels, int num_levels) +{ + int i, level=-1, start_of_level_above=0; + CMajEnv *e = GNEW(CMajEnv); + e->A=NULL; + e->n=n; + e->ordering=ordering; + e->levels=levels; + e->num_levels=num_levels; + e->A = unpackMatrix(packedMat,n); + e->lev = N_GNEW(n, int); + for (i=0; in; i++) { + if (i>=start_of_level_above) { + level++; + start_of_level_above=(level==num_levels)?e->n:levels[level]; + } + e->lev[ordering[i]]=level; + } + e->fArray1 = N_GNEW(n,float); + e->fArray2 = N_GNEW(n,float); + e->fArray3 = N_GNEW(n,float); + e->fArray4 = N_GNEW(n,float); + e->iArray1 = N_GNEW(n,int); + e->iArray2 = N_GNEW(n,int); + e->iArray3 = N_GNEW(n,int); + e->iArray4 = N_GNEW(n,int); + return e; +} +#endif /* DIGCOLA */ + diff --git a/lib/neatogen/quad_prog_solver.h b/lib/neatogen/quad_prog_solver.h new file mode 100644 index 000000000..e0084b2fd --- /dev/null +++ b/lib/neatogen/quad_prog_solver.h @@ -0,0 +1,58 @@ +/********************************************************** +* This software is part of the graphviz package * +* http://www.graphviz.org/ * +* * +* Copyright (c) 1994-2004 AT&T Corp. * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Corp. * +* * +* Information and Software Systems Research * +* AT&T Research, Florham Park NJ * +**********************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _CMAJ_H_ +#define _CMAJ_H_ + +#ifdef DIGCOLA + +typedef struct { + float **A; + int n; + int *lev; + int *iArray1; + int *iArray2; + int *iArray3; + int *iArray4; + float *fArray1; + float *fArray2; + float *fArray3; + float *fArray4; + float *A_r; + int *ordering; + int *levels; + int num_levels; +}CMajEnv; + +extern CMajEnv* initConstrainedMajorization(float *, int, int*, int*, int); + +extern int constrained_majorization_new(CMajEnv*, float*, float**, + int, int, int, float*, float); + +extern int constrained_majorization_new_with_gaps(CMajEnv*, float*, float**, + int, int, int, float*, float); +extern void deleteCMajEnv(CMajEnv *e); + +extern float** unpackMatrix(float * packedMat, int n); + +#endif + +#endif /* _CMAJ_H_ */ + +#ifdef __cplusplus +} +#endif diff --git a/lib/neatogen/smart_ini_x.c b/lib/neatogen/smart_ini_x.c new file mode 100644 index 000000000..5500aeda7 --- /dev/null +++ b/lib/neatogen/smart_ini_x.c @@ -0,0 +1,385 @@ +/********************************************************** +* This software is part of the graphviz package * +* http://www.graphviz.org/ * +* * +* Copyright (c) 1994-2004 AT&T Corp. * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Corp. * +* * +* Information and Software Systems Research * +* AT&T Research, Florham Park NJ * +**********************************************************/ + +#include "digcola.h" +#ifdef DIGCOLA +#include "kkutils.h" +#include "matrix_ops.h" +#include "conjgrad.h" + +static void +standardize(double* orthog, int nvtxs) +{ + double len, avg = 0; + int i; + for (i=0; i=n) { + neigs=n; + } + + for (i=0; i0 ? (DistType)sqrt(diff) : 0; + } + } + } + + /* Compute the balance vector: */ + for (i=0; i=y[j]) { + balance[i]+=Dij[i][j]*(-lap[i][j]); // w_{ij}*delta_{ij} + } + else { + balance[i]-=Dij[i][j]*(-lap[i][j]); // w_{ij}*delta_{ij} + } + } + } + + for (converged=false,iterations2=0; iterations2<200 && !converged; iterations2++) { + conjugate_gradient_f(lap, y, balance, n, conj_tol, n, true); + converged=true; + for (i=0; i=y[j]) { + b+=Dij[i][j]*(-lap[i][j]); + + } + else { + b-=Dij[i][j]*(-lap[i][j]); + + } + } + if ((b != balance[i]) && (fabs(1-b/balance[i])>1e-5)) { + converged=false; + balance[i]=b; + } + } + } + + for (i=0; i