Newer
Older
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { GraphQLClient } from 'graphql-request'
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';
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.model);
return assemblySymmetry ? Category.ofTable(assemblySymmetry.db[name]) : CifWriter.Category.Empty;
function getCategory(name: keyof AssemblySymmetry.Schema) {
return { name, instance: getInstance(name) }
}
function createDatabase(assemblies: ReadonlyArray<AssemblySymmetryGraphQL.Assemblies>): AssemblySymmetry.Database {
const Schema = AssemblySymmetry.Schema
const featureRows: Table.Row<typeof Schema.rcsb_assembly_symmetry_feature>[] = []
const clusterRows: Table.Row<typeof Schema.rcsb_assembly_symmetry_cluster>[] = []
const axisRows: Table.Row<typeof Schema.rcsb_assembly_symmetry_axis>[] = []
for (let i = 0, il = assemblies.length; i < il; ++i) {
const { assembly_id: _assembly_id, rcsb_assembly_symmetry } = assemblies[i]
if (!rcsb_assembly_symmetry) continue
const assembly_id = _assembly_id.toString() // TODO type will be fixed to string upstream
const source = rcsb_assembly_symmetry.source
const symmetry_features = rcsb_assembly_symmetry.symmetry_features
for (let j = 0, jl = symmetry_features.length; j < jl; ++j) {
const f = symmetry_features[j]! // TODO upstream, array members should not be nullable
featureRows.push({
id,
assembly_id,
source,
type: f.type,
stoichiometry_value: f.stoichiometry.value as string[], // TODO upstream, array members should not be nullable
stoichiometry_description: f.stoichiometry.description,
symmetry_value: f.symmetry.value,
symmetry_description: f.symmetry.description
})
const clusters = f.clusters
if (clusters) {
for (let k = 0, kl = clusters.length; k < kl; ++k) {
const c = clusters[k]! // TODO upstream, array members should not be nullable
clusterRows.push({
feature_id: id,
avg_rmsd: c.avg_rmsd || 0, // TODO upstream, should not be nullable, or???
members: c.members as string[] // TODO upstream, array members should not be nullable
})
}
}
const axes = f.symmetry_axes
if (axes) {
for (let k = 0, kl = axes.length; k < kl; ++k) {
const a = axes[k]!
axisRows.push({
feature_id: id,
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_feature: Table.ofRows(Schema.rcsb_assembly_symmetry_feature, featureRows),
rcsb_assembly_symmetry_cluster: Table.ofRows(Schema.rcsb_assembly_symmetry_cluster, clusterRows),
rcsb_assembly_symmetry_axis: Table.ofRows(Schema.rcsb_assembly_symmetry_axis, axisRows)
})
}
const _Descriptor: ModelPropertyDescriptor = {
isStatic: true,
prefix: 'rcsb',
getCategory('rcsb_assembly_symmetry_feature'),
getCategory('rcsb_assembly_symmetry_cluster'),
getCategory('rcsb_assembly_symmetry_axis')
]
}
}
const client = new GraphQLClient('http://rest-experimental.rcsb.org/graphql')
export interface AssemblySymmetry {
db: AssemblySymmetry.Database
getFeatures(assemblyId: string): Table<AssemblySymmetry.Schema['rcsb_assembly_symmetry_feature']>
getClusters(featureId: number): Table<AssemblySymmetry.Schema['rcsb_assembly_symmetry_cluster']>
getAxes(featureId: number): Table<AssemblySymmetry.Schema['rcsb_assembly_symmetry_axis']>
}
export function AssemblySymmetry(db: AssemblySymmetry.Database): AssemblySymmetry {
const f = db.rcsb_assembly_symmetry_feature
const c = db.rcsb_assembly_symmetry_cluster
const a = db.rcsb_assembly_symmetry_axis
return {
db,
getFeatures: (assemblyId: string) => Table.pick(f, f._schema, i => f.assembly_id.value(i) === assemblyId),
getClusters: (featureId: number) => Table.pick(c, c._schema, i => c.feature_id.value(i) === featureId),
getAxes: (featureId: number) => Table.pick(a, a._schema, i => a.feature_id.value(i) === featureId)
}
}
export const Schema = {
/** Uniquely identifies a record in `rcsb_assembly_symmetry_feature` */
/** A pointer to `pdbx_struct_assembly.id` */
/** Name and version of software used to calculate protein symmetry */
type: Aliased<'GLOBAL' | 'LOCAL' | 'PSEUDO'>(str),
/** Quantitative description of every individual subunit in a given protein */
stoichiometry_value: List(',', x => x),
/** Oligomeric state for a given composition of subunits */
stoichiometry_description: str,
/** Point group symmetry value */
symmetry_value: str,
/** Point group symmetry description */
symmetry_description: str
/** A pointer to `rcsb_assembly_symmetry_feature.id` */
/** Average RMSD between members of a given cluster */
/** List of `auth_label_id` values */
members: List(',', x => x)
},
/** A pointer to `rcsb_assembly_symmetry_feature.id` */
/** The order of the symmetry axis */
/** 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) {
if (model.customProperties.has(Descriptor)) return true;
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
let db: Database
if (model.sourceData.kind === 'mmCIF' && model.sourceData.frame.categoryNames.includes('rcsb_assembly_symmetry_feature')) {
const rcsb_assembly_symmetry_feature = toTable(Schema.rcsb_assembly_symmetry_feature, 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 = CifCategory.empty
}
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 = CifCategory.empty
}
db = _Database.ofTables('assembly_symmetry', Schema, {
rcsb_assembly_symmetry_feature,
rcsb_assembly_symmetry_cluster,
rcsb_assembly_symmetry_axis
})
} else {
let result: AssemblySymmetryGraphQL.Query
const variables: AssemblySymmetryGraphQL.Variables = { pdbId: model.label.toLowerCase() };
try {
result = await client.request<AssemblySymmetryGraphQL.Query>(query, variables);
} catch (e) {
console.error(e)
return false;
}
if (!result || !result.assemblies) return false;
db = createDatabase(result.assemblies as ReadonlyArray<AssemblySymmetryGraphQL.Assemblies>)
}
model.customProperties.add(Descriptor);
model._staticPropertyData.__AssemblySymmetry__ = AssemblySymmetry(db);
return true;
}
export function get(model: Model): AssemblySymmetry | undefined {
return model._staticPropertyData.__AssemblySymmetry__;