<commit_before>require 'set'
require './forte_set'
require './forte_dictionary'

#
# == Class: Matrix Analyzer
#
# Given an array where each element in the array is a group of rows - each row representing ordered musical pitches:
# permutate each group of rows in parallel using rotation.  After each rotation search each column (intersecting all
# groups) for a pattern of sonorities from a dictionary. If a sonority is found, then tabulate a score for comparison
# reporting.
#
# Useful for counterpoint such as creating a canon, composing 12t, or deriving set related composition designs
#
# @note Performance is an issue right now with larger number of voices and pitches since performance is measured
# from: number of permutations =  x^(n-1) where x = number of columns and n is the number of groups.50 columns and
# 8 groups of one row in each group would generate 50^7 permutations or 781,250,000,000 possible rotations to
# search through.
#
# @example "row row row your boat"
#
#    melody = [0, 0, 0, 4, 4, 4, 7, 7, 0, 7, 2, 0, 7, 3, 0, 0]     # from the beats of the melody
#
#    # Sonorities to search for
#    major =  [0, 4, 7]                                            # 3-11i
#    minor =  [0, 3, 7]                                            # 3-11
#
#    analysis_engine = MatrixAnalyzer.new
#    analysis_engine.report_details=(true)
#
#    # First add the rows.  Three voices = triadic; independent in separate groups
#    analysis_engine.add_row(1, melody)
#    analysis_engine.add_row(2, melody)
#    analysis_engine.add_row(3, melody)
#
#    # Then, create all transpositions of sets to search for:
#    0.upto(11) do |i|
#      analysis_engine.add_search_set(major, i)                # TnI
#      analysis_engine.add_search_set(minor, i)                # Tn
#      #  analysis_engine.add_forte_set("3-11", i)             # Tn  <== or optionally use Forte Set names like here
#      #  analysis_engine.add_forte_set("3-11i", i)            # TnI
#    end
#
#    analysis_engine.run_rotation_analysis
#
#    Output sample - one detail analysis of a possible rotation and the run summary:
#
#    [0, 0, 0, 4, 4, 4, 7, 7, 0, 7, 2, 0, 7, 3, 0, 0]  Group 1 <== original melody
#    [4, 4, 7, 7, 0, 7, 2, 0, 7, 3, 0, 0, 0, 0, 0, 4]  Group 2 <== melody rotated 12x
#    [0, 7, 2, 0, 7, 3, 0, 0, 0, 0, 0, 4, 4, 4, 7, 7]  Group 3 <== melody rotated 8x
#    ----------
#    [0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1]  Score  <== the # maj/min sets (analysis)
#    Total Score: 6
#
#    ...
#    Score : # Instances
#    -------------------
#        0 :    46
#        1 :    18
#        2 :    48
#        3 :    72
#        4 :    24
#        5 :    42
#        6 :     6  <== most saturated found = 6 solutions ex: the detail above
#
# ex:  there are 42 permutation solutions that have 5 columns holding major or minor chords
#
# TODO More report filtering options and statistic measurements
# TODO Performance improvement - allow for start and stop rotation indexes so work can be broken up
# TODO Performance improvement - parallel forking/threads, better algorithms etc...
#

class MatrixAnalyzer

  attr_accessor :minimax_score      # Range object - minimum to maximum score that must be met in the results for a
                                    # solution to be counted. default = 0..9999999

  attr_accessor :report_details     # If true, show details of the analysis, else summary only. default = false

  attr_accessor :groups             # A 3d array for holding horizontal ordered sets (rows) - each set:
                                    # 1) representing an ordered voice of pitches in the matrix
                                    # 2) should be rotatable like a cannon
                                    # 3) encoded in mod12 pitch class notation (0 = c up to 11 = b)

  attr_accessor :search_sets        # A 2d array for holding set of pitches to analyze when looking for vertical
                                    # sonorities.  These should already be transformed using Tn or TnI

  attr_accessor :summary_totals     # Used to collect a final tally of subsets found. For example:
                                    # summary_totals[9] = 21 means 21 permutations had 9 subsets found in each

  attr_accessor :max_group_index    # The last index of the groups array e.g. length - 1

  attr_accessor :max_column_index   # The last index of the columns of the rows  e.g. length - 1

  attr_accessor :rotation_count     # Used to count the total number of permutations (rotations) performed by the run.

  attr_accessor :maximum_rotations  # A calculation of the total number of rotations that this process will produce.

  protected     :groups, :search_sets, :summary_totals, :max_group_index, :max_column_index, :rotation_count,
                :maximum_rotations

  public

  # Constructor
  #
  # @param [Range] minimax_score Minimum to maximum score to display
  # @param [Boolean] report_details if true, show details, else summary only
  # @return [Object] a new MatrixAnalyzer Object

  def initialize( minimax_score = Range.new(0, 99999999), report_details = false )
    @minimax_score     = minimax_score
    @report_details    = report_details
    @groups            = Array.new
    @search_sets       = Array.new
    @summary_totals    = Array.new
    @max_group_index   = 0
    @max_column_index  = 0
    @rotation_count    = 0
    @maximum_rotations = 0
  end

  # Insert a row into the matrix of voices to search
  #
  # @param [Integer] group_id The group id to add it to (1 - n)
  # @param [Array] row an array of ordered pitches (voice)
  # @param [Integer] number_to_transpose transpose this array Tn 0-11
  #
<commit_msg>Small documentation changes<commit_after>require 'set'
require './forte_set'
require './forte_dictionary'

