rogermt commited on
Commit
e0d2eb1
·
verified ·
1 Parent(s): 9a57ca2

Add greedy stacker: overlay(T1(x), T2(x)) composition after depth-1 beam search

Browse files
Files changed (1) hide show
  1. itt_solver/beam_logging.py +77 -24
itt_solver/beam_logging.py CHANGED
@@ -26,12 +26,8 @@ def beam_minimize_with_log(phi_in, phi_target, atomic_library,
26
  enable_layer_minus_one=False,
27
  boundary_source='target'):
28
  """
29
- Beam search with gate validation and optional Layer-1 admissible mask.
30
-
31
- Uses a dual-strategy approach: each atomic transform is tried on BOTH the
32
- current (resized) field AND the original input. This is critical for
33
- shape-changing transforms (e.g. Kronecker) that must operate on the raw
34
- input rather than the tiled/resized intermediate.
35
  """
36
  phi_in = np.array(phi_in, dtype=float)
37
  phi_target = np.array(phi_target, dtype=float)
@@ -55,7 +51,6 @@ def beam_minimize_with_log(phi_in, phi_target, atomic_library,
55
 
56
  def _try_candidate(phi_after_atomic, T_atomic, T_cur, cur_field_resized,
57
  path_states, path_sigmas, depth_log, candidates, source_tag=""):
58
- """Score one candidate, check gates, and append to candidates if accepted."""
59
  phi_new_resized = _resize_to_target(phi_after_atomic, phi_target)
60
 
61
  if enable_layer_minus_one and layer_mask is not None:
@@ -78,12 +73,8 @@ def beam_minimize_with_log(phi_in, phi_target, atomic_library,
78
 
79
  if not gates_info.get('passed', False):
80
  depth_log.append({
81
- 'atomic': label,
82
- 'score': score,
83
- 'residue': residue,
84
- 'energy': energy,
85
- 'gates': gates_info,
86
- 'accepted': False,
87
  'shape': phi_candidate.shape,
88
  })
89
  return
@@ -91,16 +82,10 @@ def beam_minimize_with_log(phi_in, phi_target, atomic_library,
91
  new_states = path_states + [phi_candidate]
92
  new_sigmas = path_sigmas + [residue]
93
  T_new = T_cur.compose(T_atomic)
94
-
95
  candidates.append((T_new, phi_candidate, score, new_states, new_sigmas))
96
-
97
  depth_log.append({
98
- 'atomic': label,
99
- 'score': score,
100
- 'residue': residue,
101
- 'energy': energy,
102
- 'gates': gates_info,
103
- 'accepted': True,
104
  'shape': phi_candidate.shape,
105
  })
106
 
@@ -111,7 +96,7 @@ def beam_minimize_with_log(phi_in, phi_target, atomic_library,
111
  base_field_for_apply = path_states[-1]
112
 
113
  for idx, T_atomic in enumerate(atomic_library):
114
- # --- Strategy 1: apply to current (resized) field ---
115
  try:
116
  phi_after_atomic = T_atomic.apply(base_field_for_apply)
117
  _try_candidate(phi_after_atomic, T_atomic, T_cur,
@@ -120,8 +105,7 @@ def beam_minimize_with_log(phi_in, phi_target, atomic_library,
120
  except Exception:
121
  pass
122
 
123
- # --- Strategy 2: apply to ORIGINAL input (critical for
124
- # shape-changing transforms like Kronecker) ---
125
  try:
126
  phi_after_original = T_atomic.apply(phi_in)
127
  if phi_after_original.shape != base_field_for_apply.shape or \
@@ -144,8 +128,77 @@ def beam_minimize_with_log(phi_in, phi_target, atomic_library,
144
  if sigma_l1(best[1], phi_target) == 0:
145
  break
146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  if best is None:
148
  return identity, phi0, [phi0], [sigma_l1(phi0, phi_target)], logs
149
 
150
  T_best, phi_best, _, states_best, sigmas_best = best
151
  return T_best, phi_best, states_best, sigmas_best, logs
 
 
 
 
 
 
 
 
 
26
  enable_layer_minus_one=False,
27
  boundary_source='target'):
28
  """
29
+ Beam search with gate validation, dual-strategy (resized + original input),
30
+ and greedy stacker (overlay composition of depth-1 pieces).
 
 
 
 
31
  """
32
  phi_in = np.array(phi_in, dtype=float)
33
  phi_target = np.array(phi_target, dtype=float)
 
51
 
52
  def _try_candidate(phi_after_atomic, T_atomic, T_cur, cur_field_resized,
53
  path_states, path_sigmas, depth_log, candidates, source_tag=""):
 
54
  phi_new_resized = _resize_to_target(phi_after_atomic, phi_target)
55
 
56
  if enable_layer_minus_one and layer_mask is not None:
 
73
 
74
  if not gates_info.get('passed', False):
75
  depth_log.append({
76
+ 'atomic': label, 'score': score, 'residue': residue,
77
+ 'energy': energy, 'gates': gates_info, 'accepted': False,
 
 
 
 
78
  'shape': phi_candidate.shape,
79
  })
80
  return
 
82
  new_states = path_states + [phi_candidate]
83
  new_sigmas = path_sigmas + [residue]
84
  T_new = T_cur.compose(T_atomic)
 
85
  candidates.append((T_new, phi_candidate, score, new_states, new_sigmas))
 
86
  depth_log.append({
87
+ 'atomic': label, 'score': score, 'residue': residue,
88
+ 'energy': energy, 'gates': gates_info, 'accepted': True,
 
 
 
 
89
  'shape': phi_candidate.shape,
90
  })
91
 
 
96
  base_field_for_apply = path_states[-1]
97
 
98
  for idx, T_atomic in enumerate(atomic_library):
99
+ # Strategy 1: apply to current (resized) field
100
  try:
101
  phi_after_atomic = T_atomic.apply(base_field_for_apply)
102
  _try_candidate(phi_after_atomic, T_atomic, T_cur,
 
105
  except Exception:
106
  pass
107
 
108
+ # Strategy 2: apply to ORIGINAL input
 
109
  try:
110
  phi_after_original = T_atomic.apply(phi_in)
111
  if phi_after_original.shape != base_field_for_apply.shape or \
 
128
  if sigma_l1(best[1], phi_target) == 0:
129
  break
130
 
131
+ # --- Greedy stacker: try overlay(T1(x), T2(x)) for top candidates ---
132
+ if best is not None and sigma_l1(best[1], phi_target) > 0:
133
+ depth1_pieces = []
134
+ for T_atomic in atomic_library:
135
+ try:
136
+ piece = T_atomic.apply(phi_in)
137
+ piece_resized = _resize_to_target(piece, phi_target)
138
+ piece_sigma = sigma_l1(piece_resized, phi_target)
139
+ depth1_pieces.append((T_atomic, piece_resized, piece_sigma))
140
+ except Exception:
141
+ pass
142
+
143
+ depth1_pieces.sort(key=lambda x: x[2])
144
+ top_n = min(len(depth1_pieces), beam_width * 2)
145
+ stacker_log = []
146
+
147
+ for i in range(top_n):
148
+ T1, p1, s1 = depth1_pieces[i]
149
+ for j in range(top_n):
150
+ if i == j:
151
+ continue
152
+ T2, p2, s2 = depth1_pieces[j]
153
+
154
+ overlaid = p1.copy()
155
+ mask = (p2 != 0)
156
+ overlaid[mask] = p2[mask]
157
+
158
+ residue = sigma_l1(overlaid, phi_target)
159
+
160
+ if residue < sigma_l1(best[1], phi_target):
161
+ gates_info = validate_gates(overlaid, phi_in, phi_target,
162
+ boundary_mask=boundary_mask_resized,
163
+ max_fraction=max_fraction,
164
+ allowed_symbols=allowed_symbols)
165
+ label = f"overlay({repr(T1)},{repr(T2)})"
166
+ if gates_info.get('passed', False):
167
+ energy = dirichlet_energy(overlaid)
168
+ score = residue + lock_coeff * energy
169
+ T_composed = Transform(lambda p, _p1=p1, _p2=p2: _overlay(_p1, _p2),
170
+ f"overlay({T1.name},{T2.name})")
171
+ _, _, _, best_states, best_sigmas = best
172
+ new_states = best_states + [overlaid]
173
+ new_sigmas = best_sigmas + [residue]
174
+ if score < best[2]:
175
+ best = (T_composed, overlaid, score, new_states, new_sigmas)
176
+
177
+ stacker_log.append({
178
+ 'atomic': label, 'score': score,
179
+ 'residue': residue, 'energy': energy,
180
+ 'gates': gates_info, 'accepted': True,
181
+ 'shape': overlaid.shape,
182
+ })
183
+
184
+ if residue == 0:
185
+ break
186
+ if best is not None and sigma_l1(best[1], phi_target) == 0:
187
+ break
188
+
189
+ if stacker_log:
190
+ logs.append(stacker_log)
191
+
192
  if best is None:
193
  return identity, phi0, [phi0], [sigma_l1(phi0, phi_target)], logs
194
 
195
  T_best, phi_best, _, states_best, sigmas_best = best
196
  return T_best, phi_best, states_best, sigmas_best, logs
197
+
198
+
199
+ def _overlay(base, fg):
200
+ """Transparent overlay helper: fg non-zero pixels overwrite base."""
201
+ result = base.copy()
202
+ mask = (fg != 0)
203
+ result[mask] = fg[mask]
204
+ return result