diff --git a/src/mol-plugin-state/transforms/model.ts b/src/mol-plugin-state/transforms/model.ts index b4b24ff137cb7cab29262f9fba96234daaa7339a..977a01f95be64911aa5cc574a40c0336c61178a4 100644 --- a/src/mol-plugin-state/transforms/model.ts +++ b/src/mol-plugin-state/transforms/model.ts @@ -786,7 +786,7 @@ const StructureSelectionFromScript = PluginStateTransform.BuiltIn({ from: SO.Molecule.Structure, to: SO.Molecule.Structure, params: () => ({ - script: PD.Script({ language: 'pymol', expression: 'all' }), + script: PD.Script({ language: 'rasmol', expression: 'all' }), label: PD.Optional(PD.Text('')) }) })({ diff --git a/src/mol-script/transpilers/pymol/properties.ts b/src/mol-script/transpilers/pymol/properties.ts index 5b2b068ddce190213a17e661df728d67177a8a8c..b0d620a3d31e6ae2f3737ffec1a7ce870559a13f 100644 --- a/src/mol-script/transpilers/pymol/properties.ts +++ b/src/mol-script/transpilers/pymol/properties.ts @@ -19,8 +19,28 @@ function rangeMap(x: string) { return { min, max }; } function listOrRangeMap(x: string) { - return x.includes('-') ? rangeMap(x) : listMap(x).map(x => parseInt(x)); + if (x.includes('-') && x.includes('+')){ + const pSplit = x.split('+').map(x => x.replace(/^["']|["']$/g, '')); + const res : number[] =[]; + pSplit.forEach( x => { + if (x.includes('-')){ + const [min, max] = x.split('-').map(x=>parseInt(x)); + for (var i = min; i <= max; i++){ + res.push(i); + } + }else{ + res.push(parseInt(x)); + } + }); +// console.log(res) + return res; + }else if(x.includes('-') && !x.includes('+')){ + return rangeMap(x) + }else if(!x.includes('-') && x.includes('+')){ + return listMap(x) + } } + function elementListMap(x: string) { return x.split('+').map(B.struct.type.elementSymbol); } diff --git a/src/mol-script/transpilers/rasmol/keywords.ts b/src/mol-script/transpilers/rasmol/keywords.ts index 91c3906e62ea5badd4d7c3372bf2e37357b2ca69..f56341c448a396e8b38d8d62323e560c21c70835 100644 --- a/src/mol-script/transpilers/rasmol/keywords.ts +++ b/src/mol-script/transpilers/rasmol/keywords.ts @@ -20,18 +20,21 @@ function nucleicExpr() { return B.struct.combinator.merge([ B.struct.generator.atomGroups({ 'residue-test': B.core.set.has([ - B.set(...['G', 'C', 'A', 'T', 'U', 'I', 'DG', 'DC', 'DA', 'DT', 'DU', 'DI', '+G', '+C', '+A', '+T', '+U', '+I']), + B.core.type.set(['G', 'C', 'A', 'T', 'U', 'I', 'DG', 'DC', 'DA', 'DT', 'DU', 'DI', '+G', '+C', '+A', '+T', '+U', '+I']), B.ammp('label_comp_id') ]) }), B.struct.filter.pick({ - 0: B.struct.generator.atomGroups({ + 0: B.struct.generator.atomGroups({ 'group-by': B.ammp('residueKey') - }), - test: B.core.logic.and([ - B.core.rel.eq([B.struct.atomSet.atomCount(), 1]), - B.core.rel.eq([B.ammp('label_atom_id'), B.atomName('P')]), - ]) + }), + test: B.core.logic.and([ + B.core.set.isSubset([ + // B.core.type.set([ 'P', 'O1P', 'O2P' ]), + h.atomNameSet(['P']), + B.ammpSet('label_atom_id') + ]), + ]) }), B.struct.filter.pick({ 0: B.struct.generator.atomGroups({ diff --git a/src/mol-script/transpilers/rasmol/operators.ts b/src/mol-script/transpilers/rasmol/operators.ts index 0344052e94ac204f39a56b4a36a30c5b15c4aec6..0074a7625d8ffc5d89b27b922b4cac14ae8aa963 100644 --- a/src/mol-script/transpilers/rasmol/operators.ts +++ b/src/mol-script/transpilers/rasmol/operators.ts @@ -38,7 +38,7 @@ export const operators: OperatorList = [ '@examples': ['ASP or GLU'], name: 'or', type: h.binaryLeft, - rule: h.infixOp(/OR|\|/i), + rule: h.infixOp(/OR|\||\|\|/i), map: (op, s1, s2) => B.struct.combinator.merge([s1, s2]) } ]; diff --git a/src/mol-script/transpilers/rasmol/parser.ts b/src/mol-script/transpilers/rasmol/parser.ts index 58d0bf978f774a89722842e3d7693771e55c180f..6401a4e532423c66bb0c5c4e7be77a2cb2cd6f96 100644 --- a/src/mol-script/transpilers/rasmol/parser.ts +++ b/src/mol-script/transpilers/rasmol/parser.ts @@ -11,7 +11,10 @@ import * as P from '../../../mol-util/monadic-parser'; import * as h from '../helper'; import { MolScriptBuilder } from '../../../mol-script/language/builder'; const B = MolScriptBuilder; -import { properties, structureMap } from './properties'; +import { properties, structureMap, structureDict } from './properties'; +import { special_properties } from './special_properties'; +import { special_keywords } from './special_keywords'; +import { special_operators } from './special_operators'; import { operators } from './operators'; import { keywords } from './keywords'; import { AtomGroupArgs } from '../types'; @@ -22,96 +25,45 @@ import { OperatorList } from '../types'; // const slash = P.MonadicParser.string('/'); +const propertiesDict = h.getPropertyRules(special_properties); -// <, <=, =, >=, >, !=, and LIKE -const valueOperators: OperatorList = [ - { - '@desc': 'value comparisons', - '@examples': [], - name: '=', - abbr: ['=='], - type: h.binaryLeft, - rule: P.MonadicParser.regexp(/\s*(LIKE|>=|<=|=|!=|>|<)\s*/i, 1), - map: (op, e1, e2) => { - // console.log(op, e1, e2) - let expr; - if (e1 === 'structure') { - expr = B.core.flags.hasAny([B.ammp('secondaryStructureFlags'), structureMap(e2)]); - } else if (e2 === 'structure') { - expr = B.core.flags.hasAny([B.ammp('secondaryStructureFlags'), structureMap(e1)]); - } else if (e1.head !== undefined) { - if (e1.head.name === 'core.type.regex') { - expr = B.core.str.match([e1, B.core.type.str([e2])]); - } - } else if (e2.head !== undefined) { - if (e2.head.name === 'core.type.regex') { - expr = B.core.str.match([e2, B.core.type.str([e1])]); - } - } else if (op.toUpperCase() === 'LIKE') { - if (e1.head) { - expr = B.core.str.match([ - B.core.type.regex([`^${e2}$`, 'i']), - B.core.type.str([e1]) - ]); - } else { - expr = B.core.str.match([ - B.core.type.regex([`^${e1}$`, 'i']), - B.core.type.str([e2]) - ]); - } - } - if (!expr) { - if (e1.head) e2 = h.wrapValue(e1, e2); - if (e2.head) e1 = h.wrapValue(e2, e1); - switch (op) { - case '=': - expr = B.core.rel.eq([e1, e2]); - break; - case '!=': - expr = B.core.rel.neq([e1, e2]); - break; - case '>': - expr = B.core.rel.gr([e1, e2]); - break; - case '<': - expr = B.core.rel.lt([e1, e2]); - break; - case '>=': - expr = B.core.rel.gre([e1, e2]); - break; - case '<=': - expr = B.core.rel.lte([e1, e2]); - break; - default: throw new Error(`value operator '${op}' not supported`); - } - } - return B.struct.generator.atomGroups({ 'atom-test': expr }); - } - } -]; +const slash = P.MonadicParser.string('/'); +const dot = P.MonadicParser.string('.'); +const colon = P.MonadicParser.string(':'); +const comma = P.MonadicParser.string(','); +const star = P.MonadicParser.string('*'); +const bra = P.MonadicParser.string('('); +const ket = P.MonadicParser.string(')'); + + +/* is Parser -> MonadicParser substitution correct? */ +function orNull(rule: P.MonadicParser<any>) { + return rule.or(P.MonadicParser.of(null)); +} -function atomExpressionQuery(x: any[]) { - const [resno, inscode, chainname, atomname, altloc] = x[1]; + +function atomSelectionQuery2(x: any) { const tests: AtomGroupArgs = {}; + const props: { [k: string]: any[] } = {}; - if (chainname) { - // should be configurable, there is an option in Jmol to use auth or label - tests['chain-test'] = B.core.rel.eq([B.ammp('auth_asym_id'), chainname]); + for (const k in x) { + const ps = special_properties[k]; + if (!ps) { + throw new Error(`property '${k}' not supported, value '${x[k]}'`); + } + if (x[k] === null) continue; + if (!props[ps.level]) props[ps.level] = []; + props[ps.level].push(x[k]); } - const resProps = []; - if (resno) resProps.push(B.core.rel.eq([B.ammp('auth_seq_id'), resno])); - if (inscode) resProps.push(B.core.rel.eq([B.ammp('pdbx_PDB_ins_code'), inscode])); - if (resProps.length) tests['residue-test'] = h.andExpr(resProps); - - const atomProps = []; - if (atomname) atomProps.push(B.core.rel.eq([B.ammp('auth_atom_id'), atomname])); - if (altloc) atomProps.push(B.core.rel.eq([B.ammp('label_alt_id'), altloc])); - if (atomProps.length) tests['atom-test'] = h.andExpr(atomProps); + for (const p in props) { + tests[p] = h.andExpr(props[p]); + } return B.struct.generator.atomGroups(tests); } + const lang = P.MonadicParser.createLanguage({ Integer: () => P.MonadicParser.regexp(/-?[0-9]+/).map(Number).desc('integer'), @@ -125,76 +77,166 @@ const lang = P.MonadicParser.createLanguage({ Expression: function (r: any) { return P.MonadicParser.alt( - r.NamedAtomProperties, r.Keywords, - r.Resno.lookahead(P.MonadicParser.regexp(/\s*(?!(LIKE|>=|<=|!=|[:^%/.=><]))/i)).map((x: any) => B.struct.generator.atomGroups({ - 'residue-test': B.core.rel.eq([B.ammp('auth_seq_id'), x]) - })), - r.AtomExpression.map(atomExpressionQuery), - - r.ValueQuery, - - r.Element.map((x: string) => B.struct.generator.atomGroups({ - 'atom-test': B.core.rel.eq([B.acp('elementSymbol'), B.struct.type.elementSymbol(x)]) - })), - r.Resname.map((x: string) => B.struct.generator.atomGroups({ - 'residue-test': B.core.rel.eq([B.ammp('label_comp_id'), x]) - })), + r.NamedAtomProperties, + r.AtomSelectionMacro.map(atomSelectionQuery2), + r.Object ); }, - NamedAtomProperties: function () { - return P.MonadicParser.alt(...h.getNamedPropertyRules(properties)); + + // lys:a.ca -> resn lys and chain A and name ca + // lys*a.ca -> resn lys and chain A and name ca + // + // :a.ca -> chain A and name ca + // *a.ca -> chain A and name ca + // + // *.cg -> name ca + // :.cg -> name ca + AtomSelectionMacro: function (r: any) { + return P.MonadicParser.alt( + // :A.CA :.CA + colon.then(P.MonadicParser.alt( + P.MonadicParser.seq( + orNull(propertiesDict.chain).skip(dot), + orNull(propertiesDict.name) + ).map(x => { return { chain: x[0], name: x[1]}; }), + P.MonadicParser.seq( + orNull(propertiesDict.name).skip(dot) + ).map(x => { return {name: x[0] }; }), + )), + // *A.CA *.CA + star.then(P.MonadicParser.alt( + P.MonadicParser.seq( + orNull(propertiesDict.chain).skip(dot), + orNull(propertiesDict.name) + ).map(x => { return { chain: x[0], name: x[1]}; }), + P.MonadicParser.seq( + orNull(propertiesDict.name).skip(dot) + ).map(x => { return {name: x[0] }; }), + )), + // 1-100+201 + bra.then(P.MonadicParser.alt( + P.MonadicParser.alt( + P.MonadicParser.seq( + orNull(propertiesDict.resi).skip(ket), + ).map(x => { return { resi: x[0] };}) + ))), + // lys:a.ca lys:a lys lys.ca + P.MonadicParser.alt( + P.MonadicParser.alt( + P.MonadicParser.seq( + orNull(propertiesDict.resn).skip(colon), + orNull(propertiesDict.chain).skip(dot), + orNull(propertiesDict.name) + ).map(x => { return { resn: x[0], chain: x[1], name: x[2] }; }), + P.MonadicParser.seq( + orNull(propertiesDict.resn).skip(star), + orNull(propertiesDict.chain).skip(dot), + orNull(propertiesDict.name) + ).map(x => { return { resn: x[0], chain: x[1], name: x[2] }; }), + P.MonadicParser.seq( + orNull(propertiesDict.resn).skip(colon), + orNull(propertiesDict.chain), + ).map(x => { return { resn: x[0], chain: x[1] }; }), + P.MonadicParser.seq( + orNull(propertiesDict.resn).skip(star), + orNull(propertiesDict.chain), + ).map(x => { return { resn: x[0], chain: x[1] }; }), + P.MonadicParser.seq( + orNull(propertiesDict.resn).skip(dot), + orNull(propertiesDict.name), + ).map(x => { return { resn: x[0], name: x[1] }; }), + P.MonadicParser.seq( + orNull(propertiesDict.resn), + ).map(x => { return { resn: x[0] };}), + ) + ) + + ) + }, - Operator: function (r: any) { - return h.combineOperators(operators, P.MonadicParser.alt(r.Parens, r.Expression)); + ObjectProperty: () => { + const w = h.getReservedWords(special_properties, special_keywords, special_operators) + .sort(h.strLenSortFn).map(h.escapeRegExp).join('|'); + return P.MonadicParser.regexp(new RegExp(`(?!(${w}))[A-Z0-9_]+`, 'i')); }, - AtomExpression: function (r: any) { - return P.MonadicParser.seq( - P.MonadicParser.lookahead(r.AtomPrefix), - P.MonadicParser.seq( - r.Resno.or(P.MonadicParser.of(null)), - r.Inscode.or(P.MonadicParser.of(null)), - r.Chainname.or(P.MonadicParser.of(null)), - r.Atomname.or(P.MonadicParser.of(null)), - r.Altloc.or(P.MonadicParser.of(null)), - r.Model.or(P.MonadicParser.of(null))), - ); + ObjectProperty2: () => { + const w = h.getReservedWords(properties, keywords, operators) + .sort(h.strLenSortFn).map(h.escapeRegExp).join('|'); + return P.MonadicParser.regexp(new RegExp(`(?!(${w}))[A-Z0-9_]+`, 'i')); }, + + Object: (r: any) => { + return r.ObjectProperty2 + .map((x: any) => { throw new Error(`property 'object' not supported, value '${x}'`); }); + }, + + - AtomPrefix: () => P.MonadicParser.regexp(/[0-9:^%/.]/).desc('atom-prefix'), - Chainname: () => P.MonadicParser.regexp(/:([A-Za-z]{1,3})/, 1).desc('chainname'), - Model: () => P.MonadicParser.regexp(/\/([0-9]+)/, 1).map(Number).desc('model'), - Element: () => P.MonadicParser.regexp(/_([A-Za-z]{1,3})/, 1).desc('element'), - Atomname: () => P.MonadicParser.regexp(/\.([a-zA-Z0-9]{1,4})/, 1).map(B.atomName).desc('atomname'), - Resname: () => P.MonadicParser.regexp(/[a-zA-Z0-9]{1,4}/).desc('resname'), - Resno: (r: any) => r.Integer.desc('resno'), - Resno2: (r: any) => r.split(',').Integer.desc('resno'), - Altloc: () => P.MonadicParser.regexp(/%([a-zA-Z0-9])/, 1).desc('altloc'), - Inscode: () => P.MonadicParser.regexp(/\^([a-zA-Z0-9])/, 1).desc('inscode'), + NamedAtomProperties: function () { + return P.MonadicParser.alt(...h.getNamedPropertyRules(properties)); + }, + ValueRange: function (r: any) { + return P.MonadicParser.seq( + r.Value + .skip(P.MonadicParser.regexp(/-/i)), + r.Value + ).map(x => ({ range: x })); + }, - // function listMap(x: string) { return x.split(',').map(x => x.replace(/^["']|["']$/g, '')); } + RangeListProperty: function (r: any) { + return P.MonadicParser.seq( + P.MonadicParser.alt(...h.getPropertyNameRules(special_properties, /\s/)) + .skip(P.MonadicParser.whitespace), + P.MonadicParser.alt( + r.ValueRange, + r.Value + ).sepBy1(comma) + ).map(x => { + const [property, values] = x; + const listValues: (string | number)[] = []; + const rangeValues: any[] = []; + + values.forEach((v: any) => { + if (v.range) { + rangeValues.push( + B.core.rel.inRange([property, v.range[0], v.range[1]]) + ); + } else { + listValues.push(h.wrapValue(property, v, structureDict)); + } + }); + const rangeTest = h.orExpr(rangeValues); + const listTest = h.valuesTest(property, listValues); + let test; + if (rangeTest && listTest) { + test = B.core.logic.or([rangeTest, listTest]); + } else { + test = rangeTest ? rangeTest : listTest; + } - BracketedResname: function (r: any) { - return P.MonadicParser.regexp(/\.([a-zA-Z0-9]{1,4})/, 1) - .desc('bracketed-resname'); - // [0SD] + return B.struct.generator.atomGroups({ [h.testLevel(property)]: test }); + }); }, - ResnoRange: function (r: any) { - return P.MonadicParser.regexp(/\.([\s]){1,3}/, 1) - .desc('resno-range'); - // 123-200 - // -12--3 +// Operator: function (r: any) { +// return h.combineOperators(operators, P.MonadicParser.alt(r.Parens, r.Expression)); +// }, + + Operator: function (r: any) { + return h.combineOperators(operators, P.MonadicParser.alt(r.Parens, r.Expression, r.Operator)); }, + Keywords: () => P.MonadicParser.alt(...h.getKeywordRules(keywords)), + Query: function (r: any) { return P.MonadicParser.alt( r.Operator, @@ -223,45 +265,7 @@ const lang = P.MonadicParser.createLanguage({ return P.MonadicParser.alt(r.Number, r.String); }, - ValueParens: function (r: any) { - return P.MonadicParser.alt( - r.ValueParens, - r.ValueOperator, - r.ValueExpressions - ).wrap(P.MonadicParser.string('('), P.MonadicParser.string(')')); - }, - - ValuePropertyNames: function () { - return P.MonadicParser.alt(...h.getPropertyNameRules(properties, /LIKE|>=|<=|=|!=|>|<|\)|\s/i)); - }, - - ValueOperator: function (r: any) { - return h.combineOperators(valueOperators, P.MonadicParser.alt(r.ValueParens, r.ValueExpressions)); - }, - - ValueExpressions: function (r: any) { - return P.MonadicParser.alt( - r.Value, - r.ValuePropertyNames - ); - }, - ValueQuery: function (r: any) { - return P.MonadicParser.alt( - r.ValueOperator.map((x: any) => { - if (x.head.name) { - if (x.head.name.startsWith('structure-query.generator')) return x; - } else { - if (typeof x === 'string' && x.length <= 4) { - return B.struct.generator.atomGroups({ - 'residue-test': B.core.rel.eq([B.ammp('label_comp_id'), x]) - }); - } - } - throw new Error(`values must be part of an comparison, value '${x}'`); - }) - ); - } }); export const transpiler: Transpiler = str => lang.Query.tryParse(str); diff --git a/src/mol-script/transpilers/rasmol/properties.ts b/src/mol-script/transpilers/rasmol/properties.ts index 8164bb724857d480911a8c6e0cafb87dd18d3586..d152c3a6be86c5be16a9b9ac9df9fd6e6f7bd190 100644 --- a/src/mol-script/transpilers/rasmol/properties.ts +++ b/src/mol-script/transpilers/rasmol/properties.ts @@ -16,7 +16,14 @@ const rePosInt = /[0-9]+/; function str(x: string) { return x; } -const structureDict: {[key: string]: string} = { +export function sstrucMap(x: string) { + return B.struct.type.secondaryStructureFlags( + [structureDict[x.toUpperCase()] || 'none'] + ); +} + + +export const structureDict: {[key: string]: string} = { none: 'none', turn: 'turn', sheet: 'beta', @@ -446,6 +453,14 @@ export const properties: PropertyDict = { regex: /-?[0-9]+/, map: x => parseInt(x), level: 'residue-test', property: B.ammp('auth_seq_id') }, + hoge: { + '@desc': 'PDB residue number, not including insertion code (see also seqcode, below)', + '@examples': ['resno = 100'], +// isNumeric: true, + regex: /-?[0-9]+/, map: x => parseInt(x), + level: 'residue-test', property: B.ammp('auth_seq_id') + + }, selected: { '@desc': '1.0 if atom is selected; 0.0 if not', '@examples': [''], diff --git a/src/mol-script/transpilers/rasmol/special_keywords.ts b/src/mol-script/transpilers/rasmol/special_keywords.ts new file mode 100644 index 0000000000000000000000000000000000000000..ecdcef51e1662b086d79eadcdb112c9e2c62b6a1 --- /dev/null +++ b/src/mol-script/transpilers/rasmol/special_keywords.ts @@ -0,0 +1,232 @@ +/** + * Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + */ + +import { MolScriptBuilder } from '../../../mol-script/language/builder'; +const B = MolScriptBuilder; +import * as h from '../helper'; +import { KeywordDict } from '../types'; + +const ResDict = { + nucleic: ['A', 'C', 'T', 'G', 'U', 'DA', 'DC', 'DT', 'DG', 'DU'], + protein: ['ALA', 'ARG', 'ASN', 'ASP', 'CYS', 'CYX', 'GLN', 'GLU', 'GLY', 'HIS', 'HID', 'HIE', 'HIP', 'ILE', 'LEU', 'LYS', 'MET', 'MSE', 'PHE', 'PRO', 'SER', 'THR', 'TRP', 'TYR', 'VAL'], + solvent: ['HOH', 'WAT', 'H20', 'TIP', 'SOL'] +}; + +const Backbone = { + nucleic: ['P', "O3'", "O5'", "C5'", "C4'", "C3'", 'OP1', 'OP2', 'O3*', 'O5*', 'C5*', 'C4*', 'C3*'], + protein: ['C', 'N', 'CA', 'O'] +}; + + +export const special_keywords: KeywordDict = { + all: { + '@desc': 'All atoms currently loaded into PyMOL', + abbr: ['*'], + map: () => B.struct.generator.all() + }, + none: { + '@desc': 'No atoms (empty selection)', + map: () => B.struct.generator.empty() + }, + hydrogens: { + '@desc': 'All hydrogen atoms currently loaded into PyMOL', + abbr: ['hydro', 'h.'], + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.eq([ + B.acp('elementSymbol'), + B.es('H') + ]) + }) + }, + hetatm: { + '@desc': 'All atoms loaded from Protein Data Bank HETATM records', + abbr: ['het'], + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.eq([B.ammp('isHet'), true]) + }) + }, + visible: { + '@desc': 'All atoms in enabled objects with at least one visible representation', + abbr: ['v.'] + }, + polymer: { + '@desc': 'All atoms on the polymer (not het). Finds atoms with residue identifiers matching a known polymer, such a peptide and DNA.', + abbr: ['pol.'], + map: () => B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.nucleic.concat(ResDict.protein)), + B.ammp('label_comp_id') + ]) + }) + }, + sidechain: { + '@desc': 'Polymer non-backbone atoms (new in PyMOL 1.6.1)', + }, + present: { + '@desc': 'All atoms with defined coordinates in the current state (used in creating movies)', + abbr: ['pr.'] + }, + center: { + '@desc': 'Pseudo-atom at the center of the scene' + }, + origin: { + '@desc': 'Pseudo-atom at the origin of rotation', + }, + enabled: { + '@desc': 'All enabled objects or selections from the object list.', + }, + masked: { + '@desc': 'All masked atoms.', + abbr: ['msk.'] + }, + protected: { + '@desc': 'All protected atoms.', + abbr: ['pr.'] + }, + bonded: { + '@desc': 'All bonded atoms', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.gr([B.struct.atomProperty.core.bondCount({ + flags: B.struct.type.bondFlags(['covalent', 'metallic', 'sulfide']) + }), 0]) + }) + }, + donors: { + '@desc': 'All hydrogen bond donor atoms.', + abbr: ['don.'] + }, + acceptors: { + '@desc': 'All hydrogen bond acceptor atoms.', + abbr: ['acc.'] + }, + fixed: { + '@desc': 'All fixed atoms.', + abbr: ['fxd.'] + }, + restrained: { + '@desc': 'All restrained atoms.', + abbr: ['rst.'] + }, + organic: { + '@desc': 'All atoms in non-polymer organic compounds (e.g. ligands, buffers). Finds carbon-containing molecules that do not match known polymers.', + abbr: ['org.'], + map: () => h.asAtoms(B.struct.modifier.expandProperty({ + '0': B.struct.modifier.union([ + B.struct.generator.queryInSelection({ + '0': B.struct.generator.atomGroups({ + 'residue-test': B.core.logic.not([ + B.core.set.has([ + B.core.type.set(ResDict.nucleic.concat(ResDict.protein)), + B.ammp('label_comp_id') + ]) + ]) + }), + query: B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.eq([ + B.es('C'), + B.acp('elementSymbol') + ]) + }) + }) + ]), + property: B.ammp('residueKey') + })) + }, + inorganic: { + '@desc': 'All non-polymer inorganic atoms/ions. Finds atoms in molecules that do not contain carbon and do not match any known solvent residues.', + abbr: ['ino.'], + map: () => h.asAtoms(B.struct.modifier.expandProperty({ + '0': B.struct.modifier.union([ + B.struct.filter.pick({ + '0': B.struct.generator.atomGroups({ + 'residue-test': B.core.logic.not([ + B.core.set.has([ + B.core.type.set(ResDict.nucleic.concat(ResDict.protein).concat(ResDict.solvent)), + B.ammp('label_comp_id') + ]) + ]), + 'group-by': B.ammp('residueKey') + }), + test: B.core.logic.not([ + B.core.set.has([ + B.struct.atomSet.propertySet([B.acp('elementSymbol')]), + B.es('C') + ]) + ]) + }) + ]), + property: B.ammp('residueKey') + })) + }, + solvent: { + '@desc': 'All water molecules. The hardcoded solvent residue identifiers are currently: HOH, WAT, H20, TIP, SOL.', + abbr: ['sol.'], + map: () => B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.solvent), + B.ammp('label_comp_id') + ]) + }) + }, + guide: { + '@desc': 'All protein CA and nucleic acid C4*/C4', + map: () => B.struct.combinator.merge([ + B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.eq([ + B.atomName('CA'), + B.ammp('label_atom_id') + ]), + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.protein), + B.ammp('label_comp_id') + ]) + }), + B.struct.generator.atomGroups({ + 'atom-test': B.core.set.has([ + h.atomNameSet(['C4*', 'C4']), + B.ammp('label_atom_id') + ]), + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.nucleic), + B.ammp('label_comp_id') + ]) + }) + ]), + }, + metals: { + '@desc': 'All metal atoms (new in PyMOL 1.6.1)' + }, + backbone: { + '@desc': 'the C, N, CA, and O atoms of a protein and the equivalent atoms in a nucleic acid.', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.set.has([ + B.core.type.set(Backbone.protein.concat(ResDict.protein)), + B.ammp('label_atom_id') + ]) + }), + }, + proteinxxxxxx: { + '@desc': 'protein................', + abbr: ['polymer.protein'], + map: () => B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.protein), + B.ammp('label_comp_id') + ]) + }) + }, + nucleicxxxxx: { + '@desc': 'protein................', + abbr: ['polymer.nucleic'], + map: () => B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.nucleic), + B.ammp('label_comp_id') + ]) + }) + } +}; diff --git a/src/mol-script/transpilers/rasmol/special_operators.ts b/src/mol-script/transpilers/rasmol/special_operators.ts new file mode 100644 index 0000000000000000000000000000000000000000..9a53bde352334e70f3d3e43488e40c9a9aab4f85 --- /dev/null +++ b/src/mol-script/transpilers/rasmol/special_operators.ts @@ -0,0 +1,369 @@ +/** + * Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + */ + +import * as P from '../../../mol-util/monadic-parser'; +import * as h from '../helper'; +import { MolScriptBuilder } from '../../../mol-script/language/builder'; +const B = MolScriptBuilder; +import { OperatorList } from '../types'; +import { Expression } from '../../language/expression'; + +export const special_operators: OperatorList = [ + { + '@desc': 'Selects atoms that are not included in s1.', + '@examples': [ + 'NOT resn ALA', + 'not (resi 42 or chain A)', + '!resi 42 or chain A', + ], + name: 'not', + type: h.prefix, + rule: P.MonadicParser.alt( + P.MonadicParser.regexp(/NOT/i).skip(P.MonadicParser.whitespace), + P.MonadicParser.string('!').skip(P.MonadicParser.optWhitespace) + ), + map: (op, selection) => h.invertExpr(selection), + }, + { + '@desc': 'Selects atoms included in both s1 and s2.', + '@examples': ['chain A AND name CA'], + name: 'and', + type: h.binaryLeft, + rule: h.infixOp(/AND|&/i), + map: (op, selection, by) => + B.struct.modifier.intersectBy({ 0: selection, by }), + }, + { + '@desc': 'Selects atoms included in either s1 or s2.', + '@examples': ['chain A OR chain B'], + name: 'or', + type: h.binaryLeft, + rule: h.infixOp(/OR|\|/i), + map: (op: string, s1: Expression, s2: Expression) => B.struct.combinator.merge([s1, s2]), + }, + { + '@desc': + 'Selects atoms in s1 whose identifiers name, resi, resn, chain and segi all match atoms in s2.', + '@examples': ['chain A IN chain B'], + name: 'in', + type: h.binaryLeft, + rule: h.infixOp(/IN/i), + map: (op: string, selection: Expression, source: Expression) => { + return B.struct.filter.withSameAtomProperties({ + 0: selection, + source, + property: B.core.type.compositeKey([ + B.ammp('label_atom_id'), + B.ammp('label_seq_id'), + B.ammp('label_comp_id'), + B.ammp('auth_asym_id'), + B.ammp('label_asym_id'), + ]), + }); + }, + }, + { + '@desc': + 'Selects atoms in s1 whose identifiers name and resi match atoms in s2.', + '@examples': ['chain A LIKE chain B'], + name: 'like', + type: h.binaryLeft, + rule: h.infixOp(/LIKE|l\./i), + map: (op: string, selection: Expression, source: Expression) => { + return B.struct.filter.withSameAtomProperties({ + 0: selection, + source, + property: B.core.type.compositeKey([ + B.ammp('label_atom_id'), + B.ammp('label_seq_id'), + ]), + }); + }, + }, + { + '@desc': + 'Selects all atoms whose van der Waals radii are separated from the van der Waals radii of s1 by a minimum of X Angstroms.', + '@examples': ['solvent GAP 2'], + name: 'gap', + type: h.postfix, + rule: h + .postfixOp(/GAP\s+([-+]?[0-9]*\.?[0-9]+)/i, 1) + .map((x: any) => parseFloat(x)), + map: (distance: number, target: Expression) => { + return B.struct.filter.within({ + '0': B.struct.generator.all(), + target, + 'atom-radius': B.acp('vdw'), + 'max-radius': distance, + invert: true, + }); + }, + }, + { + '@desc': + 'Selects atoms with centers within X Angstroms of the center of any atom in s1.', + '@examples': ['resname LIG AROUND 1'], + name: 'around', + abbr: ['a.'], + type: h.postfix, + rule: h + .postfixOp(/(AROUND|a\.)\s+([-+]?[0-9]*\.?[0-9]+)/i, 2) + .map((x: any) => parseFloat(x)), + map: (radius: number, target: Expression) => { + return B.struct.modifier.exceptBy({ + '0': B.struct.filter.within({ + '0': B.struct.generator.all(), + target, + 'max-radius': radius, + }), + by: target, + }); + }, + }, + { + '@desc': + 'Expands s1 by all atoms within X Angstroms of the center of any atom in s1.', + '@examples': ['chain A EXPAND 3'], + name: 'expand', + abbr: ['x.'], + type: h.postfix, + rule: h + .postfixOp(/(EXPAND|x\.)\s+([-+]?[0-9]*\.?[0-9]+)/i, 2) + .map((x: any) => parseFloat(x)), + map: (radius: number, selection: Expression) => { + return B.struct.modifier.includeSurroundings({ 0: selection, radius }); + }, + }, + { + '@desc': + 'Selects atoms in s1 that are within X Angstroms of any atom in s2.', + '@examples': ['chain A WITHIN 3 OF chain B'], + name: 'within', + abbr: ['w.'], + type: h.binaryLeft, + rule: h.ofOp('WITHIN', 'w.'), + map: (radius: number, selection: Expression, target: Expression) => { + return B.struct.filter.within({ + 0: selection, + target, + 'max-radius': radius, + }); + }, + }, + { + '@desc': + 'Same as within, but excludes s2 from the selection (and thus is identical to s1 and s2 around X).', + '@examples': ['chain A NEAR_TO 3 OF chain B'], + name: 'near_to', + abbr: ['nto.'], + type: h.binaryLeft, + rule: h.ofOp('NEAR_TO', 'nto.'), + map: (radius: number, selection: Expression, target: Expression) => { + return B.struct.modifier.exceptBy({ + '0': B.struct.filter.within({ + '0': selection, + target, + 'max-radius': radius, + }), + by: target, + }); + }, + }, + { + '@desc': 'Selects atoms in s1 that are at least X Anstroms away from s2.', + '@examples': ['solvent BEYOND 2 OF chain A'], + name: 'beyond', + abbr: ['be.'], + type: h.binaryLeft, + rule: h.ofOp('BEYOND', 'be.'), + map: (radius: number, selection: Expression, target: Expression) => { + return B.struct.modifier.exceptBy({ + '0': B.struct.filter.within({ + '0': selection, + target, + 'max-radius': radius, + invert: true, + }), + by: target, + }); + }, + }, + { + '@desc': 'Expands selection to complete residues.', + '@examples': ['BYRESIDUE name N'], + name: 'byresidue', + abbr: ['byresi', 'byres', 'br.'], + type: h.prefix, + rule: h.prefixOp(/BYRESIDUE|byresi|byres|br\./i), + map: (op: string, selection: Expression) => { + return h.asAtoms( + B.struct.modifier.expandProperty({ + '0': B.struct.modifier.union({ 0: selection }), + property: B.ammp('residueKey'), + }) + ); + }, + }, + { + '@desc': + 'Completely selects all alpha carbons in all residues covered by a selection.', + '@examples': ['BYCALPHA chain A'], + name: 'bycalpha', + abbr: ['bca.'], + type: h.prefix, + rule: h.prefixOp(/BYCALPHA|bca\./i), + map: (op: string, selection: Expression) => { + return B.struct.generator.queryInSelection({ + '0': B.struct.modifier.expandProperty({ + '0': B.struct.modifier.union({ 0: selection }), + property: B.ammp('residueKey'), + }), + query: B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.eq([ + B.atomName('CA'), + B.ammp('label_atom_id'), + ]), + }), + }); + }, + }, + { + '@desc': 'Expands selection to complete molecules.', + '@examples': ['BYMOLECULE resi 20-30'], + name: 'bymolecule', + isUnsupported: true, // structure-query.atom-property.topology.connected-component-key' is not implemented + abbr: ['bymol', 'bm.'], + type: h.prefix, + rule: h.prefixOp(/BYMOLECULE|bymol|bm\./i), + map: (op: string, selection: Expression) => { + return h.asAtoms( + B.struct.modifier.expandProperty({ + '0': B.struct.modifier.union({ 0: selection }), + property: B.atp('connectedComponentKey'), + }) + ); + }, + }, + { + '@desc': 'Expands selection to complete fragments.', + '@examples': ['BYFRAGMENT resi 10'], + name: 'byfragment', + abbr: ['byfrag', 'bf.'], + isUnsupported: true, + type: h.prefix, + rule: h.prefixOp(/BYFRAGMENT|byfrag|bf\./i), + map: (op: string, selection: Expression) => [op, selection], + }, + { + '@desc': 'Expands selection to complete segments.', + '@examples': ['BYSEGMENT resn CYS'], + name: 'bysegment', + abbr: ['bysegi', 'byseg', 'bs.'], + type: h.prefix, + rule: h.prefixOp(/BYSEGMENT|bysegi|byseg|bs\./i), + map: (op: string, selection: Expression) => { + return h.asAtoms( + B.struct.modifier.expandProperty({ + '0': B.struct.modifier.union({ 0: selection }), + property: B.ammp('chainKey'), + }) + ); + }, + }, + { + '@desc': 'Expands selection to complete objects.', + '@examples': ['BYOBJECT chain A'], + name: 'byobject', + abbr: ['byobj', 'bo.'], + isUnsupported: true, + type: h.prefix, + rule: h.prefixOp(/BYOBJECT|byobj|bo\./i), + map: (op: string, selection: Expression) => [op, selection], + }, + { + '@desc': 'Expands selection to unit cell.', + '@examples': ['BYCELL chain A'], + name: 'bycell', + isUnsupported: true, + type: h.prefix, + rule: h.prefixOp(/BYCELL/i), + map: (op: string, selection: Expression) => [op, selection], + }, + { + '@desc': 'All rings of size ≤ 7 which have at least one atom in s1.', + '@examples': ['BYRING resn HEM'], + name: 'byring', + // isUnsupported: true, // structure-query.atom-set.atom-count' is not implemented. + type: h.prefix, + rule: h.prefixOp(/BYRING/i), + map: (op: string, selection: Expression) => { + return h.asAtoms( + B.struct.modifier.intersectBy({ + '0': B.struct.filter.pick({ + '0': B.struct.generator.rings(), + test: B.core.logic.and([ + B.core.rel.lte([B.struct.atomSet.atomCount(), 7]), + B.core.rel.gr([B.struct.atomSet.countQuery([selection]), 1]), + ]), + }), + by: selection, + }) + ); + }, + }, + { + '@desc': 'Selects atoms directly bonded to s1, excludes s1.', + '@examples': ['NEIGHBOR resn CYS'], + name: 'neighbor', + type: h.prefix, + abbr: ['nbr.'], + rule: h.prefixOp(/NEIGHBOR|nbr\./i), + map: (op: string, selection: Expression) => { + return B.struct.modifier.exceptBy({ + '0': h.asAtoms( + B.struct.modifier.includeConnected({ + '0': B.struct.modifier.union({ 0: selection }), + 'bond-test': true, + }) + ), + by: selection, + }); + }, + }, + { + '@desc': 'Selects atoms directly bonded to s1, may include s1.', + '@examples': ['BOUND_TO name CA'], + name: 'bound_to', + abbr: ['bto.'], + type: h.prefix, + rule: h.prefixOp(/BOUND_TO|bto\./i), + map: (op: string, selection: Expression) => { + return h.asAtoms( + B.struct.modifier.includeConnected({ + '0': B.struct.modifier.union({ 0: selection }), + }) + ); + }, + }, + { + '@desc': 'Extends s1 by X bonds connected to atoms in s1.', + '@examples': ['resname LIG EXTEND 3'], + name: 'extend', + abbr: ['xt.'], + type: h.postfix, + rule: h.postfixOp(/(EXTEND|xt\.)\s+([0-9]+)/i, 2).map((x: any) => parseInt(x)), + map: (count: number, selection: Expression) => { + return h.asAtoms( + B.struct.modifier.includeConnected({ + '0': B.struct.modifier.union({ 0: selection }), + 'bond-test': true, + 'layer-count': count, + }) + ); + }, + }, +]; diff --git a/src/mol-script/transpilers/rasmol/special_properties.ts b/src/mol-script/transpilers/rasmol/special_properties.ts new file mode 100644 index 0000000000000000000000000000000000000000..4ca944c6d49b7b4ccc24855f487676d48517d357 --- /dev/null +++ b/src/mol-script/transpilers/rasmol/special_properties.ts @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + */ + +import { MolScriptBuilder } from '../../../mol-script/language/builder'; +const B = MolScriptBuilder; +import { PropertyDict } from '../types'; + +//const reFloat = /[-+]?[0-9]*\.?[0-9]+/; +// const rePosInt = /[0-9]+/; + +function atomNameListMap(x: string) { return x.split(',').map(B.atomName); } +function listMap(x: string) { return x.split(',').map(x => x.replace(/^["']|["']$/g, '')); } +function rangeMap(x: string) { + const [min, max] = x.split('-').map(x => parseInt(x)); + return { min, max }; +} +function listOrRangeMap(x: string) { + if (x.includes('-') && x.includes(',')){ + const pSplit = x.split(',').map(x => x.replace(/^["']|["']$/g, '')); + const res : number[] =[]; + pSplit.forEach( x => { + if (x.includes('-')){ + const [min, max] = x.split('-').map(x=>parseInt(x)); + for (var i = min; i <= max; i++){ + res.push(i); + } + }else{ + res.push(parseInt(x)); + } + }); +// console.log(res) + return res; + }else if(x.includes('-') && !x.includes(',')){ + return rangeMap(x) + }else if(!x.includes('-') && x.includes(',')){ + return listMap(x) + } +} +function elementListMap(x: string) { + return x.split('+').map(B.struct.type.elementSymbol); +} + +//const sstrucDict: { [k: string]: string } = { +// H: 'helix', +// S: 'beta', +// L: 'none' +//}; +//function sstrucListMap(x: string) { +// return { +// flags: B.struct.type.secondaryStructureFlags( +// x.toUpperCase().split('+').map(ss => sstrucDict[ss] || 'none') +// ) +// }; +//} + +export const special_properties: PropertyDict = { + symbol: { + '@desc': 'chemical-symbol-list: list of 1- or 2-letter chemical symbols from the periodic table', + '@examples': ['symbol O+N'], + abbr: ['e.'], regex: /[a-zA-Z'",]+/, map: elementListMap, + level: 'atom-test', property: B.acp('elementSymbol') + }, + name: { + '@desc': 'atom-name-list: list of up to 4-letter codes for atoms in proteins or nucleic acids', + '@examples': ['name CA+CB+CG+CD'], + abbr: ['n.'], regex: /[a-zA-Z0-9'",]+/, map: atomNameListMap, + level: 'atom-test', property: B.ammp('label_atom_id') + }, + resn: { + '@desc': 'residue-name-list: list of 3-letter codes for amino acids or list of up to 2-letter codes for nucleic acids', + '@examples': ['resn ASP+GLU+ASN+GLN', 'resn A+G'], + abbr: ['resname', 'r.'], regex: /[a-zA-Z0-9'",]+/, map: listMap, + level: 'residue-test', property: B.ammp('label_comp_id') + }, + resi: { + '@desc': 'residue-identifier-list list of up to 4-digit residue numbers or residue-identifier-range', + '@examples': ['resi 1+10+100+1000', 'resi 1-10'], + abbr: ['resident', 'residue', 'resid', 'i.'], regex: /[0-9,-]+/, map: listOrRangeMap, + level: 'residue-test', property: B.ammp('auth_seq_id') + }, + alt: { + '@desc': 'alternate-conformation-identifier-list list of single letters', + '@examples': ['alt A+B', 'alt ""', 'alt ""+A'], + abbr: [], regex: /[a-zA-Z0-9'",]+/, map: listMap, + level: 'atom-test', property: B.ammp('label_alt_id') + }, + chain: { + '@desc': 'chain-identifier-list list of single letters or sometimes numbers', + '@examples': ['chain A'], + abbr: ['c.'], regex: /[a-zA-Z0-9'",]+/, map: listMap, + level: 'chain-test', property: B.ammp('auth_asym_id') + }, + +};