{"id":13484351,"url":"https://github.com/leanprover-community/aesop","last_synced_at":"2026-04-05T17:36:49.924Z","repository":{"id":37463736,"uuid":"372832100","full_name":"leanprover-community/aesop","owner":"leanprover-community","description":"White-box automation for Lean 4","archived":false,"fork":false,"pushed_at":"2026-04-02T18:31:45.000Z","size":4559,"stargazers_count":348,"open_issues_count":43,"forks_count":50,"subscribers_count":6,"default_branch":"master","last_synced_at":"2026-04-03T05:49:04.742Z","etag":null,"topics":["lean4"],"latest_commit_sha":null,"homepage":"","language":"Lean","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leanprover-community.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-06-01T13:02:59.000Z","updated_at":"2026-04-02T18:28:51.000Z","dependencies_parsed_at":"2023-12-02T02:24:44.448Z","dependency_job_id":"7b9ffde2-d60b-48aa-85ed-e859d3f4af03","html_url":"https://github.com/leanprover-community/aesop","commit_stats":null,"previous_names":["leanprover-community/aesop","jlimperg/aesop"],"tags_count":82,"template":false,"template_full_name":null,"purl":"pkg:github/leanprover-community/aesop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leanprover-community%2Faesop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leanprover-community%2Faesop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leanprover-community%2Faesop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leanprover-community%2Faesop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leanprover-community","download_url":"https://codeload.github.com/leanprover-community/aesop/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leanprover-community%2Faesop/sbom","scorecard":{"id":581974,"data":{"date":"2025-08-11","repo":{"name":"github.com/leanprover-community/aesop","commit":"523c8ee53f7057447fc62ec14e506fda4cf63dfa"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.8,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":10,"reason":"20 commit(s) and 10 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":2,"reason":"Found 6/29 approved changesets -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/build.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/leanprover-community/aesop/build.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/leanprover-community/aesop/build.yml/master?enable=pin","Info:   0 out of   1 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":3,"reason":"branch protection is not maximal on development and all release branches","details":["Info: 'allow deletion' disabled on branch 'master'","Info: 'force pushes' disabled on branch 'master'","Warn: 'branch protection settings apply to administrators' is disabled on branch 'master'","Warn: could not determine whether codeowners review is allowed","Info: 'up-to-date branches' is required to merge on branch 'master'","Info: status check found to merge onto on branch 'master'","Warn: PRs are not required to make changes on branch 'master'; or we don't have data to detect it.If you think it might be the latter, make sure to run Scorecard with a PAT or use Repo Rules (that are always public) instead of Branch Protection settings"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-20T19:24:58.512Z","repository_id":37463736,"created_at":"2025-08-20T19:24:58.512Z","updated_at":"2025-08-20T19:24:58.512Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31444702,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T15:22:31.103Z","status":"ssl_error","status_checked_at":"2026-04-05T15:22:00.205Z","response_time":75,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["lean4"],"created_at":"2024-07-31T17:01:22.913Z","updated_at":"2026-04-05T17:36:49.907Z","avatar_url":"https://github.com/leanprover-community.png","language":"Lean","funding_links":[],"categories":["Lean","Packages"],"sub_categories":[],"readme":"# Aesop\n\nAesop (Automated Extensible Search for Obvious Proofs) is a proof search tactic\nfor Lean 4. It is broadly similar to Isabelle's `auto`. In essence, Aesop works\nlike this:\n\n- As with `simp`, you tag a (large) collection of definitions with the\n  `@[aesop]` attribute, registering them as Aesop _rules_. Rules can be\n  arbitrary tactics. We provide convenient ways to create common types of rules,\n  e.g. rules which apply a lemma.\n- Aesop takes these rules and tries to apply each of them to the initial goal.\n  If a rule succeeds and generates subgoals, Aesop recursively applies the rules\n  to these subgoals, building a _search tree_.\n- The search tree is explored in a _best-first_ manner. You can mark rules as\n  more or less likely to be useful. Based on this information, Aesop prioritises\n  the goals in the search tree, visiting more promising goals before less\n  promising ones.\n- Before any rules are applied to a goal, it is _normalised_, using a special\n  (customisable) set of _normalisation rules_. An important built-in\n  normalisation rule runs `simp_all`, so your `@[simp]` lemmas are taken into\n  account by Aesop.\n- Rules can be marked as _safe_ to optimise Aesop's performance. A safe rule is\n  applied eagerly and is never backtracked. For example, Aesop's built-in rules\n  safely split a goal `P ∧ Q` into goals for `P` and `Q`. After this split, the\n  original goal `P ∧ Q` is never revisited.\n- Aesop provides a set of built-in rules which perform logical operations (e.g.\n  case-split on hypotheses `P ∨ Q`) and some other straightforward deductions.\n- Aesop uses indexing methods similar to those of `simp` and other Lean tactics.\n  This means it should remain reasonably fast even with a large rule set.\n- When called as `aesop?`, Aesop prints a tactic script that proves the goal,\n  similar to `simp?`. This way you can avoid the performance penalty of running\n  Aesop all the time. However, the script generation is currently not fully\n  reliable, so you may have to adjust the generated script.\n\nAesop is suitable for two main use cases:\n\n- General-purpose automation, where Aesop is used to dispatch 'trivial' goals.\n  By registering enough lemmas as Aesop rules, you can turn Aesop into a much\n  more powerful `simp`.\n- Special-purpose automation, where specific Aesop rule sets are built to\n  address a certain class of goals. Mathlib tactics such as `measurability`\n  and `continuity` are implemented by Aesop.\n\nI only occasionally update this README, so details may be out of date. If you\nhave questions, please create an issue or ping me (Jannis Limperg) on the [Lean\nZulip](https://leanprover.zulipchat.com). Pull requests are very welcome!\n\nThere's also [a paper about Aesop](https://zenodo.org/record/7430233) which\ncovers many of the topics discussed here, sometimes in more detail.\n\n## Building\n\nWith [elan](https://github.com/leanprover/elan) installed, `lake build`\nshould suffice.\n\n## Adding Aesop to Your Project\n\nTo use Aesop in a Lean 4 project, first add this package as a dependency. In\nyour `lakefile.lean`, add\n\n```lean\nrequire aesop from git \"https://github.com/leanprover-community/aesop\"\n```\n\nYou also need to make sure that your `lean-toolchain` file contains the same\nversion of Lean 4 as Aesop's, and that your versions of Aesop's dependencies\n(currently only `std4`) match. We unfortunately can't support version ranges at\nthe moment.\n\nNow the following test file should compile:\n\n```lean\nimport Aesop\n\nexample : α → α :=\n  by aesop\n```\n\n## Quickstart\n\nTo get you started, I'll explain Aesop's major concepts with a series of\nexamples. A more thorough, reference-style discussion follows in the next\nsection.\n\nWe first define our own version of lists (so as not to clash with the standard\nlibrary) and an `append` function:\n\n``` lean\ninductive MyList (α : Type _)\n  | nil\n  | cons (hd : α) (tl : MyList α)\n\nnamespace MyList\n\nprotected def append : (_ _ : MyList α) → MyList α\n  | nil, ys =\u003e ys\n  | cons x xs, ys =\u003e cons x (MyList.append xs ys)\n\ninstance : Append (MyList α) :=\n  ⟨MyList.append⟩\n```\n\nWe also tell `simp` to unfold applications of `append`:\n\n```lean\n@[simp]\ntheorem nil_append : nil ++ xs = xs := rfl\n\n@[simp]\ntheorem cons_append : cons x xs ++ ys = cons x (xs ++ ys) := rfl\n```\n\nWhen Aesop first encounters a goal, it normalises it by running a customisable\nset of normalisation rules. One such normalisation rule effectively runs\n`simp_all`, so Aesop automatically takes `simp` lemmas into account.\n\nNow we define the `NonEmpty` predicate on `MyList`:\n\n``` lean\n@[aesop safe [constructors, cases]]\ninductive NonEmpty : MyList α → Prop\n  | cons : NonEmpty (cons x xs)\n```\n\nHere we see the first proper Aesop feature: we use the **`@[aesop]`** attribute\nto construct two Aesop rules related to the `NonEmpty` type. These rules are\nadded to a global rule set. When Aesop searches for a proof, it systematically\napplies each available rule, then recursively searches for proofs of the\nsubgoals generated by the rule, and so on, building a search tree. A goal is\nproved when Aesop applies a rule that generates no subgoals.\n\nIn general, rules can be arbitrary tactics. But since you probably don't want to\nwrite a tactic for every rule, the `aesop` attribute provides several **rule\nbuilders** which construct common sorts of rules. In our example, we construct:\n\n- A **`constructors`** rule. This rule tries to apply each constructor of\n  `NonEmpty` whenever a goal has target `NonEmpty _`.\n- A **`cases`** rule. This rule searches for hypotheses `h : NonEmpty _` and\n  performs case analysis on them (like the `cases` tactic).\n\nBoth rules above are added as **safe** rules. When a safe rule succeeds on a\ngoal encountered during the proof search, it is applied and the goal is never\nvisited again. In other words, the search does not backtrack safe rules. We will\nlater see **unsafe** rules, which can backtrack.\n\nWith these rules, we can prove a theorem about `NonEmpty` and `append`:\n\n``` lean\n@[aesop unsafe 50% apply]\ntheorem nonEmpty_append₁ {xs : MyList α} ys :\n    NonEmpty xs → NonEmpty (xs ++ ys) := by\n  aesop\n```\n\nAesop finds this proof in four steps:\n\n- A built-in rule introduces the hypothesis `h : NonEmpty xs`. By default,\n  Aesop's rule set contains a number of straightforward rules for handling the\n  logical connectives `→`, `∧`, `∨` and `¬` as well as the quantifiers `∀` and\n  `∃` and some other basic types.\n- The `cases` rule for `NonEmpty` performs case analysis on `h`.\n- The `simp` rule `cons_append`, which we added earlier, unfolds the `++`\n  operation.\n- The `constructor` rule for `NonEmpty` applies `NonEmpty.cons`.\n\nIf you want to see how Aesop proves your goal (or why it doesn't prove your\ngoal, or why it takes too long to prove your goal), you can enable tracing:\n\n``` lean\nset_option trace.aesop true\n```\n\nThis makes Aesop print out the steps it takes while searching for a proof. You\ncan also look at the search tree Aesop constructed by enabling the\n`trace.aesop.tree` option. For more tracing options, type `set_option\ntrace.aesop` and see what auto-completion suggests.\n\nIf, in the example above, you call `aesop?` instead, then Aesop prints a proof\nscript. At time of writing, it looks like this:\n\n``` lean\nintro a\nobtain @⟨x, xs_1⟩ := a\nsimp_all only [cons_append]\napply MyList.NonEmpty.cons\n```\n\nWith a bit of post-processing, you can use this script instead of the Aesop\ncall. This way you avoid the performance penalty of making Aesop search for a\nproof over and over again. The proof script generation currently has some known\nbugs, but it produces usable scripts most of the time.\n\nThe `@[aesop]` attribute on `nonEmpty_append₁` adds this lemma as an **unsafe**\nrule to the default rule set. For this rule we use the **`apply`** rule builder,\nwhich generates a rule that tries to apply `nonEmpty_append₁` whenever the\ntarget is of the form `NonEmpty (_ ++ _)`.\n\nUnsafe rules are rules which can backtrack, so after they have been applied to a\ngoal, Aesop may still try other rules to solve the same goal. This makes sense\nfor `nonEmpty_append₁`: if we have a goal `NonEmpty (xs ++ ys)`, we may prove it\neither by showing `NonEmpty xs` (i.e., by applying `nonEmpty_append₁`) or by\nshowing `NonEmpty ys`. If `nonEmpty_append₁` were registered as a safe rule, we\nwould always choose `NonEmpty xs` and never investigate `NonEmpty ys`.\n\nEach unsafe rule is annotated with a **success probability**, here 50%. This is\na very rough estimate of how likely it is that the rule will to lead to a\nsuccessful proof. It is used to prioritise goals: the initial goal starts with a\npriority of 100% and whenever we apply an unsafe rule, the priority of its\nsubgoals is the priority of its parent goal multiplied with the success\nprobability of the applied rule. So applying `nonEmpty_append₁` repeatedly would\ngive us goals with priority 50%, 25%, etc. Aesop always considers the\nhighest-priority unsolved goal first, so it prefers proof attempts involving few\nand high-probability rules. Additionally, when Aesop has a choice between\nmultiple unsafe rules, it prefers the one with the highest success probability.\n(Ties are broken arbitrarily but deterministically.)\n\nAfter adding `nonEmpty_append`, Aesop can prove some consequences of this\nlemma:\n\n``` lean\nexample {α : Type u} {xs : MyList α} ys zs :\n    NonEmpty xs → NonEmpty (xs ++ ys ++ zs) := by\n  aesop\n```\n\nNext, we prove another simple theorem about `NonEmpty`:\n\n``` lean\ntheorem nil_not_nonEmpty (xs : MyList α) : xs = nil → ¬ NonEmpty xs := by\n  aesop (add 10% cases MyList)\n```\n\nHere we use an **`add`** clause to add a rule which is only used in this\nspecific Aesop call. The rule is an unsafe `cases` rule for `MyList`. (As you\ncan see, you can leave out the `unsafe` keyword and specify only a success\nprobability.) This rule is dangerous: when we apply it to a hypothesis `xs :\nMyList α`, we get `x : α` and `ys : MyList α`, so we can apply the `cases` rule\nagain to `ys`, and so on. We therefore give this rule a very low success\nprobability, to make sure that Aesop applies other rules if possible.\n\nHere are some examples where Aesop's normalisation phase is particularly useful:\n\n``` lean\n@[simp]\ntheorem append_nil {xs : MyList α} :\n    xs ++ nil = xs := by\n  induction xs \u003c;\u003e aesop\n\ntheorem append_assoc {xs ys zs : MyList α} :\n    (xs ++ ys) ++ zs = xs ++ (ys ++ zs) := by\n  induction xs \u003c;\u003e aesop\n```\n\nSince we previously added unfolding lemmas for `append` to the global `simp`\nset, Aesop can prove theorems about this function more or less by itself (though\nin fact `simp_all` would already suffice.) However, we still need to perform\ninduction explicitly. This is a deliberate design choice: techniques for\nautomating induction exist, but they are complex, somewhat slow and not entirely\nreliable, so we prefer to do it manually.\n\nMany more examples can be found in the `AesopTest` folder of this repository. In\nparticular, the file `AesopTest/List.lean` contains an Aesop-ified port of 200\nbasic list lemmas from the Lean 3 version of mathlib, and the file\n`AesopTest/SeqCalcProver.lean` shows how Aesop can help with the formalisation\nof a simple sequent calculus prover.\n\n## Reference\n\nThis section contains a systematic and fairly comprehensive account of how Aesop\noperates.\n\n### Rules\n\nA rule is a tactic plus some associated metadata. Rules come in three flavours:\n\n- **Normalisation rules** (keyword `norm`) must generate zero or one subgoal.\n  (Zero means that the rule closed the goal). Each normalisation rule is\n  associated with an integer **penalty** (default 1). Normalisation rules are\n  applied in a fixpoint loop in order of penalty, lowest first. For rules with\n  equal penalties, the order is unspecified. See below for details on\n  the normalisation algorithm.\n\n  Normalisation rules can also be simp lemmas. These are constructed with the\n  `simp` builder. They are used by a special `simp` call during\n  the normalisation process.\n\n- **Safe rules** (keyword `safe`) are applied after normalisation but before any\n  unsafe rules. When a safe rule is successfully applied to a goal, the goal\n  becomes *inactive*, meaning no other rules are considered for it. Like\n  normalisation rules, safe rules are associated with a penalty (default 1)\n  which determines the order in which the rules are tried.\n\n  Safe rules should be provability-preserving, meaning that if a goal is\n  provable and we apply a safe rule to it, the generated subgoals should still\n  be provable. This is a less precise notion than it may appear since\n  what is provable depends on the entire Aesop rule set.\n\n- **Unsafe rules** (keyword `unsafe`) are tried only if all available safe rules\n  have failed on a goal. When an unsafe rule is applied to a goal, the goal is\n  not marked as inactive, so other (unsafe) rules may be applied to it. These\n  rule applications are considered independently until one of them proves the\n  goal (or until we've exhausted all available rules and determine that the goal\n  is not provable with the current rule set).\n\n  Each unsafe rule has a **success probability** between 0% and 100%. These\n  probabilities are used to determine the priority of a goal. The initial goal\n  has priority 100% and whenever we apply an unsafe rule, the priorities of its\n  subgoals are the priority of the rule's parent goal times the rule's success\n  probability. Safe rules are treated as having 100% success probability.\n\nRules can also be **multi-rules**. These are rules which add multiple rule\napplications to a goal. For example, registering the constructors of the `Or`\ntype will generate a multi-rule that, given a goal with target `A ∨ B`,\ngenerates one rule application with goal `A` and one with goal `B`. This is\nequivalent to registering one rule for each constructor, but multi-rules can be\nboth slightly more efficient and slightly more natural.\n\n### Search Tree\n\nAesop's central data structure is a search tree. This tree alternates between\ntwo kinds of nodes:\n\n- **Goal nodes**: these nodes store a goal, plus metadata relevant to the\n  search. The parent and children of a goal node are rule application nodes. In\n  particular, each goal node has a **priority** between 0% and 100%.\n- **Rule application ('rapp') nodes**: these goals store a rule (plus metadata).\n  The parent and children of a rapp node are goal nodes. When the search tree\n  contains a rapp node with rule `r`, parent `p` and children `c₁, ..., cₙ`,\n  this means that the tactic of rule `r` was applied to the goal of `p`,\n  generating the subgoals of the `cᵢ`.\n\nWhen a goal node has multiple child rapp nodes, we have a choice of how to solve\nthe goals. This makes the tree an AND-OR tree: to prove a rapp, *all* its child\ngoals must be proved; to prove a goal, *any* of its child rapps must be proved.\n\n### Search\n\nWe start with a search tree containing a single goal node. This node's goal is\nthe goal which Aesop is supposed to solve. Then we perform the following steps\nin a loop, stopping if (a) the root goal has been proved; (b) the root goal\nbecomes unprovable; or (c) one of Aesop's rule limits has been reached. (There\nare configurable limits on, e.g., the total number of rules applied or the\nsearch depth.)\n\n- Pick the highest-priority active goal node `G`. Roughly speaking, a goal node\n  is active if it is not proved and we haven't yet applied all possible rules to\n  it.\n- If the goal of `G` has not been normalised yet, normalise it. That means we\n  run the following normalisation loop:\n  - Run the normalisation rules with negative penalty (lowest penalty first). If\n    any of these rules is successful, restart the normalisation loop with the\n    goal produced by the rule.\n  - Run `simp` on all hypotheses and the target, using the global simp set (i.e.\n    lemmas tagged `@[simp]`) plus Aesop's `simp` rules.\n  - Run the normalisation rules with positive penalty (lowest penalty first).\n    If any of these rules is successful, restart the normalisation loop.\n\n  The loop ends when all normalisation rules fail. It destructively updates\n  the goal of `G` (and may prove it outright).\n- If we haven't tried to apply the safe rules to the goal of `G` yet, try to\n  apply each safe rule (lowest penalty first). As soon as a rule succeeds, add\n  the corresponding rapp and child goals to the tree and mark `G` as inactive.\n  The child goals receive the same priority as `G`.\n- Otherwise there is at least one unsafe rule that hasn't been tried on `G` yet\n  (or else `G` would have been inactive). Try the unsafe rule with the highest\n  success probability and if it succeeds, add the corresponding rapp and child\n  goals to the tree. The child goals receive the priority of `G` times the\n  success probability of the applied rule.\n\nA goal is **unprovable** if we have applied all possible rules to it and all\nresulting child rapps are unprovable. A rapp is unprovable if any of its\nsubgoals is unprovable.\n\nDuring the search, a goal or rapp can also become **irrelevant**. This means\nthat we don't have to visit it again. Informally, goals and rapps are irrelevant\nif they are part of a branch of the search tree which has either successfully\nproved its goal already or which can never prove its goal. More formally:\n\n- A goal is irrelevant if its parent rapp is unprovable. (This means that a\n  sibling of the goal is already unprovable, in which case we know that the\n  parent rapp will never be proved.)\n- A rapp is irrelevant if its parent goal is proved. (This means that a sibling\n  of the rapp is already proved, and we only need one proof.)\n- A goal or rapp is irrelevant if any of its ancestors is irrelevant.\n\n### Rule Builders\n\nA **rule builder** is a metaprogram that turns an expression into an Aesop rule.\nWhen you tag a declaration with the `@[aesop]` attribute, the builder is applied\nto the declared constant. When you use the `add` clause, as in `(add \u003cphase\u003e\n\u003cbuilder\u003e (\u003cterm\u003e))`, the builder is applied to the given term, which may\ninvolve hypotheses from the goal. However, some builders only support global\nconstants. If the `term` is a single identifier, e.g. the name of a hypothesis,\nthe parentheses around it are optional.\n\nCurrently available builders are:\n\n- **`apply`**: generates a rule which acts like the `apply` tactic.\n- **`forward`**: when applied to a term of type `A₁ → ... Aₙ → B`, generates a\n  rule which looks for hypotheses `h₁ : A₁`, ..., `hₙ : Aₙ` in the goal and, if\n  they are found, adds a new hypothesis `fwd : B`. As an example, consider the\n  lemma `even_or_odd`:\n\n  ```lean\n  even_or_odd : ∀ (n : Nat), Even n ∨ Odd n\n  ```\n\n  Registering this as a forward rule will cause the goal\n\n  ```lean\n  n : Nat\n  m : Nat\n  ⊢ T\n  ```\n\n  to be transformed into this:\n\n  ```lean\n  n : Nat\n  hn : Even n ∨ Odd n\n  m : Nat\n  hm : Even m ∨ Odd m\n  ⊢ T\n  ```\n\n  The forward builder may also be given a list of *immediate names*:\n\n  ```\n  forward (immediate := [n]) even_or_odd\n  ```\n\n  The immediate names, here `n`, refer to the arguments of `even_or_odd`. When\n  Aesop applies a forward rule with explicit immediate names, it only matches\n  the corresponding arguments to hypotheses. (Here, `even_or_odd` has only one\n  argument, so there is no difference.)\n\n  When no immediate names are given, Aesop considers every argument immediate,\n  except for instance arguments and dependent arguments (i.e. those that can be\n  inferred from the types of later arguments).\n\n  A forward rules will never add a hypothesis to the context if another\n  hypothesis of the same type already exists.\n- **`destruct`**: works like `forward`, but after the rule has been applied,\n  propositional hypotheses (i.e., hypotheses whose types are `Prop`s) that were\n  used as immediate arguments are cleared. This is useful when you want to\n  eliminate a hypothesis. E.g. the rule\n  ```\n  @[aesop norm destruct]\n  theorem and_elim_right : α ∧ β → α := ...\n  ```\n  will cause the goal\n  ```\n  h₁ : (α ∧ β) ∧ γ\n  h₂ : δ ∧ ε\n  ```\n  to be transformed into\n  ```\n  h₁ : α\n  h₂ : δ\n  ```\n\n  If a hypothesis `h` is used as an immediate argument for a `destruct` rule\n  and another hypothesis `h'` depends on `h`, but is not also an immediate\n  argument for the same rule, then `h` is not cleared. This is because we can't\n  clear `h` without also clearing `h'`.\n- **`constructors`**: when applied to an inductive type or structure `T`,\n  generates a rule which tries to apply each constructor of `T` to the target.\n  This is a multi-rule, so if multiple constructors apply, they are considered\n  in parallel. If you use this constructor to build an unsafe rule, each\n  constructor application receives the same success probability; if this is not\n  what you want, add separate `apply` rules for the constructors.\n- **`cases`**: when applied to an inductive type or structure `T`, generates a\n  rule that performs case analysis on every hypothesis `h : T` in the context.\n  The rule recurses into subgoals, so `cases Or` will generate 6 goals when\n  applied to a goal with hypotheses `h₁ : A ∨ B ∨ C` and `h₂ : D ∨ E`. However,\n  if `T` is a recursive type (e.g. `List`), we only perform case analysis once\n  on each hypothesis. Otherwise we would loop infinitely.\n\n  The `cases_patterns` option can be used to apply the rule only on hypotheses\n  of a certain shape. E.g. the rule `cases (cases_patterns := [Fin 0]) Fin` will\n  perform case analysis only on hypotheses of type `Fin 0`. Patterns can contain\n  underscores, e.g. `0 ≤ _`. Multiple patterns can be given (separated by\n  commas); the rule is then applied whenever at least one of the patterns\n  matches a hypothesis.\n- **`simp`**: when applied to an equation `eq : A₁ → ... Aₙ → x = y`, registers\n  `eq` as a simp lemma for the built-in simp pass during normalisation. As such,\n  this builder can only build normalisation rules.\n- **`unfold`**: when applied to a definition or `let` hypothesis `f`, registers\n  `f` to be unfolded (i.e. replaced with its definition) during normalisation.\n  As such, this builder can only build normalisation rules. The unfolding\n  happens in a separate `simp` pass.\n\n  The `simp` builder can also be used to unfold definitions. The difference is\n  that `simp` rules perform smart unfolding (like the `simp` tactic) and\n  `unfold` rules perform non-smart unfolding (like the `unfold` tactic).\n  Non-smart unfolding unfolds functions even when none of their equations\n  match, so `unfold` rules would lead to looping and are forbidden.\n- **`tactic`**: takes a tactic and directly turns it into a rule. When this\n  builder is used in an `add` clause, you can use e.g. `(add safe (by\n  norm_num))` to register `norm_num` as a safe rule. The `by` block can also\n  contain multiple tactics as well as references to the hypotheses. When you\n  use `(by ...)` in an `add` clause, Aesop automatically uses the tactic\n  builder, unless you specify a different builder.\n\n  When this builder is used in the `@[aesop]` attribute, the declaration tagged\n  with the attribute must have type `TacticM Unit`, `Aesop.SingleRuleTac` or\n  `Aesop.RuleTac`. The latter are Aesop data types which associate a tactic with\n  additional metadata; using them may allow the rule to operate somewhat more\n  efficiently.\n\n  Rule tactics should not be 'no-ops': if a rule tactic is not applicable to a\n  goal, it should fail rather than return the goal unchanged. All no-op rules\n  waste time; no-op `norm` rules will send normalisation into an infinite loop;\n  and no-op `safe` rules will prevent unsafe rules from being applied.\n\n  Normalisation rules may not assign metavariables (other than the goal\n  metavariable) or introduce new metavariables (other than the new goal\n  metavariable). This can be a problem because some Lean tactics, e.g. `cases`,\n  do so even in situations where you probably would not expect them to. I'm\n  afraid there is currently no good solution for this.\n- **`default`**: The default builder. This is the builder used when you\n  register a rule without specifying a builder, but you can also use it\n  explicitly. Depending on the rule's phase, the default builder tries\n  different builders, using the first one that works. These builders are:\n  - For `safe` and `unsafe` rules: `constructors`, `tactic`, `apply`.\n  - For `norm` rules: `constructors`, `tactic`, `simp`, `apply`.\n\n#### Transparency Options\n\nThe rule builders `apply`, `constructors` and `cases` each have a\n`transparency` option. This option controls the transparency at which the rule\nis executed. For example, registering a rule with the builder `apply\n(transparency := reducible)` makes the rule act like the tactic `with_reducible\napply`.\n\nHowever, even if you change the transparency of a rule, it is still indexed at\n`reducible` transparency (since the data structure we use for indexing only\nsupports `reducible` transparency). So suppose you register an `apply` rule with\n`default` transparency. Further suppose the rule concludes `A ∧ B` and your\ntarget is `T` with `def T := A ∧ B`. Then the rule could apply to the target\nsince it can unfold `T` at `default` transparency to discover `A ∧ B`. However,\nthe rule is never applied because the indexing procedure sees only `T` and does\nnot consider the rule potentially applicable.\n\nTo override this behaviour, you can write `apply (transparency! := default)`\n(note the bang). This disables indexing, so the rule is tried on every goal.\n\n### Rule Sets\n\nRule sets are declared with the command\n\n``` lean\ndeclare_aesop_rule_sets [r₁, ..., rₙ] (default := \u003cbool\u003e)\n```\n\nwhere the `rᵢ` are arbitrary names. To avoid clashes, pick names in the\nnamespace of your package. Setting `default := true` makes the rule set active\nby default. The `default` clause can be omitted and defaults to `false`.\n\nWithin a rule set, rules are identified by their name, builder and phase\n(safe/unsafe/norm). This means you can add the same declaration as multiple\nrules with different builders or in different phases, but not with different\npriorities or different builder options (if the rule's builder has any options).\n\nRules can appear in multiple rule sets, but in this case you should make sure\nthat they have the same priority and use the same builder options. Otherwise,\nAesop will consider these rules the same and arbitrarily pick one.\n\nOut of the box, Aesop uses the default rule sets `builtin` and `default`. The\n`builtin` set contains built-in rules for handling various constructions (see\nbelow). The `default` set contains rules which were added by Aesop users without\nspecifying a rule set.\n\n### The `@[aesop]` Attribute\n\nDeclarations can be added to rule sets by annotating them with the `@[aesop]`\nattribute. As with other attributes, you can use `@[local aesop]` to add a rule\nonly within the current section or namespace and `@[scoped aesop]` to add a rule\nonly when the current namespace is open.\n\n#### Single Rule\n\nIn most cases, you'll want to add one rule for the declaration. The syntax for\nthis is\n\n``` lean\n@[aesop \u003cphase\u003e? \u003cpriority\u003e? \u003cbuilder\u003e? \u003cbuilder_option\u003e* \u003crule_sets\u003e?]\n```\n\nwhere\n\n- `\u003cphase\u003e` is `safe`, `norm` or `unsafe`. Cannot be omitted except under the\n  conditions in the next bullets.\n\n- `\u003cpriority\u003e` is:\n  - For `simp` rules, a natural number. This is used as the priority of the\n    `simp` generated `simp` lemmas, so registering a `simp` rule with priority\n    `n` is roughly equivalent to the attribute `@[simp n]`. If omitted, defaults\n    to Lean's default `simp` priority.\n  - For `safe` and `norm` rules (except `simp` rules), an integer penalty. If\n    omitted, defaults to 1.\n  - For `unsafe` rules, a percentage between 0% and 100%. Cannot be omitted.\n    You may omit the `unsafe` phase specification when giving a percentage.\n  - For `unfold` rules, a penalty can be given, but it is currently ignored.\n\n- `\u003cbuilder\u003e` is one of the builders given above. If no builder is specified,\n  the default builder for the given phase is used.\n\n  When the `simp` builder is used, the `norm` phase may be omitted since this\n  builder can only generate normalisation rules.\n\n- `\u003cbuilder_option\u003e*` is a list of zero or more builder options. See above\n  for the different builders' options.\n\n- `\u003crule_sets\u003e` is a clause of the form\n\n  ```text\n  (rule_sets := [r₁, ..., rₙ])\n  ```\n\n  where the `rᵢ` are declared rule sets. (Parentheses are mandatory.) The rule\n  is added exactly to the specified rule sets. If this clause is omitted, it\n  defaults to `(rule_sets := [default])`.\n\n#### Multiple Rules\n\nIt is occasionally useful to add multiple rules for a single declaration, e.g.\na `cases` and a `constructors` rule for the same inductive type. In this case,\nyou can write for example\n\n``` lean\n@[aesop unsafe [constructors 75%, cases 90%]]\ninductive T ...\n\n@[aesop apply [safe (rule_sets := [A]), 70% (rule_sets := [B])]]\ndef foo ...\n\n@[aesop [80% apply, safe 5 forward (immediate := x)]]\ndef bar (x : T) ...\n```\n\nIn the first example, two unsafe rules for `T` are registered, one with success\nprobability 75% and one with 90%.\n\nIn the second example, two rules are registered for `foo`. Both use the `apply`\nbuilder. The first, a `safe` rule with default penalty, is added to rule set\n`A`. The second, an `unsafe` rule with 70% success probability, is added to\nrule set `B`.\n\nIn the third example, two rules are registered for `bar`: an `unsafe` rule with\n80% success probability using the `apply` builder and a `safe` rule with penalty\n5 using the `forward` builder.\n\nIn general, the grammar for the `@[aesop]` attribute is\n\n``` lean\nattr      ::= @[aesop \u003crule_expr\u003e]\n            | @[aesop [\u003crule_expr,+\u003e]]\n\nrule_expr ::= feature\n            | feature \u003crule_expr\u003e\n            | feature [\u003crule_expr,+\u003e]\n```\n\nwhere `feature` is a phase, priority, builder or `rule_sets` clause. This\ngrammar yields one or more trees of features and each branch of these trees\nspecifies one rule. (A branch is a list of features.)\n\n### Adding External Rules\n\nYou can use the `attribute` command to add rules for constants which were\ndeclared previously, either in your own development or in a package you import:\n\n```lean\nattribute [aesop norm unfold] List.all -- List.all is from Init\n```\n\nYou can also use the `add_aesop_rules` command:\n\n``` lean\nadd_aesop_rules safe [(by linarith), Nat.add_comm 0]\n```\n\nAs you can see, this command can be used to add tactics and composite terms as\nwell. Use `local add_aesop_rules` and `scoped add_aesop_rules` to obtain the\nequivalent of `@[local aesop]` and `@[scoped aesop]`.\n\n### Erasing Rules\n\nThere are two ways to erase rules. Usually it suffices to remove the `@[aesop]`\nattribute:\n\n``` lean\nattribute [-aesop] foo\n```\n\nThis will remove all rules associated with the declaration `foo` from all rule\nsets. However, this erasing is not persistent, so the rule will reappear at the\nend of the file. This is a fundamental limitation of Lean's attribute system:\nonce a declaration is tagged with an attribute, it cannot be permanently\nuntagged.\n\nIf you want to remove only certain rules, you can use the `erase_aesop_rules`\ncommand:\n\n``` lean\nerase_aesop_rules [safe apply foo, bar (rule_sets := [A])]\n```\n\nThis will remove:\n\n- all safe rules for `foo` with the `apply` builder from all rule sets (but not\nother, for example, unsafe rules or `forward` rules);\n- all rules for `bar` from rule set `A`.\n\nIn general, the syntax is\n\n``` lean\nerase_aesop_rules [\u003crule_expr,+\u003e]\n```\n\ni.e. rules are specified in the same way as for the `@[aesop]` attribute.\nHowever, each rule must also specify the name of the declaration whose rules\nshould be erased. The `rule_expr` grammar is therefore extended such that a\n`feature` can also be the name of a declaration.\n\nNote that a rule added with one of the default builders (`safe_default`,\n`norm_default`, `unsafe_default`) will be registered under the name of the\nbuilder that is ultimately used, e.g. `apply` or `simp`. So if you want to erase\nsuch a rule, you may have to specify that builder instead of the default\nbuilder.\n\n### The `aesop` Tactic\n\nIn its most basic form, you can call the Aesop tactic just by writing\n\n``` lean\nexample : α → α := by\n  aesop\n```\n\nThis will use the rules in the default rule sets. Out of the box, these are the\n`default` rule set, containing rules tagged with the `@[aesop]` attribute\nwithout mentioning a specific rule set, and the `builtin` rule set, containing\nrules built into Aesop. However, other rule sets can also be enabled by default;\nsee the `declare_aesop_rule_sets` command.\n\nThe tactic's behaviour can also be customised with various options. A more\ninvolved Aesop call might look like this:\n\n``` text\naesop\n  (add safe foo, 10% cases Or, safe cases Empty)\n  (erase A, baz)\n  (rule_sets := [A, B])\n  (config := { maxRuleApplicationDepth := 10 })\n```\n\nHere we add some rules with an `add` clause, erase other rules with an `erase`\nclause, limit the used rule sets and set some options. Each of these clauses\nis discussed in more detail below.\n\n#### Adding Rules to an Aesop Call\n\nRules can be added to an Aesop call with an `add` clause. This won't affect any\ndeclared rule sets. The syntax of the `add` clause is\n\n``` text\n(add \u003crule_expr,+\u003e)\n```\n\ni.e. rules can be specified in the same way as for the `@[aesop]` attribute.\nAs with the `erase_aesop_rules` command, each rule must specify the name of\ndeclaration from which the rule should be built; for example\n\n``` text\n(add safe [foo 1, bar 5])\n```\n\nwill add the declaration `foo` as a safe rule with penalty 1 and `bar` as a safe\nrule with penalty 5.\n\nThe rule names can also refer to hypotheses in the goal context, but not all\nbuilders support this.\n\n#### Erasing Rules From an Aesop Call\n\nRules can be removed from an Aesop call with an `erase` clause. Again, this\naffects only the current Aesop call and not the declared rule sets. The syntax\nof the `erase` clause is\n\n``` text\n(erase \u003crule_expr,+\u003e)\n```\n\nand it works exactly like the `erase_aesop_rules` command. To erase all rules\nassociated with `x` and `y`, write\n\n``` lean\n(erase x, y)\n```\n\n#### Selecting Rule Sets\n\nBy default, Aesop uses the `default` and `builtin` rule sets, as well as rule\nsets which are declared as default rule sets. A `rule_sets` clause can be given\nto include additional rule sets, e.g.\n\n``` text\n(rule_sets := [A, B])\n```\n\nThis will use rule sets `A`, `B`, `default` and `builtin` (and any rule sets\ndeclared as default rule sets). Rule sets can also be disabled with\n`rule_sets := [-default, -builtin]`.\n\n#### Setting Options\n\nVarious options can be set with a `config` clause, whose syntax is:\n\n``` text\n(config := \u003cterm\u003e)\n```\n\nThe term is an arbitrary Lean expression of type `Aesop.Options`; see there for\ndetails. Notable options include:\n\n- `strategy` selects a best-first, depth-first or breadth-first search strategy.\n  The default is best-first.\n- `useSimpAll := false` makes the built-in `simp` rule use `simp at *` rather\n  than `simp_all`.\n- `enableSimp := false` disables the built-in `simp` rule altogether.\n\nSimilarly, options for the built-in norm simp rule can be set with\n\n``` text\n(simp_config := \u003cterm\u003e)\n```\n\nYou can give the same options here as in `simp (config := ...)`.\n\n### Built-In Rules\n\nThe set of built-in rules (those in the `builtin` rule set) is a bit unstable,\nso for now I won't document them in detail. See `Aesop/BuiltinRules.lean` and\n`Aesop/BuiltinRules/*.lean`\n\n### Proof Scripts\n\nBy calling `aesop?` instead of `aesop`, you can instruct Aesop to generate a\ntactic script which proves the goal (if Aesop succeeds). The script is printed\nas a `Try this:` suggestion, similar to `simp?`.\n\nThe scripts generated by Aesop are currently a bit idiosyncratic. For example,\nthey may contain the `aesop_cases` tactic, which is a slight variation of the\nstandard `cases`. Additionally, Aesop occasionally generates buggy scripts which\ndo not solve the goal. We hope to eventually fix these issues; until then, you\nmay have to lightly adjust the proof scripts by hand.\n\n### Tracing\n\nTo see how Aesop proves a goal -- or why it doesn't prove a goal, or why it's\nslow to prove a goal -- it is useful to see what it's doing. To that end, you\ncan enable various tracing options. These use the usual syntax, e.g.\n\n``` lean\nset_option trace.aesop true\n```\n\nThe main options are:\n\n- `trace.aesop`: print a step-by-step log of which goals Aesop tried to\n  solve, which rules it tried to apply (successfully or unsuccessfully), etc.\n- `trace.aesop.ruleSet`: print the rule set used for an Aesop call.\n- `trace.aesop.proof`: if Aesop is successful, print the proof that was\n  generated (as a Lean term). You should be able to copy-and-paste this proof\n  to replace Aesop.\n  \n### Profiling\n\nTo get an idea of where Aesop spends its time, use\n\n``` lean\nset_option trace.aesop.stats true\n```\n\nAesop then prints some statistics about this particular Aesop run.\n\nTo get statistics for multiple Aesop invocations, activate the option\n`aesop.collectStats` for the relevant files (or for certain invocations) and run\nthe command `#aesop_stats` in a file which imports all relevant files. E.g. to\nevaluate Aesop's performance in Mathlib, set the option `aesop.collectStats` in\nMathlib's `lakefile.lean`, recompile Mathlib from scratch and create a new Lean\nfile with contents\n\n``` lean\nimport Mathlib\n\n#aesop_stats\n```\n\n\nYou can also activate the `profiler` option, which augments the trace produced\nby `trace.aesop` with information about how much time each step took. Note that\nonly the timing information pertaining to goal expansions and rule applications\nis relevant. Other timings, such as those attached to new rapps and goals, are\njust artefacts of the Lean tracing API.\n\n### Checking Internal Invariants\n\nIf you encounter behaviour that looks like an internal error in Aesop, it may\nhelp to set the option `aesop.check.all` (or the more fine-grained\n`aesop.check.*` options). This makes Aesop check various invariants while the\ntactic is running. These checks are somewhat expensive, so remember to unset the\noption after you've reported the bug.\n\n### Handling Metavariables\n\nRules which create metavariables must be handled specially by Aesop. For\nexample, suppose we register transitivity of `\u003c` as an Aesop rule. Then we may\nget a goal state of this form:\n\n``` lean\nn k : Nat\n⊢ n \u003c ?m\n\nn k : Nat\n⊢ ?m \u003c k\n```\n\nWe may now solve the first goal by applying different rules. We could, for\nexample, apply the theorem `∀ n, n \u003c n + 1`. We could also use an assumption `n\n\u003c a`. Both proofs close the first goal, but crucially, they modify the second\ngoal: in the first case, it becomes `n + 1 \u003c k`; in the second case, `a \u003c k`.\nAnd of course one of these could be provable while the other is not. In other\nwords, the second subgoal now depends on the *proof* of the first subgoal\n(whereas usually we don't care *how* a goal was proven, only *that* it was\nproven). Aesop could also decide to work on the second subgoal first, in which\ncase the situation is symmetric.\n\nDue to this dependency, Aesop in effect treats the instantiations of the second\nsubgoal as *additional goals*. Thus, when we apply the theorem `∀ n, n \u003c n + 1`,\nwhich closes the first goal, Aesop realises that because this theorem was\napplied, we must now prove `n + 1 \u003c k` as well. So it adds this goal as an\nadditional subgoal of the rule application `∀ n, n \u003c n + 1` (which otherwise\nwould not have any subgoals). Similarly, when the assumption `n \u003c a` is applied,\nits rule application gains an additional subgoal `a \u003c k`.\n\nThis mechanism makes sure that we consider all potential proofs. The downside is\nthat it's quite explosive: when there are multiple metavariables in multiple\ngoals, which Aesop may visit in any order, Aesop may spend a lot of time copying\ngoals with shared metavariables. It may even try to prove the same goal more\nthan once since different rules may yield the same metavariable instantiations.\nFor these reasons, rules which create metavariables are best kept out of the\nglobal rule set and added to individual Aesop calls on an ad-hoc basis.\n\nIt is also worth noting that when a safe rule assigns a metavariable, it is\ntreated as an unsafe rule (with success probability 90%). This is because\nassigning metavariables is almost never safe, for the same reason as above: the\nusually perfectly safe rule `∀ n, n \u003c n + 1` would, if treated as safe, force us\nto commit to one particular instantiation of the metavariable `?m`.\n\nFor more details on the handling of metavariables, see the [Aesop\npaper](https://zenodo.org/record/7430233).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleanprover-community%2Faesop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleanprover-community%2Faesop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleanprover-community%2Faesop/lists"}