{"id":13749180,"url":"https://github.com/fatho/logru","last_synced_at":"2025-05-09T12:30:40.760Z","repository":{"id":38008356,"uuid":"146219983","full_name":"fatho/logru","owner":"fatho","description":"Log(ic) programming in Ru(st).","archived":false,"fork":false,"pushed_at":"2024-03-29T12:58:29.000Z","size":155,"stargazers_count":14,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-05-22T21:34:11.269Z","etag":null,"topics":["hacktoberfest","logic-programming","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","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/fatho.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","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}},"created_at":"2018-08-26T22:08:08.000Z","updated_at":"2024-08-03T07:03:05.798Z","dependencies_parsed_at":"2024-04-01T18:30:16.149Z","dependency_job_id":null,"html_url":"https://github.com/fatho/logru","commit_stats":{"total_commits":69,"total_committers":1,"mean_commits":69.0,"dds":0.0,"last_synced_commit":"80fd3f34782a13029e8cf566907f8d34dfab8e58"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fatho%2Flogru","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fatho%2Flogru/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fatho%2Flogru/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fatho%2Flogru/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fatho","download_url":"https://codeload.github.com/fatho/logru/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224859694,"owners_count":17381676,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["hacktoberfest","logic-programming","rust"],"created_at":"2024-08-03T07:00:56.550Z","updated_at":"2025-05-09T12:30:40.744Z","avatar_url":"https://github.com/fatho.png","language":"Rust","funding_links":[],"categories":["Projects"],"sub_categories":["Libraries"],"readme":"# LogRu\n\n**Log**ic programming in **Ru**st.\n\n![ci badge](https://github.com/fatho/logru/actions/workflows/ci.yml/badge.svg) [![crates.io badge](https://img.shields.io/crates/v/logru)](https://crates.io/crates/logru) [![docs.rs badge](https://img.shields.io/docsrs/logru)](https://docs.rs/logru/)\n\n## Overview\n\nAt the heart of this project is a small, efficient Rust library for solving first-order predicate\nlogic expressions like they can be found in e.g. Prolog. Compare to the latter, this library is much\nmore simplistic, though extensible.\n\nAdditionally, there is a [REPL example](examples/repl.rs) for interactively playing around with the\nimplementation.\n\nFeatures that are implemented:\n- Standard (compound) terms\n- Named variables \u0026 wildcards\n- DFS-based inference\n- A \"cut\" operation for pruning the search space\n- Integer arithmetic\n- Extensibility through custom predicate resolvers (e.g. predicates with side effects)\n\n## Showcase\n\nIn the REPL you can quickly get started by loading one of the provided test files with some\npre-defined facts and rules, e.g. for [Peano arithmetic](testfiles/arithmetic.lru):\n\n```text\n#===================#\n# LogRu REPL v0.4.1 #\n#===================#\n\n?- :load testfiles/arithmetic.lru\nLoaded!\n```\n\nWe can then ask it to solve 2 + 3 (and find the correct answer 5):\n\n```text\n?- add(s(s(z)), s(s(s(z))), X).\nFound solution:\n  X = s(s(s(s(s(z)))))\nNo more solutions.\n```\n\nIt is also possible to enumerate all pairs of terms that add up to five:\n\n```text\n?- add(X, Y, s(s(s(s(s(z)))))).\nFound solution:\n  X = s(s(s(s(s(z)))))\n  Y = z\nFound solution:\n  X = s(s(s(s(z))))\n  Y = s(z)\nFound solution:\n  X = s(s(s(z)))\n  Y = s(s(z))\nFound solution:\n  X = s(s(z))\n  Y = s(s(s(z)))\nFound solution:\n  X = s(z)\n  Y = s(s(s(s(z))))\nFound solution:\n  X = z\n  Y = s(s(s(s(s(z)))))\nNo more solutions.\n```\n\nWhile there is no standard library, using the cut operation, it is possible to build many of the\ncommon combinators, such as `once`:\n\n```text\n?- :define once(X) :- X, !.\nDefined!\n```\n\nGiven a predicate producing multiple choices...\n\n```text\n?- :define foo(hello). foo(world).\nDefined!\n?- foo(X).\nFound solution:\n  X = hello\nFound solution:\n  X = world\nNo more solutions.\n```\n\n..., we can now restrict it to produce only the first choice:\n\n```text\n?- once(foo(X)).\nFound solution:\n  X = hello\nNo more solutions.\n```\n\nOther combiantors, like negation, can also be implemented using cut.\n\n## Rust API\n\nThe core of the API doesn't work with a textual representation of terms like the REPL does, but\nencodes everything as semi-opaque IDs. There are then higher-level APIs that provide naming for\nthose IDs.\n\n### Core API\n\nAt the core of the solver are the `logru::SymbolStore` and `logru::RuleSet` types, which\nhold all known facts and rules. A few simple rules for [Peano\narithmetic](https://en.wikipedia.org/wiki/Peano_axioms#Addition) can be defined like this:\n\n```rust\nuse logru::{\n    ast::{self, exists, Query, Rule},\n    query_dfs,\n    resolve::RuleResolver,\n    search::Solution,\n    SymbolStorage,\n};\n\nlet mut syms = logru::SymbolStore::new();\nlet mut r = logru::RuleSet::new();\n\n// Obtain IDs for the symbols we want to use in our terms.\n// The order of these calls doesn't matter.\nlet s = syms.get_or_insert_named(\"s\");\nlet z = syms.get_or_insert_named(\"z\");\n\nlet is_natural = syms.get_or_insert_named(\"is_natural\");\nlet add = syms.get_or_insert_named(\"add\");\n\n// Define the fact `is_natural(z)`, i.e. that zero is a natural number\nr.insert(Rule::fact(is_natural, vec![z.into()]));\n\n// Define the rule `is_natural(s(P)) :- is_natural(P)`, i.e. that\n// the successor of P is a natural number if P is also a natural number.\nr.insert(ast::forall(|[p]| {\n    Rule::fact(is_natural, vec![ast::app(s, vec![p.into()])]).when(is_natural, vec![p.into()])\n}));\n\n// Now we define a predicate for addition that we'll call add.\n// The statement `add(P, Q, R)` is true if P + Q = R.\n\n// Define the rule `add(P, z, P) :- is_natural(P)`, i.e. that\n// adding zero to P is P if P is a natural number.\n// This is the base case of Peano addition.\nr.insert(ast::forall(|[p]| {\n    Rule::fact(add, vec![p.into(), z.into(), p.into()]).when(is_natural, vec![p.into()])\n}));\n\n// Finally, define the rule `add(P, s(Q), s(R)) :- add(P, Q, R)`,\n// the recursive case of Peano addition.\nr.insert(ast::forall(|[p, q, r]| {\n    Rule::fact(\n        add,\n        vec![\n            p.into(),\n            ast::app(s, vec![q.into()]),\n            ast::app(s, vec![r.into()]),\n        ],\n    )\n    .when(add, vec![p.into(), q.into(), r.into()])\n}));\n\n// We can now ask the solver to prove statements within this universe, e.g. that \"there exists an X\n// such that X + 2 = 3\". This statement is indeed true for X = 1, and indeed, the solver will provide\n// this answer:\n\nlet mut r = RuleResolver::new(\u0026r);\n\n// Obtain an iterator that allows us to exhaustively search the solution space:\nlet solutions = query_dfs(\n    \u0026mut r,\n    \u0026exists(|[x]| {\n        Query::single_app(\n            add,\n            vec![\n                x.into(),\n                ast::app(s, vec![ast::app(s, vec![z.into()])]),\n                ast::app(s, vec![ast::app(s, vec![ast::app(s, vec![z.into()])])]),\n            ],\n        )\n    }),\n);\n\n// Sanity check\nassert_eq!(\n    solutions.collect::\u003cVec\u003c_\u003e\u003e(),\n    vec![Solution(vec![Some(ast::app(s, vec![z.into()]))])],\n);\n```\n\nThe solver uses a left-to-right depth-first search through the provided and derived goals. This is\nefficient to implement, but requires some care in how the predicates are set up in order to avoid an\ninfinite recursion.\n\n### Textual API\n\nFor an example of the textual API, see e.g. [`examples/zebra.rs`](examples/zebra.rs), solving a\nvariant of the famous [Zebra puzzle](https://en.wikipedia.org/wiki/Zebra_Puzzle).\n\nThe syntax is very similar to Prolog, but it is far from complete.\n\n### Performance\n\nA rudimentary performance comparison with SWI Prolog has been performed using an inefficient version\nof the Zebra puzzle ([`testfiles/zebra-reverse.lru`](testfiles/zebra-reverse.lru)) where the clauses\nof the `puzzle` rule are reversed.\n\nFor both SWI Prolog and Logru, this makes the Puzzle a lot slower to solve (not surprising since\nAFAIK SWI Prolog uses the same search order).\n\nWhile Logru takes about 48ms to solve the Puzzle and to conclude that there are no further solutions\non the machine at hand, Prolog takes about 13ms to find the solution and an additional 4ms to rule\nout any further solutions for a total of 17ms on the same machine.\n\nA large portion of that difference is apparently caused by the occurs check, which seems to be off\nby default in Prolog. In a version of Logru compiled without occurs check, the same puzzle is solved\nin ~23ms.\n\nAlthough even with the occurs check enabled, SWI Prolog is only a few milliseconds slower, so there\nare likely other optimizations at play, too.\n\n```text\n?- :load testfiles/zebra-reverse.lru\nLoaded!\n?- :time puzzle($0).\nFound solution:\n  $0 = list(house(yellow, norway, water, diplomat, fox), house(blue, italy, tea, physician, horse), house(red, england, milk, photographer, snails), house(white, spain, juice, violinist, dog), house(green, japan, coffee, painter, zebra))\nNo more solutions.\nTook 0.0603s\n```\n\n```text\n?- consult('testfiles/zebra-reverse.lru').\ntrue.\n\n?- time(puzzle(Houses)).\n% 86,673 inferences, 0.013 CPU in 0.013 seconds (100% CPU, 6567116 Lips)\nHouses = list(house(yellow, norway, water, diplomat, fox), house(blue, italy, tea, physician, horse), house(red, england, milk, photographer, snails), house(white, spain, juice, violinist, dog), house(green, japan, coffee, painter, zebra)) ;\n% 22,610 inferences, 0.004 CPU in 0.004 seconds (100% CPU, 6459533 Lips)\nfalse.\n```\n\n\n## Future Plans\n\nWithout committing to any sort of timeline, additional features that are worth experimenting with\nare:\n- More natural support for conjunctions and disjunctions (`,` and `;` respectively in Prolog).\n- Some sort of standard library.\n- Recursion and memory limits.\n- A profiling mode that counts some interesting facts and figures about the solver (e.g. number of\n  steps taken, number of instantiated rules, peak memory usage).\n- Making things even faster by e.g. optimising the occurs check.\n- Auto-completion in the REPL.\n\n## License\n\nLicensed under either of\n\n * Apache License, Version 2.0\n   ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license\n   ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\n\nat your option.\n\n## Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall be\ndual licensed as above, without any additional terms or conditions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffatho%2Flogru","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffatho%2Flogru","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffatho%2Flogru/lists"}