diff --git a/.github/workflows/update-website.yml b/.github/workflows/update-website.yml index 9d6ccf0ea5..f19724a607 100644 --- a/.github/workflows/update-website.yml +++ b/.github/workflows/update-website.yml @@ -39,6 +39,7 @@ jobs: cd misc/autodoc python3 -m pip install -r requirements.txt python3 generate-docs.py + python3 generate-translate-docs.py cd ../../.. - name: Move website material to website repo diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 160452a715..241b7cb002 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -2,6 +2,7 @@ This file controls the navigation below "documentation" on the website. - [](README.md) - [Planner usage](planner-usage.md) + - [Translator options](translator-options.md) - [Search plugins](search/) - [Syntax for search plugins](search-plugin-syntax.md) - [Exit codes](exit-codes.md) diff --git a/docs/planner-usage.md b/docs/planner-usage.md index 8403ac4ab1..a123525eb6 100644 --- a/docs/planner-usage.md +++ b/docs/planner-usage.md @@ -1,4 +1,4 @@ -# Usage +# Planner Usage Fast Downward is a domain-independent classical planning system that consists of two main components: @@ -20,18 +20,18 @@ participated in IPC 2011, please also check the page on ## Translation component To translate a PDDL input task into a SAS+ task without performing a search run the following: - ./fast-downward.py --translate [] + ./fast-downward.py --translate [] Note: - Giving the domain file as input is optional. - In case no domain file is specified, the component will search for a file called `domain.pddl` located in the same directory as the task file. -- Translator component options can be specified after the input files following the `--translator-options` flag. + In case no domain file is specified for the task, the component will search for a file called `domain.pddl` located in the same directory as the problem file. +- Translator component options can be specified after the input files following the `--translator-options` flag. See [translator options](translator-options.md) for details. ## Search component To run a search on a PDDL input task run the following: - ./fast-downward.py [] \ + ./fast-downward.py [] \ --search "()" Note: @@ -39,7 +39,7 @@ Note: - The PDDL task is translated into a SAS+ task internally without the need to explicitly call the `translate` component. Instead of a PDDL task a SAS+ translator output file could be given as input. In this case the translation step is omitted. - Giving the domain file as input is optional. - In case no domain file is specified, the component will search for a file called `domain.pddl` located in the same directory as the task file. + In case no domain file is specified for the task, the component will search for a file called `domain.pddl` located in the same directory as the problem file. - `` can be any of the [search algorithms](search/SearchAlgorithm.md) with corresponding `` as input. - Search component options can be specified anywhere after the input files. Search component options following translator component options need to first be escaped with the `--search-options` flag. @@ -49,11 +49,11 @@ Note: The following example is a recommended search algorithm configuration: [A* algorithm](search/SearchAlgorithm/#a_search_eager) with the [LM-cut heuristic](search/Evaluator/#landmark-cut_heuristic), which can be run as follows: - ./fast-downward.py [] --search "astar(lmcut())" + ./fast-downward.py [] --search "astar(lmcut())" As this is a common search configuration an alias (`seq-opt-lmcut`) exists that results in the same execution and can be used as follows: - ./fast-downward.py --alias seq-opt-lmcut [] + ./fast-downward.py --alias seq-opt-lmcut [] ### Aliases @@ -65,7 +65,7 @@ To find out which actual search options the aliases corresponds to, check the so For example to run the "LAMA 2011 configuration" of the planner, call: - ./fast-downward.py --alias seq-sat-lama-2011 [] + ./fast-downward.py --alias seq-sat-lama-2011 [] Note: @@ -79,19 +79,19 @@ The search options are built with flexibility in mind, not ease of use. It is very easy to use option settings that look plausible, yet introduce significant inefficiencies. For example, an invocation like - ./fast-downward.py [] \ + ./fast-downward.py [] \ --search "lazy_greedy([ff()], preferred=[ff()])" looks plausible, yet is hugely inefficient since it will compute the FF heuristic twice per state. To circumvent this a [`let`-expression](search-plugin-syntax.md#variables_as_parameters) could be used: - ./fast-downward.py [] \ + ./fast-downward.py [] \ --search "let(hff, ff(), lazy_greedy([hff], preferred=[hff]))" ## Validating plans To validate a plan found by some search algorithm using [VAL](https://github.com/KCL-Planning/VAL) run the following: - ./fast-downward.py --validate [] \ + ./fast-downward.py --validate [] \ --search "()" Note: diff --git a/docs/quick-start.md b/docs/quick-start.md index e7dcb56fa5..a77e497f82 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -35,7 +35,7 @@ You can run the planner as follows: Assume you want to solve a planning task (e.g. from the [Fast Downward benchmarks](https://github.com/aibasel/downward-benchmarks)) using A* search with the LM-cut heuristic. To do so you can run the following: - ./fast-downward.sif --search "astar(lmcut())" + ./fast-downward.sif --search "astar(lmcut())" See more planner options at [planner usage](planner-usage.md). @@ -58,16 +58,16 @@ Note: Assume you want to solve a planning task (e.g. from the [Fast Downward benchmarks](https://github.com/aibasel/downward-benchmarks)) using A* search with the LM-cut heuristic. To do so you can run the following: sudo docker run --rm -v :/benchmarks aibasel/downward \ - /benchmarks/ /benchmarks/ \ + /benchmarks/ /benchmarks/ \ --search "astar(lmcut())" -Here `` refers to your local directory containing your domain and task file. +Here `` refers to your local directory containing your domain and problem file. Note: - The Docker flag `-v` mounts the local directory `` of your host machine under the container directory `/benchmarks`, which is the - place where the containerised planner looks for the problem. + place where the containerised planner looks for the domain and problem file. - `` must be an absolute path; relative paths are only supported as of Docker Engine version 23. See more planner options at [planner usage](planner-usage.md). @@ -128,7 +128,7 @@ vagrant ssh # Run the planner. downward/fast-downward.py \ -/vagrant/benchmarks/ /vagrant/benchmarks/ \ +/vagrant/benchmarks/ /vagrant/benchmarks/ \ --search "astar(lmcut())" # Log out from the VM. @@ -138,7 +138,7 @@ logout vagrant halt ``` -Here `` refers to your local directory containing your domain and task file. +Here `` refers to your local directory containing your domain and problem file. See more planner options at [planner usage](planner-usage.md). @@ -168,7 +168,7 @@ You can run the planner as follows: Assume you want to solve a planning task (e.g. from the [Fast Downward benchmarks](https://github.com/aibasel/downward-benchmarks)) using A* search with the LM-cut heuristic. To do so you can run the following: - ./fast-downward.py --search "astar(lmcut())" + ./fast-downward.py --search "astar(lmcut())" See more planner options at [planner usage](planner-usage.md). diff --git a/docs/translator-output-format.md b/docs/translator-output-format.md index 8a944804c4..7759af942b 100644 --- a/docs/translator-output-format.md +++ b/docs/translator-output-format.md @@ -444,8 +444,8 @@ conditions (0) and affects var6 (6). It requires that the old value of the variable is 1, so it is only applicable if the robot is in rooma. It establishes the value 0, so that the robot will be in roomb afterwards. This domain does not use explicit action_costs (as encoded in the `metric` -section), so the final line is `0`. (The search code will treat problems with -no explicit action costs as unit-cost problems though, so the action will be +section), so the final line is `0`. (The search code will treat tasks with +no explicit action costs as unit-cost tasks though, so the action will be handled as if its cost were 1.) The two pick-up operators are similar, so we only explain the last one. @@ -554,7 +554,7 @@ generated for domains that contain derived predicates, but they can also be generated for PDDL tasks without derived predicates if they use non-STRIPS features such as universally quantified conditions, as some of these are compiled away with the help of axioms. As an example, here -is an axiom rules from a Miconic-FullADL task: +is an axiom rule from a Miconic-FullADL task: *Sample axiom section (Miconic-FullADL domain):* diff --git a/misc/autodoc/generate-translate-docs.py b/misc/autodoc/generate-translate-docs.py new file mode 100755 index 0000000000..d0906eb6a0 --- /dev/null +++ b/misc/autodoc/generate-translate-docs.py @@ -0,0 +1,112 @@ +#! /usr/bin/env python3 + +import argparse +import logging +from pathlib import Path +import subprocess +import sys + +SCRIPT_DIR = Path(__file__).resolve().parent +REPO_ROOT_DIR = SCRIPT_DIR.parents[1] +BUILD = "release" +FILENAME = "translator-options.md" + +HEADER_TEXT = """ +# Translator Options + +""" + +FOOTER_TEXT = """ +# Examples + +### Translate with disabled invariant synthesis and find a plan with A* + LM-Cut: +``` +fast-downward.py domain.pddl problem.pddl --search "astar(lmcut())" --translate-options --invariant-generation-max-candidates 0 +``` +Note that the order of the arguments matters. + + +### Print the help message of the translator +``` +fast-downward.py -- --translate-options --help +``` +The singular `--` is used to skip passing a domain and problem file to the driver. +""" + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--outdir", default=f"{REPO_ROOT_DIR}/docs") + return parser.parse_args() + + +def build_translator(): + subprocess.check_call([sys.executable, "build.py", BUILD, "translate"], cwd=REPO_ROOT_DIR) + + +def import_argparser_from_translator(): + sys.path.insert(0, str(REPO_ROOT_DIR / "builds" / BUILD / "bin")) + from translate import options + return options.get_arg_parser() + + +def action_to_markdown(parser: argparse.ArgumentParser, action: argparse.Action): + names = [] + for opt in action.option_strings: + toc_entry = opt + if action.choices is not None: + choice_str = ",".join(map(str, action.choices)) + names.append(f"`{opt} {{{choice_str}}}`") + elif action.metavar is not None: + names.append(f"`{opt} {action.metavar}`") + elif action.nargs != 0: + metavar = action.dest.upper() + names.append(f"`{opt} {metavar}`") + else: + names.append(f"`{opt}`") + name = ", ".join(names) + + # Expand %(default)s, %(choices)s, etc. + help_text = action.help or "" + if help_text is not argparse.SUPPRESS: + params = { + "default": action.default, + "prog": parser.prog, + "type": action.type, + "choices": action.choices, + "metavar": action.metavar, + "dest": action.dest, + } + try: + help_text = help_text % params + except Exception: + pass + + return f"### {name} {{: data-toc-label=\"{toc_entry}\"}}\n{help_text}\n" + + +def parser_to_markdown(parser: argparse.ArgumentParser): + lines = [HEADER_TEXT] + for action in parser._actions: + if action.option_strings: + lines.append(action_to_markdown(parser, action)) + lines += [FOOTER_TEXT] + return "\n".join(lines) + + +def build_docs(path: Path): + parser = import_argparser_from_translator() + path.write_text(parser_to_markdown(parser)) + + +if __name__ == '__main__': + args = parse_args() + logging.info("building translator...") + build_translator() + logging.info("building documentation...") + outdir = SCRIPT_DIR / args.outdir + outdir.mkdir(parents=True, exist_ok=True) + outpath = outdir / FILENAME + if outpath.exists(): + sys.exit(f"{outpath} already exists.") + build_docs(outpath) diff --git a/misc/website/build_website.py b/misc/website/build_website.py index 8f7c6981af..d827892278 100755 --- a/misc/website/build_website.py +++ b/misc/website/build_website.py @@ -10,7 +10,8 @@ SCRIPT_DIR = Path(__file__).resolve().parent REPO_ROOT_DIR = SCRIPT_DIR.parents[1] BUILD_DIR = SCRIPT_DIR/"build" -GENERATED_DOC_DIR = BUILD_DIR/"docs"/"documentation"/"search" +GENERATED_DOC_DIR = BUILD_DIR/"docs"/"documentation" +GENERATED_SEARCH_DOC_DIR = GENERATED_DOC_DIR/"search" if __name__ == '__main__': try: @@ -31,14 +32,20 @@ # we remove the search directory from the documentation (in case it is # there) because we will re-generate its content in the next step from the # actual code (binary). - rmtree(GENERATED_DOC_DIR, ignore_errors=True) + rmtree(GENERATED_SEARCH_DOC_DIR, ignore_errors=True) print("Start creating documentation of search plugins") - out = subprocess.run( + subprocess.run( [REPO_ROOT_DIR/"misc"/"autodoc"/"generate-docs.py", "--outdir", - GENERATED_DOC_DIR]) + GENERATED_SEARCH_DOC_DIR]) print("Done creating documentation of search plugins") + print("Start creating documentation of translator") + subprocess.run( + [REPO_ROOT_DIR/"misc"/"autodoc"/"generate-translate-docs.py", "--outdir", + GENERATED_DOC_DIR]) + print("Done creating documentation of translator") + print("\n\n", "Done. Run 'mkdocs serve' from the build directory " "to see the website in the browser at http://localhost:8001/:\n", "cd build && mkdocs serve -a localhost:8001")