Coverage for pyEDAA/Reports/CodeCoverage/UCIS/FSMs.py: 78%

187 statements  

« prev     ^ index     » next       coverage.py v7.17.3, created at 2026-11-14 02:26 +0000

1# ==================================================================================================================== # 

2# _____ ____ _ _ ____ _ # 

3# _ __ _ _| ____| _ \ / \ / \ | _ \ ___ _ __ ___ _ __| |_ ___ # 

4# | '_ \| | | | _| | | | |/ _ \ / _ \ | |_) / _ \ '_ \ / _ \| '__| __/ __| # 

5# | |_) | |_| | |___| |_| / ___ \ / ___ \ _| _ < __/ |_) | (_) | | | |_\__ \ # 

6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)_| \_\___| .__/ \___/|_| \__|___/ # 

7# |_| |___/ |_| # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2026-2026 Electronic Design Automation Abstraction (EDA²) # 

15# # 

16# Licensed under the Apache License, Version 2.0 (the "License"); # 

17# you may not use this file except in compliance with the License. # 

18# You may obtain a copy of the License at # 

19# # 

20# http://www.apache.org/licenses/LICENSE-2.0 # 

21# # 

22# Unless required by applicable law or agreed to in writing, software # 

23# distributed under the License is distributed on an "AS IS" BASIS, # 

24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

25# See the License for the specific language governing permissions and # 

26# limitations under the License. # 

27# # 

28# SPDX-License-Identifier: Apache-2.0 # 

29# ==================================================================================================================== # 

30# 

31""" 

32The FSM coverage of the UCIS XML interchange format: an instance's ``<fsmCoverage>``, its finite state machines, their 

33states and transitions. 

34""" 

35from __future__ import annotations 

36 

37from typing import TYPE_CHECKING, Iterable, Optional as Nullable, Self 

38 

39from lxml.etree import _Element 

40from pyTooling.Common import getFullyQualifiedName 

41from pyTooling.Decorators import export, readonly 

42 

43from pyEDAA.Reports.CodeCoverage.UCIS.Bins import Bin 

44from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, MetricCoverage, ObjectAttributesMixin, UserAttribute 

45 

46if TYPE_CHECKING: 46 ↛ 47line 46 didn't jump to line 47 because the condition on line 46 was never true

47 from pyEDAA.Reports.CodeCoverage.UCIS.Instances import InstanceCoverage 

48 

49 

50# A class with a property named like a class - ``Bin`` - can't name that class in the annotation of a field: the class 

51# body's namespace, where annotations are evaluated, binds the name to the property. 

52_Bin = Bin 

53 

54 

55@export 

56class FSMCoverage(MetricCoverage): 

57 """ 

58 An ``<fsmCoverage>`` of an instance: its finite state machines. 

59 """ 

60 

61 _fsms: list[FSM] #: The finite state machines. 

62 

63 def __init__( 

64 self, 

65 metricMode: Nullable[str] = None, 

66 weight: int = 1, 

67 userAttributes: Nullable[Iterable[UserAttribute]] = None, 

68 *, 

69 parent: Nullable[InstanceCoverage] = None 

70 ) -> None: 

71 """ 

72 Initialize the FSM coverage, and append it to the FSM coverages of its instance. 

73 

74 Its finite state machines are added by creating them with this coverage as their parent. 

75 

76 :param metricMode: Optional, the mode in which the coverage was measured. Default: ``None``. 

77 :param weight: Optional, weight of the mode in computing the coverage. Default: ``1``. 

78 :param userAttributes: Optional, the user-defined attributes. Default: ``None``. 

79 :param parent: Optional, the instance the coverage belongs to. Default: ``None``. 

80 :raises TypeError: If parameter ``metricMode`` isn't of type :class:`str`. 

81 :raises ValueError: If parameter ``weight`` is ``None``. 

82 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`. 

83 :raises ValueError: If parameter ``weight`` is negative. 

84 :raises TypeError: If parameter ``userAttributes`` isn't iterable. 

85 :raises TypeError: If parameter ``userAttributes`` contains an element not of type 

86 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`. 

87 :raises TypeError: If parameter ``parent`` isn't of type 

88 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Instances.InstanceCoverage`. 

89 """ 

