<commit_before>#!/usr/bin/env python3

import colorsys
import re
import matplotlib.path
import matplotlib.patches
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
import numpy as np
from typing import List, Dict, Tuple


def default_site_colors(number_of_sites) -> list:
    """Define a list of colors appropriate to the number of sites being drawn."""
    # Use built-in palettes: for 10 or less use the default colors
    if number_of_sites <= len(mpl.rcParams['axes.prop_cycle']):
        color_list = ['C' + str(i) for i in range(number_of_sites)]
    # For 20 or more agents, use the tab20 colormap
    elif number_of_sites <= 20:
        colormap = plt.get_cmap('tab20')
        color_list = [colormap(i) for i in range(number_of_sites)]
    # For more than 20, pick linearly spaced values on HSV space
    else:
        h = np.linspace(start=0, stop=1, num=number_of_sites, endpoint=False)
        color_list = [colorsys.hsv_to_rgb(i, 0.7, 0.75) for i in range(number_of_sites)]
    return color_list


def file_name_to_string_list(contact_map_file_name: str) -> List[str]:
    """Read an "inputs.ka" style file containing contact map data, process into a clean list of 1-agent strings."""
    with open(contact_map_file_name, 'r') as cmf:
        raw_expression = cmf.read()
    spaced_string = re.sub(r'\s+', ' ', raw_expression)                 # condense spaces
    flat_string = spaced_string.replace(' [', '[')                      # remove whitespace before bracket
    flat_string = flat_string.replace(' {', '{')                        # remove whitespace before curly bracket
    split_string = flat_string.split('%agent: ')[1:]                    # break into list, discard empty first element
    split_string[-1] = split_string[-1].split(')')[0] + ')'
    cleaned_list = [agent_exp.strip() for agent_exp in split_string]    # clean trailing space
    return cleaned_list


def raw_string_to_agent_sites_bond_types(raw_string: str) -> Tuple[str, dict]:
    """Process the raw string to extract the agent's name, the list of sites, the bond types for each site."""
    raw_expression = raw_string
    kappa_ident = r'([_~][a-zA-Z0-9_~+-]+|[a-zA-Z][a-zA-Z0-9_~+-]*)'
    agent_sign_pat = r'\(([^()]*)\)'
    agent_pat = r'^' + kappa_ident + agent_sign_pat + r'$'
    agent_digest = re.match(agent_pat, raw_expression)
    agent_name = agent_digest.group(1)
    agent_sites = agent_digest.group(2)
    sites_data = {}

    if agent_sites:
        site_expression_list = re.findall(kappa_ident + r'(?:{([^}]+)})?(?:\[([^]]+)\])?', agent_sites)
        for site_expression in site_expression_list:
            site_name, site_internals_expr, site_bonds_expr = site_expression
            site_int_states = site_internals_expr.split()
            site_bnd_states = site_bonds_expr.split()
            sites_data[site_name] = {'int_states': site_int_states, 'bnd_states': site_bnd_states}
    return agent_name, sites_data


