Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# -*- coding: utf-8 -*- 

2"""This package contains naive implementations of DFA and NFA.""" 

3import itertools 

4import pprint 

5import queue 

6from copy import deepcopy, copy 

7from typing import Set, Dict, Tuple, FrozenSet, Iterable, cast, Optional 

8 

9import graphviz 

10 

11from pythomata._internal_utils import greatest_fixpoint, least_fixpoint 

12from pythomata.alphabets import MapAlphabet 

13from pythomata.core import StateType, SymbolType, Alphabet, DFA, FiniteAutomaton 

14from pythomata.utils import powerset 

15 

16 

17class SimpleDFA(DFA[StateType, SymbolType]): 

18 """ 

19 Implementation of a simple DFA. 

20 

21 It is a naive implementation where all the components of the DFA are 

22 stored explicitly. 

23 

24 If the DFA is not complete (i.e. the transition function is partial), 

25 the successor state is None in those cases where the transition is not specified. 

26 """ 

27 

28 def __init__( 

29 self, 

30 states: Set[StateType], 

31 alphabet: Alphabet, 

32 initial_state: StateType, 

33 final_states: Set[StateType], 

34 transition_function: Dict[StateType, Dict[SymbolType, StateType]], 

35 ): 

36 """ 

37 Initialize a DFA. 

38 

39 :param states: the set of states. 

40 :param alphabet: the alphabet 

41 :param initial_state: the initial state 

42 :param final_states: the set of accepting states 

43 :param transition_function: the transition function 

44 """ 

45 self._check_input( 

46 states, alphabet, initial_state, final_states, transition_function 

47 ) 

48 

49 self._states = states 

50 self._alphabet = alphabet 

51 self._initial_state = initial_state 

52 self._final_states = final_states 

53 self._transition_function = transition_function 

54 

55 self._build_indexes() 

56 

57 @property 

58 def alphabet(self) -> Alphabet: 

59 """Get the alphabet.""" 

60 return self._alphabet 

61 

62 @property 

63 def transition_function(self) -> Dict: 

64 """Get the transition function.""" 

65 return self._transition_function 

66 

67 @property 

68 def initial_state(self) -> StateType: 

69 """Get the initial state.""" 

70 return self._initial_state 

71 

72 def get_successor(self, state: StateType, symbol: SymbolType) -> StateType: 

73 """Get the successor.""" 

74 return self.transition_function.get(state, {}).get(symbol, None) 

75 

76 @property 

77 def states(self) -> Set[StateType]: 

78 """Get the set of states.""" 

79 return self._states 

80 

81 @property 

82 def final_states(self) -> Set[StateType]: 

83 """Get the set of final states.""" 

84 return self._final_states 

85 

86 @classmethod 

87 def _check_input( 

88 cls, 

89 states: Set[StateType], 

90 alphabet: Alphabet, 

91 initial_state: StateType, 

92 accepting_states: Set[StateType], 

93 transition_function: Dict, 

94 ): 

95 """ 

96 Check the consistency of the constructor parameters. 

97 

98 :return: None 

99 :raises ValueError: if some consistency check fails. 

100 """ 

101 _check_at_least_one_state(states) 

102 _check_no_none_states(states) 

103 _check_initial_state_in_states(initial_state, states) 

104 _check_accepting_states_in_states(accepting_states, states) 

105 _check_transition_function_is_valid_wrt_states_and_alphabet( 

106 transition_function, states, alphabet 

107 ) 

108 

109 def _build_indexes(self): 

110 """Build indexes for several components of the object.""" 

111 self._idx_to_state = list(self._states) 

112 self._state_to_idx = dict(map(reversed, enumerate(self._idx_to_state))) 

113 self._idx_to_symbol = list(self._alphabet) 

114 self._symbol_to_idx = dict(map(reversed, enumerate(self._idx_to_symbol))) 

115 

116 # state -> action -> state 

117 self._idx_transition_function = { 

118 self._state_to_idx[state]: { 

119 self._symbol_to_idx[symbol]: self._state_to_idx[ 

120 self._transition_function[state][symbol] 

121 ] 

122 for symbol in self._transition_function.get(state, {}) 

123 } 

124 for state in self._states 

125 } 

126 

127 # state -> (action, state) 

128 self._idx_delta_by_state = {} 

129 for s in self._idx_transition_function: 

