Skip to content

Commit

Permalink
Refactor/extract scripting api 2 (#840)
Browse files Browse the repository at this point in the history
## 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
  • Loading branch information
guyguy2001 authored Dec 20, 2024
1 parent 6f99c5e commit e802626
Show file tree
Hide file tree
Showing 2 changed files with 348 additions and 317 deletions.
341 changes: 341 additions & 0 deletions src/eterna/eternaScript/FoldingAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
import EPars from 'eterna/EPars';
import Folder, {SuboptEnsembleResult} from 'eterna/folding/Folder';
import DotPlot from 'eterna/rnatypes/DotPlot';
import SecStruct from 'eterna/rnatypes/SecStruct';
import Sequence from 'eterna/rnatypes/Sequence';
import {ExternalInterfaceCtx} from 'eterna/util/ExternalInterface';
import {Assert} from 'flashbang';

/**
* An EternaScript API exposing all functions that handle folding an arbitrary
* RNA sequence, and aren't dependent on what's the RNA in the puzzle.
*
* It adds itself to an existing script API (`ExternalInterfaceCtx`) from
* `this.registerToScriptInterface`.
*
* Note: The API in this class is still affected by the selected folder
* and the pseudoknot mode of the puzzle - just not the sequence.
*/
export default class FoldingAPI {
private readonly _getFolder: () => Folder | null;
private readonly _getIsPseudoknot: () => boolean;

private get _folder(): Folder | null {
return this._getFolder();
}

private get _isPseudoknot(): boolean {
return this._getIsPseudoknot();
}

constructor(params: { getFolder: () => Folder, getIsPseudoknot: () => boolean }) {
this._getIsPseudoknot = params.getIsPseudoknot;
this._getFolder = params.getFolder;
}

public registerToScriptInterface(scriptInterfaceCtx: ExternalInterfaceCtx) {
scriptInterfaceCtx.addCallback(
'fold',
(seq: string, constraint: string | null = null): string | null => {
if (this._folder === null) {
return null;
}
if (!this._folder.isSync()) {
throw new Error('Attempted to use asynchronous folding engine synchronously');
}
const seqArr: Sequence = Sequence.fromSequenceString(seq);
const pseudoknots = this._isPseudoknot;
const folded: SecStruct | null = this._folder.foldSequence(seqArr, null, constraint, pseudoknots);
Assert.assertIsDefined(folded);
return folded.getParenthesis({pseudoknots});
}
);

scriptInterfaceCtx.addCallback(
'fold_async',
async (seq: string, constraint: string | null = null): Promise<string | null> => {
if (this._folder === null) {
return null;
}
const seqArr: Sequence = Sequence.fromSequenceString(seq);
const pseudoknots = this._isPseudoknot;
const folded: SecStruct | null = await this._folder.foldSequence(seqArr, null, constraint, pseudoknots);
Assert.assertIsDefined(folded);
return folded.getParenthesis({pseudoknots});
}
);

scriptInterfaceCtx.addCallback(
'fold_with_binding_site',
(seq: string, site: number[], bonus: number): string | null => {
if (this._folder === null) {
return null;
}
const seqArr: Sequence = Sequence.fromSequenceString(seq);
const pseudoknots = this._isPseudoknot;
const folded: SecStruct | null = this._folder.foldSequenceWithBindingSite(
seqArr, null, site, Math.floor(bonus * 100), 2.5
);
if (folded === null) {
return null;
}
return folded.getParenthesis({pseudoknots});
}
);

scriptInterfaceCtx.addCallback(
'fold_with_binding_site_async',
async (seq: string, site: number[], bonus: number): Promise<string | null> => {
if (this._folder === null) {
return null;
}
const seqArr: Sequence = Sequence.fromSequenceString(seq);
const pseudoknots = this._isPseudoknot;
const folded: SecStruct | null = this._folder.foldSequenceWithBindingSite(
seqArr, null, site, Math.floor(bonus * 100), 2.5
);
if (folded === null) {
return null;
}
return folded.getParenthesis({pseudoknots});
}
);

scriptInterfaceCtx.addCallback(
'energy_of_structure',
(seq: string, secstruct: string): number | null => {
if (this._folder === null) {
return null;
}
const seqArr: Sequence = Sequence.fromSequenceString(seq);
const pseudoknots = this._isPseudoknot;
const structArr: SecStruct = SecStruct.fromParens(secstruct, pseudoknots);
const freeEnergy = this._isPseudoknot
? this._folder.scoreStructures(seqArr, structArr, true)
: this._folder.scoreStructures(seqArr, structArr);
return 0.01 * freeEnergy;
}
);

scriptInterfaceCtx.addCallback(
'energy_of_structure_async',
async (seq: string, secstruct: string): Promise<number | null> => {
if (this._folder === null) {
return null;
}
const seqArr: Sequence = Sequence.fromSequenceString(seq);
const pseudoknots = this._isPseudoknot;
const structArr: SecStruct = SecStruct.fromParens(secstruct, pseudoknots);
const freeEnergy = this._isPseudoknot
? this._folder.scoreStructures(seqArr, structArr, true)
: this._folder.scoreStructures(seqArr, structArr);
return 0.01 * freeEnergy;
}
);

scriptInterfaceCtx.addCallback(
'pairing_probabilities',
(seq: string, secstruct: string | null = null): number[] | null => {
if (this._folder === null) {
return null;
}
if (!this._folder.isSync()) {
throw new Error('Attempted to use asynchronous folding engine synchronously');
}
const seqArr: Sequence = Sequence.fromSequenceString(seq);
const pseudoknots = this._isPseudoknot;
let folded: SecStruct | null;
if (secstruct) {
folded = SecStruct.fromParens(secstruct, pseudoknots);
} else {
folded = this._folder.foldSequence(seqArr, null, null);
if (folded === null) {
return null;
}
}
const pp: DotPlot | null = this._folder.getDotPlot(seqArr, folded);
Assert.assertIsDefined(pp);
return pp.data;
}
);

scriptInterfaceCtx.addCallback(
'pairing_probabilities_async',
async (seq: string, secstruct: string | null = null): Promise<number[] | null> => {
if (this._folder === null) {
return null;
}
const seqArr: Sequence = Sequence.fromSequenceString(seq);
const pseudoknots = this._isPseudoknot;
let folded: SecStruct | null;
if (secstruct) {
folded = SecStruct.fromParens(secstruct, pseudoknots);
} else {
folded = await this._folder.foldSequence(seqArr, null, null);
if (folded === null) {
return null;
}
}
const pp: DotPlot | null = await this._folder.getDotPlot(seqArr, folded);
Assert.assertIsDefined(pp);
return pp.data;
}
);

scriptInterfaceCtx.addCallback(
'subopt_single_sequence',
(
seq: string, kcalDelta: number,
pseudoknotted: boolean, temp: number = EPars.DEFAULT_TEMPERATURE
): SuboptEnsembleResult | null => {
if (this._folder === null) {
return null;
}
// now get subopt stuff
const seqArr: Sequence = Sequence.fromSequenceString(seq);
return this._folder.getSuboptEnsembleNoBindingSite(seqArr,
kcalDelta, pseudoknotted, temp);
}
);

scriptInterfaceCtx.addCallback(
'subopt_single_sequence_async',
async (
seq: string, kcalDelta: number,
pseudoknotted: boolean, temp: number = EPars.DEFAULT_TEMPERATURE
): Promise<SuboptEnsembleResult | null> => {
if (this._folder === null) {
return null;
}
// now get subopt stuff
const seqArr: Sequence = Sequence.fromSequenceString(seq);
return this._folder.getSuboptEnsembleNoBindingSite(seqArr,
kcalDelta, pseudoknotted, temp);
}
);

scriptInterfaceCtx.addCallback(
'subopt_oligos',
(
seq: string, oligoStrings: string[], kcalDelta: number,
pseudoknotted: boolean, temp: number = EPars.DEFAULT_TEMPERATURE
): SuboptEnsembleResult | null => {
if (this._folder === null) {
return null;
}
// make the sequence string from the oligos
let newSequence: string = seq;
for (let oligoIndex = 0; oligoIndex < oligoStrings.length; oligoIndex++) {
const oligoSequence: string = oligoStrings[oligoIndex];
newSequence = `${newSequence}&${oligoSequence}`;
}

// now get subopt stuff
const seqArr: Sequence = Sequence.fromSequenceString(newSequence);
return this._folder.getSuboptEnsembleWithOligos(seqArr,
oligoStrings, kcalDelta, pseudoknotted, temp);
}
);

scriptInterfaceCtx.addCallback(
'subopt_oligos_async',
async (
seq: string, oligoStrings: string[], kcalDelta: number,
pseudoknotted: boolean, temp: number = EPars.DEFAULT_TEMPERATURE
): Promise<SuboptEnsembleResult | null> => {
if (this._folder === null) {
return null;
}
// make the sequence string from the oligos
let newSequence: string = seq;
for (let oligoIndex = 0; oligoIndex < oligoStrings.length; oligoIndex++) {
const oligoSequence: string = oligoStrings[oligoIndex];
newSequence = `${newSequence}&${oligoSequence}`;
}

// now get subopt stuff
const seqArr: Sequence = Sequence.fromSequenceString(newSequence);
return this._folder.getSuboptEnsembleWithOligos(seqArr,
oligoStrings, kcalDelta, pseudoknotted, temp);
}
);

scriptInterfaceCtx.addCallback(
'cofold',
(
seq: string, oligo: string, malus: number = 0.0, constraint: string | null = null
): string | null => {
if (this._folder === null) {
return null;
}
const len: number = seq.length;
const cseq = `${seq}&${oligo}`;
const seqArr: Sequence = Sequence.fromSequenceString(cseq);
const pseudoknots = this._isPseudoknot;
const folded: SecStruct | null = this._folder.cofoldSequence(
seqArr, null, Math.floor(malus * 100), constraint
);
if (folded === null) {
return null;
}
return `${folded.slice(0, len).getParenthesis({pseudoknots})
}&${folded.slice(len).getParenthesis({pseudoknots})}`;
}
);

scriptInterfaceCtx.addCallback(
'cofold_async',
async (
seq: string, oligo: string, malus: number = 0.0, constraint: string | null = null
): Promise<string | null> => {
if (this._folder === null) {
return null;
}
const len: number = seq.length;
const cseq = `${seq}&${oligo}`;
const seqArr: Sequence = Sequence.fromSequenceString(cseq);
const pseudoknots = this._isPseudoknot;
const folded: SecStruct | null = this._folder.cofoldSequence(
seqArr, null, Math.floor(malus * 100), constraint
);
if (folded === null) {
return null;
}
return `${folded.slice(0, len).getParenthesis({pseudoknots})
}&${folded.slice(len).getParenthesis({pseudoknots})}`;
}
);

scriptInterfaceCtx.addCallback(
'get_defect',
(
seq: string, secstruct: string, pseudoknotted: boolean, temp: number = EPars.DEFAULT_TEMPERATURE
): number | null => {
if (this._folder === null) {
return null;
}
return this._folder.getDefect(
Sequence.fromSequenceString(seq),
SecStruct.fromParens(secstruct, pseudoknotted),
temp, pseudoknotted
);
}
);

scriptInterfaceCtx.addCallback(
'get_defect_async',
async (
seq: string, secstruct: string, pseudoknotted: boolean, temp: number = EPars.DEFAULT_TEMPERATURE
): Promise<number | null> => {
if (this._folder === null) {
return null;
}
return this._folder.getDefect(
Sequence.fromSequenceString(seq),
SecStruct.fromParens(secstruct, pseudoknotted),
temp, pseudoknotted
);
}
);
}
}
Loading

0 comments on commit e802626

Please sign in to comment.