import math
from collections import defaultdict
import json
# ============================================================
# ALL DATA
# ============================================================
quarters = ['Q1_26','Q2_26','Q3_26','Q4_26','Q1_27','Q2_27','Q3_27','Q4_27']
qlabels = ["Q1'26","Q2'26","Q3'26","Q4'26","Q1'27","Q2'27","Q3'27","Q4'27"]
nodes = [1, 2, 3]
fabs = [1, 2, 3]
loading = {
(1,'Q1_26'):12000,(1,'Q2_26'):10000,(1,'Q3_26'):8500,(1,'Q4_26'):7500,
(1,'Q1_27'):6000,(1,'Q2_27'):5000,(1,'Q3_27'):4000,(1,'Q4_27'):2000,
(2,'Q1_26'):5000,(2,'Q2_26'):5200,(2,'Q3_26'):5400,(2,'Q4_26'):5600,
(2,'Q1_27'):6000,(2,'Q2_27'):6500,(2,'Q3_27'):7000,(2,'Q4_27'):7500,
(3,'Q1_26'):3000,(3,'Q2_26'):4500,(3,'Q3_26'):7000,(3,'Q4_26'):8000,
(3,'Q1_27'):9000,(3,'Q2_27'):11000,(3,'Q3_27'):13000,(3,'Q4_27'):16000,
}
nsteps = {1:11, 2:15, 3:17}
recipe = {
(1,1):('D',14,'D+',12),(1,2):('F',25,'F+',21),(1,3):('F',27,'F+',23),
(1,4):('A',20,'A+',16),(1,5):('F',12,'F+',9),(1,6):('D',27,'D+',21),
(1,7):('D',17,'D+',13),(1,8):('A',18,'A+',16),(1,9):('A',16,'A+',13),
(1,10):('D',14,'D+',11),(1,11):('F',18,'F+',16),
(2,1):('F',19,'F+',16),(2,2):('B',20,'B+',18),(2,3):('E',10,'E+',7),
(2,4):('B',25,'B+',19),(2,5):('B',15,'B+',11),(2,6):('F',16,'F+',14),
(2,7):('F',17,'F+',15),(2,8):('B',22,'B+',16),(2,9):('E',7,'E+',6),
(2,10):('E',9,'E+',7),(2,11):('E',20,'E+',19),(2,12):('F',21,'F+',18),
(2,13):('E',12,'E+',9),(2,14):('E',15,'E+',12),(2,15):('E',13,'E+',10),
(3,1):('C',21,'C+',20),(3,2):('D',9,'D+',7),(3,3):('E',24,'E+',23),
(3,4):('E',15,'E+',11),(3,5):('F',16,'F+',14),(3,6):('D',12,'D+',11),
(3,7):('C',24,'C+',21),(3,8):('C',19,'C+',13),(3,9):('D',15,'D+',13),
(3,10):('D',24,'D+',20),(3,11):('E',17,'E+',15),(3,12):('E',18,'E+',13),
(3,13):('F',20,'F+',18),(3,14):('C',12,'C+',11),(3,15):('D',11,'D+',10),
(3,16):('C',25,'C+',20),(3,17):('F',14,'F+',13),
}
# (utilization, capex_M$, space_m2)
ws = {
'A':(0.78,4.5,6.78),'B':(0.76,6.0,3.96),'C':(0.80,2.2,5.82),
'D':(0.80,4.0,5.61),'E':(0.76,3.5,4.65),'F':(0.80,6.0,3.68),
'A+':(0.84,6.0,6.93),'B+':(0.81,8.0,3.72),'C+':(0.86,3.2,5.75),
'D+':(0.88,5.5,5.74),'E+':(0.84,5.8,4.80),'F+':(0.90,8.0,3.57),
}
init_tools = {
(1,'A'):0,(1,'B'):0,(1,'C'):40,(1,'D'):35,(1,'E'):16,(1,'F'):36,
(2,'A'):50,(2,'B'):25,(2,'C'):0,(2,'D'):50,(2,'E'):40,(2,'F'):90,
(3,'A'):35,(3,'B'):30,(3,'C'):0,(3,'D'):50,(3,'E'):30,(3,'F'):60,
}
for f in fabs:
for w in ['A+','B+','C+','D+','E+','F+']:
init_tools[(f,w)] = 0
fab_space = {1:1500, 2:1300, 3:700}
XFER_COST = 50 # $/wafer/transfer
WKS = 13 # weeks per quarter
MO_COST = 1_000_000 # $1M per tool moveout
MPW = 10080 # minutes per week = 7*24*60
mintech_ws = ['A','B','C','D','E','F']
tor_ws = ['A+','B+','C+','D+','E+','F+']
all_ws_list = mintech_ws + tor_ws
m2t = {'A':'A+','B':'B+','C':'C+','D':'D+','E':'E+','F':'F+'}
def treq(load, rpt, util):
"""Tool requirement (fractional)"""
if load <= 0: return 0.0
return (load * rpt) / (MPW * util)
def treq_ceil(load, rpt, util):
"""Tool requirement (integer, rounded up)"""
return math.ceil(treq(load, rpt, util))
# ============================================================
# ANALYSIS PHASE
# ============================================================
print("="*80)
print("MICRON NUS-ISE BACC 2026 - QUESTION 1")
print("COMPLETE SOLUTION WITH ANALYTICAL OPTIMIZATION")
print("="*80)
# Compute aggregate tool requirements per WS per quarter
print("\n" + "="*80)
print("1. AGGREGATE TOOL REQUIREMENTS (Mintech Only)")
print("="*80)
ws_node_req = {} # (ws, node, quarter) -> fractional tool req
ws_total_req = {} # (ws, quarter) -> fractional total
for w in mintech_ws:
for t in quarters:
total = 0
for i in nodes:
L = loading[(i,t)]
req = 0
for j in range(1, nsteps[i]+1):
mw,mr,tw,tr = recipe[(i,j)]
if mw == w:
req += treq(L, mr, ws[w][0])
ws_node_req[(w,i,t)] = req
total += req
ws_total_req[(w,t)] = total
# Print summary table
print(f"\n{'WS':<3}", end="")
for t in qlabels:
print(f" {t:>7}", end="")
print(f" {'Avail':>6} {'PkGap':>6}")
for w in mintech_ws:
print(f"{w:<3}", end="")
peak = 0
for t in quarters:
r = math.ceil(ws_total_req[(w,t)])
peak = max(peak, r)
print(f" {r:>7}", end="")
avail = sum(init_tools.get((f,w),0) for f in fabs)
gap = max(0, peak - avail)
print(f" {avail:>6} {gap:>6}")
# Also compute TOR equivalent
print(f"\n{'WS':<3}", end="")
for t in qlabels:
print(f" {t:>7}", end="")
print(" (if all TOR)")
for wt in tor_ws:
print(f"{wt:<3}", end="")
for t in quarters:
total = 0
for i in nodes:
L = loading[(i,t)]
for j in range(1, nsteps[i]+1):
mw,mr,tw,tr = recipe[(i,j)]
if tw == wt:
total += treq(L, tr, ws[wt][0])
print(f" {math.ceil(total):>7}", end="")
print()
# ============================================================
# SPACE ANALYSIS
# ============================================================
print("\n" + "="*80)
print("2. INITIAL SPACE ANALYSIS")
print("="*80)
for f in fabs:
used = sum(init_tools.get((f,w),0) * ws[w][2] for w in all_ws_list)
remain = fab_space[f] - used
print(f"\n Fab {f}: {used:.1f} / {fab_space[f]} m² used ({remain:.1f} m² free)")
for w in all_ws_list:
cnt = init_tools.get((f,w),0)
if cnt > 0:
print(f" {w}: {cnt} × {ws[w][2]}m² = {cnt*ws[w][2]:.1f}m²")
# ============================================================
# KEY INSIGHT: Which nodes use which workstations
# ============================================================
print("\n" + "="*80)
print("3. NODE-WORKSTATION MAPPING")
print("="*80)
for i in nodes:
ws_used = defaultdict(list)
for j in range(1, nsteps[i]+1):
mw,mr,tw,tr = recipe[(i,j)]
ws_used[mw].append((j, mr))
print(f"\n Node {i} ({nsteps[i]} steps):")
for w in mintech_ws:
if w in ws_used:
steps_str = ", ".join(f"S{s}({r}min)" for s,r in ws_used[w])
total_rpt = sum(r for _,r in ws_used[w])
print(f" {w}: {steps_str} [total RPT={total_rpt} min]")
# ============================================================
# CRITICAL: C workstation only in Fab 1
# B workstation only in Fab 2 and Fab 3
# This constrains where nodes can run
# ============================================================
print("\n" + "="*80)
print("4. CRITICAL CONSTRAINTS")
print("="*80)
print(" - C tools ONLY in Fab 1 (40 tools) → Node 3 C-steps MUST run in Fab 1")
print(" - B tools ONLY in Fab 2 (25) and Fab 3 (30) → Node 2 B-steps in Fab 2/3")
print(" - A tools ONLY in Fab 2 (50) and Fab 3 (35) → Node 1 A-steps in Fab 2/3")
print(" - Node 3 uses C heavily (steps 1,7,8,14,16) - C is bottleneck")
# ============================================================
# GREEDY OPTIMIZATION ALGORITHM
# ============================================================
print("\n" + "="*80)
print("5. OPTIMIZATION: CONSTRUCTING FEASIBLE SOLUTION")
print("="*80)
# Strategy:
# 1. For each quarter, determine which fabs can process which nodes based on WS availability
# 2. Assign loading to minimize transfers (keep wafers in same fab across steps)
# 3. Purchase tools only when existing capacity insufficient
# 4. Prefer mintech (cheaper) unless space forces TOR (more efficient per m²)
# Track state
tool_count = {} # (f,w,t) -> count
tool_purchases = {} # (f,w,t) -> purchased count
flow = {} # (i,j,f,t) -> wafers/week
transfers = {} # (i,j,f1,f2,t) -> wafers/week transferred
total_capex = 0
total_xfer_cost = 0
# Initialize tool counts
for f in fabs:
for w in all_ws_list:
tool_count[(f,w)] = init_tools.get((f,w), 0)
def get_space_used(f, t_tools):
"""Compute space used in fab f given tool counts dict"""
return sum(t_tools.get((f,w), 0) * ws[w][2] for w in all_ws_list)
def get_space_remaining(f, t_tools):
return fab_space[f] - get_space_used(f, t_tools)
def get_ws_capacity(f, w_type, t_tools):
"""Get capacity in wafers*minutes/week for workstation type in fab"""
cnt = t_tools.get((f, w_type), 0)
return cnt * MPW * ws[w_type][0]
# For each quarter, determine optimal allocation
for t_idx, t in enumerate(quarters):
print(f"\n{'='*60}")
print(f"Quarter: {qlabels[t_idx]}")
print(f"{'='*60}")
# Current tool state
cur_tools = {}
for f in fabs:
for w in all_ws_list:
cur_tools[(f,w)] = tool_count[(f,w)]
# Compute what each node needs on each WS type
# For each (node, mintech_ws), compute total RPT*loading
node_ws_demand = {} # (node, mintech_ws) -> minutes/week needed
for i in nodes:
L = loading[(i,t)]
for w in mintech_ws:
total_min = 0
for j in range(1, nsteps[i]+1):
mw,mr,tw,tr = recipe[(i,j)]
if mw == w:
total_min += L * mr
node_ws_demand[(i,w)] = total_min
# Step 1: Determine fab assignment for each node
# Constraint: Node 3 C-steps must be in Fab 1 (only fab with C tools)
# Constraint: Node 2 B-steps must be in Fab 2 or 3
# Constraint: Node 1 A-steps must be in Fab 2 or 3
# Strategy: Assign each node's loading across fabs proportional to
# available capacity on the BOTTLENECK workstation for that node
# For simplicity and to minimize transfers: try to keep entire node in one fab
# where possible, or split at natural breakpoints
# Node 3: MUST go through Fab 1 for C steps, but D/E/F steps can go elsewhere
# Node 2: MUST go through Fab 2 or 3 for B steps
# Node 1: MUST go through Fab 2 or 3 for A steps
# Approach: Run all of Node 3's C steps in Fab 1,
# check if Fab 1 has enough D/E/F for non-C steps too
# First check C capacity for Node 3
L3 = loading[(3,t)]
c_demand_min = node_ws_demand[(3,'C')] # total C-minutes needed
c_capacity = get_ws_capacity(1, 'C', cur_tools) # Fab 1 C capacity
# Also check if we need C+ tools
c_steps_for_n3 = [(j, recipe[(3,j)][1], recipe[(3,j)][3])
for j in range(1, nsteps[3]+1) if recipe[(3,j)][0] == 'C']
c_mintech_treq = sum(treq(L3, mr, ws['C'][0]) for _, mr, _ in c_steps_for_n3)
c_tor_treq = sum(treq(L3, tr, ws['C+'][0]) for _, _, tr in c_steps_for_n3)
print(f"\n Node 3 C requirement: {c_mintech_treq:.1f} tools (mintech) or "
f"{c_tor_treq:.1f} (TOR)")
print(f" Fab 1 C tools available: {cur_tools.get((1,'C'),0)}")
# Check if we need more C tools
c_available = cur_tools.get((1,'C'), 0)
c_needed = math.ceil(c_mintech_treq)
if c_needed > c_available:
# Need to buy C or C+ tools for Fab 1
c_gap = c_needed - c_available
space_rem = get_space_remaining(1, cur_tools)
# Compare: buy C (cheaper, 2.2M, 5.82m²) vs C+ (3.2M, 5.75m², faster)
# C+ is slightly smaller and faster - better for space-constrained situations
# Check if C fits
c_space_needed = c_gap * ws['C'][2]
cp_equiv = math.ceil(c_tor_treq) - 0 # no existing C+ tools
cp_space_needed = cp_equiv * ws['C+'][2]
# Try mintech C first
if c_space_needed <= space_rem:
cur_tools[(1,'C')] += c_gap
cost = c_gap * ws['C'][1] * 1e6
total_capex += cost
tool_purchases[(1,'C',t)] = tool_purchases.get((1,'C',t),0) + c_gap
print(f" -> Purchase {c_gap} C tools for Fab 1 (${cost/1e6:.1f}M)")
else:
# Try C+ (faster, less tools needed, slightly smaller)
cp_needed = math.ceil(c_tor_treq)
cp_space = cp_needed * ws['C+'][2]
if cp_space <= space_rem:
cur_tools[(1,'C+')] += cp_needed
cost = cp_needed * ws['C+'][1] * 1e6
total_capex += cost
tool_purchases[(1,'C+',t)] = tool_purchases.get((1,'C+',t),0) + cp_needed
print(f" -> Purchase {cp_needed} C+ tools for Fab 1 (${cost/1e6:.1f}M)")
else:
# Buy mix
max_c = int(space_rem / ws['C'][2])
cur_tools[(1,'C')] += max_c
if max_c > 0:
cost = max_c * ws['C'][1] * 1e6
total_capex += cost
tool_purchases[(1,'C',t)] = tool_purchases.get((1,'C',t),0) + max_c
print(f" -> Purchase {max_c} C tools for Fab 1 (${cost/1e6:.1f}M)")
space_rem2 = get_space_remaining(1, cur_tools)
remaining_c_demand = c_mintech_treq - (c_available + max_c) * MPW * ws['C'][0]
if remaining_c_demand > 0:
cp_needed2 = math.ceil(remaining_c_demand / (MPW * ws['C+'][0]))
cp_needed2 = min(cp_needed2, int(space_rem2 / ws['C+'][2]))
if cp_needed2 > 0:
cur_tools[(1,'C+')] += cp_needed2
cost = cp_needed2 * ws['C+'][1] * 1e6
total_capex += cost
tool_purchases[(1,'C+',t)] = cp_needed2
print(f" -> Purchase {cp_needed2} C+ for Fab 1 (${cost/1e6:.1f}M)")
# Now handle all other workstations for all nodes
# For each WS type, compute total demand across all nodes, check against supply
for w in mintech_ws:
total_demand = sum(node_ws_demand[(i,w)] for i in nodes) # minutes/week
total_supply = sum(get_ws_capacity(f, w, cur_tools) for f in fabs)
tor_w = m2t[w]
total_supply_tor = sum(get_ws_capacity(f, tor_w, cur_tools) for f in fabs)
demand_tools = sum(treq(loading[(i,t)], recipe[(i,j)][1], ws[w][0])
for i in nodes for j in range(1, nsteps[i]+1)
if recipe[(i,j)][0] == w)
supply_tools = sum(cur_tools.get((f,w),0) for f in fabs)
supply_tor = sum(cur_tools.get((f,tor_w),0) for f in fabs)
needed = math.ceil(demand_tools)
if needed > supply_tools + supply_tor:
gap = needed - supply_tools - supply_tor
# Find best fab to add tools (most space remaining)
fab_space_rem = [(get_space_remaining(f, cur_tools), f) for f in fabs]
fab_space_rem.sort(reverse=True)
remaining_gap = gap
for space_r, f in fab_space_rem:
if remaining_gap <= 0:
break
# Decide mintech vs TOR
# Compare cost-effectiveness: cost per unit capacity
# Mintech: cost/tool / (MPW * util / avg_rpt)
# Just compare raw CapEx since we want min cost
mintech_can_fit = int(space_r / ws[w][2])
tor_can_fit = int(space_r / ws[tor_w][2])
# Prefer mintech (cheaper) if it fits
add_mintech = min(remaining_gap, mintech_can_fit)
if add_mintech > 0:
cur_tools[(f,w)] = cur_tools.get((f,w),0) + add_mintech
cost = add_mintech * ws[w][1] * 1e6
total_capex += cost
tool_purchases[(f,w,t)] = tool_purchases.get((f,w,t),0) + add_mintech
remaining_gap -= add_mintech
print(f" -> Purchase {add_mintech} {w} for Fab {f} (${cost/1e6:.1f}M)")
if remaining_gap > 0:
space_r2 = get_space_remaining(f, cur_tools)
add_tor = min(remaining_gap, int(space_r2 / ws[tor_w][2]))
if add_tor > 0:
cur_tools[(f,tor_w)] = cur_tools.get((f,tor_w),0) + add_tor
cost = add_tor * ws[tor_w][1] * 1e6
total_capex += cost
tool_purchases[(f,tor_w,t)] = tool_purchases.get((f,tor_w,t),0) + add_tor
remaining_gap -= add_tor
print(f" -> Purchase {add_tor} {tor_w} for Fab {f} (${cost/1e6:.1f}M)")
if remaining_gap > 0:
print(f" WARNING: Cannot fit {remaining_gap} more {w} tools!")
# Step 2: Assign flows
# For each node, assign all loading to the fab(s) that have capacity
# Try to keep entire node in one fab to minimize transfers
for i in nodes:
L = loading[(i,t)]
# Determine which fabs can handle this node
# A fab can handle a node if it has capacity for ALL WS types used by that node
ws_used_by_node = set()
for j in range(1, nsteps[i]+1):
mw,mr,tw,tr = recipe[(i,j)]
ws_used_by_node.add(mw)
# Check capacity per fab
fab_feasible = {}
for f in fabs:
max_load = float('inf')
for w in ws_used_by_node:
# How many wafers/week can this fab handle for this WS?
cap_m = get_ws_capacity(f, w, cur_tools)
cap_t = get_ws_capacity(f, m2t[w], cur_tools)
# Total capacity in minutes, convert to wafers
# Total RPT for this node on this WS
total_rpt = sum(recipe[(i,j)][1] for j in range(1, nsteps[i]+1)
if recipe[(i,j)][0] == w)
if total_rpt > 0:
max_wafers = (cap_m + cap_t) / total_rpt if total_rpt > 0 else float('inf')
else:
max_wafers = float('inf')
max_load = min(max_load, max_wafers)
fab_feasible[f] = max_load
# Distribute loading across fabs proportionally to capacity
total_cap = sum(fab_feasible.values())
if total_cap <= 0:
print(f" ERROR: No capacity for Node {i}!")
for j in range(1, nsteps[i]+1):
for f in fabs:
flow[(i,j,f,t)] = L if f == 1 else 0
continue
# Allocate
alloc = {}
remaining = L
fab_sorted = sorted(fabs, key=lambda f: fab_feasible[f], reverse=True)
for idx, f in enumerate(fab_sorted):
if idx < len(fabs) - 1:
share = min(remaining, int(L * fab_feasible[f] / total_cap))
alloc[f] = share
remaining -= share
else:
alloc[f] = remaining
# Assign all steps to same fab allocation (no transfers within a node)
for j in range(1, nsteps[i]+1):
for f in fabs:
flow[(i,j,f,t)] = alloc.get(f, 0)
nonzero = [(f, alloc[f]) for f in fabs if alloc.get(f,0) > 0]
alloc_str = ", ".join(f"Fab{f}={v}" for f,v in nonzero)
print(f" Node {i} ({L} wfr/wk): {alloc_str}")
# Check if any steps need cross-fab transfer due to WS availability
# E.g., Node 3 C-steps MUST be in Fab 1, but D/E/F steps might be elsewhere
for i in nodes:
L = loading[(i,t)]
for j in range(1, nsteps[i]+1):
mw,mr,tw,tr = recipe[(i,j)]
for f in fabs:
assigned = flow.get((i,j,f,t), 0)
if assigned > 0:
# Check if this fab has the WS
has_mintech = cur_tools.get((f,mw),0) > 0
has_tor = cur_tools.get((f,m2t[mw]),0) > 0
if not has_mintech and not has_tor:
# Need to transfer this to a fab that has the WS
# Find target fab
for f2 in fabs:
if f2 != f and (cur_tools.get((f2,mw),0) > 0 or
cur_tools.get((f2,m2t[mw]),0) > 0):
# Transfer from f to f2
# Move the load for this step
old_val = flow[(i,j,f,t)]
flow[(i,j,f,t)] = 0
flow[(i,j,f2,t)] = flow.get((i,j,f2,t), 0) + old_val
# Record transfer (between previous step and this one)
if j > 1:
transfers[(i,j-1,f,f2,t)] = transfers.get((i,j-1,f,f2,t),0) + old_val
xfer_c = old_val * XFER_COST * WKS
total_xfer_cost += xfer_c
# Also need to transfer back for next step if next step's WS
# is in original fab - handled in next iteration
break
# Update tool counts for next quarter
for f in fabs:
for w in all_ws_list:
tool_count[(f,w)] = cur_tools.get((f,w), 0)
# Record for this quarter
for f in fabs:
for w in all_ws_list:
tool_count[(f,w,t)] = cur_tools.get((f,w), 0)
# Print space usage
print(f"\n Space usage:")
for f in fabs:
used = get_space_used(f, cur_tools)
print(f" Fab {f}: {used:.1f}/{fab_space[f]} m² ({used/fab_space[f]*100:.0f}%)")
# ============================================================
# OUTPUT: COMPLETE RESULTS
# ============================================================
print("\n\n" + "="*80)
print("6. PART A RESULTS SUMMARY")
print("="*80)
print(f"\n Total CapEx: ${total_capex:>15,.0f} ({total_capex/1e6:.1f}M)")
print(f" Total Transfer: ${total_xfer_cost:>15,.0f} ({total_xfer_cost/1e6:.1f}M)")
print(f" GRAND TOTAL: ${total_capex+total_xfer_cost:>15,.0f} "
f"({(total_capex+total_xfer_cost)/1e6:.1f}M)")
# ============================================================
# TOOL ALLOCATION TABLES
# ============================================================
print("\n" + "="*80)
print("7. TOOL ALLOCATION PLAN (For Excel Answer Template)")
print("="*80)
for t_idx, t in enumerate(quarters):
print(f"\n--- {qlabels[t_idx]} ---")
print(f" {'WS':<4} {'Fab1':>6} {'Fab2':>6} {'Fab3':>6} {'Total':>6} {'NewPur':>7}")
for w in all_ws_list:
vals = [tool_count.get((f,w,t), tool_count.get((f,w),0)) for f in fabs]
total = sum(vals)
new_pur = sum(tool_purchases.get((f,w,t),0) for f in fabs)
if total > 0 or new_pur > 0:
print(f" {w:<4} {vals[0]:>6} {vals[1]:>6} {vals[2]:>6} {total:>6} {new_pur:>7}")
# ============================================================
# FLOW DISTRIBUTION TABLES
# ============================================================
print("\n" + "="*80)
print("8. FLOW DISTRIBUTION TABLES (For Excel Answer Template)")
print("="*80)
for t_idx, t in enumerate(quarters):
print(f"\n--- {qlabels[t_idx]} ---")
for i in nodes:
L = loading[(i,t)]
print(f"\n Node {i} (Loading={L}):")
print(f" {'Step':>4} {'WS':>3}", end="")
for f in fabs:
print(f" {'Fab'+str(f):>7}", end="")
print(f" {'Total':>7}")
for j in range(1, nsteps[i]+1):
mw = recipe[(i,j)][0]
print(f" {j:>4} {mw:>3}", end="")
step_total = 0
for f in fabs:
v = flow.get((i,j,f,t), 0)
step_total += v
print(f" {v:>7.0f}", end="")
print(f" {step_total:>7.0f}", end="")
# Check for transfers
if j < nsteps[i]:
for f1 in fabs:
for f2 in fabs:
if f1 != f2:
tv = transfers.get((i,j,f1,f2,t), 0)
if tv > 0:
print(f" [F{f1}->F{f2}:{tv:.0f}]", end="")
print()
# ============================================================
# PURCHASE SCHEDULE
# ============================================================
print("\n" + "="*80)
print("9. TOOL PURCHASE SCHEDULE")
print("="*80)
for t_idx, t in enumerate(quarters):
any_pur = False
for f in fabs:
for w in all_ws_list:
p = tool_purchases.get((f,w,t), 0)
if p > 0:
if not any_pur:
print(f"\n {qlabels[t_idx]}:")
any_pur = True
cost = p * ws[w][1]
print(f" Fab{f} {w}: {p} tools (${cost:.1f}M)")
# ============================================================
# CAPACITY VERIFICATION
# ============================================================
print("\n" + "="*80)
print("10. CAPACITY & DEMAND VERIFICATION")
print("="*80)
all_verified = True
for t_idx, t in enumerate(quarters):
issues = []
# Check each WS in each fab
for f in fabs:
for w in mintech_ws:
cnt = tool_count.get((f,w,t), tool_count.get((f,w),0))
cap = cnt * MPW * ws[w][0] # minutes available
# Demand on this WS in this fab
demand = 0
for i in nodes:
for j in range(1, nsteps[i]+1):
mw,mr,tw,tr = recipe[(i,j)]
if mw == w:
demand += flow.get((i,j,f,t), 0) * mr
if demand > cap + 0.1 and cap > 0:
issues.append(f" Fab{f} {w}: demand={demand:.0f}min > cap={cap:.0f}min")
elif demand > 0 and cap == 0:
# Check if TOR handles it
tor_w = m2t[w]
cnt_t = tool_count.get((f,tor_w,t), tool_count.get((f,tor_w),0))
if cnt_t == 0:
issues.append(f" Fab{f} {w}: demand={demand:.0f}min but NO tools!")
# Check demand fulfillment
for i in nodes:
L = loading[(i,t)]
for j in range(1, nsteps[i]+1):
total_assigned = sum(flow.get((i,j,f,t),0) for f in fabs)
if abs(total_assigned - L) > 0.5:
issues.append(f" Node{i} Step{j}: assigned={total_assigned:.0f} != loading={L}")
if issues:
print(f"\n {qlabels[t_idx]}: ISSUES FOUND")
for iss in issues:
print(iss)
all_verified = False
else:
print(f" {qlabels[t_idx]}: OK")
if all_verified:
print("\n ALL QUARTERS VERIFIED SUCCESSFULLY!")
else:
print("\n SOME ISSUES FOUND - Review needed")
# ============================================================
# SPACE VERIFICATION
# ============================================================
print("\n" + "="*80)
print("11. SPACE VERIFICATION")
print("="*80)
for t_idx, t in enumerate(quarters):
line = f" {qlabels[t_idx]}: "
all_ok = True
for f in fabs:
used = sum(tool_count.get((f,w,t), tool_count.get((f,w),0)) * ws[w][2]
for w in all_ws_list)
ok = used <= fab_space[f] + 0.01
if not ok: all_ok = False
line += f"F{f}={used:.0f}/{fab_space[f]}m² "
line += "OK" if all_ok else "EXCEEDED!"
print(line)
print("\n" + "="*80)
print("SOLUTION COMPLETE")
print("="*80)
aW1wb3J0IG1hdGgKZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQgZGVmYXVsdGRpY3QKaW1wb3J0IGpzb24KCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgQUxMIERBVEEKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCnF1YXJ0ZXJzID0gWydRMV8yNicsJ1EyXzI2JywnUTNfMjYnLCdRNF8yNicsJ1ExXzI3JywnUTJfMjcnLCdRM18yNycsJ1E0XzI3J10KcWxhYmVscyA9IFsiUTEnMjYiLCJRMicyNiIsIlEzJzI2IiwiUTQnMjYiLCJRMScyNyIsIlEyJzI3IiwiUTMnMjciLCJRNCcyNyJdCm5vZGVzID0gWzEsIDIsIDNdCmZhYnMgPSBbMSwgMiwgM10KCmxvYWRpbmcgPSB7CiAgICAoMSwnUTFfMjYnKToxMjAwMCwoMSwnUTJfMjYnKToxMDAwMCwoMSwnUTNfMjYnKTo4NTAwLCgxLCdRNF8yNicpOjc1MDAsCiAgICAoMSwnUTFfMjcnKTo2MDAwLCgxLCdRMl8yNycpOjUwMDAsKDEsJ1EzXzI3Jyk6NDAwMCwoMSwnUTRfMjcnKToyMDAwLAogICAgKDIsJ1ExXzI2Jyk6NTAwMCwoMiwnUTJfMjYnKTo1MjAwLCgyLCdRM18yNicpOjU0MDAsKDIsJ1E0XzI2Jyk6NTYwMCwKICAgICgyLCdRMV8yNycpOjYwMDAsKDIsJ1EyXzI3Jyk6NjUwMCwoMiwnUTNfMjcnKTo3MDAwLCgyLCdRNF8yNycpOjc1MDAsCiAgICAoMywnUTFfMjYnKTozMDAwLCgzLCdRMl8yNicpOjQ1MDAsKDMsJ1EzXzI2Jyk6NzAwMCwoMywnUTRfMjYnKTo4MDAwLAogICAgKDMsJ1ExXzI3Jyk6OTAwMCwoMywnUTJfMjcnKToxMTAwMCwoMywnUTNfMjcnKToxMzAwMCwoMywnUTRfMjcnKToxNjAwMCwKfQoKbnN0ZXBzID0gezE6MTEsIDI6MTUsIDM6MTd9CgpyZWNpcGUgPSB7CiAgICAoMSwxKTooJ0QnLDE0LCdEKycsMTIpLCgxLDIpOignRicsMjUsJ0YrJywyMSksKDEsMyk6KCdGJywyNywnRisnLDIzKSwKICAgICgxLDQpOignQScsMjAsJ0ErJywxNiksKDEsNSk6KCdGJywxMiwnRisnLDkpLCgxLDYpOignRCcsMjcsJ0QrJywyMSksCiAgICAoMSw3KTooJ0QnLDE3LCdEKycsMTMpLCgxLDgpOignQScsMTgsJ0ErJywxNiksKDEsOSk6KCdBJywxNiwnQSsnLDEzKSwKICAgICgxLDEwKTooJ0QnLDE0LCdEKycsMTEpLCgxLDExKTooJ0YnLDE4LCdGKycsMTYpLAogICAgKDIsMSk6KCdGJywxOSwnRisnLDE2KSwoMiwyKTooJ0InLDIwLCdCKycsMTgpLCgyLDMpOignRScsMTAsJ0UrJyw3KSwKICAgICgyLDQpOignQicsMjUsJ0IrJywxOSksKDIsNSk6KCdCJywxNSwnQisnLDExKSwoMiw2KTooJ0YnLDE2LCdGKycsMTQpLAogICAgKDIsNyk6KCdGJywxNywnRisnLDE1KSwoMiw4KTooJ0InLDIyLCdCKycsMTYpLCgyLDkpOignRScsNywnRSsnLDYpLAogICAgKDIsMTApOignRScsOSwnRSsnLDcpLCgyLDExKTooJ0UnLDIwLCdFKycsMTkpLCgyLDEyKTooJ0YnLDIxLCdGKycsMTgpLAogICAgKDIsMTMpOignRScsMTIsJ0UrJyw5KSwoMiwxNCk6KCdFJywxNSwnRSsnLDEyKSwoMiwxNSk6KCdFJywxMywnRSsnLDEwKSwKICAgICgzLDEpOignQycsMjEsJ0MrJywyMCksKDMsMik6KCdEJyw5LCdEKycsNyksKDMsMyk6KCdFJywyNCwnRSsnLDIzKSwKICAgICgzLDQpOignRScsMTUsJ0UrJywxMSksKDMsNSk6KCdGJywxNiwnRisnLDE0KSwoMyw2KTooJ0QnLDEyLCdEKycsMTEpLAogICAgKDMsNyk6KCdDJywyNCwnQysnLDIxKSwoMyw4KTooJ0MnLDE5LCdDKycsMTMpLCgzLDkpOignRCcsMTUsJ0QrJywxMyksCiAgICAoMywxMCk6KCdEJywyNCwnRCsnLDIwKSwoMywxMSk6KCdFJywxNywnRSsnLDE1KSwoMywxMik6KCdFJywxOCwnRSsnLDEzKSwKICAgICgzLDEzKTooJ0YnLDIwLCdGKycsMTgpLCgzLDE0KTooJ0MnLDEyLCdDKycsMTEpLCgzLDE1KTooJ0QnLDExLCdEKycsMTApLAogICAgKDMsMTYpOignQycsMjUsJ0MrJywyMCksKDMsMTcpOignRicsMTQsJ0YrJywxMyksCn0KCiMgKHV0aWxpemF0aW9uLCBjYXBleF9NJCwgc3BhY2VfbTIpCndzID0gewogICAgJ0EnOigwLjc4LDQuNSw2Ljc4KSwnQic6KDAuNzYsNi4wLDMuOTYpLCdDJzooMC44MCwyLjIsNS44MiksCiAgICAnRCc6KDAuODAsNC4wLDUuNjEpLCdFJzooMC43NiwzLjUsNC42NSksJ0YnOigwLjgwLDYuMCwzLjY4KSwKICAgICdBKyc6KDAuODQsNi4wLDYuOTMpLCdCKyc6KDAuODEsOC4wLDMuNzIpLCdDKyc6KDAuODYsMy4yLDUuNzUpLAogICAgJ0QrJzooMC44OCw1LjUsNS43NCksJ0UrJzooMC44NCw1LjgsNC44MCksJ0YrJzooMC45MCw4LjAsMy41NyksCn0KCmluaXRfdG9vbHMgPSB7CiAgICAoMSwnQScpOjAsKDEsJ0InKTowLCgxLCdDJyk6NDAsKDEsJ0QnKTozNSwoMSwnRScpOjE2LCgxLCdGJyk6MzYsCiAgICAoMiwnQScpOjUwLCgyLCdCJyk6MjUsKDIsJ0MnKTowLCgyLCdEJyk6NTAsKDIsJ0UnKTo0MCwoMiwnRicpOjkwLAogICAgKDMsJ0EnKTozNSwoMywnQicpOjMwLCgzLCdDJyk6MCwoMywnRCcpOjUwLCgzLCdFJyk6MzAsKDMsJ0YnKTo2MCwKfQpmb3IgZiBpbiBmYWJzOgogICAgZm9yIHcgaW4gWydBKycsJ0IrJywnQysnLCdEKycsJ0UrJywnRisnXToKICAgICAgICBpbml0X3Rvb2xzWyhmLHcpXSA9IDAKCmZhYl9zcGFjZSA9IHsxOjE1MDAsIDI6MTMwMCwgMzo3MDB9ClhGRVJfQ09TVCA9IDUwICAjICQvd2FmZXIvdHJhbnNmZXIKV0tTID0gMTMgICMgd2Vla3MgcGVyIHF1YXJ0ZXIKTU9fQ09TVCA9IDFfMDAwXzAwMCAgIyAkMU0gcGVyIHRvb2wgbW92ZW91dApNUFcgPSAxMDA4MCAgIyBtaW51dGVzIHBlciB3ZWVrID0gNyoyNCo2MAoKbWludGVjaF93cyA9IFsnQScsJ0InLCdDJywnRCcsJ0UnLCdGJ10KdG9yX3dzID0gWydBKycsJ0IrJywnQysnLCdEKycsJ0UrJywnRisnXQphbGxfd3NfbGlzdCA9IG1pbnRlY2hfd3MgKyB0b3Jfd3MKbTJ0ID0geydBJzonQSsnLCdCJzonQisnLCdDJzonQysnLCdEJzonRCsnLCdFJzonRSsnLCdGJzonRisnfQoKZGVmIHRyZXEobG9hZCwgcnB0LCB1dGlsKToKICAgICIiIlRvb2wgcmVxdWlyZW1lbnQgKGZyYWN0aW9uYWwpIiIiCiAgICBpZiBsb2FkIDw9IDA6IHJldHVybiAwLjAKICAgIHJldHVybiAobG9hZCAqIHJwdCkgLyAoTVBXICogdXRpbCkKCmRlZiB0cmVxX2NlaWwobG9hZCwgcnB0LCB1dGlsKToKICAgICIiIlRvb2wgcmVxdWlyZW1lbnQgKGludGVnZXIsIHJvdW5kZWQgdXApIiIiCiAgICByZXR1cm4gbWF0aC5jZWlsKHRyZXEobG9hZCwgcnB0LCB1dGlsKSkKCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgQU5BTFlTSVMgUEhBU0UKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCnByaW50KCI9Iio4MCkKcHJpbnQoIk1JQ1JPTiBOVVMtSVNFIEJBQ0MgMjAyNiAtIFFVRVNUSU9OIDEiKQpwcmludCgiQ09NUExFVEUgU09MVVRJT04gV0lUSCBBTkFMWVRJQ0FMIE9QVElNSVpBVElPTiIpCnByaW50KCI9Iio4MCkKCiMgQ29tcHV0ZSBhZ2dyZWdhdGUgdG9vbCByZXF1aXJlbWVudHMgcGVyIFdTIHBlciBxdWFydGVyCnByaW50KCJcbiIgKyAiPSIqODApCnByaW50KCIxLiBBR0dSRUdBVEUgVE9PTCBSRVFVSVJFTUVOVFMgKE1pbnRlY2ggT25seSkiKQpwcmludCgiPSIqODApCgp3c19ub2RlX3JlcSA9IHt9ICAjICh3cywgbm9kZSwgcXVhcnRlcikgLT4gZnJhY3Rpb25hbCB0b29sIHJlcQp3c190b3RhbF9yZXEgPSB7fSAgIyAod3MsIHF1YXJ0ZXIpIC0+IGZyYWN0aW9uYWwgdG90YWwKCmZvciB3IGluIG1pbnRlY2hfd3M6CiAgICBmb3IgdCBpbiBxdWFydGVyczoKICAgICAgICB0b3RhbCA9IDAKICAgICAgICBmb3IgaSBpbiBub2RlczoKICAgICAgICAgICAgTCA9IGxvYWRpbmdbKGksdCldCiAgICAgICAgICAgIHJlcSA9IDAKICAgICAgICAgICAgZm9yIGogaW4gcmFuZ2UoMSwgbnN0ZXBzW2ldKzEpOgogICAgICAgICAgICAgICAgbXcsbXIsdHcsdHIgPSByZWNpcGVbKGksaildCiAgICAgICAgICAgICAgICBpZiBtdyA9PSB3OgogICAgICAgICAgICAgICAgICAgIHJlcSArPSB0cmVxKEwsIG1yLCB3c1t3XVswXSkKICAgICAgICAgICAgd3Nfbm9kZV9yZXFbKHcsaSx0KV0gPSByZXEKICAgICAgICAgICAgdG90YWwgKz0gcmVxCiAgICAgICAgd3NfdG90YWxfcmVxWyh3LHQpXSA9IHRvdGFsCgojIFByaW50IHN1bW1hcnkgdGFibGUKcHJpbnQoZiJcbnsnV1MnOjwzfSIsIGVuZD0iIikKZm9yIHQgaW4gcWxhYmVsczoKICAgIHByaW50KGYiIHt0Oj43fSIsIGVuZD0iIikKcHJpbnQoZiIgeydBdmFpbCc6PjZ9IHsnUGtHYXAnOj42fSIpCgpmb3IgdyBpbiBtaW50ZWNoX3dzOgogICAgcHJpbnQoZiJ7dzo8M30iLCBlbmQ9IiIpCiAgICBwZWFrID0gMAogICAgZm9yIHQgaW4gcXVhcnRlcnM6CiAgICAgICAgciA9IG1hdGguY2VpbCh3c190b3RhbF9yZXFbKHcsdCldKQogICAgICAgIHBlYWsgPSBtYXgocGVhaywgcikKICAgICAgICBwcmludChmIiB7cjo+N30iLCBlbmQ9IiIpCiAgICBhdmFpbCA9IHN1bShpbml0X3Rvb2xzLmdldCgoZix3KSwwKSBmb3IgZiBpbiBmYWJzKQogICAgZ2FwID0gbWF4KDAsIHBlYWsgLSBhdmFpbCkKICAgIHByaW50KGYiIHthdmFpbDo+Nn0ge2dhcDo+Nn0iKQoKIyBBbHNvIGNvbXB1dGUgVE9SIGVxdWl2YWxlbnQKcHJpbnQoZiJcbnsnV1MnOjwzfSIsIGVuZD0iIikKZm9yIHQgaW4gcWxhYmVsczoKICAgIHByaW50KGYiIHt0Oj43fSIsIGVuZD0iIikKcHJpbnQoIiAoaWYgYWxsIFRPUikiKQoKZm9yIHd0IGluIHRvcl93czoKICAgIHByaW50KGYie3d0OjwzfSIsIGVuZD0iIikKICAgIGZvciB0IGluIHF1YXJ0ZXJzOgogICAgICAgIHRvdGFsID0gMAogICAgICAgIGZvciBpIGluIG5vZGVzOgogICAgICAgICAgICBMID0gbG9hZGluZ1soaSx0KV0KICAgICAgICAgICAgZm9yIGogaW4gcmFuZ2UoMSwgbnN0ZXBzW2ldKzEpOgogICAgICAgICAgICAgICAgbXcsbXIsdHcsdHIgPSByZWNpcGVbKGksaildCiAgICAgICAgICAgICAgICBpZiB0dyA9PSB3dDoKICAgICAgICAgICAgICAgICAgICB0b3RhbCArPSB0cmVxKEwsIHRyLCB3c1t3dF1bMF0pCiAgICAgICAgcHJpbnQoZiIge21hdGguY2VpbCh0b3RhbCk6Pjd9IiwgZW5kPSIiKQogICAgcHJpbnQoKQoKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBTUEFDRSBBTkFMWVNJUwojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKcHJpbnQoIlxuIiArICI9Iio4MCkKcHJpbnQoIjIuIElOSVRJQUwgU1BBQ0UgQU5BTFlTSVMiKQpwcmludCgiPSIqODApCgpmb3IgZiBpbiBmYWJzOgogICAgdXNlZCA9IHN1bShpbml0X3Rvb2xzLmdldCgoZix3KSwwKSAqIHdzW3ddWzJdIGZvciB3IGluIGFsbF93c19saXN0KQogICAgcmVtYWluID0gZmFiX3NwYWNlW2ZdIC0gdXNlZAogICAgcHJpbnQoZiJcbiAgRmFiIHtmfToge3VzZWQ6LjFmfSAvIHtmYWJfc3BhY2VbZl19IG3CsiB1c2VkICh7cmVtYWluOi4xZn0gbcKyIGZyZWUpIikKICAgIGZvciB3IGluIGFsbF93c19saXN0OgogICAgICAgIGNudCA9IGluaXRfdG9vbHMuZ2V0KChmLHcpLDApCiAgICAgICAgaWYgY250ID4gMDoKICAgICAgICAgICAgcHJpbnQoZiIgICAge3d9OiB7Y250fSDDlyB7d3Nbd11bMl19bcKyID0ge2NudCp3c1t3XVsyXTouMWZ9bcKyIikKCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgS0VZIElOU0lHSFQ6IFdoaWNoIG5vZGVzIHVzZSB3aGljaCB3b3Jrc3RhdGlvbnMKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCnByaW50KCJcbiIgKyAiPSIqODApCnByaW50KCIzLiBOT0RFLVdPUktTVEFUSU9OIE1BUFBJTkciKQpwcmludCgiPSIqODApCgpmb3IgaSBpbiBub2RlczoKICAgIHdzX3VzZWQgPSBkZWZhdWx0ZGljdChsaXN0KQogICAgZm9yIGogaW4gcmFuZ2UoMSwgbnN0ZXBzW2ldKzEpOgogICAgICAgIG13LG1yLHR3LHRyID0gcmVjaXBlWyhpLGopXQogICAgICAgIHdzX3VzZWRbbXddLmFwcGVuZCgoaiwgbXIpKQogICAgcHJpbnQoZiJcbiAgTm9kZSB7aX0gKHtuc3RlcHNbaV19IHN0ZXBzKToiKQogICAgZm9yIHcgaW4gbWludGVjaF93czoKICAgICAgICBpZiB3IGluIHdzX3VzZWQ6CiAgICAgICAgICAgIHN0ZXBzX3N0ciA9ICIsICIuam9pbihmIlN7c30oe3J9bWluKSIgZm9yIHMsciBpbiB3c191c2VkW3ddKQogICAgICAgICAgICB0b3RhbF9ycHQgPSBzdW0ociBmb3IgXyxyIGluIHdzX3VzZWRbd10pCiAgICAgICAgICAgIHByaW50KGYiICAgIHt3fToge3N0ZXBzX3N0cn0gW3RvdGFsIFJQVD17dG90YWxfcnB0fSBtaW5dIikKCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgQ1JJVElDQUw6IEMgd29ya3N0YXRpb24gb25seSBpbiBGYWIgMQojIEIgd29ya3N0YXRpb24gb25seSBpbiBGYWIgMiBhbmQgRmFiIDMKIyBUaGlzIGNvbnN0cmFpbnMgd2hlcmUgbm9kZXMgY2FuIHJ1bgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKcHJpbnQoIlxuIiArICI9Iio4MCkKcHJpbnQoIjQuIENSSVRJQ0FMIENPTlNUUkFJTlRTIikKcHJpbnQoIj0iKjgwKQpwcmludCgiICAtIEMgdG9vbHMgT05MWSBpbiBGYWIgMSAoNDAgdG9vbHMpIOKGkiBOb2RlIDMgQy1zdGVwcyBNVVNUIHJ1biBpbiBGYWIgMSIpCnByaW50KCIgIC0gQiB0b29scyBPTkxZIGluIEZhYiAyICgyNSkgYW5kIEZhYiAzICgzMCkg4oaSIE5vZGUgMiBCLXN0ZXBzIGluIEZhYiAyLzMiKQpwcmludCgiICAtIEEgdG9vbHMgT05MWSBpbiBGYWIgMiAoNTApIGFuZCBGYWIgMyAoMzUpIOKGkiBOb2RlIDEgQS1zdGVwcyBpbiBGYWIgMi8zIikKcHJpbnQoIiAgLSBOb2RlIDMgdXNlcyBDIGhlYXZpbHkgKHN0ZXBzIDEsNyw4LDE0LDE2KSAtIEMgaXMgYm90dGxlbmVjayIpCgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIEdSRUVEWSBPUFRJTUlaQVRJT04gQUxHT1JJVEhNCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgpwcmludCgiXG4iICsgIj0iKjgwKQpwcmludCgiNS4gT1BUSU1JWkFUSU9OOiBDT05TVFJVQ1RJTkcgRkVBU0lCTEUgU09MVVRJT04iKQpwcmludCgiPSIqODApCgojIFN0cmF0ZWd5OgojIDEuIEZvciBlYWNoIHF1YXJ0ZXIsIGRldGVybWluZSB3aGljaCBmYWJzIGNhbiBwcm9jZXNzIHdoaWNoIG5vZGVzIGJhc2VkIG9uIFdTIGF2YWlsYWJpbGl0eQojIDIuIEFzc2lnbiBsb2FkaW5nIHRvIG1pbmltaXplIHRyYW5zZmVycyAoa2VlcCB3YWZlcnMgaW4gc2FtZSBmYWIgYWNyb3NzIHN0ZXBzKQojIDMuIFB1cmNoYXNlIHRvb2xzIG9ubHkgd2hlbiBleGlzdGluZyBjYXBhY2l0eSBpbnN1ZmZpY2llbnQKIyA0LiBQcmVmZXIgbWludGVjaCAoY2hlYXBlcikgdW5sZXNzIHNwYWNlIGZvcmNlcyBUT1IgKG1vcmUgZWZmaWNpZW50IHBlciBtwrIpCgojIFRyYWNrIHN0YXRlCnRvb2xfY291bnQgPSB7fSAgIyAoZix3LHQpIC0+IGNvdW50CnRvb2xfcHVyY2hhc2VzID0ge30gICMgKGYsdyx0KSAtPiBwdXJjaGFzZWQgY291bnQKZmxvdyA9IHt9ICAjIChpLGosZix0KSAtPiB3YWZlcnMvd2Vlawp0cmFuc2ZlcnMgPSB7fSAgIyAoaSxqLGYxLGYyLHQpIC0+IHdhZmVycy93ZWVrIHRyYW5zZmVycmVkCgp0b3RhbF9jYXBleCA9IDAKdG90YWxfeGZlcl9jb3N0ID0gMAoKIyBJbml0aWFsaXplIHRvb2wgY291bnRzCmZvciBmIGluIGZhYnM6CiAgICBmb3IgdyBpbiBhbGxfd3NfbGlzdDoKICAgICAgICB0b29sX2NvdW50WyhmLHcpXSA9IGluaXRfdG9vbHMuZ2V0KChmLHcpLCAwKQoKZGVmIGdldF9zcGFjZV91c2VkKGYsIHRfdG9vbHMpOgogICAgIiIiQ29tcHV0ZSBzcGFjZSB1c2VkIGluIGZhYiBmIGdpdmVuIHRvb2wgY291bnRzIGRpY3QiIiIKICAgIHJldHVybiBzdW0odF90b29scy5nZXQoKGYsdyksIDApICogd3Nbd11bMl0gZm9yIHcgaW4gYWxsX3dzX2xpc3QpCgpkZWYgZ2V0X3NwYWNlX3JlbWFpbmluZyhmLCB0X3Rvb2xzKToKICAgIHJldHVybiBmYWJfc3BhY2VbZl0gLSBnZXRfc3BhY2VfdXNlZChmLCB0X3Rvb2xzKQoKZGVmIGdldF93c19jYXBhY2l0eShmLCB3X3R5cGUsIHRfdG9vbHMpOgogICAgIiIiR2V0IGNhcGFjaXR5IGluIHdhZmVycyptaW51dGVzL3dlZWsgZm9yIHdvcmtzdGF0aW9uIHR5cGUgaW4gZmFiIiIiCiAgICBjbnQgPSB0X3Rvb2xzLmdldCgoZiwgd190eXBlKSwgMCkKICAgIHJldHVybiBjbnQgKiBNUFcgKiB3c1t3X3R5cGVdWzBdCgojIEZvciBlYWNoIHF1YXJ0ZXIsIGRldGVybWluZSBvcHRpbWFsIGFsbG9jYXRpb24KZm9yIHRfaWR4LCB0IGluIGVudW1lcmF0ZShxdWFydGVycyk6CiAgICBwcmludChmIlxueyc9Jyo2MH0iKQogICAgcHJpbnQoZiJRdWFydGVyOiB7cWxhYmVsc1t0X2lkeF19IikKICAgIHByaW50KGYieyc9Jyo2MH0iKQogICAgCiAgICAjIEN1cnJlbnQgdG9vbCBzdGF0ZQogICAgY3VyX3Rvb2xzID0ge30KICAgIGZvciBmIGluIGZhYnM6CiAgICAgICAgZm9yIHcgaW4gYWxsX3dzX2xpc3Q6CiAgICAgICAgICAgIGN1cl90b29sc1soZix3KV0gPSB0b29sX2NvdW50WyhmLHcpXQogICAgCiAgICAjIENvbXB1dGUgd2hhdCBlYWNoIG5vZGUgbmVlZHMgb24gZWFjaCBXUyB0eXBlCiAgICAjIEZvciBlYWNoIChub2RlLCBtaW50ZWNoX3dzKSwgY29tcHV0ZSB0b3RhbCBSUFQqbG9hZGluZwogICAgbm9kZV93c19kZW1hbmQgPSB7fSAgIyAobm9kZSwgbWludGVjaF93cykgLT4gbWludXRlcy93ZWVrIG5lZWRlZAogICAgZm9yIGkgaW4gbm9kZXM6CiAgICAgICAgTCA9IGxvYWRpbmdbKGksdCldCiAgICAgICAgZm9yIHcgaW4gbWludGVjaF93czoKICAgICAgICAgICAgdG90YWxfbWluID0gMAogICAgICAgICAgICBmb3IgaiBpbiByYW5nZSgxLCBuc3RlcHNbaV0rMSk6CiAgICAgICAgICAgICAgICBtdyxtcix0dyx0ciA9IHJlY2lwZVsoaSxqKV0KICAgICAgICAgICAgICAgIGlmIG13ID09IHc6CiAgICAgICAgICAgICAgICAgICAgdG90YWxfbWluICs9IEwgKiBtcgogICAgICAgICAgICBub2RlX3dzX2RlbWFuZFsoaSx3KV0gPSB0b3RhbF9taW4KICAgIAogICAgIyBTdGVwIDE6IERldGVybWluZSBmYWIgYXNzaWdubWVudCBmb3IgZWFjaCBub2RlCiAgICAjIENvbnN0cmFpbnQ6IE5vZGUgMyBDLXN0ZXBzIG11c3QgYmUgaW4gRmFiIDEgKG9ubHkgZmFiIHdpdGggQyB0b29scykKICAgICMgQ29uc3RyYWludDogTm9kZSAyIEItc3RlcHMgbXVzdCBiZSBpbiBGYWIgMiBvciAzCiAgICAjIENvbnN0cmFpbnQ6IE5vZGUgMSBBLXN0ZXBzIG11c3QgYmUgaW4gRmFiIDIgb3IgMwogICAgCiAgICAjIFN0cmF0ZWd5OiBBc3NpZ24gZWFjaCBub2RlJ3MgbG9hZGluZyBhY3Jvc3MgZmFicyBwcm9wb3J0aW9uYWwgdG8gCiAgICAjIGF2YWlsYWJsZSBjYXBhY2l0eSBvbiB0aGUgQk9UVExFTkVDSyB3b3Jrc3RhdGlvbiBmb3IgdGhhdCBub2RlCiAgICAKICAgICMgRm9yIHNpbXBsaWNpdHkgYW5kIHRvIG1pbmltaXplIHRyYW5zZmVyczogdHJ5IHRvIGtlZXAgZW50aXJlIG5vZGUgaW4gb25lIGZhYgogICAgIyB3aGVyZSBwb3NzaWJsZSwgb3Igc3BsaXQgYXQgbmF0dXJhbCBicmVha3BvaW50cwogICAgCiAgICAjIE5vZGUgMzogTVVTVCBnbyB0aHJvdWdoIEZhYiAxIGZvciBDIHN0ZXBzLCBidXQgRC9FL0Ygc3RlcHMgY2FuIGdvIGVsc2V3aGVyZQogICAgIyBOb2RlIDI6IE1VU1QgZ28gdGhyb3VnaCBGYWIgMiBvciAzIGZvciBCIHN0ZXBzCiAgICAjIE5vZGUgMTogTVVTVCBnbyB0aHJvdWdoIEZhYiAyIG9yIDMgZm9yIEEgc3RlcHMKICAgIAogICAgIyBBcHByb2FjaDogUnVuIGFsbCBvZiBOb2RlIDMncyBDIHN0ZXBzIGluIEZhYiAxLCAKICAgICMgY2hlY2sgaWYgRmFiIDEgaGFzIGVub3VnaCBEL0UvRiBmb3Igbm9uLUMgc3RlcHMgdG9vCiAgICAKICAgICMgRmlyc3QgY2hlY2sgQyBjYXBhY2l0eSBmb3IgTm9kZSAzCiAgICBMMyA9IGxvYWRpbmdbKDMsdCldCiAgICBjX2RlbWFuZF9taW4gPSBub2RlX3dzX2RlbWFuZFsoMywnQycpXSAgIyB0b3RhbCBDLW1pbnV0ZXMgbmVlZGVkCiAgICBjX2NhcGFjaXR5ID0gZ2V0X3dzX2NhcGFjaXR5KDEsICdDJywgY3VyX3Rvb2xzKSAgIyBGYWIgMSBDIGNhcGFjaXR5CiAgICAKICAgICMgQWxzbyBjaGVjayBpZiB3ZSBuZWVkIEMrIHRvb2xzCiAgICBjX3N0ZXBzX2Zvcl9uMyA9IFsoaiwgcmVjaXBlWygzLGopXVsxXSwgcmVjaXBlWygzLGopXVszXSkgCiAgICAgICAgICAgICAgICAgICAgICAgZm9yIGogaW4gcmFuZ2UoMSwgbnN0ZXBzWzNdKzEpIGlmIHJlY2lwZVsoMyxqKV1bMF0gPT0gJ0MnXQogICAgY19taW50ZWNoX3RyZXEgPSBzdW0odHJlcShMMywgbXIsIHdzWydDJ11bMF0pIGZvciBfLCBtciwgXyBpbiBjX3N0ZXBzX2Zvcl9uMykKICAgIGNfdG9yX3RyZXEgPSBzdW0odHJlcShMMywgdHIsIHdzWydDKyddWzBdKSBmb3IgXywgXywgdHIgaW4gY19zdGVwc19mb3JfbjMpCiAgICAKICAgIHByaW50KGYiXG4gIE5vZGUgMyBDIHJlcXVpcmVtZW50OiB7Y19taW50ZWNoX3RyZXE6LjFmfSB0b29scyAobWludGVjaCkgb3IgIgogICAgICAgICAgZiJ7Y190b3JfdHJlcTouMWZ9IChUT1IpIikKICAgIHByaW50KGYiICBGYWIgMSBDIHRvb2xzIGF2YWlsYWJsZToge2N1cl90b29scy5nZXQoKDEsJ0MnKSwwKX0iKQogICAgCiAgICAjIENoZWNrIGlmIHdlIG5lZWQgbW9yZSBDIHRvb2xzCiAgICBjX2F2YWlsYWJsZSA9IGN1cl90b29scy5nZXQoKDEsJ0MnKSwgMCkKICAgIGNfbmVlZGVkID0gbWF0aC5jZWlsKGNfbWludGVjaF90cmVxKQogICAgCiAgICBpZiBjX25lZWRlZCA+IGNfYXZhaWxhYmxlOgogICAgICAgICMgTmVlZCB0byBidXkgQyBvciBDKyB0b29scyBmb3IgRmFiIDEKICAgICAgICBjX2dhcCA9IGNfbmVlZGVkIC0gY19hdmFpbGFibGUKICAgICAgICBzcGFjZV9yZW0gPSBnZXRfc3BhY2VfcmVtYWluaW5nKDEsIGN1cl90b29scykKICAgICAgICAKICAgICAgICAjIENvbXBhcmU6IGJ1eSBDIChjaGVhcGVyLCAyLjJNLCA1LjgybcKyKSB2cyBDKyAoMy4yTSwgNS43NW3CsiwgZmFzdGVyKQogICAgICAgICMgQysgaXMgc2xpZ2h0bHkgc21hbGxlciBhbmQgZmFzdGVyIC0gYmV0dGVyIGZvciBzcGFjZS1jb25zdHJhaW5lZCBzaXR1YXRpb25zCiAgICAgICAgCiAgICAgICAgIyBDaGVjayBpZiBDIGZpdHMKICAgICAgICBjX3NwYWNlX25lZWRlZCA9IGNfZ2FwICogd3NbJ0MnXVsyXQogICAgICAgIGNwX2VxdWl2ID0gbWF0aC5jZWlsKGNfdG9yX3RyZXEpIC0gMCAgIyBubyBleGlzdGluZyBDKyB0b29scwogICAgICAgIGNwX3NwYWNlX25lZWRlZCA9IGNwX2VxdWl2ICogd3NbJ0MrJ11bMl0KICAgICAgICAKICAgICAgICAjIFRyeSBtaW50ZWNoIEMgZmlyc3QKICAgICAgICBpZiBjX3NwYWNlX25lZWRlZCA8PSBzcGFjZV9yZW06CiAgICAgICAgICAgIGN1cl90b29sc1soMSwnQycpXSArPSBjX2dhcAogICAgICAgICAgICBjb3N0ID0gY19nYXAgKiB3c1snQyddWzFdICogMWU2CiAgICAgICAgICAgIHRvdGFsX2NhcGV4ICs9IGNvc3QKICAgICAgICAgICAgdG9vbF9wdXJjaGFzZXNbKDEsJ0MnLHQpXSA9IHRvb2xfcHVyY2hhc2VzLmdldCgoMSwnQycsdCksMCkgKyBjX2dhcAogICAgICAgICAgICBwcmludChmIiAgLT4gUHVyY2hhc2Uge2NfZ2FwfSBDIHRvb2xzIGZvciBGYWIgMSAoJHtjb3N0LzFlNjouMWZ9TSkiKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgICMgVHJ5IEMrIChmYXN0ZXIsIGxlc3MgdG9vbHMgbmVlZGVkLCBzbGlnaHRseSBzbWFsbGVyKQogICAgICAgICAgICBjcF9uZWVkZWQgPSBtYXRoLmNlaWwoY190b3JfdHJlcSkKICAgICAgICAgICAgY3Bfc3BhY2UgPSBjcF9uZWVkZWQgKiB3c1snQysnXVsyXQogICAgICAgICAgICBpZiBjcF9zcGFjZSA8PSBzcGFjZV9yZW06CiAgICAgICAgICAgICAgICBjdXJfdG9vbHNbKDEsJ0MrJyldICs9IGNwX25lZWRlZAogICAgICAgICAgICAgICAgY29zdCA9IGNwX25lZWRlZCAqIHdzWydDKyddWzFdICogMWU2CiAgICAgICAgICAgICAgICB0b3RhbF9jYXBleCArPSBjb3N0CiAgICAgICAgICAgICAgICB0b29sX3B1cmNoYXNlc1soMSwnQysnLHQpXSA9IHRvb2xfcHVyY2hhc2VzLmdldCgoMSwnQysnLHQpLDApICsgY3BfbmVlZGVkCiAgICAgICAgICAgICAgICBwcmludChmIiAgLT4gUHVyY2hhc2Uge2NwX25lZWRlZH0gQysgdG9vbHMgZm9yIEZhYiAxICgke2Nvc3QvMWU2Oi4xZn1NKSIpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAjIEJ1eSBtaXgKICAgICAgICAgICAgICAgIG1heF9jID0gaW50KHNwYWNlX3JlbSAvIHdzWydDJ11bMl0pCiAgICAgICAgICAgICAgICBjdXJfdG9vbHNbKDEsJ0MnKV0gKz0gbWF4X2MKICAgICAgICAgICAgICAgIGlmIG1heF9jID4gMDoKICAgICAgICAgICAgICAgICAgICBjb3N0ID0gbWF4X2MgKiB3c1snQyddWzFdICogMWU2CiAgICAgICAgICAgICAgICAgICAgdG90YWxfY2FwZXggKz0gY29zdAogICAgICAgICAgICAgICAgICAgIHRvb2xfcHVyY2hhc2VzWygxLCdDJyx0KV0gPSB0b29sX3B1cmNoYXNlcy5nZXQoKDEsJ0MnLHQpLDApICsgbWF4X2MKICAgICAgICAgICAgICAgICAgICBwcmludChmIiAgLT4gUHVyY2hhc2Uge21heF9jfSBDIHRvb2xzIGZvciBGYWIgMSAoJHtjb3N0LzFlNjouMWZ9TSkiKQogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICBzcGFjZV9yZW0yID0gZ2V0X3NwYWNlX3JlbWFpbmluZygxLCBjdXJfdG9vbHMpCiAgICAgICAgICAgICAgICByZW1haW5pbmdfY19kZW1hbmQgPSBjX21pbnRlY2hfdHJlcSAtIChjX2F2YWlsYWJsZSArIG1heF9jKSAqIE1QVyAqIHdzWydDJ11bMF0KICAgICAgICAgICAgICAgIGlmIHJlbWFpbmluZ19jX2RlbWFuZCA+IDA6CiAgICAgICAgICAgICAgICAgICAgY3BfbmVlZGVkMiA9IG1hdGguY2VpbChyZW1haW5pbmdfY19kZW1hbmQgLyAoTVBXICogd3NbJ0MrJ11bMF0pKQogICAgICAgICAgICAgICAgICAgIGNwX25lZWRlZDIgPSBtaW4oY3BfbmVlZGVkMiwgaW50KHNwYWNlX3JlbTIgLyB3c1snQysnXVsyXSkpCiAgICAgICAgICAgICAgICAgICAgaWYgY3BfbmVlZGVkMiA+IDA6CiAgICAgICAgICAgICAgICAgICAgICAgIGN1cl90b29sc1soMSwnQysnKV0gKz0gY3BfbmVlZGVkMgogICAgICAgICAgICAgICAgICAgICAgICBjb3N0ID0gY3BfbmVlZGVkMiAqIHdzWydDKyddWzFdICogMWU2CiAgICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX2NhcGV4ICs9IGNvc3QKICAgICAgICAgICAgICAgICAgICAgICAgdG9vbF9wdXJjaGFzZXNbKDEsJ0MrJyx0KV0gPSBjcF9uZWVkZWQyCiAgICAgICAgICAgICAgICAgICAgICAgIHByaW50KGYiICAtPiBQdXJjaGFzZSB7Y3BfbmVlZGVkMn0gQysgZm9yIEZhYiAxICgke2Nvc3QvMWU2Oi4xZn1NKSIpCiAgICAKICAgICMgTm93IGhhbmRsZSBhbGwgb3RoZXIgd29ya3N0YXRpb25zIGZvciBhbGwgbm9kZXMKICAgICMgRm9yIGVhY2ggV1MgdHlwZSwgY29tcHV0ZSB0b3RhbCBkZW1hbmQgYWNyb3NzIGFsbCBub2RlcywgY2hlY2sgYWdhaW5zdCBzdXBwbHkKICAgIAogICAgZm9yIHcgaW4gbWludGVjaF93czoKICAgICAgICB0b3RhbF9kZW1hbmQgPSBzdW0obm9kZV93c19kZW1hbmRbKGksdyldIGZvciBpIGluIG5vZGVzKSAgIyBtaW51dGVzL3dlZWsKICAgICAgICB0b3RhbF9zdXBwbHkgPSBzdW0oZ2V0X3dzX2NhcGFjaXR5KGYsIHcsIGN1cl90b29scykgZm9yIGYgaW4gZmFicykKICAgICAgICB0b3JfdyA9IG0ydFt3XQogICAgICAgIHRvdGFsX3N1cHBseV90b3IgPSBzdW0oZ2V0X3dzX2NhcGFjaXR5KGYsIHRvcl93LCBjdXJfdG9vbHMpIGZvciBmIGluIGZhYnMpCiAgICAgICAgCiAgICAgICAgZGVtYW5kX3Rvb2xzID0gc3VtKHRyZXEobG9hZGluZ1soaSx0KV0sIHJlY2lwZVsoaSxqKV1bMV0sIHdzW3ddWzBdKQogICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBpIGluIG5vZGVzIGZvciBqIGluIHJhbmdlKDEsIG5zdGVwc1tpXSsxKQogICAgICAgICAgICAgICAgICAgICAgICAgIGlmIHJlY2lwZVsoaSxqKV1bMF0gPT0gdykKICAgICAgICAKICAgICAgICBzdXBwbHlfdG9vbHMgPSBzdW0oY3VyX3Rvb2xzLmdldCgoZix3KSwwKSBmb3IgZiBpbiBmYWJzKQogICAgICAgIHN1cHBseV90b3IgPSBzdW0oY3VyX3Rvb2xzLmdldCgoZix0b3JfdyksMCkgZm9yIGYgaW4gZmFicykKICAgICAgICAKICAgICAgICBuZWVkZWQgPSBtYXRoLmNlaWwoZGVtYW5kX3Rvb2xzKQogICAgICAgIAogICAgICAgIGlmIG5lZWRlZCA+IHN1cHBseV90b29scyArIHN1cHBseV90b3I6CiAgICAgICAgICAgIGdhcCA9IG5lZWRlZCAtIHN1cHBseV90b29scyAtIHN1cHBseV90b3IKICAgICAgICAgICAgCiAgICAgICAgICAgICMgRmluZCBiZXN0IGZhYiB0byBhZGQgdG9vbHMgKG1vc3Qgc3BhY2UgcmVtYWluaW5nKQogICAgICAgICAgICBmYWJfc3BhY2VfcmVtID0gWyhnZXRfc3BhY2VfcmVtYWluaW5nKGYsIGN1cl90b29scyksIGYpIGZvciBmIGluIGZhYnNdCiAgICAgICAgICAgIGZhYl9zcGFjZV9yZW0uc29ydChyZXZlcnNlPVRydWUpCiAgICAgICAgICAgIAogICAgICAgICAgICByZW1haW5pbmdfZ2FwID0gZ2FwCiAgICAgICAgICAgIGZvciBzcGFjZV9yLCBmIGluIGZhYl9zcGFjZV9yZW06CiAgICAgICAgICAgICAgICBpZiByZW1haW5pbmdfZ2FwIDw9IDA6CiAgICAgICAgICAgICAgICAgICAgYnJlYWsKICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgIyBEZWNpZGUgbWludGVjaCB2cyBUT1IKICAgICAgICAgICAgICAgICMgQ29tcGFyZSBjb3N0LWVmZmVjdGl2ZW5lc3M6IGNvc3QgcGVyIHVuaXQgY2FwYWNpdHkKICAgICAgICAgICAgICAgICMgTWludGVjaDogY29zdC90b29sIC8gKE1QVyAqIHV0aWwgLyBhdmdfcnB0KSAKICAgICAgICAgICAgICAgICMgSnVzdCBjb21wYXJlIHJhdyBDYXBFeCBzaW5jZSB3ZSB3YW50IG1pbiBjb3N0CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIG1pbnRlY2hfY2FuX2ZpdCA9IGludChzcGFjZV9yIC8gd3Nbd11bMl0pCiAgICAgICAgICAgICAgICB0b3JfY2FuX2ZpdCA9IGludChzcGFjZV9yIC8gd3NbdG9yX3ddWzJdKQogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAjIFByZWZlciBtaW50ZWNoIChjaGVhcGVyKSBpZiBpdCBmaXRzCiAgICAgICAgICAgICAgICBhZGRfbWludGVjaCA9IG1pbihyZW1haW5pbmdfZ2FwLCBtaW50ZWNoX2Nhbl9maXQpCiAgICAgICAgICAgICAgICBpZiBhZGRfbWludGVjaCA+IDA6CiAgICAgICAgICAgICAgICAgICAgY3VyX3Rvb2xzWyhmLHcpXSA9IGN1cl90b29scy5nZXQoKGYsdyksMCkgKyBhZGRfbWludGVjaAogICAgICAgICAgICAgICAgICAgIGNvc3QgPSBhZGRfbWludGVjaCAqIHdzW3ddWzFdICogMWU2CiAgICAgICAgICAgICAgICAgICAgdG90YWxfY2FwZXggKz0gY29zdAogICAgICAgICAgICAgICAgICAgIHRvb2xfcHVyY2hhc2VzWyhmLHcsdCldID0gdG9vbF9wdXJjaGFzZXMuZ2V0KChmLHcsdCksMCkgKyBhZGRfbWludGVjaAogICAgICAgICAgICAgICAgICAgIHJlbWFpbmluZ19nYXAgLT0gYWRkX21pbnRlY2gKICAgICAgICAgICAgICAgICAgICBwcmludChmIiAgLT4gUHVyY2hhc2Uge2FkZF9taW50ZWNofSB7d30gZm9yIEZhYiB7Zn0gKCR7Y29zdC8xZTY6LjFmfU0pIikKICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgaWYgcmVtYWluaW5nX2dhcCA+IDA6CiAgICAgICAgICAgICAgICAgICAgc3BhY2VfcjIgPSBnZXRfc3BhY2VfcmVtYWluaW5nKGYsIGN1cl90b29scykKICAgICAgICAgICAgICAgICAgICBhZGRfdG9yID0gbWluKHJlbWFpbmluZ19nYXAsIGludChzcGFjZV9yMiAvIHdzW3Rvcl93XVsyXSkpCiAgICAgICAgICAgICAgICAgICAgaWYgYWRkX3RvciA+IDA6CiAgICAgICAgICAgICAgICAgICAgICAgIGN1cl90b29sc1soZix0b3JfdyldID0gY3VyX3Rvb2xzLmdldCgoZix0b3JfdyksMCkgKyBhZGRfdG9yCiAgICAgICAgICAgICAgICAgICAgICAgIGNvc3QgPSBhZGRfdG9yICogd3NbdG9yX3ddWzFdICogMWU2CiAgICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX2NhcGV4ICs9IGNvc3QKICAgICAgICAgICAgICAgICAgICAgICAgdG9vbF9wdXJjaGFzZXNbKGYsdG9yX3csdCldID0gdG9vbF9wdXJjaGFzZXMuZ2V0KChmLHRvcl93LHQpLDApICsgYWRkX3RvcgogICAgICAgICAgICAgICAgICAgICAgICByZW1haW5pbmdfZ2FwIC09IGFkZF90b3IKICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnQoZiIgIC0+IFB1cmNoYXNlIHthZGRfdG9yfSB7dG9yX3d9IGZvciBGYWIge2Z9ICgke2Nvc3QvMWU2Oi4xZn1NKSIpCiAgICAgICAgICAgIAogICAgICAgICAgICBpZiByZW1haW5pbmdfZ2FwID4gMDoKICAgICAgICAgICAgICAgIHByaW50KGYiICBXQVJOSU5HOiBDYW5ub3QgZml0IHtyZW1haW5pbmdfZ2FwfSBtb3JlIHt3fSB0b29scyEiKQogICAgCiAgICAjIFN0ZXAgMjogQXNzaWduIGZsb3dzCiAgICAjIEZvciBlYWNoIG5vZGUsIGFzc2lnbiBhbGwgbG9hZGluZyB0byB0aGUgZmFiKHMpIHRoYXQgaGF2ZSBjYXBhY2l0eQogICAgIyBUcnkgdG8ga2VlcCBlbnRpcmUgbm9kZSBpbiBvbmUgZmFiIHRvIG1pbmltaXplIHRyYW5zZmVycwogICAgCiAgICBmb3IgaSBpbiBub2RlczoKICAgICAgICBMID0gbG9hZGluZ1soaSx0KV0KICAgICAgICAKICAgICAgICAjIERldGVybWluZSB3aGljaCBmYWJzIGNhbiBoYW5kbGUgdGhpcyBub2RlCiAgICAgICAgIyBBIGZhYiBjYW4gaGFuZGxlIGEgbm9kZSBpZiBpdCBoYXMgY2FwYWNpdHkgZm9yIEFMTCBXUyB0eXBlcyB1c2VkIGJ5IHRoYXQgbm9kZQogICAgICAgIHdzX3VzZWRfYnlfbm9kZSA9IHNldCgpCiAgICAgICAgZm9yIGogaW4gcmFuZ2UoMSwgbnN0ZXBzW2ldKzEpOgogICAgICAgICAgICBtdyxtcix0dyx0ciA9IHJlY2lwZVsoaSxqKV0KICAgICAgICAgICAgd3NfdXNlZF9ieV9ub2RlLmFkZChtdykKICAgICAgICAKICAgICAgICAjIENoZWNrIGNhcGFjaXR5IHBlciBmYWIKICAgICAgICBmYWJfZmVhc2libGUgPSB7fQogICAgICAgIGZvciBmIGluIGZhYnM6CiAgICAgICAgICAgIG1heF9sb2FkID0gZmxvYXQoJ2luZicpCiAgICAgICAgICAgIGZvciB3IGluIHdzX3VzZWRfYnlfbm9kZToKICAgICAgICAgICAgICAgICMgSG93IG1hbnkgd2FmZXJzL3dlZWsgY2FuIHRoaXMgZmFiIGhhbmRsZSBmb3IgdGhpcyBXUz8KICAgICAgICAgICAgICAgIGNhcF9tID0gZ2V0X3dzX2NhcGFjaXR5KGYsIHcsIGN1cl90b29scykKICAgICAgICAgICAgICAgIGNhcF90ID0gZ2V0X3dzX2NhcGFjaXR5KGYsIG0ydFt3XSwgY3VyX3Rvb2xzKQogICAgICAgICAgICAgICAgIyBUb3RhbCBjYXBhY2l0eSBpbiBtaW51dGVzLCBjb252ZXJ0IHRvIHdhZmVycwogICAgICAgICAgICAgICAgIyBUb3RhbCBSUFQgZm9yIHRoaXMgbm9kZSBvbiB0aGlzIFdTCiAgICAgICAgICAgICAgICB0b3RhbF9ycHQgPSBzdW0ocmVjaXBlWyhpLGopXVsxXSBmb3IgaiBpbiByYW5nZSgxLCBuc3RlcHNbaV0rMSkgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiByZWNpcGVbKGksaildWzBdID09IHcpCiAgICAgICAgICAgICAgICBpZiB0b3RhbF9ycHQgPiAwOgogICAgICAgICAgICAgICAgICAgIG1heF93YWZlcnMgPSAoY2FwX20gKyBjYXBfdCkgLyB0b3RhbF9ycHQgaWYgdG90YWxfcnB0ID4gMCBlbHNlIGZsb2F0KCdpbmYnKQogICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICBtYXhfd2FmZXJzID0gZmxvYXQoJ2luZicpCiAgICAgICAgICAgICAgICBtYXhfbG9hZCA9IG1pbihtYXhfbG9hZCwgbWF4X3dhZmVycykKICAgICAgICAgICAgZmFiX2ZlYXNpYmxlW2ZdID0gbWF4X2xvYWQKICAgICAgICAKICAgICAgICAjIERpc3RyaWJ1dGUgbG9hZGluZyBhY3Jvc3MgZmFicyBwcm9wb3J0aW9uYWxseSB0byBjYXBhY2l0eQogICAgICAgIHRvdGFsX2NhcCA9IHN1bShmYWJfZmVhc2libGUudmFsdWVzKCkpCiAgICAgICAgCiAgICAgICAgaWYgdG90YWxfY2FwIDw9IDA6CiAgICAgICAgICAgIHByaW50KGYiICBFUlJPUjogTm8gY2FwYWNpdHkgZm9yIE5vZGUge2l9ISIpCiAgICAgICAgICAgIGZvciBqIGluIHJhbmdlKDEsIG5zdGVwc1tpXSsxKToKICAgICAgICAgICAgICAgIGZvciBmIGluIGZhYnM6CiAgICAgICAgICAgICAgICAgICAgZmxvd1soaSxqLGYsdCldID0gTCBpZiBmID09IDEgZWxzZSAwCiAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgCiAgICAgICAgIyBBbGxvY2F0ZQogICAgICAgIGFsbG9jID0ge30KICAgICAgICByZW1haW5pbmcgPSBMCiAgICAgICAgZmFiX3NvcnRlZCA9IHNvcnRlZChmYWJzLCBrZXk9bGFtYmRhIGY6IGZhYl9mZWFzaWJsZVtmXSwgcmV2ZXJzZT1UcnVlKQogICAgICAgIAogICAgICAgIGZvciBpZHgsIGYgaW4gZW51bWVyYXRlKGZhYl9zb3J0ZWQpOgogICAgICAgICAgICBpZiBpZHggPCBsZW4oZmFicykgLSAxOgogICAgICAgICAgICAgICAgc2hhcmUgPSBtaW4ocmVtYWluaW5nLCBpbnQoTCAqIGZhYl9mZWFzaWJsZVtmXSAvIHRvdGFsX2NhcCkpCiAgICAgICAgICAgICAgICBhbGxvY1tmXSA9IHNoYXJlCiAgICAgICAgICAgICAgICByZW1haW5pbmcgLT0gc2hhcmUKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIGFsbG9jW2ZdID0gcmVtYWluaW5nCiAgICAgICAgCiAgICAgICAgIyBBc3NpZ24gYWxsIHN0ZXBzIHRvIHNhbWUgZmFiIGFsbG9jYXRpb24gKG5vIHRyYW5zZmVycyB3aXRoaW4gYSBub2RlKQogICAgICAgIGZvciBqIGluIHJhbmdlKDEsIG5zdGVwc1tpXSsxKToKICAgICAgICAgICAgZm9yIGYgaW4gZmFiczoKICAgICAgICAgICAgICAgIGZsb3dbKGksaixmLHQpXSA9IGFsbG9jLmdldChmLCAwKQogICAgICAgIAogICAgICAgIG5vbnplcm8gPSBbKGYsIGFsbG9jW2ZdKSBmb3IgZiBpbiBmYWJzIGlmIGFsbG9jLmdldChmLDApID4gMF0KICAgICAgICBhbGxvY19zdHIgPSAiLCAiLmpvaW4oZiJGYWJ7Zn09e3Z9IiBmb3IgZix2IGluIG5vbnplcm8pCiAgICAgICAgcHJpbnQoZiIgIE5vZGUge2l9ICh7TH0gd2ZyL3drKToge2FsbG9jX3N0cn0iKQogICAgCiAgICAjIENoZWNrIGlmIGFueSBzdGVwcyBuZWVkIGNyb3NzLWZhYiB0cmFuc2ZlciBkdWUgdG8gV1MgYXZhaWxhYmlsaXR5CiAgICAjIEUuZy4sIE5vZGUgMyBDLXN0ZXBzIE1VU1QgYmUgaW4gRmFiIDEsIGJ1dCBEL0UvRiBzdGVwcyBtaWdodCBiZSBlbHNld2hlcmUKICAgIAogICAgZm9yIGkgaW4gbm9kZXM6CiAgICAgICAgTCA9IGxvYWRpbmdbKGksdCldCiAgICAgICAgZm9yIGogaW4gcmFuZ2UoMSwgbnN0ZXBzW2ldKzEpOgogICAgICAgICAgICBtdyxtcix0dyx0ciA9IHJlY2lwZVsoaSxqKV0KICAgICAgICAgICAgZm9yIGYgaW4gZmFiczoKICAgICAgICAgICAgICAgIGFzc2lnbmVkID0gZmxvdy5nZXQoKGksaixmLHQpLCAwKQogICAgICAgICAgICAgICAgaWYgYXNzaWduZWQgPiAwOgogICAgICAgICAgICAgICAgICAgICMgQ2hlY2sgaWYgdGhpcyBmYWIgaGFzIHRoZSBXUwogICAgICAgICAgICAgICAgICAgIGhhc19taW50ZWNoID0gY3VyX3Rvb2xzLmdldCgoZixtdyksMCkgPiAwCiAgICAgICAgICAgICAgICAgICAgaGFzX3RvciA9IGN1cl90b29scy5nZXQoKGYsbTJ0W213XSksMCkgPiAwCiAgICAgICAgICAgICAgICAgICAgaWYgbm90IGhhc19taW50ZWNoIGFuZCBub3QgaGFzX3RvcjoKICAgICAgICAgICAgICAgICAgICAgICAgIyBOZWVkIHRvIHRyYW5zZmVyIHRoaXMgdG8gYSBmYWIgdGhhdCBoYXMgdGhlIFdTCiAgICAgICAgICAgICAgICAgICAgICAgICMgRmluZCB0YXJnZXQgZmFiCiAgICAgICAgICAgICAgICAgICAgICAgIGZvciBmMiBpbiBmYWJzOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgZjIgIT0gZiBhbmQgKGN1cl90b29scy5nZXQoKGYyLG13KSwwKSA+IDAgb3IgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjdXJfdG9vbHMuZ2V0KChmMixtMnRbbXddKSwwKSA+IDApOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgVHJhbnNmZXIgZnJvbSBmIHRvIGYyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBNb3ZlIHRoZSBsb2FkIGZvciB0aGlzIHN0ZXAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvbGRfdmFsID0gZmxvd1soaSxqLGYsdCldCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmxvd1soaSxqLGYsdCldID0gMAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZsb3dbKGksaixmMix0KV0gPSBmbG93LmdldCgoaSxqLGYyLHQpLCAwKSArIG9sZF92YWwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlY29yZCB0cmFuc2ZlciAoYmV0d2VlbiBwcmV2aW91cyBzdGVwIGFuZCB0aGlzIG9uZSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiBqID4gMToKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhbnNmZXJzWyhpLGotMSxmLGYyLHQpXSA9IHRyYW5zZmVycy5nZXQoKGksai0xLGYsZjIsdCksMCkgKyBvbGRfdmFsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHhmZXJfYyA9IG9sZF92YWwgKiBYRkVSX0NPU1QgKiBXS1MKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG90YWxfeGZlcl9jb3N0ICs9IHhmZXJfYwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQWxzbyBuZWVkIHRvIHRyYW5zZmVyIGJhY2sgZm9yIG5leHQgc3RlcCBpZiBuZXh0IHN0ZXAncyBXUyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGlzIGluIG9yaWdpbmFsIGZhYiAtIGhhbmRsZWQgaW4gbmV4dCBpdGVyYXRpb24KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVhawogICAgCiAgICAjIFVwZGF0ZSB0b29sIGNvdW50cyBmb3IgbmV4dCBxdWFydGVyCiAgICBmb3IgZiBpbiBmYWJzOgogICAgICAgIGZvciB3IGluIGFsbF93c19saXN0OgogICAgICAgICAgICB0b29sX2NvdW50WyhmLHcpXSA9IGN1cl90b29scy5nZXQoKGYsdyksIDApCiAgICAKICAgICMgUmVjb3JkIGZvciB0aGlzIHF1YXJ0ZXIKICAgIGZvciBmIGluIGZhYnM6CiAgICAgICAgZm9yIHcgaW4gYWxsX3dzX2xpc3Q6CiAgICAgICAgICAgIHRvb2xfY291bnRbKGYsdyx0KV0gPSBjdXJfdG9vbHMuZ2V0KChmLHcpLCAwKQogICAgCiAgICAjIFByaW50IHNwYWNlIHVzYWdlCiAgICBwcmludChmIlxuICBTcGFjZSB1c2FnZToiKQogICAgZm9yIGYgaW4gZmFiczoKICAgICAgICB1c2VkID0gZ2V0X3NwYWNlX3VzZWQoZiwgY3VyX3Rvb2xzKQogICAgICAgIHByaW50KGYiICAgIEZhYiB7Zn06IHt1c2VkOi4xZn0ve2ZhYl9zcGFjZVtmXX0gbcKyICh7dXNlZC9mYWJfc3BhY2VbZl0qMTAwOi4wZn0lKSIpCgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIE9VVFBVVDogQ09NUExFVEUgUkVTVUxUUwojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKcHJpbnQoIlxuXG4iICsgIj0iKjgwKQpwcmludCgiNi4gUEFSVCBBIFJFU1VMVFMgU1VNTUFSWSIpCnByaW50KCI9Iio4MCkKCnByaW50KGYiXG4gIFRvdGFsIENhcEV4OiAgICAgICAke3RvdGFsX2NhcGV4Oj4xNSwuMGZ9ICh7dG90YWxfY2FwZXgvMWU2Oi4xZn1NKSIpCnByaW50KGYiICBUb3RhbCBUcmFuc2ZlcjogICAgJHt0b3RhbF94ZmVyX2Nvc3Q6PjE1LC4wZn0gKHt0b3RhbF94ZmVyX2Nvc3QvMWU2Oi4xZn1NKSIpCnByaW50KGYiICBHUkFORCBUT1RBTDogICAgICAgJHt0b3RhbF9jYXBleCt0b3RhbF94ZmVyX2Nvc3Q6PjE1LC4wZn0gIgogICAgICBmIih7KHRvdGFsX2NhcGV4K3RvdGFsX3hmZXJfY29zdCkvMWU2Oi4xZn1NKSIpCgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIFRPT0wgQUxMT0NBVElPTiBUQUJMRVMKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCnByaW50KCJcbiIgKyAiPSIqODApCnByaW50KCI3LiBUT09MIEFMTE9DQVRJT04gUExBTiAoRm9yIEV4Y2VsIEFuc3dlciBUZW1wbGF0ZSkiKQpwcmludCgiPSIqODApCgpmb3IgdF9pZHgsIHQgaW4gZW51bWVyYXRlKHF1YXJ0ZXJzKToKICAgIHByaW50KGYiXG4tLS0ge3FsYWJlbHNbdF9pZHhdfSAtLS0iKQogICAgcHJpbnQoZiIgIHsnV1MnOjw0fSB7J0ZhYjEnOj42fSB7J0ZhYjInOj42fSB7J0ZhYjMnOj42fSB7J1RvdGFsJzo+Nn0geydOZXdQdXInOj43fSIpCiAgICAKICAgIGZvciB3IGluIGFsbF93c19saXN0OgogICAgICAgIHZhbHMgPSBbdG9vbF9jb3VudC5nZXQoKGYsdyx0KSwgdG9vbF9jb3VudC5nZXQoKGYsdyksMCkpIGZvciBmIGluIGZhYnNdCiAgICAgICAgdG90YWwgPSBzdW0odmFscykKICAgICAgICBuZXdfcHVyID0gc3VtKHRvb2xfcHVyY2hhc2VzLmdldCgoZix3LHQpLDApIGZvciBmIGluIGZhYnMpCiAgICAgICAgaWYgdG90YWwgPiAwIG9yIG5ld19wdXIgPiAwOgogICAgICAgICAgICBwcmludChmIiAge3c6PDR9IHt2YWxzWzBdOj42fSB7dmFsc1sxXTo+Nn0ge3ZhbHNbMl06PjZ9IHt0b3RhbDo+Nn0ge25ld19wdXI6Pjd9IikKCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgRkxPVyBESVNUUklCVVRJT04gVEFCTEVTCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgpwcmludCgiXG4iICsgIj0iKjgwKQpwcmludCgiOC4gRkxPVyBESVNUUklCVVRJT04gVEFCTEVTIChGb3IgRXhjZWwgQW5zd2VyIFRlbXBsYXRlKSIpCnByaW50KCI9Iio4MCkKCmZvciB0X2lkeCwgdCBpbiBlbnVtZXJhdGUocXVhcnRlcnMpOgogICAgcHJpbnQoZiJcbi0tLSB7cWxhYmVsc1t0X2lkeF19IC0tLSIpCiAgICAKICAgIGZvciBpIGluIG5vZGVzOgogICAgICAgIEwgPSBsb2FkaW5nWyhpLHQpXQogICAgICAgIHByaW50KGYiXG4gIE5vZGUge2l9IChMb2FkaW5nPXtMfSk6IikKICAgICAgICBwcmludChmIiAgeydTdGVwJzo+NH0geydXUyc6PjN9IiwgZW5kPSIiKQogICAgICAgIGZvciBmIGluIGZhYnM6CiAgICAgICAgICAgIHByaW50KGYiIHsnRmFiJytzdHIoZik6Pjd9IiwgZW5kPSIiKQogICAgICAgIHByaW50KGYiIHsnVG90YWwnOj43fSIpCiAgICAgICAgCiAgICAgICAgZm9yIGogaW4gcmFuZ2UoMSwgbnN0ZXBzW2ldKzEpOgogICAgICAgICAgICBtdyA9IHJlY2lwZVsoaSxqKV1bMF0KICAgICAgICAgICAgcHJpbnQoZiIgIHtqOj40fSB7bXc6PjN9IiwgZW5kPSIiKQogICAgICAgICAgICBzdGVwX3RvdGFsID0gMAogICAgICAgICAgICBmb3IgZiBpbiBmYWJzOgogICAgICAgICAgICAgICAgdiA9IGZsb3cuZ2V0KChpLGosZix0KSwgMCkKICAgICAgICAgICAgICAgIHN0ZXBfdG90YWwgKz0gdgogICAgICAgICAgICAgICAgcHJpbnQoZiIge3Y6PjcuMGZ9IiwgZW5kPSIiKQogICAgICAgICAgICBwcmludChmIiB7c3RlcF90b3RhbDo+Ny4wZn0iLCBlbmQ9IiIpCiAgICAgICAgICAgIAogICAgICAgICAgICAjIENoZWNrIGZvciB0cmFuc2ZlcnMKICAgICAgICAgICAgaWYgaiA8IG5zdGVwc1tpXToKICAgICAgICAgICAgICAgIGZvciBmMSBpbiBmYWJzOgogICAgICAgICAgICAgICAgICAgIGZvciBmMiBpbiBmYWJzOgogICAgICAgICAgICAgICAgICAgICAgICBpZiBmMSAhPSBmMjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR2ID0gdHJhbnNmZXJzLmdldCgoaSxqLGYxLGYyLHQpLCAwKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgdHYgPiAwOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW50KGYiIFtGe2YxfS0+RntmMn06e3R2Oi4wZn1dIiwgZW5kPSIiKQogICAgICAgICAgICBwcmludCgpCgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIFBVUkNIQVNFIFNDSEVEVUxFCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgpwcmludCgiXG4iICsgIj0iKjgwKQpwcmludCgiOS4gVE9PTCBQVVJDSEFTRSBTQ0hFRFVMRSIpCnByaW50KCI9Iio4MCkKCmZvciB0X2lkeCwgdCBpbiBlbnVtZXJhdGUocXVhcnRlcnMpOgogICAgYW55X3B1ciA9IEZhbHNlCiAgICBmb3IgZiBpbiBmYWJzOgogICAgICAgIGZvciB3IGluIGFsbF93c19saXN0OgogICAgICAgICAgICBwID0gdG9vbF9wdXJjaGFzZXMuZ2V0KChmLHcsdCksIDApCiAgICAgICAgICAgIGlmIHAgPiAwOgogICAgICAgICAgICAgICAgaWYgbm90IGFueV9wdXI6CiAgICAgICAgICAgICAgICAgICAgcHJpbnQoZiJcbiAge3FsYWJlbHNbdF9pZHhdfToiKQogICAgICAgICAgICAgICAgICAgIGFueV9wdXIgPSBUcnVlCiAgICAgICAgICAgICAgICBjb3N0ID0gcCAqIHdzW3ddWzFdCiAgICAgICAgICAgICAgICBwcmludChmIiAgICBGYWJ7Zn0ge3d9OiB7cH0gdG9vbHMgKCR7Y29zdDouMWZ9TSkiKQoKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBDQVBBQ0lUWSBWRVJJRklDQVRJT04KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCnByaW50KCJcbiIgKyAiPSIqODApCnByaW50KCIxMC4gQ0FQQUNJVFkgJiBERU1BTkQgVkVSSUZJQ0FUSU9OIikKcHJpbnQoIj0iKjgwKQoKYWxsX3ZlcmlmaWVkID0gVHJ1ZQpmb3IgdF9pZHgsIHQgaW4gZW51bWVyYXRlKHF1YXJ0ZXJzKToKICAgIGlzc3VlcyA9IFtdCiAgICAKICAgICMgQ2hlY2sgZWFjaCBXUyBpbiBlYWNoIGZhYgogICAgZm9yIGYgaW4gZmFiczoKICAgICAgICBmb3IgdyBpbiBtaW50ZWNoX3dzOgogICAgICAgICAgICBjbnQgPSB0b29sX2NvdW50LmdldCgoZix3LHQpLCB0b29sX2NvdW50LmdldCgoZix3KSwwKSkKICAgICAgICAgICAgY2FwID0gY250ICogTVBXICogd3Nbd11bMF0gICMgbWludXRlcyBhdmFpbGFibGUKICAgICAgICAgICAgCiAgICAgICAgICAgICMgRGVtYW5kIG9uIHRoaXMgV1MgaW4gdGhpcyBmYWIKICAgICAgICAgICAgZGVtYW5kID0gMAogICAgICAgICAgICBmb3IgaSBpbiBub2RlczoKICAgICAgICAgICAgICAgIGZvciBqIGluIHJhbmdlKDEsIG5zdGVwc1tpXSsxKToKICAgICAgICAgICAgICAgICAgICBtdyxtcix0dyx0ciA9IHJlY2lwZVsoaSxqKV0KICAgICAgICAgICAgICAgICAgICBpZiBtdyA9PSB3OgogICAgICAgICAgICAgICAgICAgICAgICBkZW1hbmQgKz0gZmxvdy5nZXQoKGksaixmLHQpLCAwKSAqIG1yCiAgICAgICAgICAgIAogICAgICAgICAgICBpZiBkZW1hbmQgPiBjYXAgKyAwLjEgYW5kIGNhcCA+IDA6CiAgICAgICAgICAgICAgICBpc3N1ZXMuYXBwZW5kKGYiICBGYWJ7Zn0ge3d9OiBkZW1hbmQ9e2RlbWFuZDouMGZ9bWluID4gY2FwPXtjYXA6LjBmfW1pbiIpCiAgICAgICAgICAgIGVsaWYgZGVtYW5kID4gMCBhbmQgY2FwID09IDA6CiAgICAgICAgICAgICAgICAjIENoZWNrIGlmIFRPUiBoYW5kbGVzIGl0CiAgICAgICAgICAgICAgICB0b3JfdyA9IG0ydFt3XQogICAgICAgICAgICAgICAgY250X3QgPSB0b29sX2NvdW50LmdldCgoZix0b3Jfdyx0KSwgdG9vbF9jb3VudC5nZXQoKGYsdG9yX3cpLDApKQogICAgICAgICAgICAgICAgaWYgY250X3QgPT0gMDoKICAgICAgICAgICAgICAgICAgICBpc3N1ZXMuYXBwZW5kKGYiICBGYWJ7Zn0ge3d9OiBkZW1hbmQ9e2RlbWFuZDouMGZ9bWluIGJ1dCBOTyB0b29scyEiKQogICAgCiAgICAjIENoZWNrIGRlbWFuZCBmdWxmaWxsbWVudAogICAgZm9yIGkgaW4gbm9kZXM6CiAgICAgICAgTCA9IGxvYWRpbmdbKGksdCldCiAgICAgICAgZm9yIGogaW4gcmFuZ2UoMSwgbnN0ZXBzW2ldKzEpOgogICAgICAgICAgICB0b3RhbF9hc3NpZ25lZCA9IHN1bShmbG93LmdldCgoaSxqLGYsdCksMCkgZm9yIGYgaW4gZmFicykKICAgICAgICAgICAgaWYgYWJzKHRvdGFsX2Fzc2lnbmVkIC0gTCkgPiAwLjU6CiAgICAgICAgICAgICAgICBpc3N1ZXMuYXBwZW5kKGYiICBOb2Rle2l9IFN0ZXB7an06IGFzc2lnbmVkPXt0b3RhbF9hc3NpZ25lZDouMGZ9ICE9IGxvYWRpbmc9e0x9IikKICAgIAogICAgaWYgaXNzdWVzOgogICAgICAgIHByaW50KGYiXG4gIHtxbGFiZWxzW3RfaWR4XX06IElTU1VFUyBGT1VORCIpCiAgICAgICAgZm9yIGlzcyBpbiBpc3N1ZXM6CiAgICAgICAgICAgIHByaW50KGlzcykKICAgICAgICBhbGxfdmVyaWZpZWQgPSBGYWxzZQogICAgZWxzZToKICAgICAgICBwcmludChmIiAge3FsYWJlbHNbdF9pZHhdfTogT0siKQoKaWYgYWxsX3ZlcmlmaWVkOgogICAgcHJpbnQoIlxuICBBTEwgUVVBUlRFUlMgVkVSSUZJRUQgU1VDQ0VTU0ZVTExZISIpCmVsc2U6CiAgICBwcmludCgiXG4gIFNPTUUgSVNTVUVTIEZPVU5EIC0gUmV2aWV3IG5lZWRlZCIpCgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIFNQQUNFIFZFUklGSUNBVElPTgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKcHJpbnQoIlxuIiArICI9Iio4MCkKcHJpbnQoIjExLiBTUEFDRSBWRVJJRklDQVRJT04iKQpwcmludCgiPSIqODApCgpmb3IgdF9pZHgsIHQgaW4gZW51bWVyYXRlKHF1YXJ0ZXJzKToKICAgIGxpbmUgPSBmIiAge3FsYWJlbHNbdF9pZHhdfTogIgogICAgYWxsX29rID0gVHJ1ZQogICAgZm9yIGYgaW4gZmFiczoKICAgICAgICB1c2VkID0gc3VtKHRvb2xfY291bnQuZ2V0KChmLHcsdCksIHRvb2xfY291bnQuZ2V0KChmLHcpLDApKSAqIHdzW3ddWzJdIAogICAgICAgICAgICAgICAgICAgZm9yIHcgaW4gYWxsX3dzX2xpc3QpCiAgICAgICAgb2sgPSB1c2VkIDw9IGZhYl9zcGFjZVtmXSArIDAuMDEKICAgICAgICBpZiBub3Qgb2s6IGFsbF9vayA9IEZhbHNlCiAgICAgICAgbGluZSArPSBmIkZ7Zn09e3VzZWQ6LjBmfS97ZmFiX3NwYWNlW2ZdfW3CsiAiCiAgICBsaW5lICs9ICJPSyIgaWYgYWxsX29rIGVsc2UgIkVYQ0VFREVEISIKICAgIHByaW50KGxpbmUpCgpwcmludCgiXG4iICsgIj0iKjgwKQpwcmludCgiU09MVVRJT04gQ09NUExFVEUiKQpwcmludCgiPSIqODApCg==