Skip to content

fix(cli): correct tuple-domain sizes in inspect#203

Merged
pesap merged 7 commits intomainfrom
fix/set_counting
Apr 24, 2026
Merged

fix(cli): correct tuple-domain sizes in inspect#203
pesap merged 7 commits intomainfrom
fix/set_counting

Conversation

@pesap
Copy link
Copy Markdown
Collaborator

@pesap pesap commented Apr 23, 2026

Summary

  • fix arco inspect set sizing for tuple domains by using tuple-row cardinality
  • include alias-aware set-size lookup for inspect bindings
  • fix tuple-domain constraint instance estimates to avoid Cartesian overcounting
  • add regression assertions in nodal-allocation inspect test

Validation

  • cargo fmt
  • cargo test -p arco-cli

@SebastianManriqueM
Copy link
Copy Markdown
Collaborator

@pesap why this is skipping tests? because binaries were not affected in this PR?

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes arco inspect sizing/instance estimates for tuple-domain sets by using tuple-row cardinality (and avoiding Cartesian overcounts), and adds regression coverage around the nodal-allocation example.

Changes:

  • Use tuple-row cardinality for tuple-domain set sizing in inspect output (including bindings via alias-aware lookup).
  • Correct constraint instance estimation to avoid overcounting when tuple-domain bindings repeat.
  • Update example/docs syntax (wherefilter) and add regression assertions in CLI tests.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
examples/nodal-allocation/input.kdl Updates example to use filter blocks for row filtering.
docs/appendix-a-ergonomic-profile.md Updates documentation examples to use filter instead of where.
crates/arco-cli/tests/example_cli_commands.rs Adds regression assertions for tuple-domain set sizes and constraint instance counts in inspect output.
crates/arco-cli/src/inspect.rs Computes tuple-domain sizes via tuple rows, adds alias-aware size lookup, and fixes constraint instance estimation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/arco-cli/src/inspect.rs Outdated
Comment on lines +193 to +202
let mut set_sizes: BTreeMap<&str, usize> = program
.set_registry
.iter()
.map(|(name, resolved)| (name.as_str(), resolved.values.len()))
.map(|(name, resolved)| (name.as_str(), resolved_set_cardinality(resolved)))
.collect();
for (alias, canonical) in &program.set_aliases {
if let Some(size) = set_sizes.get(canonical.as_str()).copied() {
set_sizes.entry(alias.as_str()).or_insert(size);
}
}
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing alias names as additional keys in set_sizes can produce ambiguous results if an alias collides with a real set name (e.g., a set named i and an index alias i). In that case, or_insert silently keeps the original value and the alias mapping is ignored, which can make inspect sizing inconsistent/hard to reason about. Prefer keeping set_sizes keyed only by canonical set names and performing canonicalization at lookup time (e.g., a small helper that resolves domain via program.set_aliases and then reads from set_sizes).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pesap this seems to be concerning, but that is a user problem. Probably we want to throw error messages when that happens

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot based on this feedback, answer to this comment with a proposed test that we can integrate in arco that fails in that situation. Do not make the code, just write here the test that you suggest to see this problem

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a proposed test that would expose the alias-vs-set-name collision in set_sizes:

