COIN-OR::LEMON - Graph Library

source: lemon-0.x/src/work/marci/lp/lp_solver_wrapper_3.h @ 1097:c91e765266d7

Last change on this file since 1097:c91e765266d7 was 1097:c91e765266d7, checked in by marci, 15 years ago

A proposal or test implementation for linear expression`

File size: 19.0 KB
Line 
1// -*- c++ -*-
2#ifndef LEMON_LP_SOLVER_WRAPPER_H
3#define LEMON_LP_SOLVER_WRAPPER_H
4
5///\ingroup misc
6///\file
7///\brief Dijkstra algorithm.
8
9// #include <stdio.h>
10#include <stdlib.h>
11#include <iostream>
12#include <map>
13// #include <stdio>
14//#include <stdlib>
15extern "C" {
16#include "glpk.h"
17}
18
19#include <iostream>
20#include <vector>
21#include <string>
22#include <list>
23#include <memory>
24#include <utility>
25
26//#include <sage_graph.h>
27//#include <lemon/list_graph.h>
28//#include <lemon/graph_wrapper.h>
29#include <lemon/invalid.h>
30//#include <bfs_dfs.h>
31//#include <stp.h>
32//#include <lemon/max_flow.h>
33//#include <augmenting_flow.h>
34//#include <iter_map.h>
35
36using std::cout;
37using std::cin;
38using std::endl;
39
40namespace lemon {
41 
42  /// \addtogroup misc
43  /// @{
44
45  /// \brief A partitioned vector with iterable classes.
46  ///
47  /// This class implements a container in which the data is stored in an
48  /// stl vector, the range is partitioned into sets and each set is
49  /// doubly linked in a list.
50  /// That is, each class is iterable by lemon iterators, and any member of
51  /// the vector can bo moved to an other class.
52  template <typename T>
53  class IterablePartition {
54  protected:
55    struct Node {
56      T data;
57      int prev; //invalid az -1
58      int next;
59    };
60    std::vector<Node> nodes;
61    struct Tip {
62      int first;
63      int last;
64    };
65    std::vector<Tip> tips;
66  public:
67    /// The classes are indexed by integers from \c 0 to \c classNum()-1.
68    int classNum() const { return tips.size(); }
69    /// This lemon style iterator iterates through a class.
70    class ClassIt;
71    /// Constructor. The number of classes is to be given which is fixed
72    /// over the life of the container.
73    /// The partition classes are indexed from 0 to class_num-1.
74    IterablePartition(int class_num) {
75      for (int i=0; i<class_num; ++i) {
76        Tip t;
77        t.first=t.last=-1;
78        tips.push_back(t);
79      }
80    }
81  protected:
82    void befuz(ClassIt it, int class_id) {
83      if (tips[class_id].first==-1) {
84        if (tips[class_id].last==-1) {
85          nodes[it.i].prev=nodes[it.i].next=-1;
86          tips[class_id].first=tips[class_id].last=it.i;
87        }
88      } else {
89        nodes[it.i].prev=tips[class_id].last;
90        nodes[it.i].next=-1;
91        nodes[tips[class_id].last].next=it.i;
92        tips[class_id].last=it.i;
93      }
94    }
95    void kifuz(ClassIt it, int class_id) {
96      if (tips[class_id].first==it.i) {
97        if (tips[class_id].last==it.i) {
98          tips[class_id].first=tips[class_id].last=-1;
99        } else {
100          tips[class_id].first=nodes[it.i].next;
101          nodes[nodes[it.i].next].prev=-1;
102        }
103      } else {
104        if (tips[class_id].last==it.i) {
105          tips[class_id].last=nodes[it.i].prev;
106          nodes[nodes[it.i].prev].next=-1;
107        } else {
108          nodes[nodes[it.i].next].prev=nodes[it.i].prev;
109          nodes[nodes[it.i].prev].next=nodes[it.i].next;
110        }
111      }
112    }
113  public:
114    /// A new element with data \c t is pushed into the vector and into class
115    /// \c class_id.
116    ClassIt push_back(const T& t, int class_id) {
117      Node n;
118      n.data=t;
119      nodes.push_back(n);
120      int i=nodes.size()-1;
121      befuz(i, class_id);
122      return i;
123    }
124    /// A member is moved to an other class.
125    void set(ClassIt it, int old_class_id, int new_class_id) {
126      kifuz(it.i, old_class_id);
127      befuz(it.i, new_class_id);
128    }
129    /// Returns the data pointed by \c it.
130    T& operator[](ClassIt it) { return nodes[it.i].data; }
131    /// Returns the data pointed by \c it.
132    const T& operator[](ClassIt it) const { return nodes[it.i].data; }
133    ///.
134    class ClassIt {
135      friend class IterablePartition;
136    protected:
137      int i;
138    public:
139      /// Default constructor.
140      ClassIt() { }
141      /// This constructor constructs an iterator which points
142      /// to the member of th container indexed by the integer _i.
143      ClassIt(const int& _i) : i(_i) { }
144      /// Invalid constructor.
145      ClassIt(const Invalid&) : i(-1) { }
146    };
147    /// First member of class \c class_id.
148    ClassIt& first(ClassIt& it, int class_id) const {
149      it.i=tips[class_id].first;
150      return it;
151    }
152    /// Next member.
153    ClassIt& next(ClassIt& it) const {
154      it.i=nodes[it.i].next;
155      return it;
156    }
157    /// True iff the iterator is valid.
158    bool valid(const ClassIt& it) const { return it.i!=-1; }
159  };
160
161  template <typename _Col, typename _Value>
162  class Expr;
163
164  template <typename _Col, typename _Value>
165  class SmallExpr {
166    template <typename _C, typename _V>
167    friend class Expr;
168  protected:
169    _Col col;
170    _Value value;
171  public:
172    SmallExpr(_Col _col) : col(_col), value(1) {
173    }
174    SmallExpr& operator *= (_Value _value) {
175      value*=_value;
176      return *this;
177    }
178    //    template <typename _C, typename _V>
179    //    friend SmallExpr<_C, _V> operator* (_V _value,
180    //                                  const SmallExpr<_C, _V>& expr);
181    template <typename _C, typename _V>
182    friend std::ostream& operator<<(std::ostream& os,
183                                    const SmallExpr<_C, _V>& expr);
184  };
185
186  template <typename _Col, typename _Value>
187  SmallExpr<_Col, _Value>
188  operator* (_Value value,
189             const SmallExpr<_Col, _Value>& expr) {
190    SmallExpr<_Col, _Value> tmp;
191    tmp=expr;
192    tmp*=value;
193    return tmp;
194  }
195
196  template <typename _Col, typename _Value>
197  std::ostream& operator<<(std::ostream& os,
198                           const SmallExpr<_Col, _Value>& expr) {
199    os << expr.value << "*" << expr.col;
200    return os;
201  }
202
203  template <typename _Col, typename _Value>
204  class Expr {
205  protected:
206    typedef
207    typename std::map<_Col, _Value> Data;
208    Data data;
209  public:
210    Expr() { }
211    Expr(SmallExpr<_Col, _Value> expr) {
212      data.insert(std::make_pair(expr.col, expr.value));
213    }
214//     Expr(_Col col) {
215//       data.insert(std::make_pair(col, 1));
216//     }
217    Expr& operator*=(_Value _value) {
218      for (typename Data::iterator i=data.begin();
219           i!=data.end(); ++i) {
220        (*i).second *= _value;
221      }
222      return *this;
223    }
224    Expr& operator+=(SmallExpr<_Col, _Value> expr) {
225      typename Data::iterator i=data.find(expr.col);
226      if (i==data.end()) {
227        data.insert(std::make_pair(expr.col, expr.value));
228      } else {
229        (*i).second+=expr.value;
230      }
231      return *this;
232    }
233    //    template <typename _C, typename _V>
234    //    friend Expr<_C, _V> operator*(_V _value, const Expr<_C, _V>& expr);
235    template <typename _C, typename _V>
236    friend std::ostream& operator<<(std::ostream& os,
237                                    const Expr<_C, _V>& expr);
238  };
239
240  template <typename _Col, typename _Value>
241  Expr<_Col, _Value> operator*(_Value _value,
242                               const Expr<_Col, _Value>& expr) {
243    Expr<_Col, _Value> tmp;
244    tmp=expr;
245    tmp*=_value;
246    return tmp;
247  }
248
249  template <typename _Col, typename _Value>
250  std::ostream& operator<<(std::ostream& os,
251                           const Expr<_Col, _Value>& expr) {
252    for (typename Expr<_Col, _Value>::Data::const_iterator i=
253           expr.data.begin();
254         i!=expr.data.end(); ++i) {
255      os << (*i).second << "*" << (*i).first << " ";
256    }
257    return os;
258  }
259
260  /*! \e
261   */
262  template <typename _Value>
263  class LPSolverBase {
264  public:
265    /// \e
266    typedef _Value Value;
267    /// \e
268    typedef IterablePartition<int>::ClassIt RowIt;
269    /// \e
270    typedef IterablePartition<int>::ClassIt ColIt;
271  public:
272    /// \e
273    IterablePartition<int> row_iter_map;
274    /// \e
275    IterablePartition<int> col_iter_map;
276    /// \e
277    const int VALID_CLASS;
278    /// \e
279    const int INVALID_CLASS;
280  public:
281    /// \e
282    LPSolverBase() : row_iter_map(2),
283                     col_iter_map(2),
284                     VALID_CLASS(0), INVALID_CLASS(1) { }
285    /// \e
286    virtual ~LPSolverBase() { }
287
288    //MATRIX INDEPEDENT MANIPULATING FUNCTIONS
289
290  public:
291    /// \e
292    virtual void setMinimize() = 0;
293    /// \e
294    virtual void setMaximize() = 0;
295
296    //LOW LEVEL INTERFACE, MATRIX MANIPULATING FUNCTIONS
297
298  protected:
299    /// \e
300    virtual int _addRow() = 0;
301    /// \e
302    virtual int _addCol() = 0;
303    /// \e
304    virtual void _setRowCoeffs(int i,
305                               std::vector<std::pair<int, double> > coeffs) = 0;
306    /// \e
307    virtual void _setColCoeffs(int i,
308                               std::vector<std::pair<int, double> > coeffs) = 0;
309    /// \e
310    virtual void _eraseCol(int i) = 0;
311    /// \e
312    virtual void _eraseRow(int i) = 0;
313  public:
314    /// \e
315    enum Bound { FREE, LOWER, UPPER, DOUBLE, FIXED };
316  protected:
317    /// \e
318    virtual void _setColBounds(int i, Bound bound,
319                               _Value lo, _Value up) = 0;
320    /// \e
321    virtual void _setRowBounds(int i, Bound bound,
322                               _Value lo, _Value up) = 0;
323    /// \e
324    virtual void _setObjCoef(int i, _Value obj_coef) = 0;
325    /// \e
326    virtual _Value _getObjCoef(int i) = 0;
327
328    //LOW LEVEL, SOLUTION RETRIEVING FUNCTIONS
329
330  protected:
331    virtual _Value _getPrimal(int i) = 0;
332
333    //HIGH LEVEL INTERFACE, MATRIX MANIPULATING FUNTIONS
334
335  public:
336    /// \e
337    RowIt addRow() {
338      int i=_addRow();
339      RowIt row_it;
340      row_iter_map.first(row_it, INVALID_CLASS);
341      if (row_iter_map.valid(row_it)) { //van hasznalhato hely
342        row_iter_map.set(row_it, INVALID_CLASS, VALID_CLASS);
343        row_iter_map[row_it]=i;
344      } else { //a cucc vegere kell inzertalni mert nincs szabad hely
345        row_it=row_iter_map.push_back(i, VALID_CLASS);
346      }
347      return row_it;
348    }
349    /// \e
350    ColIt addCol() {
351      int i=_addCol(); 
352      ColIt col_it;
353      col_iter_map.first(col_it, INVALID_CLASS);
354      if (col_iter_map.valid(col_it)) { //van hasznalhato hely
355        col_iter_map.set(col_it, INVALID_CLASS, VALID_CLASS);
356        col_iter_map[col_it]=i;
357      } else { //a cucc vegere kell inzertalni mert nincs szabad hely
358        col_it=col_iter_map.push_back(i, VALID_CLASS);
359      }
360      return col_it;
361    }
362    /// \e
363    template <typename Begin, typename End>
364    void setRowCoeffs(RowIt row_it, Begin begin, End end) {
365      std::vector<std::pair<int, double> > coeffs;
366      for ( ; begin!=end; ++begin) {
367        coeffs.push_back(std::
368                         make_pair(col_iter_map[begin->first], begin->second));
369      }
370      _setRowCoeffs(row_iter_map[row_it], coeffs);
371    }
372    /// \e
373    template <typename Begin, typename End>
374    void setColCoeffs(ColIt col_it, Begin begin, End end) {
375      std::vector<std::pair<int, double> > coeffs;
376      for ( ; begin!=end; ++begin) {
377        coeffs.push_back(std::
378                         make_pair(row_iter_map[begin->first], begin->second));
379      }
380      _setColCoeffs(col_iter_map[col_it], coeffs);
381    }
382    /// \e
383    void eraseCol(const ColIt& col_it) {
384      col_iter_map.set(col_it, VALID_CLASS, INVALID_CLASS);
385      int cols[2];
386      cols[1]=col_iter_map[col_it];
387      _eraseCol(cols[1]);
388      col_iter_map[col_it]=0; //glpk specifikus, de kell ez??
389      ColIt it;
390      for (col_iter_map.first(it, VALID_CLASS);
391           col_iter_map.valid(it); col_iter_map.next(it)) {
392        if (col_iter_map[it]>cols[1]) --col_iter_map[it];
393      }
394    }
395    /// \e
396    void eraseRow(const RowIt& row_it) {
397      row_iter_map.set(row_it, VALID_CLASS, INVALID_CLASS);
398      int rows[2];
399      rows[1]=row_iter_map[row_it];
400      _eraseRow(rows[1]);
401      row_iter_map[row_it]=0; //glpk specifikus, de kell ez??
402      RowIt it;
403      for (row_iter_map.first(it, VALID_CLASS);
404           row_iter_map.valid(it); row_iter_map.next(it)) {
405        if (row_iter_map[it]>rows[1]) --row_iter_map[it];
406      }
407    }
408    /// \e
409    void setColBounds(const ColIt& col_it, Bound bound,
410                      _Value lo, _Value up) {
411      _setColBounds(col_iter_map[col_it], bound, lo, up);
412    }
413    /// \e
414    void setRowBounds(const RowIt& row_it, Bound bound,
415                      _Value lo, _Value up) {
416      _setRowBounds(row_iter_map[row_it], bound, lo, up);
417    }
418    /// \e
419    void setObjCoef(const ColIt& col_it, _Value obj_coef) {
420      _setObjCoef(col_iter_map[col_it], obj_coef);
421    }
422    /// \e
423    _Value getObjCoef(const ColIt& col_it) {
424      return _getObjCoef(col_iter_map[col_it]);
425    }
426
427    //SOLVER FUNCTIONS
428
429    /// \e
430    virtual void solveSimplex() = 0;
431    /// \e
432    virtual void solvePrimalSimplex() = 0;
433    /// \e
434    virtual void solveDualSimplex() = 0;
435    /// \e
436
437    //HIGH LEVEL, SOLUTION RETRIEVING FUNCTIONS
438
439  public:
440    _Value getPrimal(const ColIt& col_it) {
441      return _getPrimal(col_iter_map[col_it]);
442    }
443    /// \e
444    virtual _Value getObjVal() = 0;
445
446    //OTHER FUNCTIONS
447
448    /// \e
449    virtual int rowNum() const = 0;
450    /// \e
451    virtual int colNum() const = 0;
452    /// \e
453    virtual int warmUp() = 0;
454    /// \e
455    virtual void printWarmUpStatus(int i) = 0;
456    /// \e
457    virtual int getPrimalStatus() = 0;
458    /// \e
459    virtual void printPrimalStatus(int i) = 0;
460    /// \e
461    virtual int getDualStatus() = 0;
462    /// \e
463    virtual void printDualStatus(int i) = 0;
464    /// Returns the status of the slack variable assigned to row \c row_it.
465    virtual int getRowStat(const RowIt& row_it) = 0;
466    /// \e
467    virtual void printRowStatus(int i) = 0;
468    /// Returns the status of the variable assigned to column \c col_it.
469    virtual int getColStat(const ColIt& col_it) = 0;
470    /// \e
471    virtual void printColStatus(int i) = 0;
472  };
473 
474
475  /// \brief Wrappers for LP solvers
476  ///
477  /// This class implements a lemon wrapper for glpk.
478  /// Later other LP-solvers will be wrapped into lemon.
479  /// The aim of this class is to give a general surface to different
480  /// solvers, i.e. it makes possible to write algorithms using LP's,
481  /// in which the solver can be changed to an other one easily.
482  class LPGLPK : public LPSolverBase<double> {
483  public:
484    typedef LPSolverBase<double> Parent;
485
486  public:
487    /// \e
488    LPX* lp;
489
490  public:
491    /// \e
492    LPGLPK() : Parent(),
493                        lp(lpx_create_prob()) {
494      lpx_set_int_parm(lp, LPX_K_DUAL, 1);
495    }
496    /// \e
497    ~LPGLPK() {
498      lpx_delete_prob(lp);
499    }
500
501    //MATRIX INDEPEDENT MANIPULATING FUNCTIONS
502
503    /// \e
504    void setMinimize() {
505      lpx_set_obj_dir(lp, LPX_MIN);
506    }
507    /// \e
508    void setMaximize() {
509      lpx_set_obj_dir(lp, LPX_MAX);
510    }
511
512    //LOW LEVEL INTERFACE, MATRIX MANIPULATING FUNCTIONS
513
514  protected:
515    /// \e
516    int _addCol() {
517      return lpx_add_cols(lp, 1);
518    }
519    /// \e
520    int _addRow() {
521      return lpx_add_rows(lp, 1);
522    }
523    /// \e
524    virtual void _setRowCoeffs(int i,
525                               std::vector<std::pair<int, double> > coeffs) {
526      int mem_length=1+colNum();
527      int* indices = new int[mem_length];
528      double* doubles = new double[mem_length];
529      int length=0;
530      for (std::vector<std::pair<int, double> >::
531             const_iterator it=coeffs.begin(); it!=coeffs.end(); ++it) {
532        ++length;
533        indices[length]=it->first;
534        doubles[length]=it->second;
535//      std::cout << "  " << indices[length] << " "
536//                << doubles[length] << std::endl;
537      }
538//      std::cout << i << " " << length << std::endl;
539      lpx_set_mat_row(lp, i, length, indices, doubles);
540      delete [] indices;
541      delete [] doubles;
542    }
543    /// \e
544    virtual void _setColCoeffs(int i,
545                               std::vector<std::pair<int, double> > coeffs) {
546      int mem_length=1+rowNum();
547      int* indices = new int[mem_length];
548      double* doubles = new double[mem_length];
549      int length=0;
550      for (std::vector<std::pair<int, double> >::
551             const_iterator it=coeffs.begin(); it!=coeffs.end(); ++it) {
552        ++length;
553        indices[length]=it->first;
554        doubles[length]=it->second;
555      }
556      lpx_set_mat_col(lp, i, length, indices, doubles);
557      delete [] indices;
558      delete [] doubles;
559    }
560    /// \e
561    virtual void _eraseCol(int i) {
562      int cols[2];
563      cols[1]=i;
564      lpx_del_cols(lp, 1, cols);
565    }
566    virtual void _eraseRow(int i) {
567      int rows[2];
568      rows[1]=i;
569      lpx_del_rows(lp, 1, rows);
570    }
571    virtual void _setColBounds(int i, Bound bound,
572                               double lo, double up) {
573      switch (bound) {
574      case FREE:
575        lpx_set_col_bnds(lp, i, LPX_FR, lo, up);
576        break;
577      case LOWER:
578        lpx_set_col_bnds(lp, i, LPX_LO, lo, up);
579        break;
580      case UPPER:
581        lpx_set_col_bnds(lp, i, LPX_UP, lo, up);
582        break;
583      case DOUBLE:
584        lpx_set_col_bnds(lp, i, LPX_DB, lo, up);
585        break;
586      case FIXED:
587        lpx_set_col_bnds(lp, i, LPX_FX, lo, up);
588        break;
589      }
590    }
591    virtual void _setRowBounds(int i, Bound bound,
592                               double lo, double up) {
593      switch (bound) {
594      case FREE:
595        lpx_set_row_bnds(lp, i, LPX_FR, lo, up);
596        break;
597      case LOWER:
598        lpx_set_row_bnds(lp, i, LPX_LO, lo, up);
599        break;
600      case UPPER:
601        lpx_set_row_bnds(lp, i, LPX_UP, lo, up);
602        break;
603      case DOUBLE:
604        lpx_set_row_bnds(lp, i, LPX_DB, lo, up);
605        break;
606      case FIXED:
607        lpx_set_row_bnds(lp, i, LPX_FX, lo, up);
608        break;
609      }
610    }
611  protected:
612    /// \e
613    virtual double _getObjCoef(int i) {
614      return lpx_get_obj_coef(lp, i);
615    }
616    /// \e
617    virtual void _setObjCoef(int i, double obj_coef) {
618      lpx_set_obj_coef(lp, i, obj_coef);
619    }
620  public:
621    /// \e
622    void solveSimplex() { lpx_simplex(lp); }
623    /// \e
624    void solvePrimalSimplex() { lpx_simplex(lp); }
625    /// \e
626    void solveDualSimplex() { lpx_simplex(lp); }
627    /// \e
628  protected:
629    virtual double _getPrimal(int i) {
630      return lpx_get_col_prim(lp, i);
631    }
632  public:
633    /// \e
634    double getObjVal() { return lpx_get_obj_val(lp); }
635    /// \e
636    int rowNum() const { return lpx_get_num_rows(lp); }
637    /// \e
638    int colNum() const { return lpx_get_num_cols(lp); }
639    /// \e
640    int warmUp() { return lpx_warm_up(lp); }
641    /// \e
642    void printWarmUpStatus(int i) {
643      switch (i) {
644      case LPX_E_OK: cout << "LPX_E_OK" << endl; break;
645      case LPX_E_EMPTY: cout << "LPX_E_EMPTY" << endl; break;   
646      case LPX_E_BADB: cout << "LPX_E_BADB" << endl; break;
647      case LPX_E_SING: cout << "LPX_E_SING" << endl; break;
648      }
649    }
650    /// \e
651    int getPrimalStatus() { return lpx_get_prim_stat(lp); }
652    /// \e
653    void printPrimalStatus(int i) {
654      switch (i) {
655      case LPX_P_UNDEF: cout << "LPX_P_UNDEF" << endl; break;
656      case LPX_P_FEAS: cout << "LPX_P_FEAS" << endl; break;     
657      case LPX_P_INFEAS: cout << "LPX_P_INFEAS" << endl; break;
658      case LPX_P_NOFEAS: cout << "LPX_P_NOFEAS" << endl; break;
659      }
660    }
661    /// \e
662    int getDualStatus() { return lpx_get_dual_stat(lp); }
663    /// \e
664    void printDualStatus(int i) {
665      switch (i) {
666      case LPX_D_UNDEF: cout << "LPX_D_UNDEF" << endl; break;
667      case LPX_D_FEAS: cout << "LPX_D_FEAS" << endl; break;     
668      case LPX_D_INFEAS: cout << "LPX_D_INFEAS" << endl; break;
669      case LPX_D_NOFEAS: cout << "LPX_D_NOFEAS" << endl; break;
670      }
671    }
672    /// Returns the status of the slack variable assigned to row \c row_it.
673    int getRowStat(const RowIt& row_it) {
674      return lpx_get_row_stat(lp, row_iter_map[row_it]);
675    }
676    /// \e
677    void printRowStatus(int i) {
678      switch (i) {
679      case LPX_BS: cout << "LPX_BS" << endl; break;
680      case LPX_NL: cout << "LPX_NL" << endl; break;     
681      case LPX_NU: cout << "LPX_NU" << endl; break;
682      case LPX_NF: cout << "LPX_NF" << endl; break;
683      case LPX_NS: cout << "LPX_NS" << endl; break;
684      }
685    }
686    /// Returns the status of the variable assigned to column \c col_it.
687    int getColStat(const ColIt& col_it) {
688      return lpx_get_col_stat(lp, col_iter_map[col_it]);
689    }
690    /// \e
691    void printColStatus(int i) {
692      switch (i) {
693      case LPX_BS: cout << "LPX_BS" << endl; break;
694      case LPX_NL: cout << "LPX_NL" << endl; break;     
695      case LPX_NU: cout << "LPX_NU" << endl; break;
696      case LPX_NF: cout << "LPX_NF" << endl; break;
697      case LPX_NS: cout << "LPX_NS" << endl; break;
698      }
699    }
700  };
701 
702  /// @}
703
704} //namespace lemon
705
706#endif //LEMON_LP_SOLVER_WRAPPER_H
Note: See TracBrowser for help on using the repository browser.