samovar-py 0.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- samovar-py-0.6/PKG-INFO +123 -0
- samovar-py-0.6/README.md +83 -0
- samovar-py-0.6/UNLICENSE +24 -0
- samovar-py-0.6/pyproject.toml +26 -0
- samovar-py-0.6/setup.cfg +4 -0
- samovar-py-0.6/src/samovar/__init__.py +0 -0
- samovar-py-0.6/src/samovar/ast.py +56 -0
- samovar-py-0.6/src/samovar/database.py +74 -0
- samovar-py-0.6/src/samovar/generators/__init__.py +0 -0
- samovar-py-0.6/src/samovar/generators/base.py +64 -0
- samovar-py-0.6/src/samovar/generators/breadthfirst.py +57 -0
- samovar-py-0.6/src/samovar/generators/depthfirst.py +73 -0
- samovar-py-0.6/src/samovar/generators/stochastic.py +62 -0
- samovar-py-0.6/src/samovar/main.py +138 -0
- samovar-py-0.6/src/samovar/parser.py +166 -0
- samovar-py-0.6/src/samovar/randomness.py +20 -0
- samovar-py-0.6/src/samovar/scanner.py +120 -0
- samovar-py-0.6/src/samovar/terms.py +127 -0
- samovar-py-0.6/src/samovar/tests.py +209 -0
- samovar-py-0.6/src/samovar_py.egg-info/PKG-INFO +123 -0
- samovar-py-0.6/src/samovar_py.egg-info/SOURCES.txt +22 -0
- samovar-py-0.6/src/samovar_py.egg-info/dependency_links.txt +1 -0
- samovar-py-0.6/src/samovar_py.egg-info/entry_points.txt +2 -0
- samovar-py-0.6/src/samovar_py.egg-info/top_level.txt +1 -0
samovar-py-0.6/PKG-INFO
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: samovar-py
|
|
3
|
+
Version: 0.6
|
|
4
|
+
Summary: Samovar: model worlds using propositions and run simulations in them
|
|
5
|
+
Author-email: Chris Pressey <packages@catseye.tc>
|
|
6
|
+
License: This is free and unencumbered software released into the public domain.
|
|
7
|
+
|
|
8
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
9
|
+
distribute this software, either in source code form or as a compiled
|
|
10
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
11
|
+
means.
|
|
12
|
+
|
|
13
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
14
|
+
of this software dedicate any and all copyright interest in the
|
|
15
|
+
software to the public domain. We make this dedication for the benefit
|
|
16
|
+
of the public at large and to the detriment of our heirs and
|
|
17
|
+
successors. We intend this dedication to be an overt act of
|
|
18
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
19
|
+
software under copyright law.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
22
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
23
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
24
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
25
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
26
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
27
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
28
|
+
|
|
29
|
+
For more information, please refer to <http://unlicense.org/>
|
|
30
|
+
|
|
31
|
+
Project-URL: website, https://catseye.tc/node/Samovar
|
|
32
|
+
Classifier: License :: OSI Approved :: The Unlicense (Unlicense)
|
|
33
|
+
Classifier: Operating System :: OS Independent
|
|
34
|
+
Classifier: Programming Language :: Python :: 2.7
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Topic :: Artistic Software
|
|
37
|
+
Classifier: Topic :: Games/Entertainment :: Simulation
|
|
38
|
+
Requires-Python: >=2.7,>=3.5
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
Samovar
|
|
42
|
+
=======
|
|
43
|
+
|
|
44
|
+
Version 0.6 | _Entry_ [@ catseye.tc](https://catseye.tc/node/Samovar)
|
|
45
|
+
| _See also:_ [The League of Extraordinarily Dull Gentlemen](https://github.com/catseye/NaNoGenMo-Entries-2018/tree/master/league#readme)
|
|
46
|
+
∘ [The Swallows](https://github.com/catseye/The-Swallows#readme)
|
|
47
|
+
|
|
48
|
+
- - - -
|
|
49
|
+
|
|
50
|
+
**Samovar** is a domain-specific language (DSL) for modelling a world using
|
|
51
|
+
propositions (facts), and possible events that can occur based on those facts,
|
|
52
|
+
changing them.
|
|
53
|
+
|
|
54
|
+
Here is a short example of a Samovar description:
|
|
55
|
+
|
|
56
|
+
scenario IgnatzWithBrick {
|
|
57
|
+
|
|
58
|
+
[actor(?A),item(?I),~holding(?A,?I)] ?A picks up the ?I. [holding(?A,?I)]
|
|
59
|
+
[actor(?A),item(?I),holding(?A,?I)] ?A puts down the ?I. [~holding(?A,?I)]
|
|
60
|
+
|
|
61
|
+
actor(Ignatz).
|
|
62
|
+
item(brick).
|
|
63
|
+
|
|
64
|
+
goal [].
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
And an implementation of Samovar could take this scenario and use it to,
|
|
68
|
+
among other things, generate textual descriptions of chains of events like
|
|
69
|
+
|
|
70
|
+
Ignatz picks up the brick. Ignatz puts down the brick.
|
|
71
|
+
|
|
72
|
+
Of course, this is a very simple example. (It doesn't even prevent two
|
|
73
|
+
actors from picking up the same item at the same time!) For more complex
|
|
74
|
+
examples, and a fuller description of the language, see
|
|
75
|
+
[doc/Samovar.md](doc/Samovar.md), which also serves as a test suite.
|
|
76
|
+
|
|
77
|
+
### Implementation
|
|
78
|
+
|
|
79
|
+
The reference implementation of Samovar, `samovar`, is written in Python.
|
|
80
|
+
It can run under either Python 2.7 or Python 3.x (tested with 3.5 or higher.)
|
|
81
|
+
|
|
82
|
+
### Discussion
|
|
83
|
+
|
|
84
|
+
This looks like logic programming but the internals are actually much simpler,
|
|
85
|
+
and what it's doing is not logical inference.
|
|
86
|
+
|
|
87
|
+
The internals are far simpler than an inference engine or a theorem
|
|
88
|
+
prover: there are no logical rules in the database, only propositions, so
|
|
89
|
+
they can be selected by simple pattern-matching rather than full unification.
|
|
90
|
+
|
|
91
|
+
I originally described it as an "assertion-retraction engine", which could
|
|
92
|
+
be thought of as a highly stylized form of Prolog programming. Alternately,
|
|
93
|
+
it could be thought of as assigning preconditions and postconditions, like
|
|
94
|
+
in [Hoare logic][], to actions in a world-model. Instead of constructing a
|
|
95
|
+
proof, though, we simply chain actions together, by selecting any next one
|
|
96
|
+
whose preconditions hold, and altering the world so that its postconditions hold.
|
|
97
|
+
|
|
98
|
+
In (I think) autumn 2019, I came across the concept of a [production system][],
|
|
99
|
+
and I realized that is basically what Samovar implements. Researching it
|
|
100
|
+
further, I discovered [CLIPS][], a production system built in 1985,
|
|
101
|
+
and I realized that Samovar is not essentially different from an
|
|
102
|
+
inefficiently-implemented, stripped-down version of CLIPS.
|
|
103
|
+
|
|
104
|
+
The main innovation in Samovar is the ability to "narrate" the choices the
|
|
105
|
+
production system makes, with human-readable text.
|
|
106
|
+
|
|
107
|
+
This isn't too surprising, since my goal with Samovar was to make an expressive
|
|
108
|
+
language, a syntax in which the author could write a story generator without
|
|
109
|
+
the undue effort of switching context between prose and code. Picking logical
|
|
110
|
+
propositions for the "code" and placing them before and after each fragment of
|
|
111
|
+
prose was a reasonably "ergonomic" choice, and happens to coincide with the
|
|
112
|
+
structure of a production system.
|
|
113
|
+
|
|
114
|
+
[Hoare logic]: https://en.wikipedia.org/wiki/Hoare_logic
|
|
115
|
+
[production system]: https://en.wikipedia.org/wiki/Production_system_(computer_science)
|
|
116
|
+
[CLIPS]: https://en.wikipedia.org/wiki/CLIPS
|
|
117
|
+
|
|
118
|
+
### TODO
|
|
119
|
+
|
|
120
|
+
* Maybe allow variables to be notated so that they can bind reflexively,
|
|
121
|
+
e.g. `?*A looks at ?*B` can bind both variables to Alice.
|
|
122
|
+
* Make `?_` work such that you can say `¬holding(?_, club)` to mean
|
|
123
|
+
"if no one is holding the club".
|
samovar-py-0.6/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Samovar
|
|
2
|
+
=======
|
|
3
|
+
|
|
4
|
+
Version 0.6 | _Entry_ [@ catseye.tc](https://catseye.tc/node/Samovar)
|
|
5
|
+
| _See also:_ [The League of Extraordinarily Dull Gentlemen](https://github.com/catseye/NaNoGenMo-Entries-2018/tree/master/league#readme)
|
|
6
|
+
∘ [The Swallows](https://github.com/catseye/The-Swallows#readme)
|
|
7
|
+
|
|
8
|
+
- - - -
|
|
9
|
+
|
|
10
|
+
**Samovar** is a domain-specific language (DSL) for modelling a world using
|
|
11
|
+
propositions (facts), and possible events that can occur based on those facts,
|
|
12
|
+
changing them.
|
|
13
|
+
|
|
14
|
+
Here is a short example of a Samovar description:
|
|
15
|
+
|
|
16
|
+
scenario IgnatzWithBrick {
|
|
17
|
+
|
|
18
|
+
[actor(?A),item(?I),~holding(?A,?I)] ?A picks up the ?I. [holding(?A,?I)]
|
|
19
|
+
[actor(?A),item(?I),holding(?A,?I)] ?A puts down the ?I. [~holding(?A,?I)]
|
|
20
|
+
|
|
21
|
+
actor(Ignatz).
|
|
22
|
+
item(brick).
|
|
23
|
+
|
|
24
|
+
goal [].
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
And an implementation of Samovar could take this scenario and use it to,
|
|
28
|
+
among other things, generate textual descriptions of chains of events like
|
|
29
|
+
|
|
30
|
+
Ignatz picks up the brick. Ignatz puts down the brick.
|
|
31
|
+
|
|
32
|
+
Of course, this is a very simple example. (It doesn't even prevent two
|
|
33
|
+
actors from picking up the same item at the same time!) For more complex
|
|
34
|
+
examples, and a fuller description of the language, see
|
|
35
|
+
[doc/Samovar.md](doc/Samovar.md), which also serves as a test suite.
|
|
36
|
+
|
|
37
|
+
### Implementation
|
|
38
|
+
|
|
39
|
+
The reference implementation of Samovar, `samovar`, is written in Python.
|
|
40
|
+
It can run under either Python 2.7 or Python 3.x (tested with 3.5 or higher.)
|
|
41
|
+
|
|
42
|
+
### Discussion
|
|
43
|
+
|
|
44
|
+
This looks like logic programming but the internals are actually much simpler,
|
|
45
|
+
and what it's doing is not logical inference.
|
|
46
|
+
|
|
47
|
+
The internals are far simpler than an inference engine or a theorem
|
|
48
|
+
prover: there are no logical rules in the database, only propositions, so
|
|
49
|
+
they can be selected by simple pattern-matching rather than full unification.
|
|
50
|
+
|
|
51
|
+
I originally described it as an "assertion-retraction engine", which could
|
|
52
|
+
be thought of as a highly stylized form of Prolog programming. Alternately,
|
|
53
|
+
it could be thought of as assigning preconditions and postconditions, like
|
|
54
|
+
in [Hoare logic][], to actions in a world-model. Instead of constructing a
|
|
55
|
+
proof, though, we simply chain actions together, by selecting any next one
|
|
56
|
+
whose preconditions hold, and altering the world so that its postconditions hold.
|
|
57
|
+
|
|
58
|
+
In (I think) autumn 2019, I came across the concept of a [production system][],
|
|
59
|
+
and I realized that is basically what Samovar implements. Researching it
|
|
60
|
+
further, I discovered [CLIPS][], a production system built in 1985,
|
|
61
|
+
and I realized that Samovar is not essentially different from an
|
|
62
|
+
inefficiently-implemented, stripped-down version of CLIPS.
|
|
63
|
+
|
|
64
|
+
The main innovation in Samovar is the ability to "narrate" the choices the
|
|
65
|
+
production system makes, with human-readable text.
|
|
66
|
+
|
|
67
|
+
This isn't too surprising, since my goal with Samovar was to make an expressive
|
|
68
|
+
language, a syntax in which the author could write a story generator without
|
|
69
|
+
the undue effort of switching context between prose and code. Picking logical
|
|
70
|
+
propositions for the "code" and placing them before and after each fragment of
|
|
71
|
+
prose was a reasonably "ergonomic" choice, and happens to coincide with the
|
|
72
|
+
structure of a production system.
|
|
73
|
+
|
|
74
|
+
[Hoare logic]: https://en.wikipedia.org/wiki/Hoare_logic
|
|
75
|
+
[production system]: https://en.wikipedia.org/wiki/Production_system_(computer_science)
|
|
76
|
+
[CLIPS]: https://en.wikipedia.org/wiki/CLIPS
|
|
77
|
+
|
|
78
|
+
### TODO
|
|
79
|
+
|
|
80
|
+
* Maybe allow variables to be notated so that they can bind reflexively,
|
|
81
|
+
e.g. `?*A looks at ?*B` can bind both variables to Alice.
|
|
82
|
+
* Make `?_` work such that you can say `¬holding(?_, club)` to mean
|
|
83
|
+
"if no one is holding the club".
|
samovar-py-0.6/UNLICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For more information, please refer to <http://unlicense.org/>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "samovar-py"
|
|
7
|
+
version = "0.6"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Chris Pressey", email="packages@catseye.tc" },
|
|
10
|
+
]
|
|
11
|
+
urls = { website="https://catseye.tc/node/Samovar" }
|
|
12
|
+
description = "Samovar: model worlds using propositions and run simulations in them"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
license = { file="UNLICENSE" }
|
|
15
|
+
classifiers = [
|
|
16
|
+
"License :: OSI Approved :: The Unlicense (Unlicense)",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 2.7",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Topic :: Artistic Software",
|
|
21
|
+
"Topic :: Games/Entertainment :: Simulation",
|
|
22
|
+
]
|
|
23
|
+
requires-python = ">=2.7, >=3.5"
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
samovar = "samovar.main:sys_main"
|
samovar-py-0.6/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
from collections import namedtuple
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Python 2/3
|
|
7
|
+
try:
|
|
8
|
+
unicode = unicode
|
|
9
|
+
except NameError:
|
|
10
|
+
unicode = str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
World = namedtuple('World', ['scenarios'])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Rule(namedtuple('Rule', ['pre', 'words', 'post'])):
|
|
17
|
+
__slots__ = ()
|
|
18
|
+
|
|
19
|
+
def format(self, unifier):
|
|
20
|
+
return join_sentence_parts([unicode(t.subst(unifier)) for t in self.words])
|
|
21
|
+
|
|
22
|
+
def to_json(self):
|
|
23
|
+
return join_sentence_parts([unicode(t) for t in self.words])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Scenario = namedtuple('Scenario', ['name', 'propositions', 'rules', 'goal'])
|
|
27
|
+
Cond = namedtuple('Cond', ['exprs', 'bindings'])
|
|
28
|
+
Assert = namedtuple('Assert', ['term'])
|
|
29
|
+
Retract = namedtuple('Retract', ['term'])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def join_sentence_parts(parts):
|
|
33
|
+
acc = u''
|
|
34
|
+
quote_open = False
|
|
35
|
+
for part in parts:
|
|
36
|
+
last = '' if not acc else acc[-1]
|
|
37
|
+
if last == '':
|
|
38
|
+
acc += part
|
|
39
|
+
elif last == '"' and quote_open:
|
|
40
|
+
acc += part
|
|
41
|
+
elif last == '"' and not quote_open:
|
|
42
|
+
if part in (u'.', u',', u'!', u'?'):
|
|
43
|
+
acc += part
|
|
44
|
+
else:
|
|
45
|
+
acc += ' ' + part
|
|
46
|
+
elif last == ',' and part == u'"' and not quote_open:
|
|
47
|
+
acc += ' ' + part
|
|
48
|
+
elif last in (u'.', u',', u'!', u'?', u"'") and part == u'"' and not quote_open:
|
|
49
|
+
acc += part
|
|
50
|
+
elif part in (u'.', u',', u'!', u'?', u"'", u'"'):
|
|
51
|
+
acc += part
|
|
52
|
+
else:
|
|
53
|
+
acc += u' ' + part
|
|
54
|
+
if part == '"':
|
|
55
|
+
quote_open = not quote_open
|
|
56
|
+
return acc
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from samovar.ast import Assert, Retract
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Database(object):
|
|
5
|
+
"""A database is a set of things true at some point in time in a world."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, propositions, sorted_search=True):
|
|
8
|
+
"""Create a new Database object with the given propositions.
|
|
9
|
+
|
|
10
|
+
While the construction and iteration of the underlying set in Python 2 seems to be
|
|
11
|
+
stable, when running under Python 3, we get an unpredictable order when iterating
|
|
12
|
+
over the set. The `sorted_search` option controls whether we sort the iterable
|
|
13
|
+
before making matches, so that we can accumulate them in a predictable order.
|
|
14
|
+
This is valuable for tests, but can be turned off to improve performance.
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
self.sorted_search = sorted_search
|
|
18
|
+
self.contents = set()
|
|
19
|
+
for term in propositions:
|
|
20
|
+
self.contents.add(term)
|
|
21
|
+
|
|
22
|
+
def clone(self):
|
|
23
|
+
return Database(self.contents, sorted_search=self.sorted_search)
|
|
24
|
+
|
|
25
|
+
def add(self, term):
|
|
26
|
+
self.contents.add(term)
|
|
27
|
+
|
|
28
|
+
def remove(self, term):
|
|
29
|
+
self.contents.remove(term)
|
|
30
|
+
|
|
31
|
+
def match_all(self, patterns, env):
|
|
32
|
+
"""Find all matches for the given patterns in the database, and return a list of unifiers.
|
|
33
|
+
|
|
34
|
+
`database` is an iterable of `Term`s.
|
|
35
|
+
`patterns` is a list of `Assert`s and `Retract`s.
|
|
36
|
+
`env` is a dict mapping `Var` names to `Term`s (a previous unifier.)
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
if not patterns:
|
|
40
|
+
return [env]
|
|
41
|
+
envs = []
|
|
42
|
+
pattern = patterns[0]
|
|
43
|
+
|
|
44
|
+
if isinstance(pattern, Assert):
|
|
45
|
+
for proposition in (sorted(self.contents) if self.sorted_search else self.contents):
|
|
46
|
+
unifier = pattern.term.match(proposition, env, unique_binding=True)
|
|
47
|
+
if unifier is None:
|
|
48
|
+
continue
|
|
49
|
+
envs.extend(self.match_all(patterns[1:], unifier))
|
|
50
|
+
|
|
51
|
+
elif isinstance(pattern, Retract):
|
|
52
|
+
expanded_pattern = pattern.term.subst(env)
|
|
53
|
+
|
|
54
|
+
# To test a negative match, we require that there are
|
|
55
|
+
# no free variables remaining in our pattern. If there are,
|
|
56
|
+
# it will simply not match. TODO add a flag to turn this
|
|
57
|
+
# check on to support better debugging.
|
|
58
|
+
|
|
59
|
+
# free_vars = set()
|
|
60
|
+
# expanded_pattern.collect_variables(free_vars)
|
|
61
|
+
# if free_vars:
|
|
62
|
+
# # TODO: better exception than this
|
|
63
|
+
# raise NotImplementedError
|
|
64
|
+
|
|
65
|
+
# now we simply check if the term exists in the database.
|
|
66
|
+
# if it does not, we recurse down to the next clause in the pattern.
|
|
67
|
+
|
|
68
|
+
if not (expanded_pattern in self.contents):
|
|
69
|
+
envs.extend(self.match_all(patterns[1:], env))
|
|
70
|
+
|
|
71
|
+
else:
|
|
72
|
+
raise NotImplementedError
|
|
73
|
+
|
|
74
|
+
return envs
|
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from samovar.ast import Assert, Retract
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Python 2/3
|
|
9
|
+
try:
|
|
10
|
+
xrange = xrange
|
|
11
|
+
except NameError:
|
|
12
|
+
xrange = range
|
|
13
|
+
|
|
14
|
+
# Python 2/3
|
|
15
|
+
try:
|
|
16
|
+
unicode = unicode
|
|
17
|
+
except NameError:
|
|
18
|
+
unicode = str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Event(object):
|
|
22
|
+
def __init__(self, rule, unifier):
|
|
23
|
+
self.rule = rule
|
|
24
|
+
self.unifier = unifier
|
|
25
|
+
|
|
26
|
+
def to_json(self):
|
|
27
|
+
u = dict([(unicode(k), unicode(v)) for k, v in self.unifier.items()])
|
|
28
|
+
return [self.rule.to_json(), u]
|
|
29
|
+
|
|
30
|
+
def __str__(self):
|
|
31
|
+
return self.rule.format(self.unifier)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BaseGenerator(object):
|
|
35
|
+
def __init__(self, world, scenario, **kwargs):
|
|
36
|
+
self.world = world
|
|
37
|
+
self.scenario = scenario
|
|
38
|
+
|
|
39
|
+
def goal_is_met(self, state):
|
|
40
|
+
matches = state.match_all(self.scenario.goal.exprs, self.scenario.goal.bindings)
|
|
41
|
+
return len(matches) > 0
|
|
42
|
+
|
|
43
|
+
def candidate_rules(self, state, require_change=False):
|
|
44
|
+
"""A generator."""
|
|
45
|
+
for rule in self.scenario.rules:
|
|
46
|
+
for unifier in state.match_all(rule.pre.exprs, rule.pre.bindings):
|
|
47
|
+
if require_change and not rule.post.exprs:
|
|
48
|
+
# Rules that don't change the state of the world are not worth considering.
|
|
49
|
+
continue
|
|
50
|
+
yield (rule, unifier)
|
|
51
|
+
|
|
52
|
+
def update_state(self, state, env, rule):
|
|
53
|
+
for expr in rule.post.exprs:
|
|
54
|
+
term = expr.term.subst(env)
|
|
55
|
+
if isinstance(expr, Assert):
|
|
56
|
+
state.add(term)
|
|
57
|
+
elif isinstance(expr, Retract):
|
|
58
|
+
state.remove(term)
|
|
59
|
+
|
|
60
|
+
def debug_state(self, state, label):
|
|
61
|
+
sys.stderr.write(":::: {} State [\n".format(label))
|
|
62
|
+
for term in sorted(state.contents):
|
|
63
|
+
sys.stderr.write(":::: {}\n".format(term))
|
|
64
|
+
sys.stderr.write(":::: ]\n")
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
"""A breadth-first searching Generator that finds the
|
|
4
|
+
shortest path of events that leads to the goal state
|
|
5
|
+
(if any such path exists).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
from samovar.ast import Assert, Retract
|
|
12
|
+
from samovar.database import Database
|
|
13
|
+
|
|
14
|
+
from .base import Event, BaseGenerator
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BreadthFirstGenerator(BaseGenerator):
|
|
18
|
+
def __init__(self, world, scenario, verbosity=0, sorted_search=True, randomness=None):
|
|
19
|
+
self.world = world
|
|
20
|
+
self.scenario = scenario
|
|
21
|
+
self.verbosity = verbosity
|
|
22
|
+
self.sorted_search = sorted_search
|
|
23
|
+
self.random = randomness
|
|
24
|
+
|
|
25
|
+
def generate_events(self, **kwargs):
|
|
26
|
+
situations = [
|
|
27
|
+
([], Database(self.scenario.propositions, sorted_search=self.sorted_search))
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
seen_states = set()
|
|
31
|
+
|
|
32
|
+
while True:
|
|
33
|
+
new_situations = []
|
|
34
|
+
if self.verbosity >= 1:
|
|
35
|
+
sys.stderr.write("Considering {} situations\n".format(len(situations)))
|
|
36
|
+
start_time = time.time()
|
|
37
|
+
for (events, state) in situations:
|
|
38
|
+
for rule, unifier in self.candidate_rules(state, require_change=True):
|
|
39
|
+
new_event = Event(rule, unifier)
|
|
40
|
+
new_state = state.clone()
|
|
41
|
+
self.update_state(new_state, unifier, rule)
|
|
42
|
+
froz = frozenset(new_state.contents)
|
|
43
|
+
if froz in seen_states:
|
|
44
|
+
continue
|
|
45
|
+
seen_states.add(froz)
|
|
46
|
+
new_events = events + [new_event]
|
|
47
|
+
if self.goal_is_met(new_state):
|
|
48
|
+
return new_events
|
|
49
|
+
new_situations.append(
|
|
50
|
+
(new_events, new_state)
|
|
51
|
+
)
|
|
52
|
+
if self.verbosity >= 1:
|
|
53
|
+
end_time = time.time()
|
|
54
|
+
duration = end_time - start_time
|
|
55
|
+
sys.stderr.write("Considered {} situations in {} seconds ({} situations / second)\n".format(len(situations), duration, float(len(situations))/duration))
|
|
56
|
+
sys.stderr.write("Installing {} new situations\n".format(len(new_situations)))
|
|
57
|
+
situations = new_situations
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
# FIXME: just a sketch for now
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from samovar.ast import Assert, Retract
|
|
9
|
+
from samovar.database import Database
|
|
10
|
+
|
|
11
|
+
from .base import Event, BaseGenerator
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DepthFirstGenerator(BaseGenerator):
|
|
15
|
+
def __init__(self, world, scenario, verbosity=0, sorted_search=True, randomness=None):
|
|
16
|
+
self.world = world
|
|
17
|
+
self.scenario = scenario
|
|
18
|
+
self.verbosity = verbosity
|
|
19
|
+
self.sorted_search = sorted_search
|
|
20
|
+
self.random = randomness
|
|
21
|
+
self.seen_states = set()
|
|
22
|
+
|
|
23
|
+
def generate_events(self, **kwargs):
|
|
24
|
+
self.seen_states = set()
|
|
25
|
+
|
|
26
|
+
# Create initial stacks.
|
|
27
|
+
state = Database(self.scenario.propositions, sorted_search=self.sorted_search)
|
|
28
|
+
candidate_rules = list(self.candidate_rules(state, require_change=True))
|
|
29
|
+
stack = [
|
|
30
|
+
[None, state, candidate_rules, 0]
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
# Loop, doing work at the top of the stack.
|
|
34
|
+
done = False
|
|
35
|
+
counter = 0
|
|
36
|
+
while not done:
|
|
37
|
+
counter += 1
|
|
38
|
+
[last_event, state, candidate_rules, cr_index] = stack[-1]
|
|
39
|
+
if self.verbosity >= 1 and counter % 1000 == 0:
|
|
40
|
+
sys.stderr.write("Stack depth {}, candidate count {}, index {}\n".format(
|
|
41
|
+
len(stack), len(candidate_rules), cr_index
|
|
42
|
+
))
|
|
43
|
+
if cr_index > (len(candidate_rules) - 1):
|
|
44
|
+
# we've exhausted all the candidates on this level. backtrack.
|
|
45
|
+
if self.verbosity >= 1:
|
|
46
|
+
sys.stderr.write("*** Backtracking\n")
|
|
47
|
+
stack.pop()
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
# otherwise, try to make a match.
|
|
51
|
+
rule, unifier = candidate_rules[cr_index]
|
|
52
|
+
|
|
53
|
+
new_event = Event(rule, unifier)
|
|
54
|
+
new_state = state.clone()
|
|
55
|
+
self.update_state(new_state, unifier, rule)
|
|
56
|
+
if self.verbosity >= 2:
|
|
57
|
+
self.debug_state(new_state, "Intermediate")
|
|
58
|
+
if self.goal_is_met(new_state):
|
|
59
|
+
return [e[0] for e in stack if e[0] is not None] + [new_event]
|
|
60
|
+
|
|
61
|
+
froz = frozenset(new_state.contents)
|
|
62
|
+
if froz in self.seen_states:
|
|
63
|
+
stack[-1][3] += 1 # inc cr_index. TODO this is so ugly
|
|
64
|
+
continue
|
|
65
|
+
self.seen_states.add(froz)
|
|
66
|
+
|
|
67
|
+
# otherwise, descend to try to extend the sequence of events we've got.
|
|
68
|
+
new_candidate_rules = list(self.candidate_rules(new_state, require_change=True))
|
|
69
|
+
stack.append(
|
|
70
|
+
[new_event, new_state, new_candidate_rules, 0]
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return None
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from samovar.database import Database
|
|
6
|
+
|
|
7
|
+
from .base import xrange, Event, BaseGenerator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StochasticGenerator(BaseGenerator):
|
|
11
|
+
def __init__(self, world, scenario, verbosity=0, sorted_search=True, randomness=None):
|
|
12
|
+
self.world = world
|
|
13
|
+
self.scenario = scenario
|
|
14
|
+
self.verbosity = verbosity
|
|
15
|
+
self.sorted_search = sorted_search
|
|
16
|
+
self.random = randomness
|
|
17
|
+
self.reset_state()
|
|
18
|
+
|
|
19
|
+
def reset_state(self):
|
|
20
|
+
self.state = Database(self.scenario.propositions, sorted_search=self.sorted_search)
|
|
21
|
+
|
|
22
|
+
def generate_events(self, min_count=0, max_count=100, lengthen_factor=1.5):
|
|
23
|
+
acceptable = False
|
|
24
|
+
count = min_count
|
|
25
|
+
while not acceptable:
|
|
26
|
+
if self.verbosity >= 1:
|
|
27
|
+
sys.stderr.write("Generating {} events\n".format(count))
|
|
28
|
+
self.reset_state()
|
|
29
|
+
if self.verbosity >= 2:
|
|
30
|
+
self.debug_state(self.state, "Initial")
|
|
31
|
+
events = []
|
|
32
|
+
for i in xrange(0, count):
|
|
33
|
+
event = self.generate_event()
|
|
34
|
+
if event is None:
|
|
35
|
+
break
|
|
36
|
+
events.append(event)
|
|
37
|
+
if self.verbosity >= 2:
|
|
38
|
+
self.debug_state(self.state, "Final")
|
|
39
|
+
acceptable = self.goal_is_met(self.state)
|
|
40
|
+
if not acceptable:
|
|
41
|
+
count = int(float(count) * lengthen_factor)
|
|
42
|
+
if count > max_count:
|
|
43
|
+
raise ValueError("{}: count exceeds maximum".format(self.scenario.name))
|
|
44
|
+
return events
|
|
45
|
+
|
|
46
|
+
def generate_event(self):
|
|
47
|
+
candidates = list(self.candidate_rules(self.state))
|
|
48
|
+
|
|
49
|
+
if self.verbosity >= 3:
|
|
50
|
+
sys.stderr.write("Candidate rules:\n")
|
|
51
|
+
for rule, unifiers in candidates:
|
|
52
|
+
sys.stderr.write("-> " + rule.to_json() + "\n")
|
|
53
|
+
sys.stderr.write("---> {}\n".format(unifiers))
|
|
54
|
+
sys.stderr.write("")
|
|
55
|
+
|
|
56
|
+
if not candidates:
|
|
57
|
+
return None
|
|
58
|
+
rule, unifier = self.random.choice(candidates)
|
|
59
|
+
self.update_state(self.state, unifier, rule)
|
|
60
|
+
if self.verbosity >= 3:
|
|
61
|
+
self.debug_state(self.state, "Intermediate")
|
|
62
|
+
return Event(rule, unifier)
|