@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,664 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DNA Computing Module
|
|
3
|
+
*
|
|
4
|
+
* Implements DNA-based computation using strand displacement reactions
|
|
5
|
+
* and molecular logic gates. Based on the prime-resonance paradigm where:
|
|
6
|
+
*
|
|
7
|
+
* - DNA strands are prime sequences
|
|
8
|
+
* - Hybridization = prime product (complementary bases multiply)
|
|
9
|
+
* - Strand displacement = prime substitution transform
|
|
10
|
+
* - Logic gates = prime-based conditional operations
|
|
11
|
+
*
|
|
12
|
+
* References:
|
|
13
|
+
* - Seelig et al. (2006) - DNA logic circuits
|
|
14
|
+
* - Zhang & Winfree (2009) - Strand displacement cascades
|
|
15
|
+
* - Qian & Winfree (2011) - Seesaw gates
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
NUCLEOTIDE_PRIMES,
|
|
20
|
+
PRIME_COMPLEMENTS,
|
|
21
|
+
encodeDNA,
|
|
22
|
+
decodeDNA
|
|
23
|
+
} = require('./encoding');
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// DNA Strand Representation
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* DNAStrand - represents a single-stranded DNA molecule
|
|
31
|
+
*/
|
|
32
|
+
class DNAStrand {
|
|
33
|
+
constructor(sequence, options = {}) {
|
|
34
|
+
this.sequence = sequence.toUpperCase();
|
|
35
|
+
this.primes = encodeDNA(this.sequence);
|
|
36
|
+
this.name = options.name || '';
|
|
37
|
+
this.concentration = options.concentration || 1.0;
|
|
38
|
+
this.toehold = options.toehold || null; // Toehold region for strand displacement
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get the Watson-Crick complement
|
|
43
|
+
*/
|
|
44
|
+
complement() {
|
|
45
|
+
const compSeq = this.sequence.split('').map(n => {
|
|
46
|
+
const comps = { 'A': 'T', 'T': 'A', 'G': 'C', 'C': 'G' };
|
|
47
|
+
return comps[n] || n;
|
|
48
|
+
}).join('');
|
|
49
|
+
|
|
50
|
+
return new DNAStrand(compSeq, { name: `${this.name}'` });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get the reverse complement (for double helix formation)
|
|
55
|
+
*/
|
|
56
|
+
reverseComplement() {
|
|
57
|
+
return new DNAStrand(
|
|
58
|
+
this.complement().sequence.split('').reverse().join(''),
|
|
59
|
+
{ name: `${this.name}*` }
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Calculate binding affinity (free energy) with another strand
|
|
65
|
+
* Uses prime resonance for thermodynamic estimation
|
|
66
|
+
*/
|
|
67
|
+
bindingAffinity(other) {
|
|
68
|
+
const minLen = Math.min(this.primes.length, other.primes.length);
|
|
69
|
+
let affinity = 0;
|
|
70
|
+
let matches = 0;
|
|
71
|
+
|
|
72
|
+
for (let i = 0; i < minLen; i++) {
|
|
73
|
+
const p1 = this.primes[i];
|
|
74
|
+
const p2 = other.primes[i];
|
|
75
|
+
|
|
76
|
+
// Perfect complement: product is fixed (14 for A-T, 33 for G-C)
|
|
77
|
+
if (PRIME_COMPLEMENTS[p1] === p2) {
|
|
78
|
+
affinity += 1.0;
|
|
79
|
+
matches++;
|
|
80
|
+
} else if (p1 === p2) {
|
|
81
|
+
// Same base: mismatch penalty
|
|
82
|
+
affinity -= 0.5;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
affinity,
|
|
88
|
+
matchFraction: matches / minLen,
|
|
89
|
+
meltingTemp: this.estimateMeltingTemp(matches, minLen)
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Estimate melting temperature using nearest-neighbor model
|
|
95
|
+
*/
|
|
96
|
+
estimateMeltingTemp(matches, length) {
|
|
97
|
+
// Simplified Tm estimation
|
|
98
|
+
// Real calculation would use ΔH and ΔS from nearest-neighbor parameters
|
|
99
|
+
const gcContent = (this.sequence.match(/[GC]/g) || []).length / length;
|
|
100
|
+
const Tm = 64.9 + 41 * (gcContent - 0.5) + 16.6 * Math.log10(0.05);
|
|
101
|
+
return Tm * (matches / length); // Adjust for mismatches
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if this strand can displace another from a duplex
|
|
106
|
+
*/
|
|
107
|
+
canDisplace(incumbent, template) {
|
|
108
|
+
// Strand displacement requires:
|
|
109
|
+
// 1. A toehold region on the template
|
|
110
|
+
// 2. Higher affinity than incumbent
|
|
111
|
+
|
|
112
|
+
const myAffinity = this.bindingAffinity(template);
|
|
113
|
+
const theirAffinity = incumbent.bindingAffinity(template);
|
|
114
|
+
|
|
115
|
+
return myAffinity.affinity > theirAffinity.affinity;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Prime product representation (for hybridized duplex)
|
|
120
|
+
*/
|
|
121
|
+
primeProduct(other) {
|
|
122
|
+
const minLen = Math.min(this.primes.length, other.primes.length);
|
|
123
|
+
return this.primes.slice(0, minLen).map((p, i) => p * other.primes[i]);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get length
|
|
128
|
+
*/
|
|
129
|
+
get length() {
|
|
130
|
+
return this.sequence.length;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
toString() {
|
|
134
|
+
return `5'-${this.sequence}-3'`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* DNADuplex - represents a double-stranded DNA molecule
|
|
140
|
+
*/
|
|
141
|
+
class DNADuplex {
|
|
142
|
+
constructor(strand1, strand2, options = {}) {
|
|
143
|
+
this.strand1 = strand1; // Top strand (5' → 3')
|
|
144
|
+
this.strand2 = strand2; // Bottom strand (3' → 5')
|
|
145
|
+
this.name = options.name || '';
|
|
146
|
+
this.toeholdRegion = options.toehold || null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Check if duplex is stable (complementary)
|
|
151
|
+
*/
|
|
152
|
+
isStable() {
|
|
153
|
+
const affinity = this.strand1.bindingAffinity(this.strand2.reverseComplement());
|
|
154
|
+
return affinity.matchFraction > 0.8;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Prime signature of the duplex
|
|
159
|
+
*/
|
|
160
|
+
get primeSignature() {
|
|
161
|
+
return this.strand1.primeProduct(this.strand2.reverseComplement());
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ============================================================================
|
|
166
|
+
// DNA Logic Gates
|
|
167
|
+
// ============================================================================
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* AND Gate
|
|
171
|
+
* Output is high only when both inputs are present
|
|
172
|
+
*
|
|
173
|
+
* Implementation: Two-input strand displacement
|
|
174
|
+
* Input1 + Input2 + Gate → Output + Waste
|
|
175
|
+
*/
|
|
176
|
+
class ANDGate {
|
|
177
|
+
constructor(options = {}) {
|
|
178
|
+
this.name = options.name || 'AND';
|
|
179
|
+
this.threshold = options.threshold || 0.5;
|
|
180
|
+
|
|
181
|
+
// Gate strands (templates)
|
|
182
|
+
this.input1Binding = options.input1Binding || 'ATCGATCG';
|
|
183
|
+
this.input2Binding = options.input2Binding || 'GCTAGCTA';
|
|
184
|
+
this.outputSequence = options.output || 'TTCCAAGG';
|
|
185
|
+
|
|
186
|
+
// Convert to prime representation
|
|
187
|
+
this.input1Primes = encodeDNA(this.input1Binding);
|
|
188
|
+
this.input2Primes = encodeDNA(this.input2Binding);
|
|
189
|
+
this.outputPrimes = encodeDNA(this.outputSequence);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Evaluate gate with given input concentrations
|
|
194
|
+
*/
|
|
195
|
+
evaluate(input1Conc, input2Conc) {
|
|
196
|
+
// Both inputs must exceed threshold
|
|
197
|
+
const active = input1Conc >= this.threshold && input2Conc >= this.threshold;
|
|
198
|
+
const outputConc = active ? Math.min(input1Conc, input2Conc) : 0;
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
output: active,
|
|
202
|
+
concentration: outputConc,
|
|
203
|
+
outputPrimes: active ? this.outputPrimes : []
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Prime-based evaluation
|
|
209
|
+
*/
|
|
210
|
+
evaluatePrimes(input1Primes, input2Primes) {
|
|
211
|
+
// Check if inputs match expected patterns
|
|
212
|
+
const match1 = this.primesMatch(input1Primes, this.input1Primes);
|
|
213
|
+
const match2 = this.primesMatch(input2Primes, this.input2Primes);
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
output: match1 && match2,
|
|
217
|
+
outputPrimes: (match1 && match2) ? this.outputPrimes : [],
|
|
218
|
+
matchScores: { input1: match1, input2: match2 }
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
primesMatch(primes1, primes2, threshold = 0.8) {
|
|
223
|
+
if (primes1.length !== primes2.length) return false;
|
|
224
|
+
const matches = primes1.filter((p, i) => p === primes2[i]).length;
|
|
225
|
+
return matches / primes1.length >= threshold;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* OR Gate
|
|
231
|
+
* Output is high when either input is present
|
|
232
|
+
*/
|
|
233
|
+
class ORGate {
|
|
234
|
+
constructor(options = {}) {
|
|
235
|
+
this.name = options.name || 'OR';
|
|
236
|
+
this.threshold = options.threshold || 0.5;
|
|
237
|
+
|
|
238
|
+
this.input1Binding = options.input1Binding || 'ATCGATCG';
|
|
239
|
+
this.input2Binding = options.input2Binding || 'GCTAGCTA';
|
|
240
|
+
this.outputSequence = options.output || 'TTCCAAGG';
|
|
241
|
+
|
|
242
|
+
this.input1Primes = encodeDNA(this.input1Binding);
|
|
243
|
+
this.input2Primes = encodeDNA(this.input2Binding);
|
|
244
|
+
this.outputPrimes = encodeDNA(this.outputSequence);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
evaluate(input1Conc, input2Conc) {
|
|
248
|
+
const active = input1Conc >= this.threshold || input2Conc >= this.threshold;
|
|
249
|
+
const outputConc = active ? Math.max(input1Conc, input2Conc) : 0;
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
output: active,
|
|
253
|
+
concentration: outputConc,
|
|
254
|
+
outputPrimes: active ? this.outputPrimes : []
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
evaluatePrimes(input1Primes, input2Primes) {
|
|
259
|
+
const match1 = this.primesMatch(input1Primes, this.input1Primes);
|
|
260
|
+
const match2 = this.primesMatch(input2Primes, this.input2Primes);
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
output: match1 || match2,
|
|
264
|
+
outputPrimes: (match1 || match2) ? this.outputPrimes : [],
|
|
265
|
+
matchScores: { input1: match1, input2: match2 }
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
primesMatch(primes1, primes2, threshold = 0.8) {
|
|
270
|
+
if (primes1.length !== primes2.length) return false;
|
|
271
|
+
const matches = primes1.filter((p, i) => p === primes2[i]).length;
|
|
272
|
+
return matches / primes1.length >= threshold;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* NOT Gate (Inverter)
|
|
278
|
+
* Output is high when input is absent
|
|
279
|
+
*/
|
|
280
|
+
class NOTGate {
|
|
281
|
+
constructor(options = {}) {
|
|
282
|
+
this.name = options.name || 'NOT';
|
|
283
|
+
this.threshold = options.threshold || 0.5;
|
|
284
|
+
this.decayRate = options.decayRate || 0.1;
|
|
285
|
+
|
|
286
|
+
this.inputBinding = options.inputBinding || 'ATCGATCG';
|
|
287
|
+
this.outputSequence = options.output || 'TTCCAAGG';
|
|
288
|
+
|
|
289
|
+
this.inputPrimes = encodeDNA(this.inputBinding);
|
|
290
|
+
this.outputPrimes = encodeDNA(this.outputSequence);
|
|
291
|
+
|
|
292
|
+
// NOT gate has a constitutive output that gets suppressed by input
|
|
293
|
+
this.constitutiveOutput = 1.0;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
evaluate(inputConc) {
|
|
297
|
+
const active = inputConc < this.threshold;
|
|
298
|
+
const outputConc = active ? this.constitutiveOutput * (1 - inputConc) : 0;
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
output: active,
|
|
302
|
+
concentration: outputConc,
|
|
303
|
+
outputPrimes: active ? this.outputPrimes : []
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
evaluatePrimes(inputPrimes) {
|
|
308
|
+
const match = this.primesMatch(inputPrimes, this.inputPrimes);
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
output: !match,
|
|
312
|
+
outputPrimes: !match ? this.outputPrimes : [],
|
|
313
|
+
matchScore: match
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
primesMatch(primes1, primes2, threshold = 0.8) {
|
|
318
|
+
if (!primes1 || primes1.length === 0) return false;
|
|
319
|
+
if (primes1.length !== primes2.length) return false;
|
|
320
|
+
const matches = primes1.filter((p, i) => p === primes2[i]).length;
|
|
321
|
+
return matches / primes1.length >= threshold;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* NAND Gate (Universal gate)
|
|
327
|
+
*/
|
|
328
|
+
class NANDGate {
|
|
329
|
+
constructor(options = {}) {
|
|
330
|
+
this.andGate = new ANDGate(options);
|
|
331
|
+
this.notGate = new NOTGate({ ...options, inputBinding: options.output });
|
|
332
|
+
this.name = options.name || 'NAND';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
evaluate(input1Conc, input2Conc) {
|
|
336
|
+
const andResult = this.andGate.evaluate(input1Conc, input2Conc);
|
|
337
|
+
return this.notGate.evaluate(andResult.concentration);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ============================================================================
|
|
342
|
+
// Strand Displacement Reactions
|
|
343
|
+
// ============================================================================
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* StrandDisplacementReaction
|
|
347
|
+
* Models toehold-mediated strand displacement
|
|
348
|
+
*/
|
|
349
|
+
class StrandDisplacementReaction {
|
|
350
|
+
constructor(options = {}) {
|
|
351
|
+
this.template = options.template; // The template strand
|
|
352
|
+
this.incumbent = options.incumbent; // Currently bound strand
|
|
353
|
+
this.invader = options.invader; // Displacing strand
|
|
354
|
+
this.toehold = options.toehold || 6; // Toehold length in nucleotides
|
|
355
|
+
this.rate = options.rate || 1e6; // Rate constant (M^-1 s^-1)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Calculate displacement rate based on toehold binding
|
|
360
|
+
*/
|
|
361
|
+
calculateRate() {
|
|
362
|
+
// Rate depends on toehold binding strength
|
|
363
|
+
const toeholdPrimes = this.template.primes.slice(0, this.toehold);
|
|
364
|
+
const invaderToeholPrimes = this.invader.primes.slice(-this.toehold);
|
|
365
|
+
|
|
366
|
+
let bindingStrength = 0;
|
|
367
|
+
for (let i = 0; i < this.toehold; i++) {
|
|
368
|
+
if (PRIME_COMPLEMENTS[toeholdPrimes[i]] === invaderToeholPrimes[i]) {
|
|
369
|
+
bindingStrength += 1;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Rate scales exponentially with toehold binding
|
|
374
|
+
return this.rate * Math.exp(bindingStrength - this.toehold);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Simulate displacement reaction
|
|
379
|
+
*/
|
|
380
|
+
simulate(time, dt = 0.001) {
|
|
381
|
+
const rate = this.calculateRate();
|
|
382
|
+
const steps = Math.floor(time / dt);
|
|
383
|
+
|
|
384
|
+
let invaderConc = this.invader.concentration;
|
|
385
|
+
let incumbentConc = this.incumbent.concentration;
|
|
386
|
+
let templateConc = this.template.concentration;
|
|
387
|
+
|
|
388
|
+
const history = [];
|
|
389
|
+
|
|
390
|
+
for (let i = 0; i < steps; i++) {
|
|
391
|
+
// Displacement kinetics (simplified second-order)
|
|
392
|
+
const dDisplacement = rate * invaderConc * templateConc * dt;
|
|
393
|
+
|
|
394
|
+
invaderConc -= dDisplacement;
|
|
395
|
+
incumbentConc += dDisplacement;
|
|
396
|
+
|
|
397
|
+
history.push({
|
|
398
|
+
time: i * dt,
|
|
399
|
+
invader: invaderConc,
|
|
400
|
+
incumbent: incumbentConc,
|
|
401
|
+
displaced: this.incumbent.concentration - incumbentConc
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
finalInvaderConc: invaderConc,
|
|
407
|
+
finalIncumbentConc: incumbentConc,
|
|
408
|
+
displacementFraction: 1 - invaderConc / this.invader.concentration,
|
|
409
|
+
history
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ============================================================================
|
|
415
|
+
// DNA Circuit
|
|
416
|
+
// ============================================================================
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* DNACircuit - A network of connected DNA logic gates
|
|
420
|
+
*/
|
|
421
|
+
class DNACircuit {
|
|
422
|
+
constructor(name = 'circuit') {
|
|
423
|
+
this.name = name;
|
|
424
|
+
this.gates = new Map();
|
|
425
|
+
this.connections = [];
|
|
426
|
+
this.inputs = new Map();
|
|
427
|
+
this.outputs = new Map();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Add a gate to the circuit
|
|
432
|
+
*/
|
|
433
|
+
addGate(id, gate) {
|
|
434
|
+
this.gates.set(id, gate);
|
|
435
|
+
return this;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Connect gate output to another gate input
|
|
440
|
+
*/
|
|
441
|
+
connect(fromGateId, toGateId, inputSlot = 1) {
|
|
442
|
+
this.connections.push({
|
|
443
|
+
from: fromGateId,
|
|
444
|
+
to: toGateId,
|
|
445
|
+
slot: inputSlot
|
|
446
|
+
});
|
|
447
|
+
return this;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Set circuit input
|
|
452
|
+
*/
|
|
453
|
+
setInput(inputId, value) {
|
|
454
|
+
this.inputs.set(inputId, value);
|
|
455
|
+
return this;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Evaluate the circuit
|
|
460
|
+
*/
|
|
461
|
+
evaluate() {
|
|
462
|
+
const gateOutputs = new Map();
|
|
463
|
+
|
|
464
|
+
// Topological sort to determine evaluation order
|
|
465
|
+
const order = this.topologicalSort();
|
|
466
|
+
|
|
467
|
+
// Evaluate gates in order
|
|
468
|
+
for (const gateId of order) {
|
|
469
|
+
const gate = this.gates.get(gateId);
|
|
470
|
+
|
|
471
|
+
// Get inputs for this gate
|
|
472
|
+
const inputs = this.getGateInputs(gateId, gateOutputs);
|
|
473
|
+
|
|
474
|
+
// Evaluate gate
|
|
475
|
+
let result;
|
|
476
|
+
if (gate instanceof ANDGate || gate instanceof ORGate || gate instanceof NANDGate) {
|
|
477
|
+
result = gate.evaluate(inputs[0] || 0, inputs[1] || 0);
|
|
478
|
+
} else if (gate instanceof NOTGate) {
|
|
479
|
+
result = gate.evaluate(inputs[0] || 0);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
gateOutputs.set(gateId, result);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
outputs: Object.fromEntries(gateOutputs),
|
|
487
|
+
finalOutput: gateOutputs.get([...this.gates.keys()].pop())
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Get inputs for a gate from circuit inputs or upstream gate outputs
|
|
493
|
+
*/
|
|
494
|
+
getGateInputs(gateId, gateOutputs) {
|
|
495
|
+
const inputs = [
|
|
496
|
+
this.inputs.get(`${gateId}_in1`),
|
|
497
|
+
this.inputs.get(`${gateId}_in2`)
|
|
498
|
+
];
|
|
499
|
+
|
|
500
|
+
// Check for connections from other gates
|
|
501
|
+
for (const conn of this.connections) {
|
|
502
|
+
if (conn.to === gateId) {
|
|
503
|
+
const upstreamOutput = gateOutputs.get(conn.from);
|
|
504
|
+
if (upstreamOutput) {
|
|
505
|
+
inputs[conn.slot - 1] = upstreamOutput.concentration;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return inputs;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Topological sort for gate evaluation order
|
|
515
|
+
*/
|
|
516
|
+
topologicalSort() {
|
|
517
|
+
const visited = new Set();
|
|
518
|
+
const order = [];
|
|
519
|
+
const gateIds = [...this.gates.keys()];
|
|
520
|
+
|
|
521
|
+
const visit = (id) => {
|
|
522
|
+
if (visited.has(id)) return;
|
|
523
|
+
visited.add(id);
|
|
524
|
+
|
|
525
|
+
// Visit dependencies first
|
|
526
|
+
for (const conn of this.connections) {
|
|
527
|
+
if (conn.to === id) {
|
|
528
|
+
visit(conn.from);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
order.push(id);
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
for (const id of gateIds) {
|
|
536
|
+
visit(id);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return order;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Convert circuit to prime sequence representation
|
|
544
|
+
*/
|
|
545
|
+
toPrimeCircuit() {
|
|
546
|
+
return {
|
|
547
|
+
gates: Object.fromEntries(
|
|
548
|
+
[...this.gates.entries()].map(([id, gate]) => [
|
|
549
|
+
id,
|
|
550
|
+
{
|
|
551
|
+
type: gate.constructor.name,
|
|
552
|
+
inputPrimes: gate.input1Primes || gate.inputPrimes,
|
|
553
|
+
outputPrimes: gate.outputPrimes
|
|
554
|
+
}
|
|
555
|
+
])
|
|
556
|
+
),
|
|
557
|
+
connections: this.connections
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Create common circuits
|
|
564
|
+
*/
|
|
565
|
+
function createHalfAdder() {
|
|
566
|
+
const circuit = new DNACircuit('half-adder');
|
|
567
|
+
|
|
568
|
+
// Sum = A XOR B = (A AND NOT B) OR (NOT A AND B)
|
|
569
|
+
// Carry = A AND B
|
|
570
|
+
|
|
571
|
+
circuit.addGate('and1', new ANDGate({ name: 'carry' }));
|
|
572
|
+
circuit.addGate('not_a', new NOTGate({ name: 'not_a' }));
|
|
573
|
+
circuit.addGate('not_b', new NOTGate({ name: 'not_b' }));
|
|
574
|
+
circuit.addGate('and2', new ANDGate({ name: 'a_and_notb' }));
|
|
575
|
+
circuit.addGate('and3', new ANDGate({ name: 'nota_and_b' }));
|
|
576
|
+
circuit.addGate('or1', new ORGate({ name: 'sum' }));
|
|
577
|
+
|
|
578
|
+
// XOR construction
|
|
579
|
+
circuit.connect('not_a', 'and3', 1);
|
|
580
|
+
circuit.connect('not_b', 'and2', 2);
|
|
581
|
+
circuit.connect('and2', 'or1', 1);
|
|
582
|
+
circuit.connect('and3', 'or1', 2);
|
|
583
|
+
|
|
584
|
+
return circuit;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function createFullAdder() {
|
|
588
|
+
const halfAdder1 = createHalfAdder();
|
|
589
|
+
const halfAdder2 = createHalfAdder();
|
|
590
|
+
|
|
591
|
+
const circuit = new DNACircuit('full-adder');
|
|
592
|
+
|
|
593
|
+
// Full adder uses two half adders
|
|
594
|
+
// S = A XOR B XOR Cin
|
|
595
|
+
// Cout = (A AND B) OR (Cin AND (A XOR B))
|
|
596
|
+
|
|
597
|
+
// This is a simplified representation
|
|
598
|
+
circuit.addGate('xor1', new ANDGate({ name: 'xor1' })); // Simplified
|
|
599
|
+
circuit.addGate('xor2', new ANDGate({ name: 'xor2' }));
|
|
600
|
+
circuit.addGate('and1', new ANDGate({ name: 'and1' }));
|
|
601
|
+
circuit.addGate('and2', new ANDGate({ name: 'and2' }));
|
|
602
|
+
circuit.addGate('or1', new ORGate({ name: 'cout' }));
|
|
603
|
+
|
|
604
|
+
return circuit;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// ============================================================================
|
|
608
|
+
// Seesaw Gate (Qian & Winfree)
|
|
609
|
+
// ============================================================================
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* SeesawGate - DNA strand displacement seesaw gate
|
|
613
|
+
* Universal gate for digital circuit computation
|
|
614
|
+
*/
|
|
615
|
+
class SeesawGate {
|
|
616
|
+
constructor(options = {}) {
|
|
617
|
+
this.name = options.name || 'seesaw';
|
|
618
|
+
this.threshold = options.threshold || 0.6;
|
|
619
|
+
this.gain = options.gain || 1.0;
|
|
620
|
+
|
|
621
|
+
// Gate state
|
|
622
|
+
this.inputWeights = options.weights || [1.0];
|
|
623
|
+
this.gateStrand = options.gateStrand || 'ATCGATCGATCG';
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Evaluate seesaw gate
|
|
628
|
+
* Output = gain * (sum(weights * inputs) - threshold)
|
|
629
|
+
*/
|
|
630
|
+
evaluate(inputs) {
|
|
631
|
+
const weightedSum = inputs.reduce((sum, inp, i) => {
|
|
632
|
+
return sum + (this.inputWeights[i] || 1.0) * inp;
|
|
633
|
+
}, 0);
|
|
634
|
+
|
|
635
|
+
const output = Math.max(0, this.gain * (weightedSum - this.threshold));
|
|
636
|
+
|
|
637
|
+
return {
|
|
638
|
+
output: Math.min(1, output),
|
|
639
|
+
active: weightedSum > this.threshold,
|
|
640
|
+
weightedSum
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
module.exports = {
|
|
646
|
+
// Strand classes
|
|
647
|
+
DNAStrand,
|
|
648
|
+
DNADuplex,
|
|
649
|
+
|
|
650
|
+
// Logic gates
|
|
651
|
+
ANDGate,
|
|
652
|
+
ORGate,
|
|
653
|
+
NOTGate,
|
|
654
|
+
NANDGate,
|
|
655
|
+
SeesawGate,
|
|
656
|
+
|
|
657
|
+
// Reactions
|
|
658
|
+
StrandDisplacementReaction,
|
|
659
|
+
|
|
660
|
+
// Circuits
|
|
661
|
+
DNACircuit,
|
|
662
|
+
createHalfAdder,
|
|
663
|
+
createFullAdder,
|
|
664
|
+
};
|