api.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. /**
  2. * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { Queries, Structure, StructureQuery, StructureSymmetry, StructureProperties } from '../../../mol-model/structure';
  7. import { getAtomsTests } from '../query/atoms';
  8. import { CifWriter } from '../../../mol-io/writer/cif';
  9. import { QuerySchemas } from '../query/schemas';
  10. export enum QueryParamType {
  11. JSON,
  12. String,
  13. Integer,
  14. Boolean,
  15. Float
  16. }
  17. export interface QueryParamInfo<T extends string | number = string | number> {
  18. name: string,
  19. type: QueryParamType,
  20. description?: string,
  21. required?: boolean,
  22. defaultValue?: any,
  23. exampleValues?: any[],
  24. validation?: (v: T) => void,
  25. supportedValues?: string[],
  26. groupName?: string
  27. }
  28. export interface QueryDefinition<Params = any> {
  29. name: string,
  30. niceName: string,
  31. exampleId: string, // default is 1cbs
  32. query: (params: Params, structure: Structure, numModels: number[]) => StructureQuery,
  33. description: string,
  34. jsonParams: QueryParamInfo[],
  35. restParams: QueryParamInfo[],
  36. structureTransform?: (params: any, s: Structure) => Promise<Structure>,
  37. filter?: CifWriter.Category.Filter,
  38. '@params': Params
  39. }
  40. export const CommonQueryParamsInfo: QueryParamInfo[] = [
  41. { name: 'model_nums', type: QueryParamType.String, description: `A comma-separated list of model ids (i.e. 1,2). If set, only include atoms with the corresponding '_atom_site.pdbx_PDB_model_num' field.` },
  42. { name: 'encoding', type: QueryParamType.String, defaultValue: 'cif', description: `Determines the output encoding (text based 'CIF' or binary 'BCIF'). Ligands can also be exported as 'SDF' or 'MOL2'.`, supportedValues: ['cif', 'bcif', 'sdf', 'mol', 'mol2'] },
  43. { name: 'copy_all_categories', type: QueryParamType.Boolean, defaultValue: false, description: 'If true, copy all categories from the input file.' },
  44. { name: 'data_source', type: QueryParamType.String, defaultValue: '', description: 'Allows to control how the provided data source ID maps to input file (as specified by the server instance config).' }
  45. ];
  46. export type Encoding = 'cif' | 'bcif' | 'sdf' | 'mol' | 'mol2';
  47. export interface CommonQueryParamsInfo {
  48. model_nums?: number[],
  49. encoding?: Encoding,
  50. copy_all_categories?: boolean
  51. data_source?: string
  52. }
  53. export const AtomSiteSchemaElement = {
  54. label_entity_id: { type: QueryParamType.String, groupName: 'atom_site' },
  55. label_asym_id: { type: QueryParamType.String, groupName: 'atom_site' },
  56. auth_asym_id: { type: QueryParamType.String, groupName: 'atom_site'},
  57. label_comp_id: { type: QueryParamType.String, groupName: 'atom_site' },
  58. auth_comp_id: { type: QueryParamType.String, groupName: 'atom_site' },
  59. label_seq_id: { type: QueryParamType.Integer, groupName: 'atom_site' },
  60. auth_seq_id: { type: QueryParamType.Integer, groupName: 'atom_site' },
  61. pdbx_PDB_ins_code: { type: QueryParamType.String, groupName: 'atom_site' },
  62. label_atom_id: { type: QueryParamType.String, groupName: 'atom_site' },
  63. auth_atom_id: { type: QueryParamType.String, groupName: 'atom_site' },
  64. type_symbol: { type: QueryParamType.String, groupName: 'atom_site' }
  65. };
  66. export interface AtomSiteSchemaElement {
  67. label_entity_id?: string,
  68. label_asym_id?: string,
  69. auth_asym_id?: string,
  70. label_comp_id?: string,
  71. auth_comp_id?: string,
  72. label_seq_id?: number,
  73. auth_seq_id?: number,
  74. pdbx_PDB_ins_code?: string,
  75. label_atom_id?: string,
  76. auth_atom_id?: string,
  77. type_symbol?: string
  78. }
  79. export type AtomSiteSchema = AtomSiteSchemaElement | AtomSiteSchemaElement[]
  80. const AtomSiteTestJsonParam: QueryParamInfo = {
  81. name: 'atom_site',
  82. type: QueryParamType.JSON,
  83. description: 'Object or array of objects describing atom properties. Names are same as in wwPDB mmCIF dictionary of the atom_site category.',
  84. exampleValues: [[{ label_seq_id: 30, label_asym_id: 'A' }, { label_seq_id: 31, label_asym_id: 'A' }], { label_comp_id: 'ALA' }]
  85. };
  86. export const AtomSiteTestRestParams = (function() {
  87. const params: QueryParamInfo[] = [];
  88. for (const k of Object.keys(AtomSiteSchemaElement)) {
  89. const p = (AtomSiteSchemaElement as any)[k] as QueryParamInfo;
  90. p.name = k;
  91. params.push(p);
  92. }
  93. return params;
  94. })();
  95. const RadiusParam: QueryParamInfo = {
  96. name: 'radius',
  97. type: QueryParamType.Float,
  98. defaultValue: 5,
  99. exampleValues: [5],
  100. description: 'Value in Angstroms.',
  101. validation(v: any) {
  102. if (v < 1 || v > 10) {
  103. throw `Invalid radius for residue interaction query (must be a value between 1 and 10).`;
  104. }
  105. }
  106. };
  107. function Q<Params = any>(definition: Partial<QueryDefinition<Params>>) {
  108. return definition;
  109. }
  110. const QueryMap = {
  111. 'full': Q<{} | undefined>({ niceName: 'Full Structure', query: () => Queries.generators.all, description: 'The full structure.' }),
  112. 'ligand': Q<{ atom_site: AtomSiteSchema }>({
  113. niceName: 'Ligand',
  114. description: 'Coordinates of the first group satisfying the given criteria.',
  115. query: (p, _s, numModels) => {
  116. const tests = getAtomsTests(p.atom_site);
  117. const ligands = Queries.combinators.merge(tests.map(test => Queries.generators.atoms({
  118. ...test,
  119. unitTest: ctx => StructureProperties.unit.model_num(ctx.element) === numModels[0],
  120. groupBy: ctx => StructureProperties.residue.key(ctx.element)
  121. })));
  122. return Queries.filters.first(ligands);
  123. },
  124. jsonParams: [ AtomSiteTestJsonParam ],
  125. restParams: AtomSiteTestRestParams
  126. }),
  127. 'atoms': Q<{ atom_site: AtomSiteSchema }>({
  128. niceName: 'Atoms',
  129. description: 'Atoms satisfying the given criteria.',
  130. query: p => {
  131. return Queries.combinators.merge(getAtomsTests(p.atom_site).map(test => Queries.generators.atoms(test)));
  132. },
  133. jsonParams: [ AtomSiteTestJsonParam ],
  134. restParams: AtomSiteTestRestParams
  135. }),
  136. 'symmetryMates': Q<{ radius: number }>({
  137. niceName: 'Symmetry Mates',
  138. description: 'Computes crystal symmetry mates within the specified radius.',
  139. query: () => Queries.generators.all,
  140. structureTransform(p, s) {
  141. return StructureSymmetry.builderSymmetryMates(s, p.radius).run();
  142. },
  143. jsonParams: [ RadiusParam ],
  144. filter: QuerySchemas.assembly
  145. }),
  146. 'assembly': Q<{ name: string }>({
  147. niceName: 'Assembly',
  148. description: 'Computes structural assembly.',
  149. query: () => Queries.generators.all,
  150. structureTransform(p, s) {
  151. return StructureSymmetry.buildAssembly(s, '' + (p.name || '1')).run();
  152. },
  153. jsonParams: [{
  154. name: 'name',
  155. type: QueryParamType.String,
  156. defaultValue: '1',
  157. exampleValues: ['1'],
  158. description: 'Assembly name.'
  159. }],
  160. filter: QuerySchemas.assembly
  161. }),
  162. 'residueInteraction': Q<{ atom_site: AtomSiteSchema, radius: number }>({
  163. niceName: 'Residue Interaction',
  164. description: 'Identifies all residues within the given radius from the source residue. Takes crystal symmetry into account.',
  165. query(p) {
  166. const tests = getAtomsTests(p.atom_site);
  167. const center = Queries.combinators.merge(tests.map(test => Queries.generators.atoms({
  168. ...test,
  169. entityTest: test.entityTest
  170. ? ctx => test.entityTest!(ctx) && ctx.element.unit.conformation.operator.isIdentity
  171. : ctx => ctx.element.unit.conformation.operator.isIdentity
  172. })));
  173. return Queries.modifiers.includeSurroundings(center, { radius: p.radius !== void 0 ? p.radius : 5, wholeResidues: true });
  174. },
  175. structureTransform(p, s) {
  176. return StructureSymmetry.builderSymmetryMates(s, p.radius !== void 0 ? p.radius : 5).run();
  177. },
  178. jsonParams: [ AtomSiteTestJsonParam, RadiusParam ],
  179. restParams: [ ...AtomSiteTestRestParams, RadiusParam ],
  180. filter: QuerySchemas.interaction
  181. }),
  182. 'residueSurroundings': Q<{ atom_site: AtomSiteSchema, radius: number }>({
  183. niceName: 'Residue Surroundings',
  184. description: 'Identifies all residues within the given radius from the source residue.',
  185. query(p) {
  186. const center = Queries.combinators.merge(getAtomsTests(p.atom_site).map(test => Queries.generators.atoms(test)));
  187. return Queries.modifiers.includeSurroundings(center, { radius: p.radius, wholeResidues: true });
  188. },
  189. jsonParams: [ AtomSiteTestJsonParam, RadiusParam ],
  190. restParams: [ ...AtomSiteTestRestParams, RadiusParam ],
  191. filter: QuerySchemas.interaction
  192. })
  193. };
  194. export type QueryName = keyof typeof QueryMap
  195. export type QueryParams<Q extends QueryName> = Partial<(typeof QueryMap)[Q]['@params']>
  196. export function getQueryByName(name: QueryName): QueryDefinition {
  197. return QueryMap[name] as QueryDefinition;
  198. }
  199. export const QueryList = (function () {
  200. const list: { name: string, definition: QueryDefinition }[] = [];
  201. for (const k of Object.keys(QueryMap)) list.push({ name: k, definition: <QueryDefinition>QueryMap[k as QueryName] });
  202. list.sort(function (a, b) { return a.name < b.name ? -1 : a.name > b.name ? 1 : 0; });
  203. return list;
  204. })();
  205. // normalize the queries
  206. (function () {
  207. for (let q of QueryList) {
  208. const m = q.definition;
  209. m.name = q.name;
  210. m.jsonParams = m.jsonParams || [];
  211. m.restParams = m.restParams || m.jsonParams;
  212. }
  213. })();
  214. function _normalizeQueryParams(params: { [p: string]: string }, paramList: QueryParamInfo[]): { [p: string]: string | number | boolean } {
  215. const ret: any = {};
  216. for (const p of paramList) {
  217. const key = p.name;
  218. const value = params[key];
  219. let el: any;
  220. if (typeof value === 'undefined' || (typeof value !== 'undefined' && value !== null && value['length'] === 0)) {
  221. if (p.required) {
  222. throw `The parameter '${key}' is required.`;
  223. }
  224. if (typeof p.defaultValue !== 'undefined') el = p.defaultValue;
  225. } else {
  226. switch (p.type) {
  227. case QueryParamType.JSON: el = JSON.parse(value); break;
  228. case QueryParamType.String: el = value; break;
  229. case QueryParamType.Integer: el = parseInt(value); break;
  230. case QueryParamType.Float: el = parseFloat(value); break;
  231. case QueryParamType.Boolean: el = Boolean(+value); break;
  232. }
  233. if (p.validation) p.validation(el);
  234. }
  235. if (typeof el === 'undefined') continue;
  236. if (p.groupName) {
  237. if (typeof ret[p.groupName] === 'undefined') ret[p.groupName] = {};
  238. ret[p.groupName][key] = el;
  239. } else {
  240. ret[key] = el;
  241. }
  242. }
  243. return ret;
  244. }
  245. export function normalizeRestQueryParams(query: QueryDefinition, params: any) {
  246. // return params;
  247. return _normalizeQueryParams(params, query.restParams);
  248. }
  249. export function normalizeRestCommonParams(params: any): CommonQueryParamsInfo {
  250. return {
  251. model_nums: params.model_nums ? ('' + params.model_nums).split(',').map(n => n.trim()).filter(n => !!n).map(n => +n) : void 0,
  252. data_source: params.data_source,
  253. copy_all_categories: Boolean(params.copy_all_categories),
  254. encoding: mapEncoding(('' + params.encoding).toLocaleLowerCase())
  255. };
  256. }
  257. function mapEncoding(value: string) {
  258. switch (value) {
  259. case 'bcif':
  260. return 'bcif';
  261. case 'mol':
  262. return 'mol';
  263. case 'mol2':
  264. return 'mol2';
  265. case 'sdf':
  266. return 'sdf';
  267. default:
  268. return 'cif';
  269. }
  270. }