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