diff --git a/polytopes/pattern_manip.py b/polytopes/pattern_manip.py index 947d62d2ef223ac601526c44dcc8f01c067933a0..05c41ff158a93c195d33c589973cba214d14815a 100644 --- a/polytopes/pattern_manip.py +++ b/polytopes/pattern_manip.py @@ -32,7 +32,7 @@ import polytopes.data_manipulation as dm import polytopes.pattern_factory as pf # %% Element indexation -def get_index_of_element(element, pattern): +def get_index_from_element(element, pattern): """ Return the index of a specific element 'element' in an indexed pattern. @@ -115,7 +115,7 @@ def recursively_index_element(element, pattern): else: raise err.ElementNotFound("Element {} not found in the pattern {}.".format(element, pattern)) -def get_element_with_index(index_element, pattern, with_tuples = False): +def get_element_from_index(index_element, pattern, with_tuples = False): """ Return the element in the pattern, from its index. @@ -301,10 +301,10 @@ def get_antecedents_from_element(elt, pattern): List of the antecedents, as integers. """ - idx_elt = get_index_of_element(elt, pattern) - return get_antecedents_from_idx(idx_elt, pattern) + idx_elt = get_index_from_element(elt, pattern) + return get_antecedents_from_index(idx_elt, pattern) -def get_antecedents_from_idx(idx_elt, pattern): +def get_antecedents_from_index(idx_elt, pattern): """ Return the antecedents (as elements) of this element (as index) in this indexed pattern. @@ -321,17 +321,17 @@ def get_antecedents_from_idx(idx_elt, pattern): List of the antecedents, as integers. """ - antecedents = get_antecedents_idx_from_idx(idx_elt) + antecedents = get_antecedents_index_from_index(idx_elt) if antecedents == []: return [] to_return = [] for i in antecedents: - ant = get_element_with_index(i, pattern, with_tuples = False) + ant = get_element_from_index(i, pattern, with_tuples = False) if ant != None: to_return.append(ant) return to_return -def get_antecedents_idx_from_idx(idx_elt): +def get_antecedents_index_from_index(idx_elt): """ Return the antecedents (as indexes) of this element (as index). @@ -355,7 +355,7 @@ def get_antecedents_idx_from_idx(idx_elt): if type(idx[-1]) is tuple: if idx[-1][1] == 0: idx[-1] = idx[-1][0] - return get_antecedents_idx_from_idx(idx) + return get_antecedents_index_from_index(idx) else: # # Without other addition as homologuous # idx[-1] = idx[-1][0] @@ -377,7 +377,7 @@ def get_antecedents_idx_from_idx(idx_elt): return antecedents ######## Pivot related to antecedents -def get_pivot_from_idx(elt_idx, ant_idx, pattern): +def get_pivot_from_index(elt_idx, ant_idx, pattern): """ Returns the pivot (as element) of this element (elt_idx, as index) in relation with this antecedent (ant_idx, as index) in the pattern. @@ -397,10 +397,10 @@ def get_pivot_from_idx(elt_idx, ant_idx, pattern): The pivot, as element (integer). """ - pivot_idx = get_pivot_idx_from_idx(elt_idx, ant_idx) - return get_element_with_index(pivot_idx, pattern, with_tuples = False) + pivot_idx = get_pivot_index_from_index(elt_idx, ant_idx) + return get_element_from_index(pivot_idx, pattern, with_tuples = False) -def get_pivot_idx_from_idx(elt_idx, ant_idx): +def get_pivot_index_from_index(elt_idx, ant_idx): """ Returns the pivot (as index) of this element (elt_idx, as index) in relation with this antecedent (ant_idx, as index). @@ -438,7 +438,7 @@ def get_pivot_idx_from_idx(elt_idx, ant_idx): pivot_idx = [elt_idx[i] - ant_idx[i] for i in range(len(ant_idx))] return pivot_idx -def get_antecedents_with_pivots_from_idx(elt_idx, pattern): +def get_antecedents_with_pivots_from_index(elt_idx, pattern): """ Return a list of tuples (of integers), each tuple corresponding to a couple antecedents/pivot in relation with this antecedent (as elements). @@ -455,22 +455,22 @@ def get_antecedents_with_pivots_from_idx(elt_idx, pattern): Couples (antecedents, pivot) for this element. """ - antecedents_idx = get_antecedents_idx_from_idx(elt_idx) + antecedents_idx = get_antecedents_index_from_index(elt_idx) if antecedents_idx == []: return [] else: this_elt_ant = [] for ant_idx in antecedents_idx: - ant = get_element_with_index(ant_idx, pattern, with_tuples = False) + ant = get_element_from_index(ant_idx, pattern, with_tuples = False) if ant != None: if ant == 0: this_elt_ant.append((0,0)) else: - pivot = get_pivot_from_idx(elt_idx, ant_idx, pattern) + pivot = get_pivot_from_index(elt_idx, ant_idx, pattern) this_elt_ant.append((ant, pivot)) return this_elt_ant -def get_global_antecedents_with_pivots_from_idx(elt_idx, pattern): +def get_global_antecedents_with_pivots_from_index(elt_idx, pattern): """ Return a list of tuples (of integers), each tuple corresponding to a couple GLOBAL antecedents/pivot in relation with this antecedent (as elements). @@ -492,7 +492,7 @@ def get_global_antecedents_with_pivots_from_idx(elt_idx, pattern): Couples (global antecedents, pivot) for this element. """ - antecedents_idx = get_antecedents_idx_from_idx(elt_idx) + antecedents_idx = get_antecedents_index_from_index(elt_idx) if antecedents_idx == []: return [] elif [0 for i in range(len(elt_idx))] in antecedents_idx: @@ -500,16 +500,16 @@ def get_global_antecedents_with_pivots_from_idx(elt_idx, pattern): for idx in antecedents_idx: # Antecedents of the antecedents and etc (the ``append'' is taken in account in the for loop, so it searches for the antecedents of the added antecedents) if idx != [0 for i in range(len(elt_idx))]: - for ant_ant in get_antecedents_idx_from_idx(idx): + for ant_ant in get_antecedents_index_from_index(idx): if ant_ant not in antecedents_idx: antecedents_idx.append(ant_ant) else: this_elt_ant = [] for ant_idx in antecedents_idx: - ant = get_element_with_index(ant_idx, pattern, with_tuples = False) + ant = get_element_from_index(ant_idx, pattern, with_tuples = False) if ant != None and ant != 0: try: - pivot = get_pivot_from_idx(elt_idx, ant_idx, pattern) + pivot = get_pivot_from_index(elt_idx, ant_idx, pattern) if (pivot, ant) not in this_elt_ant: this_elt_ant.append((ant, pivot)) except NotImplementedError: @@ -534,10 +534,10 @@ def get_successors_from_element(elt, pattern): The successors of this element (as elements). """ - idx_elt = get_index_of_element(elt, pattern) - return get_successors_from_idx(idx_elt, pattern) + idx_elt = get_index_from_element(elt, pattern) + return get_successors_from_index(idx_elt, pattern) -def get_successors_from_idx(idx_elt, pattern): +def get_successors_from_index(idx_elt, pattern): """ Return the successors (as elements) of this element (as index), subject to this pattern (indexed). @@ -554,17 +554,17 @@ def get_successors_from_idx(idx_elt, pattern): The successors of this element (as elements). """ - successors = get_successors_idx_from_idx(idx_elt) + successors = get_successors_index_from_index(idx_elt) if successors == []: return [] to_return = [] for i in successors: - suc = get_element_with_index(i, pattern, with_tuples = False) + suc = get_element_from_index(i, pattern, with_tuples = False) if suc != None: to_return.append(suc) return to_return -def get_successors_idx_from_idx(idx_elt): +def get_successors_index_from_index(idx_elt): """ Return the successors (as indexes) of this element (as index). @@ -590,7 +590,7 @@ def get_successors_idx_from_idx(idx_elt): if type(idx[-1]) is tuple: if idx[-1][1] == 0: idx[-1] = idx[-1][0] - successors = get_successors_idx_from_idx(idx) + successors = get_successors_index_from_index(idx) idx[-1] = (idx[-1], 1) successors.append(idx) return successors @@ -611,7 +611,7 @@ def get_successors_idx_from_idx(idx_elt): successors.append(new_idx) return successors -def get_global_successors_from_idx(idx_elt, pattern): +def get_global_successors_from_index(idx_elt, pattern): """ Return the global successors (as elements) of this element (as index). @@ -634,11 +634,11 @@ def get_global_successors_from_idx(idx_elt, pattern): The successors of this element (as element). """ - successors = get_successors_idx_from_idx(idx_elt) # Direct successors + successors = get_successors_index_from_index(idx_elt) # Direct successors if successors == []: return [] for idx in successors: # Successors of the successors and etc (the ``append'' is taken in account in the for loop, so it searches for the successors of the added successors) - for suc_suc in get_successors_idx_from_idx(idx): + for suc_suc in get_successors_index_from_index(idx): if suc_suc not in successors: if type(suc_suc[-1]) is not tuple or suc_suc[-1][-1] != 1: successors.append(suc_suc) @@ -647,7 +647,7 @@ def get_global_successors_from_idx(idx_elt, pattern): to_return = [] for i in successors: - suc = get_element_with_index(i, pattern, with_tuples = False) + suc = get_element_from_index(i, pattern, with_tuples = False) if suc != None: to_return.append(suc) return to_return @@ -710,8 +710,8 @@ def get_under_primers(pattern): """ under_primers = [] for i in range(1, pf.get_pattern_size(pattern)): - idx_elt = get_index_of_element(i, pattern) - if 0 in get_antecedents_from_idx(idx_elt, pattern): + idx_elt = get_index_from_element(i, pattern) + if 0 in get_antecedents_from_index(idx_elt, pattern): under_primers.append(idx_elt) return under_primers @@ -815,8 +815,8 @@ def ppp_dim_1_pattern(first_elt_idx, up_idx, pattern): A PPP of 1 dimension, composed of 'first_elt_idx' and its successor, determined by the relation between the primer and 'up_idx'. """ - first_elt = get_element_with_index(first_elt_idx, pattern, with_tuples = True) - snd_elt = get_element_with_index(add_indexes(first_elt_idx, up_idx), pattern, with_tuples = True) + first_elt = get_element_from_index(first_elt_idx, pattern, with_tuples = True) + snd_elt = get_element_from_index(add_indexes(first_elt_idx, up_idx), pattern, with_tuples = True) return [first_elt, snd_elt] def swap_chord_sequence(chord_sequence, permuted_elements_list): @@ -892,7 +892,7 @@ def recursively_find_direct_antecedent(i, indexed_pattern): The antecedent, or the three elements to construct a system with (as the antecedent will be the fictive element determined by this system). """ - elt_idx = get_index_of_element(i, indexed_pattern) + elt_idx = get_index_from_element(i, indexed_pattern) if type(elt_idx[-1]) is tuple: if elt_idx[-1][1] == 1: return i-1 # Previous element, on which it is attached @@ -929,14 +929,14 @@ def recursively_find_direct_antecedent(i, indexed_pattern): primers_pattern = find_primers_of_low_level_systems(indexed_pattern) return recursively_find_direct_antecedent(i, primers_pattern) else: - return get_element_with_index(elt_minus_up_two_idx, indexed_pattern) + return get_element_from_index(elt_minus_up_two_idx, indexed_pattern) else: if -1 in elt_minus_up_two_idx: - return get_element_with_index(elt_minus_up_one_idx, indexed_pattern) + return get_element_from_index(elt_minus_up_one_idx, indexed_pattern) else: local_primer_idx = [elt_idx[i] - up_one[i] - up_two[i] for i in range(pattern_dim)] - local_primer = get_element_with_index(local_primer_idx, indexed_pattern) # a_1 - elt_minus_up_one = get_element_with_index(elt_minus_up_one_idx, indexed_pattern) # a_2 - elt_minus_up_two = get_element_with_index(elt_minus_up_two_idx, indexed_pattern) # a_3 + local_primer = get_element_from_index(local_primer_idx, indexed_pattern) # a_1 + elt_minus_up_one = get_element_from_index(elt_minus_up_one_idx, indexed_pattern) # a_2 + elt_minus_up_two = get_element_from_index(elt_minus_up_two_idx, indexed_pattern) # a_3 return (local_primer, elt_minus_up_one, elt_minus_up_two) diff --git a/polytopes/polytopical_costs.py b/polytopes/polytopical_costs.py index 19e124f16bebc06a6b725bf421cd6c2d12fde64f..dc3e97bb3a3caab28ca1ab37501db4fe9032b7e6 100644 --- a/polytopes/polytopical_costs.py +++ b/polytopes/polytopical_costs.py @@ -3,7 +3,14 @@ Created on Tue Nov 26 14:11:11 2019 @author: amarmore + +File containing cost function for polytopes, in the different paradigms. +In general, cost functions are defined to be computed in a loop (dynamic minimization algorithm), +and are coded such that they don't have to recompute pattern at each iteration. +In that sense, they're not defined (or at least, made user-friendly) for computing a cost on a single pattern. +TODO: maybe define such functions. """ + import math import numpy as np @@ -13,14 +20,6 @@ import polytopes.pattern_manip as pm import polytopes.pattern_factory as pf import polytopes.model.errors as err -""" -File containing cost function for polytopes, in the different paradigms. -In general, cost functions are defined to be computed in a loop (dynamic minimization algorithm), -and are coded such that they don't have to recompute pattern at each iteration. -In that sense, they're not defined (or at least, made user-friendly) for computing a cost on a single pattern. -TODO: maybe define such functions. -""" - # %% Louboutin (and more generally, high-level S&C) pardigm def louboutin_cost_for_a_ppp(segment, a_ppp, pattern_of_ones, reindex, current_min = math.inf): """ @@ -420,6 +419,60 @@ def voice_leading_cost(first_chord, second_chord, measure = mvt.l1_norm, triadic """ return measure(mvt.triadic_mvt_chords(first_chord, second_chord, chromatic = chromatic)) + +def best_louboutin_cost_segment(segment, irregularity_penalty = 0, target_size = 32, segment_size_penalty = 0): + """ + Compute the optimal cost in the C. Guichaoua's paradigm, for this chord_sequence, among all possible patterns. + + Disclaimer: This function should be used for tests/demonstration ONLY, as it computes patterns and antecedents/successors when called. + Prefer guichaoua_cost() for optimization at a music piece scale. + + Parameters + ---------- + segment : list of Chords, in any form + The segment, on which to compute the cost. + positive_penalty, negative_penalty : float/integer, optional + Penalty parameter related to irregularities of the polytope. + Positive corresponds to the penalty when the polytope contains addition, negative is for deletion. + They are constants and not function of the size of irregularities. + positive_segment_size_penalty, negative_segment_size_penalty : float/integer, optional + Penalty parameter to multiply to the raw penalty score for the size of the segment. + positive_segment_size_penalty is the parameter when size exceeds 'target_size', negative is for size shorter than 'target_size'. + target_size : integer, optional + The optimal size, used for the penalty related to the segment size. + The default is 32. + + Returns + ------- + this_segment_cost : integer + Optimal cost for this segment in the C. Guichaoua's paradigm. + best_pattern : nested list of integers (indexed pattern) + The pattern, resulting in the optimal score. + + """ + this_bag = sh.compute_patterns_and_ppp_for_size(len(segment)) + this_segment_cost = math.inf + + if this_bag == []: + return this_segment_cost + + for a_pattern in this_bag: + this_polytope_cost = math.inf + for i in range(len(a_pattern[0])): + this_ppp_cost = louboutin_cost_for_a_ppp(segment, a_pattern[0][i], a_pattern[3][i], a_pattern[4][i], current_min = this_segment_cost) + if this_ppp_cost < this_polytope_cost: + this_polytope_cost = this_ppp_cost + best_ppp = a_pattern[0][i] + + this_polytope_cost += irregularities_penalty_guichaoua(adding_code = a_pattern[1], deleting_code = a_pattern[2], positive_penalty = irregularity_penalty, negative_penalty = irregularity_penalty) + + if this_polytope_cost < this_segment_cost: + this_segment_cost = this_polytope_cost + best_pattern = best_ppp + + this_segment_cost += sh.penalty_cost_guichaoua(len(segment), target_size = target_size, positive_segment_size_penalty = segment_size_penalty, negative_segment_size_penalty = segment_size_penalty) + return this_segment_cost, best_pattern + # %% Guichaoua paradigm def guichaoua_cost(chord_sequence, indexed_pattern, antecedents_with_pivots, successors, correct_antecedents, current_min = math.inf): """ @@ -607,6 +660,55 @@ def guichaoua_cost_global_antecedents_successors(chord_sequence, indexed_pattern correct_antecedents[this_elt_successor] = new_correct_antecedents return score +def best_guichaoua_cost_segment(segment, positive_penalty = 0, negative_penalty = 0, target_size = 32, + positive_segment_size_penalty = 0, negative_segment_size_penalty = 0): + """ + Compute the optimal cost in the C. Guichaoua's paradigm, for this chord_sequence, among all possible patterns. + + Disclaimer: This function should be used for tests/demonstration ONLY, as it computes patterns and antecedents/successors when called. + Prefer guichaoua_cost() for optimization at a music piece scale. + + Parameters + ---------- + segment : list of Chords, in any form + The segment, on which to compute the cost. + positive_penalty, negative_penalty : float/integer, optional + Penalty parameter related to irregularities of the polytope. + Positive corresponds to the penalty when the polytope contains addition, negative is for deletion. + They are constants and not function of the size of irregularities. + positive_segment_size_penalty, negative_segment_size_penalty : float/integer, optional + Penalty parameter to multiply to the raw penalty score for the size of the segment. + positive_segment_size_penalty is the parameter when size exceeds 'target_size', negative is for size shorter than 'target_size'. + target_size : integer, optional + The optimal size, used for the penalty related to the segment size. + The default is 32. + + Returns + ------- + this_segment_cost : integer + Optimal cost for this segment in the C. Guichaoua's paradigm. + best_pattern : nested list of integers (indexed pattern) + The pattern, resulting in the optimal score. + + """ + this_bag = sh.compute_patterns_with_antecedents_for_size(len(segment)) + this_segment_cost = math.inf + + if this_bag == []: + print("No polytope for this size: {}".format(len(segment))) + return this_segment_cost + + for a_pattern in this_bag: + this_polytope_cost = guichaoua_cost(segment, a_pattern[0], a_pattern[3], a_pattern[4], a_pattern[5], current_min = this_segment_cost) + + this_polytope_cost += irregularities_penalty_guichaoua(adding_code = a_pattern[1], deleting_code = a_pattern[2], positive_penalty = positive_penalty, negative_penalty = negative_penalty) + if this_polytope_cost < this_segment_cost: + this_segment_cost = this_polytope_cost + best_pattern = a_pattern[0] + + this_segment_cost += sh.penalty_cost_guichaoua(len(segment), target_size = target_size, positive_segment_size_penalty = positive_segment_size_penalty, negative_segment_size_penalty = negative_segment_size_penalty) + return this_segment_cost, best_pattern + # %% New paradigms, developed by A. Marmoret def louboutaoua_cost(chord_sequence, indexed_pattern, antecedents_with_pivots, successors, correct_antecedents, direct_antecedents, current_min = math.inf): """ diff --git a/polytopes/segmentation_helper.py b/polytopes/segmentation_helper.py index 20a3ad193c1010733784bae09c9d6f90299d7cd7..1ae79ae23f15802d6e47e012e6504536fb72fa31 100644 --- a/polytopes/segmentation_helper.py +++ b/polytopes/segmentation_helper.py @@ -206,14 +206,14 @@ def compute_patterns_with_ppp_and_antecedents_for_size(size): successors = [[]] correct_antecedents = [None] for elt in range(1, pf.get_pattern_size(local_pattern)): - elt_idx = pm.get_index_of_element(elt, local_pattern) - antecedents_and_pivots.append(pm.get_antecedents_with_pivots_from_idx(elt_idx, local_pattern)) + elt_idx = pm.get_index_from_element(elt, local_pattern) + antecedents_and_pivots.append(pm.get_antecedents_with_pivots_from_index(elt_idx, local_pattern)) this_correct_antecedents = [] - for ant in pm.get_antecedents_from_idx(elt_idx, local_pattern): + for ant in pm.get_antecedents_from_index(elt_idx, local_pattern): if ant != 0: this_correct_antecedents.append(ant) correct_antecedents.append(this_correct_antecedents) - successors.append(pm.get_successors_from_idx(elt_idx, local_pattern)) + successors.append(pm.get_successors_from_index(elt_idx, local_pattern)) to_return.append([all_ppps, add, dele, antecedents_and_pivots, successors, correct_antecedents, bag_of_direct_antecedents]) return to_return @@ -251,14 +251,14 @@ def compute_patterns_with_antecedents_for_size(size): successors = [[]] correct_antecedents = [None] for elt in range(1, pf.get_pattern_size(local_pattern)): - elt_idx = pm.get_index_of_element(elt, local_pattern) - antecedents_and_pivots.append(pm.get_antecedents_with_pivots_from_idx(elt_idx, local_pattern)) + elt_idx = pm.get_index_from_element(elt, local_pattern) + antecedents_and_pivots.append(pm.get_antecedents_with_pivots_from_index(elt_idx, local_pattern)) this_correct_antecedents = [] - for ant in pm.get_antecedents_from_idx(elt_idx, local_pattern): + for ant in pm.get_antecedents_from_index(elt_idx, local_pattern): if ant != 0: this_correct_antecedents.append(ant) correct_antecedents.append(this_correct_antecedents) - successors.append(pm.get_successors_from_idx(elt_idx, local_pattern)) + successors.append(pm.get_successors_from_index(elt_idx, local_pattern)) to_return.append([local_pattern, add, dele, antecedents_and_pivots, successors, correct_antecedents]) return to_return @@ -296,14 +296,14 @@ def compute_patterns_with_global_antecedents_for_size(size): successors = [[]] correct_antecedents = [None] for elt in range(1, pf.get_pattern_size(local_pattern)): - elt_idx = pm.get_index_of_element(elt, local_pattern) - antecedents_and_pivots.append(pm.get_global_antecedents_with_pivots_from_idx(elt_idx, local_pattern)) + elt_idx = pm.get_index_from_element(elt, local_pattern) + antecedents_and_pivots.append(pm.get_global_antecedents_with_pivots_from_index(elt_idx, local_pattern)) this_correct_antecedents = [] for ant, _ in antecedents_and_pivots[-1]: if ant != 0: this_correct_antecedents.append(ant) correct_antecedents.append(this_correct_antecedents) - successors.append(pm.get_global_successors_from_idx(elt_idx, local_pattern)) + successors.append(pm.get_global_successors_from_index(elt_idx, local_pattern)) to_return.append([local_pattern, add, dele, antecedents_and_pivots, successors, correct_antecedents]) return to_return