001/*
002 * $Id: GroebnerBaseDistributedMPI.java 5797 2018-03-26 09:48:51Z kredel $
003 */
004
005package edu.jas.gb;
006
007
008import java.io.IOException;
009import java.util.ArrayList;
010import java.util.Collections;
011import java.util.List;
012import java.util.ListIterator;
013import java.util.concurrent.Semaphore;
014
015import org.apache.log4j.Logger;
016
017import edu.jas.kern.MPIEngine;
018import edu.jas.poly.ExpVector;
019import edu.jas.poly.GenPolynomial;
020import edu.jas.structure.RingElem;
021import edu.jas.util.DistHashTableMPI;
022import edu.jas.util.MPIChannel;
023import edu.jas.util.Terminator;
024import edu.jas.util.ThreadPool;
025
026import mpi.Comm;
027import mpi.MPIException;
028
029
030/**
031 * Groebner Base distributed algorithm with MPI. Implements a distributed memory
032 * parallel version of Groebner bases. Using MPI and pairlist class, distributed
033 * tasks do reduction.
034 * @param <C> coefficient type
035 * @author Heinz Kredel
036 */
037
038public class GroebnerBaseDistributedMPI<C extends RingElem<C>> extends GroebnerBaseAbstract<C> {
039
040
041    private static final Logger logger = Logger.getLogger(GroebnerBaseDistributedMPI.class);
042
043
044    /**
045     * Number of threads to use.
046     */
047    protected final int threads;
048
049
050    /**
051     * Default number of threads.
052     */
053    public static final int DEFAULT_THREADS = 2;
054
055
056    /*
057     * Pool of threads to use. <b>Note:</b> No ComputerThreads for one node
058     * tests
059     */
060    protected transient final ThreadPool pool;
061
062
063    /*
064     * Underlying MPI engine.
065     */
066    protected transient final Comm engine;
067
068
069    /**
070     * Constructor.
071     */
072    public GroebnerBaseDistributedMPI() throws IOException {
073        this(DEFAULT_THREADS);
074    }
075
076
077    /**
078     * Constructor.
079     * @param threads number of threads to use.
080     */
081    public GroebnerBaseDistributedMPI(int threads) throws IOException {
082        this(threads, new ThreadPool(threads));
083    }
084
085
086    /**
087     * Constructor.
088     * @param threads number of threads to use.
089     * @param pool ThreadPool to use.
090     */
091    public GroebnerBaseDistributedMPI(int threads, ThreadPool pool) throws IOException {
092        this(threads, pool, new OrderedPairlist<C>());
093    }
094
095
096    /**
097     * Constructor.
098     * @param threads number of threads to use.
099     * @param pl pair selection strategy
100     */
101    public GroebnerBaseDistributedMPI(int threads, PairList<C> pl) throws IOException {
102        this(threads, new ThreadPool(threads), pl);
103    }
104
105
106    /**
107     * Constructor.
108     * @param threads number of threads to use.
109     * @param pool ThreadPool to use.
110     * @param pl pair selection strategy
111     */
112    public GroebnerBaseDistributedMPI(int threads, ThreadPool pool, PairList<C> pl) throws IOException {
113        super(new ReductionPar<C>(), pl);
114        int size = 0;
115        try {
116            engine = MPIEngine.getCommunicator();
117            size = engine.Size();
118        } catch (MPIException e) {
119            throw new IOException(e);
120        }
121        if (size < 2) {
122            throw new IllegalArgumentException("Minimal 2 MPI processes required, not " + size);
123        }
124        if (threads != size || pool.getNumber() != size) {
125            throw new IllegalArgumentException(
126                            "threads != size: " + threads + " != " + size + ", #pool " + pool.getNumber());
127        }
128        this.threads = threads;
129        this.pool = pool;
130    }
131
132
133    /**
134     * Cleanup and terminate ThreadPool.
135     */
136    @Override
137    public void terminate() {
138        if (pool == null) {
139            return;
140        }
141        //pool.terminate();
142        pool.cancel();
143    }
144
145
146    /**
147     * Distributed Groebner base.
148     * @param modv number of module variables.
149     * @param F polynomial list.
150     * @return GB(F) a Groebner base of F or null, if a IOException occurs or on
151     *         MPI client part.
152     */
153    public List<GenPolynomial<C>> GB(int modv, List<GenPolynomial<C>> F) {
154        try {
155            if (engine.Rank() == 0) {
156                return GBmaster(modv, F);
157            }
158        } catch (MPIException e) {
159            logger.info("GBmaster: " + e);
160            e.printStackTrace();
161            return null;
162        } catch (IOException e) {
163            logger.info("GBmaster: " + e);
164            e.printStackTrace();
165            return null;
166        }
167        pool.terminate(); // not used on clients
168        try {
169            clientPart(0);
170        } catch (IOException e) {
171            logger.info("clientPart: " + e);
172            e.printStackTrace();
173        } catch (MPIException e) {
174            logger.info("clientPart: " + e);
175            e.printStackTrace();
176        }
177        return null;
178    }
179
180
181    /**
182     * Distributed Groebner base, part for MPI master.
183     * @param modv number of module variables.
184     * @param F polynomial list.
185     * @return GB(F) a Groebner base of F or null, if a IOException occurs.
186     */
187    public List<GenPolynomial<C>> GBmaster(int modv, List<GenPolynomial<C>> F)
188                    throws MPIException, IOException {
189        List<GenPolynomial<C>> G = new ArrayList<GenPolynomial<C>>();
190        GenPolynomial<C> p;
191        PairList<C> pairlist = null;
192        boolean oneInGB = false;
193        //int l = F.size();
194        int unused = 0;
195        ListIterator<GenPolynomial<C>> it = F.listIterator();
196        while (it.hasNext()) {
197            p = it.next();
198            if (p.length() > 0) {
199                p = p.monic();
200                if (p.isONE()) {
201                    oneInGB = true;
202                    G.clear();
203                    G.add(p);
204                    //return G; must signal termination to others
205                }
206                if (!oneInGB) {
207                    G.add(p);
208                }
209                if (pairlist == null) {
210                    pairlist = strategy.create(modv, p.ring);
211                    if (!p.ring.coFac.isField()) {
212                        throw new IllegalArgumentException("coefficients not from a field");
213                    }
214                }
215                // theList not updated here
216                if (p.isONE()) {
217                    unused = pairlist.putOne();
218                } else {
219                    unused = pairlist.put(p);
220                }
221            } else {
222                //l--;
223            }
224        }
225        //if (l <= 1) {
226        //return G; must signal termination to others
227        //}
228        logger.info("done pairlist, initialize DHT: " + unused);
229
230        DistHashTableMPI<Integer, GenPolynomial<C>> theList = new DistHashTableMPI<Integer, GenPolynomial<C>>(
231                        engine);
232        theList.init();
233        //logger.info("done DHT: " + theList);
234
235        List<GenPolynomial<C>> al = pairlist.getList();
236        for (int i = 0; i < al.size(); i++) {
237            // no wait required
238            GenPolynomial<C> nn = theList.put(Integer.valueOf(i), al.get(i));
239            if (nn != null) {
240                logger.info("double polynomials " + i + ", nn = " + nn + ", al(i) = " + al.get(i));
241            }
242        }
243
244        Terminator fin = new Terminator(threads - 1);
245        MPIReducerServer<C> R;
246        for (int i = 1; i < threads; i++) {
247            logger.debug("addJob " + i + " of " + threads);
248            MPIChannel chan = new MPIChannel(engine, i); // closed in server
249            R = new MPIReducerServer<C>(i, fin, chan, theList, pairlist);
250            pool.addJob(R);
251        }
252        logger.debug("main loop waiting");
253        fin.waitDone();
254        int ps = theList.size();
255        logger.info("#distributed list = " + ps);
256        // make sure all polynomials arrived: not needed in master
257        // G = (ArrayList)theList.values();
258        G = pairlist.getList();
259        if (ps != G.size()) {
260            logger.info("#distributed list = " + theList.size() + " #pairlist list = " + G.size());
261        }
262        long time = System.currentTimeMillis();
263        List<GenPolynomial<C>> Gp = minimalGB(G); // not jet distributed but threaded
264        time = System.currentTimeMillis() - time;
265        logger.debug("parallel gbmi = " + time);
266        G = Gp;
267        logger.info("theList.terminate()");
268        theList.terminate();
269        logger.info("end" + pairlist);
270        return G;
271    }
272
273
274    /**
275     * GB distributed client.
276     * @param rank of the MPI where the server runs on.
277     * @throws IOException
278     */
279    public void clientPart(int rank) throws IOException, MPIException {
280        if (rank != 0) {
281            throw new UnsupportedOperationException("only master at rank 0 implemented: " + rank);
282        }
283        Comm engine = MPIEngine.getCommunicator();
284
285        DistHashTableMPI<Integer, GenPolynomial<C>> theList = new DistHashTableMPI<Integer, GenPolynomial<C>>();
286        theList.init();
287
288        MPIChannel chan = new MPIChannel(engine, rank);
289
290        MPIReducerClient<C> R = new MPIReducerClient<C>(chan, theList);
291        R.run();
292
293        chan.close();
294        theList.terminate();
295        return;
296    }
297
298
299    /**
300     * Minimal ordered groebner basis.
301     * @param Fp a Groebner base.
302     * @return a reduced Groebner base of Fp.
303     */
304    @SuppressWarnings("unchecked")
305    @Override
306    public List<GenPolynomial<C>> minimalGB(List<GenPolynomial<C>> Fp) {
307        GenPolynomial<C> a;
308        ArrayList<GenPolynomial<C>> G;
309        G = new ArrayList<GenPolynomial<C>>(Fp.size());
310        ListIterator<GenPolynomial<C>> it = Fp.listIterator();
311        while (it.hasNext()) {
312            a = it.next();
313            if (a.length() != 0) { // always true
314                // already monic  a = a.monic();
315                G.add(a);
316            }
317        }
318        if (G.size() <= 1) {
319            return G;
320        }
321
322        ExpVector e;
323        ExpVector f;
324        GenPolynomial<C> p;
325        ArrayList<GenPolynomial<C>> F;
326        F = new ArrayList<GenPolynomial<C>>(G.size());
327        boolean mt;
328
329        while (G.size() > 0) {
330            a = G.remove(0);
331            e = a.leadingExpVector();
332
333            it = G.listIterator();
334            mt = false;
335            while (it.hasNext() && !mt) {
336                p = it.next();
337                f = p.leadingExpVector();
338                mt = e.multipleOf(f);
339            }
340            it = F.listIterator();
341            while (it.hasNext() && !mt) {
342                p = it.next();
343                f = p.leadingExpVector();
344                mt = e.multipleOf(f);
345            }
346            if (!mt) {
347                F.add(a);
348            } else {
349                // System.out.println("dropped " + a.length());
350            }
351        }
352        G = F;
353        if (G.size() <= 1) {
354            return G;
355        }
356        Collections.reverse(G); // important for lex GB
357
358        MiMPIReducerServer<C>[] mirs = (MiMPIReducerServer<C>[]) new MiMPIReducerServer[G.size()];
359        int i = 0;
360        F = new ArrayList<GenPolynomial<C>>(G.size());
361        while (G.size() > 0) {
362            a = G.remove(0);
363            // System.out.println("doing " + a.length());
364            List<GenPolynomial<C>> R = new ArrayList<GenPolynomial<C>>(G.size() + F.size());
365            R.addAll(G);
366            R.addAll(F);
367            mirs[i] = new MiMPIReducerServer<C>(R, a);
368            pool.addJob(mirs[i]);
369            i++;
370            F.add(a);
371        }
372        G = F;
373        F = new ArrayList<GenPolynomial<C>>(G.size());
374        for (i = 0; i < mirs.length; i++) {
375            a = mirs[i].getNF();
376            F.add(a);
377        }
378        return F;
379    }
380
381}
382
383
384/**
385 * Distributed server reducing worker threads.
386 * @param <C> coefficient type
387 */
388
389class MPIReducerServer<C extends RingElem<C>> implements Runnable {
390
391
392    /*
393     * Termination detection coordinator.
394     */
395    private final Terminator finaler;
396
397
398    /*
399     * Underlying MPI engine.
400     */
401    //protected transient final Comm engine;
402
403
404    /*
405     * MPI channel.
406     */
407    private final MPIChannel pairChannel;
408
409
410    /*
411     * GB rank.
412     */
413    final int rank;
414
415
416    /*
417     * Distributed HashTable of polynomials.
418     */
419    private final DistHashTableMPI<Integer, GenPolynomial<C>> theList;
420
421
422    /*
423     * Critical pair list of polynomials.
424     */
425    private final PairList<C> pairlist;
426
427
428    private static final Logger logger = Logger.getLogger(MPIReducerServer.class);
429
430
431    /**
432     * Constructor.
433     * @param r MPI rank of partner.
434     * @param fin termination coordinator to use.
435     * @param c MPI channel to use.
436     * @param dl DHT to use.
437     * @param L pair selection strategy
438     */
439    MPIReducerServer(int r, Terminator fin, MPIChannel c, DistHashTableMPI<Integer, GenPolynomial<C>> dl,
440                    PairList<C> L) {
441        rank = r;
442        finaler = fin;
443        //engine = e;
444        theList = dl;
445        pairlist = L;
446        pairChannel = c;
447        logger.debug("reducer server constructor: "); // + r);
448    }
449
450
451    /**
452     * Main method.
453     */
454    @SuppressWarnings("unchecked")
455    public void run() {
456        logger.debug("reducer server running: "); // + rank);
457        // try {
458        //     pairChannel = new MPIChannel(engine, rank);
459        // } catch (IOException e) {
460        //     e.printStackTrace();
461        //     return;
462        // } catch (MPIException e) {
463        //     e.printStackTrace();
464        //     return;
465        // }
466        if (logger.isInfoEnabled()) {
467            logger.info("reducer server running: pairChannel = " + pairChannel);
468        }
469        Pair<C> pair;
470        GenPolynomial<C> H = null;
471        boolean set = false;
472        boolean goon = true;
473        int polIndex = -1;
474        int red = 0;
475        int sleeps = 0;
476
477        // while more requests
478        while (goon) {
479            // receive request
480            logger.debug("receive request");
481            Object req = null;
482            try {
483                req = pairChannel.receive();
484            } catch (IOException e) {
485                goon = false;
486                e.printStackTrace();
487            } catch (MPIException e) {
488                goon = false;
489                e.printStackTrace();
490            } catch (ClassNotFoundException e) {
491                goon = false;
492                e.printStackTrace();
493            }
494            //logger.debug("received request, req = " + req);
495            if (req == null) {
496                goon = false;
497                break;
498            }
499            if (!(req instanceof GBTransportMessReq)) {
500                goon = false;
501                break;
502            }
503
504            // find pair
505            logger.debug("find pair");
506            while (!pairlist.hasNext()) { // wait
507                if (!set) {
508                    finaler.beIdle();
509                    set = true;
510                }
511                if (!finaler.hasJobs() && !pairlist.hasNext()) {
512                    goon = false;
513                    break;
514                }
515                try {
516                    sleeps++;
517                    if (sleeps % 10 == 0) {
518                        logger.info(" reducer is sleeping");
519                    }
520                    Thread.sleep(100);
521                } catch (InterruptedException e) {
522                    goon = false;
523                    break;
524                }
525            }
526            if (!pairlist.hasNext() && !finaler.hasJobs()) {
527                goon = false;
528                break; //continue; //break?
529            }
530            if (set) {
531                set = false;
532                finaler.notIdle();
533            }
534
535            pair = pairlist.removeNext();
536            /*
537             * send pair to client, receive H
538             */
539            logger.debug("send pair = " + pair);
540            GBTransportMess msg = null;
541            if (pair != null) {
542                msg = new GBTransportMessPairIndex(pair);
543            } else {
544                msg = new GBTransportMess(); //End();
545                // goon ?= false;
546            }
547            try {
548                pairChannel.send(msg);
549            } catch (IOException e) {
550                e.printStackTrace();
551                goon = false;
552                break;
553            } catch (MPIException e) {
554                e.printStackTrace();
555                goon = false;
556                break;
557            }
558            logger.debug("#distributed list = " + theList.size());
559            Object rh = null;
560            try {
561                rh = pairChannel.receive();
562            } catch (IOException e) {
563                e.printStackTrace();
564                goon = false;
565                break;
566            } catch (MPIException e) {
567                e.printStackTrace();
568                goon = false;
569                break;
570            } catch (ClassNotFoundException e) {
571                e.printStackTrace();
572                goon = false;
573                break;
574            }
575            //logger.debug("received H polynomial");
576            if (rh == null) {
577                if (pair != null) {
578                    pair.setZero();
579                }
580            } else if (rh instanceof GBTransportMessPoly) {
581                // update pair list
582                red++;
583                H = ((GBTransportMessPoly<C>) rh).pol;
584                if (logger.isDebugEnabled()) {
585                    logger.debug("H = " + H);
586                }
587                if (H == null) {
588                    if (pair != null) {
589                        pair.setZero();
590                    }
591                } else {
592                    if (H.isZERO()) {
593                        pair.setZero();
594                    } else {
595                        if (H.isONE()) {
596                            polIndex = pairlist.putOne();
597                            //GenPolynomial<C> nn = 
598                            theList.putWait(Integer.valueOf(polIndex), H);
599                            goon = false;
600                            break;
601                        }
602                        polIndex = pairlist.put(H);
603                        // use putWait ? but still not all distributed
604                        //GenPolynomial<C> nn = 
605                        theList.putWait(Integer.valueOf(polIndex), H);
606                    }
607                }
608            }
609        }
610        logger.info("terminated, done " + red + " reductions");
611
612        /*
613         * send end mark to client
614         */
615        logger.debug("send end");
616        try {
617            pairChannel.send(new GBTransportMessEnd());
618        } catch (IOException e) {
619            if (logger.isDebugEnabled()) {
620                e.printStackTrace();
621            }
622        } catch (MPIException e) {
623            if (logger.isDebugEnabled()) {
624                e.printStackTrace();
625            }
626        }
627        finaler.beIdle();
628        pairChannel.close();
629    }
630
631}
632
633
634/**
635 * Distributed clients reducing worker threads.
636 */
637
638class MPIReducerClient<C extends RingElem<C>> implements Runnable {
639
640
641    private final MPIChannel pairChannel;
642
643
644    private final DistHashTableMPI<Integer, GenPolynomial<C>> theList;
645
646
647    private final ReductionPar<C> red;
648
649
650    private static final Logger logger = Logger.getLogger(MPIReducerClient.class);
651
652
653    /**
654     * Constructor.
655     * @param pc MPI communication channel.
656     * @param dl DHT to use.
657     */
658    MPIReducerClient(MPIChannel pc, DistHashTableMPI<Integer, GenPolynomial<C>> dl) {
659        pairChannel = pc;
660        theList = dl;
661        red = new ReductionPar<C>();
662    }
663
664
665    /**
666     * Main run method.
667     */
668    @SuppressWarnings("unchecked")
669    public void run() {
670        logger.debug("reducer client running");
671        Pair<C> pair = null;
672        GenPolynomial<C> pi, pj, ps;
673        GenPolynomial<C> S;
674        GenPolynomial<C> H = null;
675        //boolean set = false;
676        boolean goon = true;
677        int reduction = 0;
678        //int sleeps = 0;
679        Integer pix, pjx, psx;
680
681        while (goon) {
682            /* protocol:
683             * request pair, process pair, send result
684             */
685            // pair = (Pair) pairlist.removeNext();
686            Object req = new GBTransportMessReq();
687            logger.debug("send request");
688            try {
689                pairChannel.send(req);
690            } catch (IOException e) {
691                goon = false;
692                e.printStackTrace();
693                break;
694            } catch (MPIException e) {
695                goon = false;
696                e.printStackTrace();
697                break;
698            }
699            logger.debug("receive pair, goon");
700            Object pp = null;
701            try {
702                pp = pairChannel.receive();
703            } catch (IOException e) {
704                goon = false;
705                if (logger.isDebugEnabled()) {
706                    e.printStackTrace();
707                }
708                break;
709            } catch (MPIException e) {
710                goon = false;
711                if (logger.isDebugEnabled()) {
712                    e.printStackTrace();
713                }
714                break;
715            } catch (ClassNotFoundException e) {
716                goon = false;
717                e.printStackTrace();
718            }
719            if (logger.isDebugEnabled()) {
720                logger.debug("received pair = " + pp);
721            }
722            H = null;
723            if (pp == null) { // should not happen
724                continue;
725            }
726            if (pp instanceof GBTransportMessEnd) {
727                goon = false;
728                continue;
729            }
730            if (pp instanceof GBTransportMessPair || pp instanceof GBTransportMessPairIndex) {
731                pi = pj = ps = null;
732                if (pp instanceof GBTransportMessPair) {
733                    pair = ((GBTransportMessPair<C>) pp).pair;
734                    if (pair != null) {
735                        pi = pair.pi;
736                        pj = pair.pj;
737                        //logger.debug("pair: pix = " + pair.i 
738                        //               + ", pjx = " + pair.j);
739                    }
740                }
741                if (pp instanceof GBTransportMessPairIndex) {
742                    pix = ((GBTransportMessPairIndex) pp).i;
743                    pjx = ((GBTransportMessPairIndex) pp).j;
744                    psx = ((GBTransportMessPairIndex) pp).s;
745                    pi = theList.getWait(pix);
746                    pj = theList.getWait(pjx);
747                    ps = theList.getWait(psx);
748                    //logger.info("pix = " + pix + ", pjx = " + pjx + ", psx = " + psx);
749                }
750                if (pi != null && pj != null) {
751                    S = red.SPolynomial(pi, pj);
752                    //System.out.println("S   = " + S);
753                    if (S.isZERO()) {
754                        // pair.setZero(); does not work in dist
755                    } else {
756                        if (logger.isDebugEnabled()) {
757                            logger.info("ht(S) = " + S.leadingExpVector());
758                        }
759                        H = red.normalform(theList, S);
760                        reduction++;
761                        if (H.isZERO()) {
762                            // pair.setZero(); does not work in dist
763                        } else {
764                            H = H.monic();
765                            if (logger.isInfoEnabled()) {
766                                logger.info("ht(H) = " + H.leadingExpVector());
767                            }
768                        }
769                    }
770                } else {
771                    logger.info("pi = " + pi + ", pj = " + pj + ", ps = " + ps);
772                }
773            }
774
775            // send H or must send null
776            if (logger.isDebugEnabled()) {
777                logger.debug("#distributed list = " + theList.size());
778                logger.debug("send H polynomial = " + H);
779            }
780            try {
781                pairChannel.send(new GBTransportMessPoly<C>(H));
782            } catch (IOException e) {
783                goon = false;
784                e.printStackTrace();
785            } catch (MPIException e) {
786                goon = false;
787                e.printStackTrace();
788            }
789        }
790        logger.info("terminated, done " + reduction + " reductions");
791        pairChannel.close();
792    }
793}
794
795
796/**
797 * Distributed server reducing worker threads for minimal GB Not jet distributed
798 * but threaded.
799 */
800
801class MiMPIReducerServer<C extends RingElem<C>> implements Runnable {
802
803
804    private final List<GenPolynomial<C>> G;
805
806
807    private GenPolynomial<C> H;
808
809
810    private final Semaphore done = new Semaphore(0);
811
812
813    private final Reduction<C> red;
814
815
816    private static final Logger logger = Logger.getLogger(MiMPIReducerServer.class);
817
818
819    /**
820     * Constructor.
821     * @param G polynomial list.
822     * @param p polynomial.
823     */
824    MiMPIReducerServer(List<GenPolynomial<C>> G, GenPolynomial<C> p) {
825        this.G = G;
826        H = p;
827        red = new ReductionPar<C>();
828    }
829
830
831    /**
832     * getNF. Blocks until the normal form is computed.
833     * @return the computed normal form.
834     */
835    public GenPolynomial<C> getNF() {
836        try {
837            done.acquire(); //done.P();
838        } catch (InterruptedException e) {
839        }
840        return H;
841    }
842
843
844    /**
845     * Main run method.
846     */
847    public void run() {
848        if (logger.isDebugEnabled()) {
849            logger.debug("ht(H) = " + H.leadingExpVector());
850        }
851        H = red.normalform(G, H); //mod
852        done.release(); //done.V();
853        if (logger.isDebugEnabled()) {
854            logger.debug("ht(H) = " + H.leadingExpVector());
855        }
856        // H = H.monic();
857    }
858}