Skip to content
Snippets Groups Projects
Commit 589fdcec authored by David Sehnal's avatar David Sehnal
Browse files

model-server: refactoring job API

parent 6f15c1d5
No related branches found
No related tags found
No related merge requests found
...@@ -6,8 +6,9 @@ ...@@ -6,8 +6,9 @@
import { QueryPredicate, StructureElement, StructureProperties as Props } from 'mol-model/structure'; import { QueryPredicate, StructureElement, StructureProperties as Props } from 'mol-model/structure';
import { AtomsQueryParams } from 'mol-model/structure/query/queries/generators'; import { AtomsQueryParams } from 'mol-model/structure/query/queries/generators';
import { AtomSiteSchema } from '../server/api';
export function getAtomsTests(params: any): Partial<AtomsQueryParams>[] { export function getAtomsTests(params: AtomSiteSchema): Partial<AtomsQueryParams>[] {
if (!params) return [{ }]; if (!params) return [{ }];
if (Array.isArray(params)) { if (Array.isArray(params)) {
return params.map(p => atomsTest(p)); return params.map(p => atomsTest(p));
...@@ -16,7 +17,7 @@ export function getAtomsTests(params: any): Partial<AtomsQueryParams>[] { ...@@ -16,7 +17,7 @@ export function getAtomsTests(params: any): Partial<AtomsQueryParams>[] {
} }
} }
function atomsTest(params: any): Partial<AtomsQueryParams> { function atomsTest(params: AtomSiteSchema): Partial<AtomsQueryParams> {
return { return {
entityTest: entityTest(params), entityTest: entityTest(params),
chainTest: chainTest(params), chainTest: chainTest(params),
...@@ -25,13 +26,13 @@ function atomsTest(params: any): Partial<AtomsQueryParams> { ...@@ -25,13 +26,13 @@ function atomsTest(params: any): Partial<AtomsQueryParams> {
}; };
} }
function entityTest(params: any): QueryPredicate | undefined { function entityTest(params: AtomSiteSchema): QueryPredicate | undefined {
if (!params || typeof params.entity_id === 'undefined') return void 0; if (!params || typeof params.label_entity_id === 'undefined') return void 0;
const p = Props.entity.id, id = '' + params.label_entity_id; const p = Props.entity.id, id = '' + params.label_entity_id;
return ctx => p(ctx.element) === id; return ctx => p(ctx.element) === id;
} }
function chainTest(params: any): QueryPredicate | undefined { function chainTest(params: AtomSiteSchema): QueryPredicate | undefined {
if (!params) return void 0; if (!params) return void 0;
if (typeof params.label_asym_id !== 'undefined') { if (typeof params.label_asym_id !== 'undefined') {
...@@ -45,7 +46,7 @@ function chainTest(params: any): QueryPredicate | undefined { ...@@ -45,7 +46,7 @@ function chainTest(params: any): QueryPredicate | undefined {
return void 0; return void 0;
} }
function residueTest(params: any): QueryPredicate | undefined { function residueTest(params: AtomSiteSchema): QueryPredicate | undefined {
if (!params) return void 0; if (!params) return void 0;
const props: StructureElement.Property<any>[] = [], values: any[] = []; const props: StructureElement.Property<any>[] = [], values: any[] = [];
...@@ -78,7 +79,7 @@ function residueTest(params: any): QueryPredicate | undefined { ...@@ -78,7 +79,7 @@ function residueTest(params: any): QueryPredicate | undefined {
return andEqual(props, values); return andEqual(props, values);
} }
function atomTest(params: any): QueryPredicate | undefined { function atomTest(params: AtomSiteSchema): QueryPredicate | undefined {
if (!params) return void 0; if (!params) return void 0;
const props: StructureElement.Property<any>[] = [], values: any[] = []; const props: StructureElement.Property<any>[] = [], values: any[] = [];
......
...@@ -12,11 +12,12 @@ import { resolveJob } from './query'; ...@@ -12,11 +12,12 @@ import { resolveJob } from './query';
import { StructureCache } from './structure-wrapper'; import { StructureCache } from './structure-wrapper';
import { now } from 'mol-task'; import { now } from 'mol-task';
import { PerformanceMonitor } from 'mol-util/performance-monitor'; import { PerformanceMonitor } from 'mol-util/performance-monitor';
import { QueryName } from './api';
export type LocalInput = { export type LocalInput = {
input: string, input: string,
output: string, output: string,
query: string, query: QueryName,
modelNums?: number[], modelNums?: number[],
params?: any, params?: any,
binary?: boolean binary?: boolean
...@@ -30,10 +31,15 @@ export async function runLocal(input: LocalInput) { ...@@ -30,10 +31,15 @@ export async function runLocal(input: LocalInput) {
for (const job of input) { for (const job of input) {
const binary = /\.bcif/.test(job.output); const binary = /\.bcif/.test(job.output);
JobManager.add('_local_', job.input, job.query, job.params || { }, { JobManager.add({
entryId: job.input,
queryName: job.query,
queryParams: job.params || { },
options: {
modelNums: job.modelNums, modelNums: job.modelNums,
outputFilename: job.output, outputFilename: job.output,
binary binary
}
}); });
} }
JobManager.sort(); JobManager.sort();
......
...@@ -134,7 +134,13 @@ export function initWebApi(app: express.Express) { ...@@ -134,7 +134,13 @@ export function initWebApi(app: express.Express) {
const name = args.name; const name = args.name;
const entryId = args.id; const entryId = args.id;
const queryParams = args.params || { }; const queryParams = args.params || { };
const jobId = JobManager.add('pdb', entryId, name, queryParams, { modelNums: args.modelNums, binary: args.binary }); const jobId = JobManager.add({
sourceId: 'pdb',
entryId,
queryName: name,
queryParams,
options: { modelNums: args.modelNums, binary: args.binary }
});
responseMap.set(jobId, res); responseMap.set(jobId, res);
if (JobManager.size === 1) processNextJob(); if (JobManager.size === 1) processNextJob();
}); });
......
...@@ -24,28 +24,33 @@ export interface QueryParamInfo { ...@@ -24,28 +24,33 @@ export interface QueryParamInfo {
validation?: (v: any) => void validation?: (v: any) => void
} }
export interface QueryDefinition { export interface QueryDefinition<Params = any> {
name: string, name: string,
niceName: string, niceName: string,
exampleId: string, // default is 1cbs exampleId: string, // default is 1cbs
query: (params: any, structure: Structure) => StructureQuery, query: (params: any, structure: Structure) => StructureQuery,
description: string, description: string,
params: QueryParamInfo[], params: QueryParamInfo[],
structureTransform?: (params: any, s: Structure) => Promise<Structure> structureTransform?: (params: any, s: Structure) => Promise<Structure>,
'@params': Params
} }
// const AtomSiteParams = { export interface AtomSiteSchema {
// entity_id: <QueryParamInfo>{ name: 'entity_id', type: QueryParamType.String, description: 'Corresponds to the \'_entity.id\' or \'*.label_entity_id\' field, depending on the context.' }, label_entity_id?: string,
// label_asym_id: <QueryParamInfo>{ name: 'label_asym_id', type: QueryParamType.String, description: 'Corresponds to the \'_atom_site.label_asym_id\' field.' }, label_asym_id?: string,
// auth_asym_id: <QueryParamInfo>{ name: 'auth_asym_id', type: QueryParamType.String, exampleValue: 'A', description: 'Corresponds to the \'_atom_site.auth_asym_id\' field.' }, auth_asym_id?: string,
// label_seq_id: <QueryParamInfo>{ name: 'label_seq_id', type: QueryParamType.Integer, description: 'Residue seq. number. Corresponds to the \'_atom_site.label_seq_id\' field.' }, label_comp_id?: string,
// auth_seq_id: <QueryParamInfo>{ name: 'auth_seq_id', type: QueryParamType.Integer, exampleValue: '200', description: 'Author residue seq. number. Corresponds to the \'_atom_site.auth_seq_id\' field.' }, auth_comp_id?: string,
// label_comp_id: <QueryParamInfo>{ name: 'label_comp_id', type: QueryParamType.String, description: 'Residue name. Corresponds to the \'_atom_site.label_comp_id\' field.' }, label_seq_id?: string,
// auth_comp_id: <QueryParamInfo>{ name: 'auth_comp_id', type: QueryParamType.String, exampleValue: 'REA', description: 'Author residue name. Corresponds to the \'_atom_site.auth_comp_id\' field.' }, auth_seq_id?: string,
// pdbx_PDB_ins_code: <QueryParamInfo>{ name: 'pdbx_PDB_ins_code', type: QueryParamType.String, description: 'Corresponds to the \'_atom_site.pdbx_PDB_ins_code\' field.' }, pdbx_PDB_ins_code?: string,
// };
label_atom_id?: string,
auth_atom_id?: string,
type_symbol?: string
}
const AtomSiteTestParams: QueryParamInfo = { const AtomSiteTestParams: QueryParamInfo = {
name: 'atom_site', name: 'atom_site',
...@@ -67,15 +72,19 @@ const RadiusParam: QueryParamInfo = { ...@@ -67,15 +72,19 @@ const RadiusParam: QueryParamInfo = {
} }
}; };
const QueryMap: { [id: string]: Partial<QueryDefinition> } = { function Q<Params = any>(definition: Partial<QueryDefinition<Params>>) {
'full': { niceName: 'Full Structure', query: () => Queries.generators.all, description: 'The full structure.' }, return definition;
'atoms': { }
const QueryMap = {
'full': Q<{} | undefined>({ niceName: 'Full Structure', query: () => Queries.generators.all, description: 'The full structure.' }),
'atoms': Q<{ atom_site: AtomSiteSchema }>({
niceName: 'Atoms', niceName: 'Atoms',
description: 'Atoms satisfying the given criteria.', description: 'Atoms satisfying the given criteria.',
query: p => Queries.combinators.merge(getAtomsTests(p.atom_site).map(test => Queries.generators.atoms(test))), query: p => Queries.combinators.merge(getAtomsTests(p.atom_site).map(test => Queries.generators.atoms(test))),
params: [ AtomSiteTestParams ] params: [ AtomSiteTestParams ]
}, }),
'symmetryMates': { 'symmetryMates': Q<{ radius: number }>({
niceName: 'Symmetry Mates', niceName: 'Symmetry Mates',
description: 'Computes crystal symmetry mates within the specified radius.', description: 'Computes crystal symmetry mates within the specified radius.',
query: () => Queries.generators.all, query: () => Queries.generators.all,
...@@ -83,8 +92,8 @@ const QueryMap: { [id: string]: Partial<QueryDefinition> } = { ...@@ -83,8 +92,8 @@ const QueryMap: { [id: string]: Partial<QueryDefinition> } = {
return StructureSymmetry.builderSymmetryMates(s, p.radius).run(); return StructureSymmetry.builderSymmetryMates(s, p.radius).run();
}, },
params: [ RadiusParam ] params: [ RadiusParam ]
}, }),
'assembly': { 'assembly': Q<{ name: string }>({
niceName: 'Assembly', niceName: 'Assembly',
description: 'Computes structural assembly.', description: 'Computes structural assembly.',
query: () => Queries.generators.all, query: () => Queries.generators.all,
...@@ -98,8 +107,8 @@ const QueryMap: { [id: string]: Partial<QueryDefinition> } = { ...@@ -98,8 +107,8 @@ const QueryMap: { [id: string]: Partial<QueryDefinition> } = {
exampleValues: ['1'], exampleValues: ['1'],
description: 'Assembly name.' description: 'Assembly name.'
}] }]
}, }),
'residueInteraction': { 'residueInteraction': Q<{ atom_site: AtomSiteSchema, radius: number }>({
niceName: 'Residue Interaction', niceName: 'Residue Interaction',
description: 'Identifies all residues within the given radius from the source residue. Takes crystal symmetry into account.', description: 'Identifies all residues within the given radius from the source residue. Takes crystal symmetry into account.',
query(p) { query(p) {
...@@ -116,16 +125,19 @@ const QueryMap: { [id: string]: Partial<QueryDefinition> } = { ...@@ -116,16 +125,19 @@ const QueryMap: { [id: string]: Partial<QueryDefinition> } = {
return StructureSymmetry.builderSymmetryMates(s, p.radius).run(); return StructureSymmetry.builderSymmetryMates(s, p.radius).run();
}, },
params: [ AtomSiteTestParams, RadiusParam ] params: [ AtomSiteTestParams, RadiusParam ]
}, }),
}; };
export function getQueryByName(name: string): QueryDefinition { export type QueryName = keyof typeof QueryMap
export type QueryParams<Q extends QueryName> = Partial<(typeof QueryMap)[Q]['@params']>
export function getQueryByName(name: QueryName): QueryDefinition {
return QueryMap[name] as QueryDefinition; return QueryMap[name] as QueryDefinition;
} }
export const QueryList = (function () { export const QueryList = (function () {
const list: { name: string, definition: QueryDefinition }[] = []; const list: { name: string, definition: QueryDefinition }[] = [];
for (const k of Object.keys(QueryMap)) list.push({ name: k, definition: <QueryDefinition>QueryMap[k] }); for (const k of Object.keys(QueryMap)) list.push({ name: k, definition: <QueryDefinition>QueryMap[k as QueryName] });
list.sort(function (a, b) { return a.name < b.name ? -1 : a.name > b.name ? 1 : 0 }); list.sort(function (a, b) { return a.name < b.name ? -1 : a.name > b.name ? 1 : 0 });
return list; return list;
})(); })();
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
*/ */
import { UUID } from 'mol-util'; import { UUID } from 'mol-util';
import { getQueryByName, normalizeQueryParams, QueryDefinition } from './api'; import { getQueryByName, normalizeQueryParams, QueryDefinition, QueryName, QueryParams } from './api';
import { LinkedList } from 'mol-data/generic'; import { LinkedList } from 'mol-data/generic';
export interface ResponseFormat { export interface ResponseFormat {
...@@ -28,24 +28,31 @@ export interface Job { ...@@ -28,24 +28,31 @@ export interface Job {
outputFilename?: string outputFilename?: string
} }
export function createJob(sourceId: '_local_' | string, entryId: string, queryName: string, queryParams: any, export interface JobDefinition<Name extends QueryName> {
options ?: { modelNums?: number[], outputFilename?: string, binary?: boolean }): Job { sourceId?: string, // = '_local_',
const queryDefinition = getQueryByName(queryName); entryId: string,
if (!queryDefinition) throw new Error(`Query '${queryName}' is not supported.`); queryName: Name,
queryParams: QueryParams<Name>,
options?: { modelNums?: number[], outputFilename?: string, binary?: boolean }
}
export function createJob<Name extends QueryName>(definition: JobDefinition<Name>): Job {
const queryDefinition = getQueryByName(definition.queryName);
if (!queryDefinition) throw new Error(`Query '${definition.queryName}' is not supported.`);
const normalizedParams = normalizeQueryParams(queryDefinition, queryParams); const normalizedParams = normalizeQueryParams(queryDefinition, definition.queryParams);
return { return {
id: UUID.create(), id: UUID.create(),
datetime_utc: `${new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')}`, datetime_utc: `${new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')}`,
key: `${sourceId}/${entryId}`, key: `${definition.sourceId}/${definition.entryId}`,
sourceId, sourceId: definition.sourceId || '_local_',
entryId, entryId: definition.entryId,
queryDefinition, queryDefinition,
normalizedParams, normalizedParams,
responseFormat: { isBinary: !!(options && options.binary) }, responseFormat: { isBinary: !!(definition.options && definition.options.binary) },
modelNums: options && options.modelNums, modelNums: definition.options && definition.options.modelNums,
outputFilename: options && options.outputFilename outputFilename: definition.options && definition.options.outputFilename
}; };
} }
...@@ -56,9 +63,8 @@ class _JobQueue { ...@@ -56,9 +63,8 @@ class _JobQueue {
return this.list.count; return this.list.count;
} }
add(sourceId: '_local_' | string, entryId: string, queryName: string, queryParams: any, add<Name extends QueryName>(definition: JobDefinition<Name>) {
options?: { modelNums?: number[], outputFilename?: string, binary?: boolean }) { const job = createJob(definition);
const job = createJob(sourceId, entryId, queryName, queryParams, options);
this.list.addLast(job); this.list.addLast(job);
return job.id; return job.id;
} }
......
...@@ -41,12 +41,20 @@ if (!fs.existsSync(outPath)) fs.mkdirSync(outPath); ...@@ -41,12 +41,20 @@ if (!fs.existsSync(outPath)) fs.mkdirSync(outPath);
async function run() { async function run() {
try { try {
// const request = createJob('_local_', 'e:/test/quick/1cbs_updated.cif', 'residueInteraction', { label_comp_id: 'REA' });
// const encoder = await resolveJob(request);
// const writer = wrapFile('e:/test/mol-star/1cbs_full.cif');
// const testFile = '1crn.cif' // const testFile = '1crn.cif'
const testFile = '1grm_updated.cif' const testFile = '1grm_updated.cif'
const request = createJob('_local_', path.join(examplesPath, testFile), 'full', {}); // const request = createJob({
// entryId: path.join(examplesPath, testFile),
// queryName: 'full',
// queryParams: { }
// });
const request = createJob({
entryId: path.join(examplesPath, testFile),
queryName: 'atoms',
queryParams: {
atom_site: { label_comp_id: 'ALA' }
}
});
const encoder = await resolveJob(request); const encoder = await resolveJob(request);
const writer = wrapFile(path.join(outPath, testFile)); const writer = wrapFile(path.join(outPath, testFile));
encoder.writeTo(writer); encoder.writeTo(writer);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment