Newer
Older
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { AssemblySymmetry as AssemblySymmetryGraphQL } from './graphql/types';
import query from './graphql/symmetry.gql';
import { Model, ModelPropertyDescriptor } from 'mol-model/structure';
import { CifWriter } from 'mol-io/writer/cif';
import { Database as _Database, Column, Table } from 'mol-data/db'
import { Category } from 'mol-io/writer/cif/encoder';
import { Tensor } from 'mol-math/linear-algebra';
import { CifExportContext } from 'mol-model/structure/export/mmcif';
import { toTable } from 'mol-io/reader/cif/schema';
import { CifCategory } from 'mol-io/reader/cif';
import { PropertyWrapper } from 'mol-model-props/common/wrapper';
import { Task, RuntimeContext } from 'mol-task';
import { GraphQLClient } from 'mol-util/graphql-client';
import { ajaxGet } from 'mol-util/data-source';
const { str, int, float, Aliased, Vector, List } = Column.Schema;
function getInstance(name: keyof AssemblySymmetry.Schema): (ctx: CifExportContext) => CifWriter.Category.Instance<any, any> {
return function(ctx: CifExportContext) {
const assemblySymmetry = AssemblySymmetry.get(ctx.structures[0].model);
return assemblySymmetry ? Category.ofTable(assemblySymmetry.db[name]) : CifWriter.Category.Empty;
function getCategory(name: keyof AssemblySymmetry.Schema) {
return { name, instance: getInstance(name) }
}
function createDatabaseFromJson(assemblies: ReadonlyArray<AssemblySymmetryGraphQL.Assemblies>): AssemblySymmetry.Database {
const featureRows: Table.Row<typeof Schema.rcsb_assembly_symmetry>[] = []
const clusterRows: Table.Row<typeof Schema.rcsb_assembly_symmetry_cluster>[] = []
const clusterMemberRows: Table.Row<typeof Schema.rcsb_assembly_symmetry_cluster_member>[] = []
const axisRows: Table.Row<typeof Schema.rcsb_assembly_symmetry_axis>[] = []
let clusterCount = 0
for (let i = 0, il = assemblies.length; i < il; ++i) {
const { pdbx_struct_assembly, rcsb_struct_symmetry } = assemblies[i]
if (!pdbx_struct_assembly || !rcsb_struct_symmetry) continue
const assembly_id = pdbx_struct_assembly.id
for (let j = 0, jl = rcsb_struct_symmetry.length; j < jl; ++j) {
const rss = rcsb_struct_symmetry[j]! // TODO upstream, array members should not be nullable
featureRows.push({
id,
assembly_id,
type: rss.type as SymmetryType,
stoichiometry: rss.stoichiometry as string[], // TODO upstream, array members should not be nullable
kind: rss.kind as SymmetryKind,
symbol: rss.symbol,
oligomeric_state: rss.oligomeric_state
if (clusters) {
for (let k = 0, kl = clusters.length; k < kl; ++k) {
const c = clusters[k]! // TODO upstream, array members should not be nullable
const cluster_id = clusterCount + k
id: cluster_id,
avg_rmsd: c.avg_rmsd || 0, // TODO upstream, should not be nullable, or???
for (let l = 0, ll = c.members.length; l < ll; ++l) {
const m = c.members[l]! // TODO upstream, array members should not be nullable
clusterMemberRows.push({
cluster_id: cluster_id,
asym_id: m.asym_id,
pdbx_struct_oper_list_ids: (m.pdbx_struct_oper_list_ids || []) as string[]
})
}
clusterCount += clusters.length
if (axes) {
for (let k = 0, kl = axes.length; k < kl; ++k) {
const a = axes[k]!
axisRows.push({
order: a.order!, // TODO upstream, should not be nullable, or???
start: a.start as Tensor.Data, // TODO upstream, array members should not be nullable
end: a.end as Tensor.Data // TODO upstream, array members should not be nullable
})
}
}
id += 1
}
}
return _Database.ofTables('assembly_symmetry', Schema, {
rcsb_assembly_symmetry: Table.ofRows(Schema.rcsb_assembly_symmetry, featureRows),
rcsb_assembly_symmetry_cluster: Table.ofRows(Schema.rcsb_assembly_symmetry_cluster, clusterRows),
rcsb_assembly_symmetry_cluster_member: Table.ofRows(Schema.rcsb_assembly_symmetry_cluster_member, clusterMemberRows),
rcsb_assembly_symmetry_axis: Table.ofRows(Schema.rcsb_assembly_symmetry_axis, axisRows)
function createDatabaseFromCif(model: Model): AssemblySymmetry.Database {
const Schema = AssemblySymmetry.Schema
const rcsb_assembly_symmetry = toTable(Schema.rcsb_assembly_symmetry, model.sourceData.frame.categories.rcsb_assembly_symmetry_feature)
let rcsb_assembly_symmetry_cluster
if (model.sourceData.frame.categoryNames.includes('rcsb_assembly_symmetry_cluster')) {
rcsb_assembly_symmetry_cluster = toTable(Schema.rcsb_assembly_symmetry_cluster, model.sourceData.frame.categories.rcsb_assembly_symmetry_cluster)
} else {
rcsb_assembly_symmetry_cluster = toTable(Schema.rcsb_assembly_symmetry_cluster, CifCategory.empty as any)
}
let rcsb_assembly_symmetry_cluster_member
if (model.sourceData.frame.categoryNames.includes('rcsb_assembly_symmetry_cluster_member')) {
rcsb_assembly_symmetry_cluster_member = toTable(Schema.rcsb_assembly_symmetry_cluster_member, model.sourceData.frame.categories.rcsb_assembly_symmetry_cluster_member)
} else {
rcsb_assembly_symmetry_cluster_member = toTable(Schema.rcsb_assembly_symmetry_cluster_member, CifCategory.empty as any)
}
let rcsb_assembly_symmetry_axis
if (model.sourceData.frame.categoryNames.includes('rcsb_assembly_symmetry_axis')) {
rcsb_assembly_symmetry_axis = toTable(Schema.rcsb_assembly_symmetry_axis, model.sourceData.frame.categories.rcsb_assembly_symmetry_axis)
} else {
rcsb_assembly_symmetry_axis = toTable(Schema.rcsb_assembly_symmetry_axis, CifCategory.empty as any)
}
return _Database.ofTables('rcsb_assembly_symmetry', Schema, {
rcsb_assembly_symmetry,
rcsb_assembly_symmetry_cluster,
rcsb_assembly_symmetry_cluster_member,
rcsb_assembly_symmetry_axis
})
}
const _Descriptor: ModelPropertyDescriptor = {
isStatic: true,
prefix: 'rcsb',
PropertyWrapper.defaultInfoCategory<CifExportContext>('rcsb_assembly_symmetry_info', ctx => PropertyWrapper.createInfo()),
getCategory('rcsb_assembly_symmetry'),
getCategory('rcsb_assembly_symmetry_cluster'),
getCategory('rcsb_assembly_symmetry_cluster_member'),
getCategory('rcsb_assembly_symmetry_axis')
export interface AssemblySymmetry {
getSymmetries(assemblyIds: string[]): Table<AssemblySymmetry.Schema['rcsb_assembly_symmetry']>
getClusters(symmetryId: number): Table<AssemblySymmetry.Schema['rcsb_assembly_symmetry_cluster']>
getClusterMembers(clusterId: number): Table<AssemblySymmetry.Schema['rcsb_assembly_symmetry_cluster_member']>
getAxes(symmetryId: number): Table<AssemblySymmetry.Schema['rcsb_assembly_symmetry_axis']>
}
export function AssemblySymmetry(db: AssemblySymmetry.Database): AssemblySymmetry {
const c = db.rcsb_assembly_symmetry_cluster
const cm = db.rcsb_assembly_symmetry_cluster_member
const a = db.rcsb_assembly_symmetry_axis
return {
getSymmetries: (assemblyIds: string[]) => Table.pick(f, f._schema, i => assemblyIds.includes(f.assembly_id.value(i))),
getClusters: (symmetryId: number) => Table.pick(c, c._schema, i => c.symmetry_id.value(i) === symmetryId),
getClusterMembers: (clusterId: number) => Table.pick(cm, cm._schema, i => cm.cluster_id.value(i) === clusterId),
getAxes: (symmetryId: number) => Table.pick(a, a._schema, i => a.symmetry_id.value(i) === symmetryId),
type SymmetryKind = 'GLOBAL' | 'LOCAL' | 'PSEUDO'
type SymmetryType = 'ASYMMETRIC' | 'CYCLIC' | 'DIHEDRAL' | 'HELICAL' | 'ICOSAHEDRAL' | 'OCTAHEDRAL' | 'TETRAHEDRAL'
const Client = new GraphQLClient(AssemblySymmetry.GraphQLEndpointURL, ajaxGet)
export function is(x: any): x is AssemblySymmetry {
return x['@type'] === 'rcsb_assembly_symmetry'
}
export const GraphQLEndpointURL = '//rest-dev.rcsb.org/graphql'
export const Schema = {
rcsb_assembly_symmetry_info: {
updated_datetime_utc: Column.Schema.str
},
rcsb_assembly_symmetry: {
/** Uniquely identifies a record in `rcsb_assembly_symmetry` */
/**
* A pointer to `pdbx_struct_assembly.id`.
* The value 'deposited' refers to the coordinates as given in the file.
* */
kind: Aliased<SymmetryKind>(str),
/** Quantitative description of every individual subunit in a given protein */
stoichiometry: List(',', x => x),
/**
* Symmetry symbol refers to point group or helical symmetry of identical subunits.
* Contains point group symbol (e.g., C2, C5, D2, T, O, I) or H for helical symmetry.
*/
symbol: str,
/** Point group or helical symmetry */
type: Aliased<SymmetryType>(str),
/**
* Oligomeric state refers to a composition of subunits in quaternary structure.
* Quaternary structure may be composed either exclusively of several copies of identical
* subunits, in which case they are termed homo-oligomers, or alternatively by at least
* one copy of different subunits (hetero-oligomers). Quaternary structure composed of
* a single subunit is demoted as 'Monomer'.
*/
oligomeric_state: str,
/** Uniquely identifies a record in `rcsb_assembly_symmetry_cluster` */
id: int,
/** A pointer to `rcsb_assembly_symmetry.id` */
symmetry_id: int,
/** Average RMSD between members of a given cluster */
avg_rmsd: float
},
rcsb_assembly_symmetry_cluster_member: {
/** A pointer to `rcsb_assembly_symmetry_cluster.id` */
cluster_id: int,
/** The `label_asym_id` value of the member */
asym_id: str,
/** List of `pdbx_struct_oper_list_id` values of the member */
pdbx_struct_oper_list_ids: List(',', x => x)
/** A pointer to `rcsb_assembly_symmetry.id` */
symmetry_id: int,
/**
* The number of times (order of rotation) that a subunit can be repeated by a rotation
* operation, being transformed into a new state indistinguishable from its starting state.
*/
/** The x,y,z coordinate of the start point of a symmetry axis */
/** The x,y,z coordinate of the end point of a symmetry axis */
end: Vector(3)
}
}
export type Schema = typeof Schema
export interface Database extends _Database<Schema> {}
export const Descriptor = _Descriptor;
export async function attachFromCifOrAPI(model: Model, client: GraphQLClient = Client, ctx?: RuntimeContext) {
if (model.customProperties.has(Descriptor)) return true;
let info = PropertyWrapper.tryGetInfoFromCif('rcsb_assembly_symmetry_info', model);
if (info) {
db = createDatabaseFromCif(model)
} else {
let result: AssemblySymmetryGraphQL.Query
const variables: AssemblySymmetryGraphQL.Variables = { pdbId: model.label.toLowerCase() };
try {
result = await client.request<AssemblySymmetryGraphQL.Query>(ctx || RuntimeContext.Synchronous, query, variables);
} catch (e) {
console.error(e)
return false;
}
if (!result || !result.assemblies) return false;
db = createDatabaseFromJson(result.assemblies as ReadonlyArray<AssemblySymmetryGraphQL.Assemblies>)
model.customProperties.add(Descriptor);
model._staticPropertyData.__RCSBAssemblySymmetry__ = AssemblySymmetry(db);
return true;
}
export function createAttachTask(fetch: import('mol-util/data-source').AjaxTask) {
return (model: Model) => Task.create('RCSB Assembly Symmetry', async ctx => {
if (get(model)) return true;
return await attachFromCifOrAPI(model, new GraphQLClient(AssemblySymmetry.GraphQLEndpointURL, fetch), ctx)
});
}
export function get(model: Model): AssemblySymmetry | undefined {
return model._staticPropertyData.__RCSBAssemblySymmetry__;