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.
- blixa-1.0.0.dist-info/METADATA +25 -0
- blixa-1.0.0.dist-info/RECORD +37 -0
- blixa-1.0.0.dist-info/WHEEL +5 -0
- blixa-1.0.0.dist-info/entry_points.txt +3 -0
- blixa-1.0.0.dist-info/licenses/LICENSE +23 -0
- blixa-1.0.0.dist-info/top_level.txt +4 -0
- core/__init__.py +0 -0
- core/base.py +88 -0
- core/cell.py +168 -0
- core/dna.py +233 -0
- core/experiment.py +221 -0
- core/functions.py +283 -0
- core/gene.py +199 -0
- core/population.py +447 -0
- core/protein.py +240 -0
- core/result.py +286 -0
- core/rna.py +192 -0
- core/workflow.py +202 -0
- exceptions/__init__.py +0 -0
- exceptions/bio_errors.py +21 -0
- modules/__init__.py +0 -0
- modules/crispr.py +248 -0
- modules/epigenetics.py +173 -0
- modules/genomics.py +155 -0
- modules/integration.py +213 -0
- modules/pathways.py +165 -0
- modules/proteomics.py +133 -0
- modules/singlecell.py +158 -0
- modules/statistics.py +151 -0
- modules/stemcell.py +120 -0
- modules/structure.py +127 -0
- modules/transcriptomics.py +175 -0
- modules/variants.py +144 -0
- modules/visualization.py +224 -0
- transpiler/__init__.py +0 -0
- transpiler/parser.py +262 -0
- transpiler/python_bridge.py +247 -0
|
@@ -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,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.
|
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)
|