A tile whose givens fail (no acceptable givens, settle past policy) is redrawn with a fresh salt inside the attempt instead of restarting the chunk; Stats.wave_retries counts them; the field runner attributes chunk failure lines. Measurement, idle G=60 scatter seed a, all four steps: 34.4 s (baseline) -> 3.07 s wall, one attempt, 0 wasted.
- commit
- f111d981ad249d1055688526b9f3fba86e0e4f3b
- parent
- 10e678de9447b24e25d2265eb6f101a979b025cf
- author
- dami <gazey55@gmail.com>
| M | engine/ | 11 | +++- |
| M | engine/ | 93 | ++++++++++++++++++++++++++---- |
@@ -104,7 +104,16 @@ func SubprocessRunner(exe string, timeout time.Duration) Runner {
104104 cmd.Stdout = &out105105 cmd.Stderr = &errb106106 cmd.Env = os.Environ()107- if err := cmd.Run(); err != nil {107+ runErr := cmd.Run()108+ if Verbose {109+ // the chunk's own failure lines, attributed110+ for _, line := range strings.Split(errb.String(), "\n") {111+ if strings.Contains(line, "attempt") || strings.Contains(line, "released") {112+ fmt.Fprintf(os.Stderr, "field: chunk %d: %s\n", p.Chunk, line)113+ }114+ }115+ }116+ if err := runErr; err != nil {108117 msg := strings.TrimSpace(errb.String())109118 if i := strings.LastIndex(msg, "\n"); i >= 0 {110119 msg = msg[i+1:]@@ -172,13 +172,14 @@ type Ladder struct {
172172 }173173 174174 type Stats struct {175- Ms int64 `json:"ms"`176- Attempts int `json:"attempts"`177- Refills int `json:"refills"`178- Backjumps int `json:"backjumps"`179- Regivens int `json:"regivens"`180- Releases int `json:"releases"` // repairs that took back neighbours' seam clues181- Waves int `json:"waves"`175+ Ms int64 `json:"ms"`176+ Attempts int `json:"attempts"`177+ Refills int `json:"refills"`178+ Backjumps int `json:"backjumps"`179+ Regivens int `json:"regivens"`180+ Releases int `json:"releases"` // repairs that took back neighbours' seam clues181+ Waves int `json:"waves"`182+ WaveRetries int `json:"wave_retries"` // tiles whose givens needed a fresh draw within the attempt182183 // phase timings of the successful attempt (wall clock) and of the183184 // attempts wasted before it184185 FillMs int64 `json:"fill_ms"`@@ -596,7 +597,20 @@ func generateAttempt(ctx context.Context, p Params, L *pack.Layout, attempt int)
596597 go func(k, i int) {597598 defer wg.Done()598599 defer func() { <-sem }()599- results[k], errs[k] = m.buildGivens(ctx, tag, i)600+ // a tile whose givens fail is redrawn with a fresh salt601+ // before the whole attempt is given up: the other tiles602+ // of the attempt are fine, only this draw was not603+ for wr := 0; wr < waveRetries; wr++ {604+ salt := tag605+ if wr > 0 {606+ salt = fmt.Sprintf("%s|w%d", tag, wr)607+ m.bump(&m.stats.WaveRetries)608+ }609+ results[k], errs[k] = m.buildGivens(ctx, salt, i)610+ if errs[k] == nil || ctx.Err() != nil {611+ break612+ }613+ }600614 }(k, i)601615 }602616 wg.Wait()@@ -630,7 +644,19 @@ func generateAttempt(ctx context.Context, p Params, L *pack.Layout, attempt int)
630644 for _, id := range tiles[bad].protect {631645 delete(m.used, id)632646 }633- tw, err := m.makeGivens(ctx, tag, bad, 100+round)647+ var tw *tileWork648+ var err error649+ for wr := 0; wr < waveRetries; wr++ {650+ salt := tag651+ if wr > 0 {652+ salt = fmt.Sprintf("%s|r%d", tag, wr)653+ m.stats.WaveRetries++654+ }655+ tw, err = m.makeGivens(ctx, salt, bad, 100+round)656+ if err == nil || ctx.Err() != nil {657+ break658+ }659+ }634660 if err != nil {635661 return fmt.Errorf("repair tile %d: %w", m.geom[bad].pubID, err)636662 }@@ -640,6 +666,7 @@ func generateAttempt(ctx context.Context, p Params, L *pack.Layout, attempt int)
640666 return nil641667 }642668 released := make([]int, total)669+ badRounds := make([]int, total)643670 // repair rebuilds bad's givens; when that fails because the neighbours'644671 // clues left nothing to pin on, it releases those clues, rebuilds bad645672 // and then the neighbours that lost a clue, at most twice per tile@@ -709,10 +736,24 @@ func generateAttempt(ctx context.Context, p Params, L *pack.Layout, attempt int)
709736 if round >= 3*total {710737 return nil, fmt.Errorf("coupled system has %d solutions after %d repairs", count, round)711738 }712- bad := m.tileWithoutWitness(tiles)739+ bad, nb := m.witnessGap(tiles)713740 if bad < 0 {714741 return nil, fmt.Errorf("coupled system has %d solutions but every tile has a witness (bug)", count)715742 }743+ // the second time the same tile lacks a witness, its ambiguity is744+ // correlated with the neighbour's (both alternatives agree on the745+ // shared cells): rebuild that neighbour's givens first, so one of746+ // the two can pin the other747+ badRounds[bad]++748+ if badRounds[bad] >= 2 && nb >= 0 && !m.geom[nb].fixed {749+ if Verbose {750+ fmt.Fprintf(os.Stderr, "gen: tile %d lacks a witness again; regiven its neighbour %d first\n", m.geom[bad].pubID, m.geom[nb].pubID)751+ }752+ if err := repair(nb, round); err != nil {753+ return nil, err754+ }755+ badRounds[bad] = 0756+ }716757 if err := repair(bad, round); err != nil {717758 return nil, err718759 }@@ -1638,6 +1679,11 @@ func (m *mosaic) removeClues(ctx context.Context, rng *rand.Rand, ti int, S [][]
16381679 // which no redraw can change.16391680 var errFixedNoFit = errors.New("admits no completion against the fixed context")16401681 1682+// waveRetries is how many fresh draws a tile's givens get (in a wave, or1683+// in a repair) before the attempt is given up; an attempt restart redraws1684+// every tile, which is the expensive way to fix one.1685+const waveRetries = 31686+16411687 // attemptHook, when set (tests), sees the mosaic after every assertion16421688 // held and before the output is assembled.16431689 var attemptHook func(m *mosaic, tiles []*tileWork)@@ -1682,6 +1728,14 @@ func (m *mosaic) releaseClues(tiles []*tileWork, bad int) []int {
16821728 // by any neighbour that is unanimous on a shared cell where the alternative16831729 // differs from the solution. Returns -1 if every tile has a witness.16841730 func (m *mosaic) tileWithoutWitness(tiles []*tileWork) int {1731+ bad, _ := m.witnessGap(tiles)1732+ return bad1733+}1734+1735+// witnessGap is tileWithoutWitness that also names the neighbour sharing1736+// the most cells where the unwitnessed alternative differs (the one whose1737+// own ambiguity lets it through), or -1.1738+func (m *mosaic) witnessGap(tiles []*tileWork) (bad, neighbour int) {16851739 unanimous := func(t *tileWork, gx, gy int) bool {16861740 g := m.geom[t.id]16871741 i := (gy-g.sh.Y0)*g.W + (gx - g.sh.X0)@@ -1715,9 +1769,24 @@ func (m *mosaic) tileWithoutWitness(tiles []*tileWork) int {
17151769 }17161770 }17171771 if !witness {1718- return ti1772+ shares := map[int]int{}1773+ for _, i := range d {1774+ gx, gy := g.sh.X0+i%g.W, g.sh.Y0+i/g.W1775+ for _, ui := range m.owners[m.global.Idx(gx, gy)] {1776+ if ui != ti {1777+ shares[ui]++1778+ }1779+ }1780+ }1781+ nb, best := -1, 01782+ for ui, c := range shares {1783+ if c > best || (c == best && ui < nb) {1784+ nb, best = ui, c1785+ }1786+ }1787+ return ti, nb17191788 }17201789 }17211790 }1722- return -11791+ return -1, -117231792 }