Extend ground output options wip#599
Extend ground output options wip#599FrancoisLaferriere wants to merge 6 commits intopotassco:wip-20from
Conversation
4cf3ddf to
ebabfd9
Compare
81be6ff to
5dba6e3
Compare
|
After a quick glance, this is starting to look good. I'll take a closer look later and come back to you. |
lib/control/src/solver.cc
Outdated
|
|
||
| auto do_fact_lit() -> std::optional<prg_lit_t> override { | ||
| return std::nullopt; | ||
| } // NOTE: only called in the aspif parser |
There was a problem hiding this comment.
Maybe consider dropping this comment or elaborate why this information is important.
There was a problem hiding this comment.
We should also test this for the aspif parser, then.
lib/c-api/src/opts.hh
Outdated
| } | ||
| } | ||
|
|
||
| template <class ModeT> auto parse_format(std::string_view str, ModeT &mode) -> bool { |
There was a problem hiding this comment.
I'm not a huge fan of this template function - the name/signature does not match its implementation.
I would at least a) try to find a better/more obvious name (indicating that the function actually changes internal state) and b) split it into a (mode-independent) parse/apply function and a template. I.e.
// TBD: Find good name
auto parse_format(std::string_view str) -> bool {
namespace Parse = Potassco::Parse;
using namespace std::literals;
...
}
template <class ModeT> auto parse_format(std::string_view str, ModeT &mode) -> bool {
if (parse_format(str)) {
mode = ModeT::solve;
return true;
}
return false;
}There was a problem hiding this comment.
We could use parse_backend_type as name.
There was a problem hiding this comment.
We could use parse_backend_type as name.
I think the "parse" prefix just does not fit here. In other places, parse_* means "transform string to (output) parameter of type T", but here "parse" both applies state and transforms to output.
From a pure implementation perspective, I would do the following:
- Provide enum entries for enum class AppMode in
solver.hh, e.g. via:
POTASSCO_REFLECT_ENUM_ENTRIES(AppMode, 0, 4); // or explicitly via POTASSCO_SET_ENUM_ENTRIES- Implement
auto init_app_mode(std::string_view str) -> boolinClingoOptions.
auto init_app_mode(std::string_view str) -> bool {
namespace Parse = Potassco::Parse;
using namespace std::literals;
// Parse to temporaries: we don't want partial state changes
Control::AppMode mode;
Control::ReifyFlag reify;
Control::ModeFormat format;
if (Parse::ok(Potassco::extract(str, format))) {
if (format == Control::ModeFormat::reify) {
while (Parse::matchOpt(str, ',')) {
...
}
} else if (not Parse::ok(Potassco::stringTo(str, mode))) {
return false;
}
if (str.empty()) { // Success: commit result
solver_opts_.mode = Control::AppMode::solve;
solver_opts_.format = format;
solver_opts_.reify = reify;
return true;
}
return false;
}- In
clingo_control_new, bind--modeoption parser toinit_app_mode.
auto parse_mode = [&opts](std::string_view str) { return opts.init_app_mode(str); };- In
main.cc:initOptions, compose--modeoption parser:
auto parse_mode = [this](std::string_view str) {
if (opts_.init_app_mode(str)) { // "Common" app mode?
mode_ = static_cast<Mode>(opts_.mode());
return true;
}
if (str == "clasp") { // Special app mode?
mode_ = Mode::clasp;
return true;
}
return false;
};Anyhow, to me (the naive user), the semantics of the mode option (and its tight coupling to the internal states) is unclear. For example (ignoring implementation), what's the difference between parse, rewrite, and ground, and why is aspif etc. not some kind of rewrite /grounding?
From a user's perspective, maybe it would be more clear if we'd add a new convert/transform mode with the output format as sub-arguments.
There was a problem hiding this comment.
We should probably leave the implementation aside for a moment and discuss what the options from a user's perspective should look like:
Currently, we have --mode={parse,rewrite,ground,solve,clasp}:
parse: Parses the program and prints it as parsed. Limited use maybe pretty-printing.rewrite: Rewrites the parsed program. This applies simplifications and brings rules into a shape ready for grounding. Mostly for debugging purposes.ground: This is essentially clingo's previous--textoption.solve: This is the usual clingo ground and solve mode.clasp: This turns clingo into clasp.
Next we want to add aspif/smodels/reify output. Technically, these proceed a bit like the solve mode (solve terminates an aspif/smodels slice with a zero). They don't really fit nicely in the above list.
A separate option --backend={clasp,aspif,smodels,reify} or maybe --output that requires solve mode would also be an option.
What are your thoughts?
There was a problem hiding this comment.
Here is my naive view: we have a mode clasp that turns clingo into clasp and we have a mode ground, which basically turns clingo into a grounder. The mode solve is the actual clingo and shouldn't even be needed in the CLI, so I would actually drop it.
Now, aspif/smodels/reify might internally depend on the "solve" mode, but to me, this is a non-obvious implementation detail since no "solving" is actually happening.
Conceptually, I would think that aspif/smodels/reify belong to the grounder and hence, I would either have --mode=ground[,<output-fmt>[,<options>]] or a separate option --ground-output=aspif,smodels,reify. In the latter case, I would suggest generating an error if --ground-output is used together with an explicit mode != ground.
There was a problem hiding this comment.
convert would fit into the existing mode option
But I actually prefer a separate option: --output/--convert/--ground-format whatever. I would also let this option switch the mode behind the scenes as required. To keep things simple we can even make the mode and the new option exclusive.
What about
--mode={parse|rewrite|solve|clasp}
--output={text|aspif|smodels|reify}
--reify-options=<list {sccs,steps}>
?
Option --output can set the internal mode to ground/solve as required. If both are given, we can raise an exception. Then mode becomes an advanced option for debugging and using clasp. Option --output can be used to inspect the text output and use clingo with other solvers.
I won't insist on this solution if you think there is a better one. 😄
There was a problem hiding this comment.
I don't have a strong preference. However, if we go with the separate option, I would prefer a different name for that option. Having both --output and (the poorly named clasp option) --outf might be confusing. Of course, we could also rename the latter.
There was a problem hiding this comment.
If everyone is okay with having separate options we can at least go forward with the implementation. Changing names of options later should not be a big deal.
There was a problem hiding this comment.
Let’s settle on a separate option then. What about the reifier options? We could also have a separate option, as Roland suggested, or have them comma-separated, the same way as for --pre in clasp. I tend more toward the second option for consistency with clasp.
There was a problem hiding this comment.
I have no strong preference here.
rkaminsk
left a comment
There was a problem hiding this comment.
Let's try to find good names next. Afterward, we will still need to test this.
lib/c-api/src/opts.hh
Outdated
| } | ||
| } | ||
|
|
||
| template <class ModeT> auto parse_format(std::string_view str, ModeT &mode) -> bool { |
There was a problem hiding this comment.
We could use parse_backend_type as name.
lib/control/src/solver.cc
Outdated
|
|
||
| auto do_fact_lit() -> std::optional<prg_lit_t> override { | ||
| return std::nullopt; | ||
| } // NOTE: only called in the aspif parser |
There was a problem hiding this comment.
We should also test this for the aspif parser, then.
|
I used Todo:
|
The function exists for backward compatibility with aspif 1.0. It is rather easy to implement:
|
Changes
AbstractProgramAdapterto bridgeAbstractProgramBackendImplandPotassco::AbstractProgram.AppMode::aspifas a quick way to test the new code path.Concerns
I’m not fully convinced this is the best way to pass
OutputBufferintoAbstractProgram. I use an internalstd::ostringstreamwhich is copied intoOutputBufferon everydo_end_step()call. I also tried astd::streambuf-based integration but ended up callingOutputBuffer::flush()on every character.The placement of
begin_step()should be correct, not confident aboutend_step().The adapter currently has to call
prg_.outputTerm()andprg_.endStep()directly. I did not find a cleaner way.