1# Copyright 2022 Huawei Technologies Co., Ltd 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# ============================================================================ 15"""Unique name producer for target, name of node, class name, etc.""" 16 17from typing import Union, Tuple 18 19from ..node import Node 20from ..api.node_type import NodeType 21 22 23class Namer: 24 """ 25 Used for unique identity in a class-scope. current used for target of construct-function. 26 Namer records times of name been used, and add prefix to origin name for unique name. For example, when a Namer 27 record "name1" has been used 10 times, when a new request require a unique name base on 'name1', namer will respond 28 "name1_10" as unique name. 29 """ 30 31 def __init__(self): 32 """Constructor of Namer.""" 33 self._names: {str: int} = {} 34 35 @staticmethod 36 def _real_name(name: str) -> Tuple[str, int]: 37 """ 38 Find real name. For example, "name1" is the real name of "name1_10", "name1" is the real name of "name1_10_3". 39 If not find real name before find unique name, unique name may be not unique. For example: 40 41 1. "name1" has been used 10 times, which means "name1", "name1_2", "name1_3" ... "name1_10" has been used; 42 2. new request require a unique name base on 'name1_5' 43 3. If namer not find real name of "name1_5", namer will find that "name1_5" is never used and respond 44 "name1_5" as unique name which is used before, actually. 45 46 Args: 47 name (str): Origin name which may have digit prefix. 48 49 Returns: 50 A string represents real-name and a int represents suffix. 51 """ 52 if name == '_': 53 return name, None 54 pos = name.rfind("_") 55 if pos == -1 or pos == len(name) - 1: 56 return name, None 57 digit = True 58 for i in range(pos + 1, len(name)): 59 if not name[i].isdigit(): 60 digit = False 61 break 62 if digit: 63 return name[:pos], int(name[pos + 1:]) 64 return name, None 65 66 def get_name(self, origin_name: str) -> str: 67 """ 68 Get unique name from 'origin_name'. 69 70 Args: 71 origin_name (str): Origin name which may be duplicated. 72 73 Returns: 74 A string represents unique-name. 75 """ 76 if origin_name == '_': 77 return origin_name 78 real_name, suffix_idx = Namer._real_name(origin_name) 79 name = origin_name 80 number = self._names.get(name) 81 if number is None: 82 self._names[name] = 1 83 if not suffix_idx: 84 # When _names is {x:2} and origin_name is y, 85 # origin_name is not in _names and can be returned. 86 return name 87 if suffix_idx and not self._names.get(real_name, -1) > suffix_idx: 88 # When _names is {x:2} and origin_name is x_3, 89 # return x_3 and update _names to {x:2, x_3:1} 90 return name 91 # When _names is {x:2} and origin_name is x_1, 92 # set new_name to x_1_1 by set number to 1, and continue to update name. 93 number = 1 94 while True: 95 new_name = f"{name}_{number}" 96 number += 1 97 self._names[name] = number 98 # When _names is {x:2, x_3:1}, origin_name is x and number is update to 3, 99 # new_name x_3 is conflict with key x_3, so this new_name need to be skipped. 100 if new_name in self._names.keys(): 101 continue 102 return new_name 103 104 def add_name(self, name: str): 105 """ 106 Add a name to Namer which should be unique. 107 108 Args: 109 name (str): A name should be unique in current namer. 110 """ 111 if self._names.get(name) is None: 112 self._names[name] = 1 113 114 115class TargetNamer(Namer): 116 """ 117 Used for unique-ing targets of node. 118 """ 119 def get_unique_name(self, origin_name: str) -> str: 120 """ 121 Get unique name from 'origin_name'. 122 123 Args: 124 origin_name (str): Origin name which may be duplicated. 125 126 Returns: 127 A string represents unique-name. 128 """ 129 return super(TargetNamer, self).get_name(origin_name) 130 131 132class NodeNamer(Namer): 133 """ 134 Used for unique-ing node-name which is also used as field of init-function and key of global_vars 135 """ 136 137 def get_name(self, node_or_name: Union[Node, str]) -> str: 138 """ 139 Override get_name in Namer class. 140 Get unique node_name from 'origin_name' or an instance of node. 141 142 Args: 143 node_or_name (Union[Node, str]): A string represents candidate node_name or an instance of node who require 144 A unique node_name. 145 146 Returns: 147 A string represents unique node_name. 148 """ 149 if isinstance(node_or_name, Node): 150 origin_name = node_or_name.get_name() 151 if origin_name is None or not origin_name: 152 if node_or_name.get_node_type() in (NodeType.CallCell, NodeType.CallPrimitive, NodeType.CallFunction, 153 NodeType.Tree): 154 origin_name = type(node_or_name.get_instance()).__name__ 155 elif node_or_name.get_node_type() == NodeType.Python: 156 if node_or_name.get_instance(): 157 origin_name = type(node_or_name.get_instance()).__name__ 158 else: 159 origin_name = "python_node" 160 elif node_or_name.get_node_type() == NodeType.Input: 161 origin_name = "parameter" 162 elif node_or_name.get_node_type() == NodeType.Output: 163 origin_name = "return" 164 elif node_or_name.get_node_type() == NodeType.MathOps: 165 origin_name = "math_ops" 166 else: 167 origin_name = str(node_or_name.get_node_type()) 168 elif isinstance(node_or_name, str): 169 if not node_or_name: 170 raise ValueError("input node_name is empty.") 171 origin_name = node_or_name 172 else: 173 raise ValueError("unexpected type of node_or_name:", type(node_or_name)) 174 return super(NodeNamer, self).get_name(origin_name) 175 176 177class ClassNamer(Namer): 178 """ 179 Used for unique-ing class name in a network. 180 181 Class name should be unique in a network, in other word, in a Rewrite process. So please do not invoke constructor 182 of `ClassNamer` and call `instance()` of `ClassNamer` to obtain singleton of ClassNamer. 183 """ 184 185 def __init__(self): 186 super().__init__() 187 self._prefix = "Opt" 188 189 @classmethod 190 def instance(cls): 191 """ 192 Class method of `ClassNamer` for singleton of `ClassNamer`. 193 194 Returns: 195 An instance of `ClassNamer` as singleton of `ClassNamer`. 196 """ 197 198 if not hasattr(ClassNamer, "_instance"): 199 ClassNamer._instance = ClassNamer() 200 return ClassNamer._instance 201 202 def get_name(self, origin_name: str) -> str: 203 """ 204 Unique input `origin_name`. 205 206 Args: 207 origin_name (str): A string represents original class name. 208 209 Returns: 210 A string represents a unique class name generated from `origin_name`. 211 """ 212 213 return super(ClassNamer, self).get_name(origin_name + self._prefix) 214 215 def add_name(self, name: str): 216 """ 217 Declare a `name` so that other class can not apply this `name` anymore. 218 219 Args: 220 name (str): A string represents a class name. 221 """ 222 223 super(ClassNamer, self).add_name(name + self._prefix) 224 225class FunctionNamer(Namer): 226 """ 227 Used for unique-ing function name in a network. 228 229 Function name should be unique in a network, in other word, in a Rewrite process. So please do not invoke 230 constructor of `FunctionNamer` and call `instance()` of `FunctionNamer` to obtain singleton of FunctionNamer. 231 """ 232 233 def __init__(self): 234 super().__init__() 235 self._prefix = "" 236 237 @classmethod 238 def instance(cls): 239 """ 240 Class method of `FunctionNamer` for singleton of `FunctionNamer`. 241 242 Returns: 243 An instance of `FunctionNamer` as singleton of `FunctionNamer`. 244 """ 245 246 if not hasattr(FunctionNamer, "_instance"): 247 FunctionNamer._instance = FunctionNamer() 248 return FunctionNamer._instance 249 250 def get_name(self, origin_name: str) -> str: 251 """ 252 Unique input `origin_name`. 253 254 Args: 255 origin_name (str): A string represents original function name. 256 257 Returns: 258 A string represents a unique function name generated from `origin_name`. 259 """ 260 261 return super(FunctionNamer, self).get_name(origin_name + self._prefix) 262 263 def add_name(self, name: str): 264 """ 265 Declare a `name` so that other function can not apply this `name` anymore. 266 267 Args: 268 name (str): A string represents a function name. 269 """ 270 271 super(FunctionNamer, self).add_name(name + self._prefix) 272