def _burnish_all_division_parts(self, divisions, quintuplet): lefts, middles, rights, left_lengths, right_lengths=quintuplet lefts_index, rights_index = 0, 0 burnished_divisions = [] for division_index, division in enumerate(divisions): left_length = left_lengths[division_index] left = lefts[lefts_index:lefts_index+left_length] lefts_index += left_length right_length = right_lengths[division_index] right = rights[rights_index:rights_index+right_length] rights_index += right_length available_left_length = len(division) left_length = min([left_length, available_left_length]) available_right_length = len(division) - left_length right_length = min([right_length, available_right_length]) middle_length = len(division) - left_length - right_length left = left[:left_length] middle = middle_length * [middles[division_index]] right = right[:right_length] left_part, middle_part, right_part = \ sequencetools.partition_sequence_by_counts( division, [left_length, middle_length, right_length], cyclic=False, overhang=False, ) left_part = self._burnish_division_part(left_part, left) middle_part = self._burnish_division_part(middle_part, middle) right_part = self._burnish_division_part(right_part, right) burnished_division = left_part + middle_part + right_part burnished_divisions.append(burnished_division) unburnished_weights = [mathtools.weight(x) for x in divisions] burnished_weights = [mathtools.weight(x) for x in burnished_divisions] assert burnished_weights == unburnished_weights return burnished_divisions
def _make_numeric_map_part( self, numerator, prefix, suffix, is_note_filled=True, ): prefix_weight = mathtools.weight(prefix) suffix_weight = mathtools.weight(suffix) middle = numerator - prefix_weight - suffix_weight if numerator < prefix_weight: weights = [numerator] prefix = sequencetools.split_sequence( prefix, weights, cyclic=False, overhang=False)[0] middle = self._make_middle_of_numeric_map_part(middle) suffix_space = numerator - prefix_weight if suffix_space <= 0: suffix = () elif suffix_space < suffix_weight: weights = [suffix_space] suffix = sequencetools.split_sequence( suffix, weights, cyclic=False, overhang=False, )[0] numeric_map_part = prefix + middle + suffix return [durationtools.Duration(x) for x in numeric_map_part]
def split_sequence_extended_to_weights(sequence, weights, overhang=True): '''Split sequence extended to weights. .. container:: example **Example 1.** Split sequence extended to weights with overhang: :: >>> sequencetools.split_sequence_extended_to_weights( ... [1, 2, 3, 4, 5], [7, 7, 7], overhang=True) [[1, 2, 3, 1], [3, 4], [1, 1, 2, 3], [4, 5]] .. container:: example **Example 2.** Split sequence extended to weights without overhang: :: >>> sequencetools.split_sequence_extended_to_weights( ... [1, 2, 3, 4, 5], [7, 7, 7], overhang=False) [[1, 2, 3, 1], [3, 4], [1, 1, 2, 3]] Returns sequence of sequence objects. ''' from abjad.tools import sequencetools n = int(math.ceil(float(mathtools.weight(weights)) / mathtools.weight(sequence))) sequence = sequencetools.repeat_sequence_n_times(sequence, n) return sequencetools.split_sequence_by_weights(sequence, weights, cyclic=False, overhang=overhang)
def repeat_sequence_to_weight_at_most(sequence, weight): '''Repeat `sequence` to `weight` at most: :: >>> sequencetools.repeat_sequence_to_weight_at_most((5, -5, -5), 23) (5, -5, -5, 5) Returns newly constructed `sequence` object. ''' # check input assert isinstance(weight, numbers.Number) assert 0 <= weight # initialize result result = [sequence[0]] # iterate input i = 1 while mathtools.weight(result) < weight: result.append(sequence[i % len(sequence)]) i += 1 # remove overage if weight < mathtools.weight(result): result = result[:-1] # return result return type(sequence)(result)
def _make_numeric_map_part( self, numerator, prefix, suffix, is_note_filled=True, ): prefix_weight = mathtools.weight(prefix) suffix_weight = mathtools.weight(suffix) middle = numerator - prefix_weight - suffix_weight if numerator < prefix_weight: weights = [numerator] prefix = sequencetools.split_sequence(prefix, weights, cyclic=False, overhang=False)[0] middle = self._make_middle_of_numeric_map_part(middle) suffix_space = numerator - prefix_weight if suffix_space <= 0: suffix = () elif suffix_space < suffix_weight: weights = [suffix_space] suffix = sequencetools.split_sequence( suffix, weights, cyclic=False, overhang=False, )[0] numeric_map_part = prefix + middle + suffix return [durationtools.Duration(x) for x in numeric_map_part]
def _partition_sequence_cyclically_by_weights_at_least( sequence, weights, overhang=False, ): l_copy = sequence[:] result = [] current_part = [] target_weight_index = 0 len_weights = len(weights) while l_copy: target_weight = weights[target_weight_index % len_weights] x = l_copy.pop(0) current_part.append(x) if target_weight <= mathtools.weight(current_part): result.append(current_part) current_part = [] target_weight_index += 1 assert not l_copy if current_part: if overhang: result.append(current_part) return result
def _partition_sequence_once_by_weights_at_least( sequence, weights, overhang=False, ): result = [] current_part = [] l_copy = sequence[:] for num_weight, target_weight in enumerate(weights): while True: try: x = l_copy.pop(0) except IndexError: if num_weight + 1 == len(weights): if current_part: result.append(current_part) break raise PartitionError('too few elements in sequence.') current_part.append(x) if target_weight <= mathtools.weight(current_part): result.append(current_part) current_part = [] break if l_copy: if overhang: result.append(l_copy) return result
def _partition_sequence_once_by_weights_at_least( sequence, weights, overhang=False, ): result = [] current_part = [] l_copy = sequence[:] for num_weight, target_weight in enumerate(weights): while True: try: x = l_copy.pop(0) except IndexError: if num_weight + 1 == len(weights): if current_part: result.append(current_part) break message = 'too few elements in sequence.' raise Exception(message) current_part.append(x) if target_weight <= mathtools.weight(current_part): result.append(current_part) current_part = [] break if l_copy: if overhang: result.append(l_copy) return result
def truncate_sequence_to_weight(sequence, weight): '''Truncate `sequence` to `weight`: :: >>> l = [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10] >>> for x in range(10): ... print x, sequencetools.truncate_sequence_to_weight(l, x) ... 0 [] 1 [-1] 2 [-1, 1] 3 [-1, 2] 4 [-1, 2, -1] 5 [-1, 2, -2] 6 [-1, 2, -3] 7 [-1, 2, -3, 1] 8 [-1, 2, -3, 2] 9 [-1, 2, -3, 3] Returns empty list when `weight` is ``0``: :: >>> sequencetools.truncate_sequence_to_weight([1, 2, 3, 4, 5], 0) [] Raise type error when `sequence` is not a list. Raise value error on negative `weight`. Returns new list. ''' if not isinstance(sequence, list): raise TypeError if weight < 0: raise ValueError result = [] if weight == 0: return result accumulation = 0 for x in sequence: accumulation += abs(x) if accumulation < weight: result.append(x) else: sign = mathtools.sign(x) trimmed_part = weight - mathtools.weight(result) trimmed_part *= sign result.append(trimmed_part) break return result
def _burnish_each_division(class_, input_, divisions): left_classes = input_['left_classes'] middle_classes = input_['middle_classes'] right_classes = input_['right_classes'] left_counts = input_['left_counts'] left_counts = left_counts or datastructuretools.CyclicTuple([0]) right_counts = input_['right_counts'] right_counts = right_counts or datastructuretools.CyclicTuple([0]) lefts_index, rights_index = 0, 0 burnished_divisions = [] for division_index, division in enumerate(divisions): left_count = left_counts[division_index] left = left_classes[lefts_index:lefts_index + left_count] lefts_index += left_count right_count = right_counts[division_index] right = right_classes[rights_index:rights_index + right_count] rights_index += right_count available_left_count = len(division) left_count = min([left_count, available_left_count]) available_right_count = len(division) - left_count right_count = min([right_count, available_right_count]) middle_count = len(division) - left_count - right_count left = left[:left_count] if middle_classes: middle = middle_count * [middle_classes[division_index]] else: middle = middle_count * [0] right = right[:right_count] left_part, middle_part, right_part = \ sequencetools.partition_sequence_by_counts( division, [left_count, middle_count, right_count], cyclic=False, overhang=False, ) left_part = class_._burnish_division_part(left_part, left) middle_part = class_._burnish_division_part(middle_part, middle) right_part = class_._burnish_division_part(right_part, right) burnished_division = left_part + middle_part + right_part burnished_divisions.append(burnished_division) unburnished_weights = [mathtools.weight(x) for x in divisions] burnished_weights = [mathtools.weight(x) for x in burnished_divisions] assert burnished_weights == unburnished_weights return burnished_divisions
def _partition_sequence_once_by_weights_at_most( sequence, weights, overhang=False, ): l_copy = sequence[:] result = [] current_part = [] for target_weight in weights: while True: try: x = l_copy.pop(0) except IndexError: raise PartitionError('too few elements in sequence.') current_weight = mathtools.weight(current_part) candidate_weight = current_weight + mathtools.weight([x]) if candidate_weight < target_weight: current_part.append(x) elif candidate_weight == target_weight: current_part.append(x) result.append(current_part) current_part = [] break elif target_weight < candidate_weight: if current_part: result.append(current_part) current_part = [] l_copy.insert(0, x) break else: raise PartitionError('Elements in sequence too big.') else: raise ValueError('candidate and target weights must compare.') if overhang: left_over = current_part + l_copy if left_over: result.append(left_over) return result
def _partition_sequence_once_by_weights_at_most( sequence, weights, overhang=False, ): l_copy = sequence[:] result = [] current_part = [] for target_weight in weights: while True: try: x = l_copy.pop(0) except IndexError: message = 'too few elements in sequence.' raise Exception(message) current_weight = mathtools.weight(current_part) candidate_weight = current_weight + mathtools.weight([x]) if candidate_weight < target_weight: current_part.append(x) elif candidate_weight == target_weight: current_part.append(x) result.append(current_part) current_part = [] break elif target_weight < candidate_weight: if current_part: result.append(current_part) current_part = [] l_copy.insert(0, x) break else: message = 'elements in sequence too big.' raise Exception(message) else: message = 'candidate and target weights must compare.' raise ValueError(message) if overhang: left_over = current_part + l_copy if left_over: result.append(left_over) return result
def _partition_sequence_cyclically_by_weights_at_most( sequence, weights, overhang=False, ): result = [] current_part = [] current_target_weight_index = 0 current_target_weight = weights[current_target_weight_index] l_copy = sequence[:] while l_copy: current_target_weight = weights[current_target_weight_index % len(weights)] x = l_copy.pop(0) current_part_weight = mathtools.weight(current_part) candidate_part_weight = current_part_weight + mathtools.weight([x]) if candidate_part_weight < current_target_weight: current_part.append(x) elif candidate_part_weight == current_target_weight: current_part.append(x) result.append(current_part) current_part = [] current_target_weight_index += 1 elif current_target_weight < candidate_part_weight: if current_part: l_copy.insert(0, x) result.append(current_part) current_part = [] current_target_weight_index += 1 else: raise PartitionError('elements in sequence too big.') else: raise ValueError('candidate and target rates must compare.') if current_part: if overhang: result.append(current_part) return result
def _partition_sequence_cyclically_by_weights_at_most( sequence, weights, overhang=False, ): result = [] current_part = [] current_target_weight_index = 0 current_target_weight = weights[current_target_weight_index] l_copy = sequence[:] while l_copy: current_target_weight = \ weights[current_target_weight_index % len(weights)] x = l_copy.pop(0) current_part_weight = mathtools.weight(current_part) candidate_part_weight = current_part_weight + mathtools.weight([x]) if candidate_part_weight < current_target_weight: current_part.append(x) elif candidate_part_weight == current_target_weight: current_part.append(x) result.append(current_part) current_part = [] current_target_weight_index += 1 elif current_target_weight < candidate_part_weight: if current_part: l_copy.insert(0, x) result.append(current_part) current_part = [] current_target_weight_index += 1 else: message = 'elements in sequence too big.' raise Exception(message) else: message = 'candidate and target rates must compare.' raise ValueError(message) if current_part: if overhang: result.append(current_part) return result
def repeat_sequence_to_weight_exactly(sequence, weight): '''Repeat `sequence` to `weight` exactly: :: >>> sequencetools.repeat_sequence_to_weight_exactly((5, -5, -5), 23) (5, -5, -5, 5, -3) Returns newly constructed `sequence` object. ''' # check input assert isinstance(weight, numbers.Number) assert 0 <= weight # repeat sequence and find overage sequence_weight = mathtools.weight(sequence) complete_repetitions = int(math.ceil(float(weight) / float(sequence_weight))) result = list(sequence) result = complete_repetitions * result overage = complete_repetitions * sequence_weight - weight # remove overage from result for element in reversed(result): if 0 < overage: element_weight = abs(element) candidate_overage = overage - element_weight if 0 <= candidate_overage: overage = candidate_overage result.pop() else: absolute_amount_to_keep = element_weight - overage assert 0 < absolute_amount_to_keep signed_amount_to_keep = absolute_amount_to_keep signed_amount_to_keep *= mathtools.sign(element) result.pop() result.append(signed_amount_to_keep) break else: break # return result return type(sequence)(result)
def test_mathtools_weight_01(): r'''Weight of nonempty sequence. ''' assert mathtools.weight([-1, -2, 3, 4, 5]) == 15
def truncate_sequence(sequence, sum_=None, weight=None): '''Truncates `sequence`. :: >>> sequence = [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10] .. container:: example **Example 1.** Truncates sequence to weights ranging from 1 to 10: :: >>> for n in range(1, 11): ... result = sequencetools.truncate_sequence(sequence, weight=n) ... print(n, result) ... 1 [-1] 2 [-1, 1] 3 [-1, 2] 4 [-1, 2, -1] 5 [-1, 2, -2] 6 [-1, 2, -3] 7 [-1, 2, -3, 1] 8 [-1, 2, -3, 2] 9 [-1, 2, -3, 3] 10 [-1, 2, -3, 4] .. container:: example **Example 2.** Truncates sequence to sums ranging from 1 to 10: :: >>> for n in range(1, 11): ... result = sequencetools.truncate_sequence(sequence, sum_=n) ... print(n, result) ... 1 [-1, 2] 2 [-1, 2, -3, 4] 3 [-1, 2, -3, 4, -5, 6] 4 [-1, 2, -3, 4, -5, 6, -7, 8] 5 [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10] 6 [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10] 7 [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10] 8 [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10] 9 [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10] 10 [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10] .. container:: example **Example 3.** Truncates sequence to zero weight: :: >>> sequencetools.truncate_sequence(sequence, weight=0) [] .. container:: example **Example 4.** Truncates sequence to zero sum: :: >>> sequencetools.truncate_sequence(sequence, sum_=0) [] Ignores `sum_` when `weight` and `sum_` are both set. Raises type error when `sequence` is not a list. Raises value error on negative `sum_`. Returns new object of `sequence` type. ''' if not isinstance(sequence, collections.Sequence): message = 'must by sequence {!r}.' message = message.format(sequence) raise Exception(message) sequence_type = type(sequence) if weight is not None: if weight < 0: raise ValueError result = [] if 0 < weight: total = 0 for element in sequence: total += abs(element) if total < weight: result.append(element) else: sign = mathtools.sign(element) trimmed_part = weight - mathtools.weight(result) trimmed_part *= sign result.append(trimmed_part) break elif sum_ is not None: sum_ = sum_ if sum_ < 0: raise ValueError result = [] if 0 < sum_: total = 0 for element in sequence: total += element if total < sum_: result.append(element) else: result.append(sum_ - sum(result)) break result = sequence_type(result) return result
def repeat_sequence_to_weight(sequence, weight, allow_total=Exact): '''Repeats `sequence` to `weight`. .. container:: example **Example 1.** Repeats sequence to weight of 23 exactly: :: >>> sequencetools.repeat_sequence_to_weight((5, -5, -5), 23) (5, -5, -5, 5, -3) Truncates last element when necessary. .. container:: example **Example 2.** Repeats sequence to weight of 23 more: :: >>> sequencetools.repeat_sequence_to_weight( ... (5, -5, -5), ... 23, ... allow_total=More, ... ) (5, -5, -5, 5, -5) Does not truncate last element. .. container:: example **Example 3.** Repeats sequence to weight of 23 or less: :: >>> sequencetools.repeat_sequence_to_weight( ... (5, -5, -5), ... 23, ... allow_total=Less, ... ) (5, -5, -5, 5) Discards last element when necessary. Returns new object of `sequence` type. ''' if not isinstance(sequence, collections.Sequence): message = 'must by sequence {!r}.' message = message.format(sequence) raise Exception(message) sequence_type = type(sequence) # check input assert isinstance(weight, numbers.Number), repr(weight) assert 0 <= weight if allow_total == Exact: # repeat sequence and find overage sequence_weight = mathtools.weight(sequence) complete_repetitions = int( math.ceil(float(weight) / float(sequence_weight))) result = list(sequence) result = complete_repetitions * result overage = complete_repetitions * sequence_weight - weight # remove overage from result for element in reversed(result): if 0 < overage: element_weight = abs(element) candidate_overage = overage - element_weight if 0 <= candidate_overage: overage = candidate_overage result.pop() else: absolute_amount_to_keep = element_weight - overage assert 0 < absolute_amount_to_keep signed_amount_to_keep = absolute_amount_to_keep signed_amount_to_keep *= mathtools.sign(element) result.pop() result.append(signed_amount_to_keep) break else: break elif allow_total == Less: # initialize result result = [sequence[0]] # iterate input i = 1 while mathtools.weight(result) < weight: result.append(sequence[i % len(sequence)]) i += 1 # remove overage if weight < mathtools.weight(result): result = result[:-1] # return result return type(sequence)(result) elif allow_total == More: # initialize result result = [sequence[0]] # iterate input i = 1 while mathtools.weight(result) < weight: result.append(sequence[i % len(sequence)]) i += 1 # return result return type(sequence)(result) else: message = 'is not an ordinal value constant: {!r}.' message = message.format(allow_total) raise ValueError(message) # return result result = sequence_type(result) return result
def partition_sequence_by_backgrounded_weights(sequence, weights): r'''Partition `sequence` by backgrounded `weights`: :: >>> sequencetools.partition_sequence_by_backgrounded_weights( ... [-5, -15, -10], [20, 10]) [[-5, -15], [-10]] Further examples: :: >>> sequencetools.partition_sequence_by_backgrounded_weights( ... [-5, -15, -10], [5, 5, 5, 5, 5, 5]) [[-5], [-15], [], [], [-10], []] :: >>> sequencetools.partition_sequence_by_backgrounded_weights( ... [-5, -15, -10], [1, 29]) [[-5], [-15, -10]] :: >>> sequencetools.partition_sequence_by_backgrounded_weights( ... [-5, -15, -10], [2, 28]) [[-5], [-15, -10]] :: >>> sequencetools.partition_sequence_by_backgrounded_weights( ... [-5, -15, -10], [1, 1, 1, 1, 1, 25]) [[-5], [], [], [], [], [-15, -10]] The term `backgrounded` is a short-hand concocted specifically for this function; rely on the formal definition to understand the function actually does. Input constraint: the weight of `sequence` must equal the weight of `weights` exactly. The signs of the elements in `sequence` are ignored. Formal definition: partition `sequence` into `parts` such that (1.) the length of `parts` equals the length of `weights`; (2.) the elements in `sequence` appear in order in `parts`; and (3.) some final condition that is difficult to formalize. Notionally what's going on here is that the elements of `weights` are acting as a list of successive time intervals into which the elements of `sequence` are being fit in accordance with the start offset of each `sequence` element. The function models the grouping together of successive timespans according to which of an underlying sequence of time intervals it is in which each time span begins. Note that, for any input to this function, the flattened output of this function always equals `sequence` exactly. Note too that while `partition` is being used here in the sense of the other partitioning functions in the API, the distinguishing feature is this funciton is its ability to produce empty lists as output. Returns list of `sequence` objects. ''' from abjad.tools import sequencetools assert all(0 < x for x in weights) assert mathtools.weight(sequence) == mathtools.weight(weights) start_offsets = \ mathtools.cumulative_sums([abs(x) for x in sequence])[:-1] indicator = zip(start_offsets, sequence) result = [] for interval_start, interval_stop in \ mathtools.cumulative_sums_pairwise(weights): part = [] for pair in indicator[:]: if interval_start <= pair[0] < interval_stop: part.append(pair[1]) indicator.remove(pair) result.append(part) return result
def split_sequence(sequence, weights, cyclic=False, overhang=False): """Splits sequence by weights. .. container:: example **Example 1.** Split sequence cyclically by weights with overhang: :: >>> sequencetools.split_sequence( ... (10, -10, 10, -10), ... (3, 15, 3), ... cyclic=True, ... overhang=True, ... ) [(3,), (7, -8), (-2, 1), (3,), (6, -9), (-1,)] .. container:: example **Example 2.** Split sequence cyclically by weights without overhang: :: >>> sequencetools.split_sequence( ... (10, -10, 10, -10), ... (3, 15, 3), ... cyclic=True, ... overhang=False, ... ) [(3,), (7, -8), (-2, 1), (3,), (6, -9)] .. container:: example **Example 3.** Split sequence once by weights with overhang: :: >>> sequencetools.split_sequence( ... (10, -10, 10, -10), ... (3, 15, 3), ... cyclic=False, ... overhang=True, ... ) [(3,), (7, -8), (-2, 1), (9, -10)] .. container:: example **Example 4.** Split sequence once by weights without overhang: :: >>> sequencetools.split_sequence( ... (10, -10, 10, -10), ... (3, 15, 3), ... cyclic=False, ... overhang=False, ... ) [(3,), (7, -8), (-2, 1)] Returns list of sequence types. """ from abjad.tools import sequencetools result = [] current_index = 0 current_piece = [] if cyclic: weights = sequencetools.repeat_sequence_to_weight(weights, mathtools.weight(sequence), allow_total=Less) for weight in weights: current_piece_weight = mathtools.weight(current_piece) while current_piece_weight < weight: current_piece.append(sequence[current_index]) current_index += 1 current_piece_weight = mathtools.weight(current_piece) if current_piece_weight == weight: current_piece = type(sequence)(current_piece) result.append(current_piece) current_piece = [] elif weight < current_piece_weight: overage = current_piece_weight - weight current_last_element = current_piece.pop(-1) needed = abs(current_last_element) - overage needed *= mathtools.sign(current_last_element) current_piece.append(needed) current_piece = type(sequence)(current_piece) result.append(current_piece) overage *= mathtools.sign(current_last_element) current_piece = [overage] if overhang: last_piece = current_piece last_piece.extend(sequence[current_index:]) if last_piece: last_piece = type(sequence)(last_piece) result.append(last_piece) return result
def partition_sequence_by_counts( sequence, counts, cyclic=False, overhang=False, reversed_=False, ): r'''Partitions `sequence` by `counts`. .. container:: example **Example 1.** Partitions sequence once by counts without overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(10)), ... [3], ... cyclic=False, ... overhang=False, ... ) [[0, 1, 2]] **Example 2.** Partitions sequence once by counts without overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(16)), ... [4, 3], ... cyclic=False, ... overhang=False, ... ) [[0, 1, 2, 3], [4, 5, 6]] .. container:: example **Example 3.** Partitions sequence cyclically by counts without overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(10)), ... [3], ... cyclic=True, ... overhang=False, ... ) [[0, 1, 2], [3, 4, 5], [6, 7, 8]] **Example 4.** Partitions sequence cyclically by counts without overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(16)), ... [4, 3], ... cyclic=True, ... overhang=False, ... ) [[0, 1, 2, 3], [4, 5, 6], [7, 8, 9, 10], [11, 12, 13]] .. container:: example **Example 5.** Partitions sequence once by counts with overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(10)), ... [3], ... cyclic=False, ... overhang=True, ... ) [[0, 1, 2], [3, 4, 5, 6, 7, 8, 9]] **Example 6.** Partitions sequence once by counts with overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(16)), ... [4, 3], ... cyclic=False, ... overhang=True, ... ) [[0, 1, 2, 3], [4, 5, 6], [7, 8, 9, 10, 11, 12, 13, 14, 15]] .. container:: example **Example 7.** Partitions sequence cyclically by counts with overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(10)), ... [3], ... cyclic=True, ... overhang=True, ... ) [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] **Example 8.** Partitions sequence cyclically by counts with overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(16)), ... [4, 3], ... cyclic=True, ... overhang=True, ... ) [[0, 1, 2, 3], [4, 5, 6], [7, 8, 9, 10], [11, 12, 13], [14, 15]] .. container:: example **Example 9.** Partitions sequence once by counts and asserts that sequence partitions exactly (with no overhang): :: >>> sequencetools.partition_sequence_by_counts( ... list(range(10)), ... [2, 3, 5], ... cyclic=False, ... overhang=Exact, ... ) [[0, 1], [2, 3, 4], [5, 6, 7, 8, 9]] **Example 10.** Partitions sequence cyclically by counts and asserts that sequence partitions exactly (with no overhang): :: >>> sequencetools.partition_sequence_by_counts( ... list(range(10)), ... [2], ... cyclic=True, ... overhang=Exact, ... ) [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]] .. container:: example **Example 11.** Partitions list: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(10)), ... [3], ... cyclic=False, ... overhang=True, ... ) [[0, 1, 2], [3, 4, 5, 6, 7, 8, 9]] **Example 12.** Partitions tuple: :: >>> sequencetools.partition_sequence_by_counts( ... tuple(range(10)), ... [3], ... cyclic=False, ... overhang=True, ... ) [(0, 1, 2), (3, 4, 5, 6, 7, 8, 9)] **Example 13.** Partitions string: :: >>> sequencetools.partition_sequence_by_counts( ... 'some text', ... [3], ... cyclic=False, ... overhang=True, ... ) ['som', 'e text'] .. container:: example **Example 14.** Reverses cyclic partition with overhang: :: >>> sequencetools.partition_sequence_by_counts( ... [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], ... [3], ... cyclic=True, ... overhang=True, ... reversed_=True, ... ) [[0], [1, 2, 3], [4, 5, 6], [7, 8, 9]] **Example 15.** Reverses cyclic partition without overhang: :: >>> sequencetools.partition_sequence_by_counts( ... [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], ... [3], ... cyclic=True, ... overhang=False, ... reversed_=True, ... ) [[1, 2, 3], [4, 5, 6], [7, 8, 9]] **Example 16.** Reverses acyclic partition with overhang: :: >>> sequencetools.partition_sequence_by_counts( ... [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], ... [3], ... cyclic=False, ... overhang=True, ... reversed_=True, ... ) [[0, 1, 2, 3, 4, 5, 6], [7, 8, 9]] **Example 17.** Reverses acyclic partition without overhang: :: >>> sequencetools.partition_sequence_by_counts( ... [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], ... [3], ... cyclic=False, ... overhang=False, ... reversed_=True, ... ) [[7, 8, 9]] Returns list of objects with type equal to that of `sequence`. ''' from abjad.tools import sequencetools if not isinstance(sequence, collections.Sequence): message = 'must be sequence: {!r}.' message = message.format(sequence) raise TypeError(message) if not isinstance(counts, collections.Iterable): message = 'must be iterable: {!r}.' message = message.format(counts) raise TypeError(message) sequence_type = type(sequence) if reversed_: sequence = reversed(sequence) sequence = sequence_type(sequence) if overhang == Exact: result_with_overhang = partition_sequence_by_counts( sequence, counts, cyclic=cyclic, overhang=True, ) result_without_overhang = partition_sequence_by_counts( sequence, counts, cyclic=cyclic, overhang=False, ) if result_with_overhang == result_without_overhang: return result_without_overhang else: message = 'sequence does not partition exactly.' raise Exception(message) result = [] if cyclic: if overhang: counts = sequencetools.repeat_sequence_to_weight( counts, len(sequence), ) else: counts = sequencetools.repeat_sequence_to_weight( counts, len(sequence), allow_total=Less, ) elif overhang: weight_counts = mathtools.weight(counts) len_sequence = len(sequence) if weight_counts < len_sequence: counts = list(counts) counts.append(len(sequence) - weight_counts) for start, stop in mathtools.cumulative_sums_pairwise(counts): part = sequence[start:stop] result.append(part) if reversed_: result_ = [] for part in reversed(result): part_type = type(part) part = reversed(part) part = part_type(part) result_.append(part) result = result_ return result
def partition_integer_by_ratio(n, ratio): r'''Partitions positive integer-equivalent `n` by `ratio`. :: >>> mathtools.partition_integer_by_ratio(10, [1, 2]) [3, 7] Partitions positive integer-equivalent `n` by `ratio` with negative parts: :: >>> mathtools.partition_integer_by_ratio(10, [1, -2]) [3, -7] Partitions negative integer-equivalent `n` by `ratio`: :: >>> mathtools.partition_integer_by_ratio(-10, [1, 2]) [-3, -7] Partitions negative integer-equivalent `n` by `ratio` with negative parts: :: >>> mathtools.partition_integer_by_ratio(-10, [1, -2]) [-3, 7] Returns result with weight equal to absolute value of `n`. Raises type error on noninteger `n`. Returns list of integers. ''' from abjad.tools import mathtools if not mathtools.is_integer_equivalent_number(n): message = 'is not integer-equivalent number: {!r}.' message = message.format(n) raise TypeError(message) ratio = mathtools.Ratio(ratio).numbers if not all( mathtools.is_integer_equivalent_number(part) for part in ratio ): message = 'some parts in {!r} not integer-equivalent numbers.' message = message.format(ratio) raise TypeError(message) result = [0] divisions = [ float(abs(n)) * abs(part) / mathtools.weight(ratio) for part in ratio ] cumulative_divisions = mathtools.cumulative_sums(divisions, start=None) for division in cumulative_divisions: rounded_division = int(round(division)) - sum(result) #This makes rounding behave like python 2. Would be good to remove # in the long run if sys.version_info[0] == 3: if division - round(division) == 0.5: rounded_division += 1 result.append(rounded_division) result = result[1:] # adjust signs of output elements if mathtools.sign(n) == -1: result = [-x for x in result] ratio_signs = [mathtools.sign(x) for x in ratio] result = [pair[0] * pair[1] for pair in zip(ratio_signs, result)] # return result return result
def _burnish_first_and_last_division_parts(self, divisions, quintuplet): lefts, middles, rights, left_lengths, right_lengths = quintuplet burnished_divisions = [] left_length = left_lengths[0] left = lefts[:left_length] right_length = right_lengths[0] right = rights[:right_length] if len(divisions) == 1: available_left_length = len(divisions[0]) left_length = min([left_length, available_left_length]) available_right_length = len(divisions[0]) - left_length right_length = min([right_length, available_right_length]) middle_length = len(divisions[0]) - left_length - right_length left = left[:left_length] middle = middle_length * [middles[0]] right = right[:right_length] left_part, middle_part, right_part = \ sequencetools.partition_sequence_by_counts( divisions[0], [left_length, middle_length, right_length], cyclic=False, overhang=False) left_part = self._burnish_division_part(left_part, left) middle_part = self._burnish_division_part(middle_part, middle) right_part = self._burnish_division_part(right_part, right) burnished_division = left_part + middle_part + right_part burnished_divisions.append(burnished_division) else: # first division available_left_length = len(divisions[0]) left_length = min([left_length, available_left_length]) middle_length = len(divisions[0]) - left_length left = left[:left_length] middle = middle_length * [middles[0]] left_part, middle_part = \ sequencetools.partition_sequence_by_counts( divisions[0], [left_length, middle_length], cyclic=False, overhang=False) left_part = self._burnish_division_part(left_part, left) middle_part = self._burnish_division_part(middle_part, middle) burnished_division = left_part + middle_part burnished_divisions.append(burnished_division) # middle divisions for division in divisions[1:-1]: middle_part = division middle = len(division) * [middles[0]] middle_part = self._burnish_division_part(middle_part, middle) burnished_division = middle_part burnished_divisions.append(burnished_division) # last division: available_right_length = len(divisions[-1]) right_length = min([right_length, available_right_length]) middle_length = len(divisions[-1]) - right_length right = right[:right_length] middle = middle_length * [middles[0]] middle_part, right_part = \ sequencetools.partition_sequence_by_counts( divisions[-1], [middle_length, right_length], cyclic=False, overhang=False) middle_part = self._burnish_division_part(middle_part, middle) right_part = self._burnish_division_part(right_part, right) burnished_division = middle_part + right_part burnished_divisions.append(burnished_division) unburnished_weights = [mathtools.weight(x) for x in divisions] burnished_weights = [mathtools.weight(x) for x in burnished_divisions] assert burnished_weights == unburnished_weights return burnished_divisions
def partition_sequence_by_counts( sequence, counts, cyclic=False, overhang=False, copy_elements=False, ): r'''Partitions sequence by counts. .. container:: example **Example 1a.** Partition sequence once by counts without overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(10)), ... [3], ... cyclic=False, ... overhang=False, ... ) [[0, 1, 2]] .. container:: example **Example 1b.** Partition sequence once by counts without overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(16)), ... [4, 3], ... cyclic=False, ... overhang=False, ... ) [[0, 1, 2, 3], [4, 5, 6]] .. container:: example **Example 2a.** Partition sequence cyclically by counts without overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(10)), ... [3], ... cyclic=True, ... overhang=False, ... ) [[0, 1, 2], [3, 4, 5], [6, 7, 8]] .. container:: example **Example 2b.** Partition sequence cyclically by counts without overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(16)), ... [4, 3], ... cyclic=True, ... overhang=False, ... ) [[0, 1, 2, 3], [4, 5, 6], [7, 8, 9, 10], [11, 12, 13]] .. container:: example **Example 3a.** Partition sequence once by counts with overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(10)), ... [3], ... cyclic=False, ... overhang=True, ... ) [[0, 1, 2], [3, 4, 5, 6, 7, 8, 9]] .. container:: example **Example 3b.** Partition sequence once by counts with overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(16)), ... [4, 3], ... cyclic=False, ... overhang=True, ... ) [[0, 1, 2, 3], [4, 5, 6], [7, 8, 9, 10, 11, 12, 13, 14, 15]] .. container:: example **Example 4a.** Partition sequence cyclically by counts with overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(10)), ... [3], ... cyclic=True, ... overhang=True, ... ) [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] .. container:: example **Example 4b.** Partition sequence cyclically by counts with overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(16)), ... [4, 3], ... cyclic=True, ... overhang=True, ... ) [[0, 1, 2, 3], [4, 5, 6], [7, 8, 9, 10], [11, 12, 13], [14, 15]] Returns list of sequence objects. ''' from abjad.tools import sequencetools if not isinstance(counts, (tuple, list)): message = 'must be list or tuple: {!r}.' message = message.format(counts) raise TypeError(message) result = [] if cyclic: if overhang: counts = sequencetools.repeat_sequence_to_weight( counts, len(sequence), ) else: counts = sequencetools.repeat_sequence_to_weight( counts, len(sequence), allow_total=Less, ) elif overhang: weight_counts = mathtools.weight(counts) len_sequence = len(sequence) if weight_counts < len_sequence: counts = list(counts) counts.append(len(sequence) - weight_counts) for start, stop in mathtools.cumulative_sums_pairwise(counts): result.append(type(sequence)(sequence[start:stop])) return result
def truncate_sequence(sequence, sum_=None, weight=None): '''Truncates `sequence`. .. container:: example Example sequence: :: >>> sequence = [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10] .. container:: example Truncates sequence to weights ranging from 1 to 10: :: >>> for n in range(1, 11): ... result = sequencetools.truncate_sequence(sequence, weight=n) ... print(n, result) ... 1 [-1] 2 [-1, 1] 3 [-1, 2] 4 [-1, 2, -1] 5 [-1, 2, -2] 6 [-1, 2, -3] 7 [-1, 2, -3, 1] 8 [-1, 2, -3, 2] 9 [-1, 2, -3, 3] 10 [-1, 2, -3, 4] .. container:: example Truncates sequence to sums ranging from 1 to 10: :: >>> for n in range(1, 11): ... result = sequencetools.truncate_sequence(sequence, sum_=n) ... print(n, result) ... 1 [-1, 2] 2 [-1, 2, -3, 4] 3 [-1, 2, -3, 4, -5, 6] 4 [-1, 2, -3, 4, -5, 6, -7, 8] 5 [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10] 6 [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10] 7 [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10] 8 [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10] 9 [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10] 10 [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10] .. container:: example Truncates sequence to zero weight: :: >>> sequencetools.truncate_sequence(sequence, weight=0) [] .. container:: example Truncates sequence to zero sum: :: >>> sequencetools.truncate_sequence(sequence, sum_=0) [] Ignores `sum_` when `weight` and `sum_` are both set. Raises type error when `sequence` is not a list. Raises value error on negative `sum_`. Returns new object of `sequence` type. ''' if not isinstance(sequence, list): raise TypeError if weight is not None: if weight < 0: raise ValueError result = [] if 0 < weight: total = 0 for element in sequence: total += abs(element) if total < weight: result.append(element) else: sign = mathtools.sign(element) trimmed_part = weight - mathtools.weight(result) trimmed_part *= sign result.append(trimmed_part) break elif sum_ is not None: sum_ = sum_ if sum_ < 0: raise ValueError result = [] if 0 < sum_: total = 0 for element in sequence: total += element if total < sum_: result.append(element) else: result.append(sum_ - sum(result)) break result = type(sequence)(result) return result
def partition_sequence_by_counts( sequence, counts, cyclic=False, overhang=False, copy_elements=False, ): r'''Partitions sequence by counts. .. container:: example **Example 1a.** Partition sequence once by counts without overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(10)), ... [3], ... cyclic=False, ... overhang=False, ... ) [[0, 1, 2]] .. container:: example **Example 1b.** Partition sequence once by counts without overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(16)), ... [4, 3], ... cyclic=False, ... overhang=False, ... ) [[0, 1, 2, 3], [4, 5, 6]] .. container:: example **Example 2a.** Partition sequence cyclically by counts without overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(10)), ... [3], ... cyclic=True, ... overhang=False, ... ) [[0, 1, 2], [3, 4, 5], [6, 7, 8]] .. container:: example **Example 2b.** Partition sequence cyclically by counts without overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(16)), ... [4, 3], ... cyclic=True, ... overhang=False, ... ) [[0, 1, 2, 3], [4, 5, 6], [7, 8, 9, 10], [11, 12, 13]] .. container:: example **Example 3a.** Partition sequence once by counts with overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(10)), ... [3], ... cyclic=False, ... overhang=True, ... ) [[0, 1, 2], [3, 4, 5, 6, 7, 8, 9]] .. container:: example **Example 3b.** Partition sequence once by counts with overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(16)), ... [4, 3], ... cyclic=False, ... overhang=True, ... ) [[0, 1, 2, 3], [4, 5, 6], [7, 8, 9, 10, 11, 12, 13, 14, 15]] .. container:: example **Example 4a.** Partition sequence cyclically by counts with overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(10)), ... [3], ... cyclic=True, ... overhang=True, ... ) [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] .. container:: example **Example 4b.** Partition sequence cyclically by counts with overhang: :: >>> sequencetools.partition_sequence_by_counts( ... list(range(16)), ... [4, 3], ... cyclic=True, ... overhang=True, ... ) [[0, 1, 2, 3], [4, 5, 6], [7, 8, 9, 10], [11, 12, 13], [14, 15]] Returns list of sequence objects. ''' from abjad.tools import sequencetools result = [] if cyclic: if overhang: counts = sequencetools.repeat_sequence_to_weight( counts, len(sequence)) else: counts = sequencetools.repeat_sequence_to_weight( counts, len(sequence), allow_total=Less) elif overhang: weight_counts = mathtools.weight(counts) len_sequence = len(sequence) if weight_counts < len_sequence: counts = list(counts) counts.append(len(sequence) - weight_counts) for start, stop in mathtools.cumulative_sums_pairwise(counts): result.append(type(sequence)(sequence[start:stop])) return result
def _burnish_outer_divisions(class_, input_, divisions): for list_ in divisions: assert all(isinstance(_, int) for _ in list_), repr(list_) left_classes = input_['left_classes'] middle_classes = input_['middle_classes'] right_classes = input_['right_classes'] left_counts = input_['left_counts'] left_counts = left_counts or datastructuretools.CyclicTuple([0]) right_counts = input_['right_counts'] right_counts = right_counts or datastructuretools.CyclicTuple([0]) burnished_divisions = [] left_count = 0 if left_counts: left_count = left_counts[0] left = left_classes[:left_count] right_count = 0 if right_counts: right_count = right_counts[0] right = right_classes[:right_count] if len(divisions) == 1: available_left_count = len(divisions[0]) left_count = min([left_count, available_left_count]) available_right_count = len(divisions[0]) - left_count right_count = min([right_count, available_right_count]) middle_count = len(divisions[0]) - left_count - right_count left = left[:left_count] if not middle_classes: middle_classes = [1] middle = [middle_classes[0]] middle = middle_count * middle right = right[:right_count] left_part, middle_part, right_part = \ sequencetools.partition_sequence_by_counts( divisions[0], [left_count, middle_count, right_count], cyclic=False, overhang=Exact, ) left_part = class_._burnish_division_part(left_part, left) middle_part = class_._burnish_division_part(middle_part, middle) right_part = class_._burnish_division_part(right_part, right) burnished_division = left_part + middle_part + right_part burnished_divisions.append(burnished_division) else: # first division available_left_count = len(divisions[0]) left_count = min([left_count, available_left_count]) middle_count = len(divisions[0]) - left_count left = left[:left_count] if not middle_classes: middle_classes = [1] middle = [middle_classes[0]] middle = middle_count * middle left_part, middle_part = \ sequencetools.partition_sequence_by_counts( divisions[0], [left_count, middle_count], cyclic=False, overhang=Exact, ) left_part = class_._burnish_division_part(left_part, left) middle_part = class_._burnish_division_part(middle_part, middle) burnished_division = left_part + middle_part burnished_divisions.append(burnished_division) # middle divisions for division in divisions[1:-1]: middle_part = division middle = len(division) * [middle_classes[0]] middle_part = class_._burnish_division_part( middle_part, middle, ) burnished_division = middle_part burnished_divisions.append(burnished_division) # last division: available_right_count = len(divisions[-1]) right_count = min([right_count, available_right_count]) middle_count = len(divisions[-1]) - right_count right = right[:right_count] middle = middle_count * [middle_classes[0]] middle_part, right_part = \ sequencetools.partition_sequence_by_counts( divisions[-1], [middle_count, right_count], cyclic=False, overhang=Exact, ) middle_part = class_._burnish_division_part(middle_part, middle) right_part = class_._burnish_division_part(right_part, right) burnished_division = middle_part + right_part burnished_divisions.append(burnished_division) unburnished_weights = [mathtools.weight(x) for x in divisions] burnished_weights = [mathtools.weight(x) for x in burnished_divisions] #assert burnished_weights == unburnished_weights # TODO: make the following work on Python 3: #assert tuple(burnished_weights) == tuple(unburnished_weights) return burnished_divisions
def test_mathtools_weight_02(): r'''Weight of empty sequence. ''' assert mathtools.weight([]) == 0
def split_sequence(sequence, weights, cyclic=False, overhang=False): '''Splits sequence by weights. .. container:: example **Example 1.** Split sequence cyclically by weights with overhang: :: >>> sequencetools.split_sequence( ... (10, -10, 10, -10), ... (3, 15, 3), ... cyclic=True, ... overhang=True, ... ) [(3,), (7, -8), (-2, 1), (3,), (6, -9), (-1,)] .. container:: example **Example 2.** Split sequence cyclically by weights without overhang: :: >>> sequencetools.split_sequence( ... (10, -10, 10, -10), ... (3, 15, 3), ... cyclic=True, ... overhang=False, ... ) [(3,), (7, -8), (-2, 1), (3,), (6, -9)] .. container:: example **Example 3.** Split sequence once by weights with overhang: :: >>> sequencetools.split_sequence( ... (10, -10, 10, -10), ... (3, 15, 3), ... cyclic=False, ... overhang=True, ... ) [(3,), (7, -8), (-2, 1), (9, -10)] .. container:: example **Example 4.** Split sequence once by weights without overhang: :: >>> sequencetools.split_sequence( ... (10, -10, 10, -10), ... (3, 15, 3), ... cyclic=False, ... overhang=False, ... ) [(3,), (7, -8), (-2, 1)] Returns list of sequence types. ''' from abjad.tools import sequencetools result = [] current_index = 0 current_piece = [] if cyclic: weights = sequencetools.repeat_sequence_to_weight( weights, mathtools.weight(sequence), allow_total=Less) for weight in weights: current_piece_weight = mathtools.weight(current_piece) while current_piece_weight < weight: current_piece.append(sequence[current_index]) current_index += 1 current_piece_weight = mathtools.weight(current_piece) if current_piece_weight == weight: current_piece = type(sequence)(current_piece) result.append(current_piece) current_piece = [] elif weight < current_piece_weight: overage = current_piece_weight - weight current_last_element = current_piece.pop(-1) needed = abs(current_last_element) - overage needed *= mathtools.sign(current_last_element) current_piece.append(needed) current_piece = type(sequence)(current_piece) result.append(current_piece) overage *= mathtools.sign(current_last_element) current_piece = [overage] if overhang: last_piece = current_piece last_piece.extend(sequence[current_index:]) if last_piece: last_piece = type(sequence)(last_piece) result.append(last_piece) return result
def partition_sequence_by_ratio_of_weights(sequence, weights): '''Partitions `sequence` by ratio of `weights`. :: >>> sequencetools.partition_sequence_by_ratio_of_weights( ... [1] * 10, [1, 1, 1]) [[1, 1, 1], [1, 1, 1, 1], [1, 1, 1]] :: >>> sequencetools.partition_sequence_by_ratio_of_weights( ... [1] * 10, [1, 1, 1, 1]) [[1, 1, 1], [1, 1], [1, 1, 1], [1, 1]] :: >>> sequencetools.partition_sequence_by_ratio_of_weights( ... [1] * 10, [2, 2, 3]) [[1, 1, 1], [1, 1, 1], [1, 1, 1, 1]] :: >>> sequencetools.partition_sequence_by_ratio_of_weights( ... [1] * 10, [3, 2, 2]) [[1, 1, 1, 1], [1, 1, 1], [1, 1, 1]] :: >>> sequencetools.partition_sequence_by_ratio_of_weights( ... [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2], [1, 1]) [[1, 1, 1, 1, 1, 1, 2, 2], [2, 2, 2, 2]] :: >>> sequencetools.partition_sequence_by_ratio_of_weights( ... [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2], [1, 1, 1]) [[1, 1, 1, 1, 1, 1], [2, 2, 2], [2, 2, 2]] Weights of parts of returned list equal `weights_ratio` proportions with some rounding magic. Returns list of lists. ''' from abjad.tools import sequencetools if not isinstance(sequence, collections.Sequence): message = 'must be sequence: {!r}.' message = message.format(sequence) raise Exception(message) list_weight = mathtools.weight(sequence) weights_parts = mathtools.partition_integer_by_ratio(list_weight, weights) cumulative_weights = mathtools.cumulative_sums(weights_parts, start=None) result = [] sublist = [] result.append(sublist) current_cumulative_weight = cumulative_weights.pop(0) for n in sequence: if not isinstance(n, (int, float, fractions.Fraction)): message = 'must be number: {!r}.' message = message.format(n) raise TypeError(message) sublist.append(n) while current_cumulative_weight <= \ mathtools.weight(sequencetools.flatten_sequence(result)): try: current_cumulative_weight = cumulative_weights.pop(0) sublist = [] result.append(sublist) except IndexError: break return result
def partition_integer_by_ratio(n, ratio): r'''Partitions positive integer-equivalent `n` by `ratio`. :: >>> mathtools.partition_integer_by_ratio(10, [1, 2]) [3, 7] Partitions positive integer-equivalent `n` by `ratio` with negative parts: :: >>> mathtools.partition_integer_by_ratio(10, [1, -2]) [3, -7] Partitions negative integer-equivalent `n` by `ratio`: :: >>> mathtools.partition_integer_by_ratio(-10, [1, 2]) [-3, -7] Partitions negative integer-equivalent `n` by `ratio` with negative parts: :: >>> mathtools.partition_integer_by_ratio(-10, [1, -2]) [-3, 7] Returns result with weight equal to absolute value of `n`. Raises type error on noninteger `n`. Returns list of integers. ''' from abjad.tools import mathtools if not mathtools.is_integer_equivalent_number(n): message = 'is not integer-equivalent number: {!r}.' message = message.format(n) raise TypeError(message) ratio = mathtools.Ratio(ratio).numbers if not all(mathtools.is_integer_equivalent_number(part) for part in ratio): message = 'some parts in {!r} not integer-equivalent numbers.' message = message.format(ratio) raise TypeError(message) result = [0] divisions = [ float(abs(n)) * abs(part) / mathtools.weight(ratio) for part in ratio ] cumulative_divisions = mathtools.cumulative_sums(divisions, start=None) for division in cumulative_divisions: rounded_division = int(round(division)) - sum(result) #This makes rounding behave like python 2. Would be good to remove # in the long run if sys.version_info[0] == 3: if division - round(division) == 0.5: rounded_division += 1 result.append(rounded_division) result = result[1:] # adjust signs of output elements if mathtools.sign(n) == -1: result = [-x for x in result] ratio_signs = [mathtools.sign(x) for x in ratio] result = [pair[0] * pair[1] for pair in zip(ratio_signs, result)] # return result return result
def partition_sequence_by_ratio_of_weights(sequence, weights): '''Partitions `sequence` by ratio of `weights`. :: >>> sequencetools.partition_sequence_by_ratio_of_weights( ... [1] * 10, [1, 1, 1]) [[1, 1, 1], [1, 1, 1, 1], [1, 1, 1]] :: >>> sequencetools.partition_sequence_by_ratio_of_weights( ... [1] * 10, [1, 1, 1, 1]) [[1, 1, 1], [1, 1], [1, 1, 1], [1, 1]] :: >>> sequencetools.partition_sequence_by_ratio_of_weights( ... [1] * 10, [2, 2, 3]) [[1, 1, 1], [1, 1, 1], [1, 1, 1, 1]] :: >>> sequencetools.partition_sequence_by_ratio_of_weights( ... [1] * 10, [3, 2, 2]) [[1, 1, 1, 1], [1, 1, 1], [1, 1, 1]] :: >>> sequencetools.partition_sequence_by_ratio_of_weights( ... [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2], [1, 1]) [[1, 1, 1, 1, 1, 1, 2, 2], [2, 2, 2, 2]] :: >>> sequencetools.partition_sequence_by_ratio_of_weights( ... [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2], [1, 1, 1]) [[1, 1, 1, 1, 1, 1], [2, 2, 2], [2, 2, 2]] Weights of parts of returned list equal `weights_ratio` proportions with some rounding magic. Returns list of lists. ''' from abjad.tools import sequencetools list_weight = mathtools.weight(sequence) weights_parts = mathtools.partition_integer_by_ratio(list_weight, weights) cumulative_weights = mathtools.cumulative_sums(weights_parts, start=None) result = [] sublist = [] result.append(sublist) current_cumulative_weight = cumulative_weights.pop(0) for n in sequence: if not isinstance(n, (int, long, float, fractions.Fraction)): message = 'must be number: {!r}.' message = message.format(n) raise TypeError(message) sublist.append(n) while current_cumulative_weight <= \ mathtools.weight(sequencetools.flatten_sequence(result)): try: current_cumulative_weight = cumulative_weights.pop(0) sublist = [] result.append(sublist) except IndexError: break return result
def partition_integer_by_ratio(n, ratio): r'''Partitions positive integer-equivalent `n` by `ratio`. .. container:: example >>> abjad.mathtools.partition_integer_by_ratio(10, [1, 2]) [3, 7] .. container:: example Partitions positive integer-equivalent `n` by `ratio` with negative parts: >>> abjad.mathtools.partition_integer_by_ratio(10, [1, -2]) [3, -7] .. container:: example Partitions negative integer-equivalent `n` by `ratio`: >>> abjad.mathtools.partition_integer_by_ratio(-10, [1, 2]) [-3, -7] .. container:: example Partitions negative integer-equivalent `n` by `ratio` with negative parts: >>> abjad.mathtools.partition_integer_by_ratio(-10, [1, -2]) [-3, 7] .. container:: example More examples: >>> abjad.mathtools.partition_integer_by_ratio(10, [1]) [10] >>> abjad.mathtools.partition_integer_by_ratio(10, [1, 1]) [5, 5] >>> abjad.mathtools.partition_integer_by_ratio(10, [1, -1, -1]) [3, -4, -3] >>> abjad.mathtools.partition_integer_by_ratio(-10, [1, 1, 1, 1]) [-3, -2, -3, -2] >>> abjad.mathtools.partition_integer_by_ratio(-10, [1, 1, 1, 1, 1]) [-2, -2, -2, -2, -2] Returns result with weight equal to absolute value of `n`. Returns list of integers. ''' from abjad.tools import mathtools if not mathtools.is_integer_equivalent_number(n): message = 'is not integer-equivalent number: {!r}.' message = message.format(n) raise TypeError(message) ratio = mathtools.Ratio(ratio).numbers if not all(mathtools.is_integer_equivalent_number(part) for part in ratio): message = 'some parts in {!r} not integer-equivalent numbers.' message = message.format(ratio) raise TypeError(message) result = [0] divisions = [ float(abs(n)) * abs(part) / mathtools.weight(ratio) for part in ratio ] cumulative_divisions = mathtools.cumulative_sums(divisions, start=None) for division in cumulative_divisions: rounded_division = int(round(division)) - sum(result) if division - round(division) == 0.5: rounded_division += 1 result.append(rounded_division) result = result[1:] if mathtools.sign(n) == -1: result = [-x for x in result] ratio_signs = [mathtools.sign(x) for x in ratio] result = [pair[0] * pair[1] for pair in zip(ratio_signs, result)] return result
def split_sequence(sequence, weights, cyclic=False, overhang=False): '''Splits sequence by weights. .. container:: example **Example 1.** Splits sequence cyclically by weights with overhang: :: >>> sequencetools.split_sequence( ... (10, -10, 10, -10), ... (3, 15, 3), ... cyclic=True, ... overhang=True, ... ) ((3,), (7, -8), (-2, 1), (3,), (6, -9), (-1,)) .. container:: example **Example 2.** Splits sequence cyclically by weights without overhang: :: >>> sequencetools.split_sequence( ... (10, -10, 10, -10), ... (3, 15, 3), ... cyclic=True, ... overhang=False, ... ) ((3,), (7, -8), (-2, 1), (3,), (6, -9)) .. container:: example **Example 3.** Splits sequence once by weights with overhang: :: >>> sequencetools.split_sequence( ... (10, -10, 10, -10), ... (3, 15, 3), ... cyclic=False, ... overhang=True, ... ) ((3,), (7, -8), (-2, 1), (9, -10)) .. container:: example **Example 4.** Splits sequence once by weights without overhang: :: >>> sequencetools.split_sequence( ... (10, -10, 10, -10), ... (3, 15, 3), ... cyclic=False, ... overhang=False, ... ) ((3,), (7, -8), (-2, 1)) .. container:: example **Example 5.** Splits list once by weights without overhang: :: >>> sequencetools.split_sequence( ... [10, -10, 10, -10], ... (3, 15, 3), ... cyclic=False, ... overhang=False, ... ) [[3], [7, -8], [-2, 1]] Returns new object of `sequence` type with elements also of `sequence` type. ''' from abjad.tools import sequencetools if not isinstance(sequence, collections.Sequence): message = 'must by sequence {!r}.' message = message.format(sequence) raise Exception(message) sequence_type = type(sequence) result = [] current_index = 0 current_piece = [] if cyclic: weights = sequencetools.repeat_sequence_to_weight( weights, mathtools.weight(sequence), allow_total=Less, ) for weight in weights: current_piece_weight = mathtools.weight(current_piece) while current_piece_weight < weight: current_piece.append(sequence[current_index]) current_index += 1 current_piece_weight = mathtools.weight(current_piece) if current_piece_weight == weight: current_piece = type(sequence)(current_piece) result.append(current_piece) current_piece = [] elif weight < current_piece_weight: overage = current_piece_weight - weight current_last_element = current_piece.pop(-1) needed = abs(current_last_element) - overage needed *= mathtools.sign(current_last_element) current_piece.append(needed) current_piece = type(sequence)(current_piece) result.append(current_piece) overage *= mathtools.sign(current_last_element) current_piece = [overage] if overhang: last_piece = current_piece last_piece.extend(sequence[current_index:]) if last_piece: last_piece = type(sequence)(last_piece) result.append(last_piece) result = sequence_type(result) return result
def repeat_sequence_to_weight(sequence, weight, allow_total=Exact): '''Repeats `sequence` to `weight`. .. container:: example **Example 1.** Repeats sequence to weight of 23 exactly: :: >>> sequencetools.repeat_sequence_to_weight((5, -5, -5), 23) (5, -5, -5, 5, -3) Truncates last element when necessary. .. container:: example **Example 2.** Repeats sequence to weight of 23 more: :: >>> sequencetools.repeat_sequence_to_weight( ... (5, -5, -5), ... 23, ... allow_total=More, ... ) (5, -5, -5, 5, -5) Does not truncate last element. .. container:: example **Example 3.** Repeats sequence to weight of 23 or less: :: >>> sequencetools.repeat_sequence_to_weight( ... (5, -5, -5), ... 23, ... allow_total=Less, ... ) (5, -5, -5, 5) Discards last element when necessary. Returns new object of `sequence` type. ''' if not isinstance(sequence, collections.Sequence): message = 'must by sequence {!r}.' message = message.format(sequence) raise Exception(message) sequence_type = type(sequence) # check input assert isinstance(weight, numbers.Number), repr(weight) assert 0 <= weight if allow_total == Exact: # repeat sequence and find overage sequence_weight = mathtools.weight(sequence) complete_repetitions = int( math.ceil(float(weight) / float(sequence_weight)) ) result = list(sequence) result = complete_repetitions * result overage = complete_repetitions * sequence_weight - weight # remove overage from result for element in reversed(result): if 0 < overage: element_weight = abs(element) candidate_overage = overage - element_weight if 0 <= candidate_overage: overage = candidate_overage result.pop() else: absolute_amount_to_keep = element_weight - overage assert 0 < absolute_amount_to_keep signed_amount_to_keep = absolute_amount_to_keep signed_amount_to_keep *= mathtools.sign(element) result.pop() result.append(signed_amount_to_keep) break else: break elif allow_total == Less: # initialize result result = [sequence[0]] # iterate input i = 1 while mathtools.weight(result) < weight: result.append(sequence[i % len(sequence)]) i += 1 # remove overage if weight < mathtools.weight(result): result = result[:-1] # return result return type(sequence)(result) elif allow_total == More: # initialize result result = [sequence[0]] # iterate input i = 1 while mathtools.weight(result) < weight: result.append(sequence[i % len(sequence)]) i += 1 # return result return type(sequence)(result) else: message = 'is not an ordinal value constant: {!r}.' message = message.format(allow_total) raise ValueError(message) # return result result = sequence_type(result) return result