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.
@@ -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".
@@ -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".
@@ -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"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
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)