Source code for pyuppaal.tracer

"""使用方法 ./trace_custom.exe xxx.if xxx.xtr your_output.txt
注意输入要是LF行尾, 不能是CRLF, 否则会报错unknown section
"""
# 这一行的import能够指定class的method返回自身类
# 参考链接:https://www.nuomiphp.com/eplan/11188.html
from __future__ import annotations
from typing import Dict, Generator, List
import xml.etree.ElementTree as ET

from .verifyta import Verifyta
from .utap import utap_parser


[docs]class SimTrace: """SimTrace is a class that represents a trace. """ def __init__(self, trace_string: str): """第i个state和第i个transition有相同的clock constraints 第i个transition前面跟第i个state, 详情看__str__() Args: trace_string (str): the raw trace string. parse_raw (bool, optional): whether parse the raw trace string to components. Defaults to True. """ self.__has_parse_raw: bool = False self.__raw: str = trace_string self.__states: List[List[str]] = None self.__global_variables: List[GlobalVar] = None self.__clock_constraints: List[ClockZone] = None self.__transitions: List[Transition] = None def __str__(self): """Convert the trace to string. Examples: >>> trace = SimTrace(trace_from_pedestrian) >>> str(trace) State [0]: ['Cars.Idle', 'TrafficLights.cRed_pGreen', 'LV1Pedestrian2.Idle'] global_variables [0]: [Cars.tCCrssMax=4; Cars.tCCrssMin=1; LV1Pedestrian2.tPCrssMin=0; LV1Pedestrian2.tPCrssMax=10; ] Clock_constraints [0]: [t(0) - tTL ≤ 0; t(0) - Cars.tc ≤ 0; t(0) - TrafficLights.tTL ≤ 0; t(0) - LV1Pedestrian2.tp ≤ 0; t(0) - LV1Pedestrian2.tTL ≤ 0; tTL - t(0) ≤ 55; tTL - Cars.tc ≤ 0; Cars.tc - TrafficLights.tTL ≤ 0; TrafficLights.tTL - LV1Pedestrian2.tp ≤ 0; LV1Pedestrian2.tp - LV1Pedestrian2.tTL ≤ 0; LV1Pedestrian2.tTL - tTL ≤ 0; ] transitions [0]: pWantCrss: LV1Pedestrian2 -> ['TrafficLights'] ----------------------------------- State [1]: ['Cars.Idle', 'TrafficLights._id8', 'LV1Pedestrian2.CheckTL'] global_variables [1]: [Cars.tCCrssMax=4; Cars.tCCrssMin=1; LV1Pedestrian2.tPCrssMin=0; LV1Pedestrian2.tPCrssMax=10; ] Clock_constraints [1]: [t(0) - tTL ≤ 0; t(0) - Cars.tc ≤ 0; t(0) - TrafficLights.tTL ≤ 0; t(0) - LV1Pedestrian2.tp ≤ 0; t(0) - LV1Pedestrian2.tTL ≤ 0; tTL - t(0) ≤ 55; tTL - Cars.tc ≤ 0; Cars.tc - TrafficLights.tTL ≤ 0; TrafficLights.tTL - LV1Pedestrian2.tp ≤ 0; LV1Pedestrian2.tp - LV1Pedestrian2.tTL ≤ 0; LV1Pedestrian2.tTL - tTL ≤ 0; ] transitions [1]: pGreen: TrafficLights -> ['LV1Pedestrian2'] ----------------------------------- State [2]: ['Cars.Idle', 'TrafficLights.cRed_pGreen', 'LV1Pedestrian2._id27'] global_variables [2]: [Cars.tCCrssMax=4; Cars.tCCrssMin=1; LV1Pedestrian2.tPCrssMin=0; LV1Pedestrian2.tPCrssMax=10; ] Clock_constraints [2]: [t(0) - tTL ≤ 0; t(0) - Cars.tc ≤ 0; t(0) - TrafficLights.tTL ≤ 0; t(0) - LV1Pedestrian2.tp ≤ 0; t(0) - LV1Pedestrian2.tTL ≤ 0; tTL - t(0) ≤ 55; tTL - Cars.tc ≤ 0; Cars.tc - TrafficLights.tTL ≤ 0; TrafficLights.tTL - LV1Pedestrian2.tp ≤ 0; LV1Pedestrian2.tp - LV1Pedestrian2.tTL ≤ 0; LV1Pedestrian2.tTL - tTL ≤ 0; ] Returns: str: the string representation of the trace. """ self.__parse_raw() res = '' for i, state_i in enumerate(self.__states): res += f'State [{i}]: {state_i}\n' if len(self.__global_variables[i].variables_value) > 0: res += f'global_variables [{i}]: {self.__global_variables[i]}\n' else: res += f'global_variables [{i}]: None\n' if len(self.__clock_constraints[i].clockzones) > 0: res += f'Clock_constraints [{i}]: {self.__clock_constraints[i]}\n' else: res += f'Clock_constraints [{i}]: None\n' if i < len(self.__states) - 1: res += f'transitions [{i}]: {self.__transitions[i]}\n' res += '-----------------------------------\n' return res def __repr__(self): return 'SimTrace(...)' def __len__(self): self.__parse_raw() return len(self.actions) def __eq__(self, other): return self.__str__() == other.__str__() def __getitem__(self, index: int | slice | List[int]) -> SimTrace: """Get the corresponding `SimTrace` by index. Args: index (int | slice): The indexs for slicing. Returns: SimTrace: The corresponding sliced SimTrace. """ self.__parse_raw() if isinstance(index, (int, slice)): new_trace: SimTrace = SimTrace("") new_trace.__has_parse_raw = True new_trace.__states = self.__states[index] new_trace.__clock_constraints = self.__clock_constraints[index] new_trace.__transitions = self.__transitions[index] new_trace.__global_variables = self.__global_variables[index] return new_trace else: new_trace: SimTrace = SimTrace("") new_trace.__has_parse_raw = True new_trace.__states = [self.__states[i] for i in index] new_trace.__clock_constraints = [ self.__clock_constraints[i] for i in index] new_trace.__transitions = [self.__transitions[i] for i in index] new_trace.__global_variables = [ self.__global_variables[i] for i in index] return new_trace @property def raw(self) -> str: """Original raw string of the trace. Examples: >>> trace = SimTrace(trace_from_pedestrian) >>> trace.raw State: Cars.Idle TrafficLights.cRed_pGreen LV1Pedestrian2.Idle Cars.tCCrssMax=4 Cars.tCCrssMin=1 LV1Pedestrian2.tPCrssMin=0 LV1Pedestrian2.tPCrssMax=10 t(0)-tTL<=0 t(0)-Cars.tc<=0 t(0)-TrafficLights.tTL<=0 t(0)-LV1Pedestrian2.tp<=0 t(0)-LV1Pedestrian2.tTL<=0 tTL-t(0)<=55 tTL-Cars.tc<=0 Cars.tc-TrafficLights.tTL<=0 TrafficLights.tTL-LV1Pedestrian2.tp<=0 LV1Pedestrian2.tp-LV1Pedestrian2.tTL<=0 LV1Pedestrian2.tTL-tTL<=0 Transition: LV1Pedestrian2.Idle -> LV1Pedestrian2.CheckTL {1; pWantCrss!; 1;} TrafficLights.cRed_pGreen -> TrafficLights._id8 {1; pWantCrss?; 1;} State: Cars.Idle TrafficLights._id8 LV1Pedestrian2.CheckTL Cars.tCCrssMax=4 Cars.tCCrssMin=1 LV1Pedestrian2.tPCrssMin=0 LV1Pedestrian2.tPCrssMax=10 t(0)-tTL<=0 t(0)-Cars.tc<=0 t(0)-TrafficLights.tTL<=0 t(0)-LV1Pedestrian2.tp<=0 t(0)-LV1Pedestrian2.tTL<=0 tTL-t(0)<=55 tTL-Cars.tc<=0 Cars.tc-TrafficLights.tTL<=0 TrafficLights.tTL-LV1Pedestrian2.tp<=0 LV1Pedestrian2.tp-LV1Pedestrian2.tTL<=0 LV1Pedestrian2.tTL-tTL<=0 Transition: TrafficLights._id8 -> TrafficLights.cRed_pGreen {1; pGreen!; 1;} LV1Pedestrian2.CheckTL -> LV1Pedestrian2._id27 {1; pGreen?; 1;} State: Cars.Idle TrafficLights.cRed_pGreen LV1Pedestrian2._id27 Cars.tCCrssMax=4 Cars.tCCrssMin=1 LV1Pedestrian2.tPCrssMin=0 LV1Pedestrian2.tPCrssMax=10 t(0)-tTL<=0 t(0)-Cars.tc<=0 t(0)-TrafficLights.tTL<=0 t(0)-LV1Pedestrian2.tp<=0 t(0)-LV1Pedestrian2.tTL<=0 tTL-t(0)<=55 tTL-Cars.tc<=0 Cars.tc-TrafficLights.tTL<=0 TrafficLights.tTL-LV1Pedestrian2.tp<=0 LV1Pedestrian2.tp-LV1Pedestrian2.tTL<=0 LV1Pedestrian2.tTL-tTL<=0 Transition: LV1Pedestrian2._id27 -> LV1Pedestrian2.Crossing {1; pCrss!; tp = 0;} Cars.Idle -> Cars.Idle {1; pCrss?; 1;} Returns: str: Original raw string of the trace. """ return self.__raw @property def states(self) -> List[List[str]]: self.__parse_raw() return self.__states @property def clock_constraints(self) -> List[ClockZone]: self.__parse_raw() return self.__clock_constraints @property def transitions(self) -> List[Transition]: self.__parse_raw() return self.__transitions # 这个就是get_untimed_pattern @property def actions(self) -> List[str]: self.__parse_raw() return [x.action for x in self.transitions if x.action is not None] @property def untime_pattern(self) -> List[str]: self.__parse_raw() return self.actions @property def global_variables(self) -> List[GlobalVar]: self.__parse_raw() return self.__global_variables
[docs] def filter_by_actions(self, focused_actions: List[str]) -> SimTrace: """Filter the transitions by actions. Args: focused_actions (List[str], optional): actions that you take cares of. Returns: SimTrace: The filtered SimTrace. """ self.__parse_raw() if focused_actions is None or self.raw is None: return self index_array = [i for i in range(len(self.transitions)) if self.transitions[i].action in focused_actions] return self[index_array]
def __parse_raw(self) -> None: """Parse raw string to components. """ if self.__has_parse_raw: return trace_text = self.__raw trace_text = trace_text.split('\n') clock_constraints, states, global_variables, transitions = [], [], [], [] for trace_text_i in trace_text: state_text, globalvar_name, globalvar_val, clockzones_text = [], [], [], [] if trace_text_i.startswith('State'): tmp_trace = trace_text_i[7:].strip().split(' ') for tmp_trace_i in tmp_trace: if ("=" in tmp_trace_i) and ('<' not in tmp_trace_i): # globalvariables var_name, var_val = tmp_trace_i.split('=') globalvar_name.append(var_name.strip()) globalvar_val.append(var_val.strip()) elif ('<' in tmp_trace_i) and ('-' in tmp_trace_i): # clockzones clocks, clk_bound = [ x.strip() for x in tmp_trace_i.split('<')] clock1, clock2 = [x.strip() for x in clocks.split('-')] if clk_bound[0] == '=': clk_bound = clk_bound[1:] is_equal = True else: is_equal = False clkzone = OneClockZone(clock1=clock1.strip(), clock2=clock2.strip(' '), is_equal=is_equal, bound_value=clk_bound.strip(' ')) clockzones_text.append(clkzone) else: # state state_text.append(tmp_trace_i.strip()) clock_constraints.append(ClockZone(clockzones_text)) states.append(state_text) global_variables.append( GlobalVar(globalvar_name, globalvar_val)) elif trace_text_i.startswith('Transition'): # Transition: PHisAAVFast.Retro -> PHisAAVFast._id15 {t >= tCondMin; actNode1!; t = 0;} NHisA.Rest -> NHisA._id7 {1; actNode?; 1;} # [PHisAAVFast.Retro -> PHisAAVFast._id15 {t >= tCondMin; actNode1!; t = 0; # NHisA.Rest -> NHisA._id7 {1; actNode?; 1; ''] tmp_trace = trace_text_i[12:].strip().split('}')[:-1] edges_list = [] end_process = [] sync_symbol = None start_process = None for tmp_trace_i in tmp_trace: # [NHisA.Rest -> NHisA._id7, # 1; actNode?; 1; trans_comp, edge_trans = tmp_trace_i.split('{') start_location, end_location = [ x.strip() for x in trans_comp.split('->')] guard, sync, update = [x.strip() for x in edge_trans.split(';')[:-1]] # strip tmp_edge = Edges(start_location, end_location, guard, sync, update) if tmp_edge.sync_type == 'send': start_process = tmp_edge.process sync_symbol = tmp_edge.sync_symbol elif tmp_edge.sync_type == 'receive': end_process.append(tmp_edge.process) else: start_process = tmp_edge.process end_process.append(tmp_edge.process) edges_list.append(tmp_edge) transitions.append(Transition(sync=sync_symbol, start_process=start_process, end_process=end_process, edges=edges_list)) else: pass self.__states = states self.__clock_constraints = clock_constraints self.__transitions = transitions self.__global_variables = global_variables self.__has_parse_raw = True
[docs] def save_raw(self, file_name: str) -> None: """Save raw data to file. Args: file_name (str): file name. """ with open(file_name, 'w', encoding='utf-8') as f: f.write(self.raw)
[docs] def save(self, file_name: str) -> None: """Save self.__str__() to file. Args: file_name (str): file name. """ with open(file_name, 'w', encoding='utf-8') as f: # 这里有parse_raw f.write(str(self))
[docs] def trim_transitions(self, model_path: str) -> None: """It reads the `.xml` file and gets the mapping from formal parameters to real parameters, then it replaces the formal parameters in the transitions with the real parameters. Args: model_path (str): The path of the `.xml` file. Raises: ValueError: When the `.xml` file is invalid. """ self.__parse_raw() element_tree_root: ET.Element | None = ET.ElementTree( file=model_path).getroot() if element_tree_root is None: # When the `.xml` file is invalid. raise ValueError("Invalid UPPAAL template file") # Get template parameters from .xml file templates: Generator[ET.Element, None, None] = element_tree_root.iterfind("template") param_map: Dict[str, List[str]] = dict() for template in templates: name_element: ET.Element | None = template.find("name") if name_element is None: # When the `.xml` file is invalid. raise ValueError("Invalid UPPAAL template file") name: str = name_element.text # uppaal guaranteed this to have only one param_elememt: ET.Element | None = template.find( "parameter") # uppaal guaranteed this to have only one if param_elememt is not None: def process_item(item): return item.strip().split(' ')[-1].replace('&', '') # param_elememt.text.split('//')[0].strip() 去掉注释 param_map[name] = [process_item(item) for item in param_elememt.text.split('//')[0].strip().split(',')] else: param_map[name] = [] # Get system components defination system_element: ET.Element | None = element_tree_root.find("system") if system_element is None: # When the `.xml` file is invalid. raise ValueError("Invalid UPPAAL template file") system_items: List[str] = list(filter(lambda line: len(line) > 0 and line.find("//") != 0 and line.count(' ') != len(line), # remove comment and empty line system_element.text.replace("\r\n", "\n").replace('\t', '') .split("\n")))[:-1] # unify "\n", remove "\t" and remove "system" source_map: Dict[str, Dict[str, str]] = dict() for item in system_items: name, constructor = item.replace(";", "").replace( " ", "").split('=') # remove ';' and ' ' left_brace_index: int = constructor.find('(') right_brace_index: int = constructor.find(')') # get the name of the constructor(template) constructor_name: str = constructor[:left_brace_index] # get corresponding param list real_param_list: List[str] = list(filter(lambda param: param != '', constructor[left_brace_index + 1: right_brace_index].split(','))) # get constructor param list form_param_list: List[str] = param_map[constructor_name] # ? I am not sure if default value is allowed in UPPAAL # When the `.xml` file is invalid. assert len(real_param_list) == len(form_param_list), "Invalid UPPAAL template file" if len(real_param_list) != 0: item_map: Dict[str, str] = dict() for i, real_param in enumerate(real_param_list): # map form param to real param item_map[form_param_list[i]] = real_param source_map[name] = item_map # print(source_map) for i, transition in enumerate(self.__transitions): if transition.sync is not None \ and transition.start_process in source_map.keys() \ and transition.sync in source_map[transition.start_process]: # If not in the keys, then it does not have a parameter, so we just skip it self.__transitions[i] = Transition(source_map[transition.start_process][transition.sync], transition.start_process, transition.end_process, transition.edges)
[docs]class OneClockZone: """ Represents a single clock zone constraint in a timed automaton. This class encapsulates a constraint between two clock variables, typically used in the context of timed automata. It specifies a relationship between two clocks and a bound on their difference. """ def __init__(self, clock1: str, clock2: str, is_equal: bool, bound_value: int): """clock1 - clock2 < or ≤ bound_value Args: clock1 (str): Name of the first clock variable. clock2 (str): Name of the second clock variable. is_equal (bool): Flag to indicate whether the constraint includes equality. bound_value (int): The bound value for the difference between the two clocks. """ self.__clock1: str = clock1 self.__clock2: str = clock2 self.__is_equal: bool = is_equal self.__bound_value: int = bound_value def __str__(self): sign = '≤' if self.is_equal else '<' res = f'{self.clock1} - {self.clock2} {sign} {self.bound_value}' return res def __repr__(self): return self.__str__() @property def clock1(self) -> str: """clock1 - clock2 < or ≤ bound_value Returns: str: Name of the first clock variable. """ return self.__clock1 @property def clock2(self) -> str: """clock1 - clock2 < or ≤ bound_value Returns: str: Name of the second clock variable. """ return self.__clock2 @property def is_equal(self) -> bool: """clock1 - clock2 < or ≤ bound_value Returns: bool: is_equal """ return self.__is_equal @property def bound_value(self) -> int: """clock1 - clock2 < or ≤ bound_value Returns: int: bound_value """ return self.__bound_value
[docs]class ClockZone: """Composed by `List` of `OneClockZone`. """ def __init__(self, clockzones: List[OneClockZone]): """Composed by `List` of `OneClockZone`. For the `.xtr` trace, each state contains several clock zones. Args: clockzones (List[OneClockZone]): A list of OneClockZone objects. """ self.__clockzones: List[OneClockZone] = clockzones def __str__(self): if self.clockzones: res = '[' for clock_zone in self.clockzones: res += f'{clock_zone.__str__()}; ' res += ']' return res def __repr__(self): return self.__str__() def __getitem__(self, index: int | slice | List[int]) -> ClockZone: if isinstance(index, int): return [self.__clockzones[index]] return ClockZone(self.__clockzones[index]) @property def clockzones(self) -> List[OneClockZone]: """`List` of `OneClockZone`. Returns: List[OneClockZone]: The list of OneClockZone objects. """ return self.__clockzones
[docs]class Edges: """`Edges` are components of `Transition`. One Transition contains at least one Edge. If more than 1 Edges are contained, it means that there is sync occurred. On this condition, Edges[0] is the sender(!) and others are reveivers(?). An Edge looks like this: process.start_location -> process.end_location: {guard, sync, update}. A Transition looks like this: Transition: LV1Pedestrian2.Idle -> LV1Pedestrian2.CheckTL {1; pWantCrss!; 1;} TrafficLights.cRed_pGreen -> TrafficLights._id8 {1; pWantCrss?; 1;} """ def __init__(self, start_location: str, end_location: str, guard: str, sync: str, update: str): """process.start_location -> process.end_location: {guard, sync, update} Args: start_location (str): The starting location (state) of the edge in the process. end_location (str): The ending location (state) of the edge in the process. guard (str): The condition that must be true for the transition to occur. sync (str): The synchronization action associated with the transition. update (str): The action to be executed when the transition occurs. """ self.__start_location: str = start_location self.__end_location: str = end_location self.__guard: str = guard self.__sync: str = sync self.__update: str = update # one edge has only one process self.__process: str = self.start_location.split('.')[0] def __str__(self): res = f'{self.process}.{self.start_location} -> {self.process}.{self.end_location}:' res = res + f'{{{self.guard};{self.sync};{self.update}}}' return res def __repr__(self): return self.__str__() @property def start_location(self) -> str: """Gets the start location (state) of the edge. Returns: str: The start location of the edge. """ return self.__start_location @property def end_location(self) -> str: """Gets the end location (state) of the edge. Returns: str: The end location of the edge. """ return self.__end_location @property def guard(self) -> str: """ Gets the guard condition of the edge. The guard condition must be true for the transition to occur. If the guard is '1', it implies the absence of a specific guard condition. Returns: str: The guard condition of the edge. """ return self.__guard @property def has_guard(self) -> bool: """ Determines if the edge has a guard condition. Returns: bool: True if the edge has a guard condition, False otherwise. """ return self.__guard != '1' @property def sync(self) -> str: """ Gets the synchronization action of the edge. The synchronization action is used for coordinating transitions between different processes. Returns: str: The synchronization action of the edge. """ return self.__sync @property def update(self) -> str: """ Gets the update action of the edge. The update action is executed when the transition occurs. Returns: str: The update action of the edge. """ return self.__update @property def process(self) -> str: """ Gets the process to which the edge belongs. Returns: str: The name of the process. """ return self.__process @property def is_sync(self) -> bool: """ Determines if the edge involves synchronization. Returns: bool: True if the edge involves synchronization, False otherwise. """ return '!' in self.__sync or '?' in self.__sync @property def sync_type(self) -> str: """ Gets the type of synchronization of the edge. The synchronization type can be 'send' (denoted by '!') or 'receive' (denoted by '?'). Returns: str: The synchronization type ('send', 'receive', or None if not applicable). """ if self.is_sync: return 'send' if '!' in self.__sync else 'receive' return None @property def sync_symbol(self) -> str: """ Gets the symbol used for synchronization in the edge. The symbol represents the event or message involved in the synchronization. Returns: str: The synchronization symbol, or None if synchronization is not applicable. """ if self.is_sync: return self.__sync[:-1] return None
[docs]class Transition: """One `Transition` contains at least one `Edge`. If more than 1 Edges are contained, it means that there is sync occurred. On this condition, Edges[0] is the sender(!) and others are reveivers(?). An Edge looks like this: process.start_location -> process.end_location: {guard, sync, update}. A Transition looks like this: Transition: LV1Pedestrian2.Idle -> LV1Pedestrian2.CheckTL {1; pWantCrss!; 1;} TrafficLights.cRed_pGreen -> TrafficLights._id8 {1; pWantCrss?; 1;} """ def __init__(self, sync: str, start_process: str, end_process: List[str], edges: List[Edges] = None): """ Initializes a new instance of the Transition class. Args: sync (str): The synchronization symbol for the transition. start_process (str): The name of the start process. end_process (List[str]): A list of names of the end processes. edges (List[Edges], optional): A list of Edges defining the transition. Defaults to None. """ self.__sync = sync self.__start_process = start_process self.__end_process = end_process self.__edges = edges or [] def __str__(self): """ Returns a string representation of the transition. Format: 'sync: start_process -> end_process; Edge details...' Returns: str: The string representation of the transition. """ res = f'{self.__sync}: {self.__start_process} -> {", ".join(self.__end_process)}; ' for edge in self.__edges: res += f'{edge.start_location} -> {edge.end_location}; ' return res.strip() def __repr__(self): """ Returns a formal string representation of the Transition object. Returns: str: Formal string representation of the object. """ return self.__str__() @property def sync(self) -> str: """ Gets the synchronization symbol of the transition. Returns: str: The synchronization symbol. """ return self.__sync @property def start_process(self) -> str: """ Gets the start process of the transition. Returns: str: The name of the start process. """ return self.__start_process @property def end_process(self) -> List[str]: """ Gets the end processes of the transition. Returns: List[str]: The names of the end processes. """ return self.__end_process @property def edges(self) -> List[Edges]: """ Gets the edges that make up the transition. Returns: List[Edges]: The list of edges. """ return self.__edges @property def action(self) -> str: """ Gets the action associated with the transition, typically the sync symbol. Returns: str: The action of the transition. """ return self.__sync
[docs]class GlobalVar: """ Represents global variables in a process model. Global variables are shared across different components of the model and can be used to store and manipulate data that is globally accessible. """ def __init__(self, variables_name: List[str], variables_value: List[float]): """ Initializes a new instance of the GlobalVar class. Args: variables_name (List[str]): A list of names for the global variables. variables_value (List[float]): A list of values for the global variables. """ self.variables_name = variables_name self.variables_value = variables_value def __str__(self): """ Returns a string representation of the global variables. Format: '[variable1=value1; variable2=value2; ...]' Returns: str: The string representation of the global variables. """ res = '[' for variable_name, variable_value in zip(self.variables_name, self.variables_value): res += f'{variable_name}={variable_value}; ' res = res.strip('; ') + ']' return res def __repr__(self): """ Returns a formal string representation of the GlobalVar object. Returns: str: Formal string representation of the object. """ return self.__str__()