130 self._idx_delta_by_state[s] = set( 

131 list(self._idx_transition_function[s].items()) 

132 ) 

133 

134 self._idx_initial_state = self._state_to_idx[self._initial_state] 

135 self._idx_accepting_states = frozenset( 

136 self._state_to_idx[s] for s in self.final_states 

137 ) 

138 

139 def __eq__(self, other): 

140 """Check equality with another object.""" 

141 if not isinstance(other, SimpleDFA): 

142 return False 

143 

144 return ( 

145 self._states == other.states 

146 and self._alphabet == other.alphabet 

147 and self._initial_state == other.initial_state 

148 and self.final_states == other.final_states 

149 and self._transition_function == other.transition_function 

150 ) 

151 

152 @staticmethod 

153 def from_transitions(initial_state, accepting_states, transition_function): 

154 # type: (StateType, Set[StateType], Dict[StateType, Dict[SymbolType, StateType]]) -> SimpleDFA 

155 """ 

156 Initialize a DFA without explicitly specifying the set of states and the alphabet. 

157 

158 :param initial_state: the initial state. 

159 :param accepting_states: the accepting state. 

160 :param transition_function: the transition function. 

161 :return: the DFA. 

162 """ 

163 states, alphabet = _extract_states_from_transition_function( 

164 transition_function) # type: Set[StateType], Alphabet[SymbolType] 

165 

166 return SimpleDFA( 

167 states, alphabet, initial_state, accepting_states, transition_function 

168 ) 

169 

170 def is_complete(self) -> bool: 

171 """ 

172 Check whether the automaton is complete. 

173 

174 :return: True if the automaton is complete, False otherwise. 

175 """ 

176 complete_number_of_transitions = len(self.states) * len(self.alphabet) 

177 current_number_of_transitions = sum( 

178 len(self._transition_function[state]) for state in self._transition_function 

179 ) 

180 return complete_number_of_transitions == current_number_of_transitions 

181 

182 def complete(self) -> "SimpleDFA": 

183 """ 

184 Complete the DFA. 

185 

186 :return: the completed DFA, if it's not already complete. 

187 | Otherwise, return the caller instance. 

188 """ 

189 if self.is_complete(): 

190 return self 

191 else: 

192 return self._complete() 

193 

194 def _complete(self) -> "SimpleDFA": 

195 """ 

196 Complete the DFA. 

197 

198 :return: the completed DFA. 

199 """ 

200 sink_state = _generate_sink_name(self._states) 

201 transitions = deepcopy(self._transition_function) 

202 

203 # for every missing transition, add a transition towards the sink state. 

204 for state in self._states: 

205 for action in self._alphabet: 

206 cur_transitions = self._transition_function.get(state, {}) 

207 end_state = cur_transitions.get(action, None) # type: ignore 

208 if end_state is None: 

209 transitions.setdefault(state, {})[action] = sink_state 

210 

211 # for every action, add a transition from the sink state to the sink state 

212 for action in self._alphabet: 

213 transitions.setdefault(sink_state, {})[action] = sink_state 

214 

215 return SimpleDFA( 

216 self.states.union({sink_state}), 

217 self.alphabet, 

218 self.initial_state, 

219 self.final_states, 

220 transitions, 

221 ) 

222 

223 def minimize(self) -> 'SimpleDFA': 

224 """ 

225 Minimize the DFA. 

226 

227 :return: the minimized DFA. 

228 """ 

229 dfa = self 

230 dfa = dfa.complete() 

231 

232 def greatest_fixpoint_condition(el: Tuple[int, int], current_set: Set): 

233 """Condition to say whether the pair must be removed from the bisimulation relation.""" 

234 s, t = el 

235 s_is_final = s in dfa._idx_accepting_states 

236 t_is_final = t in dfa._idx_accepting_states 

237 if s_is_final and not t_is_final or not s_is_final and t_is_final: 

238 return True 

239 

240 s_transitions = dfa._idx_transition_function.get(s, {}) 

241 t_transitions = dfa._idx_transition_function.get(t, {}) 

242 

243 for a, s_prime in s_transitions.items(): 

244 t_prime = t_transitions.get(a, None) 

245 if t_prime is not None and (s_prime, t_prime) in current_set: 

246 continue 

247 else: 

248 return True 

249 

250 for a, t_prime in t_transitions.items(): 

251 s_prime = s_transitions.get(a, None) 