#
# == Class: Matrix Analyzer
#
# Given an array where each element in the array is a group of rows - each row representing ordered musical pitches:
# permutate each group of rows in parallel using rotation.  After each rotation search each column (intersecting all
# groups) for a pattern of sonorities from a dictionary. If a sonority is found, then tabulate a score for comparison
# reporting.
#
# Useful for counterpoint such as creating a canon, composing 12t, or deriving set related composition designs
#
# @note Performance is an issue right now with larger number of voices and pitches since performance is measured
# from: number of permutations =  x^(n-1) where x = number of columns and n is the number of groups.50 columns and
# 8 groups of one row in each group would generate 50^7 permutations or 781,250,000,000 possible rotations to
# search through.
#
# @example "row row row your boat"
#
#    melody = [0, 0, 0, 4, 4, 4, 7, 7, 0, 7, 2, 0, 7, 3, 0, 0]     # from the beats of the melody
#
#    # Sonorities to search for
#    major =  [0, 4, 7]                                            # 3-11i
#    minor =  [0, 3, 7]                                            # 3-11
#
#    analysis_engine = MatrixAnalyzer.new
#    analysis_engine.report_details=(true)
#
#    # First add the rows.  Three voices = triadic; independent in separate groups
#    analysis_engine.add_row(1, melody)
#    analysis_engine.add_row(2, melody)
#    analysis_engine.add_row(3, melody)
#
#    # Then, create all transpositions of sets to search for:
#    0.upto(11) do |i|
#      analysis_engine.add_search_set(major, i)                # TnI
#      analysis_engine.add_search_set(minor, i)                # Tn
#      #  analysis_engine.add_forte_set("3-11", i)             # Tn  <== or optionally use Forte Set names like here
#      #  analysis_engine.add_forte_set("3-11i", i)            # TnI
#    end
#
#    analysis_engine.run_rotation_analysis
#
#    Output sample - one detail analysis of a possible rotation and the run summary:
#
#    [0, 0, 0, 4, 4, 4, 7, 7, 0, 7, 2, 0, 7, 3, 0, 0]  Group 1 <== original melody
#    [4, 4, 7, 7, 0, 7, 2, 0, 7, 3, 0, 0, 0, 0, 0, 4]  Group 2 <== melody rotated 12x
#    [0, 7, 2, 0, 7, 3, 0, 0, 0, 0, 0, 4, 4, 4, 7, 7]  Group 3 <== melody rotated 8x
#    ----------
#    [0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1]  Score  <== the # maj/min sets (analysis)
#    Total Score: 6
#
#    ...
#    Score : # Instances
#    -------------------
#        0 :    46
#        1 :    18
#        2 :    48
#        3 :    72
#        4 :    24
#        5 :    42
#        6 :     6  <== most saturated found = 6 solutions ex: the detail above
#
# ex:  there are 42 permutation solutions that have 5 columns holding major or minor chords
#
# TODO More report filtering options and statistic measurements
# TODO Performance improvement - allow for start and stop rotation indexes so work can be broken up
# TODO Performance improvement - parallel forking/threads, better algorithms etc...
#

class MatrixAnalyzer

  # Range object - minimum to maximum score that must be met in the results for a solution to be counted.

  attr_accessor :minimax_score

  # Show details of the analysis, else summary only.

  attr_accessor :report_details

  # A 3d array for holding horizontal ordered sets (rows) - each set:
  # 1) representing an ordered voice of pitches in the matrix
  # 2) should be rotatable like a cannon
  # 3) encoded in mod12 pitch class notation (0 = c up to 11 = b)

  attr_accessor :groups

  # A 2d array for holding set of pitches to analyze when looking for vertical sonorities.  These should already be
  # transformed using Tn or TnI

  attr_accessor :search_sets

  # Used to collect a final tally of subsets found. For example: summary_totals[9] = 21 means 21 permutations had 9
  # subsets found in each

  attr_accessor :summary_totals

  # The last index of the groups array e.g. length - 1

  attr_accessor :max_group_index

  # The last index of the columns of the rows  e.g. length - 1

  attr_accessor :max_column_index

  # Used to count the total number of permutations (rotations) performed by the run.

  attr_accessor :rotation_count

  # A calculation of the total number of rotations that this process will produce.

  attr_accessor :maximum_rotations

  protected     :groups, :search_sets, :summary_totals, :max_group_index, :max_column_index, :rotation_count,
                :maximum_rotations

  public

  # Constructor
  #
  # @param [Range] minimax_score Minimum to maximum score to display
  # @param [Boolean] report_details if true, show details, else summary only
  # @return [Object] a new MatrixAnalyzer Object

  def initialize( minimax_score = Range.new(0, 99999999), report_details = false )
    @minimax_score     = minimax_score
    @report_details    = report_details
    @groups            = Array.new
    @search_sets       = Array.new
    @summary_totals    = Array.new
    @max_group_index   = 0
    @max_column_index  = 0
    @rotation_count    = 0
    @maximum_rotations = 0
  end

  # Insert a row into the matrix of voices to search
  #
  # @param [Integer] group_id The group id to add it to (1 - n)
  # @param [Array] row an array of ordered pitches (voice)
  # @param [Integer] number_to_transpose transpose this array Tn 0-11
  #

{'fraction_non_alphanumeric': 0.08705818868603292, 'fraction_numerical': 0.035075096681356235, 'mean_word_length': 2.5584, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 3, 'word_list_counts': {'cursed_substrings.json': 17, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3504360', 'n_tokens_mistral': 3734, 'n_tokens_neox': 3290, 'n_words': 1386}