blixa 1.0.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.
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: blixa
3
+ Version: 1.0.0
4
+ Summary: Blixa: A Domain-Specific Language for Biological Research
5
+ Author: AHMED TARIG AHMED ABDALGALEEL
6
+ Requires-Python: >=3.11
7
+ License-File: LICENSE
8
+ Requires-Dist: numpy>=1.24.0
9
+ Requires-Dist: pandas>=2.0.0
10
+ Requires-Dist: biopython>=1.81
11
+ Requires-Dist: scanpy>=1.9.3
12
+ Requires-Dist: anndata>=0.9.1
13
+ Requires-Dist: matplotlib>=3.7.1
14
+ Requires-Dist: seaborn>=0.12.2
15
+ Requires-Dist: scikit-learn>=1.2.2
16
+ Requires-Dist: statsmodels>=0.14.0
17
+ Requires-Dist: joblib>=1.3.0
18
+ Requires-Dist: pytest>=7.4.0
19
+ Requires-Dist: seqfold>=0.0.9
20
+ Requires-Dist: gseapy>=1.1.0
21
+ Dynamic: author
22
+ Dynamic: license-file
23
+ Dynamic: requires-dist
24
+ Dynamic: requires-python
25
+ Dynamic: summary
@@ -0,0 +1,37 @@
1
+ blixa-1.0.0.dist-info/licenses/LICENSE,sha256=rXFI0zviRWn_V3xYbOfZWNFuia9hDQDkoiJ6JFFCd7E,1095
2
+ core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ core/base.py,sha256=auGmp07Z6zFVQj-n6Fl7OoH1yBlRxQO7ZoXKj2eBO7Q,2473
4
+ core/cell.py,sha256=jjM63wZLQeWn-YlVEqff0GrenvAwfHWVBDcR_9v01SI,6695
5
+ core/dna.py,sha256=M29oRggrBqUbRHEeLUuBvqnRVyjPvFIlbqGeY1DHW08,8993
6
+ core/experiment.py,sha256=8EkjScC1IaHUO6JXM-1VqNPwabhyiuae9SQRbgh6irA,8777
7
+ core/functions.py,sha256=G-yvawANyyenX6C8Mh_C5DyAmSRytp8cU6LuJI9DV28,10204
8
+ core/gene.py,sha256=IiX7vWvWCbhe_5uJXjjsIEPOLo7Ya8ITaqF5M9lQn_c,7438
9
+ core/population.py,sha256=yXC0SRiSxaCK5Zog0_ucwTUxSwgkdehewUBtvpad2bA,19405
10
+ core/protein.py,sha256=umNvwpfgppakCEIm7RGuY5rvA423lIOQmXpsY4SiCMM,9774
11
+ core/result.py,sha256=MPH7VIihkQOmylU6fU5-wJerZ36jzF2WpB2efuW4yBw,12256
12
+ core/rna.py,sha256=2IQo9iGhnqntPW3j__2wWBtO-O4w46aQGTbbUaal6_E,7820
13
+ core/workflow.py,sha256=z7l9P47SmGNX3qRGzJmrmyGRxFOxpd_ELL-cliklilU,7479
14
+ exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ exceptions/bio_errors.py,sha256=z9dnCLnhCw7GiFOXkNYB_-cSBS5YvsU5CUNjQHbovgY,663
16
+ modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ modules/crispr.py,sha256=TWmUf9w2uvELArijmeDXc9erPwx9Q9Wi-SzNKQmFNEk,9224
18
+ modules/epigenetics.py,sha256=0kUSMLgrPPn-rmQKvxTIcV5wbHS25Y3O_fgvtNnojmE,6360
19
+ modules/genomics.py,sha256=DZmqb_CDimois46H3myJkB98rWFQc4F-bS5OhqP2zJ4,5657
20
+ modules/integration.py,sha256=PVRMU7ox-fRXO7IHJOOB1zikZUgxHuUnp0JAp4Dy9qU,7838
21
+ modules/pathways.py,sha256=EKWjzYseCdZWz4HRX1aDdgOcjZ2c0ngviMXBPhQ1VB4,6782
22
+ modules/proteomics.py,sha256=qHvOIRwmO36YJ262Rm56LEm2sHiaZP8mnC5VVLZuZOg,5152
23
+ modules/singlecell.py,sha256=sN0cO3y7dxksZgq95hFSwwvI9_SUPsdMLCLsIjlTAaU,6179
24
+ modules/statistics.py,sha256=UVizaOMgtOqEGwRFhVA1ZMd0KxNsP2r6bFh6nGoques,5908
25
+ modules/stemcell.py,sha256=o9euy_2ohR9PCU0rQV70LVnaX3CqI550tEueJggxg9I,4300
26
+ modules/structure.py,sha256=CQzt22YvshqEB3wZIsJ7Iejqb3gqnEti5_GNy2e-83w,4868
27
+ modules/transcriptomics.py,sha256=uMhwGu0-snyvtCIUWn2iYq0vmgNFyAOD7nnZ5ss3FI8,7113
28
+ modules/variants.py,sha256=dz_G5AtmooxJdtD2eP5Gv9JswyPywYieuA-0szyqhPM,5619
29
+ modules/visualization.py,sha256=DQs0_rZSiBFPaBiR1OzccDWcHjVvDX3MjblYcKTOTuA,8852
30
+ transpiler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
+ transpiler/parser.py,sha256=qobG29U3exKiliC23eYZ8HYSKwEK77GLTVnY7OD9CC0,9261
32
+ transpiler/python_bridge.py,sha256=S1dUVIzFfFP0AhzdfOkJ-HJhcORNOedxDggnYCn5eFU,10441
33
+ blixa-1.0.0.dist-info/METADATA,sha256=uhiVksffn9AjsqoVsRzv0Wp2-JDr_Tuu2jzw_3JkrEQ,702
34
+ blixa-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
35
+ blixa-1.0.0.dist-info/entry_points.txt,sha256=X2z9ocVhe8gBvVAUu0Md_cV2JoFamXwUinsWbQR1ozw,50
36
+ blixa-1.0.0.dist-info/top_level.txt,sha256=EzXHMRD7ieylbamlEXaeUOS0f60mysZDW3eHnM_T44E,35
37
+ blixa-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ blixa = run:main
3
+ run = run:main
@@ -0,0 +1,23 @@
1
+ # LICENSE
2
+
3
+ MIT License
4
+
5
+ Copyright (c) 2026 AHMED TARIG AHMED ABDALGLEEL
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ core
2
+ exceptions
3
+ modules
4
+ transpiler
core/__init__.py ADDED
File without changes
core/base.py ADDED
@@ -0,0 +1,88 @@
1
+ # core/base.py
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, List, Dict
5
+
6
+ class BioObject(ABC):
7
+ """
8
+ Abstract Base Class representing the core contract for all biological objects.
9
+ Enforces the 'SHARED METHODS' design (Section 3 of Core Syntax).
10
+ """
11
+
12
+ @abstractmethod
13
+ def value(self) -> Any:
14
+ """Get raw sequence or core value."""
15
+ pass
16
+
17
+ @abstractmethod
18
+ def len(self) -> int:
19
+ """Get the length of the object (e.g., sequence length or cell count)."""
20
+ pass
21
+
22
+ def __len__(self) -> int:
23
+ """Support for standard Python len() function."""
24
+ return self.len()
25
+
26
+ @abstractmethod
27
+ def at(self, pos: int) -> Any:
28
+ """Access item at a specific biological position (1-based index)."""
29
+ pass
30
+
31
+ @abstractmethod
32
+ def slice(self, start: int, end: int) -> 'BioObject':
33
+ """Extract a sub-part of the object."""
34
+ pass
35
+
36
+ @abstractmethod
37
+ def head(self, n: int = 5) -> 'BioObject':
38
+ """Get the first n elements."""
39
+ pass
40
+
41
+ @abstractmethod
42
+ def tail(self, n: int = 5) -> 'BioObject':
43
+ """Get the last n elements."""
44
+ pass
45
+
46
+ @abstractmethod
47
+ def find(self, pattern: Any) -> int:
48
+ """Find the first occurrence of a pattern."""
49
+ pass
50
+
51
+ @abstractmethod
52
+ def find_all(self, pattern: Any) -> List[int]:
53
+ """Find all occurrences of a pattern."""
54
+ pass
55
+
56
+ def rep(self, pattern: Any) -> int:
57
+ """Count repetitions of a pattern. Default implementation uses find_all()."""
58
+ return len(self.find_all(pattern))
59
+
60
+ @abstractmethod
61
+ def valid(self) -> bool:
62
+ """Quick validation check. Returns True if valid, False otherwise."""
63
+ pass
64
+
65
+ @abstractmethod
66
+ def validate(self) -> bool:
67
+ """Comprehensive biological validation. Should raise BioError if invalid."""
68
+ pass
69
+
70
+ @abstractmethod
71
+ def info(self) -> Dict[str, Any]:
72
+ """Return a dictionary containing brief information about the object."""
73
+ pass
74
+
75
+ @abstractmethod
76
+ def summary(self) -> str:
77
+ """Return a formatted string containing an extensive analytical summary."""
78
+ pass
79
+
80
+ @abstractmethod
81
+ def graph(self, graph_type: str, **kwargs) -> Any:
82
+ """Universal method for data visualization."""
83
+ pass
84
+
85
+ @abstractmethod
86
+ def save(self, file_path: str) -> None:
87
+ """Save the object data to a specified file."""
88
+ pass
core/cell.py ADDED
@@ -0,0 +1,168 @@
1
+ # core/cell.py
2
+
3
+ import json
4
+ import math
5
+ from typing import Any, List, Dict, Union
6
+ from core.base import BioObject
7
+ from exceptions.bio_errors import BiologicalLogicError, MissingDataError
8
+
9
+ class Cell(BioObject):
10
+ """
11
+ Core Single Cell representation.
12
+ Handles cell types, analytical labels, and single-cell gene expression mapping securely.
13
+ """
14
+
15
+ def __init__(self, cell_type: str = "unknown", expression_data: Dict[str, float] = None, strict: bool = True):
16
+ self._type = str(cell_type).strip()
17
+ self._label = "unlabeled"
18
+ self.strict = strict
19
+
20
+ self._expression_data: Dict[str, float] = {}
21
+
22
+ # Defensive Data Injection
23
+ if expression_data is not None:
24
+ if not isinstance(expression_data, dict):
25
+ raise BiologicalLogicError("error: expression_data must be a dictionary mapping gene names to numerical expression values.")
26
+
27
+ for gene_name, expr_value in expression_data.items():
28
+ try:
29
+ val = float(expr_value)
30
+ # Clean out NaNs and enforce non-negative biological expression constraints
31
+ if math.isnan(val) or val < 0:
32
+ val = 0.0
33
+ self._expression_data[str(gene_name).strip().upper()] = val
34
+ except (ValueError, TypeError):
35
+ if self.strict:
36
+ raise BiologicalLogicError(f"error: Invalid expression value '{expr_value}' for gene '{gene_name}'.")
37
+
38
+ if self.strict:
39
+ self.validate()
40
+
41
+ # --- PROPERTIES (Syntax: cell1.type = "neural") ---
42
+ @property
43
+ def type(self) -> str:
44
+ return self._type
45
+
46
+ @type.setter
47
+ def type(self, new_type: str):
48
+ self._type = str(new_type).strip()
49
+
50
+ @property
51
+ def label(self) -> str:
52
+ return self._label
53
+
54
+ @label.setter
55
+ def label(self, new_label: str):
56
+ self._label = str(new_label).strip()
57
+
58
+ # --- SHARED METHODS (Satisfying BioObject ABC) ---
59
+ def value(self) -> str:
60
+ return self._type
61
+
62
+ def len(self) -> int:
63
+ """Length of a cell represents the number of detected expressed genes."""
64
+ return len(self._expression_data)
65
+
66
+ def at(self, pos: int) -> Any:
67
+ raise BiologicalLogicError("error: at() is not conceptually applicable to a single cell outside a population.")
68
+
69
+ def slice(self, start: int, end: int) -> 'BioObject':
70
+ raise BiologicalLogicError("error: slice() is not conceptually applicable to a single cell.")
71
+
72
+ def head(self, n: int = 5) -> 'BioObject':
73
+ raise BiologicalLogicError("error: head() requires a sequence or population.")
74
+
75
+ def tail(self, n: int = 5) -> 'BioObject':
76
+ raise BiologicalLogicError("error: tail() requires a sequence or population.")
77
+
78
+ def find(self, pattern: Any) -> int:
79
+ raise BiologicalLogicError("error: find() is not applicable to a cell. Use .expression() to query genes.")
80
+
81
+ def find_all(self, pattern: Any) -> List[int]:
82
+ raise BiologicalLogicError("error: find_all() is not applicable to a cell. Use .expression() to query genes.")
83
+
84
+ def valid(self) -> bool:
85
+ return bool(self._type)
86
+
87
+ def validate(self) -> bool:
88
+ if not self.valid():
89
+ raise BiologicalLogicError("error: Cell type cannot be completely empty.")
90
+ return True
91
+
92
+ def info(self) -> Dict[str, Any]:
93
+ return {
94
+ "type": self.type,
95
+ "label": self.label,
96
+ "detected_genes": self.len()
97
+ }
98
+
99
+ def summary(self) -> str:
100
+ return f"Cell Summary: Type='{self.type}', Label='{self.label}', Expressed Genes: {self.len()}"
101
+
102
+ def graph(self, graph_type: str, **kwargs) -> Any:
103
+ print(f"[Visualization Engine] Graphing Cell: [{graph_type}] with params {kwargs}")
104
+ return None
105
+
106
+ def save(self, file_path: str) -> None:
107
+ """Saves the cell's profile and expression dictionary to a JSON file."""
108
+ data = {
109
+ "type": self.type,
110
+ "label": self.label,
111
+ "detected_genes": self.len(),
112
+ "expression": self._expression_data
113
+ }
114
+ try:
115
+ with open(file_path, "w") as f:
116
+ json.dump(data, f, indent=4)
117
+ except Exception as e:
118
+ raise BiologicalLogicError(f"error: Failed to save cell data to '{file_path}'. Details: {str(e)}")
119
+
120
+ # --- HELPER METHOD ---
121
+ def _extract_gene_name(self, obj: Any) -> str:
122
+ """Safely extracts the gene name whether the input is a string or a BioObject (Gene)."""
123
+ if isinstance(obj, BioObject):
124
+ return str(obj.value()).strip().upper()
125
+ return str(obj).strip().upper()
126
+
127
+ # --- CELL SPECIFIC METHODS (Section 8 of Syntax) ---
128
+ def expression(self, gene_or_genes: Union[Any, List[Any]]) -> Union[float, Dict[str, float]]:
129
+ """Get expression level of specific gene(s) (can pass string or Gene objects)."""
130
+ if not self._expression_data:
131
+ raise MissingDataError(f"error: No expression data loaded for this cell.")
132
+
133
+ if isinstance(gene_or_genes, list):
134
+ return {
135
+ self._extract_gene_name(g): self._expression_data.get(self._extract_gene_name(g), 0.0)
136
+ for g in gene_or_genes
137
+ }
138
+
139
+ target_gene = self._extract_gene_name(gene_or_genes)
140
+ return self._expression_data.get(target_gene, 0.0)
141
+
142
+ def markers(self, target_type: str = None) -> Any:
143
+ """Analyze marker genes."""
144
+ raise BiologicalLogicError("error: markers() dynamic discovery requires 'use singlecell' module to cross-reference databases.")
145
+
146
+ def state(self) -> Any:
147
+ """Analyze cell state (stress, cell_cycle, etc.)."""
148
+ raise BiologicalLogicError("error: state() profiling requires 'use singlecell' module.")
149
+
150
+ # --- STEM CELL MODULE INTEGRATIONS ---
151
+ def pluripotency_score(self) -> float:
152
+ raise BiologicalLogicError("error: pluripotency_score() requires 'use stemcell' module.")
153
+
154
+ def differentiate(self, target_type: str) -> 'Cell':
155
+ raise BiologicalLogicError("error: differentiate() requires 'use stemcell' module.")
156
+
157
+ def reprogram(self, target_type: str = "iPSC") -> 'Cell':
158
+ raise BiologicalLogicError("error: reprogram() requires 'use stemcell' module.")
159
+
160
+ def lineage(self) -> Any:
161
+ raise BiologicalLogicError("error: lineage() requires 'use stemcell' module.")
162
+
163
+ def __repr__(self) -> str:
164
+ return f"cell('{self.type}', genes={self.len()})"
165
+
166
+ # General Function Wrapper
167
+ def cell(cell_type: str = "unknown", **kwargs) -> Cell:
168
+ return Cell(cell_type, **kwargs)
core/dna.py ADDED
@@ -0,0 +1,233 @@
1
+ # core/dna.py
2
+
3
+ import re
4
+ from typing import Any, List, Dict, Union
5
+ from core.base import BioObject
6
+ from exceptions.bio_errors import SequenceValidationError, BiologicalLogicError
7
+
8
+ class DNA(BioObject):
9
+ """
10
+ Core DNA sequence representation with strict validation, method chaining,
11
+ defensive error handling, and customizable biological/mathematical constants.
12
+ """
13
+
14
+ DEFAULT_CONSTANTS = {
15
+ "VALID_BASES": set("ATGC"),
16
+ "GC_WEIGHT": 1.0
17
+ }
18
+
19
+ def __init__(self, sequence: Any = "", strict: bool = True, custom_constants: dict = None):
20
+ if sequence is None:
21
+ sequence = ""
22
+ elif isinstance(sequence, BioObject):
23
+ sequence = str(sequence.value())
24
+
25
+ if not isinstance(sequence, str):
26
+ sequence = str(sequence)
27
+
28
+ self._seq = sequence.upper().strip()
29
+ self.strict = strict
30
+
31
+ self.constants = self.DEFAULT_CONSTANTS.copy()
32
+ if custom_constants:
33
+ if "VALID_BASES" in custom_constants and not isinstance(custom_constants["VALID_BASES"], set):
34
+ custom_constants["VALID_BASES"] = set(custom_constants["VALID_BASES"])
35
+ self.constants.update(custom_constants)
36
+
37
+ self._mutation_log: List[Dict[str, Any]] = []
38
+
39
+ if self.strict:
40
+ self.validate()
41
+
42
+ def _get_inheritable_constants(self) -> Dict[str, Any]:
43
+ """Filters constants to pass only mathematical ones avoiding base set leakage."""
44
+ return {k: v for k, v in self.constants.items() if k != "VALID_BASES"}
45
+
46
+ # --- SHARED METHODS ---
47
+ def value(self) -> str:
48
+ return self._seq
49
+
50
+ def len(self) -> int:
51
+ return len(self._seq)
52
+
53
+ def at(self, pos: int) -> str:
54
+ if self.len() == 0:
55
+ raise BiologicalLogicError("error: sequence is empty.")
56
+ if pos < 1 or pos > self.len():
57
+ raise BiologicalLogicError(f"error: position {pos} is outside sequence length {self.len()}.")
58
+ return self._seq[pos - 1]
59
+
60
+ def slice(self, start: int, end: int) -> 'DNA':
61
+ if self.len() == 0:
62
+ return DNA("", strict=self.strict, custom_constants=self.constants)
63
+
64
+ if start < 1 or end > self.len() or start > end:
65
+ raise BiologicalLogicError(f"error: invalid slice boundaries [{start}, {end}] for length {self.len()}.")
66
+
67
+ return DNA(self._seq[start - 1 : end], strict=self.strict, custom_constants=self.constants)
68
+
69
+ def head(self, n: int = 5) -> 'DNA':
70
+ if n < 0:
71
+ raise BiologicalLogicError("error: head() requires a non-negative integer.")
72
+ return DNA(self._seq[:n], strict=self.strict, custom_constants=self.constants)
73
+
74
+ def tail(self, n: int = 5) -> 'DNA':
75
+ if n < 0:
76
+ raise BiologicalLogicError("error: tail() requires a non-negative integer.")
77
+ if n == 0:
78
+ return DNA("", strict=self.strict, custom_constants=self.constants)
79
+ return DNA(self._seq[-n:], strict=self.strict, custom_constants=self.constants)
80
+
81
+ def find(self, pattern: Any) -> int:
82
+ idx = self._seq.find(str(pattern).upper())
83
+ return idx + 1 if idx != -1 else 0
84
+
85
+ def find_all(self, pattern: Any) -> List[int]:
86
+ pattern_str = str(pattern).upper()
87
+ if not pattern_str:
88
+ return []
89
+ return [m.start() + 1 for m in re.finditer(f"(?={pattern_str})", self._seq)]
90
+
91
+ def valid(self) -> bool:
92
+ if not self._seq:
93
+ return True
94
+ return set(self._seq).issubset(self.constants["VALID_BASES"])
95
+
96
+ def validate(self) -> bool:
97
+ if not self.valid():
98
+ invalid_chars = set(self._seq) - self.constants["VALID_BASES"]
99
+ raise SequenceValidationError(f"error: invalid dna base(s) {list(invalid_chars)} found.")
100
+ return True
101
+
102
+ def info(self) -> Dict[str, Any]:
103
+ return {"type": "DNA", "length": self.len(), "gc": self.gc()}
104
+
105
+ def summary(self) -> str:
106
+ return f"DNA Summary: {self.len()} bp, GC: {self.gc()}%\nComposition: {self.composition()}"
107
+
108
+ def graph(self, graph_type: str = "default", **kwargs) -> Any:
109
+ print(f"[Visualization Engine] Graphing DNA: [{graph_type}] with params {kwargs}")
110
+ return None
111
+
112
+ def save(self, file_path: str) -> None:
113
+ try:
114
+ with open(file_path, "w") as f:
115
+ f.write(self._seq)
116
+ except Exception as e:
117
+ raise BiologicalLogicError(f"error: Failed to save DNA sequence to '{file_path}'. Details: {str(e)}")
118
+
119
+ # --- DNA SPECIFIC METHODS ---
120
+ def gc(self) -> float:
121
+ if not self._seq:
122
+ return 0.0
123
+ g = self._seq.count('G')
124
+ c = self._seq.count('C')
125
+ result = ((g + c) / self.len()) * 100 * self.constants.get("GC_WEIGHT", 1.0)
126
+ return round(result, 2)
127
+
128
+ def rev(self) -> 'DNA':
129
+ return DNA(self._seq[::-1], strict=self.strict, custom_constants=self.constants)
130
+
131
+ def comp(self) -> 'DNA':
132
+ trans = str.maketrans("ATGCURYKMSWBDHVN", "TACGAYRMKWSVHDBN")
133
+ return DNA(self._seq.translate(trans), strict=self.strict, custom_constants=self.constants)
134
+
135
+ def rev_comp(self) -> 'DNA':
136
+ return self.rev().comp()
137
+
138
+ def set(self, pos: int, base: str) -> 'DNA':
139
+ if pos < 1 or pos > self.len():
140
+ raise BiologicalLogicError(f"error: position {pos} is out of bounds.")
141
+ seq_list = list(self._seq)
142
+ seq_list[pos - 1] = str(base).upper()
143
+ return DNA("".join(seq_list), strict=self.strict, custom_constants=self.constants)
144
+
145
+ def insert(self, pos: int, base: str) -> 'DNA':
146
+ if pos < 1 or pos > self.len() + 1:
147
+ raise BiologicalLogicError(f"error: position {pos} is out of bounds for insertion.")
148
+ seq_list = list(self._seq)
149
+ seq_list.insert(pos - 1, str(base).upper())
150
+ return DNA("".join(seq_list), strict=self.strict, custom_constants=self.constants)
151
+
152
+ def delete(self, pos: int) -> 'DNA':
153
+ if pos < 1 or pos > self.len():
154
+ raise BiologicalLogicError(f"error: position {pos} is out of bounds.")
155
+ seq_list = list(self._seq)
156
+ seq_list.pop(pos - 1)
157
+ return DNA("".join(seq_list), strict=self.strict, custom_constants=self.constants)
158
+
159
+ def mutate(self, pos: int, base: str) -> 'DNA':
160
+ orig = self.at(pos)
161
+ mutated_dna = self.set(pos, base)
162
+ mutated_dna._mutation_log = list(self._mutation_log)
163
+ mutated_dna._mutation_log.append({
164
+ "position": pos,
165
+ "original": orig,
166
+ "new": str(base).upper(),
167
+ "type": "substitution"
168
+ })
169
+ return mutated_dna
170
+
171
+ def composition(self) -> Dict[str, int]:
172
+ return {b: self._seq.count(b) for b in self.constants["VALID_BASES"]}
173
+
174
+ def codons(self) -> List[str]:
175
+ return [self._seq[i:i+3] for i in range(0, self.len() - self.len() % 3, 3)]
176
+
177
+ def motifs(self) -> Dict[str, List[int]]:
178
+ """Scans for fundamental biological motifs and returns their 1-based start positions."""
179
+ standard_motifs = {
180
+ "TATA_BOX": "TATAAA",
181
+ "CAAT_BOX": "CCAAT",
182
+ "GC_BOX": "GGGCGG"
183
+ }
184
+ found_motifs = {}
185
+ for name, pattern in standard_motifs.items():
186
+ positions = self.find_all(pattern)
187
+ if positions:
188
+ found_motifs[name] = positions
189
+ return found_motifs
190
+
191
+ def motif(self, name: str) -> List[int]:
192
+ return self.find_all(name)
193
+
194
+ def promoter(self, upstream: int = 50) -> 'DNA':
195
+ return self.head(min(upstream, self.len()))
196
+
197
+ def orfs(self) -> List['DNA']:
198
+ orfs_list = []
199
+ seq_len = self.len()
200
+ if seq_len < 3:
201
+ return orfs_list
202
+
203
+ stop_codons = {"TAA", "TAG", "TGA"}
204
+ for i in range(seq_len - 2):
205
+ if self._seq[i:i+3] == "ATG":
206
+ for j in range(i + 3, seq_len - 2, 3):
207
+ codon = self._seq[j:j+3]
208
+ if codon in stop_codons:
209
+ orfs_list.append(self.slice(i + 1, j + 3))
210
+ break
211
+ return orfs_list
212
+
213
+ def transcribe(self) -> 'BioObject':
214
+ from core.rna import RNA
215
+ rna_seq = self._seq.replace("T", "U")
216
+ return RNA(rna_seq, strict=self.strict, custom_constants=self._get_inheritable_constants())
217
+
218
+ def translate(self) -> 'BioObject':
219
+ return self.transcribe().translate()
220
+
221
+ def methylation(self) -> Any:
222
+ raise BiologicalLogicError("error: methylation() analysis requires 'use epigenetics' module.")
223
+
224
+ def snp(self) -> Any:
225
+ raise BiologicalLogicError("error: snp() variant calling requires 'use variants' module.")
226
+
227
+ def __repr__(self) -> str:
228
+ preview = f"{self._seq[:20]}..." if self.len() > 20 else self._seq
229
+ return f"dna('{preview}')"
230
+
231
+ # General Function Wrapper
232
+ def dna(sequence: Any = "", **kwargs) -> DNA:
233
+ return DNA(sequence, **kwargs)