def parsed_kappa_to_default_graphics(parsed_kappa_struct: dict, wedge_surf_dist=3, wedge_thickness=1.5, grid_spacing=10):
    """Initialize agents as a square grid, sites superposed (i.e. default values)."""
    agent_graphics = {}
    grid_base = np.ceil(np.sqrt(len(parsed_kappa_struct.keys())))
    for agent_index, agent_name in enumerate(parsed_kappa_struct.keys()):
        agent_x = np.mod(agent_index, grid_base) * grid_spacing           # default values, square grid
        agent_y = np.floor_divide(agent_index, grid_base) * grid_spacing
        binding_sites = {}                                  # each site should be sent to one or both of these
        flagpole_site_data = {}
        for site_name in parsed_kappa_struct[agent_name].keys():
            if parsed_kappa_struct[agent_name][site_name]['bnd_states']:
                binding_sites[site_name] = {'center': (agent_x, agent_y),
                                            'r': wedge_surf_dist,
                                            'theta1': 0.0, 'theta2': 0.0,       # these will be initialized later
                                            'width': wedge_thickness,
                                            'facecolor': '#000000'}
            if parsed_kappa_struct[agent_name][site_name]['int_states']:
                flagpole_site_data[site_name] = parsed_kappa_struct[agent_name][site_name]['int_states']
        flagpole_location = {'center': (agent_x, agent_y),
                             'r': wedge_surf_dist,
                             'theta1': 0.0, 'theta2': 0.0,                 # these will be initialized later
                             'width': wedge_thickness,
                             'facecolor': '#444444'}
        agent_graphics[agent_name] = {'loc_x': agent_x, 'loc_y': agent_y,
                                      'bnd_sites': binding_sites, 'flagpole_sites': flagpole_site_data,
                                      'flagpole_loc': flagpole_location}
    return agent_graphics


def initialize_sites_graphic_structure(graphic_struct: dict) -> dict:
    """Once the data structure has been created, it can now be initialized with plausible values."""
    init_site_graphics = graphic_struct
    for agent_name in init_site_graphics.keys():
        # identify number of wedges to draw
        bind_site_number = len(init_site_graphics[agent_name]['bnd_sites'])
        if init_site_graphics[agent_name]['flagpole_sites']:
            wedge_number = bind_site_number + 2
        else:
            wedge_number = bind_site_number + 1
        wedge_ends = np.rad2deg(np.linspace(0, 2*np.pi, wedge_number))
        # define default color palette based on number of wedges
        site_palette = default_site_colors(bind_site_number)
        # (re)initialize data
        wedge_center = (graphic_struct[agent_name]['loc_x'], graphic_struct[agent_name]['loc_y'])
        for bind_site_index, bind_site_name in enumerate(init_site_graphics[agent_name]['bnd_sites'].keys()):
            init_site_graphics[agent_name]['bnd_sites'][bind_site_name]['theta1'] = wedge_ends[bind_site_index]
            init_site_graphics[agent_name]['bnd_sites'][bind_site_name]['theta2'] = wedge_ends[bind_site_index+1]
            init_site_graphics[agent_name]['bnd_sites'][bind_site_name]['facecolor'] = site_palette[bind_site_index]
            init_site_graphics[agent_name]['bnd_sites'][bind_site_name]['center'] = wedge_center
        if init_site_graphics[agent_name]['flagpole_sites']:
            init_site_graphics[agent_name]['flagpole_loc']['theta1'] = wedge_ends[-2]
            init_site_graphics[agent_name]['flagpole_loc']['theta2'] = wedge_ends[-1]
            init_site_graphics[agent_name]['flagpole_loc']['center'] = wedge_center
    return init_site_graphics


def get_bond_types(parsed_kappa_struct: dict) -> dict:
    """Define the agent and site points for later spline drawing."""
    bond_dict = {}
    for agent_a in parsed_kappa_struct.keys():
        for site_a in parsed_kappa_struct[agent_a].keys():
            for type_data in parsed_kappa_struct[agent_a][site_a]['bnd_states']:
                raw_exp = type_data
                site_b, agent_b = raw_exp.split('.')
                # canonicalize bond name by alphabetical agent number, avoids duplication
                if agent_a < agent_b:
                    bond_name = agent_a + '.' + site_a + '..' + site_b + '.' + agent_b
                else:
                    bond_name = agent_b + '.' + site_b + '..' + site_b + '.' + agent_b
                bond_dict[bond_name] = {'ag_1': agent_a, 'st_1': site_a, 'st_2': site_b, 'ag_2': agent_b}
    return bond_dict