90 super().__init__(metricMode, weight, userAttributes, parent=parent) 

91 

92 self._fsms = [] 

93 

94 if parent is not None: 

95 parent._fsmCoverages.append(self) 

96 

97 @classmethod 

98 def Parse(cls, element: _Element, *, parent: Nullable[InstanceCoverage] = None) -> Self: 

99 """ 

100 Parse an FSM coverage and its finite state machines from its ``<fsmCoverage>`` element. 

101 

102 :param element: The ``<fsmCoverage>`` element. 

103 :param parent: Optional, the instance the coverage belongs to. Default: ``None``. 

104 :returns: The FSM coverage. 

105 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states. 

106 """ 

107 fsmCoverage = cls( 

108 element.attrib.get("metricMode"), 

109 int(element.attrib.get("weight", "1")), 

110 cls._ParseUserAttributes(element), 

111 parent=parent 

112 ) 

113 

114 for fsmElement in element.iterfind("{*}fsm"): 

115 FSM.Parse(fsmElement, parent=fsmCoverage) 

116 

117 return fsmCoverage 

118 

119 @readonly 

120 def FSMs(self) -> list[FSM]: 

121 """ 

122 Read-only property to access the finite state machines (:attr:`_fsms`). 

123 

124 :returns: The finite state machines, in the order the report lists them. 

125 """ 

126 return self._fsms 

127 

128 

129@export 

130class FSM(Base, ObjectAttributesMixin): 

131 """ 

132 An ``<fsm>`` of an FSM coverage: a finite state machine, the signal holding its state, its states and transitions. 

133 """ 

134 

135 _parent: Nullable[FSMCoverage] #: The FSM coverage the finite state machine belongs to. 

136 _name: Nullable[str] #: Name of the signal holding the state. 

137 _type: Nullable[str] #: Declared type of the signal holding the state. 

138 _width: Nullable[int] #: Bit width of the signal holding the state. 

139 _states: list[FSMState] #: The states. 

140 _transitions: list[FSMTransition] #: The transitions. 

141 

142 def __init__( 

143 self, 

144 name: Nullable[str] = None, 

145 objectType: Nullable[str] = None, 

146 width: Nullable[int] = None, 

147 alias: Nullable[str] = None, 

148 excluded: bool = False, 

149 excludedReason: Nullable[str] = None, 

150 weight: int = 1, 

151 userAttributes: Nullable[Iterable[UserAttribute]] = None, 

152 *, 

153 parent: Nullable[FSMCoverage] = None 

154 ) -> None: 

155 """ 

156 Initialize the finite state machine, and append it to the finite state machines of its FSM coverage. 

157 

158 Its states and transitions are added by creating them with this finite state machine as their parent. 

159 

160 :param name: Optional, name of the signal holding the state, e.g. ``ctrl``. Default: ``None``. 

161 :param objectType: Optional, declared type of the signal holding the state, e.g. ``reg``. Default: ``None``. 

162 :param width: Optional, bit width of the signal holding the state. Default: ``None``. 

163 :param alias: Optional, an alias of the finite state machine's name. Default: ``None``. 

164 :param excluded: Optional, whether the finite state machine is excluded from the coverage. Default: 

165 ``False``. 

166 :param excludedReason: Optional, why the finite state machine is excluded. Default: ``None``. 

167 :param weight: Optional, weight of the finite state machine in computing the coverage. Default: ``1``. 

168 :param userAttributes: Optional, the user-defined attributes. Default: ``None``. 

169 :param parent: Optional, the FSM coverage the finite state machine belongs to. Default: ``None``. 

170 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

171 :raises TypeError: If parameter ``objectType`` isn't of type :class:`str`. 

172 :raises TypeError: If parameter ``width`` isn't of type :class:`int`. 

173 :raises ValueError: If parameter ``width`` is less than 1. 

174 :raises TypeError: If parameter ``alias`` isn't of type :class:`str`. 

175 :raises ValueError: If parameter ``excluded`` is ``None``. 

176 :raises TypeError: If parameter ``excluded`` isn't of type :class:`bool`. 

177 :raises TypeError: If parameter ``excludedReason`` isn't of type :class:`str`. 

178 :raises ValueError: If parameter ``weight`` is ``None``. 

179 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`. 

180 :raises ValueError: If parameter ``weight`` is negative. 

181 :raises TypeError: If parameter ``userAttributes`` isn't iterable. 

182 :raises TypeError: If parameter ``userAttributes`` contains an element not of type 

183 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`. 

184 :raises TypeError: If parameter ``parent`` isn't of type :class:`FSMCoverage`. 

185 """ 

