Skip to content

Commit a389ff1

Browse files
authored
Multipod external fix (#485)
1 parent c185d27 commit a389ff1

File tree

3 files changed

+692
-113
lines changed

3 files changed

+692
-113
lines changed

src/frontend/multi_pod/deps.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,22 @@ use crate::{
1111
middleware::{Hash, Statement},
1212
};
1313

14+
/// Reference to a statement sourced from an external input POD.
15+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
16+
pub struct ExternalDependency {
17+
/// Hash of the external POD containing `statement` in its public set.
18+
pub pod_hash: Hash,
19+
/// The statement value itself.
20+
pub statement: Statement,
21+
}
22+
1423
/// Represents a source of a statement dependency.
1524
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1625
pub enum StatementSource {
1726
/// Statement created within this builder at the given index.
1827
Internal(usize),
19-
/// Statement from an external input POD (identified by POD hash).
20-
External(Hash),
28+
/// Statement from an external input POD.
29+
External(ExternalDependency),
2130
}
2231

2332
/// Dependency graph for all statements in a builder.
@@ -87,7 +96,10 @@ impl DependencyGraph {
8796

8897
// Check if this is from an external POD
8998
if let Some(&pod_hash) = external_pod_statements.get(dep_stmt) {
90-
deps.push(StatementSource::External(pod_hash));
99+
deps.push(StatementSource::External(ExternalDependency {
100+
pod_hash,
101+
statement: dep_stmt.clone(),
102+
}));
91103
} else if AnchoredKeyId::from_contains_statement(dep_stmt).is_some() {
92104
// Anchored-key Contains args may be implicit requirements that are
93105
// auto-materialized by MainPodBuilder. They are handled by anchored-key

src/frontend/multi_pod/mod.rs

Lines changed: 33 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,8 @@ impl SolvedMultiPod {
295295
added_statements_by_content.insert(original_stmt, stmt);
296296
}
297297

298-
// For the output pod, make statements public in the original order
298+
// For the output pod, make statements public in the original order.
299+
// Intermediate pods use the solver-selected public set.
299300
if pod_idx == solution.pod_count - 1 {
300301
for idx in &self.output_public_indices {
301302
let stmt = added_statements_by_content
@@ -312,6 +313,13 @@ impl SolvedMultiPod {
312313
}
313314
}
314315

316+
// Forward external premises only when the solver selected them as public
317+
// for this POD. These do not require local proving in this POD.
318+
for ext_premise_idx in &solution.pod_public_external_premises[pod_idx] {
319+
let ext_premise = &solution.external_premises[*ext_premise_idx];
320+
builder.reveal(&ext_premise.statement);
321+
}
322+
315323
// Step 4: Prove the POD
316324
let pod = builder.prove(prover)?;
317325

@@ -323,36 +331,17 @@ impl SolvedMultiPod {
323331
/// Returns (internal_pod_indices, external_pod_indices).
324332
fn compute_pod_inputs(&self, pod_idx: usize) -> (BTreeSet<usize>, BTreeSet<usize>) {
325333
let solution = &self.solution;
326-
let statements_in_pod = &solution.pod_statements[pod_idx];
327-
328-
let mut internal_pods: BTreeSet<usize> = BTreeSet::new();
334+
let internal_pods = solution.pod_internal_inputs[pod_idx].clone();
329335
let mut external_pods: BTreeSet<usize> = BTreeSet::new();
330336

331-
for &stmt_idx in statements_in_pod {
332-
for dep in &self.deps.statement_deps[stmt_idx] {
333-
match dep {
334-
StatementSource::Internal(dep_idx) => {
335-
// Check if dependency is in an earlier POD (not local)
336-
if !statements_in_pod.contains(dep_idx) {
337-
let earlier_pod_idx = (0..pod_idx)
338-
.find(|earlier_pod_idx| {
339-
solution.pod_public_statements[*earlier_pod_idx]
340-
.contains(dep_idx)
341-
})
342-
.expect("internal pod with dependency statement");
343-
internal_pods.insert(earlier_pod_idx);
344-
}
345-
}
346-
StatementSource::External(pod_hash) => {
347-
let idx = self
348-
.input_pods
349-
.iter()
350-
.position(|p| p.statements_hash() == *pod_hash)
351-
.expect("external pod with dependency statement");
352-
external_pods.insert(idx);
353-
}
354-
}
355-
}
337+
for external_idx in &solution.pod_external_inputs[pod_idx] {
338+
let pod_hash = solution.external_pod_hashes[*external_idx];
339+
let idx = self
340+
.input_pods
341+
.iter()
342+
.position(|p| p.statements_hash() == pod_hash)
343+
.expect("external pod hash from solver solution");
344+
external_pods.insert(idx);
356345
}
357346

358347
assert!(internal_pods.len() + external_pods.len() <= self.params.max_input_pods);
@@ -1196,16 +1185,16 @@ mod tests {
11961185

11971186
#[test]
11981187
fn test_external_pods_counted_in_input_limit() -> Result<()> {
1199-
// Verifies that external input PODs are counted toward max_input_pods.
1188+
// Verifies that external input PODs are counted toward max_input_pods while
1189+
// still allowing the solver to route external premises through intermediate PODs.
12001190
//
12011191
// Setup:
12021192
// - max_input_pods = 2
12031193
// - 3 external PODs (A, B, C), each with a public statement
12041194
// - 3 public operations, each copying from a different external POD
12051195
//
1206-
// Since all 3 must be public in POD 0 (the output POD), and POD 0 would need
1207-
// all 3 external PODs as inputs (3 > max_input_pods), this is infeasible.
1208-
// The solver should correctly detect and report this.
1196+
// A direct 1-POD layout would need 3 external inputs in the output POD (infeasible),
1197+
// so the solver should split the work and keep each generated POD within input limits.
12091198

12101199
let params = Params {
12111200
max_statements: 10,
@@ -1256,26 +1245,24 @@ mod tests {
12561245
multi_builder.add_pod(ext_pod_b)?;
12571246
multi_builder.add_pod(ext_pod_c)?;
12581247

1259-
// Add public operations that each depend on a different external POD
1260-
// All 3 must be public in POD 0, requiring 3 external inputs > max_input_pods
1248+
// Add public operations that each depend on a different external POD.
12611249
multi_builder.pub_op(FrontendOp::copy(stmt_a))?;
12621250
multi_builder.pub_op(FrontendOp::copy(stmt_b))?;
12631251
multi_builder.pub_op(FrontendOp::copy(stmt_c))?;
12641252

1265-
// Solver should correctly detect infeasibility and return an error
1266-
let result = multi_builder.solve();
1253+
// Solver should find a feasible multi-POD layout that respects input limits.
1254+
let solved = multi_builder.solve()?;
12671255
assert!(
1268-
result.is_err(),
1269-
"Expected solver to report infeasibility, but got: {:?}",
1270-
result
1256+
solved.solution().pod_count >= 2,
1257+
"Expected at least 2 PODs to satisfy max_input_pods=2 with 3 external sources"
12711258
);
12721259

1273-
let err_msg = result.unwrap_err().to_string();
1274-
assert!(
1275-
err_msg.contains("No feasible solution"),
1276-
"Expected 'No feasible solution' error, got: {}",
1277-
err_msg
1278-
);
1260+
let result = solved.prove(&prover)?;
1261+
for (i, pod) in result.pods.iter().enumerate() {
1262+
pod.pod
1263+
.verify()
1264+
.unwrap_or_else(|_| panic!("POD {} verification failed", i));
1265+
}
12791266

12801267
Ok(())
12811268
}

0 commit comments

Comments
 (0)