diff --git a/modules/optim/src/lpsolver.cpp b/modules/optim/src/lpsolver.cpp index b9cabc1180..b56dc3e3a6 100644 --- a/modules/optim/src/lpsolver.cpp +++ b/modules/optim/src/lpsolver.cpp @@ -25,68 +25,232 @@ void print_matrix(const Mat& X){ printf("]\n"); } } -namespace solveLP_aux{ - //return -1 if problem is unfeasible, 0 if feasible - //in latter case it returns feasible solution in z with homogenised b's and v - int initialize_simplex(const Mat& c, Mat& b, Mat& z,double& v); +void print_simplex_state(const Mat& c,const Mat&b,double v,const vector& N,const vector& B){ + printf("\tprint simplex state\n"); + + printf("v=%g\n",v); + + printf("here c goes\n"); + print_matrix(c); + + printf("non-basic: "); + for (std::vector::const_iterator it = N.begin() ; it != N.end(); ++it){ + printf("%d, ",*it); + } + printf("\n"); + + printf("here b goes\n"); + print_matrix(b); + printf("basic: "); + + for (std::vector::const_iterator it = B.begin() ; it != B.end(); ++it){ + printf("%d, ",*it); + } + printf("\n"); } + +namespace solveLP_aux{ + /**Due to technical considerations, the format of input b and c is somewhat special: + *both b and c should be one column bigger than corresponding b and c of linear problem and the leftmost column will be used internally + by this procedure - it should not be cleaned before the call to procedure and may contain mess after + it also initializes N and B and does not make any assumptions about their init values + * @return -1 if problem is unfeasible, 0 if feasible. + */ + int initialize_simplex(Mat_& c, Mat_& b,double& v,vector& N,vector& B); + inline void pivot(Mat_& c,Mat_& b,double& v,vector& N,vector& B, int leaving_index,int entering_index); + /**@return -2 means the problem is unbdd, 1 means multiple solutions, 0 means successful. + */ + int inner_simplex(Mat_& c, Mat_& b,double& v,vector& N,vector& B); + void swap_columns(Mat_& A,int col1,int col2); +} + +//return codes:-2 (no_sol - unbdd),-1(no_sol - unfsbl), 0(single_sol), 1(multiple_sol=>least_l2_norm) int solveLP(const Mat& Func, const Mat& Constr, Mat& z){ - printf("call to solveLP\n");//-3(incorrect),-2 (no_sol - unbdd),-1(no_sol - unfsbl), 0(single_sol), 1(multiple_sol=>least_l2_norm) + printf("call to solveLP\n"); //sanity check (size, type, no. of channels) (and throw exception, if appropriate) - if(Func.type()!=CV_64FC1 || Constr.type()!=CV_64FC1){ - printf("both Func and Constr should be one-channel matrices of double's\n"); - return -3; + CV_Assert(Func.type()==CV_64FC1); + CV_Assert(Constr.type()==CV_64FC1); + CV_Assert(Func.rows==1); + CV_Assert(Constr.cols-Func.cols==1); + + //copy arguments for we will shall modify them + Mat_ bigC=Mat_(1,Func.cols+1), + bigB=Mat_(Constr.rows,Constr.cols+1); + Func.copyTo(bigC.colRange(1,bigC.cols)); + Constr.copyTo(bigB.colRange(1,bigB.cols)); + double v=0; + vector N,B; + + if(solveLP_aux::initialize_simplex(bigC,bigB,v,N,B)==-1){ + return -1; } - if(Func.rows!=1){ - printf("Func should be row-vector\n"); - return -3; + Mat_ c=bigC.colRange(1,bigC.cols), + b=bigB.colRange(1,bigB.cols); + + int res=0; + if((res=solveLP_aux::inner_simplex(c,b,v,N,B))==-2){ + return -2; } - vector N(Func.cols); - N[0]=1; + + //return the optimal solution + const int z_size[]={1,c.cols}; + z.create(2,z_size,CV_64FC1); + MatIterator_ it=z.begin(); + for(int i=1;i<=c.cols;i++,it++){ + std::vector::iterator pos=B.begin(); + if((pos=std::find(B.begin(),B.end(),i))==B.end()){ + *it=0; + }else{ + *it=b.at(pos-B.begin(),b.cols-1); + } + } + + return res; +} + +int solveLP_aux::initialize_simplex(Mat_& c, Mat_& b,double& v,vector& N,vector& B){ + N.resize(c.cols); + N[0]=0; for (std::vector::iterator it = N.begin()+1 ; it != N.end(); ++it){ *it=it[-1]+1; } - if((Constr.cols-1)!=Func.cols){ - printf("Constr should have one more column when compared to Func\n"); - return -3; - } - vector B(Constr.rows); - B[0]=Func.cols+1; + B.resize(b.rows); + B[0]=N.size(); for (std::vector::iterator it = B.begin()+1 ; it != B.end(); ++it){ *it=it[-1]+1; } + v=0; - //copy arguments for we will shall modify them - Mat c=Func.clone(), - b=Constr.clone(); - double v=0; + int k=0; + { + double min=DBL_MAX; + for(int i=0;i=0){ + N.erase(N.begin()); + return 0; + } + Mat_ old_c=c.clone(); + c=0; + c(0,0)=-1; + for(int i=0;i::iterator it=std::find(B.begin(),B.end(),0); + if(it!=B.end()){ + int it_offset=it-B.begin(); + if(b(it_offset,b.cols-1)>0){ + return -1; + } + pivot(c,b,v,N,B,it_offset,0); + } + + it=std::find(N.begin(),N.end(),0); + int it_offset=it-N.begin(); + std::iter_swap(it,N.begin()); + swap_columns(c,it_offset,0); + swap_columns(b,it_offset,0); + + printf("after swaps\n"); + print_simplex_state(c,b,v,N,B); + + //start from 1, because we ignore x_0 + c=0; + v=0; + for(int i=1;i& c, Mat_& b,double& v,vector& N,vector& B){ int count=0; while(1){ printf("iteration #%d\n",count++); MatIterator_ pos_ptr; - int e=0; - for(pos_ptr=c.begin();(*pos_ptr<=0) && pos_ptr!=c.end();pos_ptr++,e++); - if(pos_ptr==c.end()){ - break; + int e=-1,pos_ctr=0,min_var=INT_MAX; + bool all_nonzero=true; + for(pos_ptr=c.begin();pos_ptr!=c.end();pos_ptr++,pos_ctr++){ + if(*pos_ptr==0){ + all_nonzero=false; + } + if(*pos_ptr>0){ + if(N[pos_ctr] min_row_ptr=b.begin(); - for(MatIterator_ it=b.begin();it!=b.end();it+=b.cols,row_it++){ + MatIterator_ min_row_ptr=b.begin(); + for(MatIterator_ it=b.begin();it!=b.end();it+=b.cols,row_it++){ double myite=0; - //check constraints, select the tightest one, TODO: smallest index + //check constraints, select the tightest one, reinforcing Bland's rule if((myite=it[e])>0){ double val=it[b.cols-1]/myite; - if(val it=min_row_ptr;col_count it=b.begin();row_count()); - if(row_count==l){ - it+=b.cols; - }else{ - //remaining constraints - double coef=it[e]; - MatIterator_ shadow_it=min_row_ptr; - for(int col_it=0;col_it<(b.cols);col_it++,it++,shadow_it++){ - if(col_it==e){ - *it=-coef*(*shadow_it); - }else{ - *it-=coef*(*shadow_it); - } - } - } - } - //objective function - double coef=*pos_ptr; - MatIterator_ shadow_it=min_row_ptr; - MatIterator_ it=c.begin(); - for(int col_it=0;col_it<(b.cols-1);col_it++,it++,shadow_it++){ - if(col_it==e){ - *it=-coef*(*shadow_it); - }else{ - *it-=coef*(*shadow_it); - } - } - v+=coef*(*shadow_it); - - //new basis and nonbasic sets - int tmp=N[e]; - N[e]=B[l]; - B[l]=tmp; + solveLP_aux::pivot(c,b,v,N,B,l,e); printf("objective, v=%g\n",v); print_matrix(c); @@ -161,105 +280,57 @@ int solveLP(const Mat& Func, const Mat& Constr, Mat& z){ } printf("\n"); } - - //return the optimal solution - //z=cv::Mat_(1,c.cols,0); - MatIterator_ it=z.begin(); - for(int i=1;i<=c.cols;i++,it++){ - std::vector::iterator pos=B.begin(); - if((pos=std::find(B.begin(),B.end(),i))==B.end()){ - *it+=0; - }else{ - *it+=b.at(pos-B.begin(),b.cols-1); - } - } - - return 0; } -int solveLP_aux::initialize_simplex(const Mat& c, Mat& b, Mat& z,double& v){//TODO - z=Mat_(1,c.cols,0.0); - v=0; - return 0; - cv::Mat mod_b=(cv::Mat_(1,b.rows)); - bool gen_new_sol_flag=false,hom_sol_given=false; - if(z.type()!=CV_64FC1 || z.rows!=1 || z.cols!=c.cols || (hom_sol_given=(countNonZero(z)==0))){ - printf("line %d\n",__LINE__); - if(hom_sol_given==false){ - printf("line %d, %d\n",__LINE__,hom_sol_given); - z=cv::Mat_(1,c.cols,(double)0); - } - //check homogeneous solution - printf("line %d\n",__LINE__); - for(MatIterator_ b_it=b.begin()+b.cols-1,mod_b_it=mod_b.begin();mod_b_it!=mod_b.end(); - b_it+=b.cols,mod_b_it++){ - if(*b_it<0){ - //if no - we need feasible solution - gen_new_sol_flag=true; - break; - } - } - printf("line %d, gen_new_sol_flag=%d - I've got here!!!\n",__LINE__,gen_new_sol_flag); - //if yes - we have feasible solution! - }else{ - //check for feasibility - MatIterator_ it=b.begin(); - for(MatIterator_ mod_b_it=mod_b.begin();it!=b.end();mod_b_it++){ - double sum=0; - for(MatIterator_ z_it=z.begin();z_it!=z.end();z_it++,it++){ - sum+=(*it)*(*z_it); - } - if((*mod_b_it=(*it-sum))<0){ - break; - } - it++; - } - if(it==b.end()){ - //z contains feasible solution - just homogenise b's TODO: and v - gen_new_sol_flag=false; - for(MatIterator_ b_it=b.begin()+b.cols-1,mod_b_it=mod_b.begin();mod_b_it!=mod_b.end(); - b_it+=b.cols,mod_b_it++){ - *b_it=*mod_b_it; - } +inline void solveLP_aux::pivot(Mat_& c,Mat_& b,double& v,vector& N,vector& B, int leaving_index,int entering_index){ + double coef=b(leaving_index,entering_index); + for(int i=0;i(1,c.cols+1,0.0), - new_b=Mat_(b.rows,b.cols+1,-1.0), - new_z=Mat_(1,c.cols+1,0.0); - new_c.end()[-1]=-1; - c.copyTo(new_c.colRange(0,new_c.cols-1)); - - b.col(b.cols-1).copyTo(new_b.col(new_b.cols-1)); - b.colRange(0,b.cols-1).copyTo(new_b.colRange(0,new_b.cols-2)); - - Mat b_slice=b.col(b.cols-1); - new_z.end()[-1]=-*(std::min_element(b_slice.begin(),b_slice.end())); - - /*printf("matrix new_c\n"); - print_matrix(new_c); - printf("matrix new_b\n"); - print_matrix(new_b); - printf("matrix new_z\n"); - print_matrix(new_z);*/ - - printf("run for the second time!\n"); - solveLP(new_c,new_b,new_z); - printf("original z was\n"); - print_matrix(z); - printf("that's what I've got\n"); - print_matrix(new_z); - printf("for the constraints\n"); - print_matrix(b); - return 0; + for(int i=0;i& A,int col1,int col2){ + for(int i=0;i(1,2)<<1,0); ASSERT_EQ(cv::countNonZero(z!=etalon_z),0); - } - if(false){ +} + +TEST(Optim_LpSolver, regression_init_unfeasible){ + cv::Mat A,B,z,etalon_z; + + if(true){ //cormen's example #4 - unfeasible A=(cv::Mat_(1,3)<<-1,-1,-1); B=(cv::Mat_(2,4)<<-2,-7.5,-3,-10000,-20,-5,-10,-30000); std::cout<<"here A goes\n"<(1,2)<<1,0); + etalon_z=(cv::Mat_(1,3)<<1250,1000,0); ASSERT_EQ(cv::countNonZero(z!=etalon_z),0); } } +TEST(Optim_LpSolver, regression_absolutely_unfeasible){ + cv::Mat A,B,z,etalon_z; + + if(true){ + //trivial absolutely unfeasible example + A=(cv::Mat_(1,1)<<1); + B=(cv::Mat_(2,2)<<1,-1); + std::cout<<"here A goes\n"<(1,2)<<1,1); + B=(cv::Mat_(1,3)<<1,1,1); + std::cout<<"here A goes\n"<(1,2)<<2,-1); + B=(cv::Mat_(2,3)<<2,-1,2,1,-5,-4); + std::cout<<"here A goes\n"<(1,4)<<10,-57,-9,-24); + B=(cv::Mat_(3,5)<<0.5,-5.5,-2.5,9,0,0.5,-1.5,-0.5,1,0,1,0,0,0,1); + std::cout<<"here A goes\n"< **contact_Vadim**: min_l2_norm, init_optional_fsbl_check, error_codes, comment_style-too_many?, copyTo temp headers + // + // ??how_check_multiple_solutions & pass_test - DONE + // Blands_rule - DONE + // (&1tests on cycling) + // + // make_more_clear (assert, assign) + // wrap in OOP + // + // non-trivial tests + // pull-request + // + // study hill and other algos + // // ??how to get smallest l2 norm // FUTURE: compress&debug-> more_tests(Cormen) -> readNumRecipes-> fast&stable || hill_climbing