186 super().__init__(userAttributes) 

187 ObjectAttributesMixin.__init__(self, alias, excluded, excludedReason, weight) 

188 

189 for parameterName, value in ( 

190 ("name", name), 

191 ("objectType", objectType) 

192 ): 

193 if value is not None and not isinstance(value, str): 193 ↛ 194line 193 didn't jump to line 194 because the condition on line 193 was never true

194 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.") 

195 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.") 

196 raise ex 

197 

198 if width is not None: 198 ↛ 208line 198 didn't jump to line 208 because the condition on line 198 was always true

199 if not isinstance(width, int) or isinstance(width, bool): 199 ↛ 200line 199 didn't jump to line 200 because the condition on line 199 was never true

200 ex = TypeError("Parameter 'width' is not of type 'int'.") 

201 ex.add_note(f"Got type '{getFullyQualifiedName(width)}'.") 

202 raise ex 

203 elif width < 1: 

204 ex = ValueError("Parameter 'width' is less than 1.") 

205 ex.add_note(f"Got value '{width}'.") 

206 raise ex 

207 

208 if parent is not None and not isinstance(parent, FSMCoverage): 208 ↛ 209line 208 didn't jump to line 209 because the condition on line 208 was never true

209 ex = TypeError("Parameter 'parent' is not of type 'FSMCoverage'.") 

210 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") 

211 raise ex 

212 

213 self._parent = parent 

214 self._name = name 

215 self._type = objectType 

216 self._width = width 

217 self._states = [] 

218 self._transitions = [] 

219 

220 if parent is not None: 220 ↛ exitline 220 didn't return from function '__init__' because the condition on line 220 was always true

221 parent._fsms.append(self) 

222 

223 @classmethod 

224 def Parse(cls, element: _Element, *, parent: Nullable[FSMCoverage] = None) -> Self: 

225 """ 

226 Parse a finite state machine, its states and transitions from its ``<fsm>`` element. 

227 

228 :param element: The ``<fsm>`` element. 

229 :param parent: Optional, the FSM coverage the finite state machine belongs to. Default: ``None``. 

230 :returns: The finite state machine. 

231 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states. 

232 """ 

233 width = element.attrib.get("width") 

234 fsm = cls( 

235 element.attrib.get("name"), 

236 element.attrib.get("type"), 

237 None if width is None else int(width), 

238 element.attrib.get("alias"), 

239 cls._ParseBoolean(element.attrib.get("excluded", "false")), 

240 element.attrib.get("excludedReason"), 

241 int(element.attrib.get("weight", "1")), 

242 cls._ParseUserAttributes(element), 

243 parent=parent 

244 ) 

245 

246 for stateElement in element.iterfind("{*}state"): 

247 FSMState.Parse(stateElement, parent=fsm) 

248 

249 for transitionElement in element.iterfind("{*}stateTransition"): 

250 FSMTransition.Parse(transitionElement, parent=fsm) 

251 

252 return fsm 

253 

254 @readonly 

255 def Parent(self) -> Nullable[FSMCoverage]: 

256 """ 

257 Read-only property to access the FSM coverage the finite state machine belongs to (:attr:`_parent`). 

258 

259 :returns: The FSM coverage; ``None``, if the finite state machine belongs to none. 

260 """ 

261 return self._parent 

262 

263 @readonly 

