@aleph-ai/tinyaleph 1.2.1 → 1.3.0
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.
- package/README.md +187 -2
- package/backends/bioinformatics/binding.js +503 -0
- package/backends/bioinformatics/dna-computing.js +664 -0
- package/backends/bioinformatics/encoding.js +339 -0
- package/backends/bioinformatics/folding.js +454 -0
- package/backends/bioinformatics/genetic-code.js +269 -0
- package/backends/bioinformatics/index.js +522 -0
- package/backends/bioinformatics/transcription.js +221 -0
- package/backends/bioinformatics/translation.js +264 -0
- package/backends/index.js +25 -1
- package/core/compound.js +532 -0
- package/core/hilbert.js +454 -1
- package/core/index.js +106 -12
- package/core/inference.js +605 -0
- package/core/resonance.js +245 -616
- package/core/symbols/archetypes.js +478 -0
- package/core/symbols/base.js +302 -0
- package/core/symbols/elements.js +487 -0
- package/core/symbols/hieroglyphs.js +303 -0
- package/core/symbols/iching.js +471 -0
- package/core/symbols/index.js +77 -0
- package/core/symbols/tarot.js +211 -0
- package/core/symbols.js +22 -0
- package/docs/design/BIOINFORMATICS_BACKEND_DESIGN.md +493 -0
- package/docs/guide/06-symbolic-ai.md +370 -0
- package/docs/guide/README.md +2 -1
- package/docs/reference/05-symbolic-ai.md +570 -0
- package/docs/reference/06-bioinformatics.md +546 -0
- package/docs/reference/README.md +32 -2
- package/docs/theory/11-prgraph-memory.md +559 -0
- package/docs/theory/12-resonant-attention.md +661 -0
- package/modular.js +33 -1
- package/package.json +1 -1
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Molecular Binding - Protein-Ligand and Protein-Protein Interactions
|
|
3
|
+
*
|
|
4
|
+
* Models molecular binding using prime resonance:
|
|
5
|
+
* - Binding affinity correlates with golden ratio resonance
|
|
6
|
+
* - Docking uses multi-system Kuramoto coupling
|
|
7
|
+
* - Affinity scoring combines electrostatic, hydrophobic, and resonance terms
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
AMINO_ACID_PRIMES,
|
|
12
|
+
getChargeFromPrime,
|
|
13
|
+
getHydrophobicityFromPrime
|
|
14
|
+
} = require('./encoding');
|
|
15
|
+
|
|
16
|
+
// Import core modules with fallback
|
|
17
|
+
let calculateResonance, findGoldenPairs, resonanceSignature, primeToFrequency, KuramotoModel;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const core = require('../../core');
|
|
21
|
+
const physics = require('../../physics');
|
|
22
|
+
|
|
23
|
+
calculateResonance = core.calculateResonance;
|
|
24
|
+
findGoldenPairs = core.findGoldenPairs;
|
|
25
|
+
resonanceSignature = core.resonanceSignature;
|
|
26
|
+
primeToFrequency = core.primeToFrequency;
|
|
27
|
+
KuramotoModel = physics.KuramotoModel;
|
|
28
|
+
} catch (e) {
|
|
29
|
+
// Fallback implementations
|
|
30
|
+
const PHI = 1.618033988749895;
|
|
31
|
+
|
|
32
|
+
calculateResonance = (p1, p2) => {
|
|
33
|
+
const ratio = Math.max(p1, p2) / Math.min(p1, p2);
|
|
34
|
+
return 1 / (1 + Math.abs(ratio - PHI));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
findGoldenPairs = (primes) => {
|
|
38
|
+
const pairs = [];
|
|
39
|
+
for (let i = 0; i < primes.length; i++) {
|
|
40
|
+
for (let j = i + 1; j < primes.length; j++) {
|
|
41
|
+
if (calculateResonance(primes[i], primes[j]) > 0.8) {
|
|
42
|
+
pairs.push([primes[i], primes[j]]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return pairs;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
resonanceSignature = (primes) => {
|
|
50
|
+
let sum = 0, count = 0;
|
|
51
|
+
for (let i = 0; i < primes.length; i++) {
|
|
52
|
+
for (let j = i + 1; j < primes.length; j++) {
|
|
53
|
+
sum += calculateResonance(primes[i], primes[j]);
|
|
54
|
+
count++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { mean: sum / Math.max(count, 1), count };
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
primeToFrequency = (p, base = 1, logScale = 10) => base + Math.log(p) / logScale;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* BindingAffinityCalculator
|
|
65
|
+
*
|
|
66
|
+
* Computes molecular binding using prime resonance scoring
|
|
67
|
+
*/
|
|
68
|
+
class BindingAffinityCalculator {
|
|
69
|
+
constructor(options = {}) {
|
|
70
|
+
this.options = {
|
|
71
|
+
resonanceWeight: options.resonanceWeight || 0.4,
|
|
72
|
+
electrostaticWeight: options.electrostaticWeight || 0.3,
|
|
73
|
+
hydrophobicWeight: options.hydrophobicWeight || 0.3,
|
|
74
|
+
goldenBonus: options.goldenBonus || 0.1,
|
|
75
|
+
...options
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Compute binding affinity between two molecules
|
|
81
|
+
* @param {number[]} mol1Primes - First molecule as prime array
|
|
82
|
+
* @param {number[]} mol2Primes - Second molecule as prime array
|
|
83
|
+
* @returns {object} Binding affinity result
|
|
84
|
+
*/
|
|
85
|
+
computeAffinity(mol1Primes, mol2Primes) {
|
|
86
|
+
const interactions = [];
|
|
87
|
+
let totalScore = 0;
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < mol1Primes.length; i++) {
|
|
90
|
+
for (let j = 0; j < mol2Primes.length; j++) {
|
|
91
|
+
const p1 = mol1Primes[i];
|
|
92
|
+
const p2 = mol2Primes[j];
|
|
93
|
+
|
|
94
|
+
// Calculate interaction components
|
|
95
|
+
const resonance = calculateResonance(p1, p2);
|
|
96
|
+
const electrostatic = this.electrostaticInteraction(p1, p2);
|
|
97
|
+
const hydrophobic = this.hydrophobicInteraction(p1, p2);
|
|
98
|
+
|
|
99
|
+
const pairScore =
|
|
100
|
+
this.options.resonanceWeight * resonance +
|
|
101
|
+
this.options.electrostaticWeight * electrostatic +
|
|
102
|
+
this.options.hydrophobicWeight * hydrophobic;
|
|
103
|
+
|
|
104
|
+
if (pairScore > 0.2) {
|
|
105
|
+
interactions.push({
|
|
106
|
+
residue1: i,
|
|
107
|
+
residue2: j,
|
|
108
|
+
prime1: p1,
|
|
109
|
+
prime2: p2,
|
|
110
|
+
score: pairScore,
|
|
111
|
+
resonance,
|
|
112
|
+
electrostatic,
|
|
113
|
+
hydrophobic
|
|
114
|
+
});
|
|
115
|
+
totalScore += pairScore;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Golden ratio bonus
|
|
121
|
+
const allPrimes = [...mol1Primes, ...mol2Primes];
|
|
122
|
+
const goldenPairs = findGoldenPairs(allPrimes);
|
|
123
|
+
const goldenBonus = goldenPairs.length * this.options.goldenBonus;
|
|
124
|
+
|
|
125
|
+
// Normalize by interface size
|
|
126
|
+
const interfaceSize = mol1Primes.length * mol2Primes.length;
|
|
127
|
+
const normalizedAffinity = (totalScore / interfaceSize) + goldenBonus;
|
|
128
|
+
|
|
129
|
+
// Convert to binding energy (kcal/mol approximation)
|
|
130
|
+
const bindingEnergy = -1.98 * 300 * Math.log(Math.max(normalizedAffinity, 0.01)) / 1000;
|
|
131
|
+
|
|
132
|
+
// Resonance signature of the complex
|
|
133
|
+
const signature = resonanceSignature(allPrimes);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
affinity: normalizedAffinity,
|
|
137
|
+
bindingEnergy,
|
|
138
|
+
interactions: interactions.sort((a, b) => b.score - a.score),
|
|
139
|
+
goldenPairs: goldenPairs.length,
|
|
140
|
+
interfaceSize,
|
|
141
|
+
resonanceSignature: signature
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Electrostatic interaction between two primes
|
|
147
|
+
*/
|
|
148
|
+
electrostaticInteraction(p1, p2) {
|
|
149
|
+
const q1 = getChargeFromPrime(p1);
|
|
150
|
+
const q2 = getChargeFromPrime(p2);
|
|
151
|
+
|
|
152
|
+
if (q1 * q2 < 0) return 1.0; // Opposite charges attract
|
|
153
|
+
if (q1 * q2 > 0) return -0.5; // Same charges repel
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Hydrophobic interaction
|
|
159
|
+
*/
|
|
160
|
+
hydrophobicInteraction(p1, p2) {
|
|
161
|
+
const h1 = getHydrophobicityFromPrime(p1);
|
|
162
|
+
const h2 = getHydrophobicityFromPrime(p2);
|
|
163
|
+
|
|
164
|
+
// Like attracts like in hydrophobic effect
|
|
165
|
+
if (h1 > 0 && h2 > 0) {
|
|
166
|
+
return Math.sqrt(h1 * h2) / 5; // Both hydrophobic
|
|
167
|
+
}
|
|
168
|
+
if (h1 < 0 && h2 < 0) {
|
|
169
|
+
return Math.sqrt(-h1 * -h2) / 10; // Both hydrophilic
|
|
170
|
+
}
|
|
171
|
+
return -0.1; // Mixed - unfavorable
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Screen a library of ligands against a target
|
|
176
|
+
*/
|
|
177
|
+
screenLibrary(targetPrimes, ligandLibrary) {
|
|
178
|
+
return ligandLibrary
|
|
179
|
+
.map(ligand => ({
|
|
180
|
+
id: ligand.id,
|
|
181
|
+
name: ligand.name,
|
|
182
|
+
...this.computeAffinity(targetPrimes, ligand.primes)
|
|
183
|
+
}))
|
|
184
|
+
.sort((a, b) => b.affinity - a.affinity);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Find hotspot residues (key binding determinants)
|
|
189
|
+
*/
|
|
190
|
+
findHotspots(mol1Primes, mol2Primes, threshold = 0.5) {
|
|
191
|
+
const affinity = this.computeAffinity(mol1Primes, mol2Primes);
|
|
192
|
+
|
|
193
|
+
// Count interactions per residue
|
|
194
|
+
const residueScores = {};
|
|
195
|
+
for (const int of affinity.interactions) {
|
|
196
|
+
const key1 = `mol1_${int.residue1}`;
|
|
197
|
+
const key2 = `mol2_${int.residue2}`;
|
|
198
|
+
|
|
199
|
+
residueScores[key1] = (residueScores[key1] || 0) + int.score;
|
|
200
|
+
residueScores[key2] = (residueScores[key2] || 0) + int.score;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Filter hotspots above threshold
|
|
204
|
+
const hotspots = Object.entries(residueScores)
|
|
205
|
+
.filter(([_, score]) => score > threshold)
|
|
206
|
+
.sort((a, b) => b[1] - a[1])
|
|
207
|
+
.map(([residue, score]) => ({ residue, score }));
|
|
208
|
+
|
|
209
|
+
return hotspots;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* MolecularDocker
|
|
215
|
+
*
|
|
216
|
+
* Docks molecules using Kuramoto oscillator synchronization
|
|
217
|
+
*/
|
|
218
|
+
class MolecularDocker {
|
|
219
|
+
constructor(options = {}) {
|
|
220
|
+
this.options = {
|
|
221
|
+
coupling: options.coupling || 0.2,
|
|
222
|
+
steps: options.steps || 500,
|
|
223
|
+
dt: options.dt || 0.01,
|
|
224
|
+
...options
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
this.affinityCalculator = new BindingAffinityCalculator(options);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Dock two molecules using oscillator dynamics
|
|
232
|
+
*/
|
|
233
|
+
dock(receptorPrimes, ligandPrimes) {
|
|
234
|
+
const n1 = receptorPrimes.length;
|
|
235
|
+
const n2 = ligandPrimes.length;
|
|
236
|
+
|
|
237
|
+
// Initialize oscillator phases
|
|
238
|
+
const receptorPhases = new Array(n1).fill(0).map(() => Math.random() * 2 * Math.PI);
|
|
239
|
+
const ligandPhases = new Array(n2).fill(0).map(() => Math.random() * 2 * Math.PI);
|
|
240
|
+
|
|
241
|
+
// Frequencies from primes
|
|
242
|
+
const receptorFreqs = receptorPrimes.map(p => primeToFrequency(p, 1, 10));
|
|
243
|
+
const ligandFreqs = ligandPrimes.map(p => primeToFrequency(p, 1, 10));
|
|
244
|
+
|
|
245
|
+
// Cross-coupling matrix
|
|
246
|
+
const crossCoupling = this.computeCrossCoupling(receptorPrimes, ligandPrimes);
|
|
247
|
+
|
|
248
|
+
// Evolution
|
|
249
|
+
const trajectory = [];
|
|
250
|
+
|
|
251
|
+
for (let step = 0; step < this.options.steps; step++) {
|
|
252
|
+
// Update receptor phases
|
|
253
|
+
for (let i = 0; i < n1; i++) {
|
|
254
|
+
let dPhase = receptorFreqs[i];
|
|
255
|
+
|
|
256
|
+
// Internal coupling (within receptor)
|
|
257
|
+
for (let j = 0; j < n1; j++) {
|
|
258
|
+
if (i !== j) {
|
|
259
|
+
dPhase += this.options.coupling * 0.5 *
|
|
260
|
+
Math.sin(receptorPhases[j] - receptorPhases[i]);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Cross coupling (receptor-ligand)
|
|
265
|
+
for (let j = 0; j < n2; j++) {
|
|
266
|
+
dPhase += crossCoupling[i][j] *
|
|
267
|
+
Math.sin(ligandPhases[j] - receptorPhases[i]);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
receptorPhases[i] += dPhase * this.options.dt;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Update ligand phases
|
|
274
|
+
for (let i = 0; i < n2; i++) {
|
|
275
|
+
let dPhase = ligandFreqs[i];
|
|
276
|
+
|
|
277
|
+
// Internal coupling (within ligand)
|
|
278
|
+
for (let j = 0; j < n2; j++) {
|
|
279
|
+
if (i !== j) {
|
|
280
|
+
dPhase += this.options.coupling * 0.5 *
|
|
281
|
+
Math.sin(ligandPhases[j] - ligandPhases[i]);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Cross coupling (ligand-receptor)
|
|
286
|
+
for (let j = 0; j < n1; j++) {
|
|
287
|
+
dPhase += crossCoupling[j][i] *
|
|
288
|
+
Math.sin(receptorPhases[j] - ligandPhases[i]);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
ligandPhases[i] += dPhase * this.options.dt;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Record trajectory
|
|
295
|
+
if (step % 10 === 0) {
|
|
296
|
+
trajectory.push({
|
|
297
|
+
step,
|
|
298
|
+
interSystemCoherence: this.calculateCoherence(receptorPhases, ligandPhases),
|
|
299
|
+
receptorOrder: this.calculateOrder(receptorPhases),
|
|
300
|
+
ligandOrder: this.calculateOrder(ligandPhases)
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Extract final pose from phases
|
|
306
|
+
const pose = this.phasesToPose(receptorPhases, ligandPhases);
|
|
307
|
+
|
|
308
|
+
// Calculate final binding score
|
|
309
|
+
const bindingScore = this.calculateBindingFromPhases(
|
|
310
|
+
receptorPhases, ligandPhases, crossCoupling
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
success: true,
|
|
315
|
+
receptorPhases: [...receptorPhases],
|
|
316
|
+
ligandPhases: [...ligandPhases],
|
|
317
|
+
pose,
|
|
318
|
+
dockingScore: bindingScore,
|
|
319
|
+
finalCoherence: trajectory[trajectory.length - 1]?.interSystemCoherence || 0,
|
|
320
|
+
trajectory
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Compute cross-coupling between two molecules
|
|
326
|
+
*/
|
|
327
|
+
computeCrossCoupling(primes1, primes2) {
|
|
328
|
+
const matrix = [];
|
|
329
|
+
|
|
330
|
+
for (let i = 0; i < primes1.length; i++) {
|
|
331
|
+
const row = [];
|
|
332
|
+
for (let j = 0; j < primes2.length; j++) {
|
|
333
|
+
const resonance = calculateResonance(primes1[i], primes2[j]);
|
|
334
|
+
row.push(resonance * this.options.coupling);
|
|
335
|
+
}
|
|
336
|
+
matrix.push(row);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return matrix;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Calculate inter-system coherence
|
|
344
|
+
*/
|
|
345
|
+
calculateCoherence(phases1, phases2) {
|
|
346
|
+
let coherence = 0;
|
|
347
|
+
let count = 0;
|
|
348
|
+
|
|
349
|
+
for (const p1 of phases1) {
|
|
350
|
+
for (const p2 of phases2) {
|
|
351
|
+
coherence += Math.cos(p1 - p2);
|
|
352
|
+
count++;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return Math.abs(coherence / count);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Calculate order parameter for one system
|
|
361
|
+
*/
|
|
362
|
+
calculateOrder(phases) {
|
|
363
|
+
let sumCos = 0, sumSin = 0;
|
|
364
|
+
for (const p of phases) {
|
|
365
|
+
sumCos += Math.cos(p);
|
|
366
|
+
sumSin += Math.sin(p);
|
|
367
|
+
}
|
|
368
|
+
return Math.sqrt(sumCos * sumCos + sumSin * sumSin) / phases.length;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Convert phases to binding pose
|
|
373
|
+
*/
|
|
374
|
+
phasesToPose(receptorPhases, ligandPhases) {
|
|
375
|
+
// Phase differences indicate relative orientations
|
|
376
|
+
const meanReceptorPhase = receptorPhases.reduce((a, b) => a + b, 0) / receptorPhases.length;
|
|
377
|
+
const meanLigandPhase = ligandPhases.reduce((a, b) => a + b, 0) / ligandPhases.length;
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
rotation: (meanLigandPhase - meanReceptorPhase) * 180 / Math.PI,
|
|
381
|
+
bindingMode: Math.abs(meanLigandPhase - meanReceptorPhase) < 0.5 ? 'aligned' : 'rotated'
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Calculate binding from final phases
|
|
387
|
+
*/
|
|
388
|
+
calculateBindingFromPhases(phases1, phases2, coupling) {
|
|
389
|
+
let score = 0;
|
|
390
|
+
|
|
391
|
+
for (let i = 0; i < phases1.length; i++) {
|
|
392
|
+
for (let j = 0; j < phases2.length; j++) {
|
|
393
|
+
const phaseDiff = Math.abs(phases1[i] - phases2[j]);
|
|
394
|
+
const inPhase = Math.cos(phaseDiff);
|
|
395
|
+
score += coupling[i][j] * (1 + inPhase) / 2;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return score / (phases1.length * phases2.length);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* ProteinProteinDocker
|
|
405
|
+
* Specialized docker for protein-protein interactions
|
|
406
|
+
*/
|
|
407
|
+
class ProteinProteinDocker extends MolecularDocker {
|
|
408
|
+
constructor(options = {}) {
|
|
409
|
+
super({
|
|
410
|
+
...options,
|
|
411
|
+
coupling: options.coupling || 0.15,
|
|
412
|
+
steps: options.steps || 1000
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Dock with interface prediction
|
|
418
|
+
*/
|
|
419
|
+
dockWithInterface(protein1Primes, protein2Primes) {
|
|
420
|
+
// First, predict interface residues
|
|
421
|
+
const interface1 = this.predictInterface(protein1Primes);
|
|
422
|
+
const interface2 = this.predictInterface(protein2Primes);
|
|
423
|
+
|
|
424
|
+
// Dock using interface residues
|
|
425
|
+
const dockResult = this.dock(
|
|
426
|
+
interface1.map(i => protein1Primes[i]),
|
|
427
|
+
interface2.map(i => protein2Primes[i])
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
...dockResult,
|
|
432
|
+
interface1,
|
|
433
|
+
interface2,
|
|
434
|
+
contactMap: this.buildContactMap(
|
|
435
|
+
dockResult.receptorPhases,
|
|
436
|
+
dockResult.ligandPhases,
|
|
437
|
+
interface1,
|
|
438
|
+
interface2
|
|
439
|
+
)
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Predict interface residues (surface exposed, interactive)
|
|
445
|
+
*/
|
|
446
|
+
predictInterface(primes) {
|
|
447
|
+
// Interface residues tend to be:
|
|
448
|
+
// 1. Charged (for electrostatic interactions)
|
|
449
|
+
// 2. Aromatic (for stacking interactions)
|
|
450
|
+
// 3. At certain sequence positions
|
|
451
|
+
|
|
452
|
+
const candidates = [];
|
|
453
|
+
|
|
454
|
+
for (let i = 0; i < primes.length; i++) {
|
|
455
|
+
const p = primes[i];
|
|
456
|
+
const charge = getChargeFromPrime(p);
|
|
457
|
+
const isAromatic = [47, 53, 59, 97].includes(p); // F, W, Y, H
|
|
458
|
+
|
|
459
|
+
let interfaceScore = 0;
|
|
460
|
+
if (Math.abs(charge) > 0) interfaceScore += 0.5;
|
|
461
|
+
if (isAromatic) interfaceScore += 0.3;
|
|
462
|
+
|
|
463
|
+
// Check neighbors for hydrophobic patches
|
|
464
|
+
const neighbors = primes.slice(Math.max(0, i - 2), i + 3);
|
|
465
|
+
const hydrophobicPatch = neighbors.filter(p => p >= 23 && p <= 43).length >= 3;
|
|
466
|
+
if (hydrophobicPatch) interfaceScore += 0.2;
|
|
467
|
+
|
|
468
|
+
if (interfaceScore > 0.3) {
|
|
469
|
+
candidates.push(i);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return candidates;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Build contact map from docking
|
|
478
|
+
*/
|
|
479
|
+
buildContactMap(phases1, phases2, indices1, indices2) {
|
|
480
|
+
const contacts = [];
|
|
481
|
+
|
|
482
|
+
for (let i = 0; i < phases1.length; i++) {
|
|
483
|
+
for (let j = 0; j < phases2.length; j++) {
|
|
484
|
+
const phaseDiff = Math.abs(phases1[i] - phases2[j]);
|
|
485
|
+
if (phaseDiff < 0.5 || phaseDiff > 2 * Math.PI - 0.5) {
|
|
486
|
+
contacts.push({
|
|
487
|
+
residue1: indices1[i],
|
|
488
|
+
residue2: indices2[j],
|
|
489
|
+
strength: 1 - Math.min(phaseDiff, 2 * Math.PI - phaseDiff) / 0.5
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return contacts;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
module.exports = {
|
|
500
|
+
BindingAffinityCalculator,
|
|
501
|
+
MolecularDocker,
|
|
502
|
+
ProteinProteinDocker
|
|
503
|
+
};
|