git @ Cat's Eye Technologies Samovar / 164f821
Merge branch 'develop-0.2' of https://github.com/catseye/Samovar into propositions-query Chris Pressey 6 years ago
11 changed file(s) with 224 addition(s) and 192 deletion(s). Raw diff Collapse all Expand all
22
33 *Version 0.2 (unreleased). Subject to change in backwards-incompatible ways.*
44
5 Samovar is a DSL for world-modeling using propositions.
5 Samovar is a DSL for modelling a world using propositions (facts), and possible
6 events that can occur based on those facts, changing them.
67
7 A Samovar world is an immutable set of rules which operate on a mutable set of
8 facts. Each rule looks like
8 Possible events are described with _event rules_. Each event rule looks like
99
1010 [A] X [B]
1111
2727
2828 [actor(α),item(β),holding(α,β)] α puts down the β. [~holding(α,β)]
2929
30 And we can package this all into a world-description:
30 And we can package this all into a scenario:
3131
32 rules
33 [actor(α),item(β),~holding(α,β)] α picks up the β. [holding(α,β)]
34 [actor(α),item(β),holding(α,β)] α puts down the β. [~holding(α,β)]
35 end
36 situations
37 [actor(Ignatz),item(brick)]
38 end
32 scenario IgnatzWithBrick {
33
34 [actor(α),item(β),~holding(α,β)] α picks up the β. [holding(α,β)]
35 [actor(α),item(β),holding(α,β)] α puts down the β. [~holding(α,β)]
36
37 actor(Ignatz).
38 item(brick).
39
40 goal [].
41 }
3942
40 And an implementation of Samovar could take this world-description and use it to,
43 And an implementation of Samovar could take this scenario and use it to,
4144 among other things, generate textual descriptions of chains of events like
4245
4346 Ignatz picks up the brick. Ignatz puts down the brick.
6366
6467 * Maybe allow `∨` - there doesn't seem to be as much call for it, though.
6568 * Allow sentence trees to be given for actions.
66 * Allow situations to define a termination condition, so that the implementation
67 can generate a scenario where the condition is met (by whatever method).
69 * Allow scenarios to define a termination condition, so that the implementation
70 can generate a scene where the condition is met (by whatever method).
71 * Allow scenarios to specify a minimum number of events to generate (maybe?)
6872 * Consider what it would take to add a predicate that evaluates to whether
6973 a given action has been taken previously or not.
4040 help="Run cProfile on standard 'heavy load' case and exit")
4141 optparser.add_option("--words",
4242 type=int, default=0,
43 help="If given, generate each situation til this many words")
43 help="If given, generate each scenario til this many words")
4444 (options, args) = optparser.parse_args(sys.argv[1:])
4545
4646 if options.profile:
6868 sys.exit(0)
6969 if options.seed is not None:
7070 random.seed(options.seed)
71 g = Generator(ast, debug=options.debug)
72 if options.length > 0:
73 events = g.generate_events(options.length)
74 elif options.words > 0:
75 events = g.generate_words(options.words)
76 for e in events:
77 if options.line_per_sentence:
78 sys.stdout.write("%s\n" % e)
79 else:
80 sys.stdout.write("%s " % e)
81 sys.stdout.write("\n")
71 for scenario in ast.scenarios:
72 if scenario.goal is None:
73 continue
74 g = Generator(ast, scenario, debug=options.debug)
75 if options.length > 0:
76 events = g.generate_events(options.length)
77 elif options.words > 0:
78 events = g.generate_words(options.words)
79 for e in events:
80 if options.line_per_sentence:
81 sys.stdout.write("%s\n" % e)
82 else:
83 sys.stdout.write("%s " % e)
84 sys.stdout.write("\n")
1111 -> shell command
1212 -> "bin/samovar %(test-body-file) --words 20 --line-per-sentence --seed 0"
1313
14 Ignatz
15 ------
14 Scenarios
15 ---------
1616
17 rules
17 The basic unit of a Samovar world is a scenario. A scenario may contain
18 any number of propositions and event rules, and an optional goal, in any order.
19
20 scenario IgnatzWithBrick {
21
1822 [actor(α),item(β),~holding(α,β)] α picks up the β. [holding(α,β)]
1923 [actor(α),item(β),holding(α,β)] α puts down the β. [~holding(α,β)]
20 end
21 situations
22 [actor(Ignatz),item(brick)]
23 end
24
25 actor(Ignatz).
26 item(brick).
2427
28 goal [].
29 }
2530 ===> Ignatz picks up the brick.
2631 ===> Ignatz puts down the brick.
2732 ===> Ignatz picks up the brick.
2833 ===> Ignatz puts down the brick.
2934
35 A source file may contain more than one scenario. By default, our
36 implementation of Samovar runs a simulation on each of the scenarios
37 that has a goal defined, even if that goal is empty.
38
39 scenario MollyWithBrick {
40
41 [actor(α),item(β),~holding(α,β)] α picks up the β. [holding(α,β)]
42 [actor(α),item(β),holding(α,β)] α puts down the β. [~holding(α,β)]
43
44 actor(Molly).
45 item(brick).
46
47 }
48
49 scenario IgnatzWithBrick {
50
51 [actor(α),item(β),~holding(α,β)] α picks up the β. [holding(α,β)]
52 [actor(α),item(β),holding(α,β)] α puts down the β. [~holding(α,β)]
53
54 actor(Ignatz).
55 item(brick).
56
57 goal [].
58 }
59 ===> Ignatz picks up the brick.
60 ===> Ignatz puts down the brick.
61 ===> Ignatz picks up the brick.
62 ===> Ignatz puts down the brick.
63
64 Scenarios can import the event rules and propositions from other scenarios.
65 This makes a scenario a good place to collect a setting, or a group of
66 characters who will appear together in scenes. These "library" scenarios
67 should have no goal, as we don't want to generate simulations for them.
68
69 scenario ItemRules {
70 [actor(α),item(β),~holding(α,β)] α picks up the β. [holding(α,β)]
71 [actor(α),item(β),holding(α,β)] α puts down the β. [~holding(α,β)]
72 }
73 scenario Actors {
74 actor(Ignatz).
75 }
76 scenario Brickyard {
77 item(brick).
78 }
79 scenario Main {
80 import ItemRules.
81 import Actors.
82 import Brickyard.
83 goal [].
84 }
85 ===> Ignatz picks up the brick.
86 ===> Ignatz puts down the brick.
87 ===> Ignatz picks up the brick.
88 ===> Ignatz puts down the brick.
89
90 There is nothing stopping an implementation from allowing a Samovar
91 description to be spread over multiple source files, but there is no
92 facility to reference one source file from another in Samovar, so how
93 they are located and collected is up to the implementation.
3094
3195 chairs
3296 ------
3397
34 rules
98 scenario Chairs {
3599
36100 [actor(ρ)∧¬sitting(ρ)]
37101 ρ walks around the room.
49113 ρ gets up and stretches.
50114 [¬sitting(ρ)∧¬in(ρ,κ)∧empty(κ)]
51115
52 end
116 actor(Hastings).
117 actor(Petersen).
118 actor(Wembley).
119 nearby(chair). empty(chair).
120 nearby(recliner).
121 empty(recliner).
122 nearby(sofa).
123 empty(sofa).
53124
54 situations
55
56 [
57 actor(Hastings),
58 actor(Petersen),
59 actor(Wembley),
60 nearby(chair), empty(chair),
61 nearby(recliner), empty(recliner),
62 nearby(sofa), empty(sofa)
63 ]
64
65 end
125 goal [].
126 }
66127 ===> Hastings sits down in the chair.
67128 ===> Hastings leans back in the chair.
68129 ===> Petersen sits down in the sofa.
69130 ===> Wembley sits down in the recliner.
70131
71 idle
72 ----
73
74 rules
75
76 [actor(ρ)]
77 ρ rubs his chin.
78 []
79
80 [actor(ρ)]
81 ρ yawns.
82 []
83
84 end
85 ???> IndexError
86132
87133 no need for functions
88134 ---------------------
96142
97143 but we can just say
98144
99 rules
145 scenario ScratchesHead {
146
100147 [actor(ρ),possessive(ρ,ξ)]
101148 ρ scratches ξ head.
102149 []
103 end
104 situations
105 [
106 actor(Alice),
107 possessive(Alice, her),
108 actor(Bob),
109 possessive(Bob, his)
110 ]
111 end
112150
113 ==> Bob scratches his head.
114 ==> Bob scratches his head.
115 ==> Alice scratches her head.
116 ==> Alice scratches her head.
117 ==> Bob scratches his head.
151 actor(Alice).
152 possessive(Alice, her).
153 actor(Bob).
154 possessive(Bob, his).
155
156 goal [].
157 }
158 ===> Bob scratches his head.
159 ===> Bob scratches his head.
160 ===> Alice scratches her head.
161 ===> Alice scratches her head.
162 ===> Bob scratches his head.
118163
119164 This loses the nice property of the function name being a readable
120165 placeholder in the sentence, but you can now use named variables
121166 instead:
122167
123 rules
168 scenario ScratchesHead {
169
124170 [actor(?Actor),possessive(?Actor,?their)]
125171 ?Actor scratches ?their head.
126172 []
127 end
128 situations
129 [
130 actor(Alice),
131 possessive(Alice, her),
132 actor(Bob),
133 possessive(Bob, his)
134 ]
135 end
136173
137 ==> Bob scratches his head.
138 ==> Bob scratches his head.
139 ==> Alice scratches her head.
140 ==> Alice scratches her head.
141 ==> Bob scratches his head.
174 actor(Alice).
175 possessive(Alice, her).
176 actor(Bob).
177 possessive(Bob, his).
178
179 goal [].
180 }
181 ===> Bob scratches his head.
182 ===> Bob scratches his head.
183 ===> Alice scratches her head.
184 ===> Alice scratches her head.
185 ===> Bob scratches his head.
0 rules
0 scenario Chairs {
11
2 [actor(ρ)∧¬sitting(ρ)]
3 ρ walks around the room.
4 []
2 [actor(ρ)∧¬sitting(ρ)]
3 ρ walks around the room.
4 []
55
6 [actor(ρ)∧¬sitting(ρ)∧nearby(κ)∧empty(κ)]
7 ρ sits down in the κ.
8 [sitting(ρ)∧in(ρ,κ)∧¬empty(κ)]
6 [actor(ρ)∧¬sitting(ρ)∧nearby(κ)∧empty(κ)]
7 ρ sits down in the κ.
8 [sitting(ρ)∧in(ρ,κ)∧¬empty(κ)]
99
10 [actor(ρ)∧sitting(ρ)∧in(ρ,κ)]
11 ρ leans back in the κ.
12 []
10 [actor(ρ)∧sitting(ρ)∧in(ρ,κ)]
11 ρ leans back in the κ.
12 []
1313
14 [actor(ρ)∧sitting(ρ)∧in(ρ,κ)]
15 ρ gets up and stretches.
16 [¬sitting(ρ)∧¬in(ρ,κ)∧empty(κ)]
14 [actor(ρ)∧sitting(ρ)∧in(ρ,κ)]
15 ρ gets up and stretches.
16 [¬sitting(ρ)∧¬in(ρ,κ)∧empty(κ)]
1717
18 end
18 actor(Hastings).
19 actor(Petersen).
20 actor(Wembley).
21 nearby(chair), empty(chair).
22 nearby(recliner), empty(recliner).
23 nearby(sofa), empty(sofa).
1924
20 situations
21
22 [
23 actor(Hastings),
24 actor(Petersen),
25 actor(Wembley),
26 nearby(chair), empty(chair),
27 nearby(recliner), empty(recliner),
28 nearby(sofa), empty(sofa)
29 ]
30
31 end
25 goal [].
26 }
+0
-23
eg/functions.samovar less more
0 rules
1
2 [actor(ρ)]
3 ρ scratches their(ρ) head.
4 []
5
6 end
7
8 functions
9
10 their(Alice) → her
11 their(Bob) → his
12
13 end
14
15 situations
16
17 [
18 actor(Alice),
19 actor(Bob)
20 ]
21
22 end
+0
-11
eg/idle.samovar less more
0 rules
1
2 [actor(ρ)]
3 ρ rubs his chin.
4 []
5
6 [actor(ρ)]
7 ρ yawns.
8 []
9
10 end
0 rules
0 scenario HeadScratching {
1
12 [actor(?Actor),possessive(?Actor,?their)]
23 ?Actor scratches ?their head.
34 []
4 end
5 situations
6 [
7 actor(Alice),
8 possessive(Alice, her),
9 actor(Bob),
10 possessive(Bob, his)
11 ]
12 end
5
6 actor(Alice).
7 possessive(Alice, her).
8 actor(Bob).
9 possessive(Bob, his).
10
11 goal [].
12 }
4242 return acc
4343
4444
45 class Situation(AST):
45 class Scenario(AST):
4646 pass
4747
4848
1313
1414
1515 class Generator(object):
16 def __init__(self, world, debug=False):
16 def __init__(self, world, scenario, debug=False):
1717 self.world = world
1818 self.debug = debug
1919 self.state = set() # set of things currently true about the world
20 for e in self.world.situations[0].cond.exprs:
21 if isinstance(e, Assert):
22 self.state.add(e.term)
20 self.scenario = scenario
21 for term in self.scenario.propositions:
22 self.state.add(term)
2323
2424 def generate_events(self, count):
2525 if self.debug:
4949
5050 def get_candidate_rules(self):
5151 candidates = []
52 for rule in self.world.rules:
52 for rule in self.scenario.rules:
5353 for unifier in match_all(self.state, rule.pre.exprs, {}):
5454 candidates.append((rule, unifier))
5555
00 # encoding: UTF-8
11
2 from samovar.ast import World, Rule, Situation, Cond, Assert, Retract
2 from samovar.ast import World, Scenario, Rule, Cond, Assert, Retract
33 from samovar.terms import Term, Var
44 from samovar.scanner import Scanner
55
66
7 # World ::= {Rules | Situations}.
8 # Rules ::= "rules" {Rule} "end".
9 # Situations ::= "situations" {Situation} "end".
7 # World ::= {Scenario}.
8 # Scenario ::= "scenario" Atom "{" {Import | Proposition | Rule | Goal} ["." | ","] "}".
9 # Import ::= "import" Atom.
10 # Goal ::= "goal" Cond.
11 # Proposition ::= Term.
1012 # Rule ::= Cond {Term | Punct} Cond.
11 # Situation ::= Cond.
1213 # Cond ::= "[" Expr {"," Expr} "]".
1314 # Expr ::= Term | NotSym Term.
1415 # Term ::= Var | Atom ["(" Term {AndSym Term} ")"].
2324 class Parser(object):
2425 def __init__(self, text):
2526 self.scanner = Scanner(text)
27 self.scenario_map = {}
2628
2729 def world(self):
30 scenarios = []
31 while self.scanner.on('scenario'):
32 scenario = self.scenario()
33 self.scenario_map[scenario.name] = scenario
34 scenarios.append(scenario)
35 return World(scenarios=scenarios)
36
37 def scenario(self):
38 propositions = []
2839 rules = []
29 situations = []
30 while self.scanner.on('rules', 'situations'):
31 if self.scanner.on('rules'):
32 rules.extend(self._section('rules', self.rule))
33 if self.scanner.on('situations'):
34 situations.extend(self._section('situations', self.situation))
35 return World(rules=rules, situations=situations)
40 goal = None
41 self.scanner.expect('scenario')
42 self.scanner.check_type('word')
43 name = self.scanner.token
44 self.scanner.scan()
45 self.scanner.expect('{')
46 while not self.scanner.on('}'):
47 if self.scanner.consume('import'):
48 self.scanner.check_type('word')
49 from_name = self.scanner.token
50 self.scanner.scan()
51 from_scenario = self.scenario_map[from_name]
52 rules.extend(from_scenario.rules)
53 propositions.extend(from_scenario.propositions)
54 elif self.scanner.consume('goal'):
55 assert goal is None
56 goal = self.cond()
57 elif self.scanner.on('['):
58 rules.append(self.rule())
59 else:
60 propositions.append(self.proposition())
61 self.scanner.consume('.')
62 self.scanner.consume(',')
63 self.scanner.expect('}')
64 return Scenario(name=name, propositions=propositions, rules=rules, goal=goal)
3665
37 def _section(self, heading, method):
38 items = []
39 self.scanner.expect(heading)
40 while not self.scanner.on('end'):
41 items.append(method())
42 self.scanner.expect('end')
43 return items
66 def proposition(self):
67 return self.term()
4468
4569 def rule(self):
4670 terms = []
4973 terms.append(self.term())
5074 post = self.cond()
5175 return Rule(pre=pre, terms=terms, post=post)
52
53 def situation(self):
54 cond = self.cond()
55 return Situation(cond=cond)
5676
5777 def cond(self):
5878 exprs = []
3333 self.token = None
3434 self.type = 'EOF'
3535 return
36 if self.scan_pattern(ur'\~|→|¬|∧|∨', 'operator'):
36 if self.scan_pattern(ur'\~|→|=|¬|∧|∨', 'operator'):
3737 return
3838 # TODO: not sure about the ? overloading (here and in punct). should be okay though?
3939 if self.scan_pattern(r'\?[a-zA-Z_]+', 'qmark'):