264 def Name(self) -> Nullable[str]: 

265 """ 

266 Read-only property to access the name of the signal holding the state (:attr:`_name`). 

267 

268 :returns: The name, e.g. ``ctrl``; ``None``, if the report states none. 

269 """ 

270 return self._name 

271 

272 @readonly 

273 def Type(self) -> Nullable[str]: 

274 """ 

275 Read-only property to access the declared type of the signal holding the state (:attr:`_type`). 

276 

277 :returns: The type, e.g. ``reg``; ``None``, if the report states none. 

278 """ 

279 return self._type 

280 

281 @readonly 

282 def Width(self) -> Nullable[int]: 

283 """ 

284 Read-only property to access the bit width of the signal holding the state (:attr:`_width`). 

285 

286 :returns: The width; ``None``, if the report states none. 

287 """ 

288 return self._width 

289 

290 @readonly 

291 def States(self) -> list[FSMState]: 

292 """ 

293 Read-only property to access the states (:attr:`_states`). 

294 

295 :returns: The states, in the order the report lists them. 

296 """ 

297 return self._states 

298 

299 @readonly 

300 def Transitions(self) -> list[FSMTransition]: 

301 """ 

302 Read-only property to access the transitions (:attr:`_transitions`). 

303 

304 :returns: The transitions, in the order the report lists them. 

305 """ 

306 return self._transitions 

307 

308 

309@export 

310class FSMState(Base): 

311 """ 

312 A ``<state>`` of a finite state machine: its name, value and bin - how often the machine was in it. 

313 """ 

314 

315 _parent: Nullable[FSM] #: The finite state machine the state belongs to. 

316 _name: Nullable[str] #: Name of the state. 

317 _value: Nullable[str] #: Value representing the state. 

318 _bin: _Bin #: How often the machine was in the state. 

319 

320 def __init__( 

321 self, 

322 coverBin: Bin, 

323 name: Nullable[str] = None, 

324 value: Nullable[str] = None, 

325 userAttributes: Nullable[Iterable[UserAttribute]] = None, 

326 *, 

327 parent: Nullable[FSM] = None 

328 ) -> None: 

329 """ 

330 Initialize the state, become its bin's parent, and append it to the states of its finite state machine. 

331 

332 :param coverBin: How often the machine was in the state. 

333 :param name: Optional, name of the state, e.g. ``IDLE``. Default: ``None``. 

334 :param value: Optional, value representing the state, e.g. ``4``. Default: ``None``. 

335 :param userAttributes: Optional, the user-defined attributes. Default: ``None``. 

336 :param parent: Optional, the finite state machine the state belongs to. Default: ``None``. 

337 :raises ValueError: If parameter ``coverBin`` is ``None``. 

338 :raises TypeError: If parameter ``coverBin`` isn't of type :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Bins.Bin`. 

339 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

340 :raises TypeError: If parameter ``value`` isn't of type :class:`str`. 

341 :raises TypeError: If parameter ``userAttributes`` isn't iterable. 

342 :raises TypeError: If parameter ``userAttributes`` contains an element not of type 

343 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`. 

344 :raises TypeError: If parameter ``parent`` isn't of type :class:`FSM`. 

345 """ 

346 super().__init__(userAttributes) 

347 

348 if coverBin is None: 348 ↛ 349line 348 didn't jump to line 349 because the condition on line 348 was never true

349 raise ValueError("Parameter 'coverBin' is None.") 

350 elif not isinstance(coverBin, Bin): 350 ↛ 351line 350 didn't jump to line 351 because the condition on line 350 was never true

351 ex = TypeError("Parameter 'coverBin' is not of type 'Bin'.") 

352 ex.add_note(f"Got type '{getFullyQualifiedName(coverBin)}'.") 

353 raise ex 

354 

355 for parameterName, parameterValue in ( 

356 ("name", name), 

357 ("value", value) 

358 ): 

359 if parameterValue is not None and not isinstance(parameterValue, str): 359 ↛ 360line 359 didn't jump to line 360 because the condition on line 359 was never true

