├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build.clj
├── deps.edn
├── doc
├── API.md
├── ARCHITECTURE.md
├── ideas.txt
└── rv.bib
├── pom.xml
├── src
└── fogus
│ └── rv
│ ├── amb.clj
│ ├── constraints.clj
│ ├── core.clj
│ ├── datalog.clj
│ ├── fuzzy
│ └── soundex.clj
│ ├── impl
│ └── unification.clj
│ ├── learn.clj
│ ├── learn
│ └── vs.clj
│ ├── productions.clj
│ ├── search.clj
│ ├── search
│ └── graph.clj
│ └── util.clj
└── test
└── rv
├── amb_test.clj
├── constraint_test.clj
├── datalog_test.clj
├── fuzzy
└── soundex_test.clj
├── learn
└── vs_test.clj
├── productions_test.clj
└── search
└── astar_test.clj
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | pom.xml.asc
5 | *.jar
6 | *.class
7 | /.lein-*
8 | /.nrepl-port
9 | .hgignore
10 | .hg/
11 | .cpcache
12 | _
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # v0.0.10
2 | - Added vector encoding for datalog tuples
3 |
4 | # v0.0.9
5 | - Added functions to enable human-in-the-loop learning to `fogus.rv.learn.vs` namespace
6 | - `(explain [vs example])` returns a structure explaining how the classifier reaches a conclusion
7 | - `(best-fit [vs example])` returns the best-fit hypothesis for an example
8 | - See [API.md](https://github.com/fogus/rv/blob/main/doc/API.md#fogusrvlearnvs) for details
9 | - Cleaned up the docstrings in the `fogus.rv.learn.vs`
10 |
11 | # v0.0.8
12 | - Added (fogus.rv.util/pairwise-every? [pred xs ys]) combinator to build Clojure
13 | style `*-every?` functions of two arguments.
14 | - Added (fogus.rv.util/positions-of [pred & xs]) function that returns the indices
15 | of values that match a predicate in any number of collections.
16 | - Added fogus.rv.learn.vs containing an implementation of version space learning
17 | using an inductive candidate-elimination algorithm.
18 | - Added docs/API.md documentation.
19 |
20 | # v0.0.7
21 | - Added (fogus.rv.util/f-by [f key coll]) combinator to build Clojure-style `*-by`
22 | functions.
23 | - Added fogus.rv.search ns containing search-related protocols GraphSearch and
24 | HeuristicSearch.
25 | - Refined fogus.rv.search.astar/astar to work in terms of the GraphSearch and
26 | HeuristicSearch protocols. Takes a graph object implementing those protocols
27 | plus a start node and goal node to find the lowest cost path between them.
28 |
29 | # v0.0.6
30 | - Added fogus.rv.constraints ns exposing two functions: satisfy1 and satisfy* that take
31 | a constraint description containing :variables and :formula mappings and return
32 | context(s) defining bindings for the variables constrained by formula.
33 | - Added a bibtex file with the references for rv
34 |
35 | # v0.0.5
36 | - Added the ability to define entity enumerations by mapping a key to a set. These are
37 | expanded into [id k v] for each element in the set.
38 | - Changed the name from reinen-vernunft to rv... please stop contacting me about the old name. ;)
39 |
40 | # v0.0.4
41 | - Added extra arity to both map->relation and table->kb to take id function, which can
42 | be used to override the default :kb/id gen or existing key.
43 | - Added a fogus.reinen-vernunft.fuzzy.soundex/encode function that implements the
44 | American soundex algorithm. https://en.wikipedia.org/wiki/Soundex
45 |
46 | # v0.0.3
47 | - Added fogus.reinen-vernunft.datalog ns based on cgrand's 39loc datalog
48 | - Added map->relation and table->kb conversion functions in fogus.reinen-vernunft.core
49 |
50 | # v0.0.2
51 | - me.fogus release
52 |
53 |
54 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Eclipse Public License - v 2.0
2 |
3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION
5 | OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
6 |
7 | 1. DEFINITIONS
8 |
9 | "Contribution" means:
10 |
11 | a) in the case of the initial Contributor, the initial content
12 | Distributed under this Agreement, and
13 |
14 | b) in the case of each subsequent Contributor:
15 | i) changes to the Program, and
16 | ii) additions to the Program;
17 | where such changes and/or additions to the Program originate from
18 | and are Distributed by that particular Contributor. A Contribution
19 | "originates" from a Contributor if it was added to the Program by
20 | such Contributor itself or anyone acting on such Contributor's behalf.
21 | Contributions do not include changes or additions to the Program that
22 | are not Modified Works.
23 |
24 | "Contributor" means any person or entity that Distributes the Program.
25 |
26 | "Licensed Patents" mean patent claims licensable by a Contributor which
27 | are necessarily infringed by the use or sale of its Contribution alone
28 | or when combined with the Program.
29 |
30 | "Program" means the Contributions Distributed in accordance with this
31 | Agreement.
32 |
33 | "Recipient" means anyone who receives the Program under this Agreement
34 | or any Secondary License (as applicable), including Contributors.
35 |
36 | "Derivative Works" shall mean any work, whether in Source Code or other
37 | form, that is based on (or derived from) the Program and for which the
38 | editorial revisions, annotations, elaborations, or other modifications
39 | represent, as a whole, an original work of authorship.
40 |
41 | "Modified Works" shall mean any work in Source Code or other form that
42 | results from an addition to, deletion from, or modification of the
43 | contents of the Program, including, for purposes of clarity any new file
44 | in Source Code form that contains any contents of the Program. Modified
45 | Works shall not include works that contain only declarations,
46 | interfaces, types, classes, structures, or files of the Program solely
47 | in each case in order to link to, bind by name, or subclass the Program
48 | or Modified Works thereof.
49 |
50 | "Distribute" means the acts of a) distributing or b) making available
51 | in any manner that enables the transfer of a copy.
52 |
53 | "Source Code" means the form of a Program preferred for making
54 | modifications, including but not limited to software source code,
55 | documentation source, and configuration files.
56 |
57 | "Secondary License" means either the GNU General Public License,
58 | Version 2.0, or any later versions of that license, including any
59 | exceptions or additional permissions as identified by the initial
60 | Contributor.
61 |
62 | 2. GRANT OF RIGHTS
63 |
64 | a) Subject to the terms of this Agreement, each Contributor hereby
65 | grants Recipient a non-exclusive, worldwide, royalty-free copyright
66 | license to reproduce, prepare Derivative Works of, publicly display,
67 | publicly perform, Distribute and sublicense the Contribution of such
68 | Contributor, if any, and such Derivative Works.
69 |
70 | b) Subject to the terms of this Agreement, each Contributor hereby
71 | grants Recipient a non-exclusive, worldwide, royalty-free patent
72 | license under Licensed Patents to make, use, sell, offer to sell,
73 | import and otherwise transfer the Contribution of such Contributor,
74 | if any, in Source Code or other form. This patent license shall
75 | apply to the combination of the Contribution and the Program if, at
76 | the time the Contribution is added by the Contributor, such addition
77 | of the Contribution causes such combination to be covered by the
78 | Licensed Patents. The patent license shall not apply to any other
79 | combinations which include the Contribution. No hardware per se is
80 | licensed hereunder.
81 |
82 | c) Recipient understands that although each Contributor grants the
83 | licenses to its Contributions set forth herein, no assurances are
84 | provided by any Contributor that the Program does not infringe the
85 | patent or other intellectual property rights of any other entity.
86 | Each Contributor disclaims any liability to Recipient for claims
87 | brought by any other entity based on infringement of intellectual
88 | property rights or otherwise. As a condition to exercising the
89 | rights and licenses granted hereunder, each Recipient hereby
90 | assumes sole responsibility to secure any other intellectual
91 | property rights needed, if any. For example, if a third party
92 | patent license is required to allow Recipient to Distribute the
93 | Program, it is Recipient's responsibility to acquire that license
94 | before distributing the Program.
95 |
96 | d) Each Contributor represents that to its knowledge it has
97 | sufficient copyright rights in its Contribution, if any, to grant
98 | the copyright license set forth in this Agreement.
99 |
100 | e) Notwithstanding the terms of any Secondary License, no
101 | Contributor makes additional grants to any Recipient (other than
102 | those set forth in this Agreement) as a result of such Recipient's
103 | receipt of the Program under the terms of a Secondary License
104 | (if permitted under the terms of Section 3).
105 |
106 | 3. REQUIREMENTS
107 |
108 | 3.1 If a Contributor Distributes the Program in any form, then:
109 |
110 | a) the Program must also be made available as Source Code, in
111 | accordance with section 3.2, and the Contributor must accompany
112 | the Program with a statement that the Source Code for the Program
113 | is available under this Agreement, and informs Recipients how to
114 | obtain it in a reasonable manner on or through a medium customarily
115 | used for software exchange; and
116 |
117 | b) the Contributor may Distribute the Program under a license
118 | different than this Agreement, provided that such license:
119 | i) effectively disclaims on behalf of all other Contributors all
120 | warranties and conditions, express and implied, including
121 | warranties or conditions of title and non-infringement, and
122 | implied warranties or conditions of merchantability and fitness
123 | for a particular purpose;
124 |
125 | ii) effectively excludes on behalf of all other Contributors all
126 | liability for damages, including direct, indirect, special,
127 | incidental and consequential damages, such as lost profits;
128 |
129 | iii) does not attempt to limit or alter the recipients' rights
130 | in the Source Code under section 3.2; and
131 |
132 | iv) requires any subsequent distribution of the Program by any
133 | party to be under a license that satisfies the requirements
134 | of this section 3.
135 |
136 | 3.2 When the Program is Distributed as Source Code:
137 |
138 | a) it must be made available under this Agreement, or if the
139 | Program (i) is combined with other material in a separate file or
140 | files made available under a Secondary License, and (ii) the initial
141 | Contributor attached to the Source Code the notice described in
142 | Exhibit A of this Agreement, then the Program may be made available
143 | under the terms of such Secondary Licenses, and
144 |
145 | b) a copy of this Agreement must be included with each copy of
146 | the Program.
147 |
148 | 3.3 Contributors may not remove or alter any copyright, patent,
149 | trademark, attribution notices, disclaimers of warranty, or limitations
150 | of liability ("notices") contained within the Program from any copy of
151 | the Program which they Distribute, provided that Contributors may add
152 | their own appropriate notices.
153 |
154 | 4. COMMERCIAL DISTRIBUTION
155 |
156 | Commercial distributors of software may accept certain responsibilities
157 | with respect to end users, business partners and the like. While this
158 | license is intended to facilitate the commercial use of the Program,
159 | the Contributor who includes the Program in a commercial product
160 | offering should do so in a manner which does not create potential
161 | liability for other Contributors. Therefore, if a Contributor includes
162 | the Program in a commercial product offering, such Contributor
163 | ("Commercial Contributor") hereby agrees to defend and indemnify every
164 | other Contributor ("Indemnified Contributor") against any losses,
165 | damages and costs (collectively "Losses") arising from claims, lawsuits
166 | and other legal actions brought by a third party against the Indemnified
167 | Contributor to the extent caused by the acts or omissions of such
168 | Commercial Contributor in connection with its distribution of the Program
169 | in a commercial product offering. The obligations in this section do not
170 | apply to any claims or Losses relating to any actual or alleged
171 | intellectual property infringement. In order to qualify, an Indemnified
172 | Contributor must: a) promptly notify the Commercial Contributor in
173 | writing of such claim, and b) allow the Commercial Contributor to control,
174 | and cooperate with the Commercial Contributor in, the defense and any
175 | related settlement negotiations. The Indemnified Contributor may
176 | participate in any such claim at its own expense.
177 |
178 | For example, a Contributor might include the Program in a commercial
179 | product offering, Product X. That Contributor is then a Commercial
180 | Contributor. If that Commercial Contributor then makes performance
181 | claims, or offers warranties related to Product X, those performance
182 | claims and warranties are such Commercial Contributor's responsibility
183 | alone. Under this section, the Commercial Contributor would have to
184 | defend claims against the other Contributors related to those performance
185 | claims and warranties, and if a court requires any other Contributor to
186 | pay any damages as a result, the Commercial Contributor must pay
187 | those damages.
188 |
189 | 5. NO WARRANTY
190 |
191 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
192 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS"
193 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
194 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF
195 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
196 | PURPOSE. Each Recipient is solely responsible for determining the
197 | appropriateness of using and distributing the Program and assumes all
198 | risks associated with its exercise of rights under this Agreement,
199 | including but not limited to the risks and costs of program errors,
200 | compliance with applicable laws, damage to or loss of data, programs
201 | or equipment, and unavailability or interruption of operations.
202 |
203 | 6. DISCLAIMER OF LIABILITY
204 |
205 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
206 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS
207 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
208 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
209 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
210 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
211 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
212 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
213 | POSSIBILITY OF SUCH DAMAGES.
214 |
215 | 7. GENERAL
216 |
217 | If any provision of this Agreement is invalid or unenforceable under
218 | applicable law, it shall not affect the validity or enforceability of
219 | the remainder of the terms of this Agreement, and without further
220 | action by the parties hereto, such provision shall be reformed to the
221 | minimum extent necessary to make such provision valid and enforceable.
222 |
223 | If Recipient institutes patent litigation against any entity
224 | (including a cross-claim or counterclaim in a lawsuit) alleging that the
225 | Program itself (excluding combinations of the Program with other software
226 | or hardware) infringes such Recipient's patent(s), then such Recipient's
227 | rights granted under Section 2(b) shall terminate as of the date such
228 | litigation is filed.
229 |
230 | All Recipient's rights under this Agreement shall terminate if it
231 | fails to comply with any of the material terms or conditions of this
232 | Agreement and does not cure such failure in a reasonable period of
233 | time after becoming aware of such noncompliance. If all Recipient's
234 | rights under this Agreement terminate, Recipient agrees to cease use
235 | and distribution of the Program as soon as reasonably practicable.
236 | However, Recipient's obligations under this Agreement and any licenses
237 | granted by Recipient relating to the Program shall continue and survive.
238 |
239 | Everyone is permitted to copy and distribute copies of this Agreement,
240 | but in order to avoid inconsistency the Agreement is copyrighted and
241 | may only be modified in the following manner. The Agreement Steward
242 | reserves the right to publish new versions (including revisions) of
243 | this Agreement from time to time. No one other than the Agreement
244 | Steward has the right to modify this Agreement. The Eclipse Foundation
245 | is the initial Agreement Steward. The Eclipse Foundation may assign the
246 | responsibility to serve as the Agreement Steward to a suitable separate
247 | entity. Each new version of the Agreement will be given a distinguishing
248 | version number. The Program (including Contributions) may always be
249 | Distributed subject to the version of the Agreement under which it was
250 | received. In addition, after a new version of the Agreement is published,
251 | Contributor may elect to Distribute the Program (including its
252 | Contributions) under the new version.
253 |
254 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient
255 | receives no rights or licenses to the intellectual property of any
256 | Contributor under this Agreement, whether expressly, by implication,
257 | estoppel or otherwise. All rights in the Program not expressly granted
258 | under this Agreement are reserved. Nothing in this Agreement is intended
259 | to be enforceable by any entity that is not a Contributor or Recipient.
260 | No third-party beneficiary rights are created under this Agreement.
261 |
262 | Exhibit A - Form of Secondary Licenses Notice
263 |
264 | "This Source Code may also be made available under the following
265 | Secondary Licenses when the conditions for such availability set forth
266 | in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public
267 | License as published by the Free Software Foundation, either version 2
268 | of the License, or (at your option) any later version, with the GNU
269 | Classpath Exception which is available at
270 | https://www.gnu.org/software/classpath/license.html."
271 |
272 | Simply including a copy of this Agreement, including this Exhibit A
273 | is not sufficient to license the Source Code under Secondary Licenses.
274 |
275 | If it is not possible or desirable to put the notice in a particular
276 | file, then You may include the notice in a location (such as a LICENSE
277 | file in a relevant directory) where a recipient would be likely to
278 | look for such a notice.
279 |
280 | You may add additional accurate notices of copyright ownership.
281 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rv
2 |
3 | Explorations in pure reasoning algorithms with Clojure.
4 |
5 | ## Including
6 |
7 | ### deps.edn
8 |
9 | me.fogus/rv {:mvn/version "0.0.10"}
10 |
11 | OR
12 |
13 | io.github.fogus/rv {:git/tag "v0.0.10" :git/sha "..."}
14 |
15 | ### Leiningen
16 |
17 | Modify your [Leiningen](http://github.com/technomancy/leiningen) dependencies to include:
18 |
19 | :dependencies [[me.fogus/rv "0.0.10"] ...]
20 |
21 | ### Maven
22 |
23 | Add the following to your `pom.xml` file:
24 |
25 |
26 | me.fogus
27 | rv
28 | 0.0.10
29 |
30 |
31 | ## Dev
32 |
33 | Namespaces under the wip sub-ns are works in progress and should only be used for experimentation. It is expected that these implementations will change frequently and may disappear altogether.
34 |
35 | clj -X:dev:test
36 |
37 | To generate the current API docs run the following:
38 |
39 | clj -Tquickdoc quickdoc '{:outfile "doc/API.md", :github/repo "https://github.com/fogus/rv", :toc false}'
40 |
41 | The above requires that you install quickdocs as a CLI tool first.
42 |
43 | ## License
44 |
45 | Copyright © 2017-2025 Fogus
46 |
47 | Distributed under the Eclipse Public License version 2.0
48 |
--------------------------------------------------------------------------------
/build.clj:
--------------------------------------------------------------------------------
1 | (ns build
2 | (:require [clojure.tools.build.api :as b]))
3 |
4 | (def lib 'me.fogus/rv)
5 | (def description "Code conversations in Clojure regarding the application of pure search, reasoning, and query algorithms.")
6 | ;;(def version (format "0.0.%s" (b/git-count-revs nil)))
7 | (def version "0.0.10")
8 | (def class-dir "target/classes")
9 | (def jar-file (format "target/%s.jar" (name lib)))
10 |
11 | ;; delay to defer side effects (artifact downloads)
12 | (def basis (delay (b/create-basis{:project "deps.edn"})))
13 |
14 | (defn clean [_]
15 | (b/delete {:path "target"}))
16 |
17 | (defn jar [_]
18 | (b/write-pom {:class-dir class-dir
19 | :lib lib
20 | :version version
21 | :basis (update-in @basis [:libs] dissoc 'org.clojure/clojure)
22 | :src-dirs ["src"]})
23 | (b/copy-dir {:src-dirs ["src" "resources"]
24 | :target-dir class-dir})
25 | (b/jar {:class-dir class-dir
26 | :jar-file jar-file}))
27 |
--------------------------------------------------------------------------------
/deps.edn:
--------------------------------------------------------------------------------
1 | {
2 | :paths ["src"]
3 | :deps {org.clojure/core.unify {:mvn/version "0.7.0"}
4 | me.fogus/evalive {:mvn/version "1.1.1"}}
5 |
6 | :aliases {:test {:extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}}
7 | :main-opts ["-m" "cognitect.test-runner"]
8 | :exec-fn cognitect.test-runner.api/test
9 | :extra-paths ["test"]}
10 |
11 | :build
12 | {:deps {io.github.clojure/tools.build {:git/tag "v0.10.6" :git/sha "52cf7d6"}}
13 | :ns-default build}
14 |
15 | :deploy
16 | {:extra-deps {slipset/deps-deploy {:mvn/version "RELEASE"}}
17 | :exec-fn deps-deploy.deps-deploy/deploy
18 | :exec-args {:installer :remote
19 | :sign-releases? true
20 | :sign-key-id "CBBDC7BE00954E2E3A46C80CA3994949855D2816"
21 | :artifact "target/rv.jar"}}
22 |
23 | :dev {:extra-deps {org.clojure/clojure {:mvn/version "1.12.0"}}}
24 | }
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/doc/API.md:
--------------------------------------------------------------------------------
1 |
2 | -----
3 | # fogus.rv.amb
4 |
5 |
6 | Provides an implementation of McCarthy's [`amb`](#fogus.rv.amb/amb) operator with
7 | binding forms and acceptance test operator.
8 |
9 |
10 |
11 |
12 | ## `accept`
13 | ``` clojure
14 |
15 | (accept condition ret)
16 | ```
17 | Function.
18 |
Source
19 |
20 | ## `amb`
21 | ``` clojure
22 |
23 | (amb & [binds & body])
24 | ```
25 | Function.
26 |
27 | A macro that provides a non-deterministic way to traverse a space
28 | and find a single solution amongst potentially many. If the search
29 | space is exhausted then [[[`amb`](#fogus.rv.amb/amb)](#fogus.rv.amb/amb)](#fogus.rv.amb/amb) will return `nil`. The general form
30 | of [[[`amb`](#fogus.rv.amb/amb)](#fogus.rv.amb/amb)](#fogus.rv.amb/amb) is as follows:
31 |
32 | (amb )
33 |
34 | Where `` is a typical Clojure bindings form:
35 |
36 | [ ... ]
37 |
38 | And `` is one or more Clojure expressions.
39 |
40 | Within the execution body the `(accept )`
41 | form is used to test some combination of the bindings for adherence
42 | to a `` and return an `` that serves as the
43 | return value of the call to [[[`amb`](#fogus.rv.amb/amb)](#fogus.rv.amb/amb)](#fogus.rv.amb/amb).
44 |
45 | A call to `(amb)` (i.e. without bindings and body) will exhaust
46 | immediately and thus result in `nil` as its value.
47 | Source
48 |
49 | -----
50 | # fogus.rv.constraints
51 |
52 |
53 | Constraints solving functions that operate on a Constraint Description
54 | which is a map describing a constraint description containing the mappings:
55 | - :variables -> seq of LVars
56 | - :formula -> list describing a predicate expression composed of a mix of
57 | the LVars in :variables and Clojure functions.
58 |
59 |
60 |
61 |
62 | ## `satisfy*`
63 | ``` clojure
64 |
65 | (satisfy* {:keys [variables formula :as c]})
66 | ```
67 |
68 | Accepts a map describing a constraint description containing the mappings:
69 | - :variables -> seq of LVars
70 | - :formula -> list describing a predicate expression composed of a mix of
71 | the LVars in :variables and Clojure functions
72 |
73 | This function will use the constraint description to calculate the all of
74 | the values for the LVars that satisfy the formula. The result is a seq of
75 | maps with mappings from LVar -> value. If there is no way to satisfy the
76 | formula then an empty seq is the result.
77 |
78 | The ordering of the results of this function is not guaranteed to be stable.
79 | Source
80 |
81 | ## `satisfy1`
82 | ``` clojure
83 |
84 | (satisfy1 {:keys [variables formula :as c]})
85 | ```
86 |
87 | Accepts a map describing a constraint description containing the mappings:
88 | - :variables -> seq of LVars
89 | - :formula -> list describing a predicate expression composed of a mix of
90 | the LVars in :variables and Clojure functions
91 |
92 | This function will use the constraint description to calculate the first
93 | set of values for the LVars that satisfy the formula. The result is a map
94 | with mappings from LVar -> value. If there is no way to satisfy the formula
95 | then an empty map is the result.
96 |
97 | The first found result of this function is not guaranteed to be stable.
98 | Source
99 |
100 | -----
101 | # fogus.rv.core
102 |
103 |
104 | Most functions in rv work off of one or more of the following core
105 | concepts:
106 |
107 | - Entity: a hashmap with a :kb/id key mapped to a unique value and namespaced keys
108 | - Table: a set of hashmaps or Entities
109 | - Fact: a vector triple in the form [entity-id attribute value]
110 | - Relation: a set of Facts pertaining to a particular Entity
111 | - LVar: a logic variable that can bind to any value in its :range
112 | - Ground: a concrete value
113 | - Query: a set of Facts containing a mix of LVars and Grounds
114 | - Rules: a set of Facts describing synthetic relations
115 | - Production: a pair of: antecedent query and consequent Facts
116 | - KB: a set of Relations about many Entities and possibly containing Productions
117 | - Constraint Description: a set of LVars and a Formula describing the domain of their values
118 | - Formula: a list describing a predicate expression of mixed LVars and clojure functions
119 |
120 |
121 |
122 |
123 | ## `->AnyT`
124 | ``` clojure
125 |
126 | (->AnyT)
127 | ```
128 | Source
129 |
130 | ## `->AskT`
131 | ``` clojure
132 |
133 | (->AskT)
134 | ```
135 | Source
136 |
137 | ## `->IgnoreT`
138 | ``` clojure
139 |
140 | (->IgnoreT)
141 | ```
142 | Source
143 |
144 | ## `AnyT`
145 |
146 |
147 |
148 | Source
149 |
150 | ## `AskT`
151 |
152 |
153 |
154 | Source
155 |
156 | ## `ID_KEY`
157 |
158 |
159 |
160 | Source
161 |
162 | ## `IgnoreT`
163 |
164 |
165 |
166 | Source
167 |
168 | ## `lv?`
169 | ``` clojure
170 |
171 | (lv? %1)
172 | ```
173 | Source
174 |
175 | ## `map->relation`
176 | ``` clojure
177 |
178 | (map->relation entity)
179 | (map->relation idfn entity)
180 | ```
181 |
182 | Converts a map to a set of tuples for that map, applying a unique
183 | :kb/id if the map doesn't already have a value mapped for that key.
184 |
185 | Relation values that are sets are expanded into individual tuples
186 | per item in the set with the same :kb/id as the entity and the
187 | attribute that the whole set was mapped to.
188 |
189 | An idfn is a function of map -> id and if provided is used to
190 | override the default entity id generation and any existing :kb/id
191 | values.
192 | Source
193 |
194 | ## `table->kb`
195 | ``` clojure
196 |
197 | (table->kb table)
198 | (table->kb idfn table)
199 | ```
200 |
201 | Converts a Table into a KB, applying unique :kb/id to maps without a
202 | mapped identity value.
203 |
204 | See map->relation for more information about how the entities in the
205 | table are converted to relations.
206 |
207 | An idfn is a function of map -> id and if provided is used to
208 | override the default entity id generation and any existing :kb/id
209 | values.
210 | Source
211 |
212 | -----
213 | # fogus.rv.datalog
214 |
215 |
216 | A minimal implementation of Datalog.
217 |
218 |
219 |
220 |
221 | ## `linked-list-rules`
222 |
223 |
224 |
225 | Source
226 |
227 | ## `q`
228 | ``` clojure
229 |
230 | (q query kb)
231 | (q query kb rules)
232 | ```
233 |
234 | Queries a knowledge base or a set of relations given a vector
235 | form of a query and an optional set of rules.
236 |
237 | A query takes the form:
238 |
239 | [:find find-spec :where clauses]
240 |
241 | A find-spec can be any number of lvars like:
242 |
243 | [:find ?e ?v :where ...]
244 |
245 | or a tuple containing a mix of lvars and grounds which is used to
246 | build output tuples from the query results:
247 |
248 | [:find [?e :an/attribute ?v] :where ...]
249 |
250 | The :where clauses are any number of tuples containing a mix of
251 | lvars and grounds:
252 |
253 | [:find ...
254 | :where
255 | [?e :an/attribute ?v]
256 | [?e :another/attr 42]]
257 |
258 | :where clauses may also contain filters defined as calls to predicates
259 | used to constrain the values that may bind to lvars:
260 |
261 | [:find ...
262 | :where
263 | [?e :an/attribute ?v]
264 | (= ?v 42)]
265 |
266 | The possible filter predicates are: =, not=, <, >, <=, >=
267 |
268 | rules are a vector of lists where each list defines a rule with a
269 | single head tuple followed by any number of rule clauses:
270 |
271 | ([?p :relationship/parent ?c] [?p :relationship/father ?c])
272 |
273 | The rule above defines a syntheic relation called
274 | `:relationship/parent` defined in terms of another relation
275 | `relationship/father`. Rules describe synthetic relations derived
276 | from real relations in the data or other synthetic relations
277 | derived from previous rule applications.
278 | Source
279 |
280 | ## `query->map`
281 | ``` clojure
282 |
283 | (query->map query)
284 | ```
285 |
286 | Accepts the vector form of a Datalog query and outputs a map
287 | of the component sections as keyword->seq mappings.
288 | Source
289 |
290 | -----
291 | # fogus.rv.fuzzy.soundex
292 |
293 |
294 | I came across the Soundex algorithm when researching the retro KAMAS outlining application.
295 | Soundex is a phonetic algorithm for indexing words by sound.
296 |
297 |
298 |
299 |
300 | ## `encode`
301 | ``` clojure
302 |
303 | (encode word & {:keys [numeric?], :as opts})
304 | ```
305 |
306 | Soundex is an algorithm for creating indices for words based on their
307 | English pronunciation. Homophones are encoded such that words can be matched
308 | despite minor differences in spelling. Example, the words "Ashcraft" and
309 | "Ashcroft" are both encoded as the same soundex code "A261".
310 |
311 | This function accepts the following keyword arguments:
312 |
313 | :numeric? -> true numerically encodes the entire word rather than using
314 | the default soundex letter prefix.
315 | Source
316 |
317 | -----
318 | # fogus.rv.impl.unification
319 |
320 |
321 | Provides internal unification functions.
322 | DO NOT USE THIS NS.
323 | There is no guarantee that it will remain stable or at all.
324 |
325 |
326 |
327 |
328 | ## `subst`
329 |
330 |
331 |
332 | Source
333 |
334 | -----
335 | # fogus.rv.learn
336 |
337 |
338 | Common learning-related functions and protocols.
339 |
340 |
341 |
342 |
343 | ## `-generalize`
344 | ``` clojure
345 |
346 | (-generalize lhs rhs)
347 | ```
348 | Source
349 |
350 | ## `-init`
351 | ``` clojure
352 |
353 | (-init basis)
354 | (-init basis arity)
355 | ```
356 | Source
357 |
358 | ## `-specialize`
359 | ``` clojure
360 |
361 | (-specialize lhs neg rhs)
362 | ```
363 | Source
364 |
365 | ## `S&G`
366 |
367 |
368 |
369 | Source
370 |
371 | -----
372 | # fogus.rv.learn.vs
373 |
374 |
375 | Version spaces are a binary classification, empirical learning algorithm.
376 | The approach, as described in 'Version spaces: a candidate elimination approach
377 | to rule learning' by Tom Mitchel (1977) takes training examples (currently
378 | Tuples of a like-arity) and manages a 'version space'. A version space is a
379 | map containing two 'boundaries' `:S` and `:G`. The `:G` boundary contains 'hypotheses'
380 | corresponding to the most general versions of the training data that are consistent
381 | and `:S` is the most specific versions. When a version space is presented with a new
382 | example it runs a 'candidate elimination' algorithm to modify the boundaries `:S`
383 | and `:G` accordingly. Examples can be marked as 'positive' examples, meaning
384 | that they are preferred instances. Anything not marked as 'positive' are taken as
385 | negative examples. Once trained, a version space can classify new examples as
386 | `::positive` or `::negative`. If new examples are not covered by the existing hypotheses
387 | in either boundary then they are classified as `::ambiguous` instead.
388 |
389 |
390 |
391 |
392 | ## `??`
393 |
394 |
395 |
396 | Source
397 |
398 | ## `?G`
399 |
400 |
401 |
402 | Source
403 |
404 | ## `?S`
405 |
406 |
407 |
408 | Source
409 |
410 | ## `applicable?`
411 | ``` clojure
412 |
413 | (applicable? vs example)
414 | (applicable? vs example positive?)
415 | ```
416 |
417 | Returns true if at least one hypothesis in the version space `vs` is consistent
418 | with the `example` and false otherwise.
419 | Source
420 |
421 | ## `arity-vec`
422 | ``` clojure
423 |
424 | (arity-vec n)
425 | ```
426 |
427 | Returns a vector template for arity n.
428 | Source
429 |
430 | ## `best-fit`
431 | ``` clojure
432 |
433 | (best-fit vs example)
434 | ```
435 |
436 | Returns the best-fit hypothesis coverage analysis (see [`explain`](#fogus.rv.learn.vs/explain)) for a given
437 | version space `vs` and compatible `example`. The metadata of the best fit return will
438 | have a mapping of `::fit-from` -> `:S` or `:G` pertaining to which boundary set the
439 | fit came from.
440 | Source
441 |
442 | ## `classify`
443 | ``` clojure
444 |
445 | (classify vs example)
446 | ```
447 |
448 | Attempts to classify an `example` using the given version space `vs`.
449 | Returns `::positive`, `::negative`, or `::ambiguous` if the boundaries
450 | G and S are incongruent.
451 | Source
452 |
453 | ## `collapsed?`
454 | ``` clojure
455 |
456 | (collapsed? vs)
457 | (collapsed? g s)
458 | ```
459 |
460 | Returns if a version space `vs` or boundaries `g` and `s` have collapsed.
461 | That is, training data have caused the hypotheses to become inconsistent,
462 | making further classification impossible.
463 | Source
464 |
465 | ## `consistent?`
466 | ``` clojure
467 |
468 | (consistent? vs example)
469 | (consistent? vs example positive?)
470 | ```
471 |
472 | Returns `true` if all hypotheses in the version space `vs`'s general and specific
473 | boundaries are consistent with the `example` features and classification.
474 | Source
475 |
476 | ## `converged?`
477 | ``` clojure
478 |
479 | (converged? vs)
480 | (converged? g s)
481 | ```
482 |
483 | Returns if a version space `vs` or boundaries `g` and `s` have
484 | converged. That is, training has caused the boundaries to converge to a single
485 | case.
486 | Source
487 |
488 | ## `covers?`
489 | ``` clojure
490 |
491 | (covers? hypothesis example)
492 | ```
493 |
494 | Takes a `hypothesis` from a version space and returns if the `example` is
495 | consistent with it.
496 | Source
497 |
498 | ## `explain`
499 | ``` clojure
500 |
501 | (explain vs example)
502 | ```
503 |
504 | Returns a structure explaining how the classifier reaches a conclusion,
505 | given a version space `vs` and a compatible `example`.
506 |
507 | The map returned contains the mappings:
508 |
509 | - `:explain/classification` -> the result of the call to [`classify`](#fogus.rv.learn.vs/classify)
510 | - `:explain/example` -> the example given
511 | - `explain/G` -> a sequence of hypotheses coverage analysis structures in the G boundary
512 | - `explain/S` -> a sequence of hypotheses coverage analysis structures in the S boundary
513 |
514 | The hypotheses coverage analyses contain the mappings:
515 |
516 | - `:hypothesis` -> The hypothesis inspected
517 | - `:covers?` -> true or false if the hypothesis covers the example
518 | - `:similarity` -> A ratio of hypothesis coverages over its arity
519 | - `:mismatched-features` -> a sequence of the features of the hypothesis that do not match the example
520 |
521 | A mismatched feature of a hypothesis has the mappings:
522 |
523 | - `:position` -> the position of the feature in the hypothesis
524 | - `:constraint` -> the value or wildcard at that position
525 |
526 | The information provided is sufficient for informing human-in-the-loop learning
527 | interactions.
528 | Source
529 |
530 | ## `refine`
531 | ``` clojure
532 |
533 | (refine vs example)
534 | (refine vs example positive?)
535 | ```
536 |
537 | Given a version space `vs` and an `example`, returns a new version space
538 | with boundaries adjusted according to the given example's features and
539 | classification. An example is marked as positive by attaching a metadata mapping
540 | `:positive?` -> boolean or by passing a boolean as the last argument. The
541 | explicit classification argument will always dominate the metadata
542 | classification.
543 | Source
544 |
545 | -----
546 | # fogus.rv.productions
547 |
548 |
549 | The simplest possible production rules system that uses a set
550 | of EAV tuples as its knowledge base.
551 |
552 |
553 |
554 |
555 | ## `apply-production`
556 | ``` clojure
557 |
558 | (apply-production production facts context)
559 | ```
560 | Source
561 |
562 | ## `cycle`
563 | ``` clojure
564 |
565 | (cycle qf kb)
566 | ```
567 |
568 | Feeds the results of states into a function qf that is responsible for
569 | detecting when production firings have stopped and returns an augmented
570 | fact set.
571 | Source
572 |
573 | ## `naive-qf`
574 | ``` clojure
575 |
576 | (naive-qf states)
577 | ```
578 |
579 | Takes the last environment in a long sequence of states in the hope that
580 | the sequence was long enough that all of the productions fired in creating it.
581 | Source
582 |
583 | ## `select-production`
584 | ``` clojure
585 |
586 | (select-production selection-strategy {:keys [productions facts]})
587 | ```
588 |
589 | Builds a sequence of bindings paired with each production and then uses a selection
590 | function to execute one of the productions that matched.
591 | Source
592 |
593 | ## `states`
594 | ``` clojure
595 |
596 | (states kb)
597 | ```
598 |
599 | Will apply the result of one production firing to the fact base and feed
600 | the result forward into the next firing.
601 | Source
602 |
603 | ## `step`
604 | ``` clojure
605 |
606 | (step kb)
607 | (step choice-fn kb)
608 | ```
609 |
610 | Takes a set of productions and facts and returns a new fact base based on the application of single production.
611 | Source
612 |
613 | ## `unifications`
614 | ``` clojure
615 |
616 | (unifications [clause & more :as clauses] facts context)
617 | ```
618 |
619 | Walks through all of the clauses in an implied antecedent and matches
620 | each against every fact provided. Returns a seq of contexts representing
621 | all of the bindings established by the antecedent unifications across all
622 | facts provided.
623 | Source
624 |
625 | -----
626 | # fogus.rv.search
627 |
628 |
629 | Common search-related functions and protocols.
630 |
631 |
632 |
633 |
634 | ## `GraphSearch`
635 |
636 |
637 |
638 |
639 | Functions related to graph-search algorithms.
640 | Source
641 |
642 | ## `HeuristicSearch`
643 |
644 |
645 |
646 |
647 | Function(s) related to heuristic-guided search.
648 | Source
649 |
650 | ## `add-route`
651 | ``` clojure
652 |
653 | (add-route _ node new-route)
654 | ```
655 |
656 | Adds a route to a `node` as a seq of nodes. Implementors of this function
657 | should return an instance of the object implementing this protocol.
658 | Source
659 |
660 | ## `cost-of`
661 | ``` clojure
662 |
663 | (cost-of _ node)
664 | ```
665 |
666 | Returns the cost to visit `node`.
667 | Source
668 |
669 | ## `estimate-cost`
670 | ``` clojure
671 |
672 | (estimate-cost _ node goal)
673 | ```
674 |
675 | Returns an estimated cost of the route from `node` to `goal`.
676 | Source
677 |
678 | ## `neighbors-of`
679 | ``` clojure
680 |
681 | (neighbors-of _ node)
682 | ```
683 |
684 | Returns a seq of neighbors of the given `node`.
685 | Source
686 |
687 | ## `route-of`
688 | ``` clojure
689 |
690 | (route-of _ node)
691 | ```
692 |
693 | Given a `node`, returns the route associated with it.
694 | Source
695 |
696 | -----
697 | # fogus.rv.search.graph
698 |
699 |
700 | A* search implementation.
701 |
702 |
703 |
704 |
705 | ## `astar`
706 | ``` clojure
707 |
708 | (astar graph start-node goal-node)
709 | ```
710 |
711 | Implements a lazy A* best-first graph traversal algorithm. Takes a
712 | `graph` object implementing both of the `fogus.rv.search.GraphSearch`
713 | and `fogus.rv.search.HeuristicSearch` protocols and a `start-node`
714 | and `goal-node` describing the bounds of the search. Returns of map
715 | with keys `:path` mapped to a sequence of nodes from `start-node` to
716 | `goal-node` and `:cost` describing the cost of the path. This search
717 | guarantees to return the lowest cost path as long as one exists.
718 | In the event that there is no path to the `goal-node` the current result
719 | is undefined.
720 | Source
721 |
722 | -----
723 | # fogus.rv.util
724 |
725 |
726 |
727 |
728 |
729 |
730 | ## `cart`
731 | ``` clojure
732 |
733 | (cart colls)
734 | ```
735 | Source
736 |
737 | ## `f-by`
738 | ``` clojure
739 |
740 | (f-by f key coll)
741 | ```
742 | Source
743 |
744 | ## `pairwise-every?`
745 | ``` clojure
746 |
747 | (pairwise-every? pred xs ys)
748 | ```
749 | Source
750 |
751 | ## `positions-of`
752 | ``` clojure
753 |
754 | (positions-of pred & xs)
755 | ```
756 | Source
757 |
758 | ## `process-bindings`
759 | ``` clojure
760 |
761 | (process-bindings bindings)
762 | ```
763 | Source
764 |
--------------------------------------------------------------------------------
/doc/ARCHITECTURE.md:
--------------------------------------------------------------------------------
1 | # reinen-vernunft architecture concerns
2 |
3 | ## EAV tuple sets
4 |
5 | If at all possible the reasoning functions provided herein will operate on EAV tuple sets. EAV stands for Entity, Attribute, Value. An EAV tuple is simply a Clojure vector of three elements, such as:
6 |
7 | [100 :person/name "Gilbert"]
8 |
9 | In the EAV tuple above the elements refer to the following:
10 |
11 | * *Entity* - a unique id that refers to an entity in the tuple set
12 | * *Attribute* - a keyword that names a common attribute
13 | * *Value* - the value of the attribute for the given entity
14 |
15 | The id contained in the first slot of the tuple should be a unique value pertaining to a set of attributes associated with a single entity. Any tuples in the EAV set with the same id will logically refer to the same entity:
16 |
17 | [100 :person/name "Gilbert"]
18 | [100 :person/age 42]
19 |
20 | The id 100 thus pertains to a single person named Gilbert who's aged 42. The id can be any type that allows mutual comparability.
21 |
22 | The entire collection of EAV tuples refering to one or more entities should be contained in a Clojure set.
23 |
24 | #{[100 :person/name "Gilbert"], [100 :person/age 42]}
25 |
26 | An EAV tuple set can be thought of as representing a relational database:
27 |
28 | #{[100 :person/name "Gilbert"]
29 | [100 :person/age 42]
30 | [200 :person/name "Zippy"]
31 | [300 :person/name "Queequeg"]
32 | [100 :person/friend 200]}
33 |
34 | In the EAV tuple set above, the `:person/friend` attribute establishes a link between two entities via their ids.
35 |
36 | ### Why EAV tuple sets?
37 |
38 | Aside from the simplicity of the data, the relational nature of the EAV tuple set is robust enough to model fairly complex domains. In addition, there are existing libraries that can directly query EAV tuple sets:
39 |
40 | * [DataScript](https://github.com/tonsky/datascript)
41 | * [Datomic](http://www.datomic.com)
42 |
43 | To name only a couple.
44 |
45 | ## Logic variables
46 |
47 | TODO
48 |
49 |
--------------------------------------------------------------------------------
/doc/ideas.txt:
--------------------------------------------------------------------------------
1 | Some possible implementations to explore:
2 |
3 | - Frames
4 | - BEINGS
5 | - Goal reduction
6 | - Hillcliming search
7 | - Microkanren
8 | - pmatch
9 | - Version Spaces
10 | - https://www.cs.cmu.edu/afs/cs/project/ai-repository/ai/areas/reasonng/0.html
11 | - "Building Problem Solvers"
12 | - Add a smarter quiessence function to productions rules
13 | - Relational algebra (built on clojure.set)
14 | - https://www.youtube.com/watch?v=HB5TrK7A4pI
15 |
--------------------------------------------------------------------------------
/doc/rv.bib:
--------------------------------------------------------------------------------
1 | @incollection{Hughes:90,
2 | AUTHOR = "J. Hughes",
3 | TITLE = "Why Functional Programming Matters",
4 | YEAR = 1990,
5 | EDITOR = "D.A. Turner",
6 | BOOKTITLE = "Research Topics in Functional Programming",
7 | PUBLISHER = "Addison Wesley",
8 | PAGES = 17}
9 |
10 | @inproceedings {Wadler:92,
11 | AUTHOR = "Wadler, P.",
12 | TITLE = "The Essence of Functional Programming",
13 | BOOKTITLE = "19th Annual Symposium on Principles of Programming Languages, Albuquerque",
14 | YEAR = 1992}
15 |
16 | @book {Ableson1985,
17 | title = "Structure and Interpretation of Computer Programs",
18 | author = {Abelson, Harold and Sussman, Gerald Jay and {with~Julie~Sussman}},
19 | edition = {1st~Editon},
20 | publisher = {MIT Press/McGraw-Hill},
21 | year = 1985}
22 |
23 | @book{marriott98programming,
24 | Title = "Programming with Constraints: An Introduction",
25 | Author = {Marriott, Kim and Stuckey, Peter J.},
26 | Publisher = {The MIT Press},
27 | Year = 1998}
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | jar
5 | me.fogus
6 | rv
7 | 0.0.10
8 | rv
9 |
10 |
11 | org.clojure
12 | core.unify
13 | 0.7.0
14 |
15 |
16 | me.fogus
17 | evalive
18 | 1.1.1
19 |
20 |
21 |
22 | src
23 |
24 |
25 |
26 | clojars
27 | https://repo.clojars.org/
28 |
29 |
30 |
31 |
32 | Eclipse Public License 1.0
33 | https://opensource.org/license/epl-2-0
34 | repo
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/fogus/rv/amb.clj:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Fogus. All rights reserved.
2 | ; The use and distribution terms for this software are covered by the
3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ; which can be found in the file epl-v10.html at the root of this distribution.
5 | ; By using this software in any fashion, you are agreeing to be bound by
6 | ; the terms of this license.
7 | ; You must not remove this notice, or any other, from this software.
8 |
9 | (ns fogus.rv.amb
10 | "Provides an implementation of McCarthy's `amb` operator with
11 | binding forms and acceptance test operator."
12 | (:require [fogus.rv.util :as util]))
13 |
14 | (defmacro accept [condition ret]
15 | `(do (when (not ~condition)
16 | (throw (ex-info "Failing" {::failure '~condition
17 | ::backtrack true})))
18 | ~ret))
19 |
20 | (defmacro amb
21 | "A macro that provides a non-deterministic way to traverse a space
22 | and find a single solution amongst potentially many. If the search
23 | space is exhausted then `amb` will return `nil`. The general form
24 | of `amb` is as follows:
25 |
26 | (amb )
27 |
28 | Where `` is a typical Clojure bindings form:
29 |
30 | [ ... ]
31 |
32 | And `` is one or more Clojure expressions.
33 |
34 | Within the execution body the `(accept )`
35 | form is used to test some combination of the bindings for adherence
36 | to a `` and return an `` that serves as the
37 | return value of the call to `amb`.
38 |
39 | A call to `(amb)` (i.e. without bindings and body) will exhaust
40 | immediately and thus result in `nil` as its value."
41 | [& [binds & body]]
42 | (when (and binds body)
43 | (let [{:keys [names values]} (util/process-bindings binds)]
44 | `(let [proc# (fn [[~@names]]
45 | (try (do ~@body)
46 | (catch clojure.lang.ExceptionInfo e#
47 | (ex-data e#))))
48 | vals# (util/cart ~values)]
49 | (loop [[v# & vs#] vals#]
50 | (let [result# (proc# v#)]
51 | (if (::backtrack result#)
52 | (when (seq vs#)
53 | (recur vs#))
54 | result#)))))))
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/fogus/rv/constraints.clj:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Fogus. All rights reserved.
2 | ; The use and distribution terms for this software are covered by the
3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ; which can be found in the file epl-v10.html at the root of this distribution.
5 | ; By using this software in any fashion, you are agreeing to be bound by
6 | ; the terms of this license.
7 | ; You must not remove this notice, or any other, from this software.
8 |
9 | (ns fogus.rv.constraints
10 | "Constraints solving functions that operate on a Constraint Description
11 | which is a map describing a constraint description containing the mappings:
12 | - :variables -> seq of LVars
13 | - :formula -> list describing a predicate expression composed of a mix of
14 | the LVars in :variables and Clojure functions."
15 | (:require [fogus.rv.core :as core]
16 | [fogus.rv.util :as util]
17 | [fogus.rv.impl.unification :as u]))
18 |
19 | (defn- cartesian-groups [vars]
20 | (let [tuples (util/cart (map :range vars))]
21 | (map #(map vector vars %) tuples)))
22 |
23 | (defn- test-in-context [formula group]
24 | (let [formula' (u/subst formula (into {} group))]
25 | (eval formula')))
26 |
27 | (defn- find1 [formula [group & more :as groupings]]
28 | (cond (nil? groupings) []
29 | (test-in-context formula group) group
30 | :else (recur formula more)))
31 |
32 | (defn satisfy1
33 | "Accepts a map describing a constraint description containing the mappings:
34 | - :variables -> seq of LVars
35 | - :formula -> list describing a predicate expression composed of a mix of
36 | the LVars in :variables and Clojure functions
37 |
38 | This function will use the constraint description to calculate the first
39 | set of values for the LVars that satisfy the formula. The result is a map
40 | with mappings from LVar -> value. If there is no way to satisfy the formula
41 | then an empty map is the result.
42 |
43 | The first found result of this function is not guaranteed to be stable."
44 | [{:keys [variables formula :as c]}]
45 | (into {} (find1 formula (cartesian-groups variables))))
46 |
47 | (defn satisfy*
48 | "Accepts a map describing a constraint description containing the mappings:
49 | - :variables -> seq of LVars
50 | - :formula -> list describing a predicate expression composed of a mix of
51 | the LVars in :variables and Clojure functions
52 |
53 | This function will use the constraint description to calculate the all of
54 | the values for the LVars that satisfy the formula. The result is a seq of
55 | maps with mappings from LVar -> value. If there is no way to satisfy the
56 | formula then an empty seq is the result.
57 |
58 | The ordering of the results of this function is not guaranteed to be stable."
59 | [{:keys [variables formula :as c]}]
60 | (let [groupings (cartesian-groups variables)
61 | groupings* (take-while seq (iterate rest groupings))]
62 | (->> groupings*
63 | (map #(find1 formula %))
64 | (keep seq)
65 | set
66 | (map #(into {} %)))))
67 |
--------------------------------------------------------------------------------
/src/fogus/rv/core.clj:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Fogus. All rights reserved.
2 | ; The use and distribution terms for this software are covered by the
3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ; which can be found in the file epl-v10.html at the root of this distribution.
5 | ; By using this software in any fashion, you are agreeing to be bound by
6 | ; the terms of this license.
7 | ; You must not remove this notice, or any other, from this software.
8 |
9 | (ns fogus.rv.core
10 | "Most functions in rv work off of one or more of the following core
11 | concepts:
12 |
13 | Entity: a hashmap with a :kb/id key mapped to a unique value and namespaced keys.
14 |
15 | {:kb/id :person/john-doe
16 | :person/name "John Doe"
17 | :person/age 42}
18 |
19 | Table: a set of hashmaps or Entities. Tables represent unstructured or
20 | semi-structured collections of data.
21 |
22 | #{{:kb/id :city/blt :city/name "Baltimore"}
23 | {:kb/id :city/atl :city/name "Atlanta"}}
24 |
25 | Fact: a vector triple in the form [entity-id attribute value] that describe that
26 | a given entity has an attribute with a specific value. You can tie facts together
27 | by referencing their :kb/ids.
28 |
29 | [:person/john-doe :person/age 42]
30 | [:city/blt :city/name \"Baltimore\"]
31 |
32 | Relation: a set of Facts pertaining to a particular Entity. You can tie facts
33 | together by referencing :kb/ids in value positions.
34 |
35 | #{[:person/john-doe :person/age 42]
36 | [:city/blt :city/name \"Baltimore\"]
37 | [:person/john-doe :address/city :city/blt]}
38 |
39 | LVar: a logic variable that can unify with any value in its :range
40 |
41 | (map->LVar {:domain 'x :range (range 0 5)})
42 |
43 | Ground: a concrete value, like a keyword, number, string, etc.
44 |
45 | 42, \"John Doe\", :city/blt
46 |
47 | Query: a set of Facts containing a mix of LVars and Grounds used to find
48 | bindings for the LVars that satisfy a set of Facts.
49 |
50 | Rules: a set of Facts describing derived or synthetic relations in terms of
51 | existing ones.
52 |
53 | Production: a map containing :antecedent -> query and :consequent -> Facts
54 | to be asserted if the query fires.
55 |
56 | KB: a set of Relations about many Entities and possibly containing Productions. It
57 | represents all the knowledge currently known or derivable.
58 |
59 | Constraint Description: a set of LVars and a Formula describing the domain
60 | of their values.
61 |
62 | Formula: a list describing a predicate expression of mixed LVars and clojure functions."
63 | (:import java.io.Writer))
64 |
65 | ;; Logic variables
66 |
67 | (defrecord LVar [domain range]
68 | Object
69 | (toString [this]
70 | (if range
71 | (str "?" domain "::" range)
72 | (str "?" domain))))
73 |
74 | (def lv? #(instance? LVar %))
75 |
76 | (deftype IgnoreT []
77 | Object
78 | (toString [_] "_")
79 | (equals [_ o] (instance? IgnoreT o)))
80 |
81 | (deftype AnyT []
82 | Object
83 | (toString [_] "*")
84 | (equals [_ o] (instance? AnyT o)))
85 |
86 | (deftype AskT []
87 | Object
88 | (toString [_] "?")
89 | (equals [_ o] true))
90 |
91 | (defmethod print-method LVar [lvar ^Writer writer]
92 | (.write writer (str lvar)))
93 |
94 | (defmethod print-method IgnoreT [i ^Writer writer]
95 | (.write writer (str i)))
96 |
97 | (defmethod print-method AnyT [a ^Writer writer]
98 | (.write writer (str a)))
99 |
100 | (def ID_KEY :kb/id)
101 |
102 | (def ^:private use-or-gen-id
103 | (let [next-id (atom 0)]
104 | (fn [entity]
105 | (if-let [id (get entity ID_KEY)]
106 | id
107 | (swap! next-id inc)))))
108 |
109 | (defn- set->tuples
110 | [id k s]
111 | (for [v s] [id k v]))
112 |
113 | (defn- vector->tuples
114 | [idfn eid k v]
115 | (let [vid (idfn v)
116 | sid (idfn v)
117 | pre [[eid k vid]
118 | [vid :sequence/items sid]
119 | [vid :sequence/indexed? true]]]
120 | (loop [elems v
121 | i 0
122 | cid sid
123 | tuples pre]
124 | (if (seq elems)
125 | (let [head [cid :cell/head (first elems)]
126 | index [cid :cell/i i]
127 | tid (when (next elems)
128 | (idfn (inc i)))]
129 | (recur (rest elems)
130 | (inc i)
131 | tid
132 | (into tuples (if tid
133 | [head index [cid :cell/tail tid]]
134 | [head index]))))
135 | tuples))))
136 |
137 | (comment
138 | (map->relation {:kb/id :primes
139 | :num/primes [1 2 3 5 7]})
140 |
141 | (vector->tuples use-or-gen-id :primes :num/primes [1 2 3 5 7])
142 |
143 | ;; becomes
144 |
145 | [:primes :num/primes 100]
146 | [100 :sequence/items 101]
147 | [100 :sequence/indexed? true]
148 | [101 :cell/head 1] [101 :cell/i 0] [101 :cell/tail 103]
149 | [103 :cell/head 2] [103 :cell/i 1] [103 :cell/tail 105]
150 | [105 :cell/head 3] [105 :cell/i 2] [105 :cell/tail 107]
151 | [107 :cell/head 5] [107 :cell/i 3] [107 :cell/tail 109]
152 | [109 :cell/head 7] [109 :cell/i 4]
153 |
154 | )
155 |
156 | (defn map->relation
157 | "Converts a map to a set of tuples for that map, applying a unique
158 | :kb/id if the map doesn't already have a value mapped for that key.
159 |
160 | Relation values that are sets are expanded into individual tuples
161 | per item in the set with the same :kb/id as the entity and the
162 | attribute that the whole set was mapped to.
163 |
164 | An idfn is a function of map -> id and if provided is used to
165 | override the default entity id generation and any existing :kb/id
166 | values."
167 | ([entity]
168 | (map->relation use-or-gen-id entity))
169 | ([idfn entity]
170 | (let [id (idfn entity)]
171 | (reduce (fn [acc [k v]]
172 | (if (= k ID_KEY)
173 | acc
174 | (cond (set? v) (concat acc (set->tuples id k v))
175 | (vector? v) (concat acc (vector->tuples idfn id k v))
176 | :default (conj acc [id k v]))))
177 | []
178 | (seq entity)))))
179 |
180 | (defn table->kb
181 | "Converts a Table into a KB, applying unique :kb/id to maps without a
182 | mapped identity value.
183 |
184 | See map->relation for more information about how the entities in the
185 | table are converted to relations.
186 |
187 | An idfn is a function of map -> id and if provided is used to
188 | override the default entity id generation and any existing :kb/id
189 | values."
190 | ([table] (table->kb use-or-gen-id table))
191 | ([idfn table]
192 | {:facts (set (mapcat #(map->relation idfn %) table))}))
193 |
194 |
--------------------------------------------------------------------------------
/src/fogus/rv/datalog.clj:
--------------------------------------------------------------------------------
1 | (ns fogus.rv.datalog
2 | "A minimal implementation of Datalog.")
3 |
4 | ;; Implementation is a modified version of Christophe Grand's 39loc Datalog implementation
5 | ;; adding more operators and allowing a Datomic-style query function. To
6 | ;; understand the core implementation I recommend reading Christophe's posts:
7 | ;;
8 | ;; - https://buttondown.com/tensegritics-curiosities/archive/writing-the-worst-datalog-ever-in-26loc
9 | ;; - https://buttondown.com/tensegritics-curiosities/archive/half-dumb-datalog-in-30-loc/
10 | ;; - https://buttondown.com/tensegritics-curiosities/archive/restrained-datalog-in-39loc/
11 | ;;
12 | ;; While this implementation may diverge over time, the articles above are
13 | ;; a master class in simplicity and emergent behavior.
14 |
15 | (defn- lookup-op [op]
16 | (case op
17 | not= not=
18 | = =
19 | < <
20 | > >
21 | <= <=
22 | >= >=))
23 |
24 | (defn- constrain [env [op & args]]
25 | (let [args (map #(let [v (env % %)] (if (set? v) % v)) args)]
26 | (if-some [free-var (->> args (filter symbol?) first)]
27 | (update env free-var (fnil conj #{}) (cons op args))
28 | (when (apply (lookup-op op) args) env))))
29 |
30 | (defn- bind [env p v]
31 | (let [p-or-v (env p p)]
32 | (cond
33 | (= p '_) env
34 | (= p-or-v v) env
35 | (symbol? p-or-v) (assoc env p v)
36 | (set? p-or-v) (reduce constrain (assoc env p v) p-or-v))))
37 |
38 | (defn- match [pattern fact env]
39 | (assert (= (count pattern) (count fact) 3) (str "[e a v] pattern expected, got: " pattern " - " fact))
40 | (reduce (fn [env [p v]] (or (bind env p v) (reduced nil)))
41 | env
42 | (map vector pattern fact)))
43 |
44 | (defn- match-patterns [patterns dfacts facts]
45 | (reduce
46 | (fn [[envs denvs] pattern]
47 | (if (seq? pattern)
48 | [(->> @envs (keep #(constrain % pattern)) set delay)
49 | (->> denvs (keep #(constrain % pattern)) set)]
50 | [(-> #{} (into (for [fact facts env @envs] (match pattern fact env))) (disj nil) delay)
51 | (-> #{}
52 | (into (for [fact facts env denvs] (match pattern fact env)))
53 | (into (for [fact dfacts env denvs] (match pattern fact env)))
54 | (into (for [fact dfacts env @envs] (match pattern fact env)))
55 | (disj nil))]))
56 | [(delay #{{}}) #{}] patterns))
57 |
58 | (defn- match-rule [dfacts facts [head & patterns]]
59 | (for [env (second (match-patterns patterns dfacts facts))]
60 | (into [] (map #(env % %)) head)))
61 |
62 | (defn- saturate [facts rules]
63 | (loop [dfacts facts, facts #{}]
64 | (let [facts' (into facts dfacts)
65 | dfacts' (into #{} (comp (mapcat #(match-rule dfacts facts %)) (remove facts')) rules)]
66 | (cond->> facts' (seq dfacts') (recur dfacts')))))
67 |
68 | (defn- q*
69 | "Underlying query impl.
70 |
71 | - facts: a set of tuples
72 | - query: a seq of ([..binds..] tuple*)
73 | - rules: a seq of ((head-tuple tuple*)*)
74 |
75 | [..binds..] can contain lvars and grounds to form output tuples
76 | "
77 | [facts query rules]
78 | (-> facts (saturate rules) (match-rule #{} query) set))
79 |
80 | (def linked-list-rules '[([?h :cell/linked ?t] [?h :cell/head _] [?t :cell/head _] (= ?h ?t))
81 | ([?h :cell/linked ?t] [?h :cell/linked ?x] [?x :cell/tail ?t])])
82 |
83 | (defn query->map
84 | "Accepts the vector form of a Datalog query and outputs a map
85 | of the component sections as keyword->seq mappings."
86 | [query]
87 | (letfn [(q->pairs [qq]
88 | (let [q (partition-by keyword? qq)]
89 | (map #(conj (vec %1) %2)
90 | (take-nth 2 q)
91 | (take-nth 2 (rest q)))))]
92 | (into {} (q->pairs query))))
93 |
94 | (defn q
95 | "Queries a knowledge base or a set of relations given a vector
96 | form of a query and an optional set of rules.
97 |
98 | A query takes the form:
99 |
100 | [:find find-spec :where clauses]
101 |
102 | A find-spec can be any number of lvars like:
103 |
104 | [:find ?e ?v :where ...]
105 |
106 | or a tuple containing a mix of lvars and grounds which is used to
107 | build output tuples from the query results:
108 |
109 | [:find [?e :an/attribute ?v] :where ...]
110 |
111 | The :where clauses are any number of tuples containing a mix of
112 | lvars and grounds:
113 |
114 | [:find ...
115 | :where
116 | [?e :an/attribute ?v]
117 | [?e :another/attr 42]]
118 |
119 | :where clauses may also contain filters defined as calls to predicates
120 | used to constrain the values that may bind to lvars:
121 |
122 | [:find ...
123 | :where
124 | [?e :an/attribute ?v]
125 | (= ?v 42)]
126 |
127 | The possible filter predicates are: =, not=, <, >, <=, >=
128 |
129 | rules are a vector of lists where each list defines a rule with a
130 | single head tuple followed by any number of rule clauses:
131 |
132 | ([?p :relationship/parent ?c] [?p :relationship/father ?c])
133 |
134 | The rule above defines a syntheic relation called
135 | `:relationship/parent` defined in terms of another relation
136 | `relationship/father`. Rules describe synthetic relations derived
137 | from real relations in the data or other synthetic relations
138 | derived from previous rule applications."
139 | ([query kb] (q query kb '()))
140 | ([query kb rules]
141 | (let [{:keys [find where]} (query->map query)
142 | find (if (vector? (first find)) (first find) find)
143 | facts (cond (map? kb) (:facts kb)
144 | (set? kb) kb
145 | :else (throw (ex-info "Cannot derive facts from KB"
146 | {:kb/type (type kb)})))]
147 | (q* facts
148 | (list* find where)
149 | rules))))
150 |
151 | (comment
152 | (def fkb
153 | {:facts
154 | #{[-1002 :response/to -51]
155 | [-51 :emergency/type :emergency.type/flood]
156 | [-50 :emergency/type :emergency.type/fire]
157 | [-1002 :response/type :response.type/kill-electricity]
158 | [-1000 :response/to -50]
159 | [-1000 :response/type :response.type/activate-sprinklers]}})
160 |
161 | (q* fkb
162 | '([?response] [_ :response/type ?response])
163 | '())
164 |
165 | (q* fkb
166 | '([?problem ?response]
167 | [?id :response/type ?response]
168 | [?id :response/to ?pid]
169 | [?pid :emergency/type ?problem])
170 | '())
171 |
172 | (q '[:find ?problem ?response
173 | :where
174 | [?id :response/type ?response]
175 | [?id :response/to ?pid]
176 | [?pid :emergency/type ?problem]]
177 | fkb)
178 |
179 |
180 |
181 | (query->map '[:find ?problem ?response
182 | :where
183 | [?id :response/type ?response]
184 | [?id :response/to ?pid]
185 | [?pid :emergency/type ?problem]])
186 |
187 | (query->map '[:find [?response :response/to ?problem]
188 | :where
189 | [?id :response/type ?response]
190 | [?id :response/to ?pid]
191 | [?pid :emergency/type ?problem]])
192 |
193 | (def vkb #{[:primes :num/primes 100]
194 | [100 :sequence/items 101]
195 | [100 :sequence/indexed? true]
196 | [101 :cell/head 1] [101 :cell/i 0] [101 :cell/tail 103]
197 | [103 :cell/head 2] [103 :cell/i 1] [103 :cell/tail 105]
198 | [105 :cell/head 3] [105 :cell/i 2] [105 :cell/tail 107]
199 | [107 :cell/head 5] [107 :cell/i 3] [107 :cell/tail 109]
200 | [109 :cell/head 7] [109 :cell/i 4]})
201 |
202 | (q '[:find ?h2
203 | :where
204 | [:primes :num/primes ?primes]
205 | [?primes :sequence/items ?e]
206 | [?e :cell/head _]
207 | [?e :cell/linked ?t]
208 | [?t :cell/head ?h2]]
209 | vkb
210 | linked-list-rules
211 | )
212 |
213 | (q '[:find ?h2
214 | :where
215 | [?e :cell/head 1]
216 | [?e :cell/linked ?t]
217 | [?t :cell/head ?h2]]
218 | vkb
219 | rvrules
220 | )
221 |
222 | (q '[:find ?s ?h
223 | :where
224 | [?s :sequence/items ?h]
225 | [?h :cell/i 0]]
226 | vkb
227 | ;;vrules
228 | )
229 |
230 | (q '[:find ?s ?h
231 | :where
232 | [?s :sequence/items ?h]
233 | [?h :cell/i 0]]
234 | vkb
235 | ;;vrules
236 | )
237 |
238 | )
239 |
--------------------------------------------------------------------------------
/src/fogus/rv/fuzzy/soundex.clj:
--------------------------------------------------------------------------------
1 | (ns fogus.rv.fuzzy.soundex
2 | "I came across the Soundex algorithm when researching the retro KAMAS outlining application.
3 | Soundex is a phonetic algorithm for indexing words by sound."
4 | (:require [clojure.string :as string]))
5 |
6 | (def ^:private ^:const IGNORE Long/MAX_VALUE)
7 | (def ^:private ^:const SKIP Long/MIN_VALUE)
8 |
9 | ;; TODO: is it better to convert to upcase or account for lowercase below?
10 |
11 | (def ^:private en-ch->code
12 | {\B 1, \F 1, \P 1, \V 1
13 | \C 2, \G 2, \J 2, \K 2, \Q 2, \S 2, \X 2, \Z 2
14 | \D 3, \T 3
15 | \L 4
16 | \M 5, \N 5
17 | \R 6
18 | \A IGNORE \E IGNORE \I IGNORE \O IGNORE \U IGNORE \Y IGNORE
19 | \H SKIP \W SKIP})
20 |
21 | (defn- en-alpha-only [x]
22 | (clojure.string/replace x #"[^A-Za-z]" ""))
23 |
24 | (defn- handle-skips
25 | [processed-word]
26 | (filter #(not= SKIP %) processed-word))
27 |
28 | (defn- drop-similar-head
29 | [encoding first-letter]
30 | (drop-while #(= % (en-ch->code first-letter)) encoding))
31 |
32 | (defn- drop-ignore-codes [encoding]
33 | (filter #(not= IGNORE %) encoding))
34 |
35 | (defn- assemble-soundex [first-letter preprocessed-code]
36 | (->> (-> preprocessed-code
37 | handle-skips
38 | (drop-similar-head first-letter)
39 | dedupe
40 | drop-ignore-codes
41 | (concat (repeat 0)))
42 | (take 3)
43 | (apply str)
44 | (str first-letter)))
45 |
46 | (defn encode
47 | "Soundex is an algorithm for creating indices for words based on their
48 | English pronunciation. Homophones are encoded such that words can be matched
49 | despite minor differences in spelling. Example, the words \"Ashcraft\" and
50 | \"Ashcroft\" are both encoded as the same soundex code \"A261\".
51 |
52 | This function accepts the following keyword arguments:
53 |
54 | :numeric? -> true numerically encodes the entire word rather than using
55 | the default soundex letter prefix."
56 | [word & {:keys [numeric?] :as opts}]
57 | (let [word (string/upper-case (en-alpha-only word))
58 | first-letter (first word)
59 | result (assemble-soundex first-letter (map en-ch->code (rest word)))]
60 | (if numeric?
61 | (apply str (en-ch->code first-letter) (rest result))
62 | result)))
63 |
64 |
--------------------------------------------------------------------------------
/src/fogus/rv/impl/unification.clj:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Fogus. All rights reserved.
2 | ; The use and distribution terms for this software are covered by the
3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ; which can be found in the file epl-v10.html at the root of this distribution.
5 | ; By using this software in any fashion, you are agreeing to be bound by
6 | ; the terms of this license.
7 | ; You must not remove this notice, or any other, from this software.
8 |
9 | (ns fogus.rv.impl.unification
10 | "Provides internal unification functions.
11 | DO NOT USE THIS NS.
12 | There is no guarantee that it will remain stable or at all."
13 | (:require [fogus.rv.core :as core]
14 | clojure.core.unify))
15 |
16 | (def subst (clojure.core.unify/make-occurs-subst-fn core/lv?))
17 |
--------------------------------------------------------------------------------
/src/fogus/rv/learn.clj:
--------------------------------------------------------------------------------
1 | (ns fogus.rv.learn
2 | "Common learning-related functions and protocols.")
3 |
4 | (defprotocol S&G
5 | (-generalize [lhs rhs])
6 | (-specialize [lhs neg rhs])
7 | (-init [basis] [basis arity]))
8 |
--------------------------------------------------------------------------------
/src/fogus/rv/learn/vs.clj:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Fogus. All rights reserved.
2 | ; The use and distribution terms for this software are covered by the
3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ; which can be found in the file epl-v10.html at the root of this distribution.
5 | ; By using this software in any fashion, you are agreeing to be bound by
6 | ; the terms of this license.
7 | ; You must not remove this notice, or any other, from this software.
8 |
9 | (ns fogus.rv.learn.vs
10 | "Version spaces are a binary classification, empirical learning algorithm.
11 | The approach, as described in 'Version spaces: a candidate elimination approach
12 | to rule learning' by Tom Mitchel (1977) takes training examples (currently
13 | Tuples of a like-arity) and manages a 'version space'. A version space is a
14 | map containing two 'boundaries' `:S` and `:G`. The `:G` boundary contains 'hypotheses'
15 | corresponding to the most general versions of the training data that are consistent
16 | and `:S` is the most specific versions. When a version space is presented with a new
17 | example it runs a 'candidate elimination' algorithm to modify the boundaries `:S`
18 | and `:G` accordingly. Examples can be marked as 'positive' examples, meaning
19 | that they are preferred instances. Anything not marked as 'positive' are taken as
20 | negative examples. Once trained, a version space can classify new examples as
21 | `::positive` or `::negative`. If new examples are not covered by the existing hypotheses
22 | in either boundary then they are classified as `::ambiguous` instead."
23 | (:require [fogus.rv.core :as core]
24 | [fogus.rv.learn :as proto]
25 | [fogus.rv.util :as util]))
26 |
27 | (def ^:const ?S (core/->IgnoreT))
28 | (def ^:const ?G (core/->AnyT))
29 | (def ^:const ?? (core/->AskT))
30 |
31 | (defn- specializable? [g neg-example s]
32 | (and (= g ?G) (not= s neg-example)))
33 |
34 | (defn- maybe-generalize [g s]
35 | (cond (= g ?S) s
36 | (= s ?S) g
37 | (= g s) g
38 | :default ?G))
39 |
40 | (defn- specialization-indexes [g neg s]
41 | (util/positions-of specializable? g neg s))
42 |
43 | (defn- generalize-sequential [g s]
44 | (map maybe-generalize g s))
45 |
46 | (defn- specialize-with [f g neg s]
47 | (map f (specialization-indexes g neg s)))
48 |
49 | (extend-protocol proto/S&G
50 | clojure.lang.PersistentVector
51 | (proto/-generalize [g s]
52 | (vec (generalize-sequential g s)))
53 | (proto/-specialize [g neg s]
54 | (vec (specialize-with #(assoc g % (get s %)) g neg s)))
55 | (proto/-init [tmpl]
56 | (let [d (count tmpl)]
57 | {:S [(vec (repeat d ?S))]
58 | :G [(vec (repeat d ?G))]
59 | :arity d})))
60 |
61 | (defn- covers-elem? [h e]
62 | (or (= h e) (= h ?G)))
63 |
64 | (declare covers? collapsed? converged?)
65 |
66 | (defn- positive [{:keys [S G arity]} example]
67 | (let [g' (filter #(covers? %1 example) G)]
68 | {:G g'
69 |
70 | :S (let [s' (map (fn [s]
71 | (if (not (covers? s example))
72 | (proto/-generalize s example)
73 | s))
74 | S)]
75 | (if (converged? g' s')
76 | s'
77 | (filter
78 | (fn [s] (not-any? #(covers? s %1) G))
79 | s')))
80 |
81 | :arity arity}))
82 |
83 | (defn- negative [{:keys [S G arity]} example]
84 | {:G (reduce (fn [acc g]
85 | (if (not (covers? g example))
86 | (conj acc g)
87 | (into acc
88 | (reduce (fn [acc g']
89 | (if (and (not (covers? g' example))
90 | (every? #(covers? g' %) S)
91 | (not-any? #(and (not= g %) (covers? % g')) G))
92 | (conj acc g')
93 | acc))
94 | []
95 | (proto/-specialize g example (first S))))))
96 | []
97 | G)
98 |
99 | :S (filter #(not (covers? %1 example)) S)
100 |
101 | :arity arity})
102 |
103 | (defn arity-vec
104 | "Returns a vector template for arity n."
105 | [n]
106 | (vec (repeat n ??)))
107 |
108 | (defn covers?
109 | "Takes a `hypothesis` from a version space and returns if the `example` is
110 | consistent with it."
111 | [hypothesis example]
112 | (util/pairwise-every? covers-elem? hypothesis example))
113 |
114 | (defn collapsed?
115 | "Returns if a version space `vs` or boundaries `g` and `s` have collapsed.
116 | That is, training data have caused the hypotheses to become inconsistent,
117 | making further classification impossible."
118 | ([vs] (collapsed? (:G vs) (:S vs)))
119 | ([g s] (and (empty? g) (empty? s))))
120 |
121 | (defn converged?
122 | "Returns if a version space `vs` or boundaries `g` and `s` have
123 | converged. That is, training has caused the boundaries to converge to a single
124 | case."
125 | ([vs] (converged? (:G vs) (:S vs)))
126 | ([g s]
127 | (and (= 1 (bounded-count 2 g) (bounded-count 2 s)) (= g s))))
128 |
129 | (defn- positive [{:keys [S G arity]} example]
130 | (let [g' (filter #(covers? %1 example) G)]
131 | {:G g'
132 |
133 | :S (let [s' (map (fn [s]
134 | (if (not (covers? s example))
135 | (proto/-generalize s example)
136 | s))
137 | S)]
138 | (if (converged? g' s')
139 | s'
140 | (filter
141 | (fn [s] (not-any? #(covers? s %1) G))
142 | s')))
143 |
144 | :arity arity}))
145 |
146 | (defn- negative [{:keys [S G arity]} example]
147 | {:G (reduce (fn [acc g]
148 | (if (not (covers? g example))
149 | (conj acc g)
150 | (into acc
151 | (reduce (fn [acc g']
152 | (if (and (not (covers? g' example))
153 | (every? #(covers? g' %) S)
154 | (not-any? #(and (not= g %) (covers? % g')) G))
155 | (conj acc g')
156 | acc))
157 | []
158 | (proto/-specialize g example (first S))))))
159 | []
160 | G)
161 |
162 | :S (filter #(not (covers? %1 example)) S)
163 |
164 | :arity arity})
165 |
166 | (defn- classification-for [example]
167 | (-> example meta ::positive))
168 |
169 | (defn refine
170 | "Given a version space `vs` and an `example`, returns a new version space
171 | with boundaries adjusted according to the given example's features and
172 | classification. An example is marked as positive by attaching a metadata mapping
173 | `:positive?` -> boolean or by passing a boolean as the last argument. The
174 | explicit classification argument will always dominate the metadata
175 | classification."
176 | ([vs example]
177 | (refine vs example (classification-for example)))
178 | ([vs example positive?]
179 | (if positive?
180 | (positive vs example)
181 | (negative vs example))))
182 |
183 | (defn consistent?
184 | "Returns `true` if all hypotheses in the version space `vs`'s general and specific
185 | boundaries are consistent with the `example` features and classification."
186 | ([vs example]
187 | (consistent? vs example (classification-for example)))
188 | ([vs example positive?]
189 | (and
190 | (every? #(and positive? (covers? % example)) (:S vs))
191 | (every? #(and positive? (covers? % example)) (:G vs)))))
192 |
193 | (defn applicable?
194 | "Returns true if at least one hypothesis in the version space `vs` is consistent
195 | with the `example` and false otherwise."
196 | ([vs example]
197 | (applicable? vs example (classification-for example)))
198 | ([vs example positive?]
199 | (boolean
200 | (or
201 | (some #(and positive? (covers? % example)) (:S vs))
202 | (some #(and positive? (covers? % example)) (:G vs))))))
203 |
204 | (defn classify
205 | "Attempts to classify an `example` using the given version space `vs`.
206 | Returns `::positive`, `::negative`, or `::ambiguous` if the boundaries
207 | G and S are incongruent."
208 | [vs example]
209 | (let [at-least-one-s? (boolean (some #(covers? % example) (:S vs)))
210 | all-g? (every? #(covers? % example) (:G vs))]
211 | (cond
212 | (and all-g? at-least-one-s?) ::positive
213 | (and (not at-least-one-s?) (not (some #(covers? % example) (:G vs)))) ::negative
214 | :otherwise ::ambiguous)))
215 |
216 | (defn- similarity
217 | "Computes a similarity score as the ratio of positions in which
218 | the hypothesis covers the example."
219 | [hypothesis example]
220 | (let [arity (count hypothesis)]
221 | (if (zero? arity)
222 | arity
223 | (/ (->> (map covers-elem? hypothesis example)
224 | (filter identity)
225 | count)
226 | arity))))
227 |
228 | (defn- explain-hypothesis
229 | [hypothesis example]
230 | (let [mismatches (keep-indexed
231 | (fn [i [h e]]
232 | (when-not (covers-elem? h e)
233 | {:position i
234 | :constraint h}))
235 | (map vector hypothesis example))]
236 | {:hypothesis hypothesis
237 | :covers? (covers? hypothesis example)
238 | :mismatched-features (vec mismatches)
239 | :similarity (similarity hypothesis example)}))
240 |
241 | (defn explain
242 | "Returns a structure explaining how the classifier reaches a conclusion,
243 | given a version space `vs` and a compatible `example`.
244 |
245 | The map returned contains the mappings:
246 |
247 | - `:explain/classification` -> the result of the call to `classify`
248 | - `:explain/example` -> the example given
249 | - `explain/G` -> a sequence of hypotheses coverage analysis structures in the G boundary
250 | - `explain/S` -> a sequence of hypotheses coverage analysis structures in the S boundary
251 |
252 | The hypotheses coverage analyses contain the mappings:
253 |
254 | - `:hypothesis` -> The hypothesis inspected
255 | - `:covers?` -> true or false if the hypothesis covers the example
256 | - `:similarity` -> A ratio of hypothesis coverages over its arity
257 | - `:mismatched-features` -> a sequence of the features of the hypothesis that do not match the example
258 |
259 | A mismatched feature of a hypothesis has the mappings:
260 |
261 | - `:position` -> the position of the feature in the hypothesis
262 | - `:constraint` -> the value or wildcard at that position
263 |
264 | The information provided is sufficient for informing human-in-the-loop learning
265 | interactions."
266 | [vs example]
267 | {:explain/classification (classify vs example)
268 | :explain/example example
269 | :explain/S (vec (map #(explain-hypothesis % example) (:S vs)))
270 | :explain/G (vec (map #(explain-hypothesis % example) (:G vs)))})
271 |
272 | (defn best-fit
273 | "Returns the best-fit hypothesis coverage analysis (see `explain`) for a given
274 | version space `vs` and compatible `example`. The metadata of the best fit return will
275 | have a mapping of `::fit-from` -> `:S` or `:G` pertaining to which boundary set the
276 | fit came from."
277 | [vs example]
278 | (let [explanation (explain vs example)
279 | best #(vec (sort-by (comp - :similarity) %))
280 | [best-s & _] (best (:explain/S explanation))
281 | [best-g & _] (best (:explain/G explanation))]
282 | (if (> (:similarity best-s) (:similarity best-g))
283 | (with-meta best-s {::fit-from :S})
284 | (with-meta best-g {::fit-from :G}))))
285 |
--------------------------------------------------------------------------------
/src/fogus/rv/productions.clj:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Fogus. All rights reserved.
2 | ; The use and distribution terms for this software are covered by the
3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ; which can be found in the file epl-v10.html at the root of this distribution.
5 | ; By using this software in any fashion, you are agreeing to be bound by
6 | ; the terms of this license.
7 | ; You must not remove this notice, or any other, from this software.
8 |
9 | (ns fogus.rv.productions
10 | "The simplest possible production rules system that uses a set
11 | of EAV tuples as its knowledge base."
12 | (:require [clojure.core.unify :as u]
13 | [clojure.set :as s])
14 | (:refer-clojure :exclude [cycle]))
15 |
16 | ;; Productions are represented as maps having two privileged keys:
17 | ;; {:antecedent ...
18 | ;; :consequent ...}
19 |
20 | ;; The :antecedent key in the production map contains a sequence of EAV 3-tuples
21 | ;; with logical variables at key locations for the purpose of pattern matching.
22 | ;; These patterns refer to facts in the knowledge base.
23 | ;;
24 | ;; [[?id :person/name "Fogus"]
25 | ;; [?id :language/speaks ?lang]]
26 | ;;
27 | ;; The antecedent describes the patterns that must be present in the EAV
28 | ;; set in order for the production to activate. The antecedent is also known as
29 | ;; the left-hand-side (LHS) of the production.
30 |
31 | ;; When a production activates, the structure in the :consequent key in the production
32 | ;; map is applied to the knowledge base to potentially create new facts.
33 | ;; The consequent also contains a sequence of EAV 3-tuples with logical
34 | ;; variables at key locations. However, the tuples describe new facts
35 | ;; with values bound to embedded logic variables as defined within the
36 | ;; context of a production activation.
37 |
38 | ;; A production set is just a data structure defines as such:
39 | ;;
40 | ;; 1. A production set is simply a vector of production definitions
41 | ;; 2. A production definition is a map containing :antecedent and :consequent keys
42 | ;; 3. An antecedent is a vector of EAV 3-tuples representing patterns in data
43 | ;; 4. An EAV 3-tuple is a vector of three elements: id, attribute, value
44 | ;; 5. A consequent is a vector of EAV 3-tuples representing new attribute assertions
45 |
46 | ;; A fact base is a set of EAV 3-tuples.
47 |
48 | ;; A knowledge base is a map with two keys in it
49 | ;; {:productions
50 | ;; :facts }
51 |
52 | ;; The production productions sytem implemented herein is a a four stage system:
53 | ;;
54 | ;; 1. Antecedent unifications
55 | ;; 2. Production selection
56 | ;; 3. Consequent substitutions and assertion
57 | ;; 4. System quiessence
58 |
59 | ;; Stage 1: Unifications
60 |
61 | (defn unifications
62 | "Walks through all of the clauses in an implied antecedent and matches
63 | each against every fact provided. Returns a seq of contexts representing
64 | all of the bindings established by the antecedent unifications across all
65 | facts provided."
66 | [[clause & more :as clauses] facts context]
67 | (if clause
68 | (let [bindings (keep #(u/unify clause % context) facts)]
69 | (mapcat #(unifications more facts %) bindings))
70 | [context]))
71 |
72 | ;; Stage 2: Production selection
73 |
74 | (defn select-production
75 | "Builds a sequence of bindings paired with each production and then uses a selection
76 | function to execute one of the productions that matched."
77 | [selection-strategy {:keys [productions facts]}]
78 | (let [possibilities
79 | (for [production productions
80 | bindings (unifications (:antecedent production) facts {})]
81 | [production bindings])]
82 | (selection-strategy possibilities)))
83 |
84 |
85 | ;; Stage 3: Consequent substitutions and assertion
86 |
87 | (defn apply-production [production facts context]
88 | (let [new-facts (set (for [rhs (:consequent production)]
89 | (u/subst rhs context)))]
90 | (s/union new-facts facts)))
91 |
92 | ;; Stage 3a: Single substitution and assertion
93 |
94 | (defn step
95 | "Takes a set of productions and facts and returns a new fact base based on the application of single production."
96 | ([kb] (step rand-nth kb))
97 | ([choice-fn kb]
98 | (when-let [[production binds] (select-production choice-fn kb)]
99 | (apply-production production (:facts kb) binds))))
100 |
101 | ;; Stage 3b: Repeated substitution and assertion
102 |
103 | (defn states
104 | "Will apply the result of one production firing to the fact base and feed
105 | the result forward into the next firing."
106 | [kb]
107 | (iterate #(step (assoc kb :facts %))
108 | (set (:facts kb))))
109 |
110 | (defn cycle
111 | "Feeds the results of states into a function qf that is responsible for
112 | detecting when production firings have stopped and returns an augmented
113 | fact set."
114 | [qf kb]
115 | (qf (states kb)))
116 |
117 | ;; Stage 4: System quiessence
118 |
119 | (defn naive-qf
120 | "Takes the last environment in a long sequence of states in the hope that
121 | the sequence was long enough that all of the productions fired in creating it."
122 | [states]
123 | (last (take 256 states)))
124 |
125 |
--------------------------------------------------------------------------------
/src/fogus/rv/search.clj:
--------------------------------------------------------------------------------
1 | (ns fogus.rv.search
2 | "Common search-related functions and protocols.")
3 |
4 | (defprotocol GraphSearch
5 | "Functions related to graph-search algorithms."
6 | (cost-of [_ node] "Returns the cost to visit `node`.")
7 | (neighbors-of [_ node] "Returns a seq of neighbors of the given `node`.")
8 | (add-route [_ node new-route]
9 | "Adds a route to a `node` as a seq of nodes. Implementors of this function
10 | should return an instance of the object implementing this protocol.")
11 | (route-of [_ node] "Given a `node`, returns the route associated with it."))
12 |
13 | (defprotocol HeuristicSearch
14 | "Function(s) related to heuristic-guided search."
15 | (estimate-cost [_ node goal]
16 | "Returns an estimated cost of the route from `node` to `goal`."))
17 |
--------------------------------------------------------------------------------
/src/fogus/rv/search/graph.clj:
--------------------------------------------------------------------------------
1 | (ns fogus.rv.search.graph
2 | "A* search implementation."
3 | (:require [fogus.rv.search :as search]
4 | [fogus.rv.util :as util]))
5 |
6 | (defn- path-cost
7 | "The g(n) function calculating the cost of the path from the start
8 | to the current node."
9 | [node-cost cheapest-nbr]
10 | (+ node-cost (or cheapest-nbr 0)))
11 |
12 | (defn- total-cost
13 | "The f(n) function built from g(n) + h(n) or
14 | total_cost = current_path_cost + estimated_remaining_cost"
15 | [graph newcost node goal]
16 | (+ newcost (search/estimate-cost graph node goal)))
17 |
18 | (defn astar
19 | "Implements a lazy A* best-first graph traversal algorithm. Takes a
20 | `graph` object implementing both of the `fogus.rv.search.GraphSearch`
21 | and `fogus.rv.search.HeuristicSearch` protocols and a `start-node`
22 | and `goal-node` describing the bounds of the search. Returns of map
23 | with keys `:path` mapped to a sequence of nodes from `start-node` to
24 | `goal-node` and `:cost` describing the cost of the path. This search
25 | guarantees to return the lowest cost path as long as one exists.
26 | In the event that there is no path to the `goal-node` the current result
27 | is undefined."
28 | [graph start-node goal-node]
29 | (loop [steps 0
30 | graph graph
31 | work-queue (sorted-set [0 start-node])]
32 | (if (empty? work-queue)
33 | (with-meta (search/route-of graph goal-node) {:steps steps})
34 | (let [[_ node :as work-item] (first work-queue)
35 | rest-work-queue (disj work-queue work-item)
36 | neighbors (search/neighbors-of graph node)
37 | cheapest-nbr (util/f-by min-key :cost (keep #(search/route-of graph %) neighbors))
38 | newcost (path-cost (search/cost-of graph node) (:cost cheapest-nbr))
39 | oldcost (:cost (search/route-of graph node))]
40 | (if (= node goal-node)
41 | (recur (inc steps)
42 | (search/add-route graph node
43 | {:cost newcost
44 | :path (conj (:path cheapest-nbr []) node)})
45 | rest-work-queue)
46 | (if (and oldcost (>= newcost oldcost))
47 | (recur (inc steps) graph rest-work-queue)
48 | (recur (inc steps)
49 | (search/add-route graph node
50 | {:cost newcost
51 | :path (conj (:path cheapest-nbr []) node)})
52 | (into rest-work-queue
53 | (map #(vector (total-cost graph newcost % goal-node) %) neighbors)))))))))
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/fogus/rv/util.clj:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Fogus. All rights reserved.
2 | ; The use and distribution terms for this software are covered by the
3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ; which can be found in the file epl-v10.html at the root of this distribution.
5 | ; By using this software in any fashion, you are agreeing to be bound by
6 | ; the terms of this license.
7 | ; You must not remove this notice, or any other, from this software.
8 |
9 | (ns fogus.rv.util)
10 |
11 | (defn process-bindings [bindings]
12 | {:names (take-nth 2 bindings)
13 | :values (vec (take-nth 2 (rest bindings)))})
14 |
15 | (defn cart [colls]
16 | (if (empty? colls)
17 | '(())
18 | (for [more (cart (rest colls))
19 | x (first colls)]
20 | (cons x more))))
21 |
22 | (defn f-by [f key coll]
23 | (when (seq coll)
24 | (reduce #(f key %1 %2)
25 | coll)))
26 |
27 | (defn pairwise-every? [pred xs ys]
28 | (every? identity (map pred xs ys)))
29 |
30 | (defn positions-of [pred & xs]
31 | (keep-indexed #(when %2 %1) (apply map pred xs)))
32 |
--------------------------------------------------------------------------------
/test/rv/amb_test.clj:
--------------------------------------------------------------------------------
1 | (ns rv.amb-test
2 | (:require [clojure.test :refer :all]
3 | [fogus.rv.amb :as rv]))
4 |
5 | (deftest test-amb-null-body
6 | (is (nil? (rv/amb))))
7 |
8 | (deftest test-amb-simple-binding
9 | (is (= 3 (rv/amb [x (range 10)]
10 | (rv/accept (= x 3)
11 | x))))
12 | (is (nil? (rv/amb [x (range 10)]
13 | (rv/accept false
14 | x))))
15 | (is (nil? (rv/amb [x (range 10)]
16 | (rv/accept (> x 10)
17 | x)))))
18 |
19 | (deftest test-amb-complex-binding
20 | (testing "that a complex binding passes as expected."
21 | (is (= ["that" "thing" "grows" "slowly"]
22 | (rv/amb [A ["the" "that" "a"]
23 | B ["frog" "elephant" "thing"]
24 | C ["walked" "treaded" "grows"]
25 | D ["slowly" "quickly"]]
26 |
27 | (rv/accept (and (= (last A) (first B))
28 | (= (last B) (first C))
29 | (= (last C) (first D)))
30 | [A B C D])))))
31 |
32 | (testing "that a complex binding fails when an imposible condition is present."
33 | (is (nil? (rv/amb [A ["the" "that" "a"]
34 | B ["frog" "elephant" "thing"]
35 | C ["walked" "treaded" "grows"]
36 | D ["slowly" "quickly"]]
37 |
38 | (rv/accept false [A B C D]))))))
39 |
--------------------------------------------------------------------------------
/test/rv/constraint_test.clj:
--------------------------------------------------------------------------------
1 | (ns rv.constraint-test
2 | (:require [clojure.test :refer :all]
3 | [fogus.rv.core :as core]
4 | [fogus.rv.constraints :as c]))
5 |
6 | (deftest test-satisfy1-no-answer
7 | (let [?x (core/->LVar 'x [0 1])
8 | ?y (core/->LVar 'y [0 1])
9 | ?z (core/->LVar 'z [0 1])
10 | c1 {:variables [?x ?y ?z]
11 | :formula `(= (+ ~?x ~?y 10000) ~?z)}]
12 | (is (= {}
13 | (c/satisfy1 c1)))))
14 |
15 | (deftest test-satisfy1-single-answer
16 | (let [?x (core/->LVar 'x [0 1])
17 | ?y (core/->LVar 'y [0 1])
18 | ?z (core/->LVar 'z [0 1])
19 | c1 {:variables [?x ?y ?z]
20 | :formula `(= (+ ~?x ~?y) ~?z)}]
21 | (is (= {?x 0 ?y 0 ?z 0}
22 | (c/satisfy1 c1)))))
23 |
24 | (deftest test-satisfy1-2
25 | (let [?x (core/->LVar 'x [0 1])
26 | ?y (core/->LVar 'y [1 2])
27 | ?z (core/->LVar 'z [2 3])
28 | c1 {:variables [?x ?y ?z]
29 | :formula `(= (+ ~?x ~?y) ~?z)}]
30 | (is (= {?x 1 ?y 1 ?z 2}
31 | (c/satisfy1 c1)))))
32 |
33 | (deftest test-satisfy1-3
34 | (let [?x (core/->LVar 'x [1 1])
35 | ?y (core/->LVar 'y [2 2])
36 | ?z (core/->LVar 'z [3 3])
37 | c1 {:variables [?x ?y ?z]
38 | :formula `(= (+ ~?x ~?y) ~?z)}]
39 | (is (= {?x 1 ?y 2 ?z 3}
40 | (c/satisfy1 c1)))))
41 |
42 | (deftest test-satisfy1-range
43 | (let [?x (core/->LVar 'x (range 1 7))
44 | ?y (core/->LVar 'y (range 3 8))
45 | c1 {:variables [?x ?y]
46 | :formula `(= (+ ~?x ~?y) 10)}]
47 | (is (= {?x 6 ?y 4}
48 | (c/satisfy1 c1)))))
49 |
50 | (deftest test-satisfy*-range
51 | (let [?x (core/->LVar 'x (range 1 7))
52 | ?y (core/->LVar 'y (range 3 8))
53 | c1 {:variables [?x ?y]
54 | :formula `(= (+ ~?x ~?y) 10)}]
55 | (is (= #{{?x 5, ?y 5} {?x 3, ?y 7} {?x 6, ?y 4} {?x 4, ?y 6}}
56 | (set (c/satisfy* c1))))))
57 |
58 | (deftest test-satisfy*-range-no-answer
59 | (let [?x (core/->LVar 'x (range 1 7))
60 | ?y (core/->LVar 'y (range 3 8))
61 | c1 {:variables [?x ?y]
62 | :formula `(= (+ ~?x ~?y) 1000000)}]
63 | (is (empty? (c/satisfy* c1)))))
64 |
--------------------------------------------------------------------------------
/test/rv/datalog_test.clj:
--------------------------------------------------------------------------------
1 | (ns rv.datalog-test
2 | (:require [clojure.test :refer :all]
3 | [fogus.rv.core :as core]
4 | [fogus.rv.datalog :as d]
5 | [fogus.rv.fuzzy.soundex :as s]))
6 |
7 | (deftest test-datalog-q*-no-rules
8 | (let [fkb #{[-1002 :response/to -51]
9 | [-51 :emergency/type :emergency.type/flood]
10 | [-50 :emergency/type :emergency.type/fire]
11 | [-1002 :response/type :response.type/kill-electricity]
12 | [-1000 :response/to -50]
13 | [-1000 :response/type :response.type/activate-sprinklers]}]
14 | (is (= #{[:response.type/kill-electricity] [:response.type/activate-sprinklers]}
15 | (#'d/q* fkb
16 | '([?response] [_ :response/type ?response])
17 | '())))
18 |
19 | (is (= #{[:emergency.type/fire :response.type/activate-sprinklers] [:emergency.type/flood :response.type/kill-electricity]}
20 | (#'d/q* fkb
21 | '([?problem ?response]
22 | [?id :response/type ?response]
23 | [?id :response/to ?pid]
24 | [?pid :emergency/type ?problem])
25 | '())))
26 |
27 | (is (= #{[:response.type/activate-sprinklers :response/to :emergency.type/fire]
28 | [:response.type/kill-electricity :response/to :emergency.type/flood]}
29 | (#'d/q* fkb
30 | '([?response :response/to ?problem]
31 | [?id :response/type ?response]
32 | [?id :response/to ?pid]
33 | [?pid :emergency/type ?problem])
34 | '())))
35 |
36 | (is (= #{[:response.type/activate-sprinklers :response/to :emergency.type/fire]
37 | [:response.type/kill-electricity :response/to :emergency.type/flood]}
38 | (d/q '[:find [?response :response/to ?problem]
39 | :where
40 | [?id :response/type ?response]
41 | [?id :response/to ?pid]
42 | [?pid :emergency/type ?problem]]
43 | fkb)))))
44 |
45 | (deftest test-datalog-ops
46 | (let [nkb #{[0 :a/num 0]
47 | [1 :a/num 1]
48 | [2 :a/num 2]
49 | [3 :a/num 3]
50 | [4 :a/num 4]
51 | [5 :a/num 5]}]
52 | (is (= #{[0]}
53 | (#'d/q* nkb '([?num] [0 :a/num ?num]) '())))
54 | (is (= #{[0] [1] [2]}
55 | (#'d/q* nkb '([?num] [_ :a/num ?num] (< ?num 3)) '())))
56 | (is (= #{[1] [2]}
57 | (#'d/q* nkb '([?num] [_ :a/num ?num] (< ?num 3) (> ?num 0)) '())))
58 | (is (= #{[0]}
59 | (#'d/q* nkb '([?num] [_ :a/num ?num] (= ?num 0)) '())))
60 | (is (= #{[0] [1] [2] [3] [4]}
61 | (#'d/q* nkb '([?num] [_ :a/num ?num] (not= ?num 5)) '())))
62 | (is (= #{[0] [1] [2] [3] [4]}
63 | (#'d/q* nkb '([?num] [_ :a/num ?num] (<= ?num 4)) '())))
64 | (is (= #{[5] [1] [2] [3] [4]}
65 | (#'d/q* nkb '([?num] [_ :a/num ?num] (>= ?num 1)) '())))))
66 |
67 | (deftest test-datalog-q*-with-rules
68 | (let [ekb {:facts #{[:homer :person/name "Homer"]
69 | [:bart :person/name "Bart"]
70 | [:lisa :person/name "Lisa"]
71 | [:marge :person/name "Marge"]
72 | [:maggie :person/name "Maggie"]
73 | [:abe :person/name "Abe"]
74 | [:mona :person/name "Mona"]
75 | [:homer :relationship/father :bart]
76 | [:marge :relationship/mother :bart]
77 | [:homer :relationship/father :lisa]
78 | [:marge :relationship/mother :lisa]
79 | [:homer :relationship/father :maggie]
80 | [:marge :relationship/mother :maggie]
81 | [:abe :relationship/father :homer]
82 | [:mona :relationship/mother :marge]}}
83 | anc-rules '[([?p :relationship/parent ?c] [?p :relationship/father ?c])
84 | ([?p :relationship/parent ?c] [?p :relationship/mother ?c])
85 | ([?gp :relationship/grand-parent ?c] [?gp :relationship/parent ?p] [?p :relationship/parent ?c])
86 | ([?p :relationship/ancestor ?c] [?p :relationship/parent ?c])
87 | ([?ancp :relationship/ancestor ?c] [?anc :relationship/ancestor ?c] [?ancp :relationship/parent ?anc])]
88 | sib-rules '[([?p :relationship/parent ?c] [?p :relationship/father ?c])
89 | ([?p :relationship/parent ?c] [?p :relationship/mother ?c])
90 | ([?c' :relationship/sibling ?c] [?p :relationship/parent ?c] (not= ?c ?c') [?p :relationship/parent ?c'])]]
91 | (is (= #{["Lisa"] ["Maggie"]}
92 | (d/q '[:find ?n
93 | :where
94 | [?s :relationship/sibling :bart]
95 | [?s :person/name ?n]]
96 | ekb
97 | sib-rules)))
98 | (is (= #{["Abe"] ["Mona"]}
99 | (d/q '[:find ?n
100 | :where
101 | [?s :relationship/grand-parent :lisa]
102 | [?s :person/name ?n]]
103 | ekb
104 | anc-rules)))))
105 |
106 | (deftest test-from-table
107 | (let [e {:person/name "Ethel"
108 | :person/age 31
109 | :address/state "NJ"
110 | :kb/id ::ethel}
111 | table #{{:person/name "Fred"
112 | :person/age 33
113 | :address/state "NY"}
114 | e
115 | {:person/name "Jimbo"
116 | :person/age 55
117 | :address/state "VA"
118 | :kb/id -1000}}]
119 | (is (= ::ethel (-> e core/map->relation ffirst)))
120 | (is (= #{["Fred"] ["Ethel"]}
121 | (d/q '[:find ?name
122 | :where
123 | [?p :person/age ?age]
124 | (> 50 ?age)
125 | [?p :person/name ?name]]
126 | (core/table->kb table))))))
127 |
128 | (deftest test-with-sets
129 | (let [table #{{:person/name "Fred"
130 | :address/state "NY"
131 | :person/tag #{}}
132 | {:person/name "Ethel"
133 | :address/state "NJ"
134 | :kb/id ::ethel
135 | :person/tag #{:foo/bar :baz/quux}}
136 | {:person/name "Jimbo"
137 | :address/state "VA"
138 | :person/tag #{:baz/quux}
139 | :kb/id -1000}}]
140 | (is (= #{["Jimbo"] ["Ethel"]}
141 | (d/q '[:find ?name
142 | :where
143 | [?p :person/tag :baz/quux]
144 | [?p :person/name ?name]]
145 | (core/table->kb table))))
146 |
147 | (is (= #{["Ethel"]}
148 | (d/q '[:find ?name
149 | :where
150 | [?p :person/tag :foo/bar]
151 | [?p :person/name ?name]]
152 | (core/table->kb table))))
153 |
154 | (is (= #{}
155 | (d/q '[:find ?name
156 | :where
157 | [?p :person/tag ::this-is-nowhere]
158 | [?p :person/name ?name]]
159 | (core/table->kb table))))))
160 |
161 | (deftest test-datalog-with-fuzzy-ids
162 | (let [table #{{:person/name "Fogus"
163 | :favorite/color "Purple"}
164 | {:person/name "Phogus"
165 | :spelled/wrong? true}}]
166 | (is (= #{["Fogus" "Purple" true] ["Phogus" "Purple" true]}
167 | (d/q '[:find ?n ?c ?sw
168 | :where
169 | [?p :person/name ?n]
170 | [?p :favorite/color ?c]
171 | [?p :spelled/wrong? ?sw]]
172 | (core/table->kb #(-> % :person/name (s/encode :numeric? true))
173 | table))))))
174 |
175 | (deftest test-datalog-vectors
176 | (testing "vector tuples"
177 | (let [res1 (d/q '[:find ?val
178 | :where
179 | [:primes :num/primes ?primes]
180 | [?primes :sequence/items ?h]
181 | [?h :cell/head _]
182 | [?h :cell/linked ?t]
183 | [?t :cell/head ?val]]
184 | (core/table->kb #{{:kb/id :primes
185 | :num/primes [2 3 5 7]}})
186 | d/linked-list-rules)
187 |
188 | res2 (d/q '[:find ?i ?val
189 | :where
190 | [:primes :num/primes ?primes]
191 | [?primes :sequence/items ?h]
192 | [?h :cell/head _]
193 | [?h :cell/linked ?t]
194 | [?t :cell/head ?val]
195 | [?t :cell/i ?i]]
196 | (core/table->kb #{{:kb/id :primes
197 | :num/primes [2 3 5 7]}})
198 | d/linked-list-rules)]
199 | (is (= #{[2] [3] [5] [7]} res1))
200 | (is (= [[0 2] [1 3] [2 5] [3 7]] (sort-by first res2))))))
201 |
--------------------------------------------------------------------------------
/test/rv/fuzzy/soundex_test.clj:
--------------------------------------------------------------------------------
1 | (ns rv.fuzzy.soundex-test
2 | (:require [clojure.test :refer :all]
3 | [fogus.rv.fuzzy.soundex :as fuzzy]))
4 |
5 | (def encodings {
6 | "Ashcraft" "A261"
7 | "Ashcroft" "A261"
8 | "Burroughs" "B620"
9 | "Burrows" "B620"
10 | "Clojure" "C426"
11 | "Ellery" "E460"
12 | "Euler" "E460"
13 | "Gauss" "G200"
14 | "Ghosh" "G200"
15 | "Gutierrez" "G362"
16 | "Heilbronn" "H416"
17 | "Hilbert" "H416"
18 | "Honeyman" "H555"
19 | "Jackson" "J250"
20 | "Johnson" "J525"
21 | "KAMAS" "K520"
22 | "Kant" "K530"
23 | "Knuth" "K530"
24 | "Ladd" "L300"
25 | "Lee" "L000"
26 | "Lissajous" "L222"
27 | "Lloyd" "L300"
28 | "Lukasiewicz" "L222"
29 | "Michael" "M240"
30 | "O'Hara" "O600"
31 | "Pfister" "P236"
32 | "Rubin" "R150"
33 | "Soundex" "S532"
34 | "Sownteks" "S532"
35 | "Tymczak" "T522"
36 | "VanDeusen" "V532"
37 | "Washington" "W252"
38 | "Wheaton" "W350"
39 | })
40 |
41 | (deftest test-single-words
42 | (is (= "A000" (fuzzy/encode "A")))
43 | (is (= "A100" (fuzzy/encode "Ab")))
44 | (is (= "A200" (fuzzy/encode "Ac")))
45 | (is (= "C123" (fuzzy/encode "CAaEeIiOoUuHhYybcd")))
46 | (is (= "C123" (fuzzy/encode "CAaEeIiOoUuHhYybcdkfsdjklsfdjkfsdjfsdkjfsdkfjsdkfsjk;sflajoweiuiowejrwekllksnwhjksdfnmsdfkpwipwj'kwer'dsfjkldfjsp")))
47 |
48 | (doseq [[k v] encodings]
49 | (is (= (fuzzy/encode k) v))))
50 |
51 | (deftest test-similar-sounds
52 | (is (= ["S532" "S532"] (map fuzzy/encode ["Soundex" "Sownteks"])))
53 |
54 | (is (= ["R163" "R163"] (map fuzzy/encode ["Robert" "Rupert"])))
55 |
56 | (is (= ["E251" "E251" "E251" "E251" "E251"]
57 | (map fuzzy/encode ["Example" "Ekzampul" "Ekzampull" "exzampull" "exzampull "])))
58 |
59 | (is (= ["M200" "M200" "M200" "M200" "M200"] (map fuzzy/encode ["Mike" "Maick" "Maiku" "Mike," " ,Mike"]))))
60 |
61 | (deftest test-numeric-results
62 | (is (apply not= (map fuzzy/encode ["Fogus" "Phogus" "Phogas" "Foegas"])))
63 | (is (apply = (map #(fuzzy/encode % :numeric? true) ["Fogus" "Phogus" "Phogas" "Foegas"]))))
64 |
--------------------------------------------------------------------------------
/test/rv/learn/vs_test.clj:
--------------------------------------------------------------------------------
1 | (ns rv.learn.vs-test
2 | (:require [clojure.test :refer :all]
3 | [fogus.rv.learn :as proto]
4 | [fogus.rv.learn.vs :as vs])
5 | (:refer-clojure :exclude [*]))
6 |
7 | (def ^:const _ vs/?S)
8 | (def ^:const * vs/?G)
9 |
10 | (deftest generalization-test
11 | (is (empty? (proto/-generalize [] [:a :b])))
12 | (is (= [:a] (proto/-generalize [:a] [:a])))
13 | (is (= [:a] (proto/-generalize [_] [:a])))
14 | (is (= [*] (proto/-generalize [:a] [:b])))
15 | (is (= [:a :b] (proto/-generalize [:a _] [:a :b])))
16 | (is (= [:b] (proto/-generalize [:b] [_])))
17 | (is (= [* :b] (proto/-generalize [:a :b] [:z :b]))))
18 |
19 | (deftest specialization-test
20 | (is (= [[:small :blue]] (proto/-specialize [:small *] [:small :yellow] [:small :blue])))
21 | (is (= [[:small *]] (proto/-specialize [* *] [:mid :blue] [:small :blue])))
22 | (is (= [[:small * *] [* * :laying]] (proto/-specialize [* * *] [:mid :blue :standing] [:small :blue :laying]))))
23 |
24 | (deftest collapsed-test
25 | (is (vs/collapsed? (-> (proto/-init (vs/arity-vec 2))
26 | (vs/refine '(1 2) true)
27 | (vs/refine '(:a :b) true)
28 | (vs/refine '("c" "d") false)
29 | (vs/refine '([] [1]) false)))))
30 |
31 | (deftest s&g-tests
32 | (let [{:keys [S G]} (-> (proto/-init (vs/arity-vec 3))
33 | (vs/refine [:vocal :jazz 50] true)
34 | (vs/refine [:band :pop 70] false)
35 | (vs/refine [:band :pop 80] false)
36 | (vs/refine [:solo :jazz 40] false)
37 | (vs/refine [:vocal :jazz 50] true)
38 | (vs/refine [:orchestra :classical 100] false)
39 | (vs/refine [:vocal :jazz 70] true))]
40 | (is (= [[:vocal * *]] G))
41 | (is (= [[:vocal :jazz *]] S)))
42 |
43 | (let [{:keys [S G]} (-> (proto/-init (vs/arity-vec 11))
44 | (vs/refine '("rookie" "P" "R" "MLB" "Active" "AL" "East" "Orioles" "Active" 19 "Mike") true)
45 | (vs/refine '("veteran" "P" "R" "MLB" "Active" "AL" "East" "Orioles" "Active" 23 "Jeff") true)
46 | (vs/refine '("ace" "LF" "L" "MLB" "Active" "NL" "West" "Giants" "IL" 19 "Jamie") false))]
47 | (is (= G
48 | [[* "P" * * * * * * * * *]
49 | [* * "R" * * * * * * * *]
50 | [* * * * * "AL" * * * * *]
51 | [* * * * * * "East" * * * *]
52 | [* * * * * * * "Orioles" * * *]
53 | [* * * * * * * * "Active" * *]]))
54 | (is (= S
55 | [[* "P" "R" "MLB" "Active" "AL" "East" "Orioles" "Active" * *]])))
56 |
57 | (let [{:keys [S G]} (-> (proto/-init (vs/arity-vec 6))
58 | (vs/refine [:sunny :warm :normal :strong :warm :same] true)
59 | (vs/refine [:sunny :warm :high :strong :warm :same] true)
60 | (vs/refine [:rainy :cold :high :strong :warm :change] false)
61 | (vs/refine [:sunny :warm :high :strong :cool :change] true))]
62 | (is (= G [[:sunny * * * * *] [* :warm * * * *]]))
63 | (is (= S [[:sunny :warm * :strong * *]]))))
64 |
65 | (deftest convergence-test
66 | (let [example [:japan "Honda" :blue 1980 :economy]
67 | {:keys [S G] :as V} (-> (proto/-init (vs/arity-vec 5))
68 | (vs/refine example true))]
69 | (testing "CONVERGENCE TEST STEP 1"
70 | (is (= G [[* * * * *]]))
71 | (is (= S [[:japan "Honda" :blue 1980 :economy]]))
72 | (is (not (vs/converged? V)))
73 | (is (= :fogus.rv.learn.vs/positive (vs/classify V example))))
74 |
75 | (testing "CONVERGENCE TEST STEP 2"
76 | (let [example [:japan "Toyota" :green 1970 :sports]
77 | {:keys [S G] :as V} (-> V (vs/refine example false))]
78 | (is (= G [[* "Honda" * * *]
79 | [* * :blue * *]
80 | [* * * 1980 *]
81 | [* * * * :economy]]))
82 | (is (= S [[:japan "Honda" :blue 1980 :economy]]))
83 | (is (not (vs/converged? V)))
84 | (is (= :fogus.rv.learn.vs/negative (vs/classify V example)))
85 |
86 | (testing "CONVERGENCE TEST STEP 3"
87 | (let [example [:japan "Toyota" :blue 1990 :economy]
88 | {:keys [S G] :as V} (-> V (vs/refine example true))]
89 | (is (= G [[* * :blue * *]
90 | [* * * * :economy]]))
91 | (is (= S [[:japan * :blue * :economy]]))
92 | (is (not (vs/converged? V)))
93 | (is (= :fogus.rv.learn.vs/positive (vs/classify V example)))
94 |
95 | (testing "CONVERGENCE TEST STEP 4"
96 | (let [example [:usa "Chrysler" :red 1980 :economy]
97 | {:keys [S G] :as V} (-> V (vs/refine example false))]
98 | (is (= G [[* * :blue * *]
99 | [:japan * * * :economy]]))
100 | (is (= S [[:japan * :blue * :economy]]))
101 | (is (not (vs/converged? V)))
102 | (is (= :fogus.rv.learn.vs/negative (vs/classify V example)))
103 |
104 | (testing "CONVERGENCE TEST STEP 5 - LAST"
105 | (let [example [:japan "Honda" :white 1980 :economy]]
106 | (is (vs/applicable? V example true))
107 | (let [{:keys [S G] :as V} (-> V (vs/refine example true))]
108 | (is (= :fogus.rv.learn.vs/positive (vs/classify V example)))
109 | (is (vs/converged? V)))))))))))))
110 |
111 | (deftest applicable?-test
112 | (let [vs {:S '[[Small Red Soft]]
113 | :G [[* * *]]}]
114 | (is (vs/applicable? vs ['Small 'Red 'Soft] true))
115 | (is (not (vs/applicable? vs ['Large 'Blue 'Hard] false)))
116 | (is (not (vs/applicable? vs ['Small 'Red 'Soft] false)))))
117 |
118 | (deftest consistent?-test
119 | (let [vs {:S '[[Small Red Soft]]
120 | :G [[* * *]]}]
121 | (is (vs/consistent? vs ['Small 'Red 'Soft] true))
122 | (is (not (vs/consistent? vs ['Large 'Blue 'Hard] false)))
123 | (is (not (vs/consistent? vs ['Small 'Red 'Soft] false)))))
124 |
125 | (deftest classify-test
126 | (let [vs {:S [['Small 'Red 'Soft]]
127 | :G [[* 'Red *]]}]
128 | (is (= :fogus.rv.learn.vs/positive (vs/classify vs '[Small Red Soft])))
129 | (is (= :fogus.rv.learn.vs/negative (vs/classify vs '[Large Blue Hard])))
130 | (is (= :fogus.rv.learn.vs/ambiguous (vs/classify vs '[Large Red Hard])))
131 | (is true? (vs/covers? (:G vs) '[Large Red Hard]))
132 | (is false? (vs/covers? (:S vs) '[Large Red Hard]))))
133 |
134 | (deftest explain-test
135 | (let [vs (-> (proto/-init (vs/arity-vec 6))
136 | (vs/refine [:sunny :warm :normal :strong :warm :same] true)
137 | (vs/refine [:sunny :warm :high :strong :warm :same] true)
138 | (vs/refine [:rainy :cold :high :strong :warm :change] false))
139 | example [:sunny :warm :high :strong :cool :change]]
140 | (testing "explain"
141 | (let [res (vs/explain vs example)]
142 | (is (= :fogus.rv.learn.vs/ambiguous (:explain/classification res)))
143 | (is (= [2/3] (map :similarity (:explain/S res))))
144 | (is (= [1 1 5/6] (map :similarity (:explain/G res))))))
145 | (is (= 1 (:similarity (vs/best-fit vs example))))))
146 |
--------------------------------------------------------------------------------
/test/rv/productions_test.clj:
--------------------------------------------------------------------------------
1 | (ns rv.productions-test
2 | (:require [clojure.test :refer :all]
3 | [fogus.rv.productions :as p]
4 | [fogus.rv.datalog :as d]))
5 |
6 | (def productions
7 | '[{:antecedent [[?id :emergency/type :emergency.type/fire]]
8 | :consequent [[-1000 :response/type :response.type/activate-sprinklers]
9 | [-1000 :response/to ?id]]}
10 | {:antecedent [[?id :emergency/type :emergency.type/flood]]
11 | :consequent [[-1002 :response/type :response.type/kill-electricity]
12 | [-1002 :response/to ?id]]}])
13 |
14 | (def all-facts #{[-50 :emergency/type :emergency.type/fire]
15 | [-51 :emergency/type :emergency.type/flood]})
16 |
17 | (def KB {:productions productions
18 | :facts all-facts})
19 |
20 | (deftest test-unifications
21 | ""
22 | (testing "that the context seq is built with a single antecedent pattern."
23 | (is (= '[{?id -50}]
24 | (p/unifications '[[?id :emergency/type :emergency.type/fire]]
25 | (:facts KB)
26 | {})))
27 |
28 | (is (= '[{?id -50} {?id -1000000}]
29 | (p/unifications '[[?id :emergency/type :emergency.type/fire]]
30 | (conj all-facts [-1000000 :emergency/type :emergency.type/fire])
31 | {}))))
32 |
33 | (testing "that the context is built with multiple antecedent patterns"
34 | (is (= '[{?id -50, ?rid -5000000}]
35 | (p/unifications '[[?id :emergency/type :emergency.type/fire]
36 | [?rid :response/to ?id]]
37 | (conj all-facts [-5000000 :response/to -50])
38 | {})))))
39 |
40 | (deftest test-select-production
41 | (testing "that productions are selected as expected"
42 | (let [first-matching-production (comp first identity)]
43 | (is (= (first (:productions KB))
44 | (first (p/select-production first-matching-production KB)))))))
45 |
46 | (deftest test-apply-production
47 | (testing "that a production applied to a KB causes expected assertions"
48 | (let [first-matching-production (comp first identity)
49 | [production binds] (p/select-production first-matching-production KB)]
50 | (is (= #{[-51 :emergency/type :emergency.type/flood]
51 | [-50 :emergency/type :emergency.type/fire]
52 | [-1000 :response/type :response.type/activate-sprinklers]
53 | [-1000 :response/to -50]}
54 | (p/apply-production production (:facts KB) binds))))))
55 |
56 | (deftest test-step
57 | (testing "that a single step occurs as expected"
58 | (let [first-matching-production (comp first identity)
59 | results (p/step first-matching-production KB)]
60 | (is (= #{[-1000 -50]}
61 | (d/q '[:find ?from ?to
62 | :where
63 | [?to :emergency/type :emergency.type/fire]
64 | [?from :response/to ?to]]
65 | results)))
66 |
67 | (is (= #{[:response.type/activate-sprinklers]}
68 | (d/q '[:find ?response
69 | :where
70 | [_ :response/type ?response]]
71 | results))))))
72 |
73 | (deftest test-cycle
74 | (testing "that the whole cycle occurs as expected"
75 | (let [results (p/cycle p/naive-qf
76 | '{:productions [{:antecedent [[?id :person/name ?n]
77 | [?id :isa/human? true]]
78 | :consequent [[?id :isa/mortal? true]]}]
79 | :facts #{[42 :person/name "Socrates"]
80 | [42 :isa/human? true]}})]
81 | (is (= #{[42 :isa/mortal? true] [42 :isa/human? true] [42 :person/name "Socrates"]}
82 | results)))
83 | (let [results (p/cycle p/naive-qf KB)]
84 | (is (= #{[:response.type/kill-electricity] [:response.type/activate-sprinklers]}
85 | (d/q '[:find ?response
86 | :where
87 | [_ :response/type ?response]]
88 | results)))
89 |
90 | (is (= #{[:emergency.type/fire :response.type/activate-sprinklers]
91 | [:emergency.type/flood :response.type/kill-electricity]}
92 | (d/q '[:find ?problem ?response
93 | :where
94 | [?id :response/type ?response]
95 | [?id :response/to ?pid]
96 | [?pid :emergency/type ?problem]]
97 | results))))))
98 |
--------------------------------------------------------------------------------
/test/rv/search/astar_test.clj:
--------------------------------------------------------------------------------
1 | (ns rv.search.astar-test
2 | (:require [clojure.test :refer :all]
3 | [fogus.rv.search :as search]
4 | [fogus.rv.search.graph :as graph]))
5 |
6 | (def ^:private ^:const ORTHO-DIRS [[-1 0] [1 0] [0 -1] [0 1]])
7 |
8 | (defn- neighbors
9 | [deltas size yx]
10 | (filter (fn [new-yx] (every? #(< -1 % size) new-yx))
11 | (map #(vec (map + yx %)) deltas)))
12 |
13 | (defn- init-routes [yxcosts]
14 | (let [size (count yxcosts)]
15 | (vec (repeat size (vec (repeat size nil))))))
16 |
17 | (deftype SimpleAsciiGraph [dirs step-est yxcosts routes]
18 | search/GraphSearch
19 | (neighbors-of [_ yx]
20 | (neighbors dirs (count yxcosts) yx))
21 | (add-route [_ node new-path]
22 | (SimpleAsciiGraph. dirs step-est yxcosts (assoc-in routes node new-path)))
23 | (route-of [_ node]
24 | (get-in routes node))
25 | (cost-of [_ yx]
26 | (get-in yxcosts yx))
27 | search/HeuristicSearch
28 | (estimate-cost [_ yx _]
29 | (let [[y x] yx
30 | sz (count yxcosts)]
31 | (* step-est (- (+ sz sz) y x 2)))))
32 |
33 | (deftest test-astar
34 | (let [z-world [[ 1 1 1 1 1]
35 | [999 999 999 999 1]
36 | [ 1 1 1 1 1]
37 | [ 1 999 999 999 999]
38 | [ 1 1 1 1 1]]
39 | z-graph (SimpleAsciiGraph. ORTHO-DIRS 900 z-world (init-routes z-world))
40 | res (graph/astar z-graph [0 0] [4 4])]
41 | (is (= 17 (:cost res)))
42 | (is (= [[0 0] [0 1] [0 2] [0 3] [0 4] [1 4] [2 4] [2 3] [2 2] [2 1] [2 0] [3 0] [4 0] [4 1] [4 2] [4 3] [4 4]]
43 | (:path res))))
44 |
45 | (let [down-path [[1 1 1 2 1]
46 | [1 1 1 999 1]
47 | [1 1 1 999 1]
48 | [1 1 1 999 1]
49 | [1 1 1 1 1]]
50 | down-graph (SimpleAsciiGraph. ORTHO-DIRS 900 down-path (init-routes down-path))
51 | res (graph/astar down-graph [0 0] [4 4])]
52 | (is (= 9 (:cost res)))
53 | (is (= [[0 0] [0 1] [0 2] [1 2] [2 2] [3 2] [4 2] [4 3] [4 4]]
54 | (:path res))))
55 |
56 | (let [up-path [[1 1 1 2 1]
57 | [1 1 1 999 1]
58 | [1 1 1 999 1]
59 | [1 1 1 999 1]
60 | [1 1 1 3 1]]
61 | up-graph (SimpleAsciiGraph. ORTHO-DIRS 900 up-path (init-routes up-path))
62 | res (graph/astar up-graph [0 0] [4 4])]
63 | (is (= 10 (:cost res)))
64 | (is (= [[0 0] [0 1] [0 2] [0 3] [0 4] [1 4] [2 4] [3 4] [4 4]]
65 | (:path res))))
66 |
67 | (let [l-path [[1 2 1 2 1]
68 | [1 2 1 999 1]
69 | [1 2 1 999 1]
70 | [1 2 2 999 1]
71 | [1 1 1 2 1]]
72 | l-graph (SimpleAsciiGraph. ORTHO-DIRS 900 l-path (init-routes l-path))
73 | res (graph/astar l-graph [0 0] [4 4])]
74 | (is (= 10 (:cost res)))
75 | (is (= [[0 0] [1 0] [2 0] [3 0] [4 0] [4 1] [4 2] [4 3] [4 4]]
76 | (:path res))))
77 |
78 | (let [short-path [[1 2 1 2 1]
79 | [1 0 1 999 1]
80 | [1 1 1 999 1]
81 | [1 1 1 999 1]
82 | [1 1 1 1 1]]
83 | short-graph (SimpleAsciiGraph. ORTHO-DIRS 900 short-path (init-routes short-path))
84 | res (graph/astar short-graph [0 0] [1 1])]
85 | (is (= 2 (:cost res)))
86 | (is (= [[0 0] [1 0] [1 1]]
87 | (:path res)))))
88 |
89 |
--------------------------------------------------------------------------------