celltype-cli 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.
- celltype_cli-0.1.0.dist-info/METADATA +267 -0
- celltype_cli-0.1.0.dist-info/RECORD +89 -0
- celltype_cli-0.1.0.dist-info/WHEEL +4 -0
- celltype_cli-0.1.0.dist-info/entry_points.txt +2 -0
- celltype_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- ct/__init__.py +3 -0
- ct/agent/__init__.py +0 -0
- ct/agent/case_studies.py +426 -0
- ct/agent/config.py +523 -0
- ct/agent/doctor.py +544 -0
- ct/agent/knowledge.py +523 -0
- ct/agent/loop.py +99 -0
- ct/agent/mcp_server.py +478 -0
- ct/agent/orchestrator.py +733 -0
- ct/agent/runner.py +656 -0
- ct/agent/sandbox.py +481 -0
- ct/agent/session.py +145 -0
- ct/agent/system_prompt.py +186 -0
- ct/agent/trace_store.py +228 -0
- ct/agent/trajectory.py +169 -0
- ct/agent/types.py +182 -0
- ct/agent/workflows.py +462 -0
- ct/api/__init__.py +1 -0
- ct/api/app.py +211 -0
- ct/api/config.py +120 -0
- ct/api/engine.py +124 -0
- ct/cli.py +1448 -0
- ct/data/__init__.py +0 -0
- ct/data/compute_providers.json +59 -0
- ct/data/cro_database.json +395 -0
- ct/data/downloader.py +238 -0
- ct/data/loaders.py +252 -0
- ct/kb/__init__.py +5 -0
- ct/kb/benchmarks.py +147 -0
- ct/kb/governance.py +106 -0
- ct/kb/ingest.py +415 -0
- ct/kb/reasoning.py +129 -0
- ct/kb/schema_monitor.py +162 -0
- ct/kb/substrate.py +387 -0
- ct/models/__init__.py +0 -0
- ct/models/llm.py +370 -0
- ct/tools/__init__.py +195 -0
- ct/tools/_compound_resolver.py +297 -0
- ct/tools/biomarker.py +368 -0
- ct/tools/cellxgene.py +282 -0
- ct/tools/chemistry.py +1371 -0
- ct/tools/claude.py +390 -0
- ct/tools/clinical.py +1153 -0
- ct/tools/clue.py +249 -0
- ct/tools/code.py +1069 -0
- ct/tools/combination.py +397 -0
- ct/tools/compute.py +402 -0
- ct/tools/cro.py +413 -0
- ct/tools/data_api.py +2114 -0
- ct/tools/design.py +295 -0
- ct/tools/dna.py +575 -0
- ct/tools/experiment.py +604 -0
- ct/tools/expression.py +655 -0
- ct/tools/files.py +957 -0
- ct/tools/genomics.py +1387 -0
- ct/tools/http_client.py +146 -0
- ct/tools/imaging.py +319 -0
- ct/tools/intel.py +223 -0
- ct/tools/literature.py +743 -0
- ct/tools/network.py +422 -0
- ct/tools/notification.py +111 -0
- ct/tools/omics.py +3330 -0
- ct/tools/ops.py +1230 -0
- ct/tools/parity.py +649 -0
- ct/tools/pk.py +245 -0
- ct/tools/protein.py +678 -0
- ct/tools/regulatory.py +643 -0
- ct/tools/remote_data.py +179 -0
- ct/tools/report.py +181 -0
- ct/tools/repurposing.py +376 -0
- ct/tools/safety.py +1280 -0
- ct/tools/shell.py +178 -0
- ct/tools/singlecell.py +533 -0
- ct/tools/statistics.py +552 -0
- ct/tools/structure.py +882 -0
- ct/tools/target.py +901 -0
- ct/tools/translational.py +123 -0
- ct/tools/viability.py +218 -0
- ct/ui/__init__.py +0 -0
- ct/ui/markdown.py +31 -0
- ct/ui/status.py +258 -0
- ct/ui/suggestions.py +567 -0
- ct/ui/terminal.py +1456 -0
- ct/ui/traces.py +112 -0
ct/tools/design.py
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Design tools: compound modification suggestions, med-chem optimization.
|
|
3
|
+
|
|
4
|
+
Provides AI-guided medicinal chemistry recommendations using RDKit-based
|
|
5
|
+
property calculations, bioisosteric replacement rules, and Lipinski/Veber
|
|
6
|
+
scoring.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from ct.tools import registry
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Common medicinal chemistry transformations: (SMARTS_pattern, SMARTS_replacement, description)
|
|
13
|
+
# Each entry: (name, smarts_from, list_of_replacements)
|
|
14
|
+
_MEDCHEM_TRANSFORMS = [
|
|
15
|
+
# Halogen walks
|
|
16
|
+
("F_to_Cl", "[cH0:1][F:2]", "[cH0:1][Cl]", "F->Cl: increase lipophilicity and steric bulk"),
|
|
17
|
+
("F_to_H", "[cH0:1][F:2]", "[cH:1]", "F->H: remove halogen, reduce MW"),
|
|
18
|
+
("Cl_to_F", "[cH0:1][Cl:2]", "[cH0:1][F]", "Cl->F: reduce lipophilicity, metabolic block"),
|
|
19
|
+
# Alkyl modifications
|
|
20
|
+
("Me_to_Et", "[CH3:1]([#6:2])", "[CH2:1]([#6:2])C", "Me->Et: increase steric bulk, explore SAR"),
|
|
21
|
+
("OH_to_OMe", "[c:1][OH:2]", "[c:1]OC", "OH->OMe: cap phenol, block glucuronidation"),
|
|
22
|
+
("OMe_to_OH", "[c:1][O:2][CH3]", "[c:1][OH]", "OMe->OH: add H-bond donor, improve solubility"),
|
|
23
|
+
# N-modifications
|
|
24
|
+
("NH_to_NMe", "[NH2:1]", "[NH:1]C", "NH2->NHMe: reduce basicity, improve metabolic stability"),
|
|
25
|
+
("NMe_to_NH", "[NH:1]([CH3])", "[NH2:1]", "NHMe->NH2: simplify, add H-bond donor"),
|
|
26
|
+
# Ring modifications
|
|
27
|
+
("phenyl_to_pyridine", "[c:1]1[c:2][c:3][c:4][c:5][cH:6]1", "[c:1]1[c:2][c:3][c:4][c:5][n:6]1",
|
|
28
|
+
"phenyl->pyridyl: improve solubility, add H-bond acceptor"),
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _compute_properties(mol) -> dict:
|
|
33
|
+
"""Compute drug-relevant molecular properties."""
|
|
34
|
+
from rdkit.Chem import Descriptors, rdMolDescriptors
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
"mw": round(Descriptors.MolWt(mol), 1),
|
|
38
|
+
"logp": round(Descriptors.MolLogP(mol), 2),
|
|
39
|
+
"hbd": Descriptors.NumHDonors(mol),
|
|
40
|
+
"hba": Descriptors.NumHAcceptors(mol),
|
|
41
|
+
"tpsa": round(Descriptors.TPSA(mol), 1),
|
|
42
|
+
"rotatable_bonds": Descriptors.NumRotatableBonds(mol),
|
|
43
|
+
"rings": Descriptors.RingCount(mol),
|
|
44
|
+
"aromatic_rings": Descriptors.NumAromaticRings(mol),
|
|
45
|
+
"fsp3": round(rdMolDescriptors.CalcFractionCSP3(mol), 3),
|
|
46
|
+
"heavy_atoms": mol.GetNumHeavyAtoms(),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _lipinski_violations(props: dict) -> int:
|
|
51
|
+
"""Count Lipinski Rule-of-5 violations."""
|
|
52
|
+
return sum([
|
|
53
|
+
props["mw"] > 500,
|
|
54
|
+
props["logp"] > 5,
|
|
55
|
+
props["hbd"] > 5,
|
|
56
|
+
props["hba"] > 10,
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _veber_violations(props: dict) -> int:
|
|
61
|
+
"""Count Veber oral bioavailability rule violations."""
|
|
62
|
+
return sum([
|
|
63
|
+
props["tpsa"] > 140,
|
|
64
|
+
props["rotatable_bonds"] > 10,
|
|
65
|
+
])
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _score_for_objective(parent_props: dict, child_props: dict,
|
|
69
|
+
objective: str) -> float:
|
|
70
|
+
"""Score a modification relative to the parent, for a given objective.
|
|
71
|
+
|
|
72
|
+
Returns a float where higher is better (range roughly -1 to +1).
|
|
73
|
+
"""
|
|
74
|
+
if objective == "potency":
|
|
75
|
+
# Favour: lower MW, moderate LogP (2-4), more H-bond interactions
|
|
76
|
+
logp_score = 1.0 - abs(child_props["logp"] - 3.0) / 5.0
|
|
77
|
+
mw_score = (parent_props["mw"] - child_props["mw"]) / 100.0
|
|
78
|
+
return round(logp_score * 0.5 + mw_score * 0.3 + 0.2, 3)
|
|
79
|
+
|
|
80
|
+
elif objective == "selectivity":
|
|
81
|
+
# Favour: more specific interactions (more HBD/HBA), higher TPSA
|
|
82
|
+
hb_delta = (child_props["hbd"] + child_props["hba"]) - (parent_props["hbd"] + parent_props["hba"])
|
|
83
|
+
tpsa_delta = (child_props["tpsa"] - parent_props["tpsa"]) / 50.0
|
|
84
|
+
return round(hb_delta * 0.3 + tpsa_delta * 0.4 + 0.3, 3)
|
|
85
|
+
|
|
86
|
+
elif objective == "admet":
|
|
87
|
+
# Favour: fewer Lipinski/Veber violations, moderate LogP
|
|
88
|
+
lip = _lipinski_violations(child_props)
|
|
89
|
+
veb = _veber_violations(child_props)
|
|
90
|
+
penalty = lip * 0.25 + veb * 0.25
|
|
91
|
+
logp_bonus = 1.0 - abs(child_props["logp"] - 2.5) / 5.0
|
|
92
|
+
return round(max(0, 1.0 - penalty) * 0.6 + logp_bonus * 0.4, 3)
|
|
93
|
+
|
|
94
|
+
elif objective == "solubility":
|
|
95
|
+
# Favour: lower LogP, higher TPSA, higher Fsp3
|
|
96
|
+
logp_score = max(0, (parent_props["logp"] - child_props["logp"]) / 3.0)
|
|
97
|
+
tpsa_score = max(0, (child_props["tpsa"] - parent_props["tpsa"]) / 40.0)
|
|
98
|
+
fsp3_score = max(0, (child_props["fsp3"] - parent_props["fsp3"]))
|
|
99
|
+
return round(logp_score * 0.4 + tpsa_score * 0.3 + fsp3_score * 0.3, 3)
|
|
100
|
+
|
|
101
|
+
elif objective == "metabolic_stability":
|
|
102
|
+
# Favour: fewer metabolic soft spots (reduce ArOH, add F-blocks)
|
|
103
|
+
logp_score = 1.0 - abs(child_props["logp"] - 2.0) / 5.0
|
|
104
|
+
mw_penalty = max(0, (child_props["mw"] - 500) / 200.0)
|
|
105
|
+
rotbond_penalty = max(0, (child_props["rotatable_bonds"] - 7) / 5.0)
|
|
106
|
+
return round(logp_score * 0.4 - mw_penalty * 0.3 - rotbond_penalty * 0.3, 3)
|
|
107
|
+
|
|
108
|
+
# Default: balanced score
|
|
109
|
+
return 0.5
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@registry.register(
|
|
113
|
+
name="design.suggest_modifications",
|
|
114
|
+
description="Suggest medicinal chemistry modifications to improve a compound's properties",
|
|
115
|
+
category="design",
|
|
116
|
+
parameters={
|
|
117
|
+
"smiles": "Input compound SMILES string",
|
|
118
|
+
"objective": "Optimization goal: potency, selectivity, admet, solubility, metabolic_stability (default: potency)",
|
|
119
|
+
"n_suggestions": "Number of suggestions to return (default 5)",
|
|
120
|
+
},
|
|
121
|
+
usage_guide=(
|
|
122
|
+
"You have a hit or lead compound and want to generate ideas for "
|
|
123
|
+
"structural modifications to improve potency, selectivity, ADMET, "
|
|
124
|
+
"solubility, or metabolic stability. Returns modified SMILES with "
|
|
125
|
+
"property comparisons and medicinal chemistry rationale."
|
|
126
|
+
),
|
|
127
|
+
)
|
|
128
|
+
def suggest_modifications(smiles: str, objective: str = "potency",
|
|
129
|
+
n_suggestions: int = 5, **kwargs) -> dict:
|
|
130
|
+
"""Suggest medicinal chemistry modifications for a compound.
|
|
131
|
+
|
|
132
|
+
Applies bioisosteric replacements and common med-chem transforms using
|
|
133
|
+
RDKit reaction SMARTS, then scores each modification against the
|
|
134
|
+
specified objective.
|
|
135
|
+
"""
|
|
136
|
+
try:
|
|
137
|
+
from rdkit import Chem
|
|
138
|
+
from rdkit.Chem import AllChem, Descriptors
|
|
139
|
+
except ImportError:
|
|
140
|
+
return {"error": "RDKit is required for compound modification suggestions (pip install rdkit)", "summary": "RDKit is required for compound modification suggestions (pip install rdkit)"}
|
|
141
|
+
valid_objectives = ("potency", "selectivity", "admet", "solubility", "metabolic_stability")
|
|
142
|
+
if objective not in valid_objectives:
|
|
143
|
+
return {"error": f"Unknown objective '{objective}'. Choose from: {', '.join(valid_objectives)}", "summary": f"Unknown objective '{objective}'. Choose from: {', '.join(valid_objectives)}"}
|
|
144
|
+
mol = Chem.MolFromSmiles(smiles)
|
|
145
|
+
if mol is None:
|
|
146
|
+
return {"error": f"Invalid SMILES: {smiles}", "summary": f"Could not parse SMILES: {smiles}"}
|
|
147
|
+
|
|
148
|
+
parent_props = _compute_properties(mol)
|
|
149
|
+
parent_violations = _lipinski_violations(parent_props)
|
|
150
|
+
canonical = Chem.MolToSmiles(mol)
|
|
151
|
+
|
|
152
|
+
suggestions = []
|
|
153
|
+
seen_smiles = {canonical}
|
|
154
|
+
|
|
155
|
+
# Apply each transformation
|
|
156
|
+
for name, smarts_from, smarts_to, description in _MEDCHEM_TRANSFORMS:
|
|
157
|
+
try:
|
|
158
|
+
rxn_smarts = f"[{smarts_from}]>>[{smarts_to}]"
|
|
159
|
+
# Use ReplaceSubstructs instead of reaction for reliability
|
|
160
|
+
pattern = Chem.MolFromSmarts(smarts_from)
|
|
161
|
+
if pattern is None:
|
|
162
|
+
continue
|
|
163
|
+
if not mol.HasSubstructMatch(pattern):
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
# Attempt reaction-based transform
|
|
167
|
+
rxn = AllChem.ReactionFromSmarts(f"{smarts_from}>>{smarts_to}")
|
|
168
|
+
if rxn is None:
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
products = rxn.RunReactants((mol,))
|
|
172
|
+
for product_set in products:
|
|
173
|
+
for product in product_set:
|
|
174
|
+
try:
|
|
175
|
+
Chem.SanitizeMol(product)
|
|
176
|
+
product_smi = Chem.MolToSmiles(product)
|
|
177
|
+
if product_smi in seen_smiles:
|
|
178
|
+
continue
|
|
179
|
+
seen_smiles.add(product_smi)
|
|
180
|
+
|
|
181
|
+
child_props = _compute_properties(product)
|
|
182
|
+
child_violations = _lipinski_violations(child_props)
|
|
183
|
+
|
|
184
|
+
score = _score_for_objective(parent_props, child_props, objective)
|
|
185
|
+
|
|
186
|
+
# Property deltas
|
|
187
|
+
deltas = {
|
|
188
|
+
"mw": round(child_props["mw"] - parent_props["mw"], 1),
|
|
189
|
+
"logp": round(child_props["logp"] - parent_props["logp"], 2),
|
|
190
|
+
"hbd": child_props["hbd"] - parent_props["hbd"],
|
|
191
|
+
"hba": child_props["hba"] - parent_props["hba"],
|
|
192
|
+
"tpsa": round(child_props["tpsa"] - parent_props["tpsa"], 1),
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
suggestions.append({
|
|
196
|
+
"smiles": product_smi,
|
|
197
|
+
"transform": name,
|
|
198
|
+
"rationale": description,
|
|
199
|
+
"score": score,
|
|
200
|
+
"properties": child_props,
|
|
201
|
+
"property_deltas": deltas,
|
|
202
|
+
"lipinski_violations": child_violations,
|
|
203
|
+
})
|
|
204
|
+
except Exception:
|
|
205
|
+
continue
|
|
206
|
+
except Exception:
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
# Also add simple functional group additions if we have few suggestions
|
|
210
|
+
_simple_additions = [
|
|
211
|
+
("add_F", "F", "Add fluorine — metabolic blocker, minimal size increase"),
|
|
212
|
+
("add_OH", "O", "Add hydroxyl — improve solubility, add H-bond donor"),
|
|
213
|
+
("add_NH2", "N", "Add amine — add H-bond donor, potential salt formation"),
|
|
214
|
+
("add_Me", "C", "Add methyl — explore steric effects, fill hydrophobic pocket"),
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
for add_name, add_atom, add_desc in _simple_additions:
|
|
218
|
+
if len(suggestions) >= n_suggestions * 3:
|
|
219
|
+
break
|
|
220
|
+
try:
|
|
221
|
+
# Find an aromatic carbon that could accept a substituent
|
|
222
|
+
pattern = Chem.MolFromSmarts("[cH]")
|
|
223
|
+
if pattern and mol.HasSubstructMatch(pattern):
|
|
224
|
+
matches = mol.GetSubstructMatches(pattern)
|
|
225
|
+
if matches:
|
|
226
|
+
from rdkit.Chem import RWMol
|
|
227
|
+
for match in matches[:1]: # just first match
|
|
228
|
+
rw = RWMol(mol)
|
|
229
|
+
new_idx = rw.AddAtom(Chem.Atom(add_atom))
|
|
230
|
+
rw.AddBond(match[0], new_idx, Chem.BondType.SINGLE)
|
|
231
|
+
try:
|
|
232
|
+
Chem.SanitizeMol(rw)
|
|
233
|
+
new_smi = Chem.MolToSmiles(rw)
|
|
234
|
+
if new_smi in seen_smiles:
|
|
235
|
+
continue
|
|
236
|
+
seen_smiles.add(new_smi)
|
|
237
|
+
|
|
238
|
+
new_mol = Chem.MolFromSmiles(new_smi)
|
|
239
|
+
if new_mol is None:
|
|
240
|
+
continue
|
|
241
|
+
|
|
242
|
+
child_props = _compute_properties(new_mol)
|
|
243
|
+
child_violations = _lipinski_violations(child_props)
|
|
244
|
+
score = _score_for_objective(parent_props, child_props, objective)
|
|
245
|
+
|
|
246
|
+
deltas = {
|
|
247
|
+
"mw": round(child_props["mw"] - parent_props["mw"], 1),
|
|
248
|
+
"logp": round(child_props["logp"] - parent_props["logp"], 2),
|
|
249
|
+
"hbd": child_props["hbd"] - parent_props["hbd"],
|
|
250
|
+
"hba": child_props["hba"] - parent_props["hba"],
|
|
251
|
+
"tpsa": round(child_props["tpsa"] - parent_props["tpsa"], 1),
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
suggestions.append({
|
|
255
|
+
"smiles": new_smi,
|
|
256
|
+
"transform": add_name,
|
|
257
|
+
"rationale": add_desc,
|
|
258
|
+
"score": score,
|
|
259
|
+
"properties": child_props,
|
|
260
|
+
"property_deltas": deltas,
|
|
261
|
+
"lipinski_violations": child_violations,
|
|
262
|
+
})
|
|
263
|
+
except Exception:
|
|
264
|
+
continue
|
|
265
|
+
except Exception:
|
|
266
|
+
continue
|
|
267
|
+
|
|
268
|
+
# Sort by score descending, take top n
|
|
269
|
+
suggestions.sort(key=lambda s: s["score"], reverse=True)
|
|
270
|
+
top = suggestions[:n_suggestions]
|
|
271
|
+
|
|
272
|
+
if not top:
|
|
273
|
+
return {
|
|
274
|
+
"summary": f"No modifications found for {smiles} (no applicable transforms matched)",
|
|
275
|
+
"parent_smiles": canonical,
|
|
276
|
+
"parent_properties": parent_props,
|
|
277
|
+
"suggestions": [],
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# Build summary
|
|
281
|
+
best = top[0]
|
|
282
|
+
logp_delta = best["property_deltas"]["logp"]
|
|
283
|
+
delta_str = f"LogP {logp_delta:+.2f}" if logp_delta != 0 else "similar LogP"
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
"summary": (
|
|
287
|
+
f"{len(top)} modification(s) suggested for {canonical[:40]} "
|
|
288
|
+
f"(objective={objective}): top suggestion {best['transform']} ({delta_str})"
|
|
289
|
+
),
|
|
290
|
+
"parent_smiles": canonical,
|
|
291
|
+
"parent_properties": parent_props,
|
|
292
|
+
"parent_lipinski_violations": parent_violations,
|
|
293
|
+
"objective": objective,
|
|
294
|
+
"suggestions": top,
|
|
295
|
+
}
|