252 if s_prime is not None and (s_prime, t_prime) in current_set: 

253 continue 

254 else: 

255 return True 

256 

257 return False 

258 

259 result = greatest_fixpoint( 

260 set( 

261 itertools.product( 

262 range(len(dfa._idx_to_state)), range(len(dfa._idx_to_state)) 

263 ) 

264 ), 

265 condition=greatest_fixpoint_condition, 

266 ) 

267 

268 state2equiv_class = {} # type: Dict[int, FrozenSet[int]] 

269 for (s, t) in result: 

270 state2equiv_class.setdefault(s, frozenset()) 

271 state2equiv_class[s] = state2equiv_class[s].union(frozenset({t})) 

272 equivalence_classes = set(map(lambda x: frozenset(x), state2equiv_class.values())) 

273 equiv_class2new_state = dict( 

274 (ec, i) for i, ec in enumerate(equivalence_classes) 

275 ) 

276 

277 new_transition_function = {} # type: Dict[int, Dict[SymbolType, int]] 

278 for state in dfa._idx_delta_by_state: 

279 new_state = equiv_class2new_state[state2equiv_class[state]] 

280 for action, next_state in dfa._idx_delta_by_state[state]: 

281 new_next_state = equiv_class2new_state[state2equiv_class[next_state]] 

282 

283 new_transition_function.setdefault(new_state, {})[ 

284 dfa._idx_to_symbol[action] 

285 ] = new_next_state 

286 

287 new_states = frozenset(equiv_class2new_state.values()) 

288 new_initial_state = equiv_class2new_state[ 

289 state2equiv_class[dfa._idx_initial_state] 

290 ] 

291 new_final_states = frozenset( 

292 s 

293 for s in set( 

294 equiv_class2new_state[state2equiv_class[old_state]] 

295 for old_state in dfa._idx_accepting_states 

296 ) 

297 ) 

298 

299 new_dfa = SimpleDFA( 

300 set(new_states), 

301 dfa.alphabet, 

302 new_initial_state, 

303 set(new_final_states), 

304 new_transition_function, 

305 ) 

306 return new_dfa 

307 

308 def reachable(self): 

309 """ 

310 Get the equivalent reachable automaton. 

311 

312 :return: the reachable DFA. 

313 """ 

314 

315 def reachable_fixpoint_rule(current_set: Set) -> Iterable: 

316 result = set() 

317 for el in current_set: 

318 for a in self._idx_transition_function.get(el, {}): 

319 result.add(self._idx_transition_function[el][a]) 

320 return result 

321 

322 result = least_fixpoint({self._idx_initial_state}, reachable_fixpoint_rule) 

323 

324 idx_new_states = result 

325 new_transition_function = {} 

326 for s in idx_new_states: 

327 for a in self._idx_transition_function.get(s, {}): 

328 next_state = self._idx_transition_function[s][a] 

329 if next_state in idx_new_states: 

330 new_transition_function.setdefault(self._idx_to_state[s], {}) 

331 state = self._idx_to_state[s] 

332 new_transition_function[state][ 

333 self._idx_to_symbol[a] 

334 ] = self._idx_to_state[next_state] 

335 

336 new_states = set(map(lambda x: self._idx_to_state[x], idx_new_states)) 

337 new_final_states = new_states.intersection(self._final_states) 

338 

339 return SimpleDFA( 

340 new_states, 

341 self.alphabet, 

342 self._initial_state, 

343 new_final_states, 

344 new_transition_function, 

345 ) 

346 

347 def coreachable(self) -> 'SimpleDFA': 

348 """ 

349 Get the equivalent co-reachable automaton. 

350 

351 :return: the co-reachable DFA. 

352 """ 

353 # least fixpoint 

354 

355 def coreachable_fixpoint_rule(current_set: Set) -> Iterable: 

356 result = set() 

357 for s in range(len(self._states)): 

358 for a in self._idx_transition_function.get(s, {}): 

359 next_state = self._idx_transition_function[s][a] 

360 if next_state in current_set: 

361 result.add(s) 

362 break 

363 return result 

364 

365 result = least_fixpoint( 

366 set(self._idx_accepting_states), coreachable_fixpoint_rule 

367 ) 

368 

369 idx_new_states = result 

370 if self._idx_initial_state not in idx_new_states: 

371 return EmptyDFA(alphabet=self.alphabet) 

372 

373 new_states = set(map(lambda x: self._idx_to_state[x], idx_new_states)) 

374 new_transition_function = {} # type: Dict[StateType, Dict[SymbolType, StateType]] 

375 for s in idx_new_states: 

376 for a in self._idx_transition_function.get(s, {}): 

377 next_state = self._idx_transition_function[s][a] 

378 if next_state in idx_new_states: 

379 new_transition_function.setdefault(self._idx_to_state[s], {}) 

380 state = self._idx_to_state[s] 

381 new_transition_function[state][ 

382 self._idx_to_symbol[a] 

383 ] = self._idx_to_state[next_state] 

384 

385 return SimpleDFA( 

386 new_states, 

387 self.alphabet, 

388 self.initial_state, 

389 set(self._final_states), 

390 new_transition_function, 

391 ) 

392 

393 def trim(self) -> 'SimpleDFA': 

394 """ 

395 Trim the automaton. 

396 

397 :return: the trimmed DFA. 

398 """ 

399 dfa = self 

400 dfa = dfa.complete() 

401 dfa = dfa.reachable() 

402 dfa = dfa.coreachable() 

403 return dfa 

404 

405 def levels_to_accepting_states(self) -> dict: 

406 """ 

407 Return a dict from states to level. 

408 

409 i.e. the number of steps to reach any accepting state. 

410 level = -1 if the state cannot reach any accepting state 

411 """ 

412 res = {accepting_state: 0 for accepting_state in self.final_states} 

413 level = 0 

414 

415 # least fixpoint 

416 z_current = set() # type: Set[StateType] 

417 z_next = set(self.final_states) 

418 

419 while z_current != z_next: 

420 level += 1 

421 z_current = z_next 

422 z_next = copy(z_current) 

423 for state in self._transition_function: 

424 for action in self._transition_function[state]: 

425 if state in z_current: 

426 continue 

427 next_state = self._transition_function[state][action] 

428 if next_state in z_current: 

429 z_next.add(state) 

430 res[state] = level 

431 break 

432 

433 z_current = z_next 

434 for failure_state in filter(lambda x: x not in z_current, self._states): 

435 res[failure_state] = -1 

436 

437 return res 

438 

439 def renumbering(self) -> "SimpleDFA": 

440 """Deterministically renumber all the states. 

441 

442 :raises ValueError: if the symbols of the transitions 

443 | cannot be sorted uniquely 

444 """ 

445 idx = 0 

446 visited_states = {self._idx_initial_state} 

447 q = queue.Queue() # type: queue.Queue 

448 

449 old_state_to_number = {} 

450 

451 q.put(self._idx_initial_state) 

452 while not q.empty(): 

453 current_state = q.get() 

454 old_state_to_number[current_state] = idx 

455 idx += 1 

456 

457 try: 

458 next_actions = sorted( 

459 self._idx_transition_function[current_state], 

460 key=lambda x: self._idx_to_symbol[x], 

461 ) 

462 except TypeError: 

463 raise TypeError("Cannot sort the transition symbols.") 

464 

465 for action in next_actions: 

466 cur_tf = self._idx_transition_function[current_state] 

467 next_state = cur_tf[action] 

468 if next_state not in visited_states: 

469 visited_states.add(next_state) 

470 q.put(next_state) 

471 

472 new_states = set(range(len(old_state_to_number))) 

473 new_initial_state = old_state_to_number[self._idx_initial_state] 

474 new_accepting_states = { 

475 old_state_to_number[x] for x in self._idx_accepting_states 

476 } 

477 new_transition_function = { 

478 old_state_to_number[start]: { 

479 self._idx_to_symbol[symbol]: old_state_to_number[end] 

480 for symbol, end in self._idx_transition_function[start].items() 

481 } 

482 for start in self._idx_transition_function 

483 } 

484 

485 return SimpleDFA( 

486 cast(Set[StateType], new_states), 

487 self.alphabet, 

488 new_initial_state, 

489 cast(Set[SimpleDFA], new_accepting_states), 

490 cast(Dict[StateType, Dict[SymbolType, StateType]], new_transition_function), 

491 ) 

492 

493 def to_graphviz(self, title: Optional[str] = None) -> graphviz.Digraph: 

494 """Convert to graphviz.Digraph object.""" 

495 g = graphviz.Digraph(format="svg") 

496 g.node("fake", style="invisible") 

497 for state in self.states: 

498 if state in self.initial_states: 

499 if state in self.final_states: 

500 g.node(str(state), root="true", shape="doublecircle") 

501 else: 

502 g.node(str(state), root="true") 

503 elif state in self.final_states: 

504 g.node(str(state), shape="doublecircle") 

505 else: 

506 g.node(str(state)) 

507 

508 for i in self.initial_states: 

509 g.edge("fake", str(i), style="bold") 

510 for start in self.transition_function: 

511 for symbol, end in self._transition_function[start].items(): 

512 g.edge(str(start), str(end), label=str(symbol)) 

513 

514 if title is not None: 

515 g.attr(label=title) 

516 g.attr(fontsize="20") 

517 

518 return g 

519 

520 

521class EmptyDFA(SimpleDFA): 

522 """Implementation of an empty DFA.""" 

523 

524 def __init__(self, alphabet: Alphabet): 

525 """Initialize an empty DFA.""" 

526 super().__init__({"0"}, alphabet, "0", set(), {}) 

527 

528 def __eq__(self, other): 

529 """Check equality with another object.""" 

530 return type(self) == type(other) == EmptyDFA 

531 

532 

533class SimpleNFA(FiniteAutomaton[StateType, SymbolType]): 

534 """This class implements a NFA.""" 

535 

536 def __init__( 

537 self, 

538 states: Set[StateType], 

539 alphabet: Alphabet[SymbolType], 

540 initial_state: StateType, 

541 accepting_states: Set[StateType], 

542 transition_function: Dict[StateType, Dict[SymbolType, Set[StateType]]], 

543 ): 

544 """ 

545 Initialize a NFA. 

546 

547 :param states: the set of states. 

548 :param alphabet: the alphabet 

549 :param initial_state: the initial state 

550 :param accepting_states: the set of accepting states 

551 :param transition_function: the transition function 

552 """ 

553 self._check_input( 

554 states, alphabet, initial_state, accepting_states, transition_function 

555 ) 

556 

557 self._states = frozenset(states) # type: FrozenSet[StateType] 

558 self._alphabet = alphabet # type: Alphabet[SymbolType] 

559 self._initial_state = initial_state # type: StateType 

560 self._accepting_states = frozenset(accepting_states) # type: FrozenSet[StateType] 

561 self._transition_function = ( 

562 transition_function 

563 ) # type: Dict[StateType, Dict[SymbolType, Set[StateType]]] 

564 

565 self._build_indexes() 

566 

567 def _build_indexes(self): 

568 self._idx_to_state = sorted(self._states) 

569 self._state_to_idx = dict(map(reversed, enumerate(self._idx_to_state))) 

570 self._idx_to_symbol = sorted(self._alphabet) 

571 self._symbol_to_idx = dict(map(reversed, enumerate(self._idx_to_symbol))) 

572 

573 # state -> action -> state 

574 self._idx_transition_function = { 

575 self._state_to_idx[state]: { 

576 self._symbol_to_idx[symbol]: set( 

577 map( 

578 lambda x: self._state_to_idx[x], 

579 self._transition_function[state][symbol], 

580 ) 

581 ) 

582 for symbol in self._transition_function.get(state, {}) 

583 } 

584 for state in self._states 

585 } 

586 

587 self._idx_initial_state = self._state_to_idx[self._initial_state] 

588 self._idx_accepting_states = frozenset( 

589 self._state_to_idx[s] for s in self._accepting_states 

590 ) 

591 

592 @classmethod 

593 def _check_input( 

594 cls, 

595 states: Set[StateType], 

596 alphabet: Alphabet[SymbolType], 

597 initial_state: StateType, 

598 accepting_states: Set[StateType], 

599 transition_function: Dict[StateType, Dict[SymbolType, Set[StateType]]], 

600 ): 

601 _check_at_least_one_state(states) 

602 _check_initial_state_in_states(initial_state, states) 

603 _check_accepting_states_in_states(accepting_states, states) 

604 _check_nondet_transition_function_is_valid_wrt_states_and_alphabet( 

605 transition_function, states, alphabet 

606 ) 

607 

608 @property 

609 def alphabet(self) -> Alphabet: 

610 """Get the alphabet.""" 

611 return self._alphabet 

612 

613 @property 

614 def states(self) -> Set[StateType]: 

615 """Get the states.""" 

616 return set(self._states) 

617 

618 @property 

619 def initial_states(self) -> Set[StateType]: 

620 """Get the initial states.""" 

621 return {self._initial_state} 

622 

623 @property 

624 def final_states(self) -> Set[StateType]: 

625 """Get the final states.""" 

626 return set(self._accepting_states) 

627 

628 @property 

629 def transition_function(self) -> Dict[StateType, Dict[SymbolType, Set[StateType]]]: 

630 """Get the transition function.""" 

631 return self._transition_function 

632 

633 def get_successors(self, state: StateType, symbol: SymbolType) -> Set[StateType]: 

634 """Get the successors states.""" 

635 return self._transition_function.get(state, {}).get(symbol, set()) 

636 

637 def to_graphviz(self, title: Optional[str] = None) -> graphviz.Digraph: 

638 """Convert to graphviz.Digraph object.""" 

639 g = graphviz.Digraph(format="svg") 

640 g.node("fake", style="invisible") 

641 for state in self.states: 

642 if state in self.initial_states: 

643 if state in self.final_states: 

644 g.node(str(state), root="true", shape="doublecircle") 

645 else: 

646 g.node(str(state), root="true") 

647 elif state in self.final_states: 

648 g.node(str(state), shape="doublecircle") 

649 else: 

650 g.node(str(state)) 

651 

652 for i in self.initial_states: 

653 g.edge("fake", str(i), style="bold") 

654 for start in self.transition_function: 

655 for symbol, states in self._transition_function[start].items(): 

656 for end in states: 

657 g.edge(str(start), str(end), label=str(symbol)) 

658 

659 if title is not None: 

660 g.attr(label=title) 

661 g.attr(fontsize="20") 

662 

663 return g 

664 

665 def determinize(self) -> DFA: 

666 """ 

667 Do determinize the NFA. 

668 

669 :return: the DFA equivalent to the DFA. 

670 """ 

671 nfa = self 

672 

673 new_states = {macro_state for macro_state in powerset(nfa._states)} 

674 initial_state = frozenset([nfa._initial_state]) 

675 final_states = { 

676 q for q in new_states if len(q.intersection(nfa._accepting_states)) != 0 

677 } 

678 transition_function = {} # type: Dict[FrozenSet[StateType], Dict[SymbolType, FrozenSet[StateType]]] 

679 

680 for state_set in new_states: 

681 for action in nfa.alphabet: 

682 

683 next_macrostate = set() 

684 for s in state_set: 

685 for next_state in nfa._transition_function.get(s, {}).get( 

686 action, set() 

687 ): 

688 next_macrostate.add(next_state) 

689 

690 transition_function.setdefault(state_set, {})[action] = frozenset(next_macrostate) 

691 

692 return SimpleDFA( 

693 new_states, 

694 nfa.alphabet, 

695 initial_state, 

696 set(final_states), 

697 transition_function, 

698 ) 

699 

700 @classmethod 

701 def from_transitions(cls, initial_state, accepting_states, transition_function): 

702 # type: (StateType, Set[StateType], Dict[StateType, Dict[SymbolType, Set[StateType]]]) -> SimpleNFA 

703 """ 

704 Initialize a DFA without explicitly specifying the set of states and the alphabet. 

705 

706 :param initial_state: the initial state. 

707 :param accepting_states: the accepting state. 

708 :param transition_function: the (nondeterministic) transition function. 

709 :return: the NFA. 

710 """ 

711 states, alphabet = _extract_states_from_nondet_transition_function( 

712 transition_function 

713 ) # type: Set[StateType], Alphabet[SymbolType] 

714 

715 return SimpleNFA( 

716 states, alphabet, initial_state, accepting_states, transition_function 

717 ) 

718 

719 def __eq__(self, other): 

720 """Check the equality with another object.""" 

721 if not isinstance(other, SimpleNFA): 

722 return False 

723 return ( 

724 self.states == other.states 

725 and self.alphabet == other.alphabet 

726 and self.initial_states == other.initial_states 

727 and self.final_states == other.final_states 

728 and self.transition_function == other.transition_function 

729 ) 

730 

731 

732def _check_at_least_one_state(states: Set[StateType]): 

733 """Check that the set of states is not empty.""" 

734 if len(states) == 0: 

735 raise ValueError( 

736 "The set of states cannot be empty. Found {} instead.".format(pprint.pformat(states)) 

737 ) 

738 

739 

740def _check_no_none_states(states: Set[StateType]): 

741 """Check that the set of states does not contain a None.""" 

742 if any(s is None for s in states): 

743 raise ValueError("A state cannot be 'None'.") 

744 

745 

746def _check_initial_state_in_states(initial_state: StateType, states: Set[StateType]): 

747 """Check that the initial state is in the set of states.""" 

748 if initial_state not in states: 

749 raise ValueError( 

750 "Initial state {} not in the set of states.".format( 

751 pprint.pformat(initial_state) 

752 ) 

753 ) 

754 

755 

756def _check_accepting_states_in_states(accepting_states: Set[StateType], states: Set[StateType]): 

757 """Check that all the accepting states are in the set of states.""" 

758 if not states.issuperset(accepting_states): 

759 wrong_accepting_states = accepting_states.difference(states) 

760 raise ValueError( 

761 "Accepting states {} not in the set of states.".format( 

762 pprint.pformat(wrong_accepting_states) 

763 ) 

764 ) 

765 

766 

767def _check_transition_function_is_valid_wrt_states_and_alphabet( 

768 transition_function: Dict, states: Set[StateType], alphabet: Alphabet 

769): 

770 """Check that a transition function is compatible with the set of states and the alphabet.""" 

771 if len(transition_function) == 0: 

772 return 

773 

774 extracted_states, extracted_alphabet = _extract_states_from_transition_function( 

775 transition_function 

776 ) # type: Set[StateType], Alphabet 

777 if not all(s in states for s in extracted_states): 

778 raise ValueError( 

779 "Transition function not valid: " 

780 "states {} are not in the set of states.".format( 

781 extracted_states.difference(states) 

782 ) 

783 ) 

784 if not all(s in alphabet for s in extracted_alphabet): 

785 raise ValueError( 

786 "Transition function not valid: " 

787 "symbols {} are not in the alphabet.".format( 

788 set(extracted_alphabet).difference(alphabet) 

789 ) 

790 ) 

791 

792 

793def _check_nondet_transition_function_is_valid_wrt_states_and_alphabet( 

794 transition_function: Dict, 

795 states: Set[StateType], 

796 alphabet: Alphabet[SymbolType], 

797): 

798 """Check that a non-det tr. function is compatible wrt the set of states and the alphabet.""" 

799 if len(transition_function) == 0: 

800 return 

801 

802 extracted_states, extracted_alphabet = _extract_states_from_nondet_transition_function( 

803 transition_function 

804 ) # type: Set[StateType], Alphabet[SymbolType] 

805 if not all(s in states for s in extracted_states): 

806 raise ValueError( 

807 "Transition function not valid: " 

808 "states {} are not in the set of states.".format( 

809 extracted_states.difference(states) 

810 ) 

811 ) 

812 if not all(s in alphabet for s in extracted_alphabet): 

813 raise ValueError( 

814 "Transition function not valid: " 

815 "some symbols are not in the alphabet." 

816 ) 

817 

818 

819def _extract_states_from_nondet_transition_function(transition_function): 

820 # type: (Dict) -> Tuple[Set[StateType], Alphabet] 

821 """Extract states from a non-deterministic transition function.""" 

822 states, symbols = set(), set() 

823 for start_state in transition_function: 

824 states.add(start_state) 

825 for symbol in transition_function[start_state]: 

826 end_states = transition_function[start_state][symbol] 

827 states = states.union(end_states) 

828 symbols.add(symbol) 

829 

830 return states, MapAlphabet(symbols) 

831 

832 

833def _extract_states_from_transition_function( 

834 transition_function: Dict 

835) -> Tuple[Set[StateType], Alphabet]: 

836 """Extract states from a transition function.""" 

837 states, symbols = set(), set() 

838 for start_state in transition_function: 

839 states.add(start_state) 

840 for symbol in transition_function[start_state]: 

841 end_state = transition_function[start_state][symbol] 

842 states.add(end_state) 

843 symbols.add(symbol) 

844 

845 return states, MapAlphabet(symbols) 

846 

847 

848def _generate_sink_name(states: Set[StateType]): 

849 """Generate a sink name.""" 

850 sink_name = "sink" 

851 while True: 

852 if sink_name not in states: 

853 return sink_name 

854 sink_name = "_" + sink_name