int main (int argc, const char *argv[]) { ClpSimplex model; int status; // Keep names if (argc < 2) { status = model.readMps("small.mps", true); } else { status = model.readMps(argv[1], true); } if (status) exit(10); /* This driver implements what I called Sprint. Cplex calls it "sifting" which is just as silly. When I thought of this trivial idea it reminded me of an LP code of the 60's called sprint which after every factorization took a subset of the matrix into memory (all 64K words!) and then iterated very fast on that subset. On the problems of those days it did not work very well, but it worked very well on aircrew scheduling problems where there were very large numbers of columns all with the same flavor. */ /* The idea works best if you can get feasible easily. To make it more general we can add in costed slacks */ int originalNumberColumns = model.numberColumns(); int numberRows = model.numberRows(); // We will need arrays to choose variables. These are too big but .. double * weight = new double [numberRows+originalNumberColumns]; int * sort = new int [numberRows+originalNumberColumns]; int numberSort = 0; // Say we are going to add slacks - if you can get a feasible // solution then do that at the comment - Add in your own coding here bool addSlacks = true; if (addSlacks) { // initial list will just be artificials // first we will set all variables as close to zero as possible int iColumn; const double * columnLower = model.columnLower(); const double * columnUpper = model.columnUpper(); double * columnSolution = model.primalColumnSolution(); for (iColumn = 0; iColumn < originalNumberColumns; iColumn++) { double value = 0.0; if (columnLower[iColumn] > 0.0) value = columnLower[iColumn]; else if (columnUpper[iColumn] < 0.0) value = columnUpper[iColumn]; columnSolution[iColumn] = value; } // now see what that does to row solution double * rowSolution = model.primalRowSolution(); memset (rowSolution, 0, numberRows * sizeof(double)); model.times(1.0, columnSolution, rowSolution); int * addStarts = new int [numberRows+1]; int * addRow = new int[numberRows]; double * addElement = new double[numberRows]; const double * lower = model.rowLower(); const double * upper = model.rowUpper(); addStarts[0] = 0; int numberArtificials = 0; double * addCost = new double [numberRows]; const double penalty = 1.0e8; int iRow; for (iRow = 0; iRow < numberRows; iRow++) { if (lower[iRow] > rowSolution[iRow]) { addRow[numberArtificials] = iRow; addElement[numberArtificials] = 1.0; addCost[numberArtificials] = penalty; numberArtificials++; addStarts[numberArtificials] = numberArtificials; } else if (upper[iRow] < rowSolution[iRow]) { addRow[numberArtificials] = iRow; addElement[numberArtificials] = -1.0; addCost[numberArtificials] = penalty; numberArtificials++; addStarts[numberArtificials] = numberArtificials; } } model.addColumns(numberArtificials, NULL, NULL, addCost, addStarts, addRow, addElement); delete [] addStarts; delete [] addRow; delete [] addElement; delete [] addCost; // Set up initial list numberSort = numberArtificials; int i; for (i = 0; i < numberSort; i++) sort[i] = i + originalNumberColumns; } else { // Get initial list in some magical way // Add in your own coding here abort(); } int numberColumns = model.numberColumns(); const double * columnLower = model.columnLower(); const double * columnUpper = model.columnUpper(); double * fullSolution = model.primalColumnSolution(); // Just do this number of passes int maxPass = 100; int iPass; double lastObjective = 1.0e31; // Just take this number of columns in small problem int smallNumberColumns = CoinMin(3 * numberRows, numberColumns); smallNumberColumns = CoinMax(smallNumberColumns, 3000); // We will be using all rows int * whichRows = new int [numberRows]; for (int iRow = 0; iRow < numberRows; iRow++) whichRows[iRow] = iRow; double originalOffset; model.getDblParam(ClpObjOffset, originalOffset); for (iPass = 0; iPass < maxPass; iPass++) { printf("Start of pass %d\n", iPass); //printf("Bug until submodel new version\n"); CoinSort_2(sort, sort + numberSort, weight); // Create small problem ClpSimplex small(&model, numberRows, whichRows, numberSort, sort); // now see what variables left out do to row solution double * rowSolution = model.primalRowSolution(); memset (rowSolution, 0, numberRows * sizeof(double)); int iRow, iColumn; // zero out ones in small problem for (iColumn = 0; iColumn < numberSort; iColumn++) { int kColumn = sort[iColumn]; fullSolution[kColumn] = 0.0; } // Get objective offset double offset = 0.0; const double * objective = model.objective(); for (iColumn = 0; iColumn < originalNumberColumns; iColumn++) offset += fullSolution[iColumn] * objective[iColumn]; small.setDblParam(ClpObjOffset, originalOffset - offset); model.times(1.0, fullSolution, rowSolution); double * lower = small.rowLower(); double * upper = small.rowUpper(); for (iRow = 0; iRow < numberRows; iRow++) { if (lower[iRow] > -1.0e50) lower[iRow] -= rowSolution[iRow]; if (upper[iRow] < 1.0e50) upper[iRow] -= rowSolution[iRow]; } /* For some problems a useful variant is to presolve problem. In this case you need to adjust smallNumberColumns to get right size problem. Also you can dispense with creating small problem and fix variables in large problem and do presolve on that. */ // Solve small.primal(); // move solution back const double * solution = small.primalColumnSolution(); for (iColumn = 0; iColumn < numberSort; iColumn++) { int kColumn = sort[iColumn]; model.setColumnStatus(kColumn, small.getColumnStatus(iColumn)); fullSolution[kColumn] = solution[iColumn]; } for (iRow = 0; iRow < numberRows; iRow++) model.setRowStatus(iRow, small.getRowStatus(iRow)); memcpy(model.primalRowSolution(), small.primalRowSolution(), numberRows * sizeof(double)); if ((small.objectiveValue() > lastObjective - 1.0e-7 && iPass > 5) || !small.numberIterations() || iPass == maxPass - 1) { break; // finished } else { lastObjective = small.objectiveValue(); // get reduced cost for large problem // this assumes minimization memcpy(weight, model.objective(), numberColumns * sizeof(double)); model.transposeTimes(-1.0, small.dualRowSolution(), weight); // now massage weight so all basic in plus good djs for (iColumn = 0; iColumn < numberColumns; iColumn++) { double dj = weight[iColumn]; double value = fullSolution[iColumn]; if (model.getColumnStatus(iColumn) == ClpSimplex::basic) dj = -1.0e50; else if (dj < 0.0 && value < columnUpper[iColumn]) dj = dj; else if (dj > 0.0 && value > columnLower[iColumn]) dj = -dj; else if (columnUpper[iColumn] > columnLower[iColumn]) dj = fabs(dj); else dj = 1.0e50; weight[iColumn] = dj; sort[iColumn] = iColumn; } // sort CoinSort_2(weight, weight + numberColumns, sort); numberSort = smallNumberColumns; } } if (addSlacks) { int i; int numberArtificials = numberColumns - originalNumberColumns; for (i = 0; i < numberArtificials; i++) sort[i] = i + originalNumberColumns; model.deleteColumns(numberArtificials, sort); } delete [] weight; delete [] sort; delete [] whichRows; model.primal(1); return 0; }
int main(int argc, const char *argv[]) { { // Empty model ClpSimplex model; // Bounds on rows - as dense vector double lower[] = {2.0, 1.0}; double upper[] = {COIN_DBL_MAX, 1.0}; // Create space for 2 rows model.resize(2, 0); // Fill in int i; // Now row bounds for (i = 0; i < 2; i++) { model.setRowLower(i, lower[i]); model.setRowUpper(i, upper[i]); } // Now add column 1 int column1Index[] = {0, 1}; double column1Value[] = {1.0, 1.0}; model.addColumn(2, column1Index, column1Value, 0.0, 2, 1.0); // Now add column 2 int column2Index[] = {1}; double column2Value[] = { -5.0}; model.addColumn(1, column2Index, column2Value, 0.0, COIN_DBL_MAX, 0.0); // Now add column 3 int column3Index[] = {0, 1}; double column3Value[] = {1.0, 1.0}; model.addColumn(2, column3Index, column3Value, 0.0, 4.0, 4.0); // solve model.dual(); /* Adding one column at a time has a significant overhead so let's try a more complicated but faster way First time adding in 10000 columns one by one */ model.allSlackBasis(); ClpSimplex modelSave = model; double time1 = CoinCpuTime(); int k; for (k = 0; k < 10000; k++) { int column2Index[] = {0, 1}; double column2Value[] = {1.0, -5.0}; model.addColumn(2, column2Index, column2Value, 0.0, 1.0, 10000.0); } printf("Time for 10000 addColumn is %g\n", CoinCpuTime() - time1); model.dual(); model = modelSave; // Now use build CoinBuild buildObject; time1 = CoinCpuTime(); for (k = 0; k < 100000; k++) { int column2Index[] = {0, 1}; double column2Value[] = {1.0, -5.0}; buildObject.addColumn(2, column2Index, column2Value, 0.0, 1.0, 10000.0); } model.addColumns(buildObject); printf("Time for 100000 addColumn using CoinBuild is %g\n", CoinCpuTime() - time1); model.dual(); model = modelSave; // Now use build +-1 int del[] = {0, 1, 2}; model.deleteColumns(3, del); CoinBuild buildObject2; time1 = CoinCpuTime(); for (k = 0; k < 10000; k++) { int column2Index[] = {0, 1}; double column2Value[] = {1.0, 1.0, -1.0}; int bias = k & 1; buildObject2.addColumn(2, column2Index, column2Value + bias, 0.0, 1.0, 10000.0); } model.addColumns(buildObject2, true); printf("Time for 10000 addColumn using CoinBuild+-1 is %g\n", CoinCpuTime() - time1); model.dual(); model = modelSave; // Now use build +-1 model.deleteColumns(3, del); CoinModel modelObject2; time1 = CoinCpuTime(); for (k = 0; k < 10000; k++) { int column2Index[] = {0, 1}; double column2Value[] = {1.0, 1.0, -1.0}; int bias = k & 1; modelObject2.addColumn(2, column2Index, column2Value + bias, 0.0, 1.0, 10000.0); } model.addColumns(modelObject2, true); printf("Time for 10000 addColumn using CoinModel+-1 is %g\n", CoinCpuTime() - time1); //model.writeMps("xx.mps"); model.dual(); model = modelSave; // Now use model CoinModel modelObject; time1 = CoinCpuTime(); for (k = 0; k < 100000; k++) { int column2Index[] = {0, 1}; double column2Value[] = {1.0, -5.0}; modelObject.addColumn(2, column2Index, column2Value, 0.0, 1.0, 10000.0); } model.addColumns(modelObject); printf("Time for 100000 addColumn using CoinModel is %g\n", CoinCpuTime() - time1); model.dual(); // Print column solution Just first 3 columns int numberColumns = model.numberColumns(); numberColumns = CoinMin(3, numberColumns); // Alternatively getColSolution() double * columnPrimal = model.primalColumnSolution(); // Alternatively getReducedCost() double * columnDual = model.dualColumnSolution(); // Alternatively getColLower() double * columnLower = model.columnLower(); // Alternatively getColUpper() double * columnUpper = model.columnUpper(); // Alternatively getObjCoefficients() double * columnObjective = model.objective(); int iColumn; std::cout << " Primal Dual Lower Upper Cost" << std::endl; for (iColumn = 0; iColumn < numberColumns; iColumn++) { double value; std::cout << std::setw(6) << iColumn << " "; value = columnPrimal[iColumn]; if (fabs(value) < 1.0e5) std::cout << std::setiosflags(std::ios::fixed | std::ios::showpoint) << std::setw(14) << value; else std::cout << std::setiosflags(std::ios::scientific) << std::setw(14) << value; value = columnDual[iColumn]; if (fabs(value) < 1.0e5) std::cout << std::setiosflags(std::ios::fixed | std::ios::showpoint) << std::setw(14) << value; else std::cout << std::setiosflags(std::ios::scientific) << std::setw(14) << value; value = columnLower[iColumn]; if (fabs(value) < 1.0e5) std::cout << std::setiosflags(std::ios::fixed | std::ios::showpoint) << std::setw(14) << value; else std::cout << std::setiosflags(std::ios::scientific) << std::setw(14) << value; value = columnUpper[iColumn]; if (fabs(value) < 1.0e5) std::cout << std::setiosflags(std::ios::fixed | std::ios::showpoint) << std::setw(14) << value; else std::cout << std::setiosflags(std::ios::scientific) << std::setw(14) << value; value = columnObjective[iColumn]; if (fabs(value) < 1.0e5) std::cout << std::setiosflags(std::ios::fixed | std::ios::showpoint) << std::setw(14) << value; else std::cout << std::setiosflags(std::ios::scientific) << std::setw(14) << value; std::cout << std::endl; } std::cout << "--------------------------------------" << std::endl; } { // Now copy a model ClpSimplex model; int status; if (argc < 2) { #if defined(SAMPLEDIR) status = model.readMps(SAMPLEDIR "/p0033.mps", true); #else fprintf(stderr, "Do not know where to find sample MPS files.\n"); exit(1); #endif } else status = model.readMps(argv[1]); if (status) { printf("errors on input\n"); exit(77); } model.initialSolve(); int numberRows = model.numberRows(); int numberColumns = model.numberColumns(); const double * rowLower = model.rowLower(); const double * rowUpper = model.rowUpper(); // Start off model2 ClpSimplex model2; model2.addRows(numberRows, rowLower, rowUpper, NULL); // Build object CoinBuild buildObject; // Add columns const double * columnLower = model.columnLower(); const double * columnUpper = model.columnUpper(); const double * objective = model.objective(); CoinPackedMatrix * matrix = model.matrix(); const int * row = matrix->getIndices(); const int * columnLength = matrix->getVectorLengths(); const CoinBigIndex * columnStart = matrix->getVectorStarts(); const double * elementByColumn = matrix->getElements(); for (int iColumn = 0; iColumn < numberColumns; iColumn++) { CoinBigIndex start = columnStart[iColumn]; buildObject.addColumn(columnLength[iColumn], row + start, elementByColumn + start, columnLower[iColumn], columnUpper[iColumn], objective[iColumn]); } // add in model2.addColumns(buildObject); model2.initialSolve(); } { // and again ClpSimplex model; int status; if (argc < 2) { #if defined(SAMPLEDIR) status = model.readMps(SAMPLEDIR "/p0033.mps", true); #else fprintf(stderr, "Do not know where to find sample MPS files.\n"); exit(1); #endif } else status = model.readMps(argv[1]); if (status) { printf("errors on input\n"); exit(77); } model.initialSolve(); int numberRows = model.numberRows(); int numberColumns = model.numberColumns(); const double * rowLower = model.rowLower(); const double * rowUpper = model.rowUpper(); // Build object CoinModel buildObject; for (int iRow = 0; iRow < numberRows; iRow++) buildObject.setRowBounds(iRow, rowLower[iRow], rowUpper[iRow]); // Add columns const double * columnLower = model.columnLower(); const double * columnUpper = model.columnUpper(); const double * objective = model.objective(); CoinPackedMatrix * matrix = model.matrix(); const int * row = matrix->getIndices(); const int * columnLength = matrix->getVectorLengths(); const CoinBigIndex * columnStart = matrix->getVectorStarts(); const double * elementByColumn = matrix->getElements(); for (int iColumn = 0; iColumn < numberColumns; iColumn++) { CoinBigIndex start = columnStart[iColumn]; buildObject.addColumn(columnLength[iColumn], row + start, elementByColumn + start, columnLower[iColumn], columnUpper[iColumn], objective[iColumn]); } // add in ClpSimplex model2; model2.loadProblem(buildObject); model2.initialSolve(); } return 0; }
//----------------------------------------------------------------------------- void CbcSolver2::resolve() { int numberColumns = modelPtr_->numberColumns(); if ((count_<10&&algorithm_==2)||!algorithm_) { OsiClpSolverInterface::resolve(); if (modelPtr_->status()==0) { count_++; double * solution = modelPtr_->primalColumnSolution(); int i; for (i=0;i<numberColumns;i++) { if (solution[i]>1.0e-6||modelPtr_->getStatus(i)==ClpSimplex::basic) { node_[i]=CoinMax(count_,node_[i]); howMany_[i]++; } } } else { if (!algorithm_==2) printf("infeasible early on\n"); } } else { // use counts int numberRows=modelPtr_->numberRows(); int * whichRow = new int[numberRows]; int * whichColumn = new int [numberColumns]; int i; const double * lower = modelPtr_->columnLower(); const double * upper = modelPtr_->columnUpper(); const double * rowUpper = modelPtr_->rowUpper(); bool equality=false; for (i=0;i<numberRows;i++) { if (rowUpper[i]==1.0) { equality=true; break; } } setBasis(basis_,modelPtr_); int nNewCol=0; // Column copy //const double * element = modelPtr_->matrix()->getElements(); const int * row = modelPtr_->matrix()->getIndices(); const CoinBigIndex * columnStart = modelPtr_->matrix()->getVectorStarts(); const int * columnLength = modelPtr_->matrix()->getVectorLengths(); int * rowActivity = new int[numberRows]; memset(rowActivity,0,numberRows*sizeof(int)); int * rowActivity2 = new int[numberRows]; memset(rowActivity2,0,numberRows*sizeof(int)); char * mark = new char[numberColumns]; memset(mark,0,numberColumns); // Get rows which are satisfied for (i=0;i<numberColumns;i++) { if (lower[i]>0.0) { CoinBigIndex j; for (j=columnStart[i]; j<columnStart[i]+columnLength[i];j++) { int iRow=row[j]; rowActivity2[iRow] ++; } } else if (!upper[i]) { mark[i]=2; // no good } } // If equality - check not infeasible if (equality) { bool feasible=true; for (i=0;i<numberRows;i++) { if (rowActivity2[i]>1) { feasible=false; break; } } if (!feasible) { delete [] rowActivity; delete [] rowActivity2; modelPtr_->setProblemStatus(1); delete [] whichRow; delete [] whichColumn; delete [] mark; printf("infeasible by inspection (over)\n"); return; } } int nNoGood=0; for (i=0;i<numberColumns;i++) { if (mark[i]==2) { nNoGood++; continue; } bool choose; if (algorithm_==1) choose = true; else choose = (node_[i]>count_-memory_&&node_[i]>0); bool any; if (equality) { // See if forced to be zero CoinBigIndex j; any=true; for (j=columnStart[i]; j<columnStart[i]+columnLength[i];j++) { int iRow=row[j]; if (rowActivity2[iRow]) any=false; // can't be in } } else { // See if not useful CoinBigIndex j; any=false; for (j=columnStart[i]; j<columnStart[i]+columnLength[i];j++) { int iRow=row[j]; if (!rowActivity2[iRow]) any=true; // useful } } if (!any&&!lower[i]) { choose=false; // and say can't be useful mark[i]=2; nNoGood++; } if (strategy_&&modelPtr_->getColumnStatus(i)==ClpSimplex::basic) choose=true; if (choose||lower[i]>0.0) { mark[i]=1; whichColumn[nNewCol++]=i; CoinBigIndex j; double value = upper[i]; if (value) { for (j=columnStart[i]; j<columnStart[i]+columnLength[i];j++) { int iRow=row[j]; rowActivity[iRow] ++; } } } } // If equality add in slacks CoinModel build; if (equality) { int row=0; for (i=0;i<numberRows;i++) { // put in all rows if wanted if(strategy_) rowActivity2[i]=0; if (!rowActivity2[i]) { double element=1.0; build.addColumn(1,&row,&element,0.0,1.0,1.0e8); // large cost row++; } } } int nOK=0; int nNewRow=0; for (i=0;i<numberRows;i++) { if (rowActivity[i]) nOK++; if (!rowActivity2[i]) whichRow[nNewRow++]=i; // not satisfied else modelPtr_->setRowStatus(i,ClpSimplex::basic); // make slack basic } if (nOK<numberRows) { for (i=0;i<numberColumns;i++) { if (!mark[i]) { CoinBigIndex j; int good=0; for (j=columnStart[i]; j<columnStart[i]+columnLength[i];j++) { int iRow=row[j]; if (!rowActivity[iRow]) { rowActivity[iRow] ++; good++; } } if (good) { nOK+=good; whichColumn[nNewCol++]=i; } } } } delete [] rowActivity; delete [] rowActivity2; if (nOK<numberRows) { modelPtr_->setProblemStatus(1); delete [] whichRow; delete [] whichColumn; delete [] mark; printf("infeasible by inspection\n"); return; } bool allIn=false; if (nNewCol+nNoGood+numberRows>numberColumns) { // add in all allIn=true; for (i=0;i<numberColumns;i++) { if (!mark[i]) { whichColumn[nNewCol++]=i; } } } ClpSimplex * temp = new ClpSimplex(modelPtr_,nNewRow,whichRow,nNewCol,whichColumn); if (equality) temp->addColumns(build); temp->setLogLevel(1); printf("small has %d rows and %d columns (%d impossible to help) %s\n", nNewRow,nNewCol,nNoGood,allIn ? "all in" : ""); temp->setSpecialOptions(128+512); temp->setDualObjectiveLimit(1.0e50); temp->dual(); assert (!temp->status()); double * solution = modelPtr_->primalColumnSolution(); const double * solution2 = temp->primalColumnSolution(); memset(solution,0,numberColumns*sizeof(double)); for (i=0;i<nNewCol;i++) { int iColumn = whichColumn[i]; solution[iColumn]=solution2[i]; modelPtr_->setStatus(iColumn,temp->getStatus(i)); } double * rowSolution = modelPtr_->primalRowSolution(); const double * rowSolution2 = temp->primalRowSolution(); double * dual = modelPtr_->dualRowSolution(); const double * dual2 = temp->dualRowSolution(); memset(dual,0,numberRows*sizeof(double)); for (i=0;i<nNewRow;i++) { int iRow=whichRow[i]; modelPtr_->setRowStatus(iRow,temp->getRowStatus(i)); rowSolution[iRow]=rowSolution2[i]; dual[iRow]=dual2[i]; } // See if optimal double * dj = modelPtr_->dualColumnSolution(); // get reduced cost for large problem // this assumes minimization memcpy(dj,modelPtr_->objective(),numberColumns*sizeof(double)); modelPtr_->transposeTimes(-1.0,dual,dj); modelPtr_->setObjectiveValue(temp->objectiveValue()); modelPtr_->setProblemStatus(0); int nBad=0; for (i=0;i<numberColumns;i++) { if (modelPtr_->getStatus(i)==ClpSimplex::atLowerBound &&upper[i]>lower[i]&&dj[i]<-1.0e-5) nBad++; } //modelPtr_->writeMps("bada.mps"); //temp->writeMps("badb.mps"); delete temp; if (nBad&&!allIn) { assert (algorithm_==2); //printf("%d bad\n",nBad); timesBad_++; // just non mark==2 int nAdded=0; for (i=0;i<numberColumns;i++) { if (!mark[i]) { whichColumn[nNewCol++]=i; nAdded++; } } assert (nAdded); { temp = new ClpSimplex(modelPtr_,nNewRow,whichRow,nNewCol,whichColumn); if (equality) temp->addColumns(build); temp->setLogLevel(2); temp->setSpecialOptions(128+512); temp->setDualObjectiveLimit(1.0e50); temp->primal(1); assert (!temp->status()); double * solution = modelPtr_->primalColumnSolution(); const double * solution2 = temp->primalColumnSolution(); memset(solution,0,numberColumns*sizeof(double)); for (i=0;i<nNewCol;i++) { int iColumn = whichColumn[i]; solution[iColumn]=solution2[i]; modelPtr_->setStatus(iColumn,temp->getStatus(i)); } double * rowSolution = modelPtr_->primalRowSolution(); const double * rowSolution2 = temp->primalRowSolution(); double * dual = modelPtr_->dualRowSolution(); const double * dual2 = temp->dualRowSolution(); memset(dual,0,numberRows*sizeof(double)); for (i=0;i<nNewRow;i++) { int iRow=whichRow[i]; modelPtr_->setRowStatus(iRow,temp->getRowStatus(i)); rowSolution[iRow]=rowSolution2[i]; dual[iRow]=dual2[i]; } modelPtr_->setObjectiveValue(temp->objectiveValue()); modelPtr_->setProblemStatus(0); iterationsBad_ += temp->numberIterations(); printf("clean %d\n",temp->numberIterations()); delete temp; } } delete [] mark; delete [] whichRow; delete [] whichColumn; basis_ = getBasis(modelPtr_); modelPtr_->setSpecialOptions(0); count_++; if ((count_%100)==0&&algorithm_==2) printf("count %d, bad %d - iterations %d\n",count_,timesBad_,iterationsBad_); for (i=0;i<numberColumns;i++) { if (solution[i]>1.0e-6||modelPtr_->getStatus(i)==ClpSimplex::basic) { node_[i]=CoinMax(count_,node_[i]); howMany_[i]++; } } if (modelPtr_->objectiveValue()>=modelPtr_->dualObjectiveLimit()) modelPtr_->setProblemStatus(1); } }