Skip to content

Commit e9d9386

Browse files
committed
simplex: concrete sort.Interface for the dual ratio test (drop sort.Slice reflection)
The Clp long-step / DSE ratio test sorts entering candidates by ascending ratio every dual pivot. sort.Slice does this via reflection (a per-element reflect-based swap); on 020 it was 740ms / 6.8% of the solve. Replaced with a concrete sort.Interface (dualCandByRatio / dual2CandByRatio) — the same pdqsort, same comparator, so the order is byte-identical and results are unchanged: 020 774 nodes / 182619 pivots / same objective, 018/021 identical. Interleaved timing (controls for thermal drift): ~11.55s vs ~11.79s, a consistent ~2% on 020 (new faster in all 5 paired runs). Numerics-neutral — no tree re-roll. All tests + PuLP green.
1 parent 9e30107 commit e9d9386

2 files changed

Lines changed: 19 additions & 2 deletions

File tree

simplex/dual2.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ type dual2Cand struct {
3939
ratio float64
4040
}
4141

42+
// dual2CandByRatio: concrete sort.Interface for the DSE ratio test (skips
43+
// sort.Slice reflection; identical pdqsort order).
44+
type dual2CandByRatio []dual2Cand
45+
46+
func (s dual2CandByRatio) Len() int { return len(s) }
47+
func (s dual2CandByRatio) Less(i, j int) bool { return s[i].ratio < s[j].ratio }
48+
func (s dual2CandByRatio) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
49+
4250
// dual2Run is a complete bounded dual simplex modeled on ClpSimplexDual:
4351
// DSE pricing, Harris ratio test with bound flips, run to optimality.
4452
func (lp *LP) dual2Run(st *State) dual2Result {
@@ -238,7 +246,7 @@ func (lp *LP) dual2Run(st *State) dual2Result {
238246
cands = append(cands, dual2Cand{j, dir, at, rd, rd / at})
239247
}
240248
}
241-
sort.Slice(cands, func(x, y int) bool { return cands[x].ratio < cands[y].ratio })
249+
sort.Sort(dual2CandByRatio(cands))
242250

243251
// bound-flipping walk, then a Harris window: the largest pivot
244252
// entry inside the first blocker's relaxed ratio wins

simplex/simplex.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,7 @@ func (lp *LP) dualRun(st *State) {
689689
}
690690
}
691691
ws.cands = cands
692-
sort.Slice(cands, func(a, b int) bool { return cands[a].ratio < cands[b].ratio })
692+
sort.Sort(dualCandByRatio(cands))
693693

694694
// dual long step (Clp "dual with flips"): boxed candidates that
695695
// can't absorb the violation get flipped; the overshooter pivots
@@ -804,6 +804,15 @@ type dualCand struct {
804804
ratio float64
805805
}
806806

807+
// dualCandByRatio sorts entering candidates by ascending ratio. Concrete
808+
// sort.Interface (not sort.Slice) so the per-pivot ratio-test sort skips
809+
// reflection — same pdqsort, same comparator, byte-identical order.
810+
type dualCandByRatio []dualCand
811+
812+
func (s dualCandByRatio) Len() int { return len(s) }
813+
func (s dualCandByRatio) Less(i, j int) bool { return s[i].ratio < s[j].ratio }
814+
func (s dualCandByRatio) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
815+
807816
// enterDirs lists the directions a nonbasic variable may enter the basis in.
808817
func enterDirs(s varStat) []float64 {
809818
switch s {

0 commit comments

Comments
 (0)