sensotwin.composite_layup
1import copy 2import math 3 4_SUBSCRIPT = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉") 5 6def _print_repeating_series(x: dict) -> str: 7 """Add subscript number to single element if applicable. 8 9 Args: 10 x: dict in the common analysis format as described in generate_composite_layup_description 11 12 Returns: 13 string description of repeating element 14 """ 15 return "{}{}".format(_create_layup_description(x["val"]), str(int(x["count"])).translate(_SUBSCRIPT) if x["count"] > 1 else "") 16 17 18def _print_symmetrical_series(x: dict) -> str: 19 """Add symmetrical string identifier to single element. 20 21 Args: 22 x: dict in the common analysis format as described in generate_composite_layup_description 23 24 Returns: 25 string description of symmetrical element 26 """ 27 return "{}{}".format(_create_layup_description(x["val"]), "s") 28 29 30def _stringlist(list: list) -> str: 31 """Generate string representation of number list without any separator""" 32 return "".join(map(str, list)) 33 34 35def _check_if_number(x): 36 """Check if passed argument can be cast to a number""" 37 try: 38 float(x) 39 return True 40 except (ValueError, TypeError): 41 return False 42 43 44def _search_for_symmetrical_series(data: list) -> list: 45 """Search for any symmetrical occurrence of numbers. 46 47 symmetrical search takes priority over other types of repetition and thus only has to handle dict objects 48 unlike later steps in the process 49 example of symmetrical numbers: [1, 2, 3, 4, 4, 3, 2, 1], the double middle value is necessary 50 51 Args: 52 data: list of dicts in the common analysis format as described in generate_composite_layup_description 53 54 Returns: 55 modified list of dicts with symmetrical dicts inserted 56 """ 57 sequence = copy.deepcopy(data) 58 current_pass_length = math.floor(len(sequence) / 2) 59 while current_pass_length > 1: 60 i = 0 61 while i + 2 * current_pass_length <= len(sequence): 62 first_slice = [x["val"] for x in sequence[i:i+current_pass_length]] 63 second_slice = [x["val"] for x in sequence[i+current_pass_length:i+2*current_pass_length]] 64 if first_slice == second_slice[::-1] and len(set(first_slice)) > 1: 65 sequence[i]["val"] = _search_for_symmetrical_series(sequence[i:i+current_pass_length]) 66 sequence[i]["desc"] = "symmetrical" 67 for _ in range(current_pass_length*2-1): 68 sequence.pop(i+1) 69 i += 1 70 current_pass_length -= 1 71 return sequence 72 73 74def _search_for_repeating_series(data: dict|list|int) -> dict|list|int: 75 """Search for any repeating occurence of numbers. 76 77 repeating series are a second level pattern and the search function thus has to correctly handle common 78 analysis dicts, lists of common analysis dicts and raw numbers as input data 79 example of repeating numbers: [1, 2, 3, 4, 1, 2, 3, 4] 80 81 Args: 82 data: data structure to recursively check for repeating angle sequences 83 84 Returns: 85 modified input data with series of repeating numbers minimised 86 """ 87 sequence = copy.deepcopy(data) 88 if isinstance(sequence, list): 89 current_pass_length = math.floor(len(sequence) / 2) 90 while current_pass_length > 0: 91 i = 0 92 while i + 2 * current_pass_length <= len(sequence): 93 first_slice = [x["val"] for x in sequence[i:i+current_pass_length]] 94 second_slice = [x["val"] for x in sequence[i+current_pass_length:i+2*current_pass_length]] 95 if first_slice == second_slice: 96 # recursive algorithm starting from maximum length does not properly parse uneven number of repetitions 97 # extra check is performed in this case 98 uneven_correction = 0 99 if i + 3 * current_pass_length <= len(sequence): 100 third_slice = [x["val"] for x in sequence[i+2*current_pass_length:i+3*current_pass_length]] 101 if first_slice == third_slice: 102 uneven_correction = 1 103 sequence[i]["val"] = _search_for_repeating_series(sequence[i:i+current_pass_length]) 104 # check for extra repeats of object in subobject and add them together with parent if necessary 105 # algorithm creates stacked repetitions of 2 when there is an even number of repetitions >= 4 106 additional_counts = 0 107 if isinstance(sequence[i]["val"], list) and len(sequence[i]["val"]) == 1 and sequence[i]["val"][0]["desc"] == "repeat": 108 additional_counts = sequence[i]["val"][0]["count"] 109 sequence[i]["val"] = copy.deepcopy(sequence[i]["val"][0]["val"]) 110 sequence[i]["desc"] = "repeat" 111 sequence[i]["count"] += 1 + additional_counts + uneven_correction 112 for _ in range( (current_pass_length+uneven_correction) * 2 - 1): 113 sequence.pop(i+1) 114 i += 1 115 current_pass_length -= 1 116 for i in range(len(sequence)): 117 if (isinstance(sequence[i]["val"], list) or isinstance(sequence[i]["val"], dict)): 118 sequence[i]["val"] = _search_for_repeating_series(sequence[i]["val"]) 119 elif isinstance(sequence, dict) and (isinstance(sequence["val"], list) or isinstance(sequence["val"], dict)): 120 sequence["val"] = _search_for_repeating_series(sequence["val"]) 121 return sequence 122 123 124def _search_for_inverted_series(data: dict|list|int) -> dict|list|int: 125 """Search for any inverted occurence of numbers. 126 127 inverted series are a second level pattern and the search function thus has to correctly handle common 128 analysis dicts, lists of common analysis dicts and raw numbers as input data 129 example of repeating numbers: [-1, 1] or [1, -1] 130 131 Args: 132 data: data structure to recursively check for inverted angle sequences 133 134 Returns: 135 modified input data with series of inverted numbers minimised 136 """ 137 sequence = copy.deepcopy(data) 138 if isinstance(sequence, list): 139 current_position = 0 140 while current_position < len(sequence): 141 if current_position < len(sequence) - 1 and _check_if_number(sequence[current_position+1]["val"]) and sequence[current_position]["val"] == -sequence[current_position+1]["val"]: 142 sequence[current_position]["val"] = "{}{}".format("±" if sequence[current_position]["val"] >= 0 else "∓", abs(sequence[current_position]["val"])) 143 sequence.pop(current_position+1) 144 elif isinstance(sequence[current_position]["val"], dict) or isinstance(sequence[current_position]["val"], list): 145 sequence[current_position]["val"] = _search_for_inverted_series(sequence[current_position]["val"]) 146 current_position += 1 147 else: 148 current_position += 1 149 elif isinstance(sequence, dict) and (isinstance(sequence["val"], list) or isinstance(sequence["val"], dict)): 150 sequence["val"] = _search_for_inverted_series(sequence["val"]) 151 return sequence 152 153 154def _reduction_chain(input): 155 """Perform sequence reduction steps in specific order 156 157 Args: 158 input: list of dicts in the common analysis format as described in generate_composite_layup_description 159 160 Returns: 161 minimized sequence information 162 """ 163 return \ 164 _search_for_repeating_series( 165 _search_for_inverted_series( 166 _search_for_repeating_series( 167 _search_for_symmetrical_series( 168 input 169 ) 170 ) 171 ) 172 ) 173 174 175def _reduce_sequence(sequence: dict) -> dict: 176 """Perform reduction steps until no more minimisation is possible. 177 178 Args: 179 sequence: dict in the common analysis format as described in generate_composite_layup_description 180 181 Returns: 182 reduced input dict 183 """ 184 old_sequence = copy.deepcopy(sequence) 185 new_sequence = _reduction_chain(copy.deepcopy(sequence)) 186 while new_sequence != old_sequence: 187 for i in range(len(new_sequence)): 188 if isinstance(new_sequence[i]["val"],list): 189 new_sequence[i] = _reduce_sequence(new_sequence[i]) 190 old_sequence = copy.deepcopy(new_sequence) 191 new_sequence = _reduction_chain(copy.deepcopy(old_sequence)) 192 return copy.deepcopy(new_sequence) 193 194 195def _create_layup_description(data: dict) -> str: 196 """Create final string description from analysed series data. 197 198 Args: 199 data: stacked dicts in the common analysis format as described in generate_composite_layup_description 200 201 Returns: 202 string representation of analysed series data 203 """ 204 result = "{}" 205 if isinstance(data, list): 206 if len(data) > 1: 207 result = "[{}]" 208 object_strings = [] 209 for i in range(len(data)): 210 if data[i]["desc"] == "repeat": 211 object_strings.append(_print_repeating_series(data[i])) 212 elif data[i]["desc"] == "symmetrical": 213 object_strings.append(_print_symmetrical_series(data[i])) 214 else: 215 object_strings.append(_create_layup_description(data[i]["val"])) 216 object_string = "|".join(object_strings) 217 elif isinstance(data, dict): 218 if data["desc"] == "repeat": 219 object_string = _print_repeating_series(data) 220 elif data["desc"] == "symmetrical": 221 object_string = _print_symmetrical_series(data) 222 else: 223 object_string = str(data["val"]) 224 else: 225 object_string = data 226 return result.format(object_string) 227 228 229def generate_composite_layup_description(series: list) -> str: 230 """Generate description string for given series of composite material alignment angles. 231 232 the common analysis dict format of this algorithm: 233 dict = { 234 'desc': None|'repeat'|'symmetrical', 235 'val': int|list(int)|dict, 236 'count': int 237 } 238 239 Args: 240 series: ordered list of integer angles of composite material layer alignments 241 242 Returns: 243 string representation of repeating angle pattern 244 """ 245 total_string = _stringlist(series) 246 total_repeats = 1 247 repeating_sequence = None 248 # find number of total repeats 249 for i in range(1, len(series)+1): 250 repeating_sequence = series[:i] 251 current_search = _stringlist(series[:i]) 252 if len(total_string.replace(current_search, "")) == 0: 253 total_repeats = len(total_string) / len(current_search) 254 break 255 # set top level object 256 sequence_objects = [{ 257 "desc": None if total_repeats == 1 else "repeat", 258 "val": [], 259 "count": total_repeats 260 }] 261 for series in repeating_sequence: 262 sequence_objects[0]["val"].append( 263 { 264 "desc": None, 265 "val": series, 266 "count": 1 267 } 268 ) 269 # recursively reduce repeating sequence 270 sequence_objects[0]["val"] = _reduce_sequence(sequence_objects[0]["val"]) 271 return _create_layup_description(sequence_objects)
def
generate_composite_layup_description(series: list) -> str:
230def generate_composite_layup_description(series: list) -> str: 231 """Generate description string for given series of composite material alignment angles. 232 233 the common analysis dict format of this algorithm: 234 dict = { 235 'desc': None|'repeat'|'symmetrical', 236 'val': int|list(int)|dict, 237 'count': int 238 } 239 240 Args: 241 series: ordered list of integer angles of composite material layer alignments 242 243 Returns: 244 string representation of repeating angle pattern 245 """ 246 total_string = _stringlist(series) 247 total_repeats = 1 248 repeating_sequence = None 249 # find number of total repeats 250 for i in range(1, len(series)+1): 251 repeating_sequence = series[:i] 252 current_search = _stringlist(series[:i]) 253 if len(total_string.replace(current_search, "")) == 0: 254 total_repeats = len(total_string) / len(current_search) 255 break 256 # set top level object 257 sequence_objects = [{ 258 "desc": None if total_repeats == 1 else "repeat", 259 "val": [], 260 "count": total_repeats 261 }] 262 for series in repeating_sequence: 263 sequence_objects[0]["val"].append( 264 { 265 "desc": None, 266 "val": series, 267 "count": 1 268 } 269 ) 270 # recursively reduce repeating sequence 271 sequence_objects[0]["val"] = _reduce_sequence(sequence_objects[0]["val"]) 272 return _create_layup_description(sequence_objects)
Generate description string for given series of composite material alignment angles.
the common analysis dict format of this algorithm: dict = { 'desc': None|'repeat'|'symmetrical', 'val': int|list(int)|dict, 'count': int }
Args: series: ordered list of integer angles of composite material layer alignments
Returns: string representation of repeating angle pattern