deba@458: /* -*- mode: C++; indent-tabs-mode: nil; -*- deba@458: * deba@458: * This file is a part of LEMON, a generic C++ optimization library. deba@458: * deba@458: * Copyright (C) 2003-2008 deba@458: * Egervary Jeno Kombinatorikus Optimalizalasi Kutatocsoport deba@458: * (Egervary Research Group on Combinatorial Optimization, EGRES). deba@458: * deba@458: * Permission to use, modify and distribute this software is granted deba@458: * provided that this copyright notice appears in all copies. For deba@458: * precise terms see the accompanying LICENSE file. deba@458: * deba@458: * This software is provided "AS IS" with no warranty of any kind, deba@458: * express or implied, and with no claim as to its suitability for any deba@458: * purpose. deba@458: * deba@458: */ deba@458: deba@458: ///\file deba@458: ///\brief Implementation of the LEMON-GLPK lp solver interface. deba@458: deba@458: #include deba@458: //#include deba@458: deba@458: extern "C" { deba@458: #include deba@458: } deba@458: deba@458: #if GLP_MAJOR_VERSION > 4 || (GLP_MAJOR_VERSION == 4 && GLP_MINOR_VERSION > 15) deba@458: #define LEMON_glp(func) (glp_##func) deba@458: #define LEMON_lpx(func) (lpx_##func) deba@458: deba@458: #define LEMON_GLP(def) (GLP_##def) deba@458: #define LEMON_LPX(def) (LPX_##def) deba@458: deba@458: #else deba@458: deba@458: #define LEMON_glp(func) (lpx_##func) deba@458: #define LEMON_lpx(func) (lpx_##func) deba@458: deba@458: #define LEMON_GLP(def) (LPX_##def) deba@458: #define LEMON_LPX(def) (LPX_##def) deba@458: deba@458: #endif deba@458: deba@458: namespace lemon { deba@458: deba@458: LpGlpk::LpGlpk() : Parent() { deba@458: solved = false; deba@458: rows = _lp_bits::LpId(1); deba@458: cols = _lp_bits::LpId(1); deba@458: lp = LEMON_glp(create_prob)(); deba@458: LEMON_glp(create_index)(lp); deba@458: messageLevel(0); deba@458: } deba@458: deba@458: LpGlpk::LpGlpk(const LpGlpk &glp) : Parent() { deba@458: solved = false; deba@458: rows = _lp_bits::LpId(1); deba@458: cols = _lp_bits::LpId(1); deba@458: lp = LEMON_glp(create_prob)(); deba@458: LEMON_glp(create_index)(lp); deba@458: messageLevel(0); deba@458: //Coefficient matrix, row bounds deba@458: LEMON_glp(add_rows)(lp, LEMON_glp(get_num_rows)(glp.lp)); deba@458: LEMON_glp(add_cols)(lp, LEMON_glp(get_num_cols)(glp.lp)); deba@458: int len; deba@458: std::vector ind(1+LEMON_glp(get_num_cols)(glp.lp)); deba@458: std::vector val(1+LEMON_glp(get_num_cols)(glp.lp)); deba@458: for (int i=1;i<=LEMON_glp(get_num_rows)(glp.lp);++i) deba@458: { deba@458: len=LEMON_glp(get_mat_row)(glp.lp,i,&*ind.begin(),&*val.begin()); deba@458: LEMON_glp(set_mat_row)(lp, i,len,&*ind.begin(),&*val.begin()); deba@458: LEMON_glp(set_row_bnds)(lp,i, deba@458: LEMON_glp(get_row_type)(glp.lp,i), deba@458: LEMON_glp(get_row_lb)(glp.lp,i), deba@458: LEMON_glp(get_row_ub)(glp.lp,i)); deba@458: } deba@458: deba@458: //Objective function, coloumn bounds deba@458: LEMON_glp(set_obj_dir)(lp, LEMON_glp(get_obj_dir)(glp.lp)); deba@458: //Objectif function's constant term treated separately deba@458: LEMON_glp(set_obj_coef)(lp,0,LEMON_glp(get_obj_coef)(glp.lp,0)); deba@458: for (int i=1;i<=LEMON_glp(get_num_cols)(glp.lp);++i) deba@458: { deba@458: LEMON_glp(set_obj_coef)(lp,i, deba@458: LEMON_glp(get_obj_coef)(glp.lp,i)); deba@458: LEMON_glp(set_col_bnds)(lp,i, deba@458: LEMON_glp(get_col_type)(glp.lp,i), deba@458: LEMON_glp(get_col_lb)(glp.lp,i), deba@458: LEMON_glp(get_col_ub)(glp.lp,i)); deba@458: } deba@458: rows = glp.rows; deba@458: cols = glp.cols; deba@458: } deba@458: deba@458: LpGlpk::~LpGlpk() { deba@458: LEMON_glp(delete_prob)(lp); deba@458: } deba@458: deba@458: int LpGlpk::_addCol() { deba@458: int i=LEMON_glp(add_cols)(lp, 1); deba@458: LEMON_glp(set_col_bnds)(lp, i, LEMON_GLP(FR), 0.0, 0.0); deba@458: solved = false; deba@458: return i; deba@458: } deba@458: deba@458: ///\e deba@458: deba@458: deba@458: LpSolverBase* LpGlpk::_newLp() deba@458: { deba@458: LpGlpk* newlp = new LpGlpk; deba@458: return newlp; deba@458: } deba@458: deba@458: ///\e deba@458: deba@458: LpSolverBase* LpGlpk::_copyLp() deba@458: { deba@458: LpGlpk *newlp = new LpGlpk(*this); deba@458: return newlp; deba@458: } deba@458: deba@458: int LpGlpk::_addRow() { deba@458: int i=LEMON_glp(add_rows)(lp, 1); deba@458: solved = false; deba@458: return i; deba@458: } deba@458: deba@458: deba@458: void LpGlpk::_eraseCol(int i) { deba@458: int ca[2]; deba@458: ca[1]=i; deba@458: LEMON_glp(del_cols)(lp, 1, ca); deba@458: solved = false; deba@458: } deba@458: deba@458: void LpGlpk::_eraseRow(int i) { deba@458: int ra[2]; deba@458: ra[1]=i; deba@458: LEMON_glp(del_rows)(lp, 1, ra); deba@458: solved = false; deba@458: } deba@458: deba@458: void LpGlpk::_getColName(int c, std::string & name) const deba@458: { deba@458: deba@458: const char *n = LEMON_glp(get_col_name)(lp,c); deba@458: name = n?n:""; deba@458: } deba@458: deba@458: deba@458: void LpGlpk::_setColName(int c, const std::string & name) deba@458: { deba@458: LEMON_glp(set_col_name)(lp,c,const_cast(name.c_str())); deba@458: deba@458: } deba@458: deba@458: int LpGlpk::_colByName(const std::string& name) const deba@458: { deba@458: int k = LEMON_glp(find_col)(lp, const_cast(name.c_str())); deba@458: return k > 0 ? k : -1; deba@458: } deba@458: deba@458: deba@458: void LpGlpk::_setRowCoeffs(int i, ConstRowIterator b, ConstRowIterator e) deba@458: { deba@458: std::vector indices; deba@458: std::vector values; deba@458: deba@458: indices.push_back(0); deba@458: values.push_back(0); deba@458: deba@458: for(ConstRowIterator it=b; it!=e; ++it) { deba@458: indices.push_back(it->first); deba@458: values.push_back(it->second); deba@458: } deba@458: deba@458: LEMON_glp(set_mat_row)(lp, i, values.size() - 1, deba@458: &indices[0], &values[0]); deba@458: deba@458: solved = false; deba@458: } deba@458: deba@458: void LpGlpk::_getRowCoeffs(int ix, RowIterator b) const deba@458: { deba@458: int length = LEMON_glp(get_mat_row)(lp, ix, 0, 0); deba@458: deba@458: std::vector indices(length + 1); deba@458: std::vector values(length + 1); deba@458: deba@458: LEMON_glp(get_mat_row)(lp, ix, &indices[0], &values[0]); deba@458: deba@458: for (int i = 1; i <= length; ++i) { deba@458: *b = std::make_pair(indices[i], values[i]); deba@458: ++b; deba@458: } deba@458: } deba@458: deba@458: void LpGlpk::_setColCoeffs(int ix, ConstColIterator b, ConstColIterator e) { deba@458: deba@458: std::vector indices; deba@458: std::vector values; deba@458: deba@458: indices.push_back(0); deba@458: values.push_back(0); deba@458: deba@458: for(ConstColIterator it=b; it!=e; ++it) { deba@458: indices.push_back(it->first); deba@458: values.push_back(it->second); deba@458: } deba@458: deba@458: LEMON_glp(set_mat_col)(lp, ix, values.size() - 1, deba@458: &indices[0], &values[0]); deba@458: deba@458: solved = false; deba@458: } deba@458: deba@458: void LpGlpk::_getColCoeffs(int ix, ColIterator b) const deba@458: { deba@458: int length = LEMON_glp(get_mat_col)(lp, ix, 0, 0); deba@458: deba@458: std::vector indices(length + 1); deba@458: std::vector values(length + 1); deba@458: deba@458: LEMON_glp(get_mat_col)(lp, ix, &indices[0], &values[0]); deba@458: deba@458: for (int i = 1; i <= length; ++i) { deba@458: *b = std::make_pair(indices[i], values[i]); deba@458: ++b; deba@458: } deba@458: } deba@458: deba@458: void LpGlpk::_setCoeff(int ix, int jx, Value value) deba@458: { deba@458: deba@458: if (LEMON_glp(get_num_cols)(lp) < LEMON_glp(get_num_rows)(lp)) { deba@458: deba@458: int length=LEMON_glp(get_mat_row)(lp, ix, 0, 0); deba@458: deba@458: std::vector indices(length + 2); deba@458: std::vector values(length + 2); deba@458: deba@458: LEMON_glp(get_mat_row)(lp, ix, &indices[0], &values[0]); deba@458: deba@458: //The following code does not suppose that the elements of the deba@458: //array indices are sorted deba@458: bool found=false; deba@458: for (int i = 1; i <= length; ++i) { deba@458: if (indices[i]==jx){ deba@458: found=true; deba@458: values[i]=value; deba@458: break; deba@458: } deba@458: } deba@458: if (!found){ deba@458: ++length; deba@458: indices[length]=jx; deba@458: values[length]=value; deba@458: } deba@458: deba@458: LEMON_glp(set_mat_row)(lp, ix, length, &indices[0], &values[0]); deba@458: deba@458: } else { deba@458: deba@458: int length=LEMON_glp(get_mat_col)(lp, jx, 0, 0); deba@458: deba@458: std::vector indices(length + 2); deba@458: std::vector values(length + 2); deba@458: deba@458: LEMON_glp(get_mat_col)(lp, jx, &indices[0], &values[0]); deba@458: deba@458: //The following code does not suppose that the elements of the deba@458: //array indices are sorted deba@458: bool found=false; deba@458: for (int i = 1; i <= length; ++i) { deba@458: if (indices[i]==ix){ deba@458: found=true; deba@458: values[i]=value; deba@458: break; deba@458: } deba@458: } deba@458: if (!found){ deba@458: ++length; deba@458: indices[length]=ix; deba@458: values[length]=value; deba@458: } deba@458: deba@458: LEMON_glp(set_mat_col)(lp, jx, length, &indices[0], &values[0]); deba@458: } deba@458: deba@458: solved = false; deba@458: } deba@458: deba@458: LpGlpk::Value LpGlpk::_getCoeff(int ix, int jx) const deba@458: { deba@458: deba@458: int length=LEMON_glp(get_mat_row)(lp, ix, 0, 0); deba@458: deba@458: std::vector indices(length + 1); deba@458: std::vector values(length + 1); deba@458: deba@458: LEMON_glp(get_mat_row)(lp, ix, &indices[0], &values[0]); deba@458: deba@458: //The following code does not suppose that the elements of the deba@458: //array indices are sorted deba@458: for (int i = 1; i <= length; ++i) { deba@458: if (indices[i]==jx){ deba@458: return values[i]; deba@458: } deba@458: } deba@458: return 0; deba@458: deba@458: } deba@458: deba@458: deba@458: void LpGlpk::_setColLowerBound(int i, Value lo) deba@458: { deba@458: if (lo==INF) { deba@458: //FIXME error deba@458: } deba@458: int b=LEMON_glp(get_col_type)(lp, i); deba@458: double up=LEMON_glp(get_col_ub)(lp, i); deba@458: if (lo==-INF) { deba@458: switch (b) { deba@458: case LEMON_GLP(FR): deba@458: case LEMON_GLP(LO): deba@458: LEMON_glp(set_col_bnds)(lp, i, LEMON_GLP(FR), lo, up); deba@458: break; deba@458: case LEMON_GLP(UP): deba@458: break; deba@458: case LEMON_GLP(DB): deba@458: case LEMON_GLP(FX): deba@458: LEMON_glp(set_col_bnds)(lp, i, LEMON_GLP(UP), lo, up); deba@458: break; deba@458: default: ; deba@458: //FIXME error deba@458: } deba@458: } else { deba@458: switch (b) { deba@458: case LEMON_GLP(FR): deba@458: case LEMON_GLP(LO): deba@458: LEMON_glp(set_col_bnds)(lp, i, LEMON_GLP(LO), lo, up); deba@458: break; deba@458: case LEMON_GLP(UP): deba@458: case LEMON_GLP(DB): deba@458: case LEMON_GLP(FX): deba@458: if (lo==up) deba@458: LEMON_glp(set_col_bnds)(lp, i, LEMON_GLP(FX), lo, up); deba@458: else deba@458: LEMON_glp(set_col_bnds)(lp, i, LEMON_GLP(DB), lo, up); deba@458: break; deba@458: default: ; deba@458: //FIXME error deba@458: } deba@458: } deba@458: deba@458: solved = false; deba@458: } deba@458: deba@458: LpGlpk::Value LpGlpk::_getColLowerBound(int i) const deba@458: { deba@458: int b=LEMON_glp(get_col_type)(lp, i); deba@458: switch (b) { deba@458: case LEMON_GLP(LO): deba@458: case LEMON_GLP(DB): deba@458: case LEMON_GLP(FX): deba@458: return LEMON_glp(get_col_lb)(lp, i); deba@458: default: ; deba@458: return -INF; deba@458: } deba@458: } deba@458: deba@458: void LpGlpk::_setColUpperBound(int i, Value up) deba@458: { deba@458: if (up==-INF) { deba@458: //FIXME error deba@458: } deba@458: int b=LEMON_glp(get_col_type)(lp, i); deba@458: double lo=LEMON_glp(get_col_lb)(lp, i); deba@458: if (up==INF) { deba@458: switch (b) { deba@458: case LEMON_GLP(FR): deba@458: case LEMON_GLP(LO): deba@458: break; deba@458: case LEMON_GLP(UP): deba@458: LEMON_glp(set_col_bnds)(lp, i, LEMON_GLP(FR), lo, up); deba@458: break; deba@458: case LEMON_GLP(DB): deba@458: case LEMON_GLP(FX): deba@458: LEMON_glp(set_col_bnds)(lp, i, LEMON_GLP(LO), lo, up); deba@458: break; deba@458: default: ; deba@458: //FIXME error deba@458: } deba@458: } else { deba@458: switch (b) { deba@458: case LEMON_GLP(FR): deba@458: LEMON_glp(set_col_bnds)(lp, i, LEMON_GLP(UP), lo, up); deba@458: break; deba@458: case LEMON_GLP(UP): deba@458: LEMON_glp(set_col_bnds)(lp, i, LEMON_GLP(UP), lo, up); deba@458: break; deba@458: case LEMON_GLP(LO): deba@458: case LEMON_GLP(DB): deba@458: case LEMON_GLP(FX): deba@458: if (lo==up) deba@458: LEMON_glp(set_col_bnds)(lp, i, LEMON_GLP(FX), lo, up); deba@458: else deba@458: LEMON_glp(set_col_bnds)(lp, i, LEMON_GLP(DB), lo, up); deba@458: break; deba@458: default: ; deba@458: //FIXME error deba@458: } deba@458: } deba@458: deba@458: solved = false; deba@458: } deba@458: deba@458: LpGlpk::Value LpGlpk::_getColUpperBound(int i) const deba@458: { deba@458: int b=LEMON_glp(get_col_type)(lp, i); deba@458: switch (b) { deba@458: case LEMON_GLP(UP): deba@458: case LEMON_GLP(DB): deba@458: case LEMON_GLP(FX): deba@458: return LEMON_glp(get_col_ub)(lp, i); deba@458: default: ; deba@458: return INF; deba@458: } deba@458: } deba@458: deba@458: void LpGlpk::_setRowBounds(int i, Value lb, Value ub) deba@458: { deba@458: //Bad parameter deba@458: if (lb==INF || ub==-INF) { deba@458: //FIXME error deba@458: } deba@458: deba@458: if (lb == -INF){ deba@458: if (ub == INF){ deba@458: LEMON_glp(set_row_bnds)(lp, i, LEMON_GLP(FR), lb, ub); deba@458: } deba@458: else{ deba@458: LEMON_glp(set_row_bnds)(lp, i, LEMON_GLP(UP), lb, ub); deba@458: } deba@458: } deba@458: else{ deba@458: if (ub==INF){ deba@458: LEMON_glp(set_row_bnds)(lp, i, LEMON_GLP(LO), lb, ub); deba@458: deba@458: } deba@458: else{ deba@458: if (lb == ub){ deba@458: LEMON_glp(set_row_bnds)(lp, i, LEMON_GLP(FX), lb, ub); deba@458: } deba@458: else{ deba@458: LEMON_glp(set_row_bnds)(lp, i, LEMON_GLP(DB), lb, ub); deba@458: } deba@458: } deba@458: } deba@458: deba@458: solved = false; deba@458: } deba@458: deba@458: void LpGlpk::_getRowBounds(int i, Value &lb, Value &ub) const deba@458: { deba@458: deba@458: int b=LEMON_glp(get_row_type)(lp, i); deba@458: switch (b) { deba@458: case LEMON_GLP(FR): deba@458: case LEMON_GLP(UP): deba@458: lb = -INF; deba@458: break; deba@458: default: deba@458: lb=LEMON_glp(get_row_lb)(lp, i); deba@458: } deba@458: deba@458: switch (b) { deba@458: case LEMON_GLP(FR): deba@458: case LEMON_GLP(LO): deba@458: ub = INF; deba@458: break; deba@458: default: deba@458: ub=LEMON_glp(get_row_ub)(lp, i); deba@458: } deba@458: deba@458: } deba@458: deba@458: void LpGlpk::_setObjCoeff(int i, Value obj_coef) deba@458: { deba@458: //i=0 means the constant term (shift) deba@458: LEMON_glp(set_obj_coef)(lp, i, obj_coef); deba@458: deba@458: solved = false; deba@458: } deba@458: deba@458: LpGlpk::Value LpGlpk::_getObjCoeff(int i) const { deba@458: //i=0 means the constant term (shift) deba@458: return LEMON_glp(get_obj_coef)(lp, i); deba@458: } deba@458: deba@458: void LpGlpk::_clearObj() deba@458: { deba@458: for (int i=0;i<=LEMON_glp(get_num_cols)(lp);++i){ deba@458: LEMON_glp(set_obj_coef)(lp, i, 0); deba@458: } deba@458: deba@458: solved = false; deba@458: } deba@458: deba@458: LpGlpk::SolveExitStatus LpGlpk::_solve() deba@458: { deba@458: // A way to check the problem to be solved deba@458: //LEMON_glp(write_cpxlp(lp,"naittvan.cpx"); deba@458: deba@458: LEMON_lpx(std_basis)(lp); deba@458: int i = LEMON_lpx(simplex)(lp); deba@458: deba@458: switch (i) { deba@458: case LEMON_LPX(E_OK): deba@458: solved = true; deba@458: return SOLVED; deba@458: default: deba@458: return UNSOLVED; deba@458: } deba@458: } deba@458: deba@458: LpGlpk::Value LpGlpk::_getPrimal(int i) const deba@458: { deba@458: return LEMON_glp(get_col_prim)(lp,i); deba@458: } deba@458: deba@458: LpGlpk::Value LpGlpk::_getDual(int i) const deba@458: { deba@458: return LEMON_glp(get_row_dual)(lp,i); deba@458: } deba@458: deba@458: LpGlpk::Value LpGlpk::_getPrimalValue() const deba@458: { deba@458: return LEMON_glp(get_obj_val)(lp); deba@458: } deba@458: bool LpGlpk::_isBasicCol(int i) const deba@458: { deba@458: return (LEMON_glp(get_col_stat)(lp, i)==LEMON_GLP(BS)); deba@458: } deba@458: deba@458: deba@458: LpGlpk::SolutionStatus LpGlpk::_getPrimalStatus() const deba@458: { deba@458: if (!solved) return UNDEFINED; deba@458: int stat= LEMON_lpx(get_status)(lp); deba@458: switch (stat) { deba@458: case LEMON_LPX(UNDEF)://Undefined (no solve has been run yet) deba@458: return UNDEFINED; deba@458: case LEMON_LPX(NOFEAS)://There is no feasible solution (primal, I guess) deba@458: case LEMON_LPX(INFEAS)://Infeasible deba@458: return INFEASIBLE; deba@458: case LEMON_LPX(UNBND)://Unbounded deba@458: return INFINITE; deba@458: case LEMON_LPX(FEAS)://Feasible deba@458: return FEASIBLE; deba@458: case LEMON_LPX(OPT)://Feasible deba@458: return OPTIMAL; deba@458: default: deba@458: return UNDEFINED; //to avoid gcc warning deba@458: //FIXME error deba@458: } deba@458: } deba@458: deba@458: LpGlpk::SolutionStatus LpGlpk::_getDualStatus() const deba@458: { deba@458: if (!solved) return UNDEFINED; deba@458: switch (LEMON_lpx(get_dual_stat)(lp)) { deba@458: case LEMON_LPX(D_UNDEF)://Undefined (no solve has been run yet) deba@458: return UNDEFINED; deba@458: case LEMON_LPX(D_NOFEAS)://There is no dual feasible solution deba@458: // case LEMON_LPX(D_INFEAS://Infeasible deba@458: return INFEASIBLE; deba@458: case LEMON_LPX(D_FEAS)://Feasible deba@458: switch (LEMON_lpx(get_status)(lp)) { deba@458: case LEMON_LPX(NOFEAS): deba@458: return INFINITE; deba@458: case LEMON_LPX(OPT): deba@458: return OPTIMAL; deba@458: default: deba@458: return FEASIBLE; deba@458: } deba@458: default: deba@458: return UNDEFINED; //to avoid gcc warning deba@458: //FIXME error deba@458: } deba@458: } deba@458: deba@458: LpGlpk::ProblemTypes LpGlpk::_getProblemType() const deba@458: { deba@458: if (!solved) return UNKNOWN; deba@458: //int stat= LEMON_glp(get_status(lp); deba@458: int statp= LEMON_lpx(get_prim_stat)(lp); deba@458: int statd= LEMON_lpx(get_dual_stat)(lp); deba@458: if (statp==LEMON_LPX(P_FEAS) && statd==LEMON_LPX(D_FEAS)) deba@458: return PRIMAL_DUAL_FEASIBLE; deba@458: if (statp==LEMON_LPX(P_FEAS) && statd==LEMON_LPX(D_NOFEAS)) deba@458: return PRIMAL_FEASIBLE_DUAL_INFEASIBLE; deba@458: if (statp==LEMON_LPX(P_NOFEAS) && statd==LEMON_LPX(D_FEAS)) deba@458: return PRIMAL_INFEASIBLE_DUAL_FEASIBLE; deba@458: if (statp==LEMON_LPX(P_NOFEAS) && statd==LEMON_LPX(D_NOFEAS)) deba@458: return PRIMAL_DUAL_INFEASIBLE; deba@458: //In all other cases deba@458: return UNKNOWN; deba@458: } deba@458: deba@458: void LpGlpk::_setMax() deba@458: { deba@458: solved = false; deba@458: LEMON_glp(set_obj_dir)(lp, LEMON_GLP(MAX)); deba@458: } deba@458: deba@458: void LpGlpk::_setMin() deba@458: { deba@458: solved = false; deba@458: LEMON_glp(set_obj_dir)(lp, LEMON_GLP(MIN)); deba@458: } deba@458: deba@458: bool LpGlpk::_isMax() const deba@458: { deba@458: return (LEMON_glp(get_obj_dir)(lp)==LEMON_GLP(MAX)); deba@458: } deba@458: deba@458: deba@458: deba@458: void LpGlpk::messageLevel(int m) deba@458: { deba@458: LEMON_lpx(set_int_parm)(lp, LEMON_LPX(K_MSGLEV), m); deba@458: } deba@458: deba@458: void LpGlpk::presolver(bool b) deba@458: { deba@458: LEMON_lpx(set_int_parm)(lp, LEMON_LPX(K_PRESOL), b); deba@458: } deba@458: deba@458: deba@458: } //END OF NAMESPACE LEMON