def define_bond_spline_points(bond_type_dict, init_graphic_struct) -> dict:
    """Define point coordinates for drawing splines based on initialized agent & site data."""
    graphic_bond_points = {}
    for bond_name in bond_type_dict.keys():
        point_dict = {}
        # get agent & site names
        ag_1_name = bond_type_dict[bond_name]['ag_1']
        ag_2_name = bond_type_dict[bond_name]['ag_2']
        st_1_name = bond_type_dict[bond_name]['st_1']
        st_2_name = bond_type_dict[bond_name]['st_2']
        # find agent center positions
        ag_1_center_x = init_graphic_struct[ag_1_name]['loc_x']
        ag_1_center_y = init_graphic_struct[ag_1_name]['loc_y']
        ag_2_center_x = init_graphic_struct[ag_2_name]['loc_x']
        ag_2_center_y = init_graphic_struct[ag_2_name]['loc_y']
        # find site midline angles, special case for symmetric dimerization
        if (ag_1_name == ag_2_name) and (st_1_name == st_2_name):
            st_1_midline = (1.2 * init_graphic_struct[ag_1_name]['bnd_sites'][st_1_name]['theta1'] +
                            0.8 * init_graphic_struct[ag_1_name]['bnd_sites'][st_1_name]['theta2']) / 2
            st_2_midline = (0.8 * init_graphic_struct[ag_2_name]['bnd_sites'][st_2_name]['theta1'] +
                            1.2 * init_graphic_struct[ag_2_name]['bnd_sites'][st_2_name]['theta2']) / 2
        else:
            st_1_midline = (init_graphic_struct[ag_1_name]['bnd_sites'][st_1_name]['theta1'] +
                            init_graphic_struct[ag_1_name]['bnd_sites'][st_1_name]['theta2']) / 2<commit_msg>Sort bond identifiers by agent, then by site name.<commit_after>#!/usr/bin/env python3

import colorsys
import re
import matplotlib.path
import matplotlib.patches
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
import numpy as np
from typing import List, Dict, Tuple


def default_site_colors(number_of_sites) -> list:
    """Define a list of colors appropriate to the number of sites being drawn."""
    # Use built-in palettes: for 10 or less use the default colors
    if number_of_sites <= len(mpl.rcParams['axes.prop_cycle']):
        color_list = ['C' + str(i) for i in range(number_of_sites)]
    # For 20 or more agents, use the tab20 colormap
    elif number_of_sites <= 20:
        colormap = plt.get_cmap('tab20')
        color_list = [colormap(i) for i in range(number_of_sites)]
    # For more than 20, pick linearly spaced values on HSV space
    else:
        h = np.linspace(start=0, stop=1, num=number_of_sites, endpoint=False)
        color_list = [colorsys.hsv_to_rgb(i, 0.7, 0.75) for i in range(number_of_sites)]
    return color_list


def file_name_to_string_list(contact_map_file_name: str) -> List[str]:
    """Read an "inputs.ka" style file containing contact map data, process into a clean list of 1-agent strings."""
    with open(contact_map_file_name, 'r') as cmf:
        raw_expression = cmf.read()
    spaced_string = re.sub(r'\s+', ' ', raw_expression)                 # condense spaces
    flat_string = spaced_string.replace(' [', '[')                      # remove whitespace before bracket
    flat_string = flat_string.replace(' {', '{')                        # remove whitespace before curly bracket
    split_string = flat_string.split('%agent: ')[1:]                    # break into list, discard empty first element
    split_string[-1] = split_string[-1].split(')')[0] + ')'
    cleaned_list = [agent_exp.strip() for agent_exp in split_string]    # clean trailing space
    return cleaned_list


def raw_string_to_agent_sites_bond_types(raw_string: str) -> Tuple[str, dict]:
    """Process the raw string to extract the agent's name, the list of sites, the bond types for each site."""
    raw_expression = raw_string
    kappa_ident = r'([_~][a-zA-Z0-9_~+-]+|[a-zA-Z][a-zA-Z0-9_~+-]*)'
    agent_sign_pat = r'\(([^()]*)\)'
    agent_pat = r'^' + kappa_ident + agent_sign_pat + r'$'
    agent_digest = re.match(agent_pat, raw_expression)
    agent_name = agent_digest.group(1)
    agent_sites = agent_digest.group(2)
    sites_data = {}

    if agent_sites:
        site_expression_list = re.findall(kappa_ident + r'(?:{([^}]+)})?(?:\[([^]]+)\])?', agent_sites)
        for site_expression in site_expression_list:
            site_name, site_internals_expr, site_bonds_expr = site_expression
            site_int_states = site_internals_expr.split()
            site_bnd_states = site_bonds_expr.split()
            sites_data[site_name] = {'int_states': site_int_states, 'bnd_states': site_bnd_states}
    return agent_name, sites_data


def parsed_kappa_to_default_graphics(parsed_kappa_struct: dict, wedge_surf_dist=3, wedge_thickness=1.5, grid_spacing=10):
    """Initialize agents as a square grid, sites superposed (i.e. default values)."""
    agent_graphics = {}
    grid_base = np.ceil(np.sqrt(len(parsed_kappa_struct.keys())))
    for agent_index, agent_name in enumerate(parsed_kappa_struct.keys()):
        agent_x = np.mod(agent_index, grid_base) * grid_spacing           # default values, square grid
        agent_y = np.floor_divide(agent_index, grid_base) * grid_spacing
        binding_sites = {}                                  # each site should be sent to one or both of these
        flagpole_site_data = {}
        for site_name in parsed_kappa_struct[agent_name].keys():
            if parsed_kappa_struct[agent_name][site_name]['bnd_states']:
                binding_sites[site_name] = {'center': (agent_x, agent_y),
                                            'r': wedge_surf_dist,
                                            'theta1': 0.0, 'theta2': 0.0,       # these will be initialized later
                                            'width': wedge_thickness,
                                            'facecolor': '#000000'}
            if parsed_kappa_struct[agent_name][site_name]['int_states']:
                flagpole_site_data[site_name] = parsed_kappa_struct[agent_name][site_name]['int_states']
        flagpole_location = {'center': (agent_x, agent_y),
                             'r': wedge_surf_dist,
                             'theta1': 0.0, 'theta2': 0.0,                 # these will be initialized later
                             'width': wedge_thickness,
                             'facecolor': '#444444'}
        agent_graphics[agent_name] = {'loc_x': agent_x, 'loc_y': agent_y,
                                      'bnd_sites': binding_sites, 'flagpole_sites': flagpole_site_data,
                                      'flagpole_loc': flagpole_location}
    return agent_graphics


def initialize_sites_graphic_structure(graphic_struct: dict) -> dict:
    """Once the data structure has been created, it can now be initialized with plausible values."""
    init_site_graphics = graphic_struct
    for agent_name in init_site_graphics.keys():
        # identify number of wedges to draw
        bind_site_number = len(init_site_graphics[agent_name]['bnd_sites'])
        if init_site_graphics[agent_name]['flagpole_sites']:
            wedge_number = bind_site_number + 2
        else:
            wedge_number = bind_site_number + 1
        wedge_ends = np.rad2deg(np.linspace(0, 2*np.pi, wedge_number))
        # define default color palette based on number of wedges
        site_palette = default_site_colors(bind_site_number)
        # (re)initialize data
        wedge_center = (graphic_struct[agent_name]['loc_x'], graphic_struct[agent_name]['loc_y'])
        for bind_site_index, bind_site_name in enumerate(init_site_graphics[agent_name]['bnd_sites'].keys()):
            init_site_graphics[agent_name]['bnd_sites'][bind_site_name]['theta1'] = wedge_ends[bind_site_index]
            init_site_graphics[agent_name]['bnd_sites'][bind_site_name]['theta2'] = wedge_ends[bind_site_index+1]
            init_site_graphics[agent_name]['bnd_sites'][bind_site_name]['facecolor'] = site_palette[bind_site_index]
            init_site_graphics[agent_name]['bnd_sites'][bind_site_name]['center'] = wedge_center
        if init_site_graphics[agent_name]['flagpole_sites']:
            init_site_graphics[agent_name]['flagpole_loc']['theta1'] = wedge_ends[-2]
            init_site_graphics[agent_name]['flagpole_loc']['theta2'] = wedge_ends[-1]
            init_site_graphics[agent_name]['flagpole_loc']['center'] = wedge_center
    return init_site_graphics


def get_bond_types(parsed_kappa_struct: dict) -> dict:
    """Define the agent and site points for later spline drawing."""
    bond_dict = {}
    for agent_a in parsed_kappa_struct.keys():
        for site_a in parsed_kappa_struct[agent_a].keys():
            for type_data in parsed_kappa_struct[agent_a][site_a]['bnd_states']:
                raw_exp = type_data
                site_b, agent_b = raw_exp.split('.')
                # canonicalize bond name by alphabetical agent number, site name if equal: avoids duplication
                if agent_a < agent_b:
                    bond_name = agent_a + '.' + site_a + '..' + site_b + '.' + agent_b
                elif agent_a > agent_b:
                    bond_name = agent_b + '.' + site_b + '..' + site_a + '.' + agent_a
                else:
                    if site_a < site_b:
                        bond_name = agent_a + '.' + site_a + '..' + site_b + '.' + agent_b
                    else:
                        bond_name = agent_b + '.' + site_b + '..' + site_a + '.' + agent_a
                bond_dict[bond_name] = {'ag_1': agent_a, 'st_1': site_a, 'st_2': site_b, 'ag_2': agent_b}
    return bond_dict


def define_bond_spline_points(bond_type_dict, init_graphic_struct) -> dict:
    """Define point coordinates for drawing splines based on initialized agent & site data."""
    graphic_bond_points = {}
    for bond_name in bond_type_dict.keys():
        point_dict = {}
        # get agent & site names
        ag_1_name = bond_type_dict[bond_name]['ag_1']
        ag_2_name = bond_type_dict[bond_name]['ag_2']
        st_1_name = bond_type_dict[bond_name]['st_1']
        st_2_name = bond_type_dict[bond_name]['st_2']
        # find agent center positions
        ag_1_center_x = init_graphic_struct[ag_1_name]['loc_x']
        ag_1_center_y = init_graphic_struct[ag_1_name]['loc_y']
        ag_2_center_x = init_graphic_struct[ag_2_name]['loc_x']
        ag_2_center_y = init_graphic_struct[ag_2_name]['loc_y']
        # find site midline angles, special case for symmetric dimerization
        if (ag_1_name == ag_2_name) and (st_1_name == st_2_name):
            st_1_midline = (1.2 * init_graphic_struct[ag_1_name]['bnd_sites'][st_1_name]['theta1'] +
                            0.8 * init_graphic_struct[ag_1_name]['bnd_sites'][st_1_name]['theta2']) / 2
            st_2_midline = (0.8 * init_graphic_struct[ag_2_name]['bnd_sites'][st_2_name]['theta1'] +
                            1.2 * init_graphic_struct[ag_2_name]['bnd_sites'][st_2_name]['theta2']) / 2
        else:
            st_1_midline = (init_graphic_struct[ag_1_name]['bnd_sites'][st_1_name]['theta1'] +
                            init_graphic_struct[ag_1_name]['bnd_sites'][st_1_name]['theta2']) / 2
{'fraction_non_alphanumeric': 0.1127359744748737, 'fraction_numerical': 0.013613400691305504, 'mean_word_length': 2.84816861059955, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 16, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 24, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '336696', 'n_tokens_mistral': 6183, 'n_tokens_neox': 5876, 'n_words': 1216}