1 // xsplit.cc: implementation of class form_finder
2 //////////////////////////////////////////////////////////////////////////
3 //
4 // Copyright 1990-2012 John Cremona
5 //                     Marcus Mo     (parallel code)
6 //
7 // This file is part of the eclib package.
8 //
9 // eclib is free software; you can redistribute it and/or modify it
10 // under the terms of the GNU General Public License as published by the
11 // Free Software Foundation; either version 2 of the License, or (at your
12 // option) any later version.
13 //
14 // eclib is distributed in the hope that it will be useful, but WITHOUT
15 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
17 // for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with eclib; if not, write to the Free Software Foundation,
21 // Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 //
23 //////////////////////////////////////////////////////////////////////////
24 
25 #include <unistd.h>  // for unlink() (not needed on linux)
26 
27 #define USE_SPARSE 1
28 #define ECLIB_INT_NUM_THREADS 8
29 #define ECLIB_RECURSION_DIM_LIMIT 5821
30 //#define ECLIB_MULTITHREAD_DEBUG
31 #include <eclib/logger.h>
32 #include <eclib/xsplit.h>
33 #include <eclib/smatrix_elim.h>
34 subspace sparse_combine(const subspace& s1, const subspace& s2);
35 mat sparse_restrict(const mat& m, const subspace& s);
36 smat restrict_mat(const smat& m, const subspace& s);
37 
38 // CLASS FORM_FINDER (was called splitter)
39 
form_finder(splitter_base * hh,int plus,int maxd,int mind,int dualflag,int bigmatsflag,int v)40 form_finder::form_finder(splitter_base* hh, int plus, int maxd, int mind, int dualflag, int bigmatsflag, int v)
41 :h(hh), plusflag(plus), dual(dualflag), bigmats(bigmatsflag), verbose(v),
42 gnfcount(0), maxdepth(maxd), mindepth(mind)
43 {
44   eclogger::setLevel( verbose );
45   denom1 = h->matden();
46   dimen  = h->matdim();
47 
48   // Create and initialise new data object as root node
49   // passing a constant pointer of current form_finder object
50   // to data class constructor
51   root = new ff_data( this );
52 
53   // Set initial values
54   // form_finder class is a friend of ff_data class
55   // so may access private members
56   root -> subdim_ = dimen;
57 
58   targetdim = 1;
59   if( !plusflag ) {           // full conjmat not needed when plusflag is true
60     targetdim=2;
61     if( bigmats ) root -> conjmat_ = h -> s_opmat(-1,dual);
62   }
63 }
64 
~form_finder(void)65 form_finder::~form_finder(void) {
66   // Decendants of root node will be recursively deleted
67   // if they have not already been deleted during find()
68   // All dynamically created objects (subspaces) held
69   // in each data node will also be deleted.
70   delete root;
71 }
72 
73 // This is only used when bigmats==1 and we compute opmats on the entire ambient space:
make_opmat(long i,ff_data & data)74 void form_finder::make_opmat(long i, ff_data &data) {
75   data.the_opmat_ = h -> s_opmat(i,dual,verbose);
76 }
77 
make_submat(ff_data & data)78 void form_finder::make_submat( ff_data &data ) {
79   // Cache current data node depth
80   long depth = data.depth_;
81 
82   if( bigmats ) {
83     // fetch the_opmat from file, or compute
84     make_opmat(depth,data);
85 
86     if( depth == 0 ) data.submat_ = data.the_opmat_;
87     else {
88 	    ECLOG(1) << "restricting the_opmat to subspace...";
89 	    data.submat_ = restrict_mat(data.the_opmat_,*(data.abs_space_));
90 	    ECLOG(1) << "done." << endl;
91 	  }
92 
93     data.the_opmat_ = smat(0,0); // releases its space
94   }
95   else {
96     if( data.submat_.nrows() == 0 ) // else we have it already
97       {
98         if( depth == 0 )
99           data.submat_ = h -> s_opmat(depth,1,verbose);
100         else
101           {
102             //data.submat_ = h -> s_opmat_restricted(depth,*(data.abs_space_),1,verbose);
103             data.submat_ = make_nested_submat(depth,data);
104           }
105       }
106   }
107 }
108 
109 /**
110  * make_nested_submat()
111  *
112  * Computes and returns the submat -- new nested version
113  */
114 
make_nested_submat(long ip,ff_data & data)115 smat form_finder::make_nested_submat(long ip, ff_data &data)
116 {
117   long depth = data.depth_;  // current depth
118   long subdim = data.subdim_;  // current dimension
119   ff_data *d = &data; // Pointer to nodes
120   int i, j, level;
121 
122   ECLOG(1) << "Computing operator of size " << subdim
123            << " at depth " << depth << "..." << flush;
124 
125   // first we go up the chain, composing pivotal indices
126 
127   vec jlist = iota((scalar)subdim);
128   smat b = d->rel_space_->bas();
129   level = depth;
130   while (level--)
131     {
132       ECLOG(2) << "["<<level<<"]" << flush;
133       jlist = d->rel_space_->pivs()[jlist];
134       d->parent_->child_ = d;
135       d = d->parent_;
136       if(level) b = mult_mod_p(d->rel_space_->bas(), b, MODULUS);
137     }
138 
139   // now compute the matrix of images of the j'th generator for j in jlist
140   ECLOG(2) << " basis done..." << flush;
141   smat m = h -> s_opmat_cols(ip, jlist, 0);
142   ECLOG(2) << " sub-opmat done..." << flush;
143   m = mult_mod_p(m,b,MODULUS);
144   ECLOG(1) <<" opmat done."<<endl;
145   return m;
146 }
147 
148 
149 /**
150  * go_down()
151  *
152  * Initiates creation of new subspace; data stored in new
153  * data node. Data node passed as parameter will become the
154  * _parent_ of this new data node.
155  */
go_down(ff_data & data,long eig,int last)156 void form_finder::go_down(ff_data &data, long eig, int last) {
157   // Cache current depth
158   long depth = data.depth_;
159 
160   // Locate required child w.r.t test eigenvalue
161   ff_data *child = data.child( eig );
162 
163   // Set new depth
164   child -> depth_ = depth + 1;
165 
166   SCALAR eig2 = eig*denom1;
167 
168   ECLOG(1) << "Increasing depth to " << depth+1 << ", "
169            << "trying eig = " << eig << "..."
170            << "after scaling, eig =  " << eig2 << "..." << endl;
171   ssubspace s(0);
172 
173   vector<int> submat_dim = dim(data.submat_);
174   stringstream submat_dim_ss;
175   std::copy(submat_dim.begin(),submat_dim.end(),ostream_iterator<int>(submat_dim_ss," "));
176 
177   ECLOG(1) << "Using sparse elimination (size = [ "
178                      << submat_dim_ss.str() << "], density ="
179 		                 << density(data.submat_) << ")..." << flush;
180   ECLOG(3) << "submat = " << data.submat_ << flush;
181 
182   s = eigenspace(data.submat_,eig2); // the relative eigenspace
183 
184   // Increment data usage counter for parent node
185   data.increaseSubmatUsage();
186 
187   // Reset current submat if all children have used it
188   // Save space (will recompute when needed)
189   //if(    ( depth == 0 )
190   //    && ( dim(s) > 0 )
191   //    && ( data.submat_.nrows() > 1000 )
192   //    && ( data.submatUsage_ == data.numChildren_ ) ) {
193   //  data.submat_ = smat(0,0);
194   //}
195 
196   ECLOG(1) << "done (dim = " << dim(s) << ")"<<endl;
197   // ECLOG(1) << ", combining subspaces..." << flush;
198 
199   child -> rel_space_ = new ssubspace(s);
200   // if( depth == 0 )
201   //   child -> abs_space_ = new ssubspace(s);
202   // else
203   //   child -> abs_space_ = new ssubspace(combine( *(data.abs_space_),s ));
204   // ECLOG(1) << "done." << endl;
205 
206   depth++; // Local depth increment (does not effect data nodes)
207 
208   child -> subdim_ = dim( *(child -> rel_space_) );
209 
210   ECLOG(1) << "Eigenvalue " << eig
211            << " has multiplicity " << child -> subdim_ << endl;
212   if(child -> subdim_>0) {
213     ECLOG(0) << " eig " << eig
214              << " gives new subspace at depth " << depth
215              << " of dimension " << child -> subdim_ << endl;
216   }
217 }
218 
go_up(ff_data & data)219 void form_finder::go_up( ff_data &data ) {
220   // Cache pointer to parent data node for access after current node is deleted
221   ff_data *parent = data.parent_;
222 
223 #ifdef ECLIB_MULTITHREAD
224   // Lock parent node with scoped lock
225   boost::mutex::scoped_lock lock( parent -> go_up_lock_ );
226 #ifdef ECLIB_MULTITHREAD_DEBUG
227   ECLOG(1) << "in go_up for eig=" << data.eigenvalue_
228            << " depth=" << data.depth_
229            <<" status=" << data.status_ << std::endl;
230 #endif
231 #endif
232 
233   // Erasing node via children array of parent which calls destructor
234   // of object (ff_data), using eigenvalue as key
235   parent -> childStatus( data.eigenvalue_, COMPLETE );
236   parent -> eraseChild( data.eigenvalue_ );
237 
238 #ifdef ECLIB_MULTITHREAD
239   lock.unlock();
240 
241   // Only last child to complete will execute the following (Detects if parent is root node)
242   if( parent -> complete() && parent -> parent_ != NULL ) go_up( *parent );
243 #endif
244 }
245 
make_basis(ff_data & data)246 void form_finder::make_basis( ff_data &data ) {
247   // Cache data values
248   long depth  = data.depth();
249   long subdim = data.subdim();
250   vector< long > eiglist = data.eiglist();
251 
252   if( subdim != targetdim ) {
253     cout << "error in form_finder::make_basis with eiglist = ";
254     for(int i=0; i<depth; i++)
255       cout << eiglist[i] << ",";
256     cout << "\nfinal subspace has dimension " << subdim << endl;
257     cout << "aborting this branch!" << endl;
258     return;
259   }
260 
261   if(plusflag) {
262     // must treat separately since we did not
263     // define abs_space[0] in order to save space
264     if(depth==0) {
265       data.bplus_    = vec(dimen);
266       data.bplus_[1] = 1;
267 	  }
268     else {
269       //data.bplus_ = getbasis1(data.abs_space_);
270       data.bplus_ = make_basis1(data);
271     }
272 
273     return;
274   }
275 
276   ssubspace* s;
277   if( bigmats ) {
278     s = data.abs_space_;  // only used when depth>0
279   }
280   ssubspace *spm_rel, *spm_abs;
281   SCALAR eig = denom1;
282   // if(depth) eig*=denom(*s);
283   smat subconjmat;            // only used when depth>0
284   if( bigmats ) {
285     subconjmat = (depth) ? restrict_mat(data.conjmat_, *s) : data.conjmat_;
286     // will only be a 2x2 in this case (genus 1 only!)
287   }
288   else {
289     //subconjmat = h->s_opmat_restricted(-1,*s,1,verbose);
290     subconjmat = make_nested_submat(-1,data);
291   }
292 
293   // C++11 loop over two variables (similar to python)
294   // for( int b : { -1,+1 } ) { /* use b as -1 or +1 */ }
295   for(long signeig=+1; signeig>-2; signeig-=2) {
296     SCALAR seig;
297            seig = eig;
298 
299     if(signeig<0) seig = -eig;
300 
301     if(depth) {
302 	    spm_rel = new ssubspace(eigenspace(subconjmat,seig));
303 	    //spm_abs  = new ssubspace(combine(*s,*spm_rel));
304     }
305     else {
306       spm_rel = new ssubspace(eigenspace(subconjmat,seig));
307       //spm_abs = spm_rel;
308     }
309 
310     if(dim(*spm_rel)!=1) {
311       cout << "error in form_finder::makebasis; ";
312       cout << "\nfinal (";
313 
314       if(signeig>0) cout << "+";
315       else cout << "-";
316 
317       cout << ") subspace has dimension " << dim(*spm_rel) << endl;
318       cout << "aborting this branch!" << endl;
319       //delete spm_abs;
320       delete spm_rel;
321       return;
322     }
323 
324     //vec v = getbasis1(spm_abs);
325     vec w = make_basis2(data, spm_rel->bas().as_mat().col(1));
326 
327     if(signeig>0) data.bplus_  = w;
328     else          data.bminus_ = w;
329 
330     //delete spm_abs;
331     delete spm_rel;
332   }
333 }
334 
make_basis2(ff_data & data,const vec & v)335 vec form_finder::make_basis2(ff_data &data, const vec& v)
336 {
337   ff_data *d = &data;
338   int level = data.depth_;
339   vec w = v;
340   while (level--)
341     {
342       w = mult_mod_p(d->rel_space_->bas(), w, MODULUS);
343       d = d->parent_;
344     }
345   return lift(w);
346 }
347 
make_basis1(ff_data & data)348 vec form_finder::make_basis1(ff_data &data)
349 {
350   vec v(1);  v.set(1,1);
351   return make_basis2(data, v);
352 }
353 
getbasis1(const ssubspace * s)354 vec getbasis1(const ssubspace* s)
355 {
356   return lift(basis(*s).as_mat().col(1));
357 }
358 
lift(const vec & v)359 vec lift(const vec& v)
360 {
361 #ifdef MODULAR
362   VEC b=v, bb;
363   if(lift(b,MODULUS,bb))
364     b = bb;
365   else
366     cout << "Unable to lift eigenvector from mod " << MODULUS << endl;
367 #else
368   makeprimitive(b);
369 #endif
370 #ifdef MULTI
371   scalar n=0; // dummy variable to gt the right type in next line
372   return b.shorten(n);
373 #else
374   return b;
375 #endif
376 }
377 
recover(vector<vector<long>> eigs)378 void form_finder::recover(vector< vector<long> > eigs) {
379   for(unsigned int iform=0; iform<eigs.size(); iform++) {
380     if(verbose) {
381 	    cout << "Form number " << iform+1 << " with eigs ";
382 
383       int n = eigs[iform].size();
384       if(n>10) n = 10;
385 
386 	    copy(eigs[iform].begin(), eigs[iform].begin() + n,
387 	       ostream_iterator<long>(cout, " "));
388 	    cout << "..." << endl;
389 	  }
390 
391     splitoff(eigs[iform]);
392   }
393   // Clears all nodes.  This cannot be done automatically since we
394   // don't know how many eigs lists were to be splt off.
395   root -> eraseChildren();
396 }
397 
splitoff(const vector<long> & eigs)398 void form_finder::splitoff(const vector<long>& eigs) {
399 
400   // Always start at root node
401   ff_data *current = root;
402 
403   // Temporary variables
404   long depth  = current -> depth_;
405   long subdim = current -> subdim_;
406 
407   if( verbose ) {
408     cout << "Entering form_finder, depth = " << depth
409          << ", dimension " << subdim << endl;
410   }
411 
412   // Walk down nodes (if any already created) for common branches
413   while( current -> numChildren_ > 0
414          && current -> child(eigs[depth]) != NULL ) {
415 
416     // Update current node pointer
417     current = current -> child(eigs[depth]);
418 
419     // Update data
420     depth  = current -> depth_;
421     subdim = current -> subdim_;
422     if (verbose) {
423       cout << "...increasing depth to " << depth
424            << ", dimension " << subdim << endl;
425     }
426   }
427 
428   // Current node is new branch point
429   // We trim all sub-branches ...
430   current -> eraseChildren();
431 
432   if( verbose ) {
433     cout << "restarting at depth = " << depth << ", "
434          << "dimension " << subdim << endl;
435   }
436 
437   // ... and grow a new branch down to required depth.
438   while( (subdim > targetdim) && (depth < maxdepth) ) {
439     // Get number of possible eigenvalues
440     if( current -> numChildren_ <= 0 ) {
441       vector<long> t_eigs = h->eigrange(depth);
442       current -> setChildren( t_eigs );
443     }
444 
445     // Create new child node
446     ff_data *child = new ff_data( this );
447 
448     // Configure data node ancestry
449     current -> addChild( eigs[depth], *child );
450 
451     // Create submat for current node
452     make_submat( *current );
453 
454     // Proceed to go down
455     go_down(*current,eigs[depth],1);
456 
457     // Update to new values
458     current = child;
459     depth   = current -> depth_;
460     subdim  = current -> subdim_;
461   }
462 
463   // Creating newforms
464   make_basis(*current);
465   h->use(current->bplus_,current->bminus_,eigs);
466 
467   return;
468 }
469 
find()470 void form_finder::find() {
471 #ifdef ECLIB_MULTITHREAD
472   // Set number of threads to use either through default
473   // ECLIB_INT_NUM_THREADS macro defined above, or
474   // ECLIB_EXT_NUM_THREADS environment variable.
475   unsigned int eclib_num_threads = ECLIB_INT_NUM_THREADS;
476 
477   stringstream s;
478   s << getenv("ECLIB_EXT_NUM_THREADS");
479   if( !s.str().empty() ) eclib_num_threads = atoi(s.str().c_str());
480 
481   // Start job queue. We keep job queue local to ensure threads are
482   // not kept busy for longer than necessary.
483   pool.start( eclib_num_threads, verbose );
484 #endif
485 
486   // Proceed in recursive find, passing a node through
487   find( *root );
488 
489 #ifdef ECLIB_MULTITHREAD
490   // Join all threads in threadpool to wait for all jobs to finish
491   // Or detect when all branches of the tree has been traversed
492   pool.close();
493 #endif
494 
495   // Clear all nodes.  This should have been be done automatically but not all nodes are deleted when running in multithreaded mode.
496   root -> eraseChildren();
497 
498   // Now compute all newforms only if recursion has finished
499   if(verbose>1) cout << "Now performing use() on all lists at once" << endl;
500   for( int nf = 0; nf < gnfcount; nf++ ) {
501     h-> use(gbplus[nf],gbminus[nf],gaplist[nf]);
502   }
503 }
504 
find(ff_data & data)505 void form_finder::find( ff_data &data ) {
506   // Cache values of current data
507   long depth  = data.depth();
508   long subdim = data.subdim();
509   vector< long > eiglist = data.eiglist();
510 
511   vector<long> subeiglist(eiglist.begin(),eiglist.begin()+depth);
512 
513   int dimold = h->dimoldpart(subeiglist);
514 
515   stringstream subeiglist_ss;
516   std::copy(subeiglist.begin(),subeiglist.end(),ostream_iterator<long>(subeiglist_ss," "));
517 
518   ECLOG(0) << "In formfinder, depth = " << depth
519            << ", aplist = [ " << subeiglist_ss.str() << "];\t"
520            << "dimsofar=" << subdim
521            << ", dimold=" << dimold
522            << ", dimnew=" << subdim-dimold << "\n";
523 
524   if( dimold == subdim ) {
525     data.setStatus( ALL_OLD );     // Set status of current node
526     ECLOG(0) << "Abandoning a common eigenspace of dimension " << subdim
527              << " which is a sum of oldclasses." << endl;
528     return;   // This branch of the recursion ends: all is old
529   }
530 
531   if( ( subdim == targetdim ) && ( depth > mindepth ) ) {
532     data.setStatus( FOUND_NEW );   // Set status of current node
533     make_basis( data );
534     store(data.bplus_,data.bminus_,subeiglist);
535     return;
536   }
537 
538   if( depth == maxdepth ) {
539     data.setStatus( MAX_DEPTH );
540     if(1) {       // we want to see THIS message whatever the verbosity level!
541       cout << "\nFound a " << subdim << "D common eigenspace\n";
542       cout << "Abandoning, even though oldforms only make up ";
543       cout << dimold << "D of this." << endl;
544     }
545     return;
546   }
547 
548   // Pass data node through to make_submat()
549   // NOTE originally called in go_down(), but relocated here since
550   // it only needs to be called once per node.
551   make_submat(data);
552 
553   // The recursive part:
554   vector<long> t_eigs = h->eigrange(depth);
555   vector<long>::const_iterator apvar = t_eigs.begin();
556 
557   stringstream t_eigs_ss;
558   std::copy(t_eigs.begin(),t_eigs.end(),ostream_iterator<long>(t_eigs_ss," "));
559 
560   ECLOG(0) << "Testing eigenvalues [ " << t_eigs_ss.str()
561            << "] at level " << (depth+1) << endl;
562 
563   // Set children counter
564   data.setChildren( t_eigs );
565 
566   while( apvar != t_eigs.end() ) {
567     ECLOG(1) << "Going down with ap = " << (*apvar) <<endl;
568     long eig = *apvar++;
569 
570     // Initiate new data node, passing a constant reference of the current
571     // form_finder object to the data class constructor
572     ff_data *child = new ff_data( this );
573 
574     // Configure data node ancestry
575     data.addChild( eig, *child );
576 
577 #ifdef ECLIB_MULTITHREAD
578     if( data.subdim_ > ECLIB_RECURSION_DIM_LIMIT ) {
579       // Post newly created child node to threadpool
580       pool.post< ff_data >( *child );
581     }
582     else {
583       // Parallel granularity control. Continue in serial.
584       go_down( data, eig, apvar==t_eigs.end() );
585       if( child -> subdim_ > 0 ) find( *child );
586       //if( child -> status_ != INTERNAL || child -> subdim_ == 0 ) go_up( *child );
587     }
588 #else
589     // Pass through current data node and new test eigenvalue to go_down()
590     go_down( data, eig, apvar==t_eigs.end() );
591 
592     // We pass new child node to find()
593     if( child -> subdim_ > 0 ) find( *child );
594 
595     go_up( *child );
596 #endif
597   }
598 
599 #ifndef ECLIB_MULTITHREAD
600   ECLOG(0) << "Finished at level " << (depth+1) << endl;
601 #endif
602 }
603 
store(vec bp,vec bm,vector<long> eigs)604 void form_finder::store(vec bp, vec bm, vector<long> eigs) {
605 #ifdef ECLIB_MULTITHREAD
606   // Lock function
607   boost::mutex::scoped_lock lock( store_lock );
608 #endif
609 
610   // Store sub-bplus,bminus,eiglists in class level containers
611   gbplus.push_back(bp);
612   gbminus.push_back(bm);
613   gaplist.push_back(eigs);
614 
615   // Increment global counter
616   gnfcount++;
617 
618   // Inform about newform count
619   ECLOG(1) << "Current newform subtotal count at " << gnfcount << endl;
620 }
621 
622 #if (METHOD==2)
sparse_combine(const subspace & s1,const subspace & s2)623 subspace sparse_combine(const subspace& s1, const subspace& s2)
624 {
625   // we assume s1, s2 are subspace mod DEFAULT_MODULUS
626    scalar d=denom(s1)*denom(s2);
627    const smat& sm1(basis(s1)), sm2(basis(s2));
628    const mat&  b = (sm1*sm2).as_mat();
629    const vec&  p = pivots(s1)[pivots(s2)];
630    return subspace(b,p,d);
631    //  return COMBINE(s1,s2);
632 }
633 
sparse_restrict(const mat & m,const subspace & s)634 mat sparse_restrict(const mat& m, const subspace& s)
635 {
636   if(dim(s)==m.nrows()) return m; // trivial special case, s is whole space
637   scalar dd = denom(s);  // will be 1 if s is a mod-p subspace
638   mat b(basis(s));
639   smat sm(m), sb(b);
640   vec piv=pivots(s);
641   smat smr = sm.select_rows(piv);
642   smat ans = smr*sb;
643   int check=0;
644   if(check) {
645     smat left = sm*sb;
646     if(dd!=1) {cout<<"(dd="<<dd<<")"; left.mult_by_scalar_mod_p(dd);}
647     smat right = sb*ans;
648     int ok = eqmodp(left,right);
649     if (!ok)
650     {
651       cout<<"Warning from sparse_restrict: subspace not invariant!\n";
652       cout<<"Difference = \n"<<left-right<<endl;
653     }
654   }
655   check=0;
656   if(check) {
657     int ok = (ans.as_mat()==RESTRICT(m,s));
658     if(!ok)
659     {
660       cout<<"Error in sparse_restrict: sparse result differs from normal!"<<endl;
661     }
662   }
663   return ans.as_mat();
664 }
665 
restrict_mat(const smat & m,const subspace & s)666 smat restrict_mat(const smat& m, const subspace& s)
667 {
668   if(dim(s)==m.nrows()) return m; // trivial special case, s is whole space
669   return mult_mod_p(m.select_rows(pivots(s)),smat(basis(s)),MODULUS);
670 }
671 
672 #endif
673 
674 // end of XSPLIT.CC
675