simple tree walk
This commit is contained in:
@@ -10,23 +10,28 @@ import tqdm
|
|||||||
# 1. CORE TYPES & ENUMS
|
# 1. CORE TYPES & ENUMS
|
||||||
# ==========================================
|
# ==========================================
|
||||||
|
|
||||||
|
|
||||||
class Edge(IntEnum):
|
class Edge(IntEnum):
|
||||||
RISING = 0
|
RISING = 0
|
||||||
FALLING_PENDING = 1
|
FALLING_PENDING = 1
|
||||||
FALLING_UTILIZED = 2
|
FALLING_UTILIZED = 2
|
||||||
|
|
||||||
|
|
||||||
class Context(IntEnum):
|
class Context(IntEnum):
|
||||||
OTHER = 0
|
OTHER = 0
|
||||||
WIRE = 1
|
WIRE = 1
|
||||||
DIODE = 2
|
DIODE = 2
|
||||||
|
|
||||||
|
|
||||||
START_STATE = "START"
|
START_STATE = "START"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Entry:
|
class Entry:
|
||||||
priority: int
|
priority: int
|
||||||
delay: int
|
delay: int
|
||||||
name: str = '-'
|
name: str = "-"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Transition:
|
class Transition:
|
||||||
@@ -37,8 +42,14 @@ class Transition:
|
|||||||
def total_delay(self) -> int:
|
def total_delay(self) -> int:
|
||||||
return sum(e.delay for e in self.entries)
|
return sum(e.delay for e in self.entries)
|
||||||
|
|
||||||
|
|
||||||
class TilesetFSM:
|
class TilesetFSM:
|
||||||
def __init__(self, transitions: List[Transition], start_state: str, accept_states: Set[Union[Tuple[Edge, Context], str]]):
|
def __init__(
|
||||||
|
self,
|
||||||
|
transitions: List[Transition],
|
||||||
|
start_state: str,
|
||||||
|
accept_states: Set[Union[Tuple[Edge, Context], str]],
|
||||||
|
):
|
||||||
self.transitions = transitions
|
self.transitions = transitions
|
||||||
self.start_state = start_state
|
self.start_state = start_state
|
||||||
self.accept_states = accept_states
|
self.accept_states = accept_states
|
||||||
@@ -53,6 +64,7 @@ class TilesetFSM:
|
|||||||
# 2. EVENT-DRIVEN SORTING (Numba-Ready)
|
# 2. EVENT-DRIVEN SORTING (Numba-Ready)
|
||||||
# ==========================================
|
# ==========================================
|
||||||
|
|
||||||
|
|
||||||
def get_timing_events(chain: Tuple[Entry, ...]) -> Tuple[Tuple[int, int], ...]:
|
def get_timing_events(chain: Tuple[Entry, ...]) -> Tuple[Tuple[int, int], ...]:
|
||||||
"""Converts a chain into a dense tuple of (tick, priority) events."""
|
"""Converts a chain into a dense tuple of (tick, priority) events."""
|
||||||
events = []
|
events = []
|
||||||
@@ -63,6 +75,7 @@ def get_timing_events(chain: Tuple[Entry, ...]) -> Tuple[Tuple[int, int], ...]:
|
|||||||
tick += e.delay
|
tick += e.delay
|
||||||
return tuple(events)
|
return tuple(events)
|
||||||
|
|
||||||
|
|
||||||
def cmp_chains(chain_a: Tuple[Entry, ...], chain_b: Tuple[Entry, ...]) -> int:
|
def cmp_chains(chain_a: Tuple[Entry, ...], chain_b: Tuple[Entry, ...]) -> int:
|
||||||
"""
|
"""
|
||||||
O(K) lexicographical comparison replicating the behavior of `-inf` padding.
|
O(K) lexicographical comparison replicating the behavior of `-inf` padding.
|
||||||
@@ -77,8 +90,10 @@ def cmp_chains(chain_a: Tuple[Entry, ...], chain_b: Tuple[Entry, ...]) -> int:
|
|||||||
tick_b, pri_b = events_b[idx_b]
|
tick_b, pri_b = events_b[idx_b]
|
||||||
|
|
||||||
if tick_a == tick_b:
|
if tick_a == tick_b:
|
||||||
if pri_a < pri_b: return -1
|
if pri_a < pri_b:
|
||||||
if pri_a > pri_b: return 1
|
return -1
|
||||||
|
if pri_a > pri_b:
|
||||||
|
return 1
|
||||||
idx_a += 1
|
idx_a += 1
|
||||||
idx_b += 1
|
idx_b += 1
|
||||||
elif tick_a < tick_b:
|
elif tick_a < tick_b:
|
||||||
@@ -87,12 +102,16 @@ def cmp_chains(chain_a: Tuple[Entry, ...], chain_b: Tuple[Entry, ...]) -> int:
|
|||||||
else:
|
else:
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
if idx_a < len(events_a): return 1
|
if idx_a < len(events_a):
|
||||||
if idx_b < len(events_b): return -1
|
return 1
|
||||||
|
if idx_b < len(events_b):
|
||||||
|
return -1
|
||||||
|
|
||||||
# Tie-breaker on component count (fewer components is preferred/smaller)
|
# Tie-breaker on component count (fewer components is preferred/smaller)
|
||||||
if len(chain_a) < len(chain_b): return -1
|
if len(chain_a) < len(chain_b):
|
||||||
if len(chain_a) > len(chain_b): return 1
|
return -1
|
||||||
|
if len(chain_a) > len(chain_b):
|
||||||
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -108,55 +127,91 @@ for edge in Edge:
|
|||||||
|
|
||||||
# A. Wire
|
# A. Wire
|
||||||
if ctx == Context.DIODE:
|
if ctx == Context.DIODE:
|
||||||
transitions.append(Transition(state, (edge, Context.WIRE), (Entry(0, 0, '---'),)))
|
transitions.append(
|
||||||
|
Transition(state, (edge, Context.WIRE), (Entry(0, 0, "---"),))
|
||||||
|
)
|
||||||
|
|
||||||
# B. Torch
|
# B. Torch
|
||||||
if ctx == Context.DIODE:
|
if ctx == Context.DIODE:
|
||||||
if edge == Edge.RISING:
|
if edge == Edge.RISING:
|
||||||
transitions.append(Transition(state, (Edge.FALLING_PENDING, Context.OTHER), (Entry(0, 2, 'torch '),)))
|
transitions.append(
|
||||||
|
Transition(
|
||||||
|
state,
|
||||||
|
(Edge.FALLING_PENDING, Context.OTHER),
|
||||||
|
(Entry(0, 2, "torch "),),
|
||||||
|
)
|
||||||
|
)
|
||||||
elif edge == Edge.FALLING_UTILIZED:
|
elif edge == Edge.FALLING_UTILIZED:
|
||||||
transitions.append(Transition(state, (Edge.RISING, Context.OTHER), (Entry(0, 2, 'torch '),)))
|
transitions.append(
|
||||||
|
Transition(
|
||||||
|
state, (Edge.RISING, Context.OTHER), (Entry(0, 2, "torch "),)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# C. Comparator
|
# C. Comparator
|
||||||
cmp_pri = -1 if ctx == Context.DIODE else 0
|
cmp_pri = -1 if ctx == Context.DIODE else 0
|
||||||
transitions.append(Transition(state, (edge, Context.DIODE), (Entry(cmp_pri, 2, 'cmp'),)))
|
transitions.append(
|
||||||
|
Transition(state, (edge, Context.DIODE), (Entry(cmp_pri, 2, "cmp"),))
|
||||||
|
)
|
||||||
|
|
||||||
# D. Repeaters
|
# D. Repeaters
|
||||||
for d in (2, 4, 6, 8):
|
for d in (2, 4, 6, 8):
|
||||||
if ctx == Context.DIODE:
|
if ctx == Context.DIODE:
|
||||||
transitions.append(Transition(state, (edge, Context.DIODE), (Entry(-3, d, f're{d}'),)))
|
transitions.append(
|
||||||
|
Transition(state, (edge, Context.DIODE), (Entry(-3, d, f"re{d}"),))
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if edge == Edge.RISING:
|
if edge == Edge.RISING:
|
||||||
if ctx != Context.WIRE:
|
if ctx != Context.WIRE:
|
||||||
transitions.append(Transition(state, (edge, Context.DIODE), (Entry(-1, d, f're{d}'),)))
|
transitions.append(
|
||||||
|
Transition(
|
||||||
|
state, (edge, Context.DIODE), (Entry(-1, d, f"re{d}"),)
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
transitions.append(Transition(state, (Edge.FALLING_UTILIZED, Context.DIODE), (Entry(-2, d, f're{d}'),)))
|
transitions.append(
|
||||||
|
Transition(
|
||||||
|
state,
|
||||||
|
(Edge.FALLING_UTILIZED, Context.DIODE),
|
||||||
|
(Entry(-2, d, f"re{d}"),),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# E. Fluids
|
# E. Fluids
|
||||||
if edge == Edge.RISING:
|
if edge == Edge.RISING:
|
||||||
for d in (5, 10, 30):
|
for d in (5, 10, 30):
|
||||||
fluid_macro = (Entry(0, 2, 'obs'), Entry(1, d, f'fluid{d}'), Entry(0, 4, 'disp'))
|
fluid_macro = (
|
||||||
transitions.append(Transition(state, (Edge.RISING, Context.OTHER), fluid_macro))
|
Entry(0, 2, "obs"),
|
||||||
|
Entry(1, d, f"fluid{d}"),
|
||||||
|
Entry(0, 4, "disp"),
|
||||||
|
)
|
||||||
|
transitions.append(
|
||||||
|
Transition(state, (Edge.RISING, Context.OTHER), fluid_macro)
|
||||||
|
)
|
||||||
|
|
||||||
# START state boundary
|
# START state boundary
|
||||||
start_transitions = []
|
start_transitions = []
|
||||||
for t in transitions:
|
for t in transitions:
|
||||||
if t.from_state == (Edge.RISING, Context.OTHER):
|
if t.from_state == (Edge.RISING, Context.OTHER):
|
||||||
if not (len(t.entries) == 1 and t.entries[0].name == '---'):
|
if not (len(t.entries) == 1 and t.entries[0].name == "---"):
|
||||||
start_transitions.append(Transition(START_STATE, t.to_state, t.entries))
|
start_transitions.append(Transition(START_STATE, t.to_state, t.entries))
|
||||||
|
|
||||||
start_transitions.append(Transition(START_STATE, (Edge.FALLING_PENDING, Context.OTHER), (Entry(0, 2, 'torch '),)))
|
start_transitions.append(
|
||||||
|
Transition(
|
||||||
|
START_STATE, (Edge.FALLING_PENDING, Context.OTHER), (Entry(0, 2, "torch "),)
|
||||||
|
)
|
||||||
|
)
|
||||||
transitions.extend(start_transitions)
|
transitions.extend(start_transitions)
|
||||||
|
|
||||||
all_states = set(t.from_state for t in transitions) | set(t.to_state for t in transitions)
|
all_states = set(t.from_state for t in transitions) | set(
|
||||||
|
t.to_state for t in transitions
|
||||||
|
)
|
||||||
|
|
||||||
# Accept states are any normal state tuple (skip START_STATE string)
|
# Accept states are any normal state tuple (skip START_STATE string)
|
||||||
accept_states = {s for s in all_states if isinstance(s, tuple) and s[0] == Edge.RISING}
|
accept_states = {s for s in all_states if isinstance(s, tuple) and s[0] == Edge.RISING}
|
||||||
|
|
||||||
model = TilesetFSM(
|
model = TilesetFSM(
|
||||||
transitions=transitions,
|
transitions=transitions, start_state=START_STATE, accept_states=accept_states
|
||||||
start_state=START_STATE,
|
|
||||||
accept_states=accept_states
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -164,6 +219,7 @@ model = TilesetFSM(
|
|||||||
# 4. FAST DP GENERATOR
|
# 4. FAST DP GENERATOR
|
||||||
# ==========================================
|
# ==========================================
|
||||||
|
|
||||||
|
|
||||||
def generate_all_fast(model: TilesetFSM, target_delay: int) -> List[List[Entry]]:
|
def generate_all_fast(model: TilesetFSM, target_delay: int) -> List[List[Entry]]:
|
||||||
all_states = set(model.adj_list.keys()) | model.accept_states
|
all_states = set(model.adj_list.keys()) | model.accept_states
|
||||||
can_reach = {d: {s: False for s in all_states} for d in range(target_delay + 1)}
|
can_reach = {d: {s: False for s in all_states} for d in range(target_delay + 1)}
|
||||||
@@ -206,7 +262,10 @@ def generate_all_fast(model: TilesetFSM, target_delay: int) -> List[List[Entry]]
|
|||||||
return [list(c) for c in sorted_chains]
|
return [list(c) for c in sorted_chains]
|
||||||
|
|
||||||
|
|
||||||
def cmp_prefix(events_prefix: Tuple[Tuple[int, int], ...], events_target: Tuple[Tuple[int, int], ...]) -> int:
|
def cmp_prefix(
|
||||||
|
events_prefix: Tuple[Tuple[int, int], ...],
|
||||||
|
events_target: Tuple[Tuple[int, int], ...],
|
||||||
|
) -> int:
|
||||||
"""
|
"""
|
||||||
Evaluates prefix divergence. Returns 0 if they match exactly so far.
|
Evaluates prefix divergence. Returns 0 if they match exactly so far.
|
||||||
If it returns non-zero, the divergence is permanent for ANY valid completion.
|
If it returns non-zero, the divergence is permanent for ANY valid completion.
|
||||||
@@ -217,8 +276,10 @@ def cmp_prefix(events_prefix: Tuple[Tuple[int, int], ...], events_target: Tuple[
|
|||||||
tick_t, pri_t = events_target[idx_t]
|
tick_t, pri_t = events_target[idx_t]
|
||||||
|
|
||||||
if tick_p == tick_t:
|
if tick_p == tick_t:
|
||||||
if pri_p < pri_t: return -1
|
if pri_p < pri_t:
|
||||||
if pri_p > pri_t: return 1
|
return -1
|
||||||
|
if pri_p > pri_t:
|
||||||
|
return 1
|
||||||
idx_p += 1
|
idx_p += 1
|
||||||
idx_t += 1
|
idx_t += 1
|
||||||
elif tick_p < tick_t:
|
elif tick_p < tick_t:
|
||||||
@@ -228,7 +289,9 @@ def cmp_prefix(events_prefix: Tuple[Tuple[int, int], ...], events_target: Tuple[
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def neighborhood(model: TilesetFSM, target_chain: List[Entry], before: int, after: int) -> Tuple[List[List[Entry]], List[List[Entry]]]:
|
def neighborhood(
|
||||||
|
model: TilesetFSM, target_chain: List[Entry], before: int, after: int
|
||||||
|
) -> Tuple[List[List[Entry]], List[List[Entry]]]:
|
||||||
"""
|
"""
|
||||||
Finds the exact immediate neighborhood around a target chain without enumerating the language.
|
Finds the exact immediate neighborhood around a target chain without enumerating the language.
|
||||||
Uses DP-guided Branch and Bound to prune the astronomical search space.
|
Uses DP-guided Branch and Bound to prune the astronomical search space.
|
||||||
@@ -239,7 +302,8 @@ def neighborhood(model: TilesetFSM, target_chain: List[Entry], before: int, afte
|
|||||||
# 1. Build the Reachability Table (O(States * Target Delay) - Extremely Fast)
|
# 1. Build the Reachability Table (O(States * Target Delay) - Extremely Fast)
|
||||||
all_states = set(model.adj_list.keys()) | model.accept_states
|
all_states = set(model.adj_list.keys()) | model.accept_states
|
||||||
can_reach = {d: {s: False for s in all_states} for d in range(target_delay + 1)}
|
can_reach = {d: {s: False for s in all_states} for d in range(target_delay + 1)}
|
||||||
for s in model.accept_states: can_reach[0][s] = True
|
for s in model.accept_states:
|
||||||
|
can_reach[0][s] = True
|
||||||
for d in range(1, target_delay + 1):
|
for d in range(1, target_delay + 1):
|
||||||
for state in all_states:
|
for state in all_states:
|
||||||
for t in model.adj_list.get(state, []):
|
for t in model.adj_list.get(state, []):
|
||||||
@@ -252,9 +316,14 @@ def neighborhood(model: TilesetFSM, target_chain: List[Entry], before: int, afte
|
|||||||
return cmp_chains(t1.entries, t2.entries)
|
return cmp_chains(t1.entries, t2.entries)
|
||||||
|
|
||||||
# Ascending sort: Explores lexicographically smaller transitions first
|
# Ascending sort: Explores lexicographically smaller transitions first
|
||||||
adj_list_asc = {k: sorted(v, key=cmp_to_key(cmp_transitions)) for k, v in model.adj_list.items()}
|
adj_list_asc = {
|
||||||
|
k: sorted(v, key=cmp_to_key(cmp_transitions)) for k, v in model.adj_list.items()
|
||||||
|
}
|
||||||
# Descending sort: Explores lexicographically larger transitions first
|
# Descending sort: Explores lexicographically larger transitions first
|
||||||
adj_list_desc = {k: sorted(v, key=cmp_to_key(cmp_transitions), reverse=True) for k, v in model.adj_list.items()}
|
adj_list_desc = {
|
||||||
|
k: sorted(v, key=cmp_to_key(cmp_transitions), reverse=True)
|
||||||
|
for k, v in model.adj_list.items()
|
||||||
|
}
|
||||||
|
|
||||||
# --- SEARCH AFTER ---
|
# --- SEARCH AFTER ---
|
||||||
found_after = {}
|
found_after = {}
|
||||||
@@ -272,23 +341,36 @@ def neighborhood(model: TilesetFSM, target_chain: List[Entry], before: int, afte
|
|||||||
found_after[ev_key] = chain
|
found_after[ev_key] = chain
|
||||||
|
|
||||||
if len(found_after) > after:
|
if len(found_after) > after:
|
||||||
sorted_items = sorted(found_after.values(), key=cmp_to_key(cmp_chains))
|
sorted_items = sorted(
|
||||||
found_after = {get_timing_events(c): c for c in sorted_items[:after]}
|
found_after.values(), key=cmp_to_key(cmp_chains)
|
||||||
|
)
|
||||||
|
found_after = {
|
||||||
|
get_timing_events(c): c for c in sorted_items[:after]
|
||||||
|
}
|
||||||
ub_chain = sorted_items[after - 1]
|
ub_chain = sorted_items[after - 1]
|
||||||
ub_events = get_timing_events(ub_chain)
|
ub_events = get_timing_events(ub_chain)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Prune branches that are <= target or > our worst accepted bound
|
# Prune branches that are <= target or > our worst accepted bound
|
||||||
div_t = cmp_prefix(events_so_far, events_target)
|
div_t = cmp_prefix(events_so_far, events_target)
|
||||||
if div_t < 0: continue
|
if div_t < 0:
|
||||||
if ub_events and cmp_prefix(events_so_far, ub_events) > 0: continue
|
continue
|
||||||
|
if ub_events and cmp_prefix(events_so_far, ub_events) > 0:
|
||||||
|
continue
|
||||||
|
|
||||||
# Push in reverse so the smallest transitions are popped/explored first
|
# Push in reverse so the smallest transitions are popped/explored first
|
||||||
for t in reversed(adj_list_asc.get(curr_state, [])):
|
for t in reversed(adj_list_asc.get(curr_state, [])):
|
||||||
t_delay = t.total_delay()
|
t_delay = t.total_delay()
|
||||||
if t_delay <= rem_delay and can_reach[rem_delay - t_delay][t.to_state]:
|
if t_delay <= rem_delay and can_reach[rem_delay - t_delay][t.to_state]:
|
||||||
new_chain = chain + t.entries
|
new_chain = chain + t.entries
|
||||||
stack.append((t.to_state, rem_delay - t_delay, new_chain, get_timing_events(new_chain)))
|
stack.append(
|
||||||
|
(
|
||||||
|
t.to_state,
|
||||||
|
rem_delay - t_delay,
|
||||||
|
new_chain,
|
||||||
|
get_timing_events(new_chain),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# --- SEARCH BEFORE ---
|
# --- SEARCH BEFORE ---
|
||||||
found_before = {}
|
found_before = {}
|
||||||
@@ -305,29 +387,43 @@ def neighborhood(model: TilesetFSM, target_chain: List[Entry], before: int, afte
|
|||||||
found_before[ev_key] = chain
|
found_before[ev_key] = chain
|
||||||
|
|
||||||
if len(found_before) > before:
|
if len(found_before) > before:
|
||||||
sorted_items = sorted(found_before.values(), key=cmp_to_key(cmp_chains))
|
sorted_items = sorted(
|
||||||
found_before = {get_timing_events(c): c for c in sorted_items[-before:]}
|
found_before.values(), key=cmp_to_key(cmp_chains)
|
||||||
|
)
|
||||||
|
found_before = {
|
||||||
|
get_timing_events(c): c for c in sorted_items[-before:]
|
||||||
|
}
|
||||||
lb_chain = sorted_items[-before]
|
lb_chain = sorted_items[-before]
|
||||||
lb_events = get_timing_events(lb_chain)
|
lb_events = get_timing_events(lb_chain)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Prune branches that are >= target or < our worst accepted bound
|
# Prune branches that are >= target or < our worst accepted bound
|
||||||
div_t = cmp_prefix(events_so_far, events_target)
|
div_t = cmp_prefix(events_so_far, events_target)
|
||||||
if div_t > 0: continue
|
if div_t > 0:
|
||||||
if lb_events and cmp_prefix(events_so_far, lb_events) < 0: continue
|
continue
|
||||||
|
if lb_events and cmp_prefix(events_so_far, lb_events) < 0:
|
||||||
|
continue
|
||||||
|
|
||||||
# Push in reverse so the largest transitions are popped/explored first
|
# Push in reverse so the largest transitions are popped/explored first
|
||||||
for t in reversed(adj_list_desc.get(curr_state, [])):
|
for t in reversed(adj_list_desc.get(curr_state, [])):
|
||||||
t_delay = t.total_delay()
|
t_delay = t.total_delay()
|
||||||
if t_delay <= rem_delay and can_reach[rem_delay - t_delay][t.to_state]:
|
if t_delay <= rem_delay and can_reach[rem_delay - t_delay][t.to_state]:
|
||||||
new_chain = chain + t.entries
|
new_chain = chain + t.entries
|
||||||
stack.append((t.to_state, rem_delay - t_delay, new_chain, get_timing_events(new_chain)))
|
stack.append(
|
||||||
|
(
|
||||||
|
t.to_state,
|
||||||
|
rem_delay - t_delay,
|
||||||
|
new_chain,
|
||||||
|
get_timing_events(new_chain),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
sorted_before = sorted(found_before.values(), key=cmp_to_key(cmp_chains))
|
sorted_before = sorted(found_before.values(), key=cmp_to_key(cmp_chains))
|
||||||
sorted_after = sorted(found_after.values(), key=cmp_to_key(cmp_chains))
|
sorted_after = sorted(found_after.values(), key=cmp_to_key(cmp_chains))
|
||||||
|
|
||||||
return [list(c) for c in sorted_before], [list(c) for c in sorted_after]
|
return [list(c) for c in sorted_before], [list(c) for c in sorted_after]
|
||||||
|
|
||||||
|
|
||||||
def format_csv_timing(chain: List[Entry], target_delay: int) -> str:
|
def format_csv_timing(chain: List[Entry], target_delay: int) -> str:
|
||||||
"""Helper to reconstruct your original CSV string format without allocating infs."""
|
"""Helper to reconstruct your original CSV string format without allocating infs."""
|
||||||
events = dict(get_timing_events(tuple(chain)))
|
events = dict(get_timing_events(tuple(chain)))
|
||||||
@@ -374,8 +470,10 @@ def parse_names(model: TilesetFSM, names: List[str]) -> List[Entry]:
|
|||||||
chunk_norm = []
|
chunk_norm = []
|
||||||
for n in chunk:
|
for n in chunk:
|
||||||
n = n.strip()
|
n = n.strip()
|
||||||
if n == 'wire': n = '---'
|
if n == "wire":
|
||||||
if n.startswith('rep'): n = n.replace('rep', 're')
|
n = "---"
|
||||||
|
if n.startswith("rep"):
|
||||||
|
n = n.replace("rep", "re")
|
||||||
chunk_norm.append(n)
|
chunk_norm.append(n)
|
||||||
|
|
||||||
# Check if this transition matches the input
|
# Check if this transition matches the input
|
||||||
@@ -400,13 +498,14 @@ def parse_names(model: TilesetFSM, names: List[str]) -> List[Entry]:
|
|||||||
|
|
||||||
return inferred_chain
|
return inferred_chain
|
||||||
|
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# USAGE EXAMPLE
|
# USAGE EXAMPLE
|
||||||
# ==========================================
|
# ==========================================
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Your target sequence (using friendly names!)
|
# Your target sequence (using friendly names!)
|
||||||
input_str = 'cmp rep2 torch rep2 torch'
|
input_str = "cmp rep2 torch rep2 torch"
|
||||||
names_list = input_str.split()
|
names_list = input_str.split()
|
||||||
|
|
||||||
print(f"Parsing: {names_list}...")
|
print(f"Parsing: {names_list}...")
|
||||||
|
|||||||
174
finite_state.py
174
finite_state.py
@@ -9,13 +9,14 @@ import tqdm
|
|||||||
class Entry:
|
class Entry:
|
||||||
priority: int
|
priority: int
|
||||||
delay: int
|
delay: int
|
||||||
name: str = '-'
|
name: str = "-"
|
||||||
|
|
||||||
def linear_parts(self) -> Tuple[int | float, ...]:
|
def linear_parts(self) -> Tuple[int | float, ...]:
|
||||||
if self.delay == 0:
|
if self.delay == 0:
|
||||||
return ()
|
return ()
|
||||||
return (self.priority,) + (-inf,) * (self.delay - 1)
|
return (self.priority,) + (-inf,) * (self.delay - 1)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Transition:
|
class Transition:
|
||||||
from_state: str
|
from_state: str
|
||||||
@@ -28,8 +29,11 @@ class Transition:
|
|||||||
def total_delay(self) -> int:
|
def total_delay(self) -> int:
|
||||||
return sum(e.delay for e in self.entries)
|
return sum(e.delay for e in self.entries)
|
||||||
|
|
||||||
|
|
||||||
class TilesetFSM:
|
class TilesetFSM:
|
||||||
def __init__(self, transitions: List[Transition], start_state: str, accept_states: Set[str]):
|
def __init__(
|
||||||
|
self, transitions: List[Transition], start_state: str, accept_states: Set[str]
|
||||||
|
):
|
||||||
self.transitions = transitions
|
self.transitions = transitions
|
||||||
self.start_state = start_state
|
self.start_state = start_state
|
||||||
self.accept_states = accept_states
|
self.accept_states = accept_states
|
||||||
@@ -39,22 +43,30 @@ class TilesetFSM:
|
|||||||
for t in transitions:
|
for t in transitions:
|
||||||
self.adj_list[t.from_state].append(t)
|
self.adj_list[t.from_state].append(t)
|
||||||
|
|
||||||
def _check_divergence(self, prefix: Tuple[int, ...], target: Tuple[int, ...]) -> int:
|
def _check_divergence(
|
||||||
|
self, prefix: Tuple[int, ...], target: Tuple[int, ...]
|
||||||
|
) -> int:
|
||||||
"""
|
"""
|
||||||
Returns -1 if prefix is lexicographically < target,
|
Returns -1 if prefix is lexicographically < target,
|
||||||
1 if prefix is lexicographically > target,
|
1 if prefix is lexicographically > target,
|
||||||
0 if prefix is a strict prefix of target (no divergence yet).
|
0 if prefix is a strict prefix of target (no divergence yet).
|
||||||
"""
|
"""
|
||||||
for p_val, t_val in zip(prefix, target):
|
for p_val, t_val in zip(prefix, target):
|
||||||
if p_val < t_val: return -1
|
if p_val < t_val:
|
||||||
if p_val > t_val: return 1
|
return -1
|
||||||
|
if p_val > t_val:
|
||||||
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def find_next(self, current_lin: Tuple[int, ...], target_delay: int) -> Optional[List[Entry]]:
|
def find_next(
|
||||||
|
self, current_lin: Tuple[int, ...], target_delay: int
|
||||||
|
) -> Optional[List[Entry]]:
|
||||||
best_next_chain = None
|
best_next_chain = None
|
||||||
best_next_lin = None
|
best_next_lin = None
|
||||||
|
|
||||||
def dfs(chain_so_far: Tuple[Transition, ...], delay_so_far: int, current_state: str):
|
def dfs(
|
||||||
|
chain_so_far: Tuple[Transition, ...], delay_so_far: int, current_state: str
|
||||||
|
):
|
||||||
nonlocal best_next_chain, best_next_lin
|
nonlocal best_next_chain, best_next_lin
|
||||||
|
|
||||||
# Base Case: Exact delay reached AND machine is in an accepting state
|
# Base Case: Exact delay reached AND machine is in an accepting state
|
||||||
@@ -73,8 +85,10 @@ class TilesetFSM:
|
|||||||
p_lin = tuple(p for t in chain_so_far for p in t.linearize())
|
p_lin = tuple(p for t in chain_so_far for p in t.linearize())
|
||||||
|
|
||||||
# Prefix Pruning
|
# Prefix Pruning
|
||||||
if self._check_divergence(p_lin, current_lin) == -1: return
|
if self._check_divergence(p_lin, current_lin) == -1:
|
||||||
if best_next_lin and self._check_divergence(p_lin, best_next_lin) == 1: return
|
return
|
||||||
|
if best_next_lin and self._check_divergence(p_lin, best_next_lin) == 1:
|
||||||
|
return
|
||||||
|
|
||||||
for t in self.adj_list.get(current_state, []):
|
for t in self.adj_list.get(current_state, []):
|
||||||
dfs(chain_so_far + (t,), delay_so_far + t.total_delay(), t.to_state)
|
dfs(chain_so_far + (t,), delay_so_far + t.total_delay(), t.to_state)
|
||||||
@@ -137,37 +151,45 @@ for signal in signals:
|
|||||||
|
|
||||||
# A. Wire
|
# A. Wire
|
||||||
if context == "DIODE":
|
if context == "DIODE":
|
||||||
transitions.append(Transition(
|
transitions.append(
|
||||||
from_state=current_state,
|
Transition(
|
||||||
to_state=f"{signal}_WIRE",
|
from_state=current_state,
|
||||||
entries=(Entry(0, 0, '---'),)
|
to_state=f"{signal}_WIRE",
|
||||||
))
|
entries=(Entry(0, 0, "---"),),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# B. Torch
|
# B. Torch
|
||||||
if context == "DIODE":
|
if context == "DIODE":
|
||||||
if signal == "NORMAL":
|
if signal == "NORMAL":
|
||||||
# Enter inverted mode as PENDING (haven't used -2 yet)
|
# Enter inverted mode as PENDING (haven't used -2 yet)
|
||||||
transitions.append(Transition(
|
transitions.append(
|
||||||
from_state=current_state,
|
Transition(
|
||||||
to_state="INV_PENDING_OTHER",
|
from_state=current_state,
|
||||||
entries=(Entry(0, 2, 'torch '),)
|
to_state="INV_PENDING_OTHER",
|
||||||
))
|
entries=(Entry(0, 2, "torch "),),
|
||||||
|
)
|
||||||
|
)
|
||||||
elif signal == "INV_UTILIZED":
|
elif signal == "INV_UTILIZED":
|
||||||
# Un-invert is ONLY allowed if we successfully utilized the -2 priority
|
# Un-invert is ONLY allowed if we successfully utilized the -2 priority
|
||||||
transitions.append(Transition(
|
transitions.append(
|
||||||
from_state=current_state,
|
Transition(
|
||||||
to_state="NORMAL_OTHER",
|
from_state=current_state,
|
||||||
entries=(Entry(0, 2, 'torch '),)
|
to_state="NORMAL_OTHER",
|
||||||
))
|
entries=(Entry(0, 2, "torch "),),
|
||||||
|
)
|
||||||
|
)
|
||||||
# Notice there is no torch transition for INV_PENDING!
|
# Notice there is no torch transition for INV_PENDING!
|
||||||
|
|
||||||
# C. Comparator
|
# C. Comparator
|
||||||
cmp_pri = -1 if context == "DIODE" else 0
|
cmp_pri = -1 if context == "DIODE" else 0
|
||||||
transitions.append(Transition(
|
transitions.append(
|
||||||
from_state=current_state,
|
Transition(
|
||||||
to_state=f"{signal}_DIODE",
|
from_state=current_state,
|
||||||
entries=(Entry(cmp_pri, 2, 'cmp'),)
|
to_state=f"{signal}_DIODE",
|
||||||
))
|
entries=(Entry(cmp_pri, 2, "cmp"),),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# D. Repeaters
|
# D. Repeaters
|
||||||
for d in (2, 4, 6, 8):
|
for d in (2, 4, 6, 8):
|
||||||
@@ -180,30 +202,38 @@ for signal in signals:
|
|||||||
if signal == "NORMAL":
|
if signal == "NORMAL":
|
||||||
rep_pri = -1
|
rep_pri = -1
|
||||||
next_signal = signal
|
next_signal = signal
|
||||||
allow_rep = (context != "WIRE") # Block redundant rep after
|
allow_rep = context != "WIRE" # Block redundant rep after
|
||||||
else:
|
else:
|
||||||
# We are in an inverted mode and facing OTHER/WIRE.
|
# We are in an inverted mode and facing OTHER/WIRE.
|
||||||
# This yields the special -2 priority!
|
# This yields the special -2 priority!
|
||||||
rep_pri = -2
|
rep_pri = -2
|
||||||
next_signal = "INV_UTILIZED" # Mark the -2 as successfully utilized
|
next_signal = "INV_UTILIZED" # Mark the -2 as successfully utilized
|
||||||
allow_rep = True
|
allow_rep = True
|
||||||
|
|
||||||
if allow_rep:
|
if allow_rep:
|
||||||
transitions.append(Transition(
|
transitions.append(
|
||||||
from_state=current_state,
|
Transition(
|
||||||
to_state=f"{next_signal}_DIODE",
|
from_state=current_state,
|
||||||
entries=(Entry(rep_pri, d, f're{d}'),)
|
to_state=f"{next_signal}_DIODE",
|
||||||
))
|
entries=(Entry(rep_pri, d, f"re{d}"),),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# E. Fluids
|
# E. Fluids
|
||||||
if signal == "NORMAL":
|
if signal == "NORMAL":
|
||||||
for d in (5, 10, 30):
|
for d in (5, 10, 30):
|
||||||
fluid_macro = (Entry(0, 2, 'obs'), Entry(1, d, f'fluid{d}'), Entry(0, 4, 'disp'))
|
fluid_macro = (
|
||||||
transitions.append(Transition(
|
Entry(0, 2, "obs"),
|
||||||
from_state=current_state,
|
Entry(1, d, f"fluid{d}"),
|
||||||
to_state="NORMAL_OTHER",
|
Entry(0, 4, "disp"),
|
||||||
entries=fluid_macro
|
)
|
||||||
))
|
transitions.append(
|
||||||
|
Transition(
|
||||||
|
from_state=current_state,
|
||||||
|
to_state="NORMAL_OTHER",
|
||||||
|
entries=fluid_macro,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# 2. CREATE THE BOUNDARY 'START' STATE
|
# 2. CREATE THE BOUNDARY 'START' STATE
|
||||||
@@ -213,39 +243,42 @@ start_transitions = []
|
|||||||
for t in transitions:
|
for t in transitions:
|
||||||
if t.from_state == "NORMAL_OTHER":
|
if t.from_state == "NORMAL_OTHER":
|
||||||
# Allow everything EXCEPT the zero-delay at the very start
|
# Allow everything EXCEPT the zero-delay at the very start
|
||||||
if not (len(t.entries) == 1 and t.entries[0].name == '---'):
|
if not (len(t.entries) == 1 and t.entries[0].name == "---"):
|
||||||
start_transitions.append(Transition(
|
start_transitions.append(
|
||||||
from_state="START",
|
Transition(from_state="START", to_state=t.to_state, entries=t.entries)
|
||||||
to_state=t.to_state,
|
)
|
||||||
entries=t.entries
|
start_transitions.append(
|
||||||
))
|
Transition(
|
||||||
start_transitions.append(Transition(
|
from_state="START",
|
||||||
from_state="START",
|
to_state="INV_PENDING_OTHER",
|
||||||
to_state="INV_PENDING_OTHER",
|
entries=(Entry(0, 2, "torch "),),
|
||||||
entries=(Entry(0, 2, 'torch '),)
|
)
|
||||||
))
|
)
|
||||||
transitions.extend(start_transitions)
|
transitions.extend(start_transitions)
|
||||||
|
|
||||||
all_states = set(t.from_state for t in transitions) | set(t.to_state for t in transitions)
|
all_states = set(t.from_state for t in transitions) | set(
|
||||||
|
t.to_state for t in transitions
|
||||||
|
)
|
||||||
accept_states = {s for s in all_states if s.startswith("NORMAL")}
|
accept_states = {s for s in all_states if s.startswith("NORMAL")}
|
||||||
|
|
||||||
model = TilesetFSM(
|
model = TilesetFSM(
|
||||||
transitions=transitions,
|
transitions=transitions, start_state="START", accept_states=accept_states
|
||||||
start_state='START',
|
|
||||||
accept_states=accept_states
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# 2. USAGE EXAMPLES
|
# 2. USAGE EXAMPLES
|
||||||
# ==========================================
|
# ==========================================
|
||||||
|
|
||||||
|
|
||||||
def display_chain(entries):
|
def display_chain(entries):
|
||||||
# Print the chain, highlighting the name and generated priority
|
# Print the chain, highlighting the name and generated priority
|
||||||
return " <- ".join([f"{e.name}({e.priority})" for e in entries])
|
return " <- ".join([f"{e.name}({e.priority})" for e in entries])
|
||||||
|
|
||||||
|
|
||||||
def linearize_entries(entries):
|
def linearize_entries(entries):
|
||||||
return tuple(p for e in entries for p in e.linear_parts())
|
return tuple(p for e in entries for p in e.linear_parts())
|
||||||
|
|
||||||
|
|
||||||
def generate_all_fast(model: TilesetFSM, target_delay: int) -> List[List[Entry]]:
|
def generate_all_fast(model: TilesetFSM, target_delay: int) -> List[List[Entry]]:
|
||||||
"""
|
"""
|
||||||
Uses Dynamic Programming to generate all valid chains of a given delay in O(N log N) time,
|
Uses Dynamic Programming to generate all valid chains of a given delay in O(N log N) time,
|
||||||
@@ -273,7 +306,9 @@ def generate_all_fast(model: TilesetFSM, target_delay: int) -> List[List[Entry]]
|
|||||||
# 3. Guided DFS
|
# 3. Guided DFS
|
||||||
raw_chains = []
|
raw_chains = []
|
||||||
|
|
||||||
def build_chain(current_state: str, remaining_delay: int, current_chain: Tuple[Entry, ...]):
|
def build_chain(
|
||||||
|
current_state: str, remaining_delay: int, current_chain: Tuple[Entry, ...]
|
||||||
|
):
|
||||||
bar.update(1)
|
bar.update(1)
|
||||||
if remaining_delay == 0 and current_state in model.accept_states:
|
if remaining_delay == 0 and current_state in model.accept_states:
|
||||||
raw_chains.append(list(current_chain))
|
raw_chains.append(list(current_chain))
|
||||||
@@ -283,7 +318,9 @@ def generate_all_fast(model: TilesetFSM, target_delay: int) -> List[List[Entry]]
|
|||||||
t_delay = t.total_delay()
|
t_delay = t.total_delay()
|
||||||
if t_delay <= remaining_delay:
|
if t_delay <= remaining_delay:
|
||||||
if can_reach[remaining_delay - t_delay][t.to_state]:
|
if can_reach[remaining_delay - t_delay][t.to_state]:
|
||||||
build_chain(t.to_state, remaining_delay - t_delay, current_chain + t.entries)
|
build_chain(
|
||||||
|
t.to_state, remaining_delay - t_delay, current_chain + t.entries
|
||||||
|
)
|
||||||
|
|
||||||
with tqdm.tqdm() as bar:
|
with tqdm.tqdm() as bar:
|
||||||
build_chain(model.start_state, target_delay, ())
|
build_chain(model.start_state, target_delay, ())
|
||||||
@@ -306,21 +343,24 @@ def generate_all_fast(model: TilesetFSM, target_delay: int) -> List[List[Entry]]
|
|||||||
unique_chains[lin_key] = chain
|
unique_chains[lin_key] = chain
|
||||||
|
|
||||||
# 5. Sort the purely unique chains
|
# 5. Sort the purely unique chains
|
||||||
sorted_chains = sorted(unique_chains.values(),
|
sorted_chains = sorted(
|
||||||
key=lambda chain: tuple(p for e in chain for p in e.linear_parts()))
|
unique_chains.values(),
|
||||||
|
key=lambda chain: tuple(p for e in chain for p in e.linear_parts()),
|
||||||
|
)
|
||||||
|
|
||||||
return sorted_chains
|
return sorted_chains
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
target_delay = 20
|
target_delay = 20
|
||||||
|
|
||||||
with open('chains.csv', 'w') as f:
|
with open("chains.csv", "w") as f:
|
||||||
for chain in generate_all_fast(model, target_delay):
|
for chain in generate_all_fast(model, target_delay):
|
||||||
for el in chain:
|
for el in chain:
|
||||||
f.write(el.name)
|
f.write(el.name)
|
||||||
f.write(' ')
|
f.write(" ")
|
||||||
f.write(',')
|
f.write(",")
|
||||||
for el in linearize_entries(chain):
|
for el in linearize_entries(chain):
|
||||||
f.write(f'{el:+}' if isfinite(el) else ' ')
|
f.write(f"{el:+}" if isfinite(el) else " ")
|
||||||
f.write(' ')
|
f.write(" ")
|
||||||
f.write('\n')
|
f.write("\n")
|
||||||
|
|||||||
45
grammar.py
45
grammar.py
@@ -1,26 +1,31 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Tuple, Optional, List
|
from typing import Tuple, Optional, List
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Entry:
|
class Entry:
|
||||||
priority: int
|
priority: int
|
||||||
delay: int
|
delay: int
|
||||||
name: str = '-'
|
name: str = "-"
|
||||||
|
|
||||||
def linear_parts(self) -> Tuple[int, ...]:
|
def linear_parts(self) -> Tuple[int, ...]:
|
||||||
return (self.priority,) * self.delay
|
return (self.priority,) * self.delay
|
||||||
|
|
||||||
|
|
||||||
# A ComponentBlock is an atomic sequence of Entries that can be physically built.
|
# A ComponentBlock is an atomic sequence of Entries that can be physically built.
|
||||||
ComponentBlock = Tuple[Entry, ...]
|
ComponentBlock = Tuple[Entry, ...]
|
||||||
|
|
||||||
|
|
||||||
def block_linearize(block: ComponentBlock) -> Tuple[int, ...]:
|
def block_linearize(block: ComponentBlock) -> Tuple[int, ...]:
|
||||||
"""Linearizes a single macro-block."""
|
"""Linearizes a single macro-block."""
|
||||||
return tuple(p for e in block for p in e.linear_parts())
|
return tuple(p for e in block for p in e.linear_parts())
|
||||||
|
|
||||||
|
|
||||||
def chain_linearize(chain: Tuple[ComponentBlock, ...]) -> Tuple[int, ...]:
|
def chain_linearize(chain: Tuple[ComponentBlock, ...]) -> Tuple[int, ...]:
|
||||||
"""Linearizes a full sequence of blocks."""
|
"""Linearizes a full sequence of blocks."""
|
||||||
return tuple(p for b in chain for p in block_linearize(b))
|
return tuple(p for b in chain for p in block_linearize(b))
|
||||||
|
|
||||||
|
|
||||||
def flatten_chain(chain: Tuple[ComponentBlock, ...]) -> List[Entry]:
|
def flatten_chain(chain: Tuple[ComponentBlock, ...]) -> List[Entry]:
|
||||||
"""Converts the internal tuple-based chain back to your flat List[Entry] format."""
|
"""Converts the internal tuple-based chain back to your flat List[Entry] format."""
|
||||||
return [entry for block in chain for entry in block]
|
return [entry for block in chain for entry in block]
|
||||||
@@ -30,18 +35,24 @@ class TilesetModel:
|
|||||||
def __init__(self, allowed_blocks: List[ComponentBlock]):
|
def __init__(self, allowed_blocks: List[ComponentBlock]):
|
||||||
self.blocks = allowed_blocks
|
self.blocks = allowed_blocks
|
||||||
|
|
||||||
def _check_divergence(self, prefix: Tuple[int, ...], target: Tuple[int, ...]) -> int:
|
def _check_divergence(
|
||||||
|
self, prefix: Tuple[int, ...], target: Tuple[int, ...]
|
||||||
|
) -> int:
|
||||||
"""
|
"""
|
||||||
Returns -1 if prefix is lexicographically < target,
|
Returns -1 if prefix is lexicographically < target,
|
||||||
1 if prefix is lexicographically > target,
|
1 if prefix is lexicographically > target,
|
||||||
0 if prefix is a strict prefix of target (no divergence yet).
|
0 if prefix is a strict prefix of target (no divergence yet).
|
||||||
"""
|
"""
|
||||||
for p_val, t_val in zip(prefix, target):
|
for p_val, t_val in zip(prefix, target):
|
||||||
if p_val < t_val: return -1
|
if p_val < t_val:
|
||||||
if p_val > t_val: return 1
|
return -1
|
||||||
|
if p_val > t_val:
|
||||||
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def find_next(self, current_chain: Tuple[ComponentBlock, ...]) -> Optional[Tuple[ComponentBlock, ...]]:
|
def find_next(
|
||||||
|
self, current_chain: Tuple[ComponentBlock, ...]
|
||||||
|
) -> Optional[Tuple[ComponentBlock, ...]]:
|
||||||
"""Finds the lexicographically next chain that perfectly matches the total delay."""
|
"""Finds the lexicographically next chain that perfectly matches the total delay."""
|
||||||
target_delay = sum(e.delay for b in current_chain for e in b)
|
target_delay = sum(e.delay for b in current_chain for e in b)
|
||||||
current_lin = chain_linearize(current_chain)
|
current_lin = chain_linearize(current_chain)
|
||||||
@@ -85,7 +96,9 @@ class TilesetModel:
|
|||||||
dfs((), 0)
|
dfs((), 0)
|
||||||
return best_next_chain
|
return best_next_chain
|
||||||
|
|
||||||
def find_previous(self, current_chain: Tuple[ComponentBlock, ...]) -> Optional[Tuple[ComponentBlock, ...]]:
|
def find_previous(
|
||||||
|
self, current_chain: Tuple[ComponentBlock, ...]
|
||||||
|
) -> Optional[Tuple[ComponentBlock, ...]]:
|
||||||
"""Finds the lexicographically previous chain matching total delay."""
|
"""Finds the lexicographically previous chain matching total delay."""
|
||||||
target_delay = sum(e.delay for b in current_chain for e in b)
|
target_delay = sum(e.delay for b in current_chain for e in b)
|
||||||
current_lin = chain_linearize(current_chain)
|
current_lin = chain_linearize(current_chain)
|
||||||
@@ -123,24 +136,26 @@ class TilesetModel:
|
|||||||
dfs((), 0)
|
dfs((), 0)
|
||||||
return best_prev_chain
|
return best_prev_chain
|
||||||
|
|
||||||
|
|
||||||
# --- Define the "Alphabet" of valid Macro-Blocks ---
|
# --- Define the "Alphabet" of valid Macro-Blocks ---
|
||||||
|
|
||||||
# Standard Components
|
# Standard Components
|
||||||
repeaters = [(Entry(p, d, f'rep({p},{d})'),) for p in (-1, -3) for d in (2, 4, 6, 8)]
|
repeaters = [(Entry(p, d, f"rep({p},{d})"),) for p in (-1, -3) for d in (2, 4, 6, 8)]
|
||||||
comparators = [(Entry(p, 2, f'cmp({p})'),) for p in (0, -1)]
|
comparators = [(Entry(p, 2, f"cmp({p})"),) for p in (0, -1)]
|
||||||
others = [(Entry(0, 2, 'other'),)]
|
others = [(Entry(0, 2, "other"),)]
|
||||||
|
|
||||||
# Subsequences: Signal Inverted Repeaters
|
# Subsequences: Signal Inverted Repeaters
|
||||||
# (Note: Updates flow right-to-left. Torch2 updates latest so it goes on the left)
|
# (Note: Updates flow right-to-left. Torch2 updates latest so it goes on the left)
|
||||||
inv_repeaters = [
|
inv_repeaters = [
|
||||||
(Entry(0, 2, 'torch2'), Entry(p, d, f'inv_rep({p},{d})'), Entry(0, 2, 'torch1'))
|
(Entry(0, 2, "torch2"), Entry(p, d, f"inv_rep({p},{d})"), Entry(0, 2, "torch1"))
|
||||||
for p in (-2, -3) for d in (2, 4, 6, 8)
|
for p in (-2, -3)
|
||||||
|
for d in (2, 4, 6, 8)
|
||||||
]
|
]
|
||||||
|
|
||||||
# Subsequences: Fluids & Observers
|
# Subsequences: Fluids & Observers
|
||||||
# (Observer updates latest so it goes on the left)
|
# (Observer updates latest so it goes on the left)
|
||||||
fluids = [
|
fluids = [
|
||||||
(Entry(0, 2, 'obs'), Entry(1, d, f'fluid({d})'), Entry(0, 4, 'disp'))
|
(Entry(0, 2, "obs"), Entry(1, d, f"fluid({d})"), Entry(0, 4, "disp"))
|
||||||
for d in (5, 10, 30)
|
for d in (5, 10, 30)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -153,9 +168,9 @@ model = TilesetModel(ALL_MACRO_BLOCKS)
|
|||||||
|
|
||||||
# Let's say we have a specific chain of Total Delay = 14
|
# Let's say we have a specific chain of Total Delay = 14
|
||||||
current = (
|
current = (
|
||||||
(Entry(0, 2, 'cmp(0)'),),
|
(Entry(0, 2, "cmp(0)"),),
|
||||||
(Entry(-3, 8, 'rep(-3,8)'),),
|
(Entry(-3, 8, "rep(-3,8)"),),
|
||||||
(Entry(-1, 4, 'rep(-1,4)'),)
|
(Entry(-1, 4, "rep(-1,4)"),),
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"Current Chain: {flatten_chain(current)}")
|
print(f"Current Chain: {flatten_chain(current)}")
|
||||||
|
|||||||
168
permute.py
168
permute.py
@@ -10,75 +10,161 @@ from typing import Iterable
|
|||||||
|
|
||||||
# Note that components which update later in time have stronger significance on the order of execution; so for our notation we place these on the left-hand-side (first in lists) to match left-to-right lexicographic notation.
|
# Note that components which update later in time have stronger significance on the order of execution; so for our notation we place these on the left-hand-side (first in lists) to match left-to-right lexicographic notation.
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Entry:
|
class Entry:
|
||||||
priority: int
|
priority: int
|
||||||
delay: int
|
delay: int
|
||||||
name: str = '-'
|
name: str = "-"
|
||||||
|
|
||||||
|
|
||||||
# We can model this by linearizing these tuples into tuples, then lexicographically sort these linearized keys.
|
# We can model this by linearizing these tuples into tuples, then lexicographically sort these linearized keys.
|
||||||
# We directly sort on priority: more-negative priorities update before more-positive priorities, so these are correctly sorted lexicographically. However we only want this behavior in the case that the components update at the same time, so we can 'pad out' the effect of delay by repeating the priority.
|
# We directly sort on priority: more-negative priorities update before more-positive priorities, so these are correctly sorted lexicographically. However we only want this behavior in the case that the components update at the same time, so we can 'pad out' the effect of delay by repeating the priority.
|
||||||
|
|
||||||
|
|
||||||
def linear_parts(entry: Entry):
|
def linear_parts(entry: Entry):
|
||||||
assert entry.delay > 0
|
assert entry.delay > 0
|
||||||
# return (-np.inf,) * (entry.delay - 1) + (entry.priority,)
|
# return (-np.inf,) * (entry.delay - 1) + (entry.priority,)
|
||||||
return (entry.priority,) * entry.delay
|
return (entry.priority,) * entry.delay
|
||||||
|
|
||||||
|
|
||||||
def linearize(entries: Iterable[Entry]):
|
def linearize(entries: Iterable[Entry]):
|
||||||
return [part for entry in entries for part in linear_parts(entry)]
|
return [part for entry in entries for part in linear_parts(entry)]
|
||||||
|
|
||||||
|
|
||||||
def sort(chains: list[Entry]):
|
def sort(chains: list[Entry]):
|
||||||
return sorted(chains, key=linearize)
|
return sorted(chains, key=linearize)
|
||||||
|
|
||||||
|
|
||||||
# one framework to produce a tileset is to encode binary values by comparator and repeater. all cases have equal delay, so it just falls to the priorities. comparators have priority 0 at the end and -1 in the middle, while repeaters have priority -1 at the end and -2 in the middle.
|
# one framework to produce a tileset is to encode binary values by comparator and repeater. all cases have equal delay, so it just falls to the priorities. comparators have priority 0 at the end and -1 in the middle, while repeaters have priority -1 at the end and -2 in the middle.
|
||||||
|
|
||||||
chains = [
|
chains = [
|
||||||
[Entry(-0, 2, 'cmp'), Entry(-1, 2, 'cmp'), Entry(-1, 2, 'cmp'), Entry(-1, 2, 'cmp')],
|
[
|
||||||
[Entry(-0, 2, 'cmp'), Entry(-1, 2, 'cmp'), Entry(-1, 2, 'cmp'), Entry(-2, 2, 'rep')],
|
Entry(-0, 2, "cmp"),
|
||||||
[Entry(-0, 2, 'cmp'), Entry(-1, 2, 'cmp'), Entry(-2, 2, 'rep'), Entry(-1, 2, 'cmp')],
|
Entry(-1, 2, "cmp"),
|
||||||
[Entry(-0, 2, 'cmp'), Entry(-1, 2, 'cmp'), Entry(-2, 2, 'rep'), Entry(-2, 2, 'rep')],
|
Entry(-1, 2, "cmp"),
|
||||||
[Entry(-0, 2, 'cmp'), Entry(-3, 2, 'rep'), Entry(-1, 2, 'cmp'), Entry(-1, 2, 'cmp')],
|
Entry(-1, 2, "cmp"),
|
||||||
[Entry(-0, 2, 'cmp'), Entry(-3, 2, 'rep'), Entry(-1, 2, 'cmp'), Entry(-3, 2, 'rep')],
|
],
|
||||||
[Entry(-0, 2, 'cmp'), Entry(-3, 2, 'rep'), Entry(-3, 2, 'rep'), Entry(-1, 2, 'cmp')],
|
[
|
||||||
[Entry(-0, 2, 'cmp'), Entry(-3, 2, 'rep'), Entry(-3, 2, 'rep'), Entry(-3, 2, 'rep')],
|
Entry(-0, 2, "cmp"),
|
||||||
[Entry(-1, 2, 'rep'), Entry(-1, 2, 'cmp'), Entry(-1, 2, 'cmp'), Entry(-1, 2, 'cmp')],
|
Entry(-1, 2, "cmp"),
|
||||||
[Entry(-1, 2, 'rep'), Entry(-1, 2, 'cmp'), Entry(-1, 2, 'cmp'), Entry(-3, 2, 'rep')],
|
Entry(-1, 2, "cmp"),
|
||||||
[Entry(-1, 2, 'rep'), Entry(-1, 2, 'cmp'), Entry(-3, 2, 'rep'), Entry(-1, 2, 'cmp')],
|
Entry(-2, 2, "rep"),
|
||||||
[Entry(-1, 2, 'rep'), Entry(-1, 2, 'cmp'), Entry(-3, 2, 'rep'), Entry(-3, 2, 'rep')],
|
],
|
||||||
[Entry(-1, 2, 'rep'), Entry(-3, 2, 'rep'), Entry(-1, 2, 'cmp'), Entry(-1, 2, 'cmp')],
|
[
|
||||||
[Entry(-1, 2, 'rep'), Entry(-3, 2, 'rep'), Entry(-1, 2, 'cmp'), Entry(-3, 2, 'rep')],
|
Entry(-0, 2, "cmp"),
|
||||||
[Entry(-1, 2, 'rep'), Entry(-3, 2, 'rep'), Entry(-3, 2, 'rep'), Entry(-1, 2, 'cmp')],
|
Entry(-1, 2, "cmp"),
|
||||||
[Entry(-1, 2, 'rep'), Entry(-3, 2, 'rep'), Entry(-3, 2, 'rep'), Entry(-3, 2, 'rep')],
|
Entry(-2, 2, "rep"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Entry(-0, 2, "cmp"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
Entry(-2, 2, "rep"),
|
||||||
|
Entry(-2, 2, "rep"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Entry(-0, 2, "cmp"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Entry(-0, 2, "cmp"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Entry(-0, 2, "cmp"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Entry(-0, 2, "cmp"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Entry(-1, 2, "rep"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Entry(-1, 2, "rep"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Entry(-1, 2, "rep"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Entry(-1, 2, "rep"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Entry(-1, 2, "rep"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Entry(-1, 2, "rep"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Entry(-1, 2, "rep"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
Entry(-1, 2, "cmp"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Entry(-1, 2, "rep"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
Entry(-3, 2, "rep"),
|
||||||
|
],
|
||||||
]
|
]
|
||||||
shuffle(chains)
|
shuffle(chains)
|
||||||
|
|
||||||
# another framework is to use permutations of repeaters on varying delay. now all cases have equal priority, so it just falls to the delays.
|
# another framework is to use permutations of repeaters on varying delay. now all cases have equal priority, so it just falls to the delays.
|
||||||
|
|
||||||
chains = [
|
chains = [
|
||||||
[Entry(-1, 8, 'r4'), Entry(-3, 6, 'r3'), Entry(-3, 4, 'r2'), Entry(-3, 2, 'r1')],
|
[Entry(-1, 8, "r4"), Entry(-3, 6, "r3"), Entry(-3, 4, "r2"), Entry(-3, 2, "r1")],
|
||||||
[Entry(-1, 8, 'r4'), Entry(-3, 6, 'r3'), Entry(-3, 2, 'r1'), Entry(-3, 4, 'r2')],
|
[Entry(-1, 8, "r4"), Entry(-3, 6, "r3"), Entry(-3, 2, "r1"), Entry(-3, 4, "r2")],
|
||||||
[Entry(-1, 8, 'r4'), Entry(-3, 4, 'r2'), Entry(-3, 6, 'r3'), Entry(-3, 2, 'r1')],
|
[Entry(-1, 8, "r4"), Entry(-3, 4, "r2"), Entry(-3, 6, "r3"), Entry(-3, 2, "r1")],
|
||||||
[Entry(-1, 8, 'r4'), Entry(-3, 4, 'r2'), Entry(-3, 2, 'r1'), Entry(-3, 6, 'r3')],
|
[Entry(-1, 8, "r4"), Entry(-3, 4, "r2"), Entry(-3, 2, "r1"), Entry(-3, 6, "r3")],
|
||||||
[Entry(-1, 8, 'r4'), Entry(-3, 2, 'r1'), Entry(-3, 6, 'r3'), Entry(-3, 4, 'r2')],
|
[Entry(-1, 8, "r4"), Entry(-3, 2, "r1"), Entry(-3, 6, "r3"), Entry(-3, 4, "r2")],
|
||||||
[Entry(-1, 8, 'r4'), Entry(-3, 2, 'r1'), Entry(-3, 4, 'r2'), Entry(-3, 6, 'r3')],
|
[Entry(-1, 8, "r4"), Entry(-3, 2, "r1"), Entry(-3, 4, "r2"), Entry(-3, 6, "r3")],
|
||||||
[Entry(-1, 6, 'r3'), Entry(-3, 8, 'r4'), Entry(-3, 4, 'r2'), Entry(-3, 2, 'r1')],
|
[Entry(-1, 6, "r3"), Entry(-3, 8, "r4"), Entry(-3, 4, "r2"), Entry(-3, 2, "r1")],
|
||||||
[Entry(-1, 6, 'r3'), Entry(-3, 8, 'r4'), Entry(-3, 2, 'r1'), Entry(-3, 4, 'r2')],
|
[Entry(-1, 6, "r3"), Entry(-3, 8, "r4"), Entry(-3, 2, "r1"), Entry(-3, 4, "r2")],
|
||||||
[Entry(-1, 6, 'r3'), Entry(-3, 4, 'r2'), Entry(-3, 8, 'r4'), Entry(-3, 2, 'r1')],
|
[Entry(-1, 6, "r3"), Entry(-3, 4, "r2"), Entry(-3, 8, "r4"), Entry(-3, 2, "r1")],
|
||||||
[Entry(-1, 6, 'r3'), Entry(-3, 4, 'r2'), Entry(-3, 2, 'r1'), Entry(-3, 8, 'r4')],
|
[Entry(-1, 6, "r3"), Entry(-3, 4, "r2"), Entry(-3, 2, "r1"), Entry(-3, 8, "r4")],
|
||||||
[Entry(-1, 6, 'r3'), Entry(-3, 2, 'r1'), Entry(-3, 8, 'r4'), Entry(-3, 4, 'r2')],
|
[Entry(-1, 6, "r3"), Entry(-3, 2, "r1"), Entry(-3, 8, "r4"), Entry(-3, 4, "r2")],
|
||||||
[Entry(-1, 6, 'r3'), Entry(-3, 2, 'r1'), Entry(-3, 4, 'r2'), Entry(-3, 8, 'r4')],
|
[Entry(-1, 6, "r3"), Entry(-3, 2, "r1"), Entry(-3, 4, "r2"), Entry(-3, 8, "r4")],
|
||||||
[Entry(-1, 4, 'r2'), Entry(-3, 8, 'r4'), Entry(-3, 6, 'r3'), Entry(-3, 2, 'r1')],
|
[Entry(-1, 4, "r2"), Entry(-3, 8, "r4"), Entry(-3, 6, "r3"), Entry(-3, 2, "r1")],
|
||||||
[Entry(-1, 4, 'r2'), Entry(-3, 8, 'r4'), Entry(-3, 2, 'r1'), Entry(-3, 6, 'r3')],
|
[Entry(-1, 4, "r2"), Entry(-3, 8, "r4"), Entry(-3, 2, "r1"), Entry(-3, 6, "r3")],
|
||||||
[Entry(-1, 4, 'r2'), Entry(-3, 6, 'r3'), Entry(-3, 8, 'r4'), Entry(-3, 2, 'r1')],
|
[Entry(-1, 4, "r2"), Entry(-3, 6, "r3"), Entry(-3, 8, "r4"), Entry(-3, 2, "r1")],
|
||||||
[Entry(-1, 4, 'r2'), Entry(-3, 6, 'r3'), Entry(-3, 2, 'r1'), Entry(-3, 8, 'r4')],
|
[Entry(-1, 4, "r2"), Entry(-3, 6, "r3"), Entry(-3, 2, "r1"), Entry(-3, 8, "r4")],
|
||||||
[Entry(-1, 4, 'r2'), Entry(-3, 2, 'r1'), Entry(-3, 8, 'r4'), Entry(-3, 6, 'r3')],
|
[Entry(-1, 4, "r2"), Entry(-3, 2, "r1"), Entry(-3, 8, "r4"), Entry(-3, 6, "r3")],
|
||||||
[Entry(-1, 4, 'r2'), Entry(-3, 2, 'r1'), Entry(-3, 6, 'r3'), Entry(-3, 8, 'r4')],
|
[Entry(-1, 4, "r2"), Entry(-3, 2, "r1"), Entry(-3, 6, "r3"), Entry(-3, 8, "r4")],
|
||||||
[Entry(-1, 2, 'r1'), Entry(-3, 8, 'r4'), Entry(-3, 6, 'r3'), Entry(-3, 4, 'r2')],
|
[Entry(-1, 2, "r1"), Entry(-3, 8, "r4"), Entry(-3, 6, "r3"), Entry(-3, 4, "r2")],
|
||||||
[Entry(-1, 2, 'r1'), Entry(-3, 8, 'r4'), Entry(-3, 4, 'r2'), Entry(-3, 6, 'r3')],
|
[Entry(-1, 2, "r1"), Entry(-3, 8, "r4"), Entry(-3, 4, "r2"), Entry(-3, 6, "r3")],
|
||||||
[Entry(-1, 2, 'r1'), Entry(-3, 6, 'r3'), Entry(-3, 8, 'r4'), Entry(-3, 4, 'r2')],
|
[Entry(-1, 2, "r1"), Entry(-3, 6, "r3"), Entry(-3, 8, "r4"), Entry(-3, 4, "r2")],
|
||||||
[Entry(-1, 2, 'r1'), Entry(-3, 6, 'r3'), Entry(-3, 4, 'r2'), Entry(-3, 8, 'r4')],
|
[Entry(-1, 2, "r1"), Entry(-3, 6, "r3"), Entry(-3, 4, "r2"), Entry(-3, 8, "r4")],
|
||||||
[Entry(-1, 2, 'r1'), Entry(-3, 4, 'r2'), Entry(-3, 8, 'r4'), Entry(-3, 6, 'r3')],
|
[Entry(-1, 2, "r1"), Entry(-3, 4, "r2"), Entry(-3, 8, "r4"), Entry(-3, 6, "r3")],
|
||||||
[Entry(-1, 2, 'r1'), Entry(-3, 4, 'r2'), Entry(-3, 6, 'r3'), Entry(-3, 8, 'r4')],
|
[Entry(-1, 2, "r1"), Entry(-3, 4, "r2"), Entry(-3, 6, "r3"), Entry(-3, 8, "r4")],
|
||||||
]
|
]
|
||||||
shuffle(chains)
|
shuffle(chains)
|
||||||
|
|
||||||
|
|||||||
170
simple-enum.py
Normal file
170
simple-enum.py
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
from contextlib import redirect_stdout
|
||||||
|
from functools import cache
|
||||||
|
from pathlib import Path
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
|
||||||
|
class Comp:
|
||||||
|
def __init__(self, priority: int, delay: int, name: str, head: bool = True, blocks = ()):
|
||||||
|
self.priority = priority
|
||||||
|
self.delay = delay
|
||||||
|
self.head = head
|
||||||
|
self.name = name
|
||||||
|
self.blocks = tuple(blocks)
|
||||||
|
|
||||||
|
def __lt__(self, other: Comp) -> bool:
|
||||||
|
if self.priority == other.priority:
|
||||||
|
return self.delay > other.delay
|
||||||
|
else:
|
||||||
|
return self.priority < other.priority
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"{self.name} ({self.priority} / {self.delay})"
|
||||||
|
|
||||||
|
COMPONENTS = [
|
||||||
|
Comp(priority=-3, delay=8, name="R4", head=False, blocks=[
|
||||||
|
'repeater facing=south powered=false locked=false delay=4',
|
||||||
|
]),
|
||||||
|
Comp(priority=-3, delay=6, name="R3", head=False, blocks=[
|
||||||
|
'repeater facing=south powered=false locked=false delay=3'
|
||||||
|
]),
|
||||||
|
Comp(priority=-3, delay=4, name="R2", head=False, blocks=[
|
||||||
|
'repeater facing=south powered=false locked=false delay=2'
|
||||||
|
]),
|
||||||
|
Comp(priority=-3, delay=2, name="R1", head=False, blocks=[
|
||||||
|
'repeater facing=south powered=false locked=false delay=1'
|
||||||
|
]),
|
||||||
|
Comp(priority=-1, delay=8, name="- R4", head=True, blocks=[
|
||||||
|
'redstone_wire north=side south=side power=0',
|
||||||
|
'repeater facing=south powered=false locked=false delay=4'
|
||||||
|
]),
|
||||||
|
Comp(priority=-1, delay=6, name="- R3", head=True, blocks=[
|
||||||
|
'redstone_wire north=side south=side power=0',
|
||||||
|
'repeater facing=south powered=false locked=false delay=3'
|
||||||
|
]),
|
||||||
|
Comp(priority=-1, delay=4, name="- R2", head=True, blocks=[
|
||||||
|
'redstone_wire north=side south=side power=0',
|
||||||
|
'repeater facing=south powered=false locked=false delay=2'
|
||||||
|
]),
|
||||||
|
Comp(priority=-1, delay=2, name="C", head=False, blocks=[
|
||||||
|
'comparator facing=south powered=false mode=compare'
|
||||||
|
]),
|
||||||
|
Comp(priority=-1, delay=2, name="- R1", head=True, blocks=[
|
||||||
|
'redstone_wire north=side south=side power=0',
|
||||||
|
'repeater facing=south powered=false locked=false delay=1'
|
||||||
|
]),
|
||||||
|
Comp(priority=0, delay=2, name="- C", head=True, blocks=[
|
||||||
|
'redstone_wire north=side south=side power=0',
|
||||||
|
'comparator facing=south powered=false mode=compare'
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
|
||||||
|
COMPONENTS.sort()
|
||||||
|
|
||||||
|
|
||||||
|
from functools import cache
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
|
def tileset(total_delay, head=True):
|
||||||
|
tree = {}
|
||||||
|
seen = set()
|
||||||
|
|
||||||
|
for comp in COMPONENTS:
|
||||||
|
if comp.delay <= total_delay:
|
||||||
|
if head and not comp.head:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = (comp.priority, comp.delay)
|
||||||
|
if key in seen:
|
||||||
|
continue
|
||||||
|
|
||||||
|
seen.add(key)
|
||||||
|
tree[comp] = tileset(total_delay - comp.delay, head=False)
|
||||||
|
|
||||||
|
return tree
|
||||||
|
|
||||||
|
|
||||||
|
def show(tree, flat=False):
|
||||||
|
if flat:
|
||||||
|
|
||||||
|
def _print_flat(current_tree, current_path):
|
||||||
|
# If the dict is empty, we've reached the end of a valid chain
|
||||||
|
if not current_tree:
|
||||||
|
print(" ".join(str(comp) for comp in current_path))
|
||||||
|
return
|
||||||
|
for comp, subtree in current_tree.items():
|
||||||
|
_print_flat(subtree, current_path + [comp])
|
||||||
|
|
||||||
|
_print_flat(tree, [])
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def _print_tree(current_tree, prefix=""):
|
||||||
|
items = list(current_tree.items())
|
||||||
|
for i, (comp, subtree) in enumerate(items):
|
||||||
|
is_last = i == len(items) - 1
|
||||||
|
connector = "└ " if is_last else "├ "
|
||||||
|
print(f"{prefix}{connector}{comp}")
|
||||||
|
|
||||||
|
# If this is the last item, children don't need a vertical line
|
||||||
|
extension = " " if is_last else "│ "
|
||||||
|
_print_tree(subtree, prefix + extension)
|
||||||
|
|
||||||
|
_print_tree(tree)
|
||||||
|
|
||||||
|
|
||||||
|
# def diorama(tree, step=1.125):
|
||||||
|
# x = 0
|
||||||
|
#
|
||||||
|
# def inner(prefix, node):
|
||||||
|
# nonlocal x
|
||||||
|
#
|
||||||
|
# if not node:
|
||||||
|
# z = 0
|
||||||
|
# for pref in prefix:
|
||||||
|
# for block in pref.blocks:
|
||||||
|
# print(f'p {x:.3f} 0 {z} {block}')
|
||||||
|
# print(f'p {x:.3f} -1 {z} smooth_stone_slab type=top')
|
||||||
|
# z += 1
|
||||||
|
# x -= step
|
||||||
|
# else:
|
||||||
|
# for comp, sub in node.items():
|
||||||
|
# inner((*prefix, comp), sub)
|
||||||
|
#
|
||||||
|
# inner((), tree)
|
||||||
|
|
||||||
|
def dioramas(tree):
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
def inner(prefix, node):
|
||||||
|
nonlocal count
|
||||||
|
|
||||||
|
if not node:
|
||||||
|
count += 1
|
||||||
|
print('```mc-diorama')
|
||||||
|
z = 0
|
||||||
|
for pref in prefix:
|
||||||
|
for block in pref.blocks:
|
||||||
|
print(f'p 0 0 {z} {block}')
|
||||||
|
print(f'p 0 -1 {z} smooth_stone_slab type=top')
|
||||||
|
z += 1
|
||||||
|
print('```')
|
||||||
|
else:
|
||||||
|
for comp, sub in node.items():
|
||||||
|
inner((*prefix, comp), sub)
|
||||||
|
|
||||||
|
inner((), tree)
|
||||||
|
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
# show(tileset(4, head=True))
|
||||||
|
# print()
|
||||||
|
|
||||||
|
with Path('~/src/wireless/content/tilesets-lexicographic.typ').expanduser().open('w') as f, redirect_stdout(f):
|
||||||
|
total = dioramas(tileset(10, head=True))
|
||||||
|
print(f'{total = }')
|
||||||
Reference in New Issue
Block a user