@@ -1480,14 +1480,6 @@ def _match_all_isoforms_for_plotting(
14801480 minor_isoform ,
14811481 min_similarity_for_matching = 0.4
14821482):
1483- """
1484- Match ALL reference isoforms (including zero-expressed ones) to isoforms in other haplotypes.
1485-
1486- Returns
1487- -------
1488- dict
1489- Key: (haplotype, ref_isoform_id), Value: {'transcript_idx', 'transcript_id', 'similarity'}
1490- """
14911483 import numpy as np
14921484
14931485 matches = {}
@@ -1499,52 +1491,55 @@ def _match_all_isoforms_for_plotting(
14991491 if len (hap_indices ) == 0 :
15001492 continue
15011493
1502- # For each reference isoform, find best match in this haplotype
1503- for ref_iso in all_ref_isoforms :
1504- ref_iso_id = ref_iso ['transcript_id' ]
1505- ref_exon_struct = ref_iso ['exon_structure' ]
1506- ref_intron_struct = ref_iso ['intron_structure' ]
1507-
1508- # If this is the reference haplotype, use direct match
1509- if hap == reference_haplotype :
1510- matches [(hap , ref_iso_id )] = {
1494+ if hap == reference_haplotype :
1495+ for ref_iso in all_ref_isoforms :
1496+ matches [(hap , ref_iso ['transcript_id' ])] = {
15111497 'transcript_idx' : ref_iso ['transcript_idx' ],
1512- 'transcript_id' : ref_iso_id ,
1498+ 'transcript_id' : ref_iso [ 'transcript_id' ] ,
15131499 'similarity' : 1.0
15141500 }
1515- continue
1516-
1517- # Find best matching transcript in this haplotype
1518- best_match_idx = None
1519- best_similarity = 0.0
1520-
1501+ continue
1502+
1503+ # Build full similarity matrix: ref_isoform x hap_transcript
1504+ similarity_matrix = {}
1505+ for ref_iso in all_ref_isoforms :
1506+ ref_iso_id = ref_iso [ 'transcript_id' ]
15211507 for idx in hap_indices :
15221508 tid = transcript_ids [idx ]
15231509 exon_struct = exon_lengths_dict .get (tid , [])
15241510 intron_struct = intron_lengths_dict .get (tid , [])
15251511
15261512 similarity = _calculate_combined_structure_similarity (
1527- ref_exon_struct , exon_struct ,
1528- ref_intron_struct , intron_struct
1513+ ref_iso [ 'exon_structure' ] , exon_struct ,
1514+ ref_iso [ 'intron_structure' ] , intron_struct
15291515 )
1530-
1531- if similarity > best_similarity :
1532- best_similarity = similarity
1533- best_match_idx = idx
1516+ similarity_matrix [(ref_iso_id , idx )] = similarity
1517+
1518+ # Greedy assignment: best similarity pair first, no reuse of hap transcripts
1519+ assigned_hap_transcripts = set ()
1520+ assigned_ref_isoforms = set ()
1521+
1522+ # Sort all pairs by similarity descending
1523+ sorted_pairs = sorted (similarity_matrix .items (), key = lambda x : x [1 ], reverse = True )
1524+
1525+ for (ref_iso_id , idx ), similarity in sorted_pairs :
1526+ if ref_iso_id in assigned_ref_isoforms :
1527+ continue
1528+ if idx in assigned_hap_transcripts :
1529+ continue
1530+ if similarity < min_similarity_for_matching :
1531+ continue # remaining pairs will only be worse
15341532
1535- # Only add if similarity meets threshold
1536- if best_match_idx is not None and best_similarity >= min_similarity_for_matching :
1537- matches [( hap , ref_iso_id )] = {
1538- 'transcript_idx ' : best_match_idx ,
1539- 'transcript_id' : transcript_ids [ best_match_idx ],
1540- 'similarity' : best_similarity
1541- }
1533+ matches [( hap , ref_iso_id )] = {
1534+ 'transcript_idx' : idx ,
1535+ 'transcript_id' : transcript_ids [ idx ],
1536+ 'similarity ' : similarity
1537+ }
1538+ assigned_hap_transcripts . add ( idx )
1539+ assigned_ref_isoforms . add ( ref_iso_id )
15421540
15431541 return matches
15441542
1545-
1546-
1547-
15481543def _identify_major_minor_isoforms (
15491544 synt_indices , haplotypes , transcript_ids ,
15501545 exon_lengths_dict , intron_lengths_dict ,
@@ -1864,93 +1859,3 @@ def _calculate_length_based_similarity(lengths1, lengths2, tolerance=10):
18641859
18651860 # Ensure the result is bounded between 0 and 1
18661861 return max (0.0 , min (1.0 , final_similarity ))
1867-
1868-
1869-
1870- def _calculate_length_based_similarity (lengths1 , lengths2 , tolerance = 10 ):
1871- """
1872- Calculate similarity between two genomic structures based on element counts and lengths.
1873-
1874- Parameters
1875- ----------
1876- lengths1, lengths2 : list of int
1877- Lengths of genomic elements (exons or introns)
1878- tolerance : int, default=10
1879- Base pair tolerance for length comparison
1880-
1881- Returns
1882- -------
1883- float
1884- Similarity score between 0 and 1
1885- """
1886- if not lengths1 or not lengths2 :
1887- return 0.0
1888-
1889- # Ensure we're working with lists
1890- if not isinstance (lengths1 , list ):
1891- lengths1 = [lengths1 ]
1892- if not isinstance (lengths2 , list ):
1893- lengths2 = [lengths2 ]
1894-
1895- # Component 1: Check if the number of elements is the same
1896- count1 = len (lengths1 )
1897- count2 = len (lengths2 )
1898-
1899- # Penalize heavily for different counts
1900- if count1 != count2 :
1901- count_similarity = 1.0 - abs (count1 - count2 ) / max (count1 , count2 )
1902- # If counts differ significantly, return low similarity
1903- if count_similarity < 0.5 :
1904- return count_similarity * 0.5 # Max 0.25 if counts differ a lot
1905- else :
1906- count_similarity = 1.0
1907-
1908- # Component 2: Calculate length similarity for corresponding elements
1909- # Sort lengths to compare corresponding elements by size
1910- lengths1_sorted = sorted (lengths1 )
1911- lengths2_sorted = sorted (lengths2 )
1912-
1913- # Compare corresponding lengths (pair shortest with shortest, etc.)
1914- n_comparisons = min (len (lengths1_sorted ), len (lengths2_sorted ))
1915-
1916- if n_comparisons == 0 :
1917- return 0.0
1918-
1919- length_similarities = []
1920- for i in range (n_comparisons ):
1921- len1 = lengths1_sorted [i ]
1922- len2 = lengths2_sorted [i ]
1923-
1924- # Calculate absolute difference
1925- diff = abs (len1 - len2 )
1926-
1927- # Apply tolerance: differences within tolerance have no penalty
1928- if diff <= tolerance :
1929- similarity = 1.0
1930- else :
1931- # Calculate similarity based on relative difference beyond tolerance
1932- adjusted_diff = diff - tolerance
1933- avg_length = (len1 + len2 ) / 2
1934- # Use exponential decay for penalty to avoid harsh drops
1935- similarity = max (0.0 , 1.0 - (adjusted_diff / avg_length ))
1936-
1937- length_similarities .append (similarity )
1938-
1939- # Average length similarity
1940- avg_length_similarity = sum (length_similarities ) / n_comparisons
1941-
1942- # Penalize for extra elements (if counts differ)
1943- if count1 != count2 :
1944- extra_elements = abs (count1 - count2 )
1945- penalty = extra_elements / max (count1 , count2 ) * 0.2 # 20% penalty per extra element
1946- avg_length_similarity *= (1 - penalty )
1947-
1948- # Combine count and length similarity
1949- # Give more weight to length similarity if counts match
1950- if count1 == count2 :
1951- final_similarity = 0.2 * count_similarity + 0.8 * avg_length_similarity
1952- else :
1953- final_similarity = 0.5 * count_similarity + 0.5 * avg_length_similarity
1954-
1955- # Ensure the result is bounded between 0 and 1
1956- return max (0.0 , min (1.0 , final_similarity ))
0 commit comments