Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@
| 1044 edges -> 2647 facts | 0.0142s | 0.0131s | -7.7% | 34/41 | -0.0011s vs 0.0018s -- **within** |

So: a small, consistently-signed lean toward #133 that does **not** separate from noise at this sample size. Note that the summary statistic is itself unstable -- a second run of the smaller shape gave -5.2% and 30/41 -- which is the point. An earlier note in this file claimed 0.037s against 0.048s -- roughly 23% -- and that number is **retracted**: it was min-of-7 across separate processes, which picks up the tail of the distribution rather than a difference between the arms. What does hold at every measurement: the two derive identical closures, and the micro-benchmark of `_unify` alone is a real 1.7-2x, diluted at `materialise` level by how often unification actually binds a new variable -- a property of the rule shape, not a constant. A bare speed-up number in this file will be reused on a workload it was never true for.

## 2026-07-28 - Optimize Ganter's NextClosure in FCA concept mining
**Learning:** Found a major bottleneck in `src/tacet/distill/fca.py` during `_next_intent` generation. The generator expressions within `any()`/`all()` and double allocations due to set intersections (`(B - {k for k in B if k > m}) | {m}`) caused severe execution overhead within a deep recursive loop context, jumping from ~0.65s to ~1.2s when discovering > 7000 concepts. Explicit comprehensions and explicit early-exit loops replace generator objects effectively without breaking bounds.
**Action:** In extreme hot loops like multi-relational concept lattice construction, avoid Python's generic short-circuit unrolling functions (`all`, `any`) with generic loops and avoid double allocations for set constructions.
21 changes: 16 additions & 5 deletions src/tacet/distill/fca.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,12 +215,23 @@ def _next_intent(self, B: set[int], n_attr: int) -> set[int] | None:
for m in range(n_attr - 1, -1, -1):
if m in B:
continue
candidate = (B - {k for k in B if k > m}) | {m}

# ⚡ Bolt Optimization: explicit comprehension avoids double allocation
# compared to set difference `(B - {k for k in B if k > m}) | {m}`
candidate = {k for k in B if k < m}
candidate.add(m)

closure = self.attrs_of(self.objects_of(frozenset(candidate)))
# The lectic-next-closure step requires the closure to
# introduce no attribute smaller than ``m`` that was not
# already in B.
if all(k >= m or k in B for k in (closure - candidate)):

# ⚡ Bolt Optimization: explicit early-exit loop replaces all() with generator
# to avoid significant function call overhead on hot paths.
valid = True
for k in closure - candidate:
if k < m and k not in B:
valid = False
break

if valid:
return set(closure)
return None

Expand Down
Loading