nova-clone 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. nova_clone/__init__.py +1 -0
  2. nova_clone/algorithm/__init__.py +2 -0
  3. nova_clone/algorithm/_build_intbc_graph.py +135 -0
  4. nova_clone/algorithm/_call_clones_from_coresets.py +235 -0
  5. nova_clone/algorithm/_clone_call_ops.py +258 -0
  6. nova_clone/algorithm/_clone_calling_algorithm.py +1571 -0
  7. nova_clone/algorithm/_compute_cellbc_vs_coreset_overlap_matrix.py +143 -0
  8. nova_clone/algorithm/_compute_coreset_co_assignment_matrix.py +170 -0
  9. nova_clone/algorithm/_find_maximal_coresets.py +428 -0
  10. nova_clone/algorithm/_keep_reliable_edges.py +202 -0
  11. nova_clone/algorithm/_make_coresets_disjoint.py +114 -0
  12. nova_clone/algorithm/_prune_coresets.py +164 -0
  13. nova_clone/algorithm/_prune_custom_intbcs.py +150 -0
  14. nova_clone/algorithm/_prune_likely_homoplastic_intbcs.py +162 -0
  15. nova_clone/algorithm/_prune_likely_sequencing_errors.py +201 -0
  16. nova_clone/algorithm/_prune_low_cellbc_count_intbcs.py +154 -0
  17. nova_clone/algorithm/_prune_low_umi_count_intbcs.py +152 -0
  18. nova_clone/algorithm/_types.py +150 -0
  19. nova_clone/algorithm/_utils.py +53 -0
  20. nova_clone/algorithm/cassiopeia/__init__.py +1 -0
  21. nova_clone/algorithm/cassiopeia/_call_lineage_groups.py +270 -0
  22. nova_clone/caching/__init__.py +24 -0
  23. nova_clone/caching/_cached.py +175 -0
  24. nova_clone/caching/_cached_computation.py +369 -0
  25. nova_clone/caching/_cached_parallel_computation.py +428 -0
  26. nova_clone/caching/_common.py +200 -0
  27. nova_clone/data/__init__.py +1 -0
  28. nova_clone/qc/__init__.py +179 -0
  29. nova_clone/qc/_clone_intbc_sets.py +234 -0
  30. nova_clone/qc/_compare_clone_calls.py +218 -0
  31. nova_clone/qc/_data_retention.py +175 -0
  32. nova_clone/qc/_homoplasy.py +81 -0
  33. nova_clone/qc/_inter_clonal_kinship.py +60 -0
  34. nova_clone/qc/_intra_clonal_kinship.py +172 -0
  35. nova_clone/qc/_multiplet_rate.py +158 -0
  36. nova_clone/qc/_number_of_clones.py +153 -0
  37. nova_clone/qc/_pre_qc.py +485 -0
  38. nova_clone/simulation/HDBSCAN_method_adjusted.py +68 -0
  39. nova_clone/simulation/__init__.py +0 -0
  40. nova_clone/simulation/_caching.py +40 -0
  41. nova_clone/simulation/benchmarking.py +1221 -0
  42. nova_clone/simulation/clone_to_sequences.py +408 -0
  43. nova_clone/simulation/creating_tree.py +416 -0
  44. nova_clone/simulation/evaluation.py +344 -0
  45. nova_clone/simulation/jaccard_hierarchical_by_intBC.py +78 -0
  46. nova_clone/simulation/plot_benchmark_lines.py +226 -0
  47. nova_clone/simulation/slice_benchmark.py +640 -0
  48. nova_clone/simulation/slice_benchmark_clone_calling.py +68 -0
  49. nova_clone-0.1.0.dist-info/METADATA +298 -0
  50. nova_clone-0.1.0.dist-info/RECORD +53 -0
  51. nova_clone-0.1.0.dist-info/WHEEL +5 -0
  52. nova_clone-0.1.0.dist-info/licenses/LICENSE +28 -0
  53. nova_clone-0.1.0.dist-info/top_level.txt +1 -0