360 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.") 

361 ex.add_note(f"Got type '{getFullyQualifiedName(parameterValue)}'.") 

362 raise ex 

363 

364 if parent is not None and not isinstance(parent, FSM): 364 ↛ 365line 364 didn't jump to line 365 because the condition on line 364 was never true

365 ex = TypeError("Parameter 'parent' is not of type 'FSM'.") 

366 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") 

367 raise ex 

368 

369 self._parent = parent 

370 self._name = name 

371 self._value = value 

372 self._bin = coverBin 

373 coverBin._parent = self 

374 

375 if parent is not None: 375 ↛ exitline 375 didn't return from function '__init__' because the condition on line 375 was always true

376 parent._states.append(self) 

377 

378 @classmethod 

379 def Parse(cls, element: _Element, *, parent: Nullable[FSM] = None) -> Self: 

380 """ 

381 Parse a state and its bin from its ``<state>`` element. 

382 

383 :param element: The ``<state>`` element. 

384 :param parent: Optional, the finite state machine the state belongs to. Default: ``None``. 

385 :returns: The state. 

386 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states. 

387 """ 

388 return cls( 

389 Bin.Parse(element.find("{*}stateBin")), 

390 element.attrib.get("stateName"), 

391 element.attrib.get("stateValue"), 

392 cls._ParseUserAttributes(element), 

393 parent=parent 

394 ) 

395 

396 @readonly 

397 def Parent(self) -> Nullable[FSM]: 

398 """ 

399 Read-only property to access the finite state machine the state belongs to (:attr:`_parent`). 

400 

401 :returns: The finite state machine; ``None``, if the state belongs to none. 

402 """ 

403 return self._parent 

404 

405 @readonly 

406 def Name(self) -> Nullable[str]: 

407 """ 

408 Read-only property to access the name of the state (:attr:`_name`). 

409 

410 :returns: The name, e.g. ``IDLE``; ``None``, if the report states none. 

411 """ 

412 return self._name 

413 

414 @readonly 

415 def Value(self) -> Nullable[str]: 

416 """ 

417 Read-only property to access the value representing the state (:attr:`_value`). 

418 

419 :returns: The value, as stated; ``None``, if the report states none. 

420 """ 

421 return self._value 

422 

423 @readonly 

424 def Bin(self) -> Bin: 

425 """ 

426 Read-only property to access the state's bin (:attr:`_bin`). 

427 

428 :returns: The bin, stating how often the machine was in the state. 

429 """ 

430 return self._bin 

431 

432 

433@export 

434class FSMTransition(Base): 

435 """ 

436 A ``<stateTransition>`` of a finite state machine: the states it passes, and its bin - how often it happened. 

437 """ 

438 

439 _parent: Nullable[FSM] #: The finite state machine the transition belongs to. 

440 _states: list[str] #: The states the transition passes, from the first one. 

441 _bin: _Bin #: How often the transition happened. 

442 

443 def __init__( 

444 self, 

445 states: Iterable[str], 

446 coverBin: Bin, 

447 userAttributes: Nullable[Iterable[UserAttribute]] = None, 

448 *, 

449 parent: Nullable[FSM] = None 

450 ) -> None: 

451 """ 

452 Initialize the transition, become its bin's parent, and append it to the transitions of its finite state machine. 

453 

454 :param states: The states the transition passes, from the first one, e.g. ``["IDLE", "ACTIVE"]``. 

455 :param coverBin: How often the transition happened. 

456 :param userAttributes: Optional, the user-defined attributes. Default: ``None``. 

457 :param parent: Optional, the finite state machine the transition belongs to. Default: ``None``. 

458 :raises ValueError: If parameter ``states`` is ``None``. 

459 :raises TypeError: If parameter ``states`` isn't iterable. 

460 :raises TypeError: If parameter ``states`` contains an element not of type :class:`str`. 

461 :raises ValueError: If parameter ``states`` has less than two states. 

462 :raises ValueError: If parameter ``coverBin`` is ``None``. 

463 :raises TypeError: If parameter ``coverBin`` isn't of type :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Bins.Bin`. 

464 :raises TypeError: If parameter ``userAttributes`` isn't iterable. 

465 :raises TypeError: If parameter ``userAttributes`` contains an element not of type 

466 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`. 

467 :raises TypeError: If parameter ``parent`` isn't of type :class:`FSM`. 

468 """ 

