Skip to content

Commit e802626

Browse files
authored
Refactor/extract scripting api 2 (#840)
## Summary This is part of the process of extracting the script-facing API from PoseEditMode in order to make it usable for non-booster scripts. Replaces #749 ## Implementation Notes I did it like the previous PR, except I didn't implement get_folder/select_folder, since they are behind a "lock", and I didn't want to think yet of whether or not I want the extracted logic to inherit the lock. However, while get_folder is locked, the calculation functions (such as `fold`) which also access the folder, aren't locked (even on `dev`). Is this intended? ## Testing I ran https://eternagame.org/scripts/13774466, and it passed. The script checks that all of the extracted functions (expect folding with binding sites) work and return the same values. This is an unrelated issue, but : For some reason, both on main/master (https://eternagame.org/puzzles/6096060/play) and on my branch, the first time I run it I get the following errors, and on future runs it passes. Reloading the page makes the issue appear again for a single execution. Errors: ![image](https://github.com/user-attachments/assets/1fca8f90-85f4-485c-9ff4-a44fcdb2445a) Successful run: ![image](https://github.com/user-attachments/assets/ecfe8020-2724-44f3-819f-0a6c13182786) (In my branch I just copy pasted the script, since I didn't have it on eternadev.org, and changed `Lib.fold`->`applet.fold`. I shouldn't have Lib in the terminal, right?) ## Related Issues Enables eternagame/eternagame.org#378, #750
1 parent 6f99c5e commit e802626

File tree

2 files changed

+348
-317
lines changed

2 files changed

+348
-317
lines changed
Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
import EPars from 'eterna/EPars';
2+
import Folder, {SuboptEnsembleResult} from 'eterna/folding/Folder';
3+
import DotPlot from 'eterna/rnatypes/DotPlot';
4+
import SecStruct from 'eterna/rnatypes/SecStruct';
5+
import Sequence from 'eterna/rnatypes/Sequence';
6+
import {ExternalInterfaceCtx} from 'eterna/util/ExternalInterface';
7+
import {Assert} from 'flashbang';
8+
9+
/**
10+
* An EternaScript API exposing all functions that handle folding an arbitrary
11+
* RNA sequence, and aren't dependent on what's the RNA in the puzzle.
12+
*
13+
* It adds itself to an existing script API (`ExternalInterfaceCtx`) from
14+
* `this.registerToScriptInterface`.
15+
*
16+
* Note: The API in this class is still affected by the selected folder
17+
* and the pseudoknot mode of the puzzle - just not the sequence.
18+
*/
19+
export default class FoldingAPI {
20+
private readonly _getFolder: () => Folder | null;
21+
private readonly _getIsPseudoknot: () => boolean;
22+
23+
private get _folder(): Folder | null {
24+
return this._getFolder();
25+
}
26+
27+
private get _isPseudoknot(): boolean {
28+
return this._getIsPseudoknot();
29+
}
30+
31+
constructor(params: { getFolder: () => Folder, getIsPseudoknot: () => boolean }) {
32+
this._getIsPseudoknot = params.getIsPseudoknot;
33+
this._getFolder = params.getFolder;
34+
}
35+
36+
public registerToScriptInterface(scriptInterfaceCtx: ExternalInterfaceCtx) {
37+
scriptInterfaceCtx.addCallback(
38+
'fold',
39+
(seq: string, constraint: string | null = null): string | null => {
40+
if (this._folder === null) {
41+
return null;
42+
}
43+
if (!this._folder.isSync()) {
44+
throw new Error('Attempted to use asynchronous folding engine synchronously');
45+
}
46+
const seqArr: Sequence = Sequence.fromSequenceString(seq);
47+
const pseudoknots = this._isPseudoknot;
48+
const folded: SecStruct | null = this._folder.foldSequence(seqArr, null, constraint, pseudoknots);
49+
Assert.assertIsDefined(folded);
50+
return folded.getParenthesis({pseudoknots});
51+
}
52+
);
53+
54+
scriptInterfaceCtx.addCallback(
55+
'fold_async',
56+
async (seq: string, constraint: string | null = null): Promise<string | null> => {
57+
if (this._folder === null) {
58+
return null;
59+
}
60+
const seqArr: Sequence = Sequence.fromSequenceString(seq);
61+
const pseudoknots = this._isPseudoknot;
62+
const folded: SecStruct | null = await this._folder.foldSequence(seqArr, null, constraint, pseudoknots);
63+
Assert.assertIsDefined(folded);
64+
return folded.getParenthesis({pseudoknots});
65+
}
66+
);
67+
68+
scriptInterfaceCtx.addCallback(
69+
'fold_with_binding_site',
70+
(seq: string, site: number[], bonus: number): string | null => {
71+
if (this._folder === null) {
72+
return null;
73+
}
74+
const seqArr: Sequence = Sequence.fromSequenceString(seq);
75+
const pseudoknots = this._isPseudoknot;
76+
const folded: SecStruct | null = this._folder.foldSequenceWithBindingSite(
77+
seqArr, null, site, Math.floor(bonus * 100), 2.5
78+
);
79+
if (folded === null) {
80+
return null;
81+
}
82+
return folded.getParenthesis({pseudoknots});
83+
}
84+
);
85+
86+
scriptInterfaceCtx.addCallback(
87+
'fold_with_binding_site_async',
88+
async (seq: string, site: number[], bonus: number): Promise<string | null> => {
89+
if (this._folder === null) {
90+
return null;
91+
}
92+
const seqArr: Sequence = Sequence.fromSequenceString(seq);
93+
const pseudoknots = this._isPseudoknot;
94+
const folded: SecStruct | null = this._folder.foldSequenceWithBindingSite(
95+
seqArr, null, site, Math.floor(bonus * 100), 2.5
96+
);
97+
if (folded === null) {
98+
return null;
99+
}
100+
return folded.getParenthesis({pseudoknots});
101+
}
102+
);
103+
104+
scriptInterfaceCtx.addCallback(
105+
'energy_of_structure',
106+
(seq: string, secstruct: string): number | null => {
107+
if (this._folder === null) {
108+
return null;
109+
}
110+
const seqArr: Sequence = Sequence.fromSequenceString(seq);
111+
const pseudoknots = this._isPseudoknot;
112+
const structArr: SecStruct = SecStruct.fromParens(secstruct, pseudoknots);
113+
const freeEnergy = this._isPseudoknot
114+
? this._folder.scoreStructures(seqArr, structArr, true)
115+
: this._folder.scoreStructures(seqArr, structArr);
116+
return 0.01 * freeEnergy;
117+
}
118+
);
119+
120+
scriptInterfaceCtx.addCallback(
121+
'energy_of_structure_async',
122+
async (seq: string, secstruct: string): Promise<number | null> => {
123+
if (this._folder === null) {
124+
return null;
125+
}
126+
const seqArr: Sequence = Sequence.fromSequenceString(seq);
127+
const pseudoknots = this._isPseudoknot;
128+
const structArr: SecStruct = SecStruct.fromParens(secstruct, pseudoknots);
129+
const freeEnergy = this._isPseudoknot
130+
? this._folder.scoreStructures(seqArr, structArr, true)
131+
: this._folder.scoreStructures(seqArr, structArr);
132+
return 0.01 * freeEnergy;
133+
}
134+
);
135+
136+
scriptInterfaceCtx.addCallback(
137+
'pairing_probabilities',
138+
(seq: string, secstruct: string | null = null): number[] | null => {
139+
if (this._folder === null) {
140+
return null;
141+
}
142+
if (!this._folder.isSync()) {
143+
throw new Error('Attempted to use asynchronous folding engine synchronously');
144+
}
145+
const seqArr: Sequence = Sequence.fromSequenceString(seq);
146+
const pseudoknots = this._isPseudoknot;
147+
let folded: SecStruct | null;
148+
if (secstruct) {
149+
folded = SecStruct.fromParens(secstruct, pseudoknots);
150+
} else {
151+
folded = this._folder.foldSequence(seqArr, null, null);
152+
if (folded === null) {
153+
return null;
154+
}
155+
}
156+
const pp: DotPlot | null = this._folder.getDotPlot(seqArr, folded);
157+
Assert.assertIsDefined(pp);
158+
return pp.data;
159+
}
160+
);
161+
162+
scriptInterfaceCtx.addCallback(
163+
'pairing_probabilities_async',
164+
async (seq: string, secstruct: string | null = null): Promise<number[] | null> => {
165+
if (this._folder === null) {
166+
return null;
167+
}
168+
const seqArr: Sequence = Sequence.fromSequenceString(seq);
169+
const pseudoknots = this._isPseudoknot;
170+
let folded: SecStruct | null;
171+
if (secstruct) {
172+
folded = SecStruct.fromParens(secstruct, pseudoknots);
173+
} else {
174+
folded = await this._folder.foldSequence(seqArr, null, null);
175+
if (folded === null) {
176+
return null;
177+
}
178+
}
179+
const pp: DotPlot | null = await this._folder.getDotPlot(seqArr, folded);
180+
Assert.assertIsDefined(pp);
181+
return pp.data;
182+
}
183+
);
184+
185+
scriptInterfaceCtx.addCallback(
186+
'subopt_single_sequence',
187+
(
188+
seq: string, kcalDelta: number,
189+
pseudoknotted: boolean, temp: number = EPars.DEFAULT_TEMPERATURE
190+
): SuboptEnsembleResult | null => {
191+
if (this._folder === null) {
192+
return null;
193+
}
194+
// now get subopt stuff
195+
const seqArr: Sequence = Sequence.fromSequenceString(seq);
196+
return this._folder.getSuboptEnsembleNoBindingSite(seqArr,
197+
kcalDelta, pseudoknotted, temp);
198+
}
199+
);
200+
201+
scriptInterfaceCtx.addCallback(
202+
'subopt_single_sequence_async',
203+
async (
204+
seq: string, kcalDelta: number,
205+
pseudoknotted: boolean, temp: number = EPars.DEFAULT_TEMPERATURE
206+
): Promise<SuboptEnsembleResult | null> => {
207+
if (this._folder === null) {
208+
return null;
209+
}
210+
// now get subopt stuff
211+
const seqArr: Sequence = Sequence.fromSequenceString(seq);
212+
return this._folder.getSuboptEnsembleNoBindingSite(seqArr,
213+
kcalDelta, pseudoknotted, temp);
214+
}
215+
);
216+
217+
scriptInterfaceCtx.addCallback(
218+
'subopt_oligos',
219+
(
220+
seq: string, oligoStrings: string[], kcalDelta: number,
221+
pseudoknotted: boolean, temp: number = EPars.DEFAULT_TEMPERATURE
222+
): SuboptEnsembleResult | null => {
223+
if (this._folder === null) {
224+
return null;
225+
}
226+
// make the sequence string from the oligos
227+
let newSequence: string = seq;
228+
for (let oligoIndex = 0; oligoIndex < oligoStrings.length; oligoIndex++) {
229+
const oligoSequence: string = oligoStrings[oligoIndex];
230+
newSequence = `${newSequence}&${oligoSequence}`;
231+
}
232+
233+
// now get subopt stuff
234+
const seqArr: Sequence = Sequence.fromSequenceString(newSequence);
235+
return this._folder.getSuboptEnsembleWithOligos(seqArr,
236+
oligoStrings, kcalDelta, pseudoknotted, temp);
237+
}
238+
);
239+
240+
scriptInterfaceCtx.addCallback(
241+
'subopt_oligos_async',
242+
async (
243+
seq: string, oligoStrings: string[], kcalDelta: number,
244+
pseudoknotted: boolean, temp: number = EPars.DEFAULT_TEMPERATURE
245+
): Promise<SuboptEnsembleResult | null> => {
246+
if (this._folder === null) {
247+
return null;
248+
}
249+
// make the sequence string from the oligos
250+
let newSequence: string = seq;
251+
for (let oligoIndex = 0; oligoIndex < oligoStrings.length; oligoIndex++) {
252+
const oligoSequence: string = oligoStrings[oligoIndex];
253+
newSequence = `${newSequence}&${oligoSequence}`;
254+
}
255+
256+
// now get subopt stuff
257+
const seqArr: Sequence = Sequence.fromSequenceString(newSequence);
258+
return this._folder.getSuboptEnsembleWithOligos(seqArr,
259+
oligoStrings, kcalDelta, pseudoknotted, temp);
260+
}
261+
);
262+
263+
scriptInterfaceCtx.addCallback(
264+
'cofold',
265+
(
266+
seq: string, oligo: string, malus: number = 0.0, constraint: string | null = null
267+
): string | null => {
268+
if (this._folder === null) {
269+
return null;
270+
}
271+
const len: number = seq.length;
272+
const cseq = `${seq}&${oligo}`;
273+
const seqArr: Sequence = Sequence.fromSequenceString(cseq);
274+
const pseudoknots = this._isPseudoknot;
275+
const folded: SecStruct | null = this._folder.cofoldSequence(
276+
seqArr, null, Math.floor(malus * 100), constraint
277+
);
278+
if (folded === null) {
279+
return null;
280+
}
281+
return `${folded.slice(0, len).getParenthesis({pseudoknots})
282+
}&${folded.slice(len).getParenthesis({pseudoknots})}`;
283+
}
284+
);
285+
286+
scriptInterfaceCtx.addCallback(
287+
'cofold_async',
288+
async (
289+
seq: string, oligo: string, malus: number = 0.0, constraint: string | null = null
290+
): Promise<string | null> => {
291+
if (this._folder === null) {
292+
return null;
293+
}
294+
const len: number = seq.length;
295+
const cseq = `${seq}&${oligo}`;
296+
const seqArr: Sequence = Sequence.fromSequenceString(cseq);
297+
const pseudoknots = this._isPseudoknot;
298+
const folded: SecStruct | null = this._folder.cofoldSequence(
299+
seqArr, null, Math.floor(malus * 100), constraint
300+
);
301+
if (folded === null) {
302+
return null;
303+
}
304+
return `${folded.slice(0, len).getParenthesis({pseudoknots})
305+
}&${folded.slice(len).getParenthesis({pseudoknots})}`;
306+
}
307+
);
308+
309+
scriptInterfaceCtx.addCallback(
310+
'get_defect',
311+
(
312+
seq: string, secstruct: string, pseudoknotted: boolean, temp: number = EPars.DEFAULT_TEMPERATURE
313+
): number | null => {
314+
if (this._folder === null) {
315+
return null;
316+
}
317+
return this._folder.getDefect(
318+
Sequence.fromSequenceString(seq),
319+
SecStruct.fromParens(secstruct, pseudoknotted),
320+
temp, pseudoknotted
321+
);
322+
}
323+
);
324+
325+
scriptInterfaceCtx.addCallback(
326+
'get_defect_async',
327+
async (
328+
seq: string, secstruct: string, pseudoknotted: boolean, temp: number = EPars.DEFAULT_TEMPERATURE
329+
): Promise<number | null> => {
330+
if (this._folder === null) {
331+
return null;
332+
}
333+
return this._folder.getDefect(
334+
Sequence.fromSequenceString(seq),
335+
SecStruct.fromParens(secstruct, pseudoknotted),
336+
temp, pseudoknotted
337+
);
338+
}
339+
);
340+
}
341+
}

0 commit comments

Comments
 (0)