nova_clone/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from .algorithm import clone_calling_algorithm
@@ -0,0 +1,2 @@
1
+ from ._clone_calling_algorithm import clone_calling_algorithm
2
+ from ._clone_call_ops import merge_clones, drop_clones
@@ -0,0 +1,135 @@
1
+ """
2
+ Module for building an IntBC graph from a list of intBC tables.
3
+
4
+ The main function exposed by this module is `build_intbc_graph`.
5
+ """
6
+
7
+ import logging
8
+ import sys
9
+ import time
10
+ from collections import defaultdict
11
+ from typing import Set
12
+
13
+ from ._types import *
14
+ from ._utils import _cell_intbc_sets_from_intbc_tables
15
+
16
+
17
+ def _init_logger():
18
+ logger = logging.getLogger(__name__)
19
+ logger.setLevel(logging.INFO)
20
+ fmt_str = "[%(asctime)s] - %(name)s - %(levelname)s - %(message)s"
21
+ formatter = logging.Formatter(fmt_str)
22
+
23
+ consoleHandler = logging.StreamHandler(sys.stdout)
24
+ consoleHandler.setFormatter(formatter)
25
+ logger.addHandler(consoleHandler)
26
+
27
+
28
+ _init_logger()
29
+
30
+
31
+ class BuildIntBCGraphReturnType(NamedTuple):
32
+ """
33
+ Object returned by the build_intbc_graph function.
34
+
35
+ Attributes:
36
+ intbc_graph: The intBC graph, of type GraphType.
37
+ total_time: Total time taken.
38
+ """
39
+
40
+ intbc_graph: GraphType
41
+ total_time: float
42
+
43
+
44
+ def build_intbc_graph(
45
+ intbc_table_per_sample: Dict[SampleNameType, IntBCTableType],
46
+ ) -> BuildIntBCGraphReturnType:
47
+ """
48
+ Build the intBC graph from a list of intBC tables.
49
+
50
+ Args:
51
+ intbc_table_per_sample: intBC table per sample.
52
+
53
+ Returns:
54
+ intbc_graph: The intBC graph, of type GraphType.
55
+ """
56
+ st = time.time()
57
+ logger = logging.getLogger(__name__)
58
+ logger.info("Going to build_intbc_graph ...")
59
+ edge_counts = defaultdict(lambda: defaultdict(int))
60
+ node_counts = defaultdict(int)
61
+ cell_intbc_sets = _cell_intbc_sets_from_intbc_tables(
62
+ intbc_table_per_sample=intbc_table_per_sample,
63
+ )
64
+ for cell_intbc_set in cell_intbc_sets.values():
65
+ for intbc_1 in cell_intbc_set:
66
+ node_counts[intbc_1] += 1
67
+ for intbc_2 in cell_intbc_set:
68
+ if intbc_1 == intbc_2:
69
+ continue
70
+ edge_counts[intbc_1][intbc_2] += 1
71
+ # Make sure that intBCs with no neighbors are present in the edge dict!
72
+ for intbc in node_counts:
73
+ edge_counts[intbc]
74
+ # Convert to a normal Dict
75
+ edge_counts = {x: {y: z for (y, z) in d.items()} for (x, d) in edge_counts.items()}
76
+ node_counts = {x: y for (x, y) in node_counts.items()}
77
+ intbc_graph = (edge_counts, node_counts)
78
+ total_time = time.time() - st
79
+ res = BuildIntBCGraphReturnType(
80
+ intbc_graph=intbc_graph,
81
+ total_time=total_time,
82
+ )
83
+ num_nodes = len(node_counts)
84
+ num_edges = sum([len(x) for x in edge_counts.values()]) // 2
85
+ logger.info(
86
+ f"IntBC graph has {num_nodes} nodes and {num_edges} edges. Density level: {100.0 * num_edges / (num_nodes * (num_nodes - 1.0) / 2.0 + 1e-9):.2f}%"
87
+ )
88
+ logger.info(f"Done with build_intbc_graph! Total time: {total_time}")
89
+ return res
90
+
91
+
92
+ def test_build_intbc_graph():
93
+ """
94
+ Basic test.
95
+ """
96
+ intbc_table_1 = pd.DataFrame(
97
+ data=[
98
+ ["cellBC_1", "AAA", 40],
99
+ ["cellBC_1", "BBB", 40],
100
+ ["cellBC_2", "AAA", 40],
101
+ ["cellBC_2", "BBB", 40],
102
+ ["cellBC_2", "CCC", 40],
103
+ ["cellBC_3", "DDD", 40],
104
+ ["cellBC_3", "EEE", 40],
105
+ ["cellBC_3", "FFF", 40],
106
+ ],
107
+ columns=["cellBC", "intBC", "UMI_count"],
108
+ )
109
+ intbc_table_2 = pd.DataFrame(
110
+ data=[
111
+ ["cellBC_1", "EEE", 10],
112
+ ["cellBC_1", "FFF", 40],
113
+ ["cellBC_2", "GGG", 40], # Just observed once
114
+ ],
115
+ columns=["cellBC", "intBC", "UMI_count"],
116
+ )
117
+ graph = build_intbc_graph(
118
+ intbc_table_per_sample={
119
+ "sample1": intbc_table_1,
120
+ "sample2": intbc_table_2,
121
+ },
122
+ ).intbc_graph
123
+ expected_graph = (
124
+ {
125
+ "AAA": {"BBB": 2, "CCC": 1},
126
+ "BBB": {"AAA": 2, "CCC": 1},
127
+ "CCC": {"AAA": 1, "BBB": 1},
128
+ "DDD": {"EEE": 1, "FFF": 1},
129
+ "EEE": {"DDD": 1, "FFF": 2},
130
+ "FFF": {"DDD": 1, "EEE": 2},
131
+ "GGG": {},
132
+ },
133
+ {"AAA": 2, "BBB": 2, "CCC": 1, "DDD": 1, "EEE": 2, "FFF": 2, "GGG": 1},
134
+ )
135
+ assert graph == expected_graph
@@ -0,0 +1,235 @@
1
+ """
2
+ Module for calling clones once coresets have been identified.
3
+
4
+ The main function exposed by this module is `call_clones_from_coresets`.
5
+ """
6
+
7
+ import time
8
+
9
+ import editdistance
10
+ import numpy as np
11
+ import pandas as pd
12
+ import tqdm
13
+
14
+ from ._compute_cellbc_vs_coreset_overlap_matrix import (
15
+ compute_cellbc_vs_coreset_overlap_matrix,
16
+ )
17
+ from ._prune_low_umi_count_intbcs import prune_low_umi_count_intbcs
18
+ from ._types import *
19
+ from ._utils import _cell_intbc_sets_from_intbc_tables
20
+
21
+ import logging
22
+ import sys
23
+
24
+ from ._types import *
25
+
26
+
27
+ def _init_logger():
28
+ logger = logging.getLogger(__name__)
29
+ logger.setLevel(logging.INFO)
30
+ fmt_str = "[%(asctime)s] - %(name)s - %(levelname)s - %(message)s"
31
+ formatter = logging.Formatter(fmt_str)
32
+
33
+ consoleHandler = logging.StreamHandler(sys.stdout)
34
+ consoleHandler.setFormatter(formatter)
35
+ logger.addHandler(consoleHandler)
36
+
37
+
38
+ _init_logger()
39
+
40
+
41
+ class CallClonesFromCoresetsReturnType(NamedTuple):
42
+ """
43
+ Object returned by the `call_clones_from_coresets` function.
44
+
45
+ Attributes:
46
+ clone_call: The clone call.
47
+ total_time: Total time taken.
48
+ """
49
+
50
+ clone_call: CloneCallType
51
+ total_time: float
52
+
53
+
54
+ def call_clones_from_coresets(
55
+ intbc_table_per_sample: Dict[SampleNameType, IntBCTableType],
56
+ min_intbc_umi_count: int,
57
+ coresets: List[Coreset[str]],
58
+ min_overlap_for_assigning_cellbc: int,
59
+ min_intbc_counts_for_adding_intbc_to_clone_intbc_set: int,
60
+ min_intbc_fraction_for_adding_to_clone_intbc_set: float,
61
+ edit_distance_cutoff: int,
62
+ custom_intbc_filtering_function: str,
63
+ ) -> CallClonesFromCoresetsReturnType:
64
+ """
65
+ Call clones given the coresets characterizing each clone.
66
+ """
67
+ st = time.time()
68
+
69
+ # Validate arguments
70
+ validate_intbc_table_per_sample(intbc_table_per_sample=intbc_table_per_sample)
71
+
72
+ # Filter intBCs based on min_intbc_umi_count
73
+ all_cellbc = set(
74
+ list(
75
+ _cell_intbc_sets_from_intbc_tables(
76
+ intbc_table_per_sample=intbc_table_per_sample
77
+ ).keys()
78
+ )
79
+ )
80
+ intbc_table_per_sample = prune_low_umi_count_intbcs(
81
+ intbc_table_per_sample=intbc_table_per_sample,
82
+ min_intbc_umi_count=min_intbc_umi_count
83
+ ).intbc_table_per_sample
84
+ cell_intbc_sets = _cell_intbc_sets_from_intbc_tables(
85
+ intbc_table_per_sample=intbc_table_per_sample,
86
+ )
87
+
88
+ cellbc_vs_coreset_overlap_matrix = compute_cellbc_vs_coreset_overlap_matrix( # We MUST prune low-count UMIs before doing this! Else the background RNA will mess it up. In constrast, it's OK to not have discarded sequencing errors (bc they are not in the coresets anyway) nor homoplastic intBCs (because they are also not in the coresets anyway). Sequencing errors and homoplastic intBCs will be recovered de-novo.
89
+ cellbc_intbc_sets=cell_intbc_sets,
90
+ coresets=coresets,
91
+ ).cellbc_vs_coreset_overlap_matrix
92
+
93
+ cells = sorted(list(cell_intbc_sets.keys()))
94
+ num_clones = cellbc_vs_coreset_overlap_matrix.shape[1]
95
+ num_cells = cellbc_vs_coreset_overlap_matrix.shape[0]
96
+ assert len(cells) == num_cells
97
+
98
+ # First compute clone_intbc_counts by using the confidently mapped cells.
99
+ clone_intbc_counts = {
100
+ clone_idx: defaultdict(int) for clone_idx in range(num_clones)
101
+ }
102
+ clone_mapping = {cell: {} for cell in cell_intbc_sets}
103
+ clone_to_cells_mapping = {clone_idx: set() for clone_idx in range(num_clones)}
104
+ clone_sizes = defaultdict(int)
105
+ for cell in tqdm.tqdm(cells):
106
+ clones_assigned = list(
107
+ np.where(
108
+ cellbc_vs_coreset_overlap_matrix.loc[cell, :]
109
+ >= min_overlap_for_assigning_cellbc
110
+ )[0]
111
+ )
112
+ clone_mapping[cell] = set(clones_assigned)
113
+ if len(clones_assigned) == 1:
114
+ # Is confidently assigned, so use to compute clone intBC sets
115
+ clone_idx = clones_assigned[0]
116
+ clone_to_cells_mapping[clone_idx].add(cell)
117
+ clone_sizes[clone_idx] += 1
118
+ for intbc in cell_intbc_sets[cell]:
119
+ clone_intbc_counts[clone_idx][intbc] += 1
120
+
121
+ # Now it's time to figure out the intBC sets of each clone from their intBC
122
+ # counts
123
+ clone_intbc_sets = {}
124
+ for clone_idx in tqdm.tqdm(range(num_clones)):
125
+ clone_size = clone_sizes[clone_idx]
126
+ kept_intbcs = []
127
+ intbc_counts = sorted(
128
+ clone_intbc_counts[clone_idx].items(), key=lambda x: -x[1]
129
+ )
130
+ for intbc, counts in intbc_counts:
131
+ if custom_intbc_filtering_function(intbc):
132
+ # Is weird, so we discard it immediately
133
+ continue
134
+ else:
135
+ # First see if it looks anything like another *kept* intBC
136
+ ds = {
137
+ kept_intbc: editdistance.eval(intbc, kept_intbc)
138
+ for kept_intbc in kept_intbcs
139
+ }
140
+ close_kept_intbcs = [
141
+ kept_intbc
142
+ for (kept_intbc, d) in ds.items()
143
+ if d <= edit_distance_cutoff
144
+ ]
145
+ if len(close_kept_intbcs) == 0:
146
+ # If it meets the fraction & count cutoff, we keep it
147
+ intbc_fraction = counts / clone_size
148
+ if (
149
+ (
150
+ (
151
+ intbc_fraction
152
+ >= min_intbc_fraction_for_adding_to_clone_intbc_set
153
+ )
154
+ )
155
+ and counts
156
+ >= min_intbc_counts_for_adding_intbc_to_clone_intbc_set
157
+ ):
158
+ kept_intbcs.append(intbc)
159
+ clone_intbc_sets[clone_idx] = set(kept_intbcs)
160
+
161
+ # At this point, we have our clone intBC sets! It's time to compute the
162
+ # cell intBC sets (simply by intersection)
163
+ observed_cell_intbc_sets = cell_intbc_sets
164
+ cell_intbc_sets = {}
165
+ for cell, clones in tqdm.tqdm(clone_mapping.items()):
166
+ observed = observed_cell_intbc_sets[cell]
167
+ if len(clones) != 1:
168
+ # We did not confidently map the cell, so we just drop all intBCs
169
+ cell_intbc_set = set()
170
+ else:
171
+ clone_idx = list(clones)[0]
172
+ clone_intbc_set = clone_intbc_sets[clone_idx]
173
+ cell_intbc_set = observed.intersection(clone_intbc_set)
174
+ cell_intbc_sets[cell] = cell_intbc_set
175
+
176
+ cell_to_clones_mapping = {
177
+ x: set(["clone_" + str(y) for y in ys]) for (x, ys) in clone_mapping.items()
178
+ }
179
+ for cellbc in all_cellbc:
180
+ if cellbc not in cell_to_clones_mapping:
181
+ cell_to_clones_mapping[cellbc] = (
182
+ set()
183
+ ) # These lost all their intBCs due to low UMI counts
184
+ if cellbc not in cell_intbc_sets:
185
+ cell_intbc_sets[cellbc] = (
186
+ set()
187
+ ) # These lost all their intBCs due to low UMI counts
188
+ clone_intbc_sets = {("clone_" + str(x)): y for (x, y) in clone_intbc_sets.items()}
189
+ clone_to_cells_mapping = {
190
+ ("clone_" + str(x)): y for (x, y) in clone_to_cells_mapping.items()
191
+ }
192
+
193
+ clone_call = CloneCallType(
194
+ clone_to_cells_mapping=clone_to_cells_mapping,
195
+ cell_to_clones_mapping=cell_to_clones_mapping,
196
+ clone_intbc_sets=clone_intbc_sets,
197
+ cell_intbc_sets=cell_intbc_sets,
198
+ )
199
+
200
+ total_time = time.time() - st
201
+ res = CallClonesFromCoresetsReturnType(
202
+ clone_call=clone_call,
203
+ total_time=total_time,
204
+ )
205
+ return res
206
+
207
+
208
+ def test_call_clones_from_coresets():
209
+ intbc_table = pd.DataFrame(
210
+ [
211
+ ["c1", "A", 5],
212
+ ["c1", "B", 5],
213
+ ["c2", "C", 5],
214
+ ],
215
+ columns=["cellBC", "intBC", "UMI_count"],
216
+ )
217
+ out = call_clones_from_coresets(
218
+ intbc_table_per_sample={"s0": intbc_table},
219
+ min_intbc_umi_count=1,
220
+ coresets=[{"A", "B"}, {"C"}],
221
+ min_overlap_for_assigning_cellbc=1,
222
+ min_intbc_counts_for_adding_intbc_to_clone_intbc_set=1,
223
+ min_intbc_fraction_for_adding_to_clone_intbc_set=0.0,
224
+ edit_distance_cutoff=0,
225
+ custom_intbc_filtering_function=lambda intbc: False,
226
+ )
227
+ assert out.total_time >= 0.0
228
+ cc = out.clone_call
229
+ c1, c2 = "s0_c1", "s0_c2"
230
+ assert cc.clone_to_cells_mapping["clone_0"] == {c1}
231
+ assert cc.clone_to_cells_mapping["clone_1"] == {c2}
232
+ assert cc.clone_intbc_sets["clone_0"] == {"A", "B"}
233
+ assert cc.clone_intbc_sets["clone_1"] == {"C"}
234
+ assert cc.cell_intbc_sets[c1] == {"A", "B"}
235
+ assert cc.cell_intbc_sets[c2] == {"C"}
@@ -0,0 +1,258 @@
1
+ """
2
+ Operations for e.g. merging clones, dropping clones, in a clone call.
3
+ """
4
+ import typing as t
5
+ from ._types import CloneCallType
6
+ from copy import deepcopy
7
+ from collections import defaultdict
8
+
9
+
10
+ def merge_clones(
11
+ clones: t.List[str],
12
+ clone_call: CloneCallType,
13
+ new_clone_name: str,
14
+ ) -> CloneCallType:
15
+ """
16
+ Merge the given clones into one.
17
+
18
+ clone_to_cells_mapping: Dict[CloneType, Set[CellBCType]]
19
+ => we replace the clones by a new key with union of cells.
20
+ cell_to_clones_mapping: Dict[CellBCType, Set[CloneType]]
21
+ => we collapse the merged clones into one. To be conservative, multiplets contained in exclusively the `clones` become unmapped cells.
22
+ clone_intbc_sets: Dict[CloneType, Set[IntBCType]]
23
+ => we take the union.
24
+ cell_intbc_sets: Dict[CellBCType, Set[IntBCType]]
25
+ => Multiplets contained in exclusively the `clones` have their intBC sets cleared.
26
+ """
27
+ clone_to_cells_mapping = deepcopy(clone_call.clone_to_cells_mapping)
28
+ cell_to_clones_mapping = deepcopy(clone_call.cell_to_clones_mapping)
29
+ clone_intbc_sets = deepcopy(clone_call.clone_intbc_sets)
30
+ cell_intbc_sets = deepcopy(clone_call.cell_intbc_sets)
31
+ new_clone_to_cells_mapping = defaultdict(set)
32
+ new_cell_to_clones_mapping = {}
33
+ new_clone_intbc_sets = defaultdict(set)
34
+ new_cell_intbc_sets = {}
35
+ clones_set = set(clones)
36
+ for clone in clone_to_cells_mapping:
37
+ if clone not in clones:
38
+ new_clone_to_cells_mapping[clone] = clone_to_cells_mapping[clone]
39
+ else:
40
+ new_clone_to_cells_mapping[new_clone_name] = new_clone_to_cells_mapping[new_clone_name].union(
41
+ clone_to_cells_mapping[clone]
42
+ )
43
+ for cell in cell_to_clones_mapping:
44
+ cell_clones = cell_to_clones_mapping[cell]
45
+ new_cell_clones = set()
46
+ if len(cell_clones) >= 2 and all(clone in clones_set for clone in cell_clones):
47
+ # This is a multiplet that mapped only to the merged clones.
48
+ # We unmap it to be safe, i.e. we leave `new_cell_clones` empty.
49
+ pass
50
+ else:
51
+ for clone in cell_clones:
52
+ if clone in clones_set:
53
+ new_cell_clones.add(new_clone_name)
54
+ else:
55
+ new_cell_clones.add(clone)
56
+ new_cell_to_clones_mapping[cell] = new_cell_clones
57
+ for clone in clone_intbc_sets:
58
+ intbcs = clone_intbc_sets[clone]
59
+ if clone not in clones_set:
60
+ new_clone_intbc_sets[clone] = intbcs
61
+ else:
62
+ new_clone_intbc_sets[new_clone_name] = new_clone_intbc_sets[new_clone_name].union(intbcs)
63
+ new_cell_intbc_sets = cell_intbc_sets
64
+ for cell in new_cell_intbc_sets:
65
+ if len(new_cell_to_clones_mapping[cell]) == 0:
66
+ new_cell_intbc_sets[cell] = set()
67
+ return CloneCallType( # NOTE: We cast to dict to avoid returning defaultdict
68
+ clone_to_cells_mapping=dict(new_clone_to_cells_mapping),
69
+ cell_to_clones_mapping=dict(new_cell_to_clones_mapping),
70
+ clone_intbc_sets=dict(new_clone_intbc_sets),
71
+ cell_intbc_sets=dict(new_cell_intbc_sets),
72
+ )
73
+
74
+
75
+
76
+ def _get_test_clone_call():
77
+ clone_call = CloneCallType(
78
+ clone_to_cells_mapping={
79
+ "clone_1": {"cell_1", "cell_2", "cell_3"},
80
+ "clone_2": {"cell_4", "cell_5", "cell_6"},
81
+ "clone_3": {"cell_7", "cell_8"},
82
+ "clone_4": {"cell_10"},
83
+ },
84
+ cell_to_clones_mapping={
85
+ "cell_1": {"clone_1"},
86
+ "cell_2": {"clone_1"},
87
+ "cell_3": {"clone_1"},
88
+ "cell_4": {"clone_2"},
89
+ "cell_5": {"clone_2"},
90
+ "cell_6": {"clone_2"},
91
+ "cell_7": {"clone_3"},
92
+ "cell_8": {"clone_3"},
93
+ "cell_9": {"clone_2", "clone_3"},
94
+ "cell_10": {"clone_4"},
95
+ "cell_11": set(),
96
+ "cell_12": {"clone_1", "clone_4"}
97
+ },
98
+ clone_intbc_sets={
99
+ "clone_1": {"A", "B"},
100
+ "clone_2": {"C", "D"},
101
+ "clone_3": {"E", "F"},
102
+ "clone_4": {"F", "G"},
103
+ },
104
+ cell_intbc_sets={
105
+ "cell_1": {"A", "B"},
106
+ "cell_2": {"A", "B"},
107
+ "cell_3": {"A", "B"},
108
+ "cell_4": {"C", "D"},
109
+ "cell_5": {"C", "D"},
110
+ "cell_6": {"C", "D"},
111
+ "cell_7": {"E", "F"},
112
+ "cell_8": {"E", "F"},
113
+ "cell_9": {},
114
+ "cell_10": {"F", "G"},
115
+ "cell_11": set(),
116
+ "cell_12": {"A", "B", "F", "G"},
117
+ }
118
+ )
119
+ return clone_call
120
+
121
+
122
+ def test_merge_clones():
123
+ clone_call = _get_test_clone_call()
124
+ clone_call = merge_clones(
125
+ clones=["clone_2", "clone_3", "clone_4"],
126
+ clone_call=clone_call,
127
+ new_clone_name="clone_2_3_4"
128
+ )
129
+ assert clone_call.clone_to_cells_mapping == {
130
+ "clone_1": {"cell_1", "cell_2", "cell_3"},
131
+ "clone_2_3_4": {"cell_4", "cell_5", "cell_6", "cell_7", "cell_8", "cell_10"},
132
+ }
133
+ assert clone_call.cell_to_clones_mapping == {
134
+ "cell_1": {"clone_1"},
135
+ "cell_2": {"clone_1"},
136
+ "cell_3": {"clone_1"},
137
+ "cell_4": {"clone_2_3_4"},
138
+ "cell_5": {"clone_2_3_4"},
139
+ "cell_6": {"clone_2_3_4"},
140
+ "cell_7": {"clone_2_3_4"},
141
+ "cell_8": {"clone_2_3_4"},
142
+ "cell_9": set(),
143
+ "cell_10": {"clone_2_3_4"},
144
+ "cell_11": set(),
145
+ "cell_12": {"clone_1", "clone_2_3_4"},
146
+ }
147
+ assert clone_call.clone_intbc_sets == {
148
+ "clone_1": {"A", "B"},
149
+ "clone_2_3_4": {"C", "D", "E", "F", "G"},
150
+ }
151
+ assert clone_call.cell_intbc_sets == {
152
+ "cell_1": {"A", "B"},
153
+ "cell_2": {"A", "B"},
154
+ "cell_3": {"A", "B"},
155
+ "cell_4": {"C", "D"},
156
+ "cell_5": {"C", "D"},
157
+ "cell_6": {"C", "D"},
158
+ "cell_7": {"E", "F"},
159
+ "cell_8": {"E", "F"},
160
+ "cell_9": set(),
161
+ "cell_10": {"F", "G"},
162
+ "cell_11": set(),
163
+ "cell_12": {"A", "B", "F", "G"},
164
+ }
165
+
166
+
167
+ def drop_clones(
168
+ clones: t.List[str],
169
+ clone_call: CloneCallType
170
+ ) -> CloneCallType:
171
+ """
172
+ Drop the given clones.
173
+
174
+ clone_to_cells_mapping: Dict[CloneType, Set[CellBCType]]
175
+ => we drop the clones from here.
176
+ cell_to_clones_mapping: Dict[CellBCType, Set[CloneType]]
177
+ => We unmap every cell which maps to *any* of the dropped clones.
178
+ clone_intbc_sets: Dict[CloneType, Set[IntBCType]]
179
+ => we drop the clones from here
180
+ cell_intbc_sets: Dict[CellBCType, Set[IntBCType]]
181
+ => we clear the intBC set of every cell that we unmapped
182
+ """
183
+ clone_to_cells_mapping = deepcopy(clone_call.clone_to_cells_mapping)
184
+ cell_to_clones_mapping = deepcopy(clone_call.cell_to_clones_mapping)
185
+ clone_intbc_sets = deepcopy(clone_call.clone_intbc_sets)
186
+ cell_intbc_sets = deepcopy(clone_call.cell_intbc_sets)
187
+ new_clone_to_cells_mapping = defaultdict(set)
188
+ new_cell_to_clones_mapping = {}
189
+ new_clone_intbc_sets = defaultdict(set)
190
+ new_cell_intbc_sets = {}
191
+ clones_set = set(clones)
192
+ for clone in clone_to_cells_mapping:
193
+ if clone not in clones:
194
+ new_clone_to_cells_mapping[clone] = clone_to_cells_mapping[clone]
195
+ for cell in cell_to_clones_mapping:
196
+ cell_clones = cell_to_clones_mapping[cell]
197
+ new_cell_clones = set()
198
+ if any([clone in clones_set for clone in cell_clones]):
199
+ new_cell_to_clones_mapping[cell] = set()
200
+ else:
201
+ new_cell_to_clones_mapping[cell] = cell_clones
202
+ for clone in clone_intbc_sets:
203
+ intbcs = clone_intbc_sets[clone]
204
+ if clone not in clones:
205
+ new_clone_intbc_sets[clone] = intbcs
206
+ for cell in cell_intbc_sets:
207
+ if len(new_cell_to_clones_mapping[cell]) == 0:
208
+ new_cell_intbc_sets[cell] = set()
209
+ else:
210
+ new_cell_intbc_sets[cell] = cell_intbc_sets[cell]
211
+ return CloneCallType( # NOTE: We cast to dict to avoid returning defaultdict
212
+ clone_to_cells_mapping=dict(new_clone_to_cells_mapping),
213
+ cell_to_clones_mapping=dict(new_cell_to_clones_mapping),
214
+ clone_intbc_sets=dict(new_clone_intbc_sets),
215
+ cell_intbc_sets=dict(new_cell_intbc_sets),
216
+ )
217
+
218
+
219
+ def test_drop_clones():
220
+ clone_call = _get_test_clone_call()
221
+ clone_call = drop_clones(
222
+ clones=["clone_2", "clone_3", "clone_4"],
223
+ clone_call=clone_call,
224
+ )
225
+ assert clone_call.clone_to_cells_mapping == {
226
+ "clone_1": {"cell_1", "cell_2", "cell_3"},
227
+ }
228
+ assert clone_call.cell_to_clones_mapping == {
229
+ "cell_1": {"clone_1"},
230
+ "cell_2": {"clone_1"},
231
+ "cell_3": {"clone_1"},
232
+ "cell_4": set(),
233
+ "cell_5": set(),
234
+ "cell_6": set(),
235
+ "cell_7": set(),
236
+ "cell_8": set(),
237
+ "cell_9": set(),
238
+ "cell_10": set(),
239
+ "cell_11": set(),
240
+ "cell_12": set(),
241
+ }
242
+ assert clone_call.clone_intbc_sets == {
243
+ "clone_1": {"A", "B"},
244
+ }
245
+ assert clone_call.cell_intbc_sets == {
246
+ "cell_1": {"A", "B"},
247
+ "cell_2": {"A", "B"},
248
+ "cell_3": {"A", "B"},
249
+ "cell_4": set(),
250
+ "cell_5": set(),
251
+ "cell_6": set(),
252
+ "cell_7": set(),
253
+ "cell_8": set(),
254
+ "cell_9": set(),
255
+ "cell_10": set(),
256
+ "cell_11": set(),
257
+ "cell_12": set(),
258
+ }