-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmdp.py
More file actions
325 lines (261 loc) · 9.88 KB
/
Copy pathmdp.py
File metadata and controls
325 lines (261 loc) · 9.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
from antlr4 import *
from gramLexer import gramLexer
from gramListener import gramListener
from gramParser import gramParser
import sys
import random
# ============================
# 1. Model class: mdp
# ============================
class mdp:
"""
Data structure for MC / MDP models:
- states: list of states
- actions: list of actions (can be empty for MC)
- transitions: dict (state, action) -> [(next_state, weight or prob), ...]
Convention: for MC (no actions), use action=None
- use_actions / use_no_actions: used to detect mixed MC/MDP models
"""
def __init__(self):
self.states = []
self.actions = []
self.transitions = {} # { (state, action) : [(next_state, weight), ...] }
self.use_actions = False
self.use_no_actions = False
# --------- Interfaces to fill the model ---------
def add_states(self, states):
self.states = states
def add_actions(self, actions):
self.actions = actions
def add_trans_with_action(self, dep, act, targets, weights):
"""
Transition with action:
dep [act] -> w1: t1 + w2: t2 + ...
"""
self.use_actions = True
key = (dep, act)
if key not in self.transitions:
self.transitions[key] = []
for t, w in zip(targets, weights):
self.transitions[key].append((t, w))
def add_trans_without_action(self, dep, targets, weights):
"""
Transition without action (MC):
dep -> w1: t1 + w2: t2 + ...
Convention: action = None
"""
self.use_no_actions = True
key = (dep, None)
if key not in self.transitions:
self.transitions[key] = []
for t, w in zip(targets, weights):
self.transitions[key].append((t, w))
# --------- Tools: normalization + model checking ---------
def normalize(self):
"""
Convert weights into real probabilities:
weight / sum(weight)
"""
new_trans = {}
for key, lst in self.transitions.items():
total = sum(w for (_, w) in lst)
if total <= 0:
continue
new_trans[key] = [(s, w / total) for (s, w) in lst]
self.transitions = new_trans
def check(self):
"""
Basic model validation:
- All states / targets must be declared
- Mixed MC/MDP transitions are not allowed (warning)
"""
ok = True
# 1) Check if all states are declared
for (s, a), lst in self.transitions.items():
if s not in self.states:
print("Error: state", s, "not declared in States")
ok = False
for (t, _) in lst:
if t not in self.states:
print("Error: target state", t, "not declared in States")
ok = False
# 2) Mixed transitions warning
if self.use_actions and self.use_no_actions:
print("Error: model mixes transitions with and without actions")
ok = False
return ok
def get_absorbing_states(self):
"""
Automatically detect absorbing states:
A state is absorbing if its only transition is to itself with prob = 1
(requires calling normalize() first)
"""
absorbing = set()
for (s, a), lst in self.transitions.items():
if len(lst) == 1:
t, p = lst[0]
if t == s and abs(p - 1.0) < 1e-12:
absorbing.add(s)
return absorbing
# --------- Simple MC / MDP simulation ---------
# def simulate_mc(self, init_state, horizon=10):
# """
# MC simulation: assumes action=None transitions
# Returns a list of states visited
# """
# path = [init_state]
# current = init_state
# for _ in range(horizon):
# key = (current, None)
# if key not in self.transitions:
# break
# targets, probs = zip(*self.transitions[key])
# next_state = random.choices(targets, weights=probs, k=1)[0]
# path.append(next_state)
# current = next_state
# return path
# def simulate_mdp(self, init_state, policy=None, horizon=10):
# """
# MDP simulation with optional policy(state) -> action
# If policy=None, choose a random available action
# """
# current = init_state
# path = [init_state]
# for _ in range(horizon):
# available = [(s, a) for (s, a) in self.transitions.keys()
# if s == current and a is not None]
# if not available:
# break
# if policy is None:
# _, action = random.choice(available)
# else:
# action = policy(current)
# key = (current, action)
# targets, probs = zip(*self.transitions[key])
# next_state = random.choices(targets, weights=probs, k=1)[0]
# path.append((action, next_state))
# current = next_state
# return path
# --------- General simulation (Scheme C + automatic absorbing states) ---------
def simulate(self, init_state, policy=None, horizon=10, terminal_states=None):
"""
General simulation (Scheme C):
- If transitions with actions exist in current state → treat as MDP
- Else if (state, None) exists → treat as MC
- Else stop
Extra:
- If terminal_states=None → automatically use absorbing states
- Otherwise use user-provided terminal state set
Returns: (path, path_probability)
"""
if terminal_states is None:
terminal_states = self.get_absorbing_states()
else:
terminal_states = set(terminal_states)
current = init_state
path = [init_state]
path_prob = 1.0 # Initial probability
for _ in range(horizon):
if current in terminal_states:
break
available_actions = [a for (s, a) in self.transitions.keys()
if s == current and a is not None]
if available_actions:
if policy is None:
action = random.choice(available_actions)
else:
action = policy(current)
if (current, action) not in self.transitions:
action = random.choice(available_actions)
key = (current, action)
elif (current, None) in self.transitions:
key = (current, None)
else:
break
targets, probs = zip(*self.transitions[key])
next_state = random.choices(targets, weights=probs, k=1)[0]
prob = probs[targets.index(next_state)]
path_prob *= prob
path.append(next_state)
current = next_state
return path, path_prob
# ============================
# 2. Listener that builds the model
# ============================
class MDPBuilderListener(gramListener):
"""
Listener that fills the mdp object instead of printing
"""
def __init__(self):
self.model = mdp()
def enterDefstates(self, ctx):
states = [str(x) for x in ctx.ID()]
self.model.add_states(states)
def enterDefactions(self, ctx):
actions = [str(x) for x in ctx.ID()]
self.model.add_actions(actions)
def enterTransact(self, ctx):
ids = [str(x) for x in ctx.ID()]
dep = ids.pop(0)
act = ids.pop(0)
targets = ids
weights = [int(str(x)) for x in ctx.INT()]
self.model.add_trans_with_action(dep, act, targets, weights)
def enterTransnoact(self, ctx):
ids = [str(x) for x in ctx.ID()]
dep = ids.pop(0)
targets = ids
weights = [int(str(x)) for x in ctx.INT()]
self.model.add_trans_without_action(dep, targets, weights)
# ============================
# 3. Original print listener (debug)
# ============================
class gramPrintListener(gramListener):
def __init__(self):
pass
def enterDefstates(self, ctx):
print("States: %s" % str([str(x) for x in ctx.ID()]))
def enterDefactions(self, ctx):
print("Actions: %s" % str([str(x) for x in ctx.ID()]))
def enterTransact(self, ctx):
ids = [str(x) for x in ctx.ID()]
dep = ids.pop(0)
act = ids.pop(0)
weights = [int(str(x)) for x in ctx.INT()]
print("Transition from " + dep + " with action "+ act + " and targets " + str(ids) + " with weights " + str(weights))
def enterTransnoact(self, ctx):
ids = [str(x) for x in ctx.ID()]
dep = ids.pop(0)
weights = [int(str(x)) for x in ctx.INT()]
print("Transition from " + dep + " with no action and targets " + str(ids) + " with weights " + str(weights))
# ============================
# 4. main: parse + build + test
# ============================
def main():
lexer = gramLexer(StdinStream())
stream = CommonTokenStream(lexer)
parser = gramParser(stream)
tree = parser.program()
builder = MDPBuilderListener()
walker = ParseTreeWalker()
walker.walk(builder, tree)
model = builder.model
model.normalize()
ok = model.check()
if not ok:
print("Model has errors, please check above messages.")
print("=== States ===")
print(model.states)
print("=== Actions ===")
print(model.actions)
print("=== Transitions (after normalization) ===")
for key, lst in model.transitions.items():
print(key, "->", lst)
if model.states:
init = model.states[0]
print("\nGeneral simulation from", init)
path, prob = model.simulate(init_state=init, horizon=10)
print("Path:", path)
print("Path Probability:", prob)
if __name__ == '__main__':
main()