Skip to content

Conversation

@maartenflippo
Copy link
Contributor

@maartenflippo maartenflippo commented Jul 6, 2025

Our current FlatZinc parser implementation works, but it has a few flaws:

  1. It is difficult to support both the original fzn format, as well as the new JSON-based format.
  2. The error messages have very little information. They lack any indication of where the error happened, which can make for difficult debugging.
  3. We write a lot of code to resolve variables, constants, etc. All that code has to be repeated when implementing other tools that consume FlatZinc (e.g. a proof processor or proof checker).

That is why this PR introduces a new FlatZinc parsing crate, which provides the following:

  • A new AST that is heavily inspired by flatzinc-serde. It improves the AST we obtain from flatzinc mainly because we do not attempt to infer type information of identifiers in constraint arguments.
  • The AST tracks, for every node, where it originated in the source. This allows for more descriptive error messages.
  • Derive macros that allow for easy specification of the shape of constraints that are supported by the consumer of the crate. No more resolving constants/variables/arrays. That is all done in generated code. Look at the crate documentation for fzn-rs for an idea of what that looks like.

@maartenflippo maartenflippo force-pushed the feat/improved-fzn-parser branch 2 times, most recently from 6e4bd9e to efde93c Compare July 10, 2025 14:24
@ImkoMarijnissen ImkoMarijnissen self-requested a review July 28, 2025 10:59
@github-actions github-actions bot dismissed ImkoMarijnissen’s stale review July 28, 2025 10:59

Review re-requested

Copy link
Contributor

@ImkoMarijnissen ImkoMarijnissen left a comment

Choose a reason for hiding this comment

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

Similar error:

thread 'main' panicked at pumpkin-solver/src/bin/pumpkin-solver/flatzinc/compiler/mod.rs:29:61:
handle errors: IncorrectNumberOfArguments { expected: 3, actual: 4, span: Span { start: 7182213, end: 7182263 } }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

@maartenflippo maartenflippo force-pushed the feat/improved-fzn-parser branch from 945b37e to 438aa3b Compare August 13, 2025 20:16
@maartenflippo
Copy link
Contributor Author

Similar error:

thread 'main' panicked at pumpkin-solver/src/bin/pumpkin-solver/flatzinc/compiler/mod.rs:29:61:
handle errors: IncorrectNumberOfArguments { expected: 3, actual: 4, span: Span { start: 7182213, end: 7182263 } }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

@ImkoMarijnissen what instance is this?

@maartenflippo
Copy link
Contributor Author

maartenflippo commented Sep 10, 2025

I updated this branch to fix. I can now run the last 5 years of the minizinc challenge without getting errors. Sadly we do incur a performance penalty, as the "lex then parse" approach in the parser is not as fast as I would have hoped.

On challenge years 2020-2024, we get the following score:

Main With fzn-rs
179 163

All in all I believe that, while the impact on score is not great, it is still a step forward. If we agree that the crate itself looks good, then I move to merge this PR and hopefully improve the performance of the flatzinc parser later. The reason for this is that there are many branches that depend on this already (it really makes it so much easier to consume flatzinc). Since the performance change is completely internal to the crate, we can change it (hopefully) without hurting consumers of this crate.

Please re-examine #258 to see how this changes pumpkin-solver.

@EmirDe
Copy link
Contributor

EmirDe commented Sep 10, 2025

Quick question: when you compare the new version and the old version, specifically on benchmarks where we can find optimal solution, are the results consistent? Just wondering if there could be some problems with the parsing!

@maartenflippo
Copy link
Contributor Author

Yes the solutions are consistent

@maartenflippo
Copy link
Contributor Author

maartenflippo commented Sep 11, 2025

I looked some more into the results, and I cannot really pinpoint where the slowdown comes from. In both configurations, if I run a profiler on instances that take a long time to parse, the main hotspot is NotificationEngine::watch_all, with anywhere from 20-40% of the runtime.

What is also curious is that the instances that show a slowdown in initTime (which is the statistic that measures how long it takes to set up the solver to start solving) does not correlate with flatzinc model size. So large models may be fast in one configuration, and slow in another. This makes sense if we account for the slow IO on the cluster, but it makes it very difficult to track down the issues locally in the profiler.

What I did figure out is that the impact of lexing the source before parsing (which is not done in the main branch currently) is actually negligible. Lexing does not show up in a single one of my 10 profiles. I profiled the 10 instances which took the longest on DelftBLue.

@ImkoMarijnissen could you also have a look at this PR again and give your feedback on it and the apparent slowdown?

@EmirDe
Copy link
Contributor

EmirDe commented Sep 12, 2025

I looked some more into the results, and I cannot really pinpoint where the slowdown comes from. In both configurations, if I run a profiler on instances that take a long time to parse, the main hotspot is NotificationEngine::watch_all, with anywhere from 20-40% of the runtime.

What is also curious is that the instances that show a slowdown in initTime (which is the statistic that measures how long it takes to set up the solver to start solving) does not correlate with flatzinc model size. So large models may be fast in one configuration, and slow in another. This makes sense if we account for the slow IO on the cluster, but it makes it very difficult to track down the issues locally in the profiler.

What I did figure out is that the impact of lexing the source before parsing (which is not done in the main branch currently) is actually negligible. Lexing does not show up in a single one of my 10 profiles. I profiled the 10 instances which took the longest on DelftBLue.

@ImkoMarijnissen could you also have a look at this PR again and give your feedback on it and the apparent slowdown?

Quick questions: when you tests and profiling, do you stop the solver after parsing? Is slow parsing something only on DelftBlue or also locally?

Let us discuss this in the solver meeting!

@EmirDe
Copy link
Contributor

EmirDe commented Oct 2, 2025

Macro can type check? Then from_ast can say that everything is type-checked.
Solve -> SolveItem, SolveToken?
example in the library documentation

The Rc is not Send, but popular crates like anyhow do expect errors to
be Send. Since these are errors, we pay the price to allocate a String
instead.
@maartenflippo maartenflippo force-pushed the feat/improved-fzn-parser branch from cfe2685 to 1ee9455 Compare October 2, 2025 14:38
@@ -0,0 +1,107 @@
use quote::quote;

/// Construct a token stream that initialises a value with name `value_type` and the arguments
Copy link
Contributor

Choose a reason for hiding this comment

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

This appears to be the exact same documentation as used in annotation.rs, perhaps it could be clarified what the difference is?

//!
//! /// The `TypedInstance` is parameterized by the constraint type, as well as any annotations you
//! /// may need to parse.
//! type MyInstance = TypedInstance<i64, MyConstraints>;
Copy link
Contributor

Choose a reason for hiding this comment

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

It's a bit unclear from this documentation what the i64 signifies here

@@ -1,5 +1,5 @@
[workspace]
members = ["./pumpkin-solver", "./drcp-format", "./pumpkin-solver-py", "./pumpkin-macros", "./drcp-debugger", "./pumpkin-crates/*"]
members = ["./pumpkin-solver", "./drcp-format", "./pumpkin-solver-py", "./pumpkin-macros", "./drcp-debugger", "./pumpkin-crates/*", "./fzn-rs", "./fzn-rs-derive"]
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the pipeline is not running the tests for these crates if they are not part of default-members, do all test cases pass for these crates?

//! fn parse_flatzinc(source: &str) -> MyInstance {
//! // First, the source string is parsed into a structured representation.
//! //
//! // Note: the `fzn_rs::fzn` module is only available with the `fzn` feature enabled.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it explained anywhere what this feature is?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants