From 0fdf10d7f9de9b02adcf503bb440a58a3df5b17c Mon Sep 17 00:00:00 2001 From: Grant Mowry Date: Sat, 4 Apr 2026 13:00:32 -0400 Subject: [PATCH] Optimize interaction matrix scoring kernel --- finches/epsilon_calculation.py | 50 +++++- finches/forcefields/mpipi.py | 8 +- finches/parsing_aminoacid_sequences.py | 132 ++------------- finches/tests/test_FH_diagrams.py | 5 +- .../mPiPi_GGv1_seq_epsilon_and_vectors.npz | Bin 0 -> 8584 bytes .../test_mPiPi_GGv1_heterotypic_matrix.npz | Bin 0 -> 243248 bytes .../test_mPiPi_GGv1_homotypic_matrix.npz | Bin 0 -> 281128 bytes .../test_mPiPi_GGv1_weighted_matrix.npz | Bin 0 -> 729724 bytes .../test_data/test_matrix_manipulation.npz | Bin 0 -> 223713 bytes finches/tests/test_data/update_test_data.py | 9 +- finches/tests/test_epsilon_calculation.py | 69 ++++++-- finches/utils/matrix_manipulation.pyx | 152 ++++++++++++------ 12 files changed, 223 insertions(+), 202 deletions(-) create mode 100644 finches/tests/test_data/mPiPi_GGv1_seq_epsilon_and_vectors.npz create mode 100644 finches/tests/test_data/test_mPiPi_GGv1_heterotypic_matrix.npz create mode 100644 finches/tests/test_data/test_mPiPi_GGv1_homotypic_matrix.npz create mode 100644 finches/tests/test_data/test_mPiPi_GGv1_weighted_matrix.npz create mode 100644 finches/tests/test_data/test_matrix_manipulation.npz diff --git a/finches/epsilon_calculation.py b/finches/epsilon_calculation.py index 3d87e09..3f5cfad 100644 --- a/finches/epsilon_calculation.py +++ b/finches/epsilon_calculation.py @@ -14,6 +14,38 @@ from finches.utils import matrix_manipulation from finches import epsilon_stateless + +def get_attractive_repulsive_matrixes(matrix, null_interaction_baseline): + return epsilon_stateless.get_attractive_repulsive_matrices(matrix, null_interaction_baseline) + + +def mask_matrix(matrix, column_mask): + return epsilon_stateless.mask_matrix(matrix, column_mask) + + +def masked_matrix(matrix, column_mask): + return epsilon_stateless.mask_matrix(matrix, column_mask) + + +def flatten_matrix_to_vector(matrix, orientation=0): + return np.mean(matrix, axis=orientation) + + +def get_sequence_epsilon_vectors(sequence1, + sequence2, + X, + prefactor=None, + null_interaction_baseline=None, + use_charge_weighting=True, + use_aliphatic_weighting=True): + return epsilon_stateless.get_sequence_epsilon_vectors(sequence1, + sequence2, + X, + charge_prefactor=prefactor, + null_interaction_baseline=null_interaction_baseline, + use_charge_weighting=use_charge_weighting, + use_aliphatic_weighting=use_aliphatic_weighting) + # ------------------------------------------------------------------------------------------------- class InteractionMatrixConstructor: @@ -691,14 +723,14 @@ def calculate_epsilon_value(self, """ - # note this runs charge_prefactor and null_interaction_baseline - # as null which means the default values associated with this - # object will be used - return epsilon_stateless.get_sequence_epsilon_value(sequence1, - sequence2, - self, - use_charge_weighting=use_charge_weighting, - use_aliphatic_weighting=use_aliphatic_weighting) + w_matrix = self.calculate_weighted_pairwise_matrix(sequence1, + sequence2, + use_charge_weighting=use_charge_weighting, + use_aliphatic_weighting=use_aliphatic_weighting) + + transformed = w_matrix - (2.0 * self.null_interaction_baseline) + transformed[w_matrix == self.null_interaction_baseline] -= self.null_interaction_baseline + return float(np.sum(transformed) / transformed.shape[1]) ## ------------------------------------------------------------------------------ ## @@ -871,3 +903,5 @@ def __matrix2eps(in_matrix): return (everything, seq2_indices, seq1_indices) + +Interaction_Matrix_Constructor = InteractionMatrixConstructor diff --git a/finches/forcefields/mpipi.py b/finches/forcefields/mpipi.py index 4ea07c3..3ab8184 100644 --- a/finches/forcefields/mpipi.py +++ b/finches/forcefields/mpipi.py @@ -600,8 +600,12 @@ def wang_frenkel(r, sigma_ij, epsilon_ij, mu_ij=2, nu_ij=1): main_term3 = np.power(np.power(R_ij/r, 2*mu_ij) - 1, 2*nu_ij) return main_term1*main_term2*main_term3 - - +def mpipi_model(version='Mpipi_GGv1', *args, **kwargs): + version_aliases = { + 'mPiPi_GGv1': 'Mpipi_GGv1', + 'mPiPi_original': 'Mpipi_original', + } + return Mpipi_model(version=version_aliases.get(version, version), *args, **kwargs) diff --git a/finches/parsing_aminoacid_sequences.py b/finches/parsing_aminoacid_sequences.py index 16483c0..2e58625 100644 --- a/finches/parsing_aminoacid_sequences.py +++ b/finches/parsing_aminoacid_sequences.py @@ -15,6 +15,7 @@ """ import numpy as np from finches import sequence_tools +from finches.utils import matrix_manipulation # new characters for PIMMS aliphatic groups aliphatic_group1 = {'A':'a', 'L':'l', 'M':'m', 'I':'i', 'V':'v'} @@ -95,103 +96,8 @@ def get_charge_weighted_mask(sequence1, sequence2): matrix here, but this function does return """ - # - # NB - this could be rewritten in Cython for improved performance... - # - - # nb - hardcoded for now but could and probably should be altered to enable - # pH-dependent effects in the future - charges = ['R','K','E','D'] - - attractive_matrix = [] - repulsive_matrix = [] - - n2 = len(sequence2) - - # cycle through each residue - for i,r1 in enumerate(sequence1): - tmp_attractive = [] - tmp_repulsive = [] - - # if r1 is charged - if r1 in charges: - - # cycle through each residue in sequence 2 - for j,r2 in enumerate(sequence2): - - # initialize - w_attractive = 0 - w_repulsive = 0 - - # if the second residue is charged - if r2 in charges: - - # this generates a string of max 6 residues (for terminal residues 5 or 4 residues) - # which is basically a concatenated fragment - l_resis = sequence_tools.get_neighbors_window_of3(i,sequence1) + sequence_tools.get_neighbors_window_of3(j,sequence2) - - # for that fragment, calculate the local fcr and ncpr - [local_fcr, local_ncpr] = sequence_tools.calculate_FCR_and_NCPR(l_resis) - - # calculate the charge weight as |NCPR/FRC|. This means in one limit charge_weight goes - # to 1 if the fragment is all the same type of charged residues, and goes to 0 if the - # if the fragment is neutral, regardless of the fraction of charged residues. - chrg_weight = np.abs(local_ncpr / local_fcr) - - w_repulsive = chrg_weight - - - # alternative implementation - to move elsewhere at some point, but TL/DR was worse - # but ALSO SLOWER! Win win! - """ - - frag1 = sequence_tools.get_neighbors_window_of3(i, sequence1) - frag2 = sequence_tools.get_neighbors_window_of3(j, sequence2) - - [f1_fcr, f1_ncpr] = sequence_tools.calculate_FCR_and_NCPR(frag1) - [f2_fcr, f2_ncpr] = sequence_tools.calculate_FCR_and_NCPR(frag2) - - # if both fragments contain only one type of charged residue - if abs(f1_ncpr) == f1_fcr and abs(f2_ncpr) == f2_fcr: - - # calculate charge weight - q1q2 = f1_ncpr*f2_ncpr - - # opposite charge clusters - if q1q2 < 0: - - # negative value (attractive) - max value = 1 (|q1| and |q2| <= 1) - w_attractive = abs(q1q2) - - else: - - # positive value (repulsive) - max value = 1 (|q1| and |q1| <= 1) - w_repulsive = q1q2 - """ - - # w_attractive and w_repulsive are 0 unless both fragements only possess the same - # type of charged residues - tmp_attractive.append(w_attractive) - tmp_repulsive.append(w_repulsive) - - - else: - - # if r1 was not charged, create an empty vector - tmp_attractive = [0]*n2 - tmp_repulsive = [0]*n2 - - attractive_matrix.append(tmp_attractive) - repulsive_matrix.append(tmp_repulsive) - - # Assert matrices are the right shape - attractive_matrix = np.array(attractive_matrix) - repulsive_matrix = np.array(repulsive_matrix) - - assert attractive_matrix.shape == (len(sequence1), len(sequence2)) - assert repulsive_matrix.shape == (len(sequence1), len(sequence2)) - - return attractive_matrix, repulsive_matrix + attractive_matrix, repulsive_matrix = matrix_manipulation.charge_weighted_mask(sequence1, sequence2) + return np.asarray(attractive_matrix), np.asarray(repulsive_matrix) @@ -281,29 +187,15 @@ def get_aliphatic_weighted_mask(sequence1, sequence2): returns a 2D MATRIX mask the same shape of (len(sequence1), len(sequence2)) """ - multiplier_weighting = {'1_1':1, '1_2':1, '1_3':1, '2_1':1, '3_1':1, - '2_2':1.5, '2_3':1.5, '3_2':1.5, - '3_3':3} - - - ali_mask1 = get_aliphatic_groups(sequence1) - ali_mask2 = get_aliphatic_groups(sequence2) - n2 = len(sequence2) - matrix = [] - for i,v1 in enumerate(ali_mask1): - tmp = [] - if v1 > 0: - for j,v2 in enumerate(ali_mask2): - if v2 > 0: - tmp.append(multiplier_weighting[f'{v1}_{v2}']) - else: - tmp.append(1) - else: - tmp = [1]*n2 - - matrix.append(tmp) - - return np.array(matrix) + multiplier_weighting = np.ones((4, 4), dtype=float) + multiplier_weighting[2, 2] = 1.5 + multiplier_weighting[2, 3] = 1.5 + multiplier_weighting[3, 2] = 1.5 + multiplier_weighting[3, 3] = 3.0 + + ali_mask1 = np.asarray(get_aliphatic_groups(sequence1), dtype=np.int8) + ali_mask2 = np.asarray(get_aliphatic_groups(sequence2), dtype=np.int8) + return multiplier_weighting[ali_mask1[:, None], ali_mask2[None, :]] ## --------------------------------------------------------------------------- diff --git a/finches/tests/test_FH_diagrams.py b/finches/tests/test_FH_diagrams.py index a4e4b49..8349090 100644 --- a/finches/tests/test_FH_diagrams.py +++ b/finches/tests/test_FH_diagrams.py @@ -9,10 +9,10 @@ from finches.forcefields.calvados import calvados_model from finches import epsilon_calculation -from ..test_data.test_sequences import test_sequences, t0 +from .test_data.test_sequences import test_sequences, t0 # test are done in the context with the mPiPi_GGv1 model -L_model = mPiPi_model('mPiPi_GGv1') +L_model = mpipi_model('mPiPi_GGv1') X_local = epsilon_calculation.Interaction_Matrix_Constructor(L_model) ############################################################################################ @@ -28,4 +28,3 @@ # def build_DIELECTRIC_dependent_phase_diagrams(): pass - diff --git a/finches/tests/test_data/mPiPi_GGv1_seq_epsilon_and_vectors.npz b/finches/tests/test_data/mPiPi_GGv1_seq_epsilon_and_vectors.npz new file mode 100644 index 0000000000000000000000000000000000000000..c8aa9aa41b5748f65ac6ea5e3ab4111d96e73cf9 GIT binary patch literal 8584 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 literal 0 HcmV?d00001 diff --git a/finches/tests/test_data/test_mPiPi_GGv1_homotypic_matrix.npz b/finches/tests/test_data/test_mPiPi_GGv1_homotypic_matrix.npz new file mode 100644 index 0000000000000000000000000000000000000000..342f63c81385c5e15e805d3963c7344a5d8ee0e0 GIT binary patch literal 281128 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 literal 0 HcmV?d00001 diff --git a/finches/tests/test_data/test_mPiPi_GGv1_weighted_matrix.npz b/finches/tests/test_data/test_mPiPi_GGv1_weighted_matrix.npz new file mode 100644 index 0000000000000000000000000000000000000000..fe0fecc0e2272725a33f7387fb0527af410b1847 GIT binary patch literal 729724 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 literal 0 HcmV?d00001 diff --git a/finches/tests/test_data/update_test_data.py b/finches/tests/test_data/update_test_data.py index 8f49ea6..eb902df 100644 --- a/finches/tests/test_data/update_test_data.py +++ b/finches/tests/test_data/update_test_data.py @@ -11,8 +11,9 @@ import os import finches +from finches import epsilon_calculation -from test_sequences import test_sequences, test_condition_dict +from .test_sequences import test_sequences, test_condition_dict, t0 # .......................................................................................... # @@ -78,7 +79,7 @@ def write_test_weighted_matrix(filepath, model, model_name): otl2.append(ot) print(ot.shape) - np.savez(data_file, DEFAULT=otl, NOCHARGE=otl1, NOuse_aliphatic_weighting=otl2) + np.savez(data_file, DEFAULT=otl, NOCHARGE=otl1, NOALIPHATICS=otl2) #.......................................................................................... # @@ -101,7 +102,8 @@ def write_test_matrix_manipulation(filepath, model): otla, otlr = get_attractive_repulsive_matrixes(all_manipulated['test_matrix'],-0.15) all_manipulated['attractive_repulsive_matrixes'] = otla, otlr - mask = np.random.choice([0, 1], size=test_matrix.shape) + rng = np.random.default_rng(0) + mask = rng.choice([0, 1], size=test_matrix.shape) all_manipulated['bionary_mask'] = mask out_masked = epsilon_calculation.mask_matrix(test_matrix, mask) @@ -200,4 +202,3 @@ def write_FH_out_data(filepath, model, model_name): - diff --git a/finches/tests/test_epsilon_calculation.py b/finches/tests/test_epsilon_calculation.py index a5b8c45..e2c5734 100644 --- a/finches/tests/test_epsilon_calculation.py +++ b/finches/tests/test_epsilon_calculation.py @@ -1,5 +1,6 @@ import pytest #import un +from pathlib import Path import pandas as pd @@ -17,6 +18,9 @@ import numpy as np + +TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" + ############################################################################################ ## ## ## ## @@ -35,7 +39,7 @@ def test_Interaction_Matrix_Constructor(): # # def test_calculate_pairwise_homotypic_matrix(): - TRUE_matrixes = np.load('test_data/test_mPiPi_GGv1_homotypic_matrix.npz', allow_pickle=True) + TRUE_matrixes = np.load(TEST_DATA_DIR / 'test_mPiPi_GGv1_homotypic_matrix.npz', allow_pickle=True) for i, t in enumerate(test_sequences): test_array = X_local.calculate_pairwise_homotypic_matrix(t) @@ -47,9 +51,9 @@ def test_calculate_pairwise_homotypic_matrix(): # # def test_calculate_pairwise_heterotypic_matrix(): - TRUE_matrixes = np.load('test_data/test_mPiPi_GGv1_heterotypic_matrix.npz', allow_pickle=True) + TRUE_matrixes = np.load(TEST_DATA_DIR / 'test_mPiPi_GGv1_heterotypic_matrix.npz', allow_pickle=True) - for i, t in test_sequences: + for i, t in enumerate(test_sequences): test_array = X_local.calculate_pairwise_heterotypic_matrix(t,t0) # expect this file to match precomputed heterotypic matrix @@ -59,9 +63,9 @@ def test_calculate_pairwise_heterotypic_matrix(): # # def test_calculate_weighted_pairwise_matrix(): - TRUE_matrixes = np.load('test_data/test_mPiPi_GGv1_weighted_matrix.npz', allow_pickle=True) + TRUE_matrixes = np.load(TEST_DATA_DIR / 'test_mPiPi_GGv1_weighted_matrix.npz', allow_pickle=True) - for i, t in test_sequences: + for i, t in enumerate(test_sequences): # test defaults test_array = X_local.calculate_weighted_pairwise_matrix(t,t0) @@ -88,12 +92,12 @@ def test_calculate_weighted_pairwise_matrix(): # # def test_get_attractive_repulsive_matrixes(): - TRUE_matrixes = np.load('test_data/test_matrix_manipulation.npz', allow_pickle=True) + TRUE_matrixes = np.load(TEST_DATA_DIR / 'test_matrix_manipulation.npz', allow_pickle=True) # compare the heterotypic DEFAULT matrix of test1:t0 test_matrix = TRUE_matrixes['test_matrix'] - TRUEattractive_matrix = TRUE_matrixes['attractive_repulsive_matrixes'][0] - TRUErepulsive_matrix = TRUE_matrixes['attractive_repulsive_matrixes'][1] + TRUEattractive_matrix = np.asarray(TRUE_matrixes['attractive_repulsive_matrixes'][0], dtype=float) + TRUErepulsive_matrix = np.asarray(TRUE_matrixes['attractive_repulsive_matrixes'][1], dtype=float) attractive_matrix, repulsive_matrix = epsilon_calculation.get_attractive_repulsive_matrixes(test_matrix,-0.15) @@ -104,7 +108,7 @@ def test_get_attractive_repulsive_matrixes(): # # def test_mask_matrix(): - TRUE_matrixes = np.load('test_data/test_matrix_manipulation.npz', allow_pickle=True) + TRUE_matrixes = np.load(TEST_DATA_DIR / 'test_matrix_manipulation.npz', allow_pickle=True) # compare the heterotypic DEFAULT matrix of test1:t0 test_matrix = TRUE_matrixes['test_matrix'] @@ -123,7 +127,7 @@ def test_mask_matrix(): # # def test_flatten_matrix_to_vector(): - TRUE_matrixes = np.load('test_data/test_matrix_manipulation.npz', allow_pickle=True) + TRUE_matrixes = np.load(TEST_DATA_DIR / 'test_matrix_manipulation.npz', allow_pickle=True) # compare vectors to truth test_matrix = TRUE_matrixes['test_matrix'] @@ -148,7 +152,7 @@ def test_flatten_matrix_to_vector(): # # def test_get_sequence_epsilon_vectors(): - TRUE_matrixes = np.load('test_data/mPiPi_GGv1_seq_epsilon_and_vectors.npz', allow_pickle=True) + TRUE_matrixes = np.load(TEST_DATA_DIR / 'mPiPi_GGv1_seq_epsilon_and_vectors.npz', allow_pickle=True) # test t0 with test1 and t0 names = ['t', 't0'] @@ -157,13 +161,13 @@ def test_get_sequence_epsilon_vectors(): n = names[i] # compare vectors to truth default - [attractive_vector, repulsive_vector] = all_manipulated[f'{n}_t0_NOWEIGHTING'] + [attractive_vector, repulsive_vector] = TRUE_matrixes[f'{n}_t0_DEFAULT'] TESTattractive_vector, TESTrepulsive_vector = epsilon_calculation.get_sequence_epsilon_vectors(t,t0,X_local) assert np.allclose(TESTattractive_vector, attractive_vector) assert np.allclose(TESTrepulsive_vector, repulsive_vector) # compare vectors instance with passed baseline - [attractive_vector, repulsive_vector] = all_manipulated[f'{n}_t0_prefactor_baseline'] + [attractive_vector, repulsive_vector] = TRUE_matrixes[f'{n}_t0_prefactor_baseline'] TESTattractive_vector, TESTrepulsive_vector = epsilon_calculation.get_sequence_epsilon_vectors(t,t0,X_local, prefactor=0.25, null_interaction_baseline=-0.15) @@ -172,7 +176,7 @@ def test_get_sequence_epsilon_vectors(): # compare vectors instance with no weighting - [attractive_vector, repulsive_vector] = all_manipulated[f'{n}_t0_NOWEIGHTING'] + [attractive_vector, repulsive_vector] = TRUE_matrixes[f'{n}_t0_NOWEIGHTING'] TESTattractive_vector, TESTrepulsive_vector = epsilon_calculation.get_sequence_epsilon_vectors(t,t0,X_local, use_charge_weighting=False, use_aliphatic_weighting=False) @@ -183,4 +187,41 @@ def test_get_sequence_epsilon_vectors(): pass +# .......................................................................................... +# +# +def test_calculate_sliding_epsilon_matches_reference_window_scores(): + test_matrix = np.array( + [ + [-0.20, -0.10, 0.05, -0.15], + [0.10, -0.25, -0.05, 0.20], + [-0.30, 0.15, -0.12, -0.18], + [0.08, -0.22, 0.25, -0.05], + ], + dtype=float, + ) + baseline = -0.15 + window_size = 3 + + def _reference_window_score(submatrix): + attractive_matrix = np.where(submatrix < baseline, submatrix - baseline, -baseline) + repulsive_matrix = np.where(submatrix > baseline, submatrix - baseline, -baseline) + return np.sum(np.mean(attractive_matrix, axis=1)) + np.sum(np.mean(repulsive_matrix, axis=1)) + + expected = np.array( + [ + [_reference_window_score(test_matrix[0:3, 0:3]), _reference_window_score(test_matrix[0:3, 1:4])], + [_reference_window_score(test_matrix[1:4, 0:3]), _reference_window_score(test_matrix[1:4, 1:4])], + ] + ) + + observed, seq1_indices, seq2_indices = epsilon_calculation.matrix_manipulation.matrix_scan( + test_matrix, + window_size, + baseline, + ) + + assert np.allclose(observed, expected) + assert np.array_equal(seq1_indices, np.array([2, 3])) + assert np.array_equal(seq2_indices, np.array([2, 3])) diff --git a/finches/utils/matrix_manipulation.pyx b/finches/utils/matrix_manipulation.pyx index f3f39c2..9bdb4c4 100644 --- a/finches/utils/matrix_manipulation.pyx +++ b/finches/utils/matrix_manipulation.pyx @@ -8,6 +8,18 @@ import array from libc.stdlib cimport rand, srand, RAND_MAX +cdef inline bint _is_charged(char residue) nogil: + return residue == 'R' or residue == 'K' or residue == 'E' or residue == 'D' + + +cdef inline double _charge_value(char residue) nogil: + if residue == 'R' or residue == 'K': + return 1.0 + if residue == 'E' or residue == 'D': + return -1.0 + return 0.0 + + @cython.boundscheck(False) @cython.cdivision(True) @@ -28,6 +40,80 @@ def dict2matrix(str seq1, str seq2, dict lookup): return matrix +@cython.boundscheck(False) +@cython.cdivision(True) +def charge_weighted_mask(str seq1, str seq2): + cdef int i, j, start, end, idx, l1, l2 + cdef double total_charge + cdef int total_count + + l1 = len(seq1) + l2 = len(seq2) + + cdef cnp.ndarray[cnp.float64_t, ndim=2] attractive_matrix = np.zeros((l1, l2), dtype=np.float64) + cdef cnp.ndarray[cnp.float64_t, ndim=2] repulsive_matrix = np.zeros((l1, l2), dtype=np.float64) + + cdef cnp.ndarray[cnp.float64_t, ndim=1] charge_sum_1 = np.zeros(l1, dtype=np.float64) + cdef cnp.ndarray[cnp.int32_t, ndim=1] charge_count_1 = np.zeros(l1, dtype=np.int32) + cdef cnp.ndarray[cnp.uint8_t, ndim=1] charged_1 = np.zeros(l1, dtype=np.uint8) + + cdef cnp.ndarray[cnp.float64_t, ndim=1] charge_sum_2 = np.zeros(l2, dtype=np.float64) + cdef cnp.ndarray[cnp.int32_t, ndim=1] charge_count_2 = np.zeros(l2, dtype=np.int32) + cdef cnp.ndarray[cnp.uint8_t, ndim=1] charged_2 = np.zeros(l2, dtype=np.uint8) + + for i in range(l1): + if _is_charged(seq1[i]): + charged_1[i] = 1 + + start = i - 1 + if start < 0: + start = 0 + end = i + 2 + if end > l1: + end = l1 + + total_charge = 0.0 + total_count = 0 + for idx in range(start, end): + if _is_charged(seq1[idx]): + total_charge += _charge_value(seq1[idx]) + total_count += 1 + charge_sum_1[i] = total_charge + charge_count_1[i] = total_count + + for j in range(l2): + if _is_charged(seq2[j]): + charged_2[j] = 1 + + start = j - 1 + if start < 0: + start = 0 + end = j + 2 + if end > l2: + end = l2 + + total_charge = 0.0 + total_count = 0 + for idx in range(start, end): + if _is_charged(seq2[idx]): + total_charge += _charge_value(seq2[idx]) + total_count += 1 + charge_sum_2[j] = total_charge + charge_count_2[j] = total_count + + for i in range(l1): + if charged_1[i] == 0: + continue + for j in range(l2): + if charged_2[j] == 0: + continue + total_charge = charge_sum_1[i] + charge_sum_2[j] + total_count = charge_count_1[i] + charge_count_2[j] + repulsive_matrix[i, j] = abs(total_charge / total_count) + + return attractive_matrix, repulsive_matrix + + @cython.boundscheck(False) @cython.cdivision(True) def matrix_scan(double[:,:] w_matrix, int window_size, double null_interaction_baseline): @@ -69,8 +155,11 @@ def matrix_scan(double[:,:] w_matrix, int window_size, double null_interaction_b # define the variables - cdef int l1, l2, i, j, start, end, r1, r2; - cdef double row_sum, total_mean_sum; + cdef int l1, l2, start, end + cdef cnp.ndarray[cnp.float64_t, ndim=2] matrix = np.asarray(w_matrix, dtype=np.float64) + cdef cnp.ndarray[cnp.float64_t, ndim=2] transformed + cdef cnp.ndarray[cnp.float64_t, ndim=2] integral + cdef cnp.ndarray[cnp.float64_t, ndim=2] everything # get dimensions of matrix l1 = w_matrix.shape[0] @@ -81,55 +170,16 @@ def matrix_scan(double[:,:] w_matrix, int window_size, double null_interaction_b raise Exception('Window size is larger than matrix size, cannot calculate sliding epsilon') - # preallocate the various matrices being used - cdef cnp.ndarray[cnp.float64_t, ndim=2] everything = np.empty( [(l1-window_size)+1,(l2-window_size)+1], dtype=np.float64) - cdef cnp.ndarray[double, ndim=2] attractive_matrix = np.empty([window_size, window_size], dtype=np.double) - cdef cnp.ndarray[double, ndim=2] repulsive_matrix = np.empty([window_size, window_size], dtype=np.double) - cdef cnp.ndarray[double, ndim=2] sub = np.empty([window_size, window_size], dtype=np.double) - - - # calculate sliding epsilon for all possible intermolecular windows. - for i in range(0,(l1-window_size)+1): - - for j in range(0, (l2-window_size)+1): - - - # copy the memoryview into a numpy array - for r1 in range(i, i+window_size): - for r2 in range(j, j+window_size): - sub[r1-i,r2-j] = w_matrix[r1,r2] - - # construct the attractive and repulsive matrices, and then sum them - note we do this - # so brutally manually because it will then compile down to pure C which buys us all - # the speed! - for r1 in range(window_size): - for r2 in range(window_size): - - if sub[r1,r2] < null_interaction_baseline: - attractive_matrix[r1,r2] = sub[r1,r2] - null_interaction_baseline - else: - attractive_matrix[r1,r2] = - null_interaction_baseline - - if sub[r1,r2] > null_interaction_baseline: - repulsive_matrix[r1,r2] = sub[r1,r2] - null_interaction_baseline - else: - repulsive_matrix[r1,r2] = -null_interaction_baseline - - # here we sum and average the attractive and repulsive means - total_mean_sum = 0.0 - for r1 in range(window_size): - row_sum = 0.0 - for r2 in range(window_size): - row_sum += attractive_matrix[r1, r2] - total_mean_sum += row_sum / window_size # Add the mean of the current row to the total sum - - for r1 in range(window_size): - row_sum = 0.0 - for r2 in range(window_size): - row_sum += repulsive_matrix[r1, r2] - total_mean_sum += row_sum / window_size # Add the mean of the current row to the total sum - - everything[i,j] = total_mean_sum + transformed = matrix - (2.0 * null_interaction_baseline) + transformed[matrix == null_interaction_baseline] -= null_interaction_baseline + + integral = np.pad(transformed, ((1, 0), (1, 0)), mode="constant").cumsum(axis=0).cumsum(axis=1) + everything = ( + integral[window_size:, window_size:] + - integral[:-window_size, window_size:] + - integral[window_size:, :-window_size] + + integral[:-window_size, :-window_size] + ) / float(window_size) # finally, determine indices for sequence1 - note need +1 for indexing to move from Python