Skip to content

Commit 6178245

Browse files
weinbe58claude
andauthored
refactor(core): consolidate check_lane_strict into check_lane (#273) (#289)
* refactor(core): consolidate check_lane_strict into check_lane (#273) Replace check_lane with the stricter validation that also verifies the site/word is a valid forward source for the bus. Remove check_lane_strict as a separate function. Fixes: - Bus.resolve_forward/resolve_backward now use .get() to avoid panicking on mismatched src/dst lengths - Example arch spec lane encoding updated from 0xC000000000010000 (word_id=1, old convention) to 0xC000000000000000 (word_id=0, correct forward-source convention) - AOD rectangle tests updated to use valid forward source sites across two words instead of destination sites on one word Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(core): update group_rectangle smoke test for stricter check_lane The .sst test file used sites 5,6 (bus destinations) as forward sources. Updated to use valid forward sources across two words to form the same 2x2 rectangle geometry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(core): add site_id bounds check for WordBus lanes and clarify docs - check_lane now validates site_id is in range for WordBus lanes (previously only word_id was checked, but site_id is used in lane_endpoints to build LocationAddr) - Clarify archspec.md lane example: word_id encodes the forward source for that specific lane, not necessarily word 0 in general Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent bf28857 commit 6178245

File tree

8 files changed

+97
-93
lines changed

8 files changed

+97
-93
lines changed

crates/bloqade-lanes-bytecode-core/src/arch/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ pub(crate) fn example_arch_spec() -> ArchSpec {
5858
"entangling_zones": [0],
5959
"measurement_mode_zones": [0],
6060
"paths": [
61-
{"lane": "0xC000000000010000", "waypoints": [[1.0, 12.5], [1.0, 7.5], [1.0, 2.5]]}
61+
{"lane": "0xC000000000000000", "waypoints": [[1.0, 12.5], [1.0, 7.5], [1.0, 2.5]]}
6262
]
6363
}"#;
6464
serde_json::from_str(json).unwrap()

crates/bloqade-lanes-bytecode-core/src/arch/query.rs

Lines changed: 48 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,20 @@ impl Bus {
111111
/// For site buses, this maps source site → destination site.
112112
/// For word buses, this maps source word → destination word.
113113
pub fn resolve_forward(&self, src: u32) -> Option<u32> {
114-
self.src.iter().position(|&s| s == src).map(|i| self.dst[i])
114+
self.src
115+
.iter()
116+
.position(|&s| s == src)
117+
.and_then(|i| self.dst.get(i).copied())
115118
}
116119

117120
/// Given a destination value, return the source value (backward move).
118121
/// For site buses, this maps destination site → source site.
119122
/// For word buses, this maps destination word → source word.
120123
pub fn resolve_backward(&self, dst: u32) -> Option<u32> {
121-
self.dst.iter().position(|&d| d == dst).map(|i| self.src[i])
124+
self.dst
125+
.iter()
126+
.position(|&d| d == dst)
127+
.and_then(|i| self.src.get(i).copied())
122128
}
123129
}
124130

@@ -189,7 +195,7 @@ impl ArchSpec {
189195

190196
// Validate the lane address up front so callers always get None
191197
// for invalid lanes (e.g. out-of-range word_id or site_id).
192-
if !self.check_lane_strict(lane).is_empty() {
198+
if !self.check_lane(lane).is_empty() {
193199
return None;
194200
}
195201

@@ -262,76 +268,53 @@ impl ArchSpec {
262268
}
263269
}
264270

265-
/// Check whether a lane address is valid (bus exists, word/site in range).
271+
/// Check whether a lane address is valid.
272+
///
273+
/// Validates that the bus exists, word/site are in range, and the
274+
/// site/word is a valid forward source for the bus. In the lane address
275+
/// convention, `site_id` and `word_id` always encode the forward-direction
276+
/// source — the `direction` field only controls which endpoint is src vs
277+
/// dst, not which site/word is encoded.
266278
pub fn check_lane(&self, addr: &LaneAddr) -> Vec<String> {
267279
let num_words = self.geometry.words.len() as u32;
268280
let sites_per_word = self.geometry.sites_per_word;
269281
let mut errors = Vec::new();
270282

271283
match addr.move_type {
272284
MoveType::SiteBus => {
273-
if self.site_bus_by_id(addr.bus_id).is_none() {
274-
errors.push(format!("unknown site_bus id {}", addr.bus_id));
275-
}
276285
if addr.word_id >= num_words {
277286
errors.push(format!("word_id {} out of range", addr.word_id));
278287
}
279288
if addr.site_id >= sites_per_word {
280289
errors.push(format!("site_id {} out of range", addr.site_id));
281290
}
291+
if let Some(bus) = self.site_bus_by_id(addr.bus_id) {
292+
if errors.is_empty() && bus.resolve_forward(addr.site_id).is_none() {
293+
errors.push(format!(
294+
"site_id {} is not a valid source for site_bus {}",
295+
addr.site_id, addr.bus_id
296+
));
297+
}
298+
} else {
299+
errors.push(format!("unknown site_bus id {}", addr.bus_id));
300+
}
282301
}
283302
MoveType::WordBus => {
284-
if self.word_bus_by_id(addr.bus_id).is_none() {
285-
errors.push(format!("unknown word_bus id {}", addr.bus_id));
286-
}
287303
if addr.word_id >= num_words {
288304
errors.push(format!("word_id {} out of range", addr.word_id));
289305
}
290-
}
291-
}
292-
errors
293-
}
294-
295-
/// Strict lane validation: checks everything in [`check_lane`] plus
296-
/// verifies that the site/word can be resolved through the bus.
297-
///
298-
/// In the lane address convention, `site_id` and `word_id` always refer
299-
/// to the forward-direction source. The `direction` field only controls
300-
/// which endpoint is src vs dst in the result — it does not change which
301-
/// site/word is encoded. Therefore, validation always checks against the
302-
/// bus's forward resolution (`bus.src` list).
303-
///
304-
/// This is used by [`lane_endpoints`](Self::lane_endpoints) to guarantee
305-
/// that returned endpoints are fully valid.
306-
pub fn check_lane_strict(&self, addr: &LaneAddr) -> Vec<String> {
307-
// Start with the basic checks
308-
let mut errors = self.check_lane(addr);
309-
if !errors.is_empty() {
310-
return errors;
311-
}
312-
313-
// Verify the site/word is a valid forward source for the bus.
314-
// The direction field only flips src/dst — it doesn't change
315-
// which site/word is encoded in the lane address.
316-
match addr.move_type {
317-
MoveType::SiteBus => {
318-
if let Some(bus) = self.site_bus_by_id(addr.bus_id)
319-
&& bus.resolve_forward(addr.site_id).is_none()
320-
{
321-
errors.push(format!(
322-
"site_id {} is not a valid source for site_bus {}",
323-
addr.site_id, addr.bus_id
324-
));
306+
if addr.site_id >= sites_per_word {
307+
errors.push(format!("site_id {} out of range", addr.site_id));
325308
}
326-
}
327-
MoveType::WordBus => {
328-
if let Some(bus) = self.word_bus_by_id(addr.bus_id)
329-
&& bus.resolve_forward(addr.word_id).is_none()
330-
{
331-
errors.push(format!(
332-
"word_id {} is not a valid source for word_bus {}",
333-
addr.word_id, addr.bus_id
334-
));
309+
if let Some(bus) = self.word_bus_by_id(addr.bus_id) {
310+
if errors.is_empty() && bus.resolve_forward(addr.word_id).is_none() {
311+
errors.push(format!(
312+
"word_id {} is not a valid source for word_bus {}",
313+
addr.word_id, addr.bus_id
314+
));
315+
}
316+
} else {
317+
errors.push(format!("unknown word_bus id {}", addr.bus_id));
335318
}
336319
}
337320
}
@@ -850,10 +833,10 @@ mod tests {
850833
assert!(spec.lane_endpoints(&lane).is_none());
851834
}
852835

853-
// ── check_lane_strict tests ──
836+
// ── check_lane tests ──
854837

855838
#[test]
856-
fn check_lane_strict_valid_forward() {
839+
fn check_lane_valid_forward() {
857840
let spec = example_arch_spec();
858841
let lane = crate::arch::addr::LaneAddr {
859842
direction: crate::arch::addr::Direction::Forward,
@@ -862,11 +845,11 @@ mod tests {
862845
site_id: 0,
863846
bus_id: 0,
864847
};
865-
assert!(spec.check_lane_strict(&lane).is_empty());
848+
assert!(spec.check_lane(&lane).is_empty());
866849
}
867850

868851
#[test]
869-
fn check_lane_strict_valid_backward() {
852+
fn check_lane_valid_backward() {
870853
let spec = example_arch_spec();
871854
// Backward with forward source site_id=0 should be valid
872855
let lane = crate::arch::addr::LaneAddr {
@@ -876,11 +859,11 @@ mod tests {
876859
site_id: 0,
877860
bus_id: 0,
878861
};
879-
assert!(spec.check_lane_strict(&lane).is_empty());
862+
assert!(spec.check_lane(&lane).is_empty());
880863
}
881864

882865
#[test]
883-
fn check_lane_strict_destination_site_rejected() {
866+
fn check_lane_destination_site_rejected() {
884867
let spec = example_arch_spec();
885868
// Site 5 is a destination, not a forward source
886869
let lane = crate::arch::addr::LaneAddr {
@@ -890,13 +873,13 @@ mod tests {
890873
site_id: 5,
891874
bus_id: 0,
892875
};
893-
let errors = spec.check_lane_strict(&lane);
876+
let errors = spec.check_lane(&lane);
894877
assert!(!errors.is_empty());
895878
assert!(errors[0].contains("not a valid source"));
896879
}
897880

898881
#[test]
899-
fn check_lane_strict_destination_site_backward_also_rejected() {
882+
fn check_lane_destination_site_backward_also_rejected() {
900883
let spec = example_arch_spec();
901884
// Site 5 with backward direction — still not a forward source
902885
let lane = crate::arch::addr::LaneAddr {
@@ -906,13 +889,13 @@ mod tests {
906889
site_id: 5,
907890
bus_id: 0,
908891
};
909-
let errors = spec.check_lane_strict(&lane);
892+
let errors = spec.check_lane(&lane);
910893
assert!(!errors.is_empty());
911894
assert!(errors[0].contains("not a valid source"));
912895
}
913896

914897
#[test]
915-
fn check_lane_strict_invalid_bus() {
898+
fn check_lane_invalid_bus() {
916899
let spec = example_arch_spec();
917900
let lane = crate::arch::addr::LaneAddr {
918901
direction: crate::arch::addr::Direction::Forward,
@@ -921,7 +904,7 @@ mod tests {
921904
site_id: 0,
922905
bus_id: 99,
923906
};
924-
let errors = spec.check_lane_strict(&lane);
907+
let errors = spec.check_lane(&lane);
925908
assert!(!errors.is_empty());
926909
}
927910
}

crates/bloqade-lanes-bytecode-core/src/bytecode/validate.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,10 +1073,17 @@ mod tests {
10731073
#[test]
10741074
fn test_lane_group_aod_constraint_rectangle_passes() {
10751075
let arch = lane_group_arch_spec();
1076-
let lanes: Vec<(u32, u32)> = [0, 1, 5, 6]
1077-
.iter()
1078-
.map(|&s| make_lane(Direction::Forward, MoveType::SiteBus, 0, s, 0))
1079-
.collect();
1076+
// Use valid forward sources on two words to form a 2x2 rectangle:
1077+
// Word 0, Site 0: (1.0, 2.5)
1078+
// Word 0, Site 1: (3.0, 2.5)
1079+
// Word 1, Site 0: (1.0, 12.5)
1080+
// Word 1, Site 1: (3.0, 12.5)
1081+
let lanes: Vec<(u32, u32)> = vec![
1082+
make_lane(Direction::Forward, MoveType::SiteBus, 0, 0, 0),
1083+
make_lane(Direction::Forward, MoveType::SiteBus, 0, 1, 0),
1084+
make_lane(Direction::Forward, MoveType::SiteBus, 1, 0, 0),
1085+
make_lane(Direction::Forward, MoveType::SiteBus, 1, 1, 0),
1086+
];
10801087
let program = Program {
10811088
version: Version::new(1, 0),
10821089
instructions: vec![
@@ -1094,10 +1101,15 @@ mod tests {
10941101
#[test]
10951102
fn test_lane_group_aod_constraint_not_rectangle() {
10961103
let arch = lane_group_arch_spec();
1097-
let lanes: Vec<(u32, u32)> = [0, 1, 5]
1098-
.iter()
1099-
.map(|&s| make_lane(Direction::Forward, MoveType::SiteBus, 0, s, 0))
1100-
.collect();
1104+
// 3 corners of a rectangle (missing word 1, site 1) — not a complete rectangle
1105+
// Word 0, Site 0: (1.0, 2.5)
1106+
// Word 0, Site 1: (3.0, 2.5)
1107+
// Word 1, Site 0: (1.0, 12.5)
1108+
let lanes: Vec<(u32, u32)> = vec![
1109+
make_lane(Direction::Forward, MoveType::SiteBus, 0, 0, 0),
1110+
make_lane(Direction::Forward, MoveType::SiteBus, 0, 1, 0),
1111+
make_lane(Direction::Forward, MoveType::SiteBus, 1, 0, 0),
1112+
];
11011113
let program = Program {
11021114
version: Version::new(1, 0),
11031115
instructions: vec![

docs/src/arch/archspec.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ The lane is identified by its encoded `LaneAddr`, serialized as a hex string. Se
151151
```jsonc
152152
"paths": [
153153
{
154-
"lane": "0xC000000000010000", // encoded LaneAddr (hex, 16-digit)
154+
"lane": "0xC000000000000000", // encoded LaneAddr (hex, 16-digit)
155155
"waypoints": [[1.0, 12.5], [1.0, 7.5], [1.0, 2.5]] // physical trajectory
156156
}
157157
]
@@ -164,7 +164,7 @@ Each `TransportPath` entry has:
164164
| `lane` | string | Encoded `LaneAddr` as a `"0x..."` hex string. |
165165
| `waypoints` | [x, y][] | Sequence of physical coordinate waypoints. |
166166

167-
To decode the lane hex string, parse it as a 64-bit unsigned integer. The low 32 bits (data0) contain `[word_id:16][site_id:16]` and the high 32 bits (data1) contain `[dir:1][mt:1][pad:14][bus_id:16]`. For example, `"0xC000000000010000"` decodes to direction=Backward, move_type=WordBus, word=1, site=0, bus=0.
167+
To decode the lane hex string, parse it as a 64-bit unsigned integer. The low 32 bits (data0) contain `[word_id:16][site_id:16]` and the high 32 bits (data1) contain `[dir:1][mt:1][pad:14][bus_id:16]`. For example, `"0xC000000000000000"` decodes to direction=Backward, move_type=WordBus, word=0, site=0, bus=0. In the lane address convention, `word_id` always encodes the forward-direction source word for that lane; in this specific example the bus mapping is src=[0], dst=[1], so `direction=Backward` means the move goes from word 1 back to word 0.
168168

169169
This field is omitted from the JSON when not needed.
170170

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
.version 1
22
; Move 4 lanes forming a 2x2 rectangle on the grid.
3-
; Word 0 grid: x=[1,3,5,7,9] y=[2.5,5.0]
4-
; Sites 0-4 = row 0 (y=2.5), sites 5-9 = row 1 (y=5.0)
5-
; Sites 0,1,5,6 -> (1,2.5),(3,2.5),(1,5.0),(3,5.0) = 2x2 rectangle
63
; site_bus 0: src=[0,1,2,3,4] dst=[5,6,7,8,9]
4+
; Word 0: y_start=2.5, Word 1: y_start=12.5
5+
; Site 0: x=1.0, Site 1: x=3.0
6+
; (word 0, site 0) = (1.0, 2.5)
7+
; (word 0, site 1) = (3.0, 2.5)
8+
; (word 1, site 0) = (1.0, 12.5)
9+
; (word 1, site 1) = (3.0, 12.5)
10+
; = 2x2 rectangle using valid forward sources
711

812
const_lane 0x0000000000000000 ; fwd, site_bus, word 0, site 0, bus 0
913
const_lane 0x0000000000000001 ; fwd, site_bus, word 0, site 1, bus 0
10-
const_lane 0x0000000000000005 ; fwd, site_bus, word 0, site 5, bus 0
11-
const_lane 0x0000000000000006 ; fwd, site_bus, word 0, site 6, bus 0
14+
const_lane 0x0000000000010000 ; fwd, site_bus, word 1, site 0, bus 0
15+
const_lane 0x0000000000010001 ; fwd, site_bus, word 1, site 1, bus 0
1216
move 4
1317
halt

python/bloqade/lanes/bytecode/_native.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ class TransportPath:
475475
lane (LaneAddress): Lane address identifying the transport lane.
476476
waypoints (list[tuple[float, float]]): Sequence of ``(x, y)`` coordinate waypoints.
477477
478-
Note: In JSON, the lane is serialized as a 16-digit hex string (e.g. ``"0xC000000000010000"``).
478+
Note: In JSON, the lane is serialized as a 16-digit hex string (e.g. ``"0xC000000000000000"``).
479479
"""
480480

481481
def __init__(

python/tests/bytecode/test_arch_spec.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
"measurement_mode_zones": [0],
109109
"paths": [
110110
{
111-
"lane": "0xC000000000010000",
111+
"lane": "0xC000000000000000",
112112
"waypoints": [[1.0, 12.5], [1.0, 7.5], [1.0, 2.5]],
113113
}
114114
],
@@ -174,7 +174,7 @@ def _build_spec_from_python():
174174
TransportPath(
175175
lane=LaneAddress(
176176
MoveType.WORD,
177-
word_id=1,
177+
word_id=0,
178178
site_id=0,
179179
bus_id=0,
180180
direction=Direction.BACKWARD,
@@ -331,11 +331,11 @@ def test_paths(self):
331331
spec = ArchSpec.from_json(EXAMPLE_JSON)
332332
assert spec.paths is not None
333333
assert len(spec.paths) == 1
334-
assert spec.paths[0].lane_encoded == 0xC000000000010000
334+
assert spec.paths[0].lane_encoded == 0xC000000000000000
335335
lane = spec.paths[0].lane
336336
assert lane.direction == Direction.BACKWARD
337337
assert lane.move_type == MoveType.WORD
338-
assert lane.word_id == 1
338+
assert lane.word_id == 0
339339
assert lane.site_id == 0
340340
assert lane.bus_id == 0
341341
assert len(spec.paths[0].waypoints) == 3
@@ -522,20 +522,25 @@ def test_consistency_fail_direction(self):
522522

523523
def test_aod_constraint_rectangle_pass(self):
524524
spec = ArchSpec.from_json(EXAMPLE_JSON)
525-
# 2x2 rectangle: sites 0,1,5,6
525+
# 2x2 rectangle using valid forward sources on 2 words:
526+
# Word 0, Site 0: (1.0, 2.5) Word 0, Site 1: (3.0, 2.5)
527+
# Word 1, Site 0: (1.0, 12.5) Word 1, Site 1: (3.0, 12.5)
526528
lanes = [
527-
LaneAddress(MoveType.SITE, word_id=0, site_id=s, bus_id=0)
528-
for s in [0, 1, 5, 6]
529+
LaneAddress(MoveType.SITE, word_id=0, site_id=0, bus_id=0),
530+
LaneAddress(MoveType.SITE, word_id=0, site_id=1, bus_id=0),
531+
LaneAddress(MoveType.SITE, word_id=1, site_id=0, bus_id=0),
532+
LaneAddress(MoveType.SITE, word_id=1, site_id=1, bus_id=0),
529533
]
530534
errors = spec.check_lanes(lanes)
531535
assert errors == []
532536

533537
def test_aod_constraint_not_rectangle(self):
534538
spec = ArchSpec.from_json(EXAMPLE_JSON)
535-
# L-shape: sites 0,1,5
539+
# 3 corners of a rectangle (missing word 1, site 1)
536540
lanes = [
537-
LaneAddress(MoveType.SITE, word_id=0, site_id=s, bus_id=0)
538-
for s in [0, 1, 5]
541+
LaneAddress(MoveType.SITE, word_id=0, site_id=0, bus_id=0),
542+
LaneAddress(MoveType.SITE, word_id=0, site_id=1, bus_id=0),
543+
LaneAddress(MoveType.SITE, word_id=1, site_id=0, bus_id=0),
539544
]
540545
errors = spec.check_lanes(lanes)
541546
assert len(errors) > 0

tests/arch_spec_integration.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const EXAMPLE_JSON: &str = r#"{
3333
"entangling_zones": [0],
3434
"measurement_mode_zones": [0],
3535
"paths": [
36-
{"lane": "0xC000000000010000", "waypoints": [[1.0, 12.5], [1.0, 7.5], [1.0, 2.5]]}
36+
{"lane": "0xC000000000000000", "waypoints": [[1.0, 12.5], [1.0, 7.5], [1.0, 2.5]]}
3737
]
3838
}"#;
3939

0 commit comments

Comments
 (0)