Visitor#
This example shows how to use a visitor to retrieve cross-references between operators.
Setup#
The same QuadFlightControl
example is used. To setup the example, see
QuadFlightControl example setup code.
All modules are loaded:
model.load_all_modules()
Visitor definition#
The ReferenceVisitor
is derived from SwanVisitor
class. One needs:
an attribute to store the current visited operator
dictionaries to store the cross-references between operators
some methods to retrieve the data
class ReferenceVisitor(SwanVisitor):
def __init__(self) -> None:
super().__init__()
self._current_op: swan.Operator = None
self._called = {}
self._callers = {}
def get_caller(self, name):
"""Information for calling operator 'name'"""
return self._callers.get(name, None)
def get_called(self, name):
"""Information for called operator 'name'"""
return self._called.get(name, None)
The following methods are implemented, overridden from the base class:
the
SwanVisitor.visit_Operator()
: it keeps track of the current operator that is the caller, and calls the default process to go-on.and
SwanVisitor.visit_PathIdCall()
which implements the name of a called operator, amongst the possible operator expressions. Here, we first assess that the call corresponds to an operator declaration. Then we create the cross-references (see Complete example).
def visit_Operator(
self, swan_obj: swan.Operator, owner: Union[swan.Any, None], property: Union[str, None]
) -> None:
self._current_op = swan_obj
self._callers[swan_obj.get_full_path()] = {}
super().visit_Operator(swan_obj, owner, property)
self._current_op = None
def visit_PathIdOpCall(
self, swan_obj: swan.PathIdOpCall, owner: Union[swan.Any, None], property: Union[str, None]
) -> None:
if self._current_op is None:
return
name = str(swan_obj.path_id)
op = self._current_op.body.get_declaration(name)
if op is None:
logger.debug(f"Declaration of {name} not found")
return
self._add_reference(cast(swan.Operator, op).get_full_path())
The visitor is instantiated, and visits all modules in the model:
visitor = ReferenceVisitor()
for module in model.modules:
visitor.visit(module)
Finally, the caller_stat()
and called_stat()
functions
print the results for a given operator. These functions use the
ReferenceVisitor.get_caller()
and ReferenceVisitor.get_called()
methods (see Complete example).
Complete example#
This is the complete script for the visitor section. Some logging support is shown
in the code, as well as the use of the doctest
Python library. If one runs the
example, nothing is shown as the test succeeds.
# Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""
This example demonstrates the use of a visitor (see: :ref:`ref_swan_model_visitor`).
The :py:class:`ReferenceVisitor` visitor is used to explore the
"QuadFlightControl" example. This visitor finds for each operator:
- which operators call it
- which operators are called by
The functions :py:func:`caller_stat` and :py:func:`called_stat` print calls information
for a given operator.
Caller stats:
>>> caller_stat('QuadFlightControl::QuadFlightControl')
QuadFlightControl::QuadFlightControl calls:
QuadFlightControl::MotorControl: called 1 time(s)
QuadFlightControl::FlightControl: called 1 time(s)
Called stats:
>>> called_stat('QuadUtils::LimiterUnSymmetrical')
QuadUtils::LimiterUnSymmetrical called by:
QuadFlightControl::MotorControl: called 4 time(s)
QuadFlightControl::PitchRollFilter: called 1 time(s)
"""
import logging
from pathlib import Path
from typing import cast, Union
from ansys.scadeone.core.scadeone import ScadeOne
from ansys.scadeone.core.svc.swan_visitor import SwanVisitor
import ansys.scadeone.core.swan as swan
# Update according to your installation
s_one_install = Path(r"C:\Scade One")
quad_flight_project = (
s_one_install / "examples/QuadFlightControl/QuadFlightControl" / "QuadFlightControl.sproj"
)
app = ScadeOne(install_dir=s_one_install)
model = app.load_project(quad_flight_project).model
model.load_all_modules()
# Logging settings.
logging.basicConfig()
logger = logging.getLogger("visitor")
logger.setLevel(logging.ERROR)
app.logger.logger.setLevel(logging.ERROR)
class ReferenceVisitor(SwanVisitor):
def __init__(self) -> None:
super().__init__()
self._current_op: swan.Operator = None
self._called = {}
self._callers = {}
def get_caller(self, name):
"""Information for calling operator 'name'"""
return self._callers.get(name, None)
def get_called(self, name):
"""Information for called operator 'name'"""
return self._called.get(name, None)
def visit_Operator(
self, swan_obj: swan.Operator, owner: Union[swan.Any, None], property: Union[str, None]
) -> None:
self._current_op = swan_obj
self._callers[swan_obj.get_full_path()] = {}
super().visit_Operator(swan_obj, owner, property)
self._current_op = None
def visit_PathIdOpCall(
self, swan_obj: swan.PathIdOpCall, owner: Union[swan.Any, None], property: Union[str, None]
) -> None:
if self._current_op is None:
return
name = str(swan_obj.path_id)
op = self._current_op.body.get_declaration(name)
if op is None:
logger.debug(f"Declaration of {name} not found")
return
self._add_reference(cast(swan.Operator, op).get_full_path())
def _add_reference(self, called_operator):
"""Add information for 'called_operator' with
respect to current operator"""
# calls of used_operator
current_op_name = self._current_op.get_full_path()
if called_operator not in self._called:
self._called[called_operator] = {}
if current_op_name not in self._called[called_operator]:
self._called[called_operator][current_op_name] = 0
self._called[called_operator][current_op_name] += 1
# calls of current operator
if called_operator not in self._callers[current_op_name]:
self._callers[current_op_name][called_operator] = 0
self._callers[current_op_name][called_operator] += 1
visitor = ReferenceVisitor()
for module in model.modules:
visitor.visit(module)
def caller_stat(caller):
"""Print which operators are called by 'caller' operator."""
stat = visitor.get_caller(caller)
print(f"{caller} calls:")
for k, v in stat.items():
print(f" {k}: called {v} time(s)")
def called_stat(called):
"""Print which operators call 'called' operator."""
print(f"{called} called by:")
stat = visitor.get_called(called)
for k, v in stat.items():
print(f" {k}: called {v} time(s)")
if __name__ == "__main__":
import doctest
(failure_count, test_count) = doctest.testmod()
# nothing printed out if success