buttle 0.3.0__cp315-cp315-win_amd64.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.
buttle/__init__.py ADDED
File without changes
buttle/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ main()
Binary file
buttle/buttle_rust.pyi ADDED
@@ -0,0 +1,38 @@
1
+ def _align_protein(
2
+ seq1: str,
3
+ seq2: str
4
+ ) -> tuple[str, str]:
5
+ """
6
+ Perform pairwise protein sequence alignment using the Gotoh algorithm.
7
+
8
+ Uses BLOSUM62 substitution scores and affine gap penalties.
9
+
10
+ Implemented in Rust. For use only with validated Protein objects.
11
+
12
+ Args:
13
+ seq1: First protein sequence.
14
+ seq2: Second protein sequence.
15
+
16
+ Returns:
17
+ A tuple containing the two aligned protein sequences,
18
+ including gap characters.
19
+ """
20
+ ...
21
+
22
+ def _merge(
23
+ left_names:list[str],
24
+ left_sequences: list[str],
25
+ right_names:list[str],
26
+ right_sequences: list[str],
27
+ gap_open:float,
28
+ gap_extend:float,
29
+ ) -> tuple[list[str], list[str]]:
30
+ '''
31
+ Globally align two profiles, made up of already aligned sequences, using affine gap penalties.
32
+
33
+ Inputs are two Profiles to merge (separated out into their constituent lists of names and sequences to pass to rust),
34
+ as well as the gap open and gap extend penalties to use. This internal function does not perform input validation.
35
+
36
+ Returns a tuple of a list of sequence names, and a list of sequence alignments.
37
+ '''
38
+ ...
buttle/cli.py ADDED
@@ -0,0 +1,565 @@
1
+ import argparse, sys
2
+ lazy from pathlib import Path
3
+ lazy from . import msa as btmsa
4
+ lazy from . import constants as btc
5
+ lazy from . import renderers as btr
6
+ lazy from . import sequences as bts
7
+ lazy from . import comparisons as btcomp
8
+ lazy from . import pairwise_align as bta
9
+ lazy from . import models as btm
10
+ lazy from . import pairwise_align as btpa
11
+ lazy from . import buttle_rust as btrust
12
+
13
+
14
+ def main():
15
+
16
+ # Argument Parser Construction
17
+
18
+ parser = argparse.ArgumentParser(
19
+ prog="buttle",
20
+ usage="buttle <command> [options]",
21
+ add_help=False,
22
+ formatter_class=argparse.RawDescriptionHelpFormatter,
23
+ )
24
+
25
+ parser.add_argument(
26
+ "-h","--help",
27
+ action="store_true"
28
+ )
29
+
30
+ subparsers = parser.add_subparsers(
31
+ title="programs",
32
+ metavar=""
33
+ )
34
+
35
+ parser_transcribe = subparsers.add_parser(
36
+ "transcribe",
37
+ help="Transcribe a DNA sequence into RNA."
38
+ )
39
+ parser_transcribe.add_argument(
40
+ "dna",
41
+ help="Provide a DNA sequence to transcribe into RNA."
42
+ )
43
+ parser_transcribe.set_defaults(func=cli_transcribe)
44
+
45
+
46
+ parser_translate = subparsers.add_parser(
47
+ "translate",
48
+ help="Translate a DNA sequence into protein."
49
+ )
50
+ parser_translate.add_argument(
51
+ "dna",
52
+ help="Provide a DNA sequence to translate into protein."
53
+ )
54
+ parser_translate.set_defaults(func=cli_translate)
55
+
56
+
57
+ parser_rcomp = subparsers.add_parser(
58
+ "rcomp",
59
+ help="Return the reverse complement of a DNA sequence."
60
+ )
61
+ parser_rcomp.add_argument(
62
+ "dna",
63
+ help="Provide a DNA sequence to receive back the reverse complement sequence."
64
+ )
65
+ parser_rcomp.set_defaults(func=cli_reverse_comp)
66
+
67
+
68
+ parser_gc = subparsers.add_parser(
69
+ "gc",
70
+ help = "Return the GC content of a DNA sequence."
71
+ )
72
+ parser_gc.add_argument(
73
+ "dna",
74
+ help="Provide a DNA sequence to return the GC content."
75
+ )
76
+ parser_gc.set_defaults(func=cli_gc)
77
+
78
+
79
+ parser_charge = subparsers.add_parser(
80
+ "charge",
81
+ help="Return the net charge of a protein sequence."
82
+ )
83
+ parser_charge.add_argument(
84
+ "protein",
85
+ help="Provide a protein sequence and receive back the net charge of that protein."
86
+ )
87
+ parser_charge.set_defaults(func=cli_charge)
88
+
89
+
90
+ parser_mw = subparsers.add_parser(
91
+ "mw",
92
+ help="Return the molecular weight of a protein sequence."
93
+ )
94
+ parser_mw.add_argument(
95
+ "protein",
96
+ help="Provide a protein sequence and receive back the molecular weight of that protein."
97
+ )
98
+ parser_mw.set_defaults(func=cli_mw)
99
+
100
+
101
+ parser_ec = subparsers.add_parser(
102
+ "ec",
103
+ help="Return the extinction coefficient of a protein sequence."
104
+ )
105
+ parser_ec.add_argument(
106
+ "protein",
107
+ help="Provide a protein sequence and receive back the extinction coefficient of that protein."
108
+ )
109
+ parser_ec.set_defaults(func=cli_ec)
110
+
111
+
112
+ parser_props = subparsers.add_parser(
113
+ "props",
114
+ help="Return the molecular weight, extinction coefficient, and charge of a protein sequence."
115
+ )
116
+ parser_props.add_argument(
117
+ "protein",
118
+ help="Provide a protein sequence and receive back the extinction coefficient, molecular weight, and charge of that protein."
119
+ )
120
+ parser_props.set_defaults(func=cli_props)
121
+
122
+
123
+ parser_glycosylation = subparsers.add_parser(
124
+ "glycosylation",
125
+ help = "Returns the protein sequence with N-linked glycosylation sites highlighted, if they are present."
126
+ )
127
+ parser_glycosylation.add_argument(
128
+ "protein",
129
+ help="Provide a protein sequence to locate potential N-linked glycosylation sites."
130
+ )
131
+ parser_glycosylation.set_defaults(func=cli_glycosylation)
132
+
133
+
134
+ parser_motifs = subparsers.add_parser(
135
+ "motifs",
136
+ help = "Given 2 protein sequences, looks for regions of amino acid similarity."
137
+ )
138
+ parser_motifs.add_argument(
139
+ "s1",
140
+ help="First sequence for comparison."
141
+ )
142
+ parser_motifs.add_argument(
143
+ "s2",
144
+ help="Second sequence for comparison."
145
+ )
146
+ parser_motifs.set_defaults(func=cli_motifs)
147
+
148
+
149
+ parser_length = subparsers.add_parser(
150
+ "length",
151
+ help = "Given a protein or DNA sequence, returns the length of the sequence."
152
+ )
153
+ parser_length.add_argument(
154
+ "sequence",
155
+ help = "Sequence to determine the length of."
156
+ )
157
+ parser_length.set_defaults(func=cli_length)
158
+
159
+
160
+ parser_find_pos = subparsers.add_parser(
161
+ "find_pos",
162
+ help = "Given a protein or DNA sequence, and a search string, returns the position of the search string in the sequence, if it exists."
163
+ )
164
+ parser_find_pos.add_argument(
165
+ "sequence",
166
+ help = "Sequence to search in."
167
+ )
168
+ parser_find_pos.add_argument(
169
+ "search_string",
170
+ help = "Sequence to search for."
171
+ )
172
+ parser_find_pos.set_defaults(func=cli_find_pos)
173
+
174
+
175
+ parser_pairwise_dna = subparsers.add_parser(
176
+ "dna_align",
177
+ help = "Align two given DNA sequences."
178
+ )
179
+ parser_pairwise_dna.add_argument(
180
+ "dna1",
181
+ help = "Sequence 1 for alignment."
182
+ )
183
+ parser_pairwise_dna.add_argument(
184
+ "dna2",
185
+ help = "Sequence 2 for alignment."
186
+ )
187
+ parser_pairwise_dna.add_argument(
188
+ "--save",
189
+ metavar = "FILE",
190
+ help = "Save a PNG image of the alignment with the provided filename."
191
+ )
192
+ parser_pairwise_dna.add_argument(
193
+ "--diff",
194
+ action="store_true",
195
+ help = "This option will output dots for bases that are identical to the first sequence."
196
+ )
197
+ parser_pairwise_dna.add_argument(
198
+ "--s1",
199
+ metavar = "Seq 1 Start",
200
+ type=int,
201
+ default=1,
202
+ help = "The base number that sequence 1 starts at. Defaults to 1."
203
+ )
204
+ parser_pairwise_dna.add_argument(
205
+ "--s2",
206
+ metavar = "Seq 2 Start",
207
+ type=int,
208
+ default=1,
209
+ help = "The base number that sequence 2 starts at. Defaults to 1."
210
+ )
211
+ parser_pairwise_dna.set_defaults(func=cli_dna_align)
212
+
213
+
214
+ parser_pairwise_protein = subparsers.add_parser(
215
+ "protein_align",
216
+ help = "Align two given protein sequences."
217
+ )
218
+ parser_pairwise_protein.add_argument(
219
+ "seq1",
220
+ help = "Sequence 1 for alignment."
221
+ )
222
+ parser_pairwise_protein.add_argument(
223
+ "seq2",
224
+ help = "Sequence 2 for alignment."
225
+ )
226
+ parser_pairwise_protein.add_argument(
227
+ "--diff",
228
+ action="store_true",
229
+ help = "This option will output dots for bases that are identical to the first sequence."
230
+ )
231
+ parser_pairwise_protein.add_argument(
232
+ "--name1",
233
+ metavar = "NAME",
234
+ help = "The name of sequence 1. Defaults to '1'."
235
+ )
236
+ parser_pairwise_protein.add_argument(
237
+ "--name2",
238
+ metavar = "NAME",
239
+ help = "The name of sequence 2. Defaults to '2'."
240
+ )
241
+ parser_pairwise_protein.add_argument(
242
+ "--wrap",
243
+ metavar = "N",
244
+ type=_positive_int,
245
+ default=300,
246
+ help = "Specfiy how many residues of the alignment are rendered per line (image only). Defaults to 300."
247
+ )
248
+ parser_pairwise_protein.add_argument(
249
+ "--real-ruler",
250
+ nargs="?",
251
+ const = 1,
252
+ type = _positive_int,
253
+ default = None,
254
+ metavar = "START",
255
+ help = "This option will display the ruler based on the amino acid numbers of the reference sequence, starting at START (defaults to 1)."
256
+ )
257
+ parser_pairwise_protein.add_argument(
258
+ "--save",
259
+ nargs="?",
260
+ const=True,
261
+ metavar = "FILE",
262
+ help = "Save alignment to FILE, or choose a filename interactively."
263
+ )
264
+ parser_pairwise_protein.add_argument(
265
+ "--consensus",
266
+ action = "store_true",
267
+ help = "This option will add a consensus sequence to the bottom of the alignment."
268
+ )
269
+ parser_pairwise_protein.add_argument(
270
+ "--backend",
271
+ choices=("python", "rust"),
272
+ default="rust",
273
+ help = "Choose whether to use the rust implementation (compiled, default) or python (much slower for large numbers of sequences.)"
274
+ )
275
+ parser_pairwise_protein.set_defaults(func=cli_protein_align)
276
+
277
+
278
+ parser_msa = subparsers.add_parser(
279
+ "msa",
280
+ help = "Aligned multiple protein sequences, provided in a fasta file or a fasta formmated string."
281
+ )
282
+ parser_msa.add_argument(
283
+ "file",
284
+ help = "Input fasta file, or a fasta-formatted string."
285
+ )
286
+ parser_msa.add_argument(
287
+ "--diff",
288
+ action = "store_true",
289
+ help = "This option will output dots for amino acids that are identical to the first sequence."
290
+ )
291
+ parser_msa.add_argument(
292
+ "--consensus",
293
+ action = "store_true",
294
+ help = "This option will add a consensus sequence to the bottom of the alignment."
295
+ )
296
+ parser_msa.add_argument(
297
+ "--wrap",
298
+ metavar = "N",
299
+ type = _positive_int,
300
+ default = 300,
301
+ help = "Specify how many residues of the alignment are rendered per line (image only). Defaults to 300."
302
+ )
303
+ parser_msa.add_argument(
304
+ "--real-ruler",
305
+ nargs="?",
306
+ const = 1,
307
+ type = _positive_int,
308
+ default = None,
309
+ metavar = "START",
310
+ help = "This option will display the ruler based on the amino acid numbers of the reference sequence, starting at START (defaults to 1)."
311
+ )
312
+ parser_msa.add_argument(
313
+ "--save",
314
+ nargs="?",
315
+ const=True,
316
+ metavar = "OUTPUT_FILE",
317
+ help = "Save alignment to FILE, or choose a filename interactively."
318
+ )
319
+ parser_msa.add_argument(
320
+ "--backend",
321
+ choices=("python", "rust"),
322
+ default="rust",
323
+ help = "Choose whether to use the rust implementation (compiled, default) or python (much slower for large numbers of sequences.)"
324
+ )
325
+ parser_msa.add_argument(
326
+ "--gap-extend",
327
+ metavar = "N",
328
+ type = _non_positive_int,
329
+ default = -3,
330
+ help = "Specify the profile merge level affine gap extend penalty. Defaults to -3."
331
+ )
332
+ parser_msa.add_argument(
333
+ "--gap-open",
334
+ metavar = "N",
335
+ type = _non_positive_int,
336
+ default = -10,
337
+ help = "Specify the profile merge level affine gap open penalty. Defaults to -10."
338
+ )
339
+ parser_msa.set_defaults(func=cli_msa)
340
+
341
+
342
+ parser_identity = subparsers.add_parser(
343
+ "identity",
344
+ help = "Returns all pairwise sequence identities from an input FASTA file or FASTA-formatted string."
345
+ )
346
+ parser_identity.add_argument(
347
+ "file",
348
+ help = "Input fasta file, or a fasta-formatted string."
349
+ )
350
+ parser_identity.set_defaults(func=cli_identity)
351
+
352
+
353
+ args = parser.parse_args()
354
+
355
+ if args.help:
356
+ print(btc.MY_HELP)
357
+ return
358
+
359
+ if hasattr(args, "func"):
360
+ try:
361
+ args.func(args)
362
+ except ValueError as error:
363
+ print(error, file=sys.stderr)
364
+ sys.exit(1)
365
+ else:
366
+ parser.print_help()
367
+
368
+
369
+ # CLI Function Dispatchers
370
+
371
+ def cli_transcribe(args):
372
+ print(f"{btc.RED_TERM}{bts.transcribe(args.dna)}{btc.RESET}")
373
+
374
+
375
+ def cli_translate(args):
376
+ f1, f2, f3 = bts.translate(args.dna)
377
+ print(f"{btc.RED_TERM}Frame 1: {f1}")
378
+ print(f"Frame 2: {f2}")
379
+ print(f"Frame 3: {f3}{btc.RESET}")
380
+
381
+
382
+ def cli_reverse_comp(args):
383
+ print(f"{btc.RED_TERM}{bts.reverse_comp(args.dna)}{btc.RESET}")
384
+
385
+
386
+ def cli_gc(args):
387
+ length, contents, at_content, gc_content = bts.gc(args.dna)
388
+ print(f"{btc.RED_TERM}Total bases:{btc.RESET} {length}")
389
+ print(f"{btc.RED_TERM}A:{btc.RESET} {contents["A"]}")
390
+ print(f"{btc.RED_TERM}C:{btc.RESET} {contents["C"]}")
391
+ print(f"{btc.RED_TERM}G:{btc.RESET} {contents["G"]}")
392
+ print(f"{btc.RED_TERM}T:{btc.RESET} {contents["T"]}")
393
+ print(f"{btc.RED_TERM}AT Content:{btc.RESET} {at_content:.2f}")
394
+ print(f"{btc.RED_TERM}GC Content:{btc.RESET} {gc_content:.2f}")
395
+
396
+
397
+ def cli_charge(args):
398
+ protein = btm.Protein(name="", sequence=args.protein)
399
+ print(f"{btc.RED_TERM}Charge = {protein.charge()}{btc.RESET}")
400
+
401
+
402
+ def cli_mw(args):
403
+ protein = btm.Protein(name="", sequence=args.protein)
404
+ print(f"{btc.RED_TERM}Molecular Weight = {protein.mw():.1f}{btc.RESET}")
405
+
406
+
407
+ def cli_ec(args):
408
+ protein = btm.Protein(name="", sequence=args.protein)
409
+ print(f"{btc.RED_TERM}Extinction Coefficient = {protein.ec()}{btc.RESET}")
410
+
411
+
412
+ def cli_props(args):
413
+ protein = btm.Protein(name="", sequence=args.protein)
414
+ print(f"{btc.RED_TERM}Molecular Weight = {protein.mw():.1f}{btc.RESET}")
415
+ print(f"{btc.RED_TERM}Charge = {protein.charge()}{btc.RESET}")
416
+ print(f"{btc.RED_TERM}Extinction Coefficient = {protein.ec()}{btc.RESET}")
417
+
418
+
419
+ def cli_glycosylation(args):
420
+ protein = btm.Protein(name="", sequence=args.protein)
421
+ sites = protein.glycosylation()
422
+ if len(sites) == 0:
423
+ print("No N-linked glycosylation sites found.")
424
+ else:
425
+ for i, aa in enumerate(args.protein):
426
+ aa = aa.upper()
427
+ if i not in sites and i-1 not in sites and i-2 not in sites:
428
+ print(aa, end="")
429
+ else:
430
+ print(f"{btc.RED_TERM}{aa}{btc.RESET}", end="")
431
+ print()
432
+
433
+
434
+ def cli_motifs(args):
435
+ protein1 = btm.Protein(name="", sequence=args.s1)
436
+ protein2 = btm.Protein(name="", sequence=args.s2)
437
+ similars = btcomp.motifs(protein1, protein2)
438
+ if not similars:
439
+ print("No significant similarity found.")
440
+ for key in similars:
441
+ print(f"{btc.RED_TERM}Peptide size = {key[0]}. Threshold of similarity = {key[1]}.{btc.RESET}")
442
+ for ((peptide1, start1, end1), (peptide2, start2, end2), score) in similars[key]:
443
+ print(f"{start1}-{peptide1}-{end1}")
444
+ print(f"{start2}-{peptide2}-{end2}")
445
+ print(f"Score: {score}/{key[0]} ({score/key[0]:.1%})")
446
+ print()
447
+
448
+
449
+ def cli_length(args):
450
+ print(f"Length: {btc.RED_TERM}{bts.length(args.sequence)}{btc.RESET}")
451
+
452
+
453
+ def cli_find_pos(args):
454
+ hits = bts.find_pos(args.sequence, args.search_string)
455
+ if not hits:
456
+ print("Not found")
457
+ else:
458
+ print("Found!")
459
+ for hit in hits:
460
+ print(f"At position: {btc.RED_TERM}{hit}{btc.RED_TERM}")
461
+
462
+
463
+ def cli_dna_align(args):
464
+ align1, align2 = bta.pairwise_dna(args.dna1, args.dna2)
465
+ btr.print_dna_pairwise_alignment([align1, align2], args.diff)
466
+
467
+ if args.save:
468
+ btr.save_dna_pairwise_alignment([align1, align2], filename=args.save, start1=args.s1, start2=args.s2, diff=args.diff)
469
+
470
+
471
+ def cli_protein_align(args):
472
+ protein1 = btm.Protein("", args.seq1, allow_x=True)
473
+ protein2 = btm.Protein("", args.seq2, allow_x=True)
474
+
475
+ if args.backend == "rust":
476
+ align1, align2 = btrust._align_protein(protein1.sequence, protein2.sequence)
477
+ align1 = list(align1)
478
+ align2 = list(align2)
479
+ else:
480
+ align1, align2 = btpa.pairwise_protein(protein1, protein2)
481
+
482
+ profile = btm.Profile(
483
+ name = [args.name1 or "1", args.name2 or "2"],
484
+ alignment = [align1, align2]
485
+ )
486
+
487
+ if args.consensus:
488
+ profile = btmsa.add_consensus(profile)
489
+ consensus = True
490
+ else:
491
+ consensus = False
492
+
493
+ _output_protein_alignment(profile, args, consensus)
494
+
495
+
496
+ def cli_msa(args):
497
+ profile = btmsa.msa(args.file, args.backend, int(args.gap_open), int(args.gap_extend))
498
+ if args.consensus:
499
+ profile = btmsa.add_consensus(profile)
500
+ consensus = True
501
+ else:
502
+ consensus = False
503
+ _output_protein_alignment(profile, args, consensus)
504
+
505
+
506
+ def cli_identity(args):
507
+ identities = btcomp.identity(args.file)
508
+ for key in identities:
509
+ print(key, f"{identities[key]:.3f}")
510
+
511
+
512
+ def _output_protein_alignment(profile:btm.Profile, args, consensus:bool):
513
+ if args.save:
514
+ filename = args.save
515
+
516
+ # If no filename provided, use dialog window to choose location and filename.
517
+ if filename is True:
518
+ from . import dialogs as btdi
519
+ filename = btdi.get_save_filename()
520
+ if not filename:
521
+ print("Save cancelled.")
522
+ return
523
+
524
+ # Determine the requested image format
525
+ suffix = Path(filename).suffix.lower()
526
+ if suffix == ".png":
527
+ scale = 1
528
+ elif suffix in {".tif", ".tiff"}:
529
+ scale = 4
530
+ else:
531
+ raise ValueError(f"Unsupported image format {suffix}")
532
+
533
+ # Render the alignment properly, then save it as an image
534
+ image = btr.render_protein_alignment_image(profile, scale, wrap=args.wrap, start=args.real_ruler, consensus=consensus, diff=args.diff)
535
+ btr.save_protein_alignment_image(image, suffix, filename=filename)
536
+
537
+ else:
538
+
539
+ # Terminal output
540
+ btr.print_protein_alignment(profile, consensus, args.diff)
541
+
542
+
543
+ def _positive_int(value:str):
544
+ try:
545
+ value = int(value)
546
+ if value < 1:
547
+ raise ValueError
548
+ except ValueError:
549
+ raise argparse.ArgumentTypeError("Ruler start must be a positive integer.")
550
+ return value
551
+
552
+
553
+ def _non_positive_int(value:str):
554
+ try:
555
+ value = int(value)
556
+ if value > 0:
557
+ raise ValueError
558
+ except ValueError:
559
+ raise argparse.ArgumentTypeError("Gap penalty must be a non positive integer.")
560
+ return value
561
+
562
+
563
+
564
+ if __name__ == "__main__":
565
+ main()
buttle/comparisons.py ADDED
@@ -0,0 +1,79 @@
1
+ lazy from collections import defaultdict
2
+ lazy from itertools import combinations
3
+ lazy from pathlib import Path
4
+ lazy from . import constants as btc
5
+ lazy from . import models as btm
6
+ lazy from . import pairwise_align as btpa
7
+ lazy from . import parsers as btp
8
+ lazy from . import buttle_rust as btrust
9
+
10
+
11
+ def motifs(protein1:btm.Protein, protein2:btm.Protein) -> dict:
12
+ '''
13
+ Given two protein sequences, this method looks for similar stretches fo amino acids, ranging from length 4 to length 40.
14
+
15
+ Matches count as +1, while conservative substitutions count for +0.5. Any two peptides where total_score / length > threshold is considered a match.
16
+
17
+ Threshold is more stringent for shorter peptides, and more lenient for longer peptides. Defined in constant THRESHOLD.
18
+ '''
19
+
20
+ # Determine shortest sequence and set that as seq1
21
+ length1 = len(protein1.sequence)
22
+ length2 = len(protein2.sequence)
23
+
24
+ if length1 > length2:
25
+ seq1 = protein2.sequence
26
+ seq2 = protein1.sequence
27
+ else:
28
+ seq1 = protein1.sequence
29
+ seq2 = protein2.sequence
30
+
31
+ # Initialize the dictionary that will hold matches
32
+ similars = defaultdict(list)
33
+
34
+ # Loop that looks at peptides of length 4 to 40 in the two sequences, compares them, and looks for regions of similarity.
35
+ for size in range(4, min(40, length1, length2)+1):
36
+
37
+ for min_size, max_size, cutoff in btc.THRESHOLD:
38
+ if min_size <= size <= max_size:
39
+ threshold = size * cutoff
40
+ break
41
+ else:
42
+ raise ValueError(f"No threshold defined for motif size {size}.")
43
+
44
+ for n in range(0, len(seq1) - size + 1):
45
+ peptide1 = seq1[n:n+size]
46
+ for x in range(0, len(seq2) - size + 1):
47
+ count = 0
48
+ peptide2 = seq2[x:x+size]
49
+ for z in range(size):
50
+ if peptide1[z] == peptide2[z]:
51
+ count += 1
52
+ continue
53
+ if peptide2[z] in btc.CONSERVATIVES[peptide1[z]]:
54
+ count += 0.5
55
+ if count >= threshold:
56
+ similars[(size, threshold)].append(((peptide1, n+1, n + size), (peptide2, x+1, x + size), count))
57
+
58
+ return similars
59
+
60
+
61
+ def identity(
62
+ file:str
63
+ ) -> dict[tuple[str,str], float]:
64
+
65
+ if Path(file).exists():
66
+ sequences = btp.parse_fasta_file(file)
67
+ else:
68
+ sequences = btp.parse_fasta_string(file)
69
+
70
+ identities = {}
71
+
72
+ for protein1, protein2 in combinations(sequences, 2):
73
+ align1, align2 = btrust._align_protein(protein1.sequence, protein2.sequence)
74
+ length = len(align1)
75
+ identical = sum(1 for aa1, aa2 in zip(align1, align2, strict=True) if aa1==aa2)
76
+ percent_identity = identical/length
77
+ identities[(protein1.name, protein2.name)] = percent_identity
78
+
79
+ return identities