469 super().__init__(userAttributes) 

470 

471 if coverBin is None: 471 ↛ 472line 471 didn't jump to line 472 because the condition on line 471 was never true

472 raise ValueError("Parameter 'coverBin' is None.") 

473 elif not isinstance(coverBin, Bin): 473 ↛ 474line 473 didn't jump to line 474 because the condition on line 473 was never true

474 ex = TypeError("Parameter 'coverBin' is not of type 'Bin'.") 

475 ex.add_note(f"Got type '{getFullyQualifiedName(coverBin)}'.") 

476 raise ex 

477 

478 if parent is not None and not isinstance(parent, FSM): 478 ↛ 479line 478 didn't jump to line 479 because the condition on line 478 was never true

479 ex = TypeError("Parameter 'parent' is not of type 'FSM'.") 

480 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") 

481 raise ex 

482 

483 self._parent = parent 

484 self._states = stateList = [] 

485 self._bin = coverBin 

486 coverBin._parent = self 

487 

488 if states is None: 488 ↛ 489line 488 didn't jump to line 489 because the condition on line 488 was never true

489 raise ValueError("Parameter 'states' is None.") 

490 elif not isinstance(states, Iterable): 490 ↛ 491line 490 didn't jump to line 491 because the condition on line 490 was never true

491 ex = TypeError("Parameter 'states' is not iterable.") 

492 ex.add_note(f"Got type '{getFullyQualifiedName(states)}'.") 

493 raise ex 

494 

495 for state in states: 

496 if not isinstance(state, str): 496 ↛ 497line 496 didn't jump to line 497 because the condition on line 496 was never true

497 ex = TypeError("Parameter 'states' contains an element not of type 'str'.") 

498 ex.add_note(f"Got type '{getFullyQualifiedName(state)}'.") 

499 raise ex 

500 

501 stateList.append(state) 

502 

503 if len(self._states) < 2: 

504 ex = ValueError("Parameter 'states' has less than two states.") 

505 ex.add_note(f"Got {len(self._states)} state(s).") 

506 raise ex 

507 

508 if parent is not None: 508 ↛ exitline 508 didn't return from function '__init__' because the condition on line 508 was always true

509 parent._transitions.append(self) 

510 

511 @classmethod 

512 def Parse(cls, element: _Element, *, parent: Nullable[FSM] = None) -> Self: 

513 """ 

514 Parse a transition and its bin from its ``<stateTransition>`` element. 

515 

516 :param element: The ``<stateTransition>`` element. 

517 :param parent: Optional, the finite state machine the transition belongs to. Default: ``None``. 

518 :returns: The transition. 

519 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states. 

520 """ 

521 return cls( 

522 [stateElement.text or "" for stateElement in element.iterfind("{*}state")], 

523 Bin.Parse(element.find("{*}transitionBin")), 

524 cls._ParseUserAttributes(element), 

525 parent=parent 

526 ) 

527 

528 @readonly 

529 def Parent(self) -> Nullable[FSM]: 

530 """ 

531 Read-only property to access the finite state machine the transition belongs to (:attr:`_parent`). 

532 

533 :returns: The finite state machine; ``None``, if the transition belongs to none. 

534 """ 

535 return self._parent 

536 

537 @readonly 

538 def States(self) -> list[str]: 

539 """ 

540 Read-only property to access the states the transition passes (:attr:`_states`). 

541 

542 :returns: The states, from the first one, as the report states them. 

543 """ 

544 return self._states 

545 

546 @readonly 

547 def Bin(self) -> Bin: 

548 """ 

549 Read-only property to access the transition's bin (:attr:`_bin`). 

550 

551 :returns: The bin, stating how often the transition happened. 

552 """ 

553 return self._bin