#[test]
fn inspect_alias_does_not_shadow_real_set_with_same_name() {
    // Build a minimal program where:
    //   - a real set called "i" exists with, say, 3 elements
    //   - a *different* canonical set (e.g. "nodes") has an alias that is also named "i"
    //     (i.e. `program.set_aliases["i"] = "nodes"` where "nodes" has 5 elements)
    //
    // Because `or_insert` keeps the first value, the result depends on insertion
    // order: whichever key "i" wins, the other mapping is silently lost.
    //
    // The test asserts that the size reported for a variable/parameter whose
    // declared domain is the real set "i" is exactly 3, not 5.
    //
    // Without the fix (canonical lookup at read time rather than at insert time),
    // the test fails whenever the alias "i" → "nodes" is inserted first and the
    // real set "i" is overwritten, or whenever a variable whose domain is "nodes"
    // gets the wrong size because the "i" entry in set_sizes already points at
    // the real set.

    let input_kdl = r#"
        sets {
            i 1 2 3
            nodes {
                alias "i"   // collision: alias name == existing set name
                values a b c d e
            }
        }
        variables {
            x domain="i"     // should have size 3
        }
    "#;

    let output = run_inspect_json(input_kdl);
    let variable = find_variable(&output, "x");
    assert_eq!(variable["binding_size"], 3,
        "size of domain 'i' (the real set, 3 elements) must not be confused \
         with alias 'i' → 'nodes' (5 elements)");
}

The key assertion is that x, whose domain is the real set i (3 elements), reports a binding size of 3 and is not silently overridden by the alias i → nodes (5 elements). This test will fail with the current or_insert-into-set_sizes approach whenever insertion order makes the alias entry win.

Comment thread crates/arco-cli/src/inspect.rs Outdated
Comment on lines +280 to +283
program
.set_aliases
.iter()
.find_map(|(alias, canonical)| (canonical == set_name).then(|| alias.clone()))
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find_set_alias returns only a single alias for a canonical set name, but set_aliases can naturally contain multiple aliases pointing at the same canonical set. Returning an arbitrary single alias can make the inspect output misleading. Consider either (a) changing the record shape to expose multiple aliases (e.g., aliases: Vec<String>), or (b) returning None (or a deterministic/explicit choice) when multiple aliases exist so the output doesn’t imply there is only one.

