Skip to main content

xlog_prob/compilation/
gpu_cnf.rs

1//! GPU-native Tseitin CNF encoding for PIR graphs.
2
3use std::ffi::c_void;
4use std::sync::Arc;
5
6use cudarc::driver::LaunchConfig;
7use xlog_core::{Result, XlogError};
8use xlog_cuda::memory::TrackedCudaSlice;
9use xlog_cuda::provider::{cnf_kernels, CNF_MODULE};
10use xlog_cuda::{AsKernelParam, CudaKernelProvider, LaunchAsync};
11use xlog_solve::GpuCnf;
12
13use crate::compilation::gpu_pir::GpuPirGraph;
14use crate::compilation::gpu_pir::GpuPirRoots;
15
16/// GPU-resident CNF variable tables for PIR ids.
17pub struct GpuCnfVarTables {
18    /// DIMACS variable assigned to each PIR node.
19    pub node_var: TrackedCudaSlice<u32>,
20    /// DIMACS variable assigned to each probabilistic leaf.
21    pub leaf_var: TrackedCudaSlice<u32>,
22    /// DIMACS variable assigned to each choice variable.
23    pub choice_var: TrackedCudaSlice<u32>,
24    /// Largest assigned DIMACS variable.
25    pub max_var: u32,
26}
27
28/// GPU-resident CNF encoding bundle (CNF + var tables).
29pub struct GpuCnfEncoding {
30    /// Encoded clauses and offsets resident on the device.
31    pub cnf: GpuCnf,
32    /// Maps from PIR identities to DIMACS variables.
33    pub vars: GpuCnfVarTables,
34    /// Largest variable id that is semantically meaningful and should be eligible for branching
35    /// in the GPU CDCL verifier (len = 1, device-resident).
36    ///
37    /// For PIR Tseitin encodings this is the end of the leaf+choice var range, excluding internal
38    /// node Tseitin vars which are propagation-only.
39    pub decision_var_limit: TrackedCudaSlice<u32>,
40}
41
42const MAX_GRID_X: u64 = 65_535;
43
44fn checked_grid_dim(n: u32, block: u32, context: &str) -> Result<u32> {
45    if block == 0 {
46        return Err(XlogError::Kernel(format!(
47            "{context}: CUDA launch block size must be nonzero"
48        )));
49    }
50    let grid = if n == 0 {
51        1
52    } else {
53        u64::from(n).div_ceil(u64::from(block))
54    };
55    if grid > MAX_GRID_X {
56        return Err(XlogError::Kernel(format!(
57            "{context}: launch grid {grid} exceeds x-dimension limit {MAX_GRID_X} \
58             for {n} elements with block size {block}"
59        )));
60    }
61    Ok(grid as u32)
62}
63
64/// Encode a GPU PIR graph into GPU-resident Tseitin CNF.
65pub fn encode_cnf_gpu(
66    pir: &GpuPirGraph,
67    roots: &GpuPirRoots,
68    provider: &Arc<CudaKernelProvider>,
69) -> Result<GpuCnfEncoding> {
70    if roots.roots.is_empty() {
71        return Err(XlogError::Compilation(
72            "Cannot encode CNF for empty PIR root set".to_string(),
73        ));
74    }
75    let num_nodes = pir.node_type.len();
76    if num_nodes == 0 {
77        return Err(XlogError::Compilation(
78            "Cannot encode CNF for empty PIR graph".to_string(),
79        ));
80    }
81
82    let num_nodes_u32 = u32::try_from(num_nodes)
83        .map_err(|_| XlogError::Compilation("PIR node count overflow".to_string()))?;
84    let num_roots_u32 = u32::try_from(roots.roots.len())
85        .map_err(|_| XlogError::Compilation("PIR root count exceeds u32::MAX".to_string()))?;
86
87    let num_edges = pir.children.len();
88    let n64 = num_nodes as u64;
89    let e64 = num_edges as u64;
90
91    let var_cap = u32::try_from(
92        n64.checked_mul(3)
93            .ok_or_else(|| XlogError::Kernel("CNF var capacity overflow".to_string()))?,
94    )
95    .map_err(|_| XlogError::Kernel("CNF var capacity exceeds u32::MAX".to_string()))?;
96    let clause_cap = u32::try_from(
97        e64.checked_add(
98            n64.checked_mul(4)
99                .ok_or_else(|| XlogError::Kernel("CNF clause capacity overflow".to_string()))?,
100        )
101        .ok_or_else(|| XlogError::Kernel("CNF clause capacity overflow".to_string()))?,
102    )
103    .map_err(|_| XlogError::Kernel("CNF clause capacity exceeds u32::MAX".to_string()))?;
104    let lit_cap =
105        u32::try_from(
106            e64.checked_mul(3)
107                .ok_or_else(|| XlogError::Kernel("CNF literal capacity overflow".to_string()))?
108                .checked_add(n64.checked_mul(12).ok_or_else(|| {
109                    XlogError::Kernel("CNF literal capacity overflow".to_string())
110                })?)
111                .ok_or_else(|| XlogError::Kernel("CNF literal capacity overflow".to_string()))?,
112        )
113        .map_err(|_| XlogError::Kernel("CNF literal capacity exceeds u32::MAX".to_string()))?;
114
115    let leaf_cap = num_nodes_u32;
116    let choice_cap = num_nodes_u32;
117
118    let memory = provider.memory();
119    let device = provider.device().inner();
120
121    let mut reachable = memory.alloc::<u32>(num_nodes)?;
122    let mut queue = memory.alloc::<u32>(num_nodes)?;
123    let mut queue_ready = memory.alloc::<u32>(num_nodes)?;
124    let mut head = memory.alloc::<u32>(1)?;
125    let mut tail = memory.alloc::<u32>(1)?;
126    let mut in_flight = memory.alloc::<u32>(1)?;
127
128    let mut leaf_used = memory.alloc::<u32>(leaf_cap as usize)?;
129    let mut choice_used = memory.alloc::<u32>(choice_cap as usize)?;
130    let mut leaf_var = memory.alloc::<u32>(leaf_cap as usize)?;
131    let mut choice_var = memory.alloc::<u32>(choice_cap as usize)?;
132
133    let mut node_needs_var = memory.alloc::<u32>(num_nodes)?;
134    let mut node_var = memory.alloc::<u32>(num_nodes)?;
135
136    let mut clause_counts = memory.alloc::<u32>(num_nodes)?;
137    let mut lit_counts = memory.alloc::<u32>(num_nodes)?;
138
139    let mut leaf_prefix = memory.alloc::<u32>(leaf_cap as usize)?;
140    let mut choice_prefix = memory.alloc::<u32>(choice_cap as usize)?;
141
142    let mut node_last = memory.alloc::<u32>(1)?;
143    let mut clause_last = memory.alloc::<u32>(1)?;
144    let mut lit_last = memory.alloc::<u32>(1)?;
145
146    let mut num_leaf = memory.alloc::<u32>(1)?;
147    let mut num_choice = memory.alloc::<u32>(1)?;
148    let mut base_choice = memory.alloc::<u32>(1)?;
149    let mut base_node = memory.alloc::<u32>(1)?;
150    let mut decision_var_limit = memory.alloc::<u32>(1)?;
151
152    let d_num_vars = memory.alloc::<u32>(1)?;
153    let d_num_clauses = memory.alloc::<u32>(1)?;
154    let d_num_lits = memory.alloc::<u32>(1)?;
155
156    let mut d_offsets = memory.alloc::<u32>((clause_cap as usize) + 1)?;
157    let d_lits = memory.alloc::<i32>(lit_cap as usize)?;
158
159    device
160        .memset_zeros(&mut reachable)
161        .map_err(|e| XlogError::Kernel(format!("zero reachable: {}", e)))?;
162    device
163        .memset_zeros(&mut queue)
164        .map_err(|e| XlogError::Kernel(format!("zero queue: {}", e)))?;
165    device
166        .memset_zeros(&mut queue_ready)
167        .map_err(|e| XlogError::Kernel(format!("zero queue_ready: {}", e)))?;
168    device
169        .memset_zeros(&mut head)
170        .map_err(|e| XlogError::Kernel(format!("zero head: {}", e)))?;
171    device
172        .memset_zeros(&mut tail)
173        .map_err(|e| XlogError::Kernel(format!("zero tail: {}", e)))?;
174    device
175        .memset_zeros(&mut in_flight)
176        .map_err(|e| XlogError::Kernel(format!("zero in_flight: {}", e)))?;
177    device
178        .memset_zeros(&mut leaf_used)
179        .map_err(|e| XlogError::Kernel(format!("zero leaf_used: {}", e)))?;
180    device
181        .memset_zeros(&mut choice_used)
182        .map_err(|e| XlogError::Kernel(format!("zero choice_used: {}", e)))?;
183    device
184        .memset_zeros(&mut leaf_var)
185        .map_err(|e| XlogError::Kernel(format!("zero leaf_var: {}", e)))?;
186    device
187        .memset_zeros(&mut choice_var)
188        .map_err(|e| XlogError::Kernel(format!("zero choice_var: {}", e)))?;
189    device
190        .memset_zeros(&mut node_needs_var)
191        .map_err(|e| XlogError::Kernel(format!("zero node_needs_var: {}", e)))?;
192    device
193        .memset_zeros(&mut node_var)
194        .map_err(|e| XlogError::Kernel(format!("zero node_var: {}", e)))?;
195    device
196        .memset_zeros(&mut clause_counts)
197        .map_err(|e| XlogError::Kernel(format!("zero clause_counts: {}", e)))?;
198    device
199        .memset_zeros(&mut lit_counts)
200        .map_err(|e| XlogError::Kernel(format!("zero lit_counts: {}", e)))?;
201
202    let reach_init_fn = device
203        .get_func(CNF_MODULE, cnf_kernels::CNF_REACHABILITY_INIT)
204        .ok_or_else(|| XlogError::Kernel("cnf_reachability_init kernel not found".to_string()))?;
205    let reach_bfs_fn = device
206        .get_func(CNF_MODULE, cnf_kernels::CNF_REACHABILITY_BFS)
207        .ok_or_else(|| XlogError::Kernel("cnf_reachability_bfs kernel not found".to_string()))?;
208    let mark_leaf_choice_fn = device
209        .get_func(CNF_MODULE, cnf_kernels::CNF_MARK_LEAF_CHOICE)
210        .ok_or_else(|| XlogError::Kernel("cnf_mark_leaf_choice kernel not found".to_string()))?;
211    let assign_leaf_var_fn = device
212        .get_func(CNF_MODULE, cnf_kernels::CNF_ASSIGN_LEAF_VAR)
213        .ok_or_else(|| XlogError::Kernel("cnf_assign_leaf_var kernel not found".to_string()))?;
214    let assign_choice_var_fn = device
215        .get_func(CNF_MODULE, cnf_kernels::CNF_ASSIGN_CHOICE_VAR)
216        .ok_or_else(|| XlogError::Kernel("cnf_assign_choice_var kernel not found".to_string()))?;
217    let mark_node_vars_fn = device
218        .get_func(CNF_MODULE, cnf_kernels::CNF_MARK_NODE_VARS)
219        .ok_or_else(|| XlogError::Kernel("cnf_mark_node_vars kernel not found".to_string()))?;
220    let count_clauses_fn = device
221        .get_func(CNF_MODULE, cnf_kernels::CNF_COUNT_CLAUSES)
222        .ok_or_else(|| XlogError::Kernel("cnf_count_clauses kernel not found".to_string()))?;
223    let capture_last_fn = device
224        .get_func(CNF_MODULE, cnf_kernels::CNF_CAPTURE_LAST_COUNTS)
225        .ok_or_else(|| XlogError::Kernel("cnf_capture_last_counts kernel not found".to_string()))?;
226    let leaf_choice_totals_fn = device
227        .get_func(CNF_MODULE, cnf_kernels::CNF_COMPUTE_LEAF_CHOICE_TOTALS)
228        .ok_or_else(|| {
229            XlogError::Kernel("cnf_compute_leaf_choice_totals kernel not found".to_string())
230        })?;
231    let compute_totals_fn = device
232        .get_func(CNF_MODULE, cnf_kernels::CNF_COMPUTE_TOTALS)
233        .ok_or_else(|| XlogError::Kernel("cnf_compute_totals kernel not found".to_string()))?;
234    let assign_node_var_fn = device
235        .get_func(CNF_MODULE, cnf_kernels::CNF_ASSIGN_NODE_VAR)
236        .ok_or_else(|| XlogError::Kernel("cnf_assign_node_var kernel not found".to_string()))?;
237    let emit_clauses_fn = device
238        .get_func(CNF_MODULE, cnf_kernels::CNF_EMIT_CLAUSES)
239        .ok_or_else(|| XlogError::Kernel("cnf_emit_clauses kernel not found".to_string()))?;
240    let set_clause_end_fn = device
241        .get_func(CNF_MODULE, cnf_kernels::CNF_SET_CLAUSE_END)
242        .ok_or_else(|| XlogError::Kernel("cnf_set_clause_end kernel not found".to_string()))?;
243
244    let block = 256u32;
245
246    let grid_roots = checked_grid_dim(num_roots_u32, block, "cnf_reachability_init")?;
247    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
248    unsafe {
249        reach_init_fn.clone().launch(
250            LaunchConfig {
251                grid_dim: (grid_roots, 1, 1),
252                block_dim: (block, 1, 1),
253                shared_mem_bytes: 0,
254            },
255            (
256                &roots.roots,
257                num_roots_u32,
258                num_nodes_u32,
259                &mut reachable,
260                &mut queue,
261                &mut queue_ready,
262                &mut head,
263                &mut tail,
264                &mut in_flight,
265            ),
266        )
267    }
268    .map_err(|e| XlogError::Kernel(format!("cnf_reachability_init failed: {}", e)))?;
269
270    let grid_nodes = checked_grid_dim(num_nodes_u32, block, "cnf node kernels")?;
271    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
272    unsafe {
273        reach_bfs_fn.clone().launch(
274            LaunchConfig {
275                grid_dim: (grid_nodes, 1, 1),
276                block_dim: (block, 1, 1),
277                shared_mem_bytes: 0,
278            },
279            (
280                &pir.node_type,
281                &pir.child_offsets,
282                &pir.children,
283                &pir.decision_child_false,
284                &pir.decision_child_true,
285                num_nodes_u32,
286                &mut reachable,
287                &mut queue,
288                &mut queue_ready,
289                &mut head,
290                &mut tail,
291                &mut in_flight,
292            ),
293        )
294    }
295    .map_err(|e| XlogError::Kernel(format!("cnf_reachability_bfs failed: {}", e)))?;
296
297    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
298    unsafe {
299        mark_leaf_choice_fn.clone().launch(
300            LaunchConfig {
301                grid_dim: (grid_nodes, 1, 1),
302                block_dim: (block, 1, 1),
303                shared_mem_bytes: 0,
304            },
305            (
306                &pir.node_type,
307                &pir.leaf_id,
308                &pir.decision_var,
309                &reachable,
310                num_nodes_u32,
311                leaf_cap,
312                choice_cap,
313                &mut leaf_used,
314                &mut choice_used,
315            ),
316        )
317    }
318    .map_err(|e| XlogError::Kernel(format!("cnf_mark_leaf_choice failed: {}", e)))?;
319
320    if leaf_cap > 0 {
321        device
322            .dtod_copy(&leaf_used, &mut leaf_prefix)
323            .map_err(|e| XlogError::Kernel(format!("copy leaf_used: {}", e)))?;
324        provider.exclusive_scan_u32_inplace(&mut leaf_prefix, leaf_cap)?;
325    }
326    if choice_cap > 0 {
327        device
328            .dtod_copy(&choice_used, &mut choice_prefix)
329            .map_err(|e| XlogError::Kernel(format!("copy choice_used: {}", e)))?;
330        provider.exclusive_scan_u32_inplace(&mut choice_prefix, choice_cap)?;
331    }
332
333    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
334    unsafe {
335        leaf_choice_totals_fn.clone().launch(
336            LaunchConfig {
337                grid_dim: (1, 1, 1),
338                block_dim: (1, 1, 1),
339                shared_mem_bytes: 0,
340            },
341            (
342                &leaf_prefix,
343                &leaf_used,
344                leaf_cap,
345                &choice_prefix,
346                &choice_used,
347                choice_cap,
348                &mut num_leaf,
349                &mut num_choice,
350                &mut base_choice,
351                &mut base_node,
352                &mut decision_var_limit,
353            ),
354        )
355    }
356    .map_err(|e| XlogError::Kernel(format!("cnf_compute_leaf_choice_totals failed: {}", e)))?;
357
358    if leaf_cap > 0 {
359        let grid_leaf = checked_grid_dim(leaf_cap, block, "cnf_assign_leaf_var")?;
360        // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
361        unsafe {
362            assign_leaf_var_fn.clone().launch(
363                LaunchConfig {
364                    grid_dim: (grid_leaf, 1, 1),
365                    block_dim: (block, 1, 1),
366                    shared_mem_bytes: 0,
367                },
368                (&leaf_used, &leaf_prefix, leaf_cap, &mut leaf_var),
369            )
370        }
371        .map_err(|e| XlogError::Kernel(format!("cnf_assign_leaf_var failed: {}", e)))?;
372    }
373    if choice_cap > 0 {
374        let grid_choice = checked_grid_dim(choice_cap, block, "cnf_assign_choice_var")?;
375        // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
376        unsafe {
377            assign_choice_var_fn.clone().launch(
378                LaunchConfig {
379                    grid_dim: (grid_choice, 1, 1),
380                    block_dim: (block, 1, 1),
381                    shared_mem_bytes: 0,
382                },
383                (
384                    &choice_used,
385                    &choice_prefix,
386                    choice_cap,
387                    &base_choice,
388                    &mut choice_var,
389                ),
390            )
391        }
392        .map_err(|e| XlogError::Kernel(format!("cnf_assign_choice_var failed: {}", e)))?;
393    }
394
395    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
396    unsafe {
397        mark_node_vars_fn.clone().launch(
398            LaunchConfig {
399                grid_dim: (grid_nodes, 1, 1),
400                block_dim: (block, 1, 1),
401                shared_mem_bytes: 0,
402            },
403            (
404                &pir.node_type,
405                &reachable,
406                num_nodes_u32,
407                &mut node_needs_var,
408            ),
409        )
410    }
411    .map_err(|e| XlogError::Kernel(format!("cnf_mark_node_vars failed: {}", e)))?;
412
413    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
414    unsafe {
415        count_clauses_fn.clone().launch(
416            LaunchConfig {
417                grid_dim: (grid_nodes, 1, 1),
418                block_dim: (block, 1, 1),
419                shared_mem_bytes: 0,
420            },
421            (
422                &pir.node_type,
423                &pir.child_offsets,
424                &reachable,
425                num_nodes_u32,
426                &mut clause_counts,
427                &mut lit_counts,
428            ),
429        )
430    }
431    .map_err(|e| XlogError::Kernel(format!("cnf_count_clauses failed: {}", e)))?;
432
433    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
434    unsafe {
435        capture_last_fn.clone().launch(
436            LaunchConfig {
437                grid_dim: (1, 1, 1),
438                block_dim: (1, 1, 1),
439                shared_mem_bytes: 0,
440            },
441            (
442                &node_needs_var,
443                &clause_counts,
444                &lit_counts,
445                num_nodes_u32,
446                &mut node_last,
447                &mut clause_last,
448                &mut lit_last,
449            ),
450        )
451    }
452    .map_err(|e| XlogError::Kernel(format!("cnf_capture_last_counts failed: {}", e)))?;
453
454    provider.exclusive_scan_u32_inplace(&mut node_needs_var, num_nodes_u32)?;
455    provider.exclusive_scan_u32_inplace(&mut clause_counts, num_nodes_u32)?;
456    provider.exclusive_scan_u32_inplace(&mut lit_counts, num_nodes_u32)?;
457
458    let mut totals_params: Vec<*mut c_void> = vec![
459        (&node_needs_var).as_kernel_param(),
460        (&clause_counts).as_kernel_param(),
461        (&lit_counts).as_kernel_param(),
462        (&node_last).as_kernel_param(),
463        (&clause_last).as_kernel_param(),
464        (&lit_last).as_kernel_param(),
465        num_nodes_u32.as_kernel_param(),
466        (&base_node).as_kernel_param(),
467        var_cap.as_kernel_param(),
468        clause_cap.as_kernel_param(),
469        lit_cap.as_kernel_param(),
470        (&d_num_vars).as_kernel_param(),
471        (&d_num_clauses).as_kernel_param(),
472        (&d_num_lits).as_kernel_param(),
473    ];
474    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
475    unsafe {
476        compute_totals_fn.clone().launch(
477            LaunchConfig {
478                grid_dim: (1, 1, 1),
479                block_dim: (1, 1, 1),
480                shared_mem_bytes: 0,
481            },
482            &mut totals_params,
483        )
484    }
485    .map_err(|e| XlogError::Kernel(format!("cnf_compute_totals failed: {}", e)))?;
486
487    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
488    unsafe {
489        assign_node_var_fn.clone().launch(
490            LaunchConfig {
491                grid_dim: (grid_nodes, 1, 1),
492                block_dim: (block, 1, 1),
493                shared_mem_bytes: 0,
494            },
495            (
496                &pir.node_type,
497                &pir.leaf_id,
498                &reachable,
499                &node_needs_var,
500                &base_node,
501                num_nodes_u32,
502                leaf_cap,
503                &leaf_var,
504                &mut node_var,
505            ),
506        )
507    }
508    .map_err(|e| XlogError::Kernel(format!("cnf_assign_node_var failed: {}", e)))?;
509
510    let mut emit_params: Vec<*mut c_void> = vec![
511        (&pir.node_type).as_kernel_param(),
512        (&pir.child_offsets).as_kernel_param(),
513        (&pir.children).as_kernel_param(),
514        (&pir.leaf_id).as_kernel_param(),
515        (&pir.decision_var).as_kernel_param(),
516        (&pir.decision_child_false).as_kernel_param(),
517        (&pir.decision_child_true).as_kernel_param(),
518        (&reachable).as_kernel_param(),
519        (&node_var).as_kernel_param(),
520        (&leaf_var).as_kernel_param(),
521        (&choice_var).as_kernel_param(),
522        (&clause_counts).as_kernel_param(),
523        (&lit_counts).as_kernel_param(),
524        num_nodes_u32.as_kernel_param(),
525        leaf_cap.as_kernel_param(),
526        choice_cap.as_kernel_param(),
527        (&d_offsets).as_kernel_param(),
528        (&d_lits).as_kernel_param(),
529    ];
530
531    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
532    unsafe {
533        emit_clauses_fn.clone().launch(
534            LaunchConfig {
535                grid_dim: (grid_nodes, 1, 1),
536                block_dim: (block, 1, 1),
537                shared_mem_bytes: 0,
538            },
539            &mut emit_params,
540        )
541    }
542    .map_err(|e| XlogError::Kernel(format!("cnf_emit_clauses failed: {}", e)))?;
543
544    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
545    unsafe {
546        set_clause_end_fn.clone().launch(
547            LaunchConfig {
548                grid_dim: (1, 1, 1),
549                block_dim: (1, 1, 1),
550                shared_mem_bytes: 0,
551            },
552            (&mut d_offsets, &d_num_clauses, &d_num_lits),
553        )
554    }
555    .map_err(|e| XlogError::Kernel(format!("cnf_set_clause_end failed: {}", e)))?;
556    // No device synchronize: returns device-resident CNF; same-stream ordering suffices.
557    Ok(GpuCnfEncoding {
558        cnf: GpuCnf {
559            var_cap,
560            clause_cap,
561            lit_cap,
562            num_vars: d_num_vars,
563            num_clauses: d_num_clauses,
564            num_lits: d_num_lits,
565            clause_offsets: d_offsets,
566            literals: d_lits,
567        },
568        vars: GpuCnfVarTables {
569            node_var,
570            leaf_var,
571            choice_var,
572            max_var: var_cap,
573        },
574        decision_var_limit,
575    })
576}