#include #include "commonlib.h" #include "lp_lib.h" #include "lp_report.h" #include "lp_pricePSE.h" #include "lp_price.h" #if libBLAS > 0 #include "myblas.h" #endif #ifdef FORTIFY # include "lp_fortify.h" #endif /* Simplex pricing utility module - w/interface for lp_solve v5.0+ ------------------------------------------------------------------------- Author: Kjell Eikland Contact: kjell.eikland@broadpark.no License terms: LGPL. Requires: lp_lib.h, commonlib.h Release notes: v1.0.0 1 July 2004 Routines extracted from lp_lib. v1.0.1 10 July 2004 Added comparison operators for determination of entering and leaving variables. Added routines for multiple and partial pricing and made corresponding changes to colprim and rowdual. v1.0.2 20 August 2004 Implemented relative pivot size control in rowprim and rowdual. v1.1.0 15 October 2004 Added dual long step logic. v1.1.1 22 October 2004 Added bound sort order to variable selections. v1.2.0 24 March 2005 Completed multiple pricing logic. ------------------------------------------------------------------------- */ /* Comparison operators for entering and leaving variables for both the primal and dual simplexes. The functions compare a candidate variable with an incumbent. */ int CMP_CALLMODEL compareImprovementVar(const pricerec *current, const pricerec *candidate) { register int result = COMP_PREFERNONE; register lprec *lp = current->lp; register REAL testvalue, margin = PREC_IMPROVEGAP; int currentcolno, currentvarno = current->varno, candidatecolno, candidatevarno = candidate->varno; MYBOOL isdual = candidate->isdual; if(isdual) { candidatevarno = lp->var_basic[candidatevarno]; currentvarno = lp->var_basic[currentvarno]; } candidatecolno = candidatevarno - lp->rows; currentcolno = currentvarno - lp->rows; /* Do pivot-based selection unless Bland's (first index) rule is active */ if(lp->_piv_rule_ != PRICER_FIRSTINDEX) { MYBOOL candbetter; /* Find the largest value - normalize in case of the dual, since constraint violation is expressed as a negative number. */ /* Use absolute test for "small numbers", relative otherwise */ testvalue = candidate->pivot; if(fabs(testvalue) < LIMIT_ABS_REL) testvalue -= current->pivot; else testvalue = my_reldiff(testvalue, current->pivot); if(isdual) testvalue = -testvalue; candbetter = (MYBOOL) (testvalue > 0); if(candbetter) { if(testvalue > margin) result = COMP_PREFERCANDIDATE; } #if 0 /* Give more opportunity to optimize on non-primary criteria */ else if (testvalue < -margin) #else /* Give reduced opportunity to optimize on non-primary criteria */ else if (testvalue < -lp->epsvalue) #endif result = COMP_PREFERINCUMBENT; #ifdef UseSortOnBound /* Extra selection criterion based on the variable's range; variable with - DUAL: small bound out; PRIMAL: large bound in */ if(result == COMP_PREFERNONE) { testvalue = lp->upbo[candidatevarno] - lp->upbo[currentvarno]; if(testvalue < -margin) result = COMP_PREFERINCUMBENT; else if(testvalue > margin) result = COMP_PREFERCANDIDATE; result = my_chsign(isdual, result); } #endif #ifdef UseSortOnColumnLength /* Prevent long columns from entering the basis */ if(result == COMP_PREFERNONE) { if(candidatecolno > 0) testvalue = mat_collength(lp->matA, candidatecolno) + (is_obj_in_basis(lp) && (lp->obj[candidatecolno] != 0) ? 1 : 0); else testvalue = 1; if(currentcolno > 0) testvalue -= mat_collength(lp->matA, currentcolno) + (is_obj_in_basis(lp) && (lp->obj[currentcolno] != 0) ? 1 : 0); else testvalue -= 1; if(testvalue > 0) result = COMP_PREFERINCUMBENT; else if(testvalue < 0) result = COMP_PREFERCANDIDATE; result = my_chsign(isdual, result); } #endif /* Select absolute best if the non-primary criteria failed to separate */ if((result == COMP_PREFERNONE) && candbetter) { result = COMP_PREFERCANDIDATE; goto Finish; } } /* Final tie-breakers */ if(result == COMP_PREFERNONE) { /* Add randomization tie-braker */ if(lp->piv_strategy & PRICE_RANDOMIZE) { result = my_sign(PRICER_RANDFACT - rand_uniform(lp, 1.0)); if(candidatevarno < currentvarno) result = -result; } /* Resolve ties via index ordinal */ if(result == COMP_PREFERNONE) { if(candidatevarno < currentvarno) result = COMP_PREFERCANDIDATE; else /* if(candidatevarno > currentvarno) */ result = COMP_PREFERINCUMBENT; if(lp->_piv_left_) result = -result; } } Finish: return( result ); } int CMP_CALLMODEL compareSubstitutionVar(const pricerec *current, const pricerec *candidate) { register int result = COMP_PREFERNONE; register lprec *lp = current->lp; register REAL testvalue = candidate->theta, margin = current->theta; MYBOOL isdual = candidate->isdual, candbetter; int currentcolno, currentvarno = current->varno, candidatecolno, candidatevarno = candidate->varno; if(!isdual) { candidatevarno = lp->var_basic[candidatevarno]; currentvarno = lp->var_basic[currentvarno]; } candidatecolno = candidatevarno - lp->rows; currentcolno = currentvarno - lp->rows; /* Compute the ranking test metric. */ if(isdual) { testvalue = fabs(testvalue); margin = fabs(margin); } /* Use absolute test for "small numbers", relative otherwise */ if(fabs(testvalue) < LIMIT_ABS_REL) testvalue -= margin; else testvalue = my_reldiff(testvalue, margin); /* Find if the new Theta is smaller or near equal (i.e. testvalue <= eps) compared to the previous best; ties will be broken by pivot size or index NB! The margin below is essential in maintaining primal/dual feasibility during the primal/dual simplex, respectively. Sometimes a small value prevents the selection of a suitable pivot, thereby weakening the numerical stability of some models */ margin = PREC_SUBSTFEASGAP; candbetter = (MYBOOL) (testvalue < 0); if(candbetter) { if(testvalue < -margin) result = COMP_PREFERCANDIDATE; } else if(testvalue > margin) result = COMP_PREFERINCUMBENT; /* Resolve a tie */ if(result == COMP_PREFERNONE) { REAL currentpivot = fabs(current->pivot), candidatepivot = fabs(candidate->pivot); /* Handle first index / Bland's rule specially */ if(lp->_piv_rule_ == PRICER_FIRSTINDEX) { #if 1 /* Special secondary selection by pivot size (limited stability protection) */ margin = candidate->epspivot; if((candidatepivot >= margin) && (currentpivot < margin)) result = COMP_PREFERCANDIDATE; #endif } else { /* General secondary selection based on pivot size */ #if 0 if(candidatepivot > MIN_STABLEPIVOT) testvalue = my_reldiff(testvalue, currentpivot); else #endif testvalue = candidatepivot - currentpivot; if(testvalue > margin) result = COMP_PREFERCANDIDATE; else if(testvalue < -margin) result = COMP_PREFERINCUMBENT; #ifdef UseSortOnBound /* Extra selection criterion based on the variable's range; variable with - PRIMAL: small bound out; DUAL: large bound in */ if(result == COMP_PREFERNONE) { testvalue = lp->upbo[candidatevarno] - lp->upbo[currentvarno]; if(testvalue < -margin) result = COMP_PREFERCANDIDATE; else if(testvalue > margin) result = COMP_PREFERINCUMBENT; result = my_chsign(isdual, result); } #endif #ifdef UseSortOnColumnLength /* Prevent long columns from entering the basis */ if(result == COMP_PREFERNONE) { if(candidatecolno > 0) testvalue = mat_collength(lp->matA, candidatecolno) + (is_obj_in_basis(lp) && (lp->obj[candidatecolno] != 0) ? 1 : 0); else testvalue = 1; if(currentcolno > 0) testvalue -= mat_collength(lp->matA, currentcolno) + (is_obj_in_basis(lp) && (lp->obj[currentcolno] != 0) ? 1 : 0); else testvalue -= 1; if(testvalue > 0) result = COMP_PREFERCANDIDATE; else if(testvalue < 0) result = COMP_PREFERINCUMBENT; result = my_chsign(isdual, result); } #endif } } /* Select absolute best if the non-primary criteria failed to separate */ if((result == COMP_PREFERNONE) && candbetter) { result = COMP_PREFERCANDIDATE; goto Finish; } /* Final tie-breakers */ if(result == COMP_PREFERNONE) { /* Add randomization tie-braker */ if(lp->piv_strategy & PRICE_RANDOMIZE) { result = my_sign(PRICER_RANDFACT - rand_uniform(lp, 1.0)); if(candidatevarno < currentvarno) result = -result; } /* Resolve ties via index ordinal (also prefers slacks over user variables) */ if(result == COMP_PREFERNONE) { if(candidatevarno < currentvarno) result = COMP_PREFERCANDIDATE; else /* if(candidatevarno > currentvarno) */ result = COMP_PREFERINCUMBENT; if(lp->_piv_left_) result = -result; } } Finish: return( result ); } int CMP_CALLMODEL compareBoundFlipVar(const pricerec *current, const pricerec *candidate) { register REAL testvalue, margin; register int result = COMP_PREFERNONE; register lprec *lp = current->lp; MYBOOL candbetter; int currentvarno = current->varno, candidatevarno = candidate->varno; if(!current->isdual) { candidatevarno = lp->var_basic[candidatevarno]; currentvarno = lp->var_basic[currentvarno]; } /* Compute the ranking test metric. */ testvalue = candidate->theta; margin = current->theta; if(candidate->isdual) { testvalue = fabs(testvalue); margin = fabs(margin); } if(fabs(margin) < LIMIT_ABS_REL) testvalue -= margin; else testvalue = my_reldiff(testvalue, margin); /* Find if the new Theta is smaller or near equal (i.e. testvalue <= eps) compared to the previous best; ties will be broken by pivot size or index */ margin = PREC_SUBSTFEASGAP; candbetter = (MYBOOL) (testvalue < 0); if(candbetter) { if(testvalue < -margin) result = COMP_PREFERCANDIDATE; } else if(testvalue > margin) result = COMP_PREFERINCUMBENT; /* Resolve a tie */ if(result == COMP_PREFERNONE) { /* Tertiary selection based on priority for large pivot sizes */ if(result == COMP_PREFERNONE) { REAL currentpivot = fabs(current->pivot), candidatepivot = fabs(candidate->pivot); if(candidatepivot > currentpivot+margin) result = COMP_PREFERCANDIDATE; else if(candidatepivot < currentpivot-margin) result = COMP_PREFERINCUMBENT; } /* Secondary selection based on priority for narrow-bounded variables */ if(result == COMP_PREFERNONE) result = compareREAL(&(lp->upbo[currentvarno]), &(lp->upbo[candidatevarno])); } /* Select absolute best if the non-primary criteria failed to separate */ if((result == COMP_PREFERNONE) && candbetter) { result = COMP_PREFERCANDIDATE; goto Finish; } /* Quaternary selection by index value */ if(result == COMP_PREFERNONE) { if(candidatevarno < currentvarno) result = COMP_PREFERCANDIDATE; else result = COMP_PREFERINCUMBENT; if(lp->_piv_left_) result = -result; } Finish: return( result ); } /* Validity operators for entering and leaving columns for both the primal and dual simplex. All candidates must satisfy these tests to qualify to be allowed to be a subject for the comparison functions/operators. */ STATIC MYBOOL validImprovementVar(pricerec *candidate) { register REAL candidatepivot = fabs(candidate->pivot); #ifdef Paranoia return( (MYBOOL) ((candidate->varno > 0) && (candidatepivot > candidate->lp->epsvalue)) ); #else return( (MYBOOL) (candidatepivot > candidate->lp->epsvalue) ); #endif } STATIC MYBOOL validSubstitutionVar(pricerec *candidate) { register lprec *lp = candidate->lp; register REAL theta = (candidate->isdual ? fabs(candidate->theta) : candidate->theta); #ifdef Paranoia if(candidate->varno <= 0) return( FALSE ); else #endif if(fabs(candidate->pivot) >= lp->infinite) return( (MYBOOL) (theta < lp->infinite) ); else return( (MYBOOL) ((theta < lp->infinite) && (fabs(candidate->pivot) >= candidate->epspivot)) ); } int CMP_CALLMODEL compareImprovementQS(const UNIONTYPE QSORTrec *current, const UNIONTYPE QSORTrec *candidate) { return( compareImprovementVar((pricerec *) current->pvoidint2.ptr, (pricerec *) candidate->pvoidint2.ptr) ); } int CMP_CALLMODEL compareSubstitutionQS(const UNIONTYPE QSORTrec *current, const UNIONTYPE QSORTrec *candidate) { return( compareBoundFlipVar((pricerec *) current->pvoidint2.ptr, (pricerec *) candidate->pvoidint2.ptr) ); /* return( compareSubstitutionVar((pricerec *) current->self, (pricerec *) candidate->self) ); */ } /* Function to add a valid pivot candidate into the specified list */ STATIC int addCandidateVar(pricerec *candidate, multirec *multi, findCompare_func findCompare, MYBOOL allowSortedExpand) { int insertpos, delta = 1; pricerec *targetrec; /* Find the insertion point (if any) */ if((multi->freeList[0] == 0) || (multi->sorted && allowSortedExpand) || (candidate->isdual && (multi->used == 1) && ((multi->step_last >= multi->epszero) || multi_truncatingvar(multi, ((pricerec *) (multi->sortedList[0].pvoidreal.ptr))->varno))) ) { UNIONTYPE QSORTrec searchTarget; /* Make sure that the list is sorted before the search for an insertion point */ if((multi->freeList[0] == 0) && !multi->sorted) { multi->sorted = QS_execute(multi->sortedList, multi->used, findCompare, &insertpos); multi->dirty = (MYBOOL) (insertpos > 0); } /* Perform the search */ searchTarget.pvoidint2.ptr = (void *) candidate; insertpos = sizeof(searchTarget); insertpos = findIndexEx(&searchTarget, multi->sortedList-delta, multi->used, delta, insertpos, findCompare, TRUE); if(insertpos > 0) return( -1 ); insertpos = -insertpos - delta; /* Check if the candidate is worse than the worst of the list */ if(((insertpos >= multi->size) && (multi->freeList[0] == 0)) || ((insertpos == multi->used) && (!allowSortedExpand || (multi->step_last >= multi->epszero)))) return( -1 ); #ifdef Paranoia /* Do validation */ if((insertpos < 0) || (insertpos > multi->used)) return( -1 ); #endif /* Define the target for storing the candidate; Case 1: List is full and we must discard the previously worst candidate Case 2: List is not full and we simply use the next free position */ if(multi->freeList[0] == 0) targetrec = (pricerec *) multi->sortedList[multi->used-1].pvoidreal.ptr; else { delta = multi->freeList[0]--; delta = multi->freeList[delta]; targetrec = &(multi->items[delta]); } } else { delta = multi->freeList[0]--; delta = multi->freeList[delta]; targetrec = &(multi->items[delta]); insertpos = multi->used; } /* Insert the new candidate record in the data store */ MEMCOPY(targetrec, candidate, 1); /* Store the pointer data and handle tree cases: Case 1: The list is unsorted and not full; simply append pointer to list, Case 2: The list is sorted and full; insert the pointer by discarding previous last, Case 3: The list is sorted and not full; shift the inferior items down, and increment count */ if((multi->used < multi->size) && (insertpos >= multi->used)) { QS_append(multi->sortedList, insertpos, targetrec); multi->used++; } else { if(multi->used == multi->size) QS_insert(multi->sortedList, insertpos, targetrec, multi->size-1); /* Discard previous last */ else { QS_insert(multi->sortedList, insertpos, targetrec, multi->used); /* Keep previous last */ multi->used++; } } multi->active = insertpos; #ifdef Paranoia if((insertpos >= multi->size) || (insertpos >= multi->used)) report(multi->lp, SEVERE, "addCandidateVar: Insertion point beyond limit!\n"); #endif return( insertpos ); } STATIC MYBOOL findImprovementVar(pricerec *current, pricerec *candidate, MYBOOL collectMP, int *candidatecount) /* PRIMAL: Find a variable to enter the basis DUAL: Find a variable to leave the basis Allowed variable set: Any pivot PRIMAL:larger or DUAL:smaller than threshold value of 0 */ { MYBOOL Action = FALSE, #ifdef ExtractedValidityTest Accept = TRUE; #else /* Check for validity and compare result with previous best */ Accept = validImprovementVar(candidate); #endif if(Accept) { if(candidatecount != NULL) (*candidatecount)++; if(collectMP) { if(addCandidateVar(candidate, current->lp->multivars, (findCompare_func *) compareImprovementQS, FALSE) < 0) return(Action); } if(current->varno > 0) Accept = (MYBOOL) (compareImprovementVar(current, candidate) > 0); } /* Apply candidate if accepted */ if(Accept) { (*current) = *candidate; /* Force immediate acceptance for Bland's rule using the primal simplex */ if(!candidate->isdual) Action = (MYBOOL) (candidate->lp->_piv_rule_ == PRICER_FIRSTINDEX); } return(Action); } /* Bound flip variable accumulation routine */ STATIC MYBOOL collectMinorVar(pricerec *candidate, multirec *longsteps, MYBOOL isphase2, MYBOOL isbatch) { int inspos; /* 1. Check for ratio and pivot validity (to have the extra flexibility that all bound-flip candidates are also possible as basis-entering variables */ if(!validSubstitutionVar(candidate)) return( FALSE ); /* 2. If the free-list is empty we need to see if we have a better candidate, and for this the candidate list has to be sorted by merit */ if(!isbatch && !longsteps->sorted && (longsteps->used > 1) && ((longsteps->freeList[0] == 0) || multi_truncatingvar(longsteps, candidate->varno) || (longsteps->step_last >= longsteps->epszero) )) { longsteps->sorted = QS_execute(longsteps->sortedList, longsteps->used, (findCompare_func *) compareSubstitutionQS, &inspos); longsteps->dirty = (MYBOOL) (inspos > 0); if(longsteps->dirty) multi_recompute(longsteps, 0, isphase2, TRUE); } /* 3. Now handle three cases... - Add to the list when the list is not full and there is opportunity for improvement, - Check if we should replace an incumbent when the list is full, - Check if we should replace an incumbent when the list is not full, there is no room for improvement, but the current candidate is better than an incumbent. */ inspos = addCandidateVar(candidate, longsteps, (findCompare_func *) compareSubstitutionQS, TRUE); /* 4. Recompute steps and objective, and (if relevant) determine if we may be suboptimal in relation to an incumbent MILP solution. */ return( (MYBOOL) (inspos >= 0) && ((isbatch == TRUE) || multi_recompute(longsteps, inspos, isphase2, TRUE)) ); } STATIC MYBOOL findSubstitutionVar(pricerec *current, pricerec *candidate, int *candidatecount) /* PRIMAL: Find a variable to leave the basis DUAL: Find a variable to enter the basis Allowed variable set: "Equal-valued" smallest thetas! */ { MYBOOL Action = FALSE, #ifdef ExtractedValidityTest Accept = TRUE; #else /* Check for validity and comparison result with previous best */ Accept = validSubstitutionVar(candidate); #endif if(Accept) { if(candidatecount != NULL) (*candidatecount)++; if(current->varno != 0) Accept = (MYBOOL) (compareSubstitutionVar(current, candidate) > 0); } /* Apply candidate if accepted */ if(Accept) { (*current) = *candidate; /* Force immediate acceptance for Bland's rule using the dual simplex */ #ifdef ForceEarlyBlandRule if(candidate->isdual) Action = (MYBOOL) (candidate->lp->_piv_rule_ == PRICER_FIRSTINDEX); #endif } return(Action); } /* Partial pricing management routines */ STATIC partialrec *partial_createBlocks(lprec *lp, MYBOOL isrow) { partialrec *blockdata; blockdata = (partialrec *) calloc(1, sizeof(*blockdata)); blockdata->lp = lp; blockdata->blockcount = 1; blockdata->blocknow = 1; blockdata->isrow = isrow; return(blockdata); } STATIC int partial_countBlocks(lprec *lp, MYBOOL isrow) { partialrec *blockdata = IF(isrow, lp->rowblocks, lp->colblocks); if(blockdata == NULL) return( 1 ); else return( blockdata->blockcount ); } STATIC int partial_activeBlocks(lprec *lp, MYBOOL isrow) { partialrec *blockdata = IF(isrow, lp->rowblocks, lp->colblocks); if(blockdata == NULL) return( 1 ); else return( blockdata->blocknow ); } STATIC void partial_freeBlocks(partialrec **blockdata) { if((blockdata == NULL) || (*blockdata == NULL)) return; FREE((*blockdata)->blockend); FREE((*blockdata)->blockpos); FREE(*blockdata); } /* Function to provide for left-right or right-left scanning of entering/leaving variables; note that *end must have been initialized by the calling routine! */ STATIC void makePriceLoop(lprec *lp, int *start, int *end, int *delta) { int offset = is_piv_mode(lp, PRICE_LOOPLEFT); if((offset) || (((lp->total_iter+offset) % 2 == 0) && is_piv_mode(lp, PRICE_LOOPALTERNATE))) { *delta = -1; /* Step backwards - "left" */ swapINT(start, end); lp->_piv_left_ = TRUE; } else { *delta = 1; /* Step forwards - "right" */ lp->_piv_left_ = FALSE; } } /* Routine to verify accuracy of the current basis factorization */ STATIC MYBOOL serious_facterror(lprec *lp, REAL *bvector, int maxcols, REAL tolerance) { int i, j, ib, ie, nz, nc; REAL sum, tsum = 0, err = 0; MATrec *mat = lp->matA; if(bvector == 0) bvector = lp->bsolveVal; nc =0; nz = 0; for(i = 1; (i <= lp->rows) && (nc <= maxcols); i++) { /* Do we have a non-slack variable? (we choose to skip slacks, since they have "natural" good accuracy properties) */ j = lp->var_basic[i] - lp->rows; if(j <= 0) continue; nc++; /* Compute cross product for basic, non-slack column */ ib = mat->col_end[j-1]; ie = mat->col_end[j]; nz += ie - ib; sum = get_OF_active(lp, j+lp->rows, bvector[0]); for(; ib < ie; ib++) sum += COL_MAT_VALUE(ib)*bvector[COL_MAT_ROWNR(ib)]; /* Catch high precision early, so we don't to uneccessary work */ tsum += sum; SETMAX(err, fabs(sum)); if((tsum / nc > tolerance / 100) && (err < tolerance / 100)) break; } err /= mat->infnorm; return( (MYBOOL) (err >= tolerance) ); } /* Computation of reduced costs */ STATIC void update_reducedcosts(lprec *lp, MYBOOL isdual, int leave_nr, int enter_nr, REAL *prow, REAL *drow) { /* "Fast" update of the dual reduced cost vector; note that it must be called after the pivot operation and only applies to a major "true" iteration */ int i; REAL hold; if(isdual) { hold = -drow[enter_nr]/prow[enter_nr]; for(i=1; i <= lp->sum; i++) if(!lp->is_basic[i]) { if(i == leave_nr) drow[i] = hold; else { drow[i] += hold*prow[i]; my_roundzero(drow[i], lp->epsmachine); } } } else report(lp, SEVERE, "update_reducedcosts: Cannot update primal reduced costs!\n"); } STATIC void compute_reducedcosts(lprec *lp, MYBOOL isdual, int row_nr, int *coltarget, MYBOOL dosolve, REAL *prow, int *nzprow, REAL *drow, int *nzdrow, int roundmode) { REAL epsvalue = lp->epsvalue; /* Any larger value can produce a suboptimal result */ roundmode |= MAT_ROUNDRC; if(isdual) { bsolve_xA2(lp, coltarget, row_nr, prow, epsvalue, nzprow, /* Calculate net sensitivity given a leaving variable */ 0, drow, epsvalue, nzdrow, /* Calculate the net objective function values */ roundmode); } else { REAL *bVector; #if 1 /* Legacy mode, that is possibly a little faster */ if((lp->multivars == NULL) && (lp->P1extraDim == 0)) bVector = drow; else #endif bVector = lp->bsolveVal; if(dosolve) { bsolve(lp, 0, bVector, lp->bsolveIdx, epsvalue*DOUBLEROUND, 1.0); if(!isdual && (row_nr == 0) && (lp->improve & IMPROVE_SOLUTION) && !refactRecent(lp) && serious_facterror(lp, bVector, lp->rows, lp->epsvalue)) set_action(&lp->spx_action, ACTION_REINVERT); } prod_xA(lp, coltarget, bVector, lp->bsolveIdx, epsvalue, 1.0, drow, nzdrow, roundmode); } } /* Primal: Prevent acceptance of an entering variable when the magnitude of other candidates is also very small. Dual: Prevent acceptance of a leaving variable when the magnitude of other candidates is also very small. Both of these cases are associated with numerical stalling, which we could argue should be detected and handled by the stalling monitor routine. */ STATIC MYBOOL verify_stability(lprec *lp, MYBOOL isprimal, REAL xfeas, REAL sfeas, int nfeas) { MYBOOL testOK = TRUE; return( testOK ); #if 1 /* Try to make dual feasibility as tight as possible */ if(!isprimal) /* if(lp->P1extraVal == 0) */ { xfeas /= (1+lp->rhsmax); sfeas /= (1+lp->rhsmax); } #endif xfeas = fabs(xfeas); /* Maximum (positive) infeasibility */ /* if(xfeas < lp->epspivot) { */ if(xfeas < lp->epssolution) { REAL f; sfeas = fabs(sfeas); /* Make sum of infeasibilities positive */ xfeas = (sfeas-xfeas)/nfeas; /* Average "residual" feasibility */ f = 1 + log10((REAL) nfeas); /* Some numerical complexity scalar */ /* Numerical errors can interact to cause non-convergence, and the idea is to relax the tolerance to account for this and only marginally weakening the (user-specified) tolerance. */ if((sfeas-xfeas) < f*lp->epsprimal) testOK = FALSE; } return( testOK ); } /* Find an entering column for the case that the specified basic variable is fixed or zero - typically used for artificial variable elimination */ STATIC int find_rowReplacement(lprec *lp, int rownr, REAL *prow, int *nzprow) /* The logic in this section generally follows Chvatal: Linear Programming, p. 130 Basically, the function is a specialized coldual(). */ { int i, bestindex; REAL bestvalue; /* Solve for "local reduced cost" */ set_action(&lp->piv_strategy, PRICE_FORCEFULL); compute_reducedcosts(lp, TRUE, rownr, NULL, TRUE, prow, nzprow, NULL, NULL, MAT_ROUNDDEFAULT); clear_action(&lp->piv_strategy, PRICE_FORCEFULL); /* Find a suitably non-singular variable to enter ("most orthogonal") */ bestindex = 0; bestvalue = 0; for(i = 1; i <= lp->sum-abs(lp->P1extraDim); i++) { if(!lp->is_basic[i] && !is_fixedvar(lp, i) && (fabs(prow[i]) > bestvalue)) { bestindex = i; bestvalue = fabs(prow[i]); } } /* Prepare to update inverse and pivot/iterate (compute Bw=a) */ if(i > lp->sum-abs(lp->P1extraDim)) bestindex = 0; else fsolve(lp, bestindex, prow, nzprow, lp->epsmachine, 1.0, TRUE); return( bestindex ); } /* Find the primal simplex entering non-basic column variable */ STATIC int colprim(lprec *lp, REAL *drow, int *nzdrow, MYBOOL skipupdate, int partialloop, int *candidatecount, MYBOOL updateinfeas, REAL *xviol) { int i, ix, iy, iz, ninfeas, nloop = 0; REAL f, sinfeas, xinfeas, epsvalue = lp->epsdual; pricerec current, candidate; MYBOOL collectMP = FALSE; int *coltarget = NULL; /* Identify pivot column according to pricing strategy; set entering variable initial threshold reduced cost value to "0" */ current.pivot = lp->epsprimal; /* Minimum acceptable improvement */ current.varno = 0; current.lp = lp; current.isdual = FALSE; candidate.lp = lp; candidate.isdual = FALSE; *candidatecount = 0; /* Update local value of pivot setting and determine active multiple pricing set */ lp->_piv_rule_ = get_piv_rule(lp); doLoop: nloop++; if((lp->multivars != NULL) && ((lp->simplex_mode & SIMPLEX_PRIMAL_PRIMAL) != 0)) { collectMP = multi_mustupdate(lp->multivars); if(collectMP) { multi_restart(lp->multivars); coltarget = NULL; } else coltarget = multi_indexSet(lp->multivars, FALSE); } /* Compute reduced costs c - c*Inv(B), if necessary (i.e. the previous iteration was not a "minor" iteration/bound flip) */ if(!skipupdate) { #ifdef UsePrimalReducedCostUpdate /* Recompute from scratch only at the beginning, otherwise update */ if((lp->current_iter > 0) && (refactRecent(lp) == AUTOMATIC)) #endif compute_reducedcosts(lp, FALSE, 0, coltarget, (MYBOOL) ((nloop <= 1) || (partialloop > 1)), NULL, NULL, drow, nzdrow, MAT_ROUNDDEFAULT); } /* Loop over active partial column set; we presume that reduced costs have only been updated for columns in the active partial range. */ ix = 1; iy = nzdrow[0]; ninfeas = 0; xinfeas = 0; sinfeas = 0; makePriceLoop(lp, &ix, &iy, &iz); iy *= iz; for(; ix*iz <= iy; ix += iz) { i = nzdrow[ix]; #if 0 /* Not necessary since we masked them out in compute_reducedcosts() */ if(i > lp->sum-abs(lp->P1extraDim)) continue; #endif /* Check if the pivot candidate is on the block-list */ if(lp->rejectpivot[0] > 0) { int kk; for(kk = 1; (kk <= lp->rejectpivot[0]) && (i != lp->rejectpivot[kk]); kk++); if(kk <= lp->rejectpivot[0]) continue; } /* Retrieve the applicable reduced cost - threshold should not be smaller than 0 */ f = my_chsign(lp->is_lower[i], drow[i]); if(f <= epsvalue) continue; /* Find entering variable according to strategy (largest positive f) */ ninfeas++; SETMAX(xinfeas, f); sinfeas += f; candidate.pivot = normalizeEdge(lp, i, f, FALSE); candidate.varno = i; if(findImprovementVar(¤t, &candidate, collectMP, candidatecount)) break; } /* Check if we should loop again after a multiple pricing update */ if(lp->multivars != NULL) { if(collectMP) { if(!lp->multivars->sorted) lp->multivars->sorted = QS_execute(lp->multivars->sortedList, lp->multivars->used, (findCompare_func *) compareImprovementQS, NULL); coltarget = multi_indexSet(lp->multivars, TRUE); } else if((current.varno == 0) && (lp->multivars->retries == 0)) { ix = partial_blockStart(lp, FALSE); iy = partial_blockEnd(lp, FALSE); lp->multivars->used = 0; lp->multivars->retries++; goto doLoop; } /* Shrink the candidate list */ lp->multivars->retries = 0; if(current.varno != 0) multi_removevar(lp->multivars, current.varno); } /* Check for optimality */ if(xviol != NULL) *xviol = xinfeas; if(updateinfeas) lp->suminfeas = fabs(sinfeas); if((lp->multivars == NULL) && (current.varno > 0) && !verify_stability(lp, TRUE, xinfeas, sinfeas, ninfeas)) current.varno = 0; /* Produce statistics */ if(lp->spx_trace) { if(current.varno > 0) report(lp, DETAILED, "colprim: Column %d reduced cost = " RESULTVALUEMASK "\n", current.varno, current.pivot); else report(lp, DETAILED, "colprim: No positive reduced costs found, optimality!\n"); } return( current.varno ); } /* colprim */ /* Find the primal simplex leaving basic column variable */ STATIC int rowprim(lprec *lp, int colnr, LREAL *theta, REAL *pcol, int *nzpcol, MYBOOL forceoutEQ, REAL *xviol) { int i, ii, iy, iz, Hpass, k, *nzlist; LREAL f, savef; REAL Heps, Htheta, Hlimit, epsvalue, epspivot, p = 0.0; pricerec current, candidate; MYBOOL isupper = !lp->is_lower[colnr], HarrisTwoPass = FALSE; /* Update local value of pivot setting */ lp->_piv_rule_ = get_piv_rule(lp); if(nzpcol == NULL) nzlist = (int *) mempool_obtainVector(lp->workarrays, lp->rows+1, sizeof(*nzlist)); else nzlist = nzpcol; /* Find unconditional non-zeros and optionally compute relative size of epspivot */ epspivot = lp->epspivot; epsvalue = lp->epsvalue; Hlimit = 0; Htheta = 0; k = 0; for(i = 1; i <= lp->rows; i++) { p = fabs(pcol[i]); if(p > Hlimit) Hlimit = p; if(p > epsvalue) { k++; nzlist[k] = i; SETMAX(Htheta, p); } #ifdef Paranoia else { if(lp->spx_trace) report(lp, FULL, "rowprim: Row %d with pivot " RESULTVALUEMASK " rejected as too small\n", i, p); } #endif } if(xviol != NULL) *xviol = Htheta; Htheta = 0; /* Update non-zero list based on the new pivot threshold */ #ifdef UseRelativePivot_Primal /* epspivot *= sqrt(lp->matA->dynrange) / lp->matA->infnorm; */ epspivot /= MAX(1, sqrt(lp->matA->colmax[colnr])); iy = k; k = 0; p = 0; for(ii = 1; ii <= iy; ii++) { i = nzlist[ii]; p = fabs(pcol[i]); /* Compress the list of valid alternatives, if appropriate */ if(p > epspivot) { k++; nzlist[k] = i; } #ifdef Paranoia else { if(lp->spx_trace) report(lp, FULL, "rowprim: Row %d with pivot " RESULTVALUEMASK " rejected as too small\n", i, p); } #endif } #endif /* Initialize counters */ nzlist[0] = k; k = 0; Retry: k++; HarrisTwoPass = is_piv_mode(lp, PRICE_HARRISTWOPASS); if(HarrisTwoPass) Hpass = 1; else Hpass = 2; current.theta = lp->infinite; current.pivot = 0; current.varno = 0; current.isdual = FALSE; current.epspivot = epspivot; current.lp = lp; candidate.epspivot = epspivot; candidate.isdual = FALSE; candidate.lp = lp; savef = 0; for(; Hpass <= 2; Hpass++) { Htheta = lp->infinite; if(Hpass == 1) { Hlimit = lp->infinite; /* Don't apply any limit in the first pass */ Heps = epspivot/lp->epsprimal; /* Scaled to lp->epsprimal used in compute_theta() */ } else { Hlimit = current.theta; /* This is the smallest Theta of the first pass */ Heps = 0.0; } current.theta = lp->infinite; current.pivot = 0; current.varno = 0; savef = 0; ii = 1; iy = nzlist[0]; makePriceLoop(lp, &ii, &iy, &iz); iy *= iz; for(; ii*iz <= iy; ii += iz) { i = nzlist[ii]; f = pcol[i]; candidate.theta = f; candidate.pivot = f; candidate.varno = i; /*i =*/ compute_theta(lp, i, &candidate.theta, isupper, my_if(lp->upbo[lp->var_basic[i]] < lp->epsprimal, Heps/10, Heps), TRUE); if(fabs(candidate.theta) >= lp->infinite) { savef = f; candidate.theta = 2*lp->infinite; continue; } /* Find the candidate leaving variable according to strategy (smallest theta) */ if((Hpass == 2) && (candidate.theta > Hlimit)) continue; /* Give a slight preference to fixed variables (mainly equality slacks) */ if(forceoutEQ) { p = candidate.pivot; if(lp->upbo[lp->var_basic[i]] < lp->epsprimal) { /* Give an extra early boost to equality slack elimination, if specified */ if(forceoutEQ == AUTOMATIC) candidate.pivot *= 1.0+lp->epspivot; else candidate.pivot *= 10.0; } } if(HarrisTwoPass) { f = candidate.theta; if(Hpass == 2) candidate.theta = 1; if(findSubstitutionVar(¤t, &candidate, NULL)) break; if((Hpass == 2) && (current.varno == candidate.varno)) Htheta = f; } else if(findSubstitutionVar(¤t, &candidate, NULL)) break; /* Restore temporarily modified pivot */ if(forceoutEQ && (current.varno == candidate.varno)) current.pivot = p; } } if(HarrisTwoPass) current.theta = Htheta; /* Handle case of no available leaving variable */ if(current.varno == 0) { if(lp->upbo[colnr] >= lp->infinite) { /* Optionally try again with reduced pivot threshold level */ if(k < 2) { epspivot = epspivot / 10; goto Retry; } } else { #if 1 i = 1; while((pcol[i] >= 0) && (i <= lp->rows)) i++; if(i > lp->rows) { /* Empty column with upper bound! */ lp->is_lower[colnr] = !lp->is_lower[colnr]; /* lp->is_lower[colnr] = FALSE; */ lp->rhs[0] += lp->upbo[colnr]*pcol[0]; } else /* if(pcol[i]<0) */ { current.varno = i; } #endif } } else if(current.theta >= lp->infinite) { report(lp, IMPORTANT, "rowprim: Numeric instability pcol[%d] = %g, rhs[%d] = %g, upbo = %g\n", current.varno, savef, current.varno, lp->rhs[current.varno], lp->upbo[lp->var_basic[current.varno]]); } /* Return working array to pool */ if(nzpcol == NULL) mempool_releaseVector(lp->workarrays, (char *) nzlist, FALSE); if(lp->spx_trace) report(lp, DETAILED, "row_prim: %d, pivot size = " RESULTVALUEMASK "\n", current.varno, current.pivot); /* *theta = current.theta; */ *theta = fabs(current.theta); return(current.varno); } /* rowprim */ /* Find the dual simplex leaving basic variable */ STATIC int rowdual(lprec *lp, REAL *rhvec, MYBOOL forceoutEQ, MYBOOL updateinfeas, REAL *xviol) { int k, i, iy, iz, ii, ninfeas; register REAL rh; REAL up, lo = 0, epsvalue, sinfeas, xinfeas; pricerec current, candidate; MYBOOL collectMP = FALSE; /* Initialize */ if(rhvec == NULL) rhvec = lp->rhs; epsvalue = lp->epsdual; current.pivot = -epsvalue; /* Initialize leaving variable threshold; "less than 0" */ current.theta = 0; current.varno = 0; current.isdual = TRUE; current.lp = lp; candidate.isdual = TRUE; candidate.lp = lp; /* Loop over active partial row set */ if(is_action(lp->piv_strategy, PRICE_FORCEFULL)) { k = 1; iy = lp->rows; } else { k = partial_blockStart(lp, TRUE); iy = partial_blockEnd(lp, TRUE); } ninfeas = 0; xinfeas = 0; sinfeas = 0; makePriceLoop(lp, &k, &iy, &iz); iy *= iz; for(; k*iz <= iy; k += iz) { /* Map loop variable to target */ i = k; /* Check if the pivot candidate is on the block-list */ if(lp->rejectpivot[0] > 0) { int kk; for(kk = 1; (kk <= lp->rejectpivot[0]) && (i != lp->rejectpivot[kk]); kk++); if(kk <= lp->rejectpivot[0]) continue; } /* Set local variables - express violation as a negative number */ ii = lp->var_basic[i]; up = lp->upbo[ii]; lo = 0; rh = rhvec[i]; if(rh > up) rh = up - rh; else rh -= lo; up -= lo; /* Analyze relevant constraints ... KE version skips uninteresting alternatives and gives a noticeable speedup */ /* if((rh < -epsvalue*sqrt(lp->matA->rowmax[i])) || */ if((rh < -epsvalue) || ((forceoutEQ == TRUE) && (up < epsvalue))) { /* It causes instability to remove the "TRUE" test */ /* Accumulate stats */ ninfeas++; SETMIN(xinfeas, rh); sinfeas += rh; /* Give a slight preference to fixed variables (mainly equality slacks) */ if(up < epsvalue) { /* Break out immediately if we are directed to force slacks out of the basis */ if(forceoutEQ == TRUE) { current.varno = i; current.pivot = -1; break; } /* Give an extra early boost to equality slack elimination, if specified */ if(forceoutEQ == AUTOMATIC) rh *= 10.0; else /* .. or just the normal. marginal boost */ rh *= 1.0+lp->epspivot; } /* Select leaving variable according to strategy (the most negative/largest violation) */ candidate.pivot = normalizeEdge(lp, i, rh, TRUE); candidate.varno = i; if(findImprovementVar(¤t, &candidate, collectMP, NULL)) break; } } /* Verify infeasibility */ if(updateinfeas) lp->suminfeas = fabs(sinfeas); if((ninfeas > 1) && !verify_stability(lp, FALSE, xinfeas, sinfeas, ninfeas)) { report(lp, IMPORTANT, "rowdual: Check for reduced accuracy and tolerance settings.\n"); current.varno = 0; } /* Produce statistics */ if(lp->spx_trace) { report(lp, NORMAL, "rowdual: Infeasibility sum " RESULTVALUEMASK " in %7d constraints.\n", sinfeas, ninfeas); if(current.varno > 0) { report(lp, DETAILED, "rowdual: rhs[%d] = " RESULTVALUEMASK "\n", current.varno, lp->rhs[current.varno]); } else report(lp, FULL, "rowdual: Optimality - No primal infeasibilities found\n"); } if(xviol != NULL) *xviol = fabs(xinfeas); return(current.varno); } /* rowdual */ STATIC void longdual_testset(lprec *lp, int which, int rownr, REAL *prow, int *nzprow, REAL *drow, int *nzdrow) { int i,j; REAL F = lp->infinite; if(which == 0) { /* Maros Example-1 - raw data */ j = 1; i = lp->rows+j; lp->upbo[i] = 0; lp->is_lower[i] = TRUE; nzprow[j] = i; prow[i] = 2; drow[i] = -1; j = 2; i = lp->rows+j; lp->upbo[i] = 1; lp->is_lower[i] = TRUE; nzprow[j] = i; prow[i] = -2; drow[i] = 2; j = 3; i = lp->rows+j; lp->upbo[i] = 1; lp->is_lower[i] = TRUE; nzprow[j] = i; prow[i] = 1; drow[i] = 5; j = 4; i = lp->rows+j; lp->upbo[i] = 1; lp->is_lower[i] = FALSE; nzprow[j] = i; prow[i] = 3; drow[i] = -6; j = 5; i = lp->rows+j; lp->upbo[i] = 1; lp->is_lower[i] = FALSE; nzprow[j] = i; prow[i] = -4; drow[i] = -2; j = 6; i = lp->rows+j; lp->upbo[i] = 1; lp->is_lower[i] = TRUE; nzprow[j] = i; prow[i] = -1; drow[i] = 0; j = 7; i = lp->rows+j; lp->upbo[i] = 2; lp->is_lower[i] = FALSE; nzprow[j] = i; prow[i] = 1; drow[i] = 0; j = 8; i = lp->rows+j; lp->upbo[i] = 1; lp->is_lower[i] = FALSE; nzprow[j] = i; prow[i] = -2; drow[i] = 0; j = 9; i = lp->rows+j; lp->upbo[i] = 5; lp->is_lower[i] = TRUE; nzprow[j] = i; prow[i] = -1; drow[i] = 4; j = 10; i = lp->rows+j; lp->upbo[i] = F; lp->is_lower[i] = TRUE; nzprow[j] = i; prow[i] = -2; drow[i] = 10; nzprow[0] = i-lp->rows; lp->rhs[rownr] = -11; lp->upbo[lp->var_basic[rownr]] = F; lp->rhs[0] = 1; } else if(which == 1) { /* Maros Example-1 - presorted in correct order */ j = 1; i = lp->rows+j; lp->upbo[i] = 0; lp->is_lower[i] = TRUE; nzprow[j] = i; prow[i] = 2; drow[i] = -1; j = 2; i = lp->rows+j; lp->upbo[i] = 1; lp->is_lower[i] = TRUE; nzprow[j] = i; prow[i] = 1; drow[i] = 5; j = 3; i = lp->rows+j; lp->upbo[i] = 1; lp->is_lower[i] = FALSE; nzprow[j] = i; prow[i] = -4; drow[i] = -2; j = 4; i = lp->rows+j; lp->upbo[i] = 1; lp->is_lower[i] = FALSE; nzprow[j] = i; prow[i] = -2; drow[i] = 0; j = 5; i = lp->rows+j; lp->upbo[i] = 1; lp->is_lower[i] = TRUE; nzprow[j] = i; prow[i] = -1; drow[i] = 0; j = 6; i = lp->rows+j; lp->upbo[i] = 2; lp->is_lower[i] = FALSE; nzprow[j] = i; prow[i] = 1; drow[i] = 0; j = 7; i = lp->rows+j; lp->upbo[i] = 1; lp->is_lower[i] = TRUE; nzprow[j] = i; prow[i] = -2; drow[i] = 2; j = 8; i = lp->rows+j; lp->upbo[i] = 1; lp->is_lower[i] = FALSE; nzprow[j] = i; prow[i] = 3; drow[i] = -6; j = 9; i = lp->rows+j; lp->upbo[i] = 5; lp->is_lower[i] = TRUE; nzprow[j] = i; prow[i] = -1; drow[i] = 4; j = 10; i = lp->rows+j; lp->upbo[i] = F; lp->is_lower[i] = TRUE; nzprow[j] = i; prow[i] = -2; drow[i] = 10; nzprow[0] = i-lp->rows; lp->rhs[rownr] = -11; lp->upbo[lp->var_basic[rownr]] = F; lp->rhs[0] = 1; } else if(which == 10) { /* Maros Example-2 - raw data */ j = 1; i = lp->rows+j; lp->upbo[i] = 5; lp->is_lower[i] = TRUE; nzprow[j] = i; prow[i] = -2; drow[i] = 2; j = 2; i = lp->rows+j; lp->upbo[i] = 1; lp->is_lower[i] = TRUE; nzprow[j] = i; prow[i] = 3; drow[i] = 3; j = 3; i = lp->rows+j; lp->upbo[i] = 1; lp->is_lower[i] = FALSE; nzprow[j] = i; prow[i] = -2; drow[i] = 0; j = 4; i = lp->rows+j; lp->upbo[i] = 2; lp->is_lower[i] = FALSE; nzprow[j] = i; prow[i] = -1; drow[i] = -2; j = 5; i = lp->rows+j; lp->upbo[i] = 2; lp->is_lower[i] = TRUE; nzprow[j] = i; prow[i] = 1; drow[i] = 0; j = 6; i = lp->rows+j; lp->upbo[i] = F; lp->is_lower[i] = TRUE; nzprow[j] = i; prow[i] = 3; drow[i] = 9; nzprow[0] = i-lp->rows; lp->rhs[rownr] = 14; lp->upbo[lp->var_basic[rownr]] = 2; lp->rhs[0] = 6; } } /* Find the dual simplex entering non-basic variable */ STATIC int coldual(lprec *lp, int row_nr, REAL *prow, int *nzprow, REAL *drow, int *nzdrow, MYBOOL dualphase1, MYBOOL skipupdate, int *candidatecount, REAL *xviol) { int i, iy, iz, ix, k, nbound; LREAL w, g, quot; REAL viol, p, epspivot = lp->epspivot; #ifdef MachinePrecRoundRHS REAL epsvalue = lp->epsmachine; #else REAL epsvalue = lp->epsvalue; #endif pricerec current, candidate; MYBOOL isbatch = FALSE, /* Requires that lp->longsteps->size > lp->sum */ dolongsteps = (MYBOOL) (lp->longsteps != NULL); /* Initialize */ if(xviol != NULL) *xviol = lp->infinite; if(dolongsteps && !dualphase1) dolongsteps = AUTOMATIC; /* Sets Phase1 = TRUE, Phase2 = AUTOMATIC */ current.theta = lp->infinite; current.pivot = 0; current.varno = 0; current.epspivot = epspivot; current.isdual = TRUE; current.lp = lp; candidate.epspivot = epspivot; candidate.isdual = TRUE; candidate.lp = lp; *candidatecount = 0; /* Compute reduced costs */ if(!skipupdate) { #ifdef UseDualReducedCostUpdate /* Recompute from scratch only at the beginning, otherwise update */ if((lp->current_iter > 0) && (refactRecent(lp) < AUTOMATIC)) compute_reducedcosts(lp, TRUE, row_nr, NULL, TRUE, prow, nzprow, NULL, NULL, MAT_ROUNDDEFAULT); else #endif compute_reducedcosts(lp, TRUE, row_nr, NULL, TRUE, prow, nzprow, drow, nzdrow, MAT_ROUNDDEFAULT); } #if 0 /* Override all above to do in-line testing with fixed test set */ if(lp->rows > 1 && lp->columns > 10) longdual_testset(lp, 10, row_nr, prow, nzprow, drow, nzdrow); #endif /* Compute the current violation of the bounds of the outgoing variable, negative for violation of lower bound, positive for upper bound violation. (Basic variables are always lower-bounded, by lp_solve convention) */ g = 1; viol = lp->rhs[row_nr]; if(viol > 0) { /* Check if the leaving variable is >= its upper bound */ p = lp->upbo[lp->var_basic[row_nr]]; if(p < lp->infinite) { viol -= p; my_roundzero(viol, epsvalue); if(viol > 0) g = -1; } /* Do validation of numerics */ if(g == 1) { if(viol >= lp->infinite) { report(lp, IMPORTANT, "coldual: Large basic solution value %g at iter %.0f indicates numerical instability\n", lp->rhs[row_nr], (double) get_total_iter(lp)); lp->spx_status = NUMFAILURE; return( 0 ); } if(skipupdate) report(lp, DETAILED, "coldual: Inaccurate bound-flip accuracy at iter %.0f\n", (double) get_total_iter(lp)); else report(lp, SEVERE, "coldual: Leaving variable %d does not violate bounds at iter %.0f\n", row_nr, (double) get_total_iter(lp)); return( -1 ); } } /* Update local value of pivot setting */ lp->_piv_rule_ = get_piv_rule(lp); /* Condense list of relevant targets */ p = 0; k = 0; nbound = 0; ix = 1; iy = nzprow[0]; for(ix = 1; ix <= iy; ix++) { i = nzprow[ix]; w = prow[i] * g; /* Change sign if upper bound of the leaving variable is violated */ /* Change sign if the non-basic variable is currently upper-bounded */ /* w *= 2*lp->is_lower[i] - 1; */ /* fails on AIX!!! */ w = my_chsign(!lp->is_lower[i], w); /* Check if the candidate is worth using for anything */ if(w < -epsvalue) { /* Tally bounded variables */ if(lp->upbo[i] < lp->infinite) nbound++; /* Update the nz-index */ k++; nzprow[k] = i; SETMAX(p, -w); } #ifdef Paranoia else { if(lp->spx_trace) { report(lp, FULL, "coldual: Candidate variable prow[%d] rejected with %g too small\n", i, w); } } #endif } nzprow[0] = k; if(xviol != NULL) *xviol = p; #ifdef UseRelativePivot_Dual /* epspivot *= sqrt(lp->matA->dynrange) / lp->matA->infnorm; */ epspivot /= MAX(1, sqrt(lp->matA->rowmax[row_nr])); #endif current.epspivot = epspivot; candidate.epspivot = epspivot; /* Initialize the long-step structures if indicated */ if(dolongsteps) { if((nzprow[0] <= 1) || (nbound == 0)) { /* Don't bother */ dolongsteps = FALSE; lp->longsteps->indexSet[0] = 0; } else { multi_restart(lp->longsteps); multi_valueInit(lp->longsteps, g*viol, lp->rhs[0]); } } /* Loop over all entering column candidates */ ix = 1; iy = nzprow[0]; makePriceLoop(lp, &ix, &iy, &iz); iy *= iz; for(; ix*iz <= iy; ix += iz) { i = nzprow[ix]; /* Compute the dual ratio (prow = w and drow = cbar in Chvatal's "nomenclatura") */ w = prow[i] * g; /* Change sign if upper bound of the leaving variable is violated */ quot = -drow[i] / w; /* Remember this sign-reversal in multi_recompute! */ /* Apply the selected pivot strategy (smallest theta) */ candidate.theta = quot; /* Note that abs() is applied in findSubstitutionVar */ candidate.pivot = w; candidate.varno = i; /* Collect candidates for minor iterations/bound flips */ if(dolongsteps) { if(isbatch && (ix == iy)) isbatch = AUTOMATIC; if(collectMinorVar(&candidate, lp->longsteps, (MYBOOL) (dolongsteps == AUTOMATIC), isbatch) && lp->spx_trace) report(lp, DETAILED, "coldual: Long-dual break point with %d bound-flip variables\n", lp->longsteps->used); if(lp->spx_status == FATHOMED) return( 0 ); } /* We have a candidate for entering the basis; check if it is better than the incumbent */ else if(findSubstitutionVar(¤t, &candidate, candidatecount)) break; } /* Set entering variable and long-step bound swap variables */ if(dolongsteps) { *candidatecount = lp->longsteps->used; i = multi_enteringvar(lp->longsteps, NULL, 3); } else i = current.varno; if(lp->spx_trace) report(lp, NORMAL, "coldual: Entering column %d, reduced cost %g, pivot value %g, bound swaps %d\n", i, drow[i], prow[i], multi_used(lp->longsteps)); return( i ); } /* coldual */ INLINE REAL normalizeEdge(lprec *lp, int item, REAL edge, MYBOOL isdual) { #if 1 /* Don't use the pricer "close to home", since this can possibly worsen the final feasibility picture (mainly a Devex issue?) */ if(fabs(edge) > lp->epssolution) #endif edge /= getPricer(lp, item, isdual); if((lp->piv_strategy & PRICE_RANDOMIZE) != 0) edge *= (1.0-PRICER_RANDFACT) + PRICER_RANDFACT*rand_uniform(lp, 1.0); return( edge ); } /* Support routines for block detection and partial pricing */ STATIC int partial_findBlocks(lprec *lp, MYBOOL autodefine, MYBOOL isrow) { int i, jj, n, nb, ne, items; REAL hold, biggest, *sum = NULL; MATrec *mat = lp->matA; partialrec *blockdata; if(!mat_validate(mat)) return( 1 ); blockdata = IF(isrow, lp->rowblocks, lp->colblocks); items = IF(isrow, lp->rows, lp->columns); allocREAL(lp, &sum, items+1, FALSE); /* Loop over items and compute the average column index for each */ sum[0] = 0; for(i = 1; i <= items; i++) { n = 0; if(isrow) { nb = mat->row_end[i-1]; ne = mat->row_end[i]; } else { nb = mat->col_end[i-1]; ne = mat->col_end[i]; } n = ne-nb; sum[i] = 0; if(n > 0) { if(isrow) for(jj = nb; jj < ne; jj++) sum[i] += ROW_MAT_COLNR(jj); else for(jj = nb; jj < ne; jj++) sum[i] += COL_MAT_ROWNR(jj); sum[i] /= n; } else sum[i] = sum[i-1]; } /* Loop over items again, find largest difference and make monotone */ hold = 0; biggest = 0; for(i = 2; i <= items; i++) { hold = sum[i] - sum[i-1]; if(hold > 0) { if(hold > biggest) biggest = hold; } else hold = 0; sum[i-1] = hold; } /* Loop over items again and find differences exceeding threshold; the discriminatory power of this routine depends strongly on the magnitude of the scaling factor - from empirical evidence > 0.9 */ biggest = MAX(1, 0.9*biggest); n = 0; nb = 0; ne = 0; for(i = 1; i < items; i++) if(sum[i] > biggest) { ne += i-nb; /* Compute sum of index gaps between maxima */ nb = i; n++; /* Increment count */ } /* Clean up */ FREE(sum); /* Require that the maxima are spread "nicely" across the columns, otherwise return that there is only one monolithic block. (This is probably an area for improvement in the logic!) */ if(n > 0) { ne /= n; /* Average index gap between maxima */ i = IF(isrow, lp->columns, lp->rows); nb = i / ne; /* Another estimated block count */ if(abs(nb - n) > 2) /* Probably Ok to require equality (nb==n)*/ n = 1; else if(autodefine) /* Generate row/column break-indeces for partial pricing */ set_partialprice(lp, nb, NULL, isrow); } else n = 1; return( n ); } STATIC int partial_blockStart(lprec *lp, MYBOOL isrow) { partialrec *blockdata; blockdata = IF(isrow, lp->rowblocks, lp->colblocks); if(blockdata == NULL) return( 1 ); else { if((blockdata->blocknow < 1) || (blockdata->blocknow > blockdata->blockcount)) blockdata->blocknow = 1; return( blockdata->blockend[blockdata->blocknow-1] ); } } STATIC int partial_blockEnd(lprec *lp, MYBOOL isrow) { partialrec *blockdata; blockdata = IF(isrow, lp->rowblocks, lp->colblocks); if(blockdata == NULL) return( IF(isrow, lp->rows, lp->sum) ); else { if((blockdata->blocknow < 1) || (blockdata->blocknow > blockdata->blockcount)) blockdata->blocknow = 1; return( blockdata->blockend[blockdata->blocknow]-1 ); } } STATIC int partial_blockNextPos(lprec *lp, int block, MYBOOL isrow) { partialrec *blockdata; blockdata = IF(isrow, lp->rowblocks, lp->colblocks); #ifdef Paranoia if((blockdata == NULL) || (block <= 1) || (block > blockdata->blockcount)) { report(lp, SEVERE, "partial_blockNextPos: Invalid block %d specified.\n", block); return( -1 ); } #endif block--; if(blockdata->blockpos[block] == blockdata->blockend[block+1]) blockdata->blockpos[block] = blockdata->blockend[block]; else blockdata->blockpos[block]++; return( blockdata->blockpos[block] ); } STATIC MYBOOL partial_blockStep(lprec *lp, MYBOOL isrow) { partialrec *blockdata; blockdata = IF(isrow, lp->rowblocks, lp->colblocks); if(blockdata == NULL) return( FALSE ); else if(blockdata->blocknow < blockdata->blockcount) { blockdata->blocknow++; return( TRUE); } else { blockdata->blocknow = 1; return( TRUE ); } } STATIC MYBOOL partial_isVarActive(lprec *lp, int varno, MYBOOL isrow) { partialrec *blockdata; blockdata = IF(isrow, lp->rowblocks, lp->colblocks); if(blockdata == NULL) return( TRUE ); else { return( (MYBOOL) ((varno >= blockdata->blockend[blockdata->blocknow-1]) && (varno < blockdata->blockend[blockdata->blocknow])) ); } } /* Multiple pricing routines */ STATIC multirec *multi_create(lprec *lp, MYBOOL truncinf) { multirec *multi; multi = (multirec *) calloc(1, sizeof(*multi)); if(multi != NULL) { multi->active = 1; multi->lp = lp; multi->epszero = lp->epsprimal; multi->truncinf = truncinf; } return(multi); } STATIC void multi_free(multirec **multi) { if((multi == NULL) || (*multi == NULL)) return; FREE((*multi)->items); FREE((*multi)->valueList); FREE((*multi)->indexSet); FREE((*multi)->freeList); FREE((*multi)->sortedList); FREE(*multi); } STATIC MYBOOL multi_mustupdate(multirec *multi) { return( (MYBOOL) ((multi != NULL) && (multi->used < multi->limit)) ); } STATIC MYBOOL multi_resize(multirec *multi, int blocksize, int blockdiv, MYBOOL doVlist, MYBOOL doIset) { MYBOOL ok = TRUE; if((blocksize > 1) && (blockdiv > 0)) { int oldsize = multi->size; multi->size = blocksize; if(blockdiv > 1) multi->limit += (multi->size-oldsize) / blockdiv; multi->items = (pricerec *) realloc(multi->items, (multi->size+1)*sizeof(*(multi->items))); multi->sortedList = (UNIONTYPE QSORTrec *) realloc(multi->sortedList, (multi->size+1)*sizeof(*(multi->sortedList))); ok = (multi->items != NULL) && (multi->sortedList != NULL) && allocINT(multi->lp, &(multi->freeList), multi->size+1, AUTOMATIC); if(ok) { int i, n; if(oldsize == 0) i = 0; else i = multi->freeList[0]; multi->freeList[0] = i + (multi->size-oldsize); for(n = multi->size - 1, i++; i <= multi->freeList[0]; i++, n--) multi->freeList[i] = n; } if(doVlist) ok &= allocREAL(multi->lp, &(multi->valueList), multi->size+1, AUTOMATIC); if(doIset) { ok &= allocINT(multi->lp, &(multi->indexSet), multi->size+1, AUTOMATIC); if(ok && (oldsize == 0)) multi->indexSet[0] = 0; } if(!ok) goto Undo; } else { Undo: multi->size = 0; FREE(multi->items); FREE(multi->valueList); FREE(multi->indexSet); FREE(multi->freeList); FREE(multi->sortedList); } multi->active = 1; return( ok ); } STATIC int multi_size(multirec *multi) { if(multi == NULL) return( 0 ); else return( multi->size ); } STATIC int multi_used(multirec *multi) { if(multi == NULL) return( 0 ); else return( multi->used ); } STATIC int multi_restart(multirec *multi) { int i, n = multi->used; multi->used = 0; multi->sorted = FALSE; multi->dirty = FALSE; if(multi->freeList != NULL) { for(i = 1; i <= multi->size; i++) multi->freeList[i] = multi->size - i; multi->freeList[0] = multi->size; } #if 0 if(multi->indexSet != NULL) multi->indexSet[0] = 0; #endif return( n ); } STATIC void multi_valueInit(multirec *multi, REAL step_base, REAL obj_base) { multi->step_base = multi->step_last = step_base; multi->obj_base = multi->obj_last = obj_base; #ifdef Paranoia if(step_base > 0) report(multi->lp, SEVERE, "multi_valueInit: Positive constraint violation %g provided at iteration %6.0f\n", step_base, (double) get_total_iter(multi->lp)); #endif } STATIC REAL *multi_valueList(multirec *multi) { return(multi->valueList); } STATIC int *multi_indexSet(multirec *multi, MYBOOL regenerate) { if(regenerate) multi_populateSet(multi, NULL, -1); return(multi->indexSet); } STATIC int multi_getvar(multirec *multi, int item) { #ifdef Paranoia if((item < 1) || (item >= multi->size)) return(-1); #endif return( ((pricerec *) &(multi->sortedList[item].pvoidreal.ptr))->varno ); } STATIC MYBOOL multi_recompute(multirec *multi, int index, MYBOOL isphase2, MYBOOL fullupdate) { int i, n; REAL lB, uB, Alpha, this_theta, prev_theta; lprec *lp = multi->lp; pricerec *thisprice; /* Define target update window */ if(multi->dirty) { index = 0; n = multi->used - 1; } else if(fullupdate) n = multi->used - 1; else n = index; /* Initialize accumulators from the specified update index */ if(index == 0) { multi->maxpivot = 0; multi->maxbound = 0; multi->step_last = multi->step_base; multi->obj_last = multi->obj_base; thisprice = NULL; this_theta = 0; } else { multi->obj_last = multi->valueList[index-1]; multi->step_last = multi->sortedList[index-1].pvoidreal.realval; thisprice = (pricerec *) (multi->sortedList[index-1].pvoidreal.ptr); this_theta = thisprice->theta; } /* Update step lengths and objective values */ while((index <= n) && (multi->step_last < multi->epszero)) { /* Update parameters for this loop */ prev_theta = this_theta; thisprice = (pricerec *) (multi->sortedList[index].pvoidreal.ptr); this_theta = thisprice->theta; Alpha = fabs(thisprice->pivot); uB = lp->upbo[thisprice->varno]; lB = 0; SETMAX(multi->maxpivot, Alpha); SETMAX(multi->maxbound, uB); /* Do the value updates */ if(isphase2) { multi->obj_last += (this_theta - prev_theta) * multi->step_last; /* Sign-readjusted from coldual()/Maros */ if(uB >= lp->infinite) multi->step_last = lp->infinite; else multi->step_last += Alpha*(uB-lB); } else { multi->obj_last += (this_theta - prev_theta) * multi->step_last; /* Sign-readjusted from coldual()/Maros */ multi->step_last += Alpha; } /* Store updated values at the indexed locations */ multi->sortedList[index].pvoidreal.realval = multi->step_last; multi->valueList[index] = multi->obj_last; #ifdef Paranoia if(lp->spx_trace && (multi->step_last > lp->infinite)) report(lp, SEVERE, "multi_recompute: A very large step-size %g was generated at iteration %6.0f\n", multi->step_last, (double) get_total_iter(lp)); #endif index++; } /* Discard candidates entered earlier that now make the OF worsen, and make sure that the released positions are added to the free list. */ n = index; while(n < multi->used) { i = ++multi->freeList[0]; multi->freeList[i] = (int) (((pricerec *) multi->sortedList[n].pvoidreal.ptr) - multi->items); n++; } multi->used = index; if(multi->sorted && (index == 1)) multi->sorted = FALSE; multi->dirty = FALSE; /* Return TRUE if the step is now positive */ return( (MYBOOL) (multi->step_last >= multi->epszero) ); } STATIC MYBOOL multi_truncatingvar(multirec *multi, int varnr) { return( multi->truncinf && is_infinite(multi->lp, multi->lp->upbo[varnr]) ); } STATIC MYBOOL multi_removevar(multirec *multi, int varnr) { int i = 1; int *coltarget = multi->indexSet; if(coltarget == NULL) return( FALSE ); while((i <= multi->used) && (coltarget[i] != varnr)) i++; if(i > multi->used) return( FALSE ); for(; i < multi->used; i++) coltarget[i] = coltarget[i+1]; coltarget[0]--; multi->used--; multi->dirty = TRUE; return( TRUE ); } STATIC int multi_enteringvar(multirec *multi, pricerec *current, int priority) { lprec *lp = multi->lp; int i = 0, bestindex, colnr; REAL bound, score, bestscore = -lp->infinite; REAL b1, b2, b3; pricerec *candidate, *bestcand; /* Check that we have a candidate */ multi->active = bestindex = 0; if((multi == NULL) || (multi->used == 0)) return( bestindex ); /* Check for pruning possibility of the B&B tree */ if(multi->objcheck && (lp->solutioncount > 0) && bb_better(lp, OF_WORKING | OF_PROJECTED, OF_TEST_WE)) { lp->spx_status = FATHOMED; return( bestindex ); } /* Check the trivial case */ if(multi->used == 1) { bestcand = (pricerec *) (multi->sortedList[bestindex].pvoidreal.ptr); goto Finish; } /* Set priority weights */ Redo: switch(priority) { case 0: b1 = 0.0, b2 = 0.0, b3 = 1.0; /* Only OF */ bestindex = multi->used - 2; break; case 1: b1 = 0.2, b2 = 0.3, b3 = 0.5; break; /* Emphasize OF */ case 2: b1 = 0.3, b2 = 0.5, b3 = 0.2; break; /* Emphasize bound */ case 3: b1 = 0.6, b2 = 0.2, b3 = 0.2; break; /* Emphasize pivot */ case 4: b1 = 1.0, b2 = 0.0, b3 = 0.0; break; /* Only pivot */ default: b1 = 0.4, b2 = 0.2, b3 = 0.4; /* Balanced default */ } bestcand = (pricerec *) (multi->sortedList[bestindex].pvoidreal.ptr); /* Loop over all candidates to get the best entering candidate; start at the end to try to maximize the chain length */ for(i = multi->used - 1; i >= 0; i--) { candidate = (pricerec *) (multi->sortedList[i].pvoidreal.ptr); colnr = candidate->varno; bound = lp->upbo[colnr]; score = fabs(candidate->pivot) / multi->maxpivot; score = pow(1.0 + score , b1) * pow(1.0 + log(bound / multi->maxbound + 1), b2) * pow(1.0 + (REAL) i / multi->used , b3); if(score > bestscore) { bestscore = score; bestindex = i; bestcand = candidate; } } /* Do pivot protection */ if((priority < 4) && (fabs(bestcand->pivot) < lp->epssolution)) { bestindex = 0; priority++; goto Redo; } Finish: /* Make sure we shrink the list and update */ multi->active = colnr = bestcand->varno; if(bestindex < multi->used - 1) { #if 0 /* if(lp->upbo[colnr] >= lp->infinite) */ QS_swap(multi->sortedList, bestindex, multi->used-1); multi_recompute(multi, bestindex, (bestcand->isdual == AUTOMATIC), TRUE); #else multi->used = i + 1; #endif } multi_populateSet(multi, NULL, multi->active); /* Compute the entering theta and update parameters */ score = (multi->used == 1 ? multi->step_base : multi->sortedList[multi->used-2].pvoidreal.realval); score /= bestcand->pivot; score = my_chsign(!lp->is_lower[multi->active], score); if(lp->spx_trace && (fabs(score) > 1/lp->epsprimal)) report(lp, IMPORTANT, "multi_enteringvar: A very large Theta %g was generated (pivot %g)\n", score, bestcand->pivot); multi->step_base = score; if(current != NULL) *current = *bestcand; return( multi->active ); } STATIC REAL multi_enteringtheta(multirec *multi) { return( multi->step_base ); } STATIC int multi_populateSet(multirec *multi, int **list, int excludenr) { int n = 0; if(list == NULL) list = &(multi->indexSet); if((multi->used > 0) && ((*list != NULL) || allocINT(multi->lp, list, multi->size+1, FALSE))) { int i, colnr; for(i = 0; i < multi->used; i++) { colnr = ((pricerec *) (multi->sortedList[i].pvoidreal.ptr))->varno; if((colnr != excludenr) && /* Prevent an unbounded variable from "bound-flip"; this could actually indicate that we should let the entering variable be bound-swapped (in the case that it is bounded), but we disregard this possibility here, since it brings with it issues of pivot size, etc. */ ((excludenr > 0) && (multi->lp->upbo[colnr] < multi->lp->infinite))) { n++; (*list)[n] = colnr; } } (*list)[0] = n; } return( n ); }