Suggested change
program
.set_aliases
.iter()
.find_map(|(alias, canonical)| (canonical == set_name).then(|| alias.clone()))
let mut aliases = program
.set_aliases
.iter()
.filter_map(|(alias, canonical)| (canonical == set_name).then(|| alias.clone()));
let alias = aliases.next()?;
if aliases.next().is_some() {
None
} else {
Some(alias)
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot based on the feedback here (#203 (comment)), answer this comment with the code of a proposed test with a case that fails because of the problem you are pointing out. Do not write code just put the code in a comment answering this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a proposed test that would fail because find_set_alias silently returns only the alphabetically-first alias when multiple aliases exist, and the SetRecord.alias field hides the others:

#[test]
fn inspect_set_record_exposes_all_aliases_not_just_one() {
    // "lines" is the canonical set; it has two aliases: "i" and "j".
    // With the current single-Option<String> field, only one alias is
    // surfaced in the JSON output — the other is silently discarded.
    //
    // The test asserts that the set record for "lines" contains BOTH
    // aliases in its output.  With the current code it will fail because
    // `find_set_alias` returns (alphabetically) "i" and drops "j" entirely.
    let output = run_arco_inspect_json(concat!(
        "sets {\n",
        "    lines {\n",
        "        alias \"i\"\n",
        "        alias \"j\"\n",
        "        values a b c\n",
        "    }\n",
        "}\n",
    ));

    // Locate the "lines" set in the output
    let sets = output["sets"].as_array().expect("sets array");
    let lines_set = sets
        .iter()
        .find(|s| s["name"] == "lines")
        .expect("lines set not found");

    // Both aliases must appear; with the current code only "i" is present
    let aliases = lines_set["aliases"]
        .as_array()
        .expect("aliases should be an array, not a single optional string");

    let alias_values: Vec<&str> = aliases
        .iter()
        .map(|a| a.as_str().unwrap())
        .collect();

    assert!(alias_values.contains(&"i"), "alias 'i' missing");
    assert!(alias_values.contains(&"j"), "alias 'j' missing");
}

The test fails in two ways today:

  1. SetRecord.alias is Option<String>, not a Vec<String>, so the JSON key is "alias" (singular), not "aliases" (plural) — the .as_array() call panics.
  2. Even if the field were changed to Vec<String>, the current find_set_alias implementation would only populate it with "i" (the alphabetically-first alias in the BTreeMap), dropping "j".

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pesap opinions?

Copy link
Copy Markdown
Collaborator

@SebastianManriqueM SebastianManriqueM left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably I'm not familiar enough with rust, some parts of the code seem to be a little bit complicated to read

Comment thread crates/arco-cli/src/inspect.rs
.get(binding.domain.as_str())
.map_or(binding.domain.as_str(), String::as_str);

if let Some(resolved_set) = program.set_registry.get(canonical_domain) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anyway to make this block easier to read?, is probably a rust thing but it is supercomplicated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplified in commit 390c41a. The three nested ifs are now a single boolean:

let should_count = resolved_set.tuple_rows.is_none()
    || seen_tuple_domains.insert(canonical_domain);
if should_count {
    instances = instances.saturating_mul(resolved_set_cardinality(resolved_set));
}

Plain English: count the set's cardinality unless it is a tuple-domain that has already been counted.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pesap this looks better to me

…traint_instances

Agent-Logs-Url: https://github.com/NatLabRockies/arco/sessions/08ddfe6d-8090-489d-93ab-24dbb1a952c7

Co-authored-by: SebastianManriqueM <104728742+SebastianManriqueM@users.noreply.github.com>
@SebastianManriqueM SebastianManriqueM self-requested a review April 23, 2026 23:00
Copy link
Copy Markdown
Collaborator

@SebastianManriqueM SebastianManriqueM left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets review the suggestions from copilot before merging. Sounds good?

…stances

Agent-Logs-Url: https://github.com/NatLabRockies/arco/sessions/08ddfe6d-8090-489d-93ab-24dbb1a952c7

Co-authored-by: SebastianManriqueM <104728742+SebastianManriqueM@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 23, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • coin-or-tools.github.io
    • Triggering command: /usr/bin/wget wget REDACTED r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.02.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.03.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.04.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.05.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.06.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.07.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.08.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.09.rcgu.o ys-3252b334b80b7908/out/build/CMakeFiles/CMakeScratch/TryCompile-4OIUFg/src.cxx r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.11.rcgu.o r_im�� r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.13.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.14.rcgu.o lib/rustlib/x86_64-REDACTED-linux-gnu/bin/self-contained/cc hs-sys-3252b334bas artifacts,future-W den stable-x86_64-un-o (dns block)
  • glaros.dtc.umn.edu
    • Triggering command: /usr/bin/wget wget REDACTED r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.02.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.03.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.04.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.05.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.06.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.07.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.08.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.09.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.10.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.11.rcgu.o r_im�� r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.13.rcgu.o r_impl-9872c3c975d1b1fa.thiserror_impl.a377cbc6183cc32e-cgu.14.rcgu.o king/target/debug/build/highs-sys-3252b334b80b7908/out/build/CMakeFiles/_CMakeLTOTest-C/bin/CMak/usr/local/bin/cmake -E cmake_echo_color &#34;--switch=&#34; --progress-dir=/tmp/codeql-scratch-5f16eb8b5e693179/dbs/rust/working/target/debug/build/highs-sys-3252b334b 80b7908/out/build/CMakeFiles/CMakeScratch/TryCompile-WTdr43/CMakeFiles --progress-num=1 &#34;Buildi 1fca4583b.6i65nh-plugin stable-x86_64-un/usr/libexec/gcc/x86_64-linux-gnu/13/liblto_plugin.so 3179/dbs/rust/wo-plugin-opt=/usr/libexec/gcc/x86_64-linux-gnu/13/lto-wrapper (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@pesap pesap merged commit 0e4d0c7 into main Apr 24, 2026
21 checks passed
@pesap pesap deleted the fix/set_counting branch April 24, 2026 22:40
@github-actions github-actions Bot mentioned this pull request Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants