Skip to content
Snippets Groups Projects
Commit b99612c0 authored by Alexander Rose's avatar Alexander Rose
Browse files

Merge branch 'master' into bond-repr

parents d549b1c4 a2020705
No related branches found
No related tags found
No related merge requests found
......@@ -18,6 +18,7 @@ import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif';
import { openCif, downloadCif } from './helpers';
import { BitFlags } from 'mol-util';
import { SecondaryStructureType } from 'mol-model/structure/model/types';
import { UnitRings } from 'mol-model/structure/structure/unit/rings';
async function downloadFromPdb(pdb: string) {
......@@ -100,6 +101,21 @@ export function printSequence(model: Model) {
console.log();
}
export function printRings(structure: Structure) {
console.log('\nRings\n=============');
for (const unit of structure.units) {
if (!Unit.isAtomic(unit)) continue;
const { all, byFingerprint } = unit.rings;
const fps: string[] = [];
for (let i = 0, _i = Math.min(5, all.length); i < _i; i++) {
fps[fps.length] = UnitRings.getRingFingerprint(unit, all[i]);
}
if (all.length > 5) fps.push('...')
console.log(`Unit ${unit.id}, ${all.length} ring(s), ${byFingerprint.size} different fingerprint(s).\n ${fps.join(', ')}`);
}
console.log();
}
export function printUnits(structure: Structure) {
console.log('\nUnits\n=============');
const l = Element.Location();
......@@ -143,6 +159,7 @@ async function run(mmcif: mmCIF_Database) {
printSequence(models[0]);
//printIHMModels(models[0]);
printUnits(structure);
printRings(structure);
//printBonds(structure);
//printSecStructure(models[0]);
}
......
......@@ -21,9 +21,10 @@ import { ArrayEncoder, ArrayEncoding } from '../../common/binary-cif';
export interface Field<Key = any, Data = any> {
name: string,
type: Field.Type,
value(key: Key, data: Data): string | number
valueKind?: (key: Key, data: Data) => Column.ValueKind,
defaultFormat?: Field.Format,
value(key: Key, data: Data): string | number
shouldInclude?: (data: Data) => boolean
}
export namespace Field {
......@@ -35,31 +36,31 @@ export namespace Field {
typedArray?: ArrayEncoding.TypedArrayCtor
}
export function getDigitCount(field: Field) {
if (field.defaultFormat && typeof field.defaultFormat.digitCount !== 'undefined') return Math.max(0, Math.min(field.defaultFormat.digitCount, 16));
return 6;
}
export type ParamsBase<K, D> = { valueKind?: (k: K, d: D) => Column.ValueKind, encoder?: ArrayEncoder, shouldInclude?: (data: D) => boolean }
export function str<K, D = any>(name: string, value: (k: K, d: D) => string, params?: { valueKind?: (k: K, d: D) => Column.ValueKind, encoder?: ArrayEncoder }): Field<K, D> {
return { name, type: Type.Str, value, valueKind: params && params.valueKind, defaultFormat: params && params.encoder ? { encoder: params.encoder } : void 0 };
export function str<K, D = any>(name: string, value: (k: K, d: D) => string, params?: ParamsBase<K, D>): Field<K, D> {
return { name, type: Type.Str, value, valueKind: params && params.valueKind, defaultFormat: params && params.encoder ? { encoder: params.encoder } : void 0, shouldInclude: params && params.shouldInclude };
}
export function int<K, D = any>(name: string, value: (k: K, d: D) => number, params?: { valueKind?: (k: K, d: D) => Column.ValueKind, encoder?: ArrayEncoder, typedArray?: ArrayEncoding.TypedArrayCtor }): Field<K, D> {
export function int<K, D = any>(name: string, value: (k: K, d: D) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }): Field<K, D> {
return {
name,
type: Type.Int,
value,
valueKind: params && params.valueKind,
defaultFormat: params ? { encoder: params.encoder, typedArray: params.typedArray } : void 0 };
defaultFormat: params ? { encoder: params.encoder, typedArray: params.typedArray } : void 0,
shouldInclude: params && params.shouldInclude
};
}
export function float<K, D = any>(name: string, value: (k: K, d: D) => number, params?: { valueKind?: (k: K, d: D) => Column.ValueKind, encoder?: ArrayEncoder, typedArray?: ArrayEncoding.TypedArrayCtor, digitCount?: number }): Field<K, D> {
export function float<K, D = any>(name: string, value: (k: K, d: D) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor, digitCount?: number }): Field<K, D> {
return {
name,
type: Type.Float,
value,
valueKind: params && params.valueKind,
defaultFormat: params ? { encoder: params.encoder, typedArray: params.typedArray, digitCount: typeof params.digitCount !== 'undefined' ? params.digitCount : void 0 } : void 0
defaultFormat: params ? { encoder: params.encoder, typedArray: params.typedArray, digitCount: typeof params.digitCount !== 'undefined' ? params.digitCount : void 0 } : void 0,
shouldInclude: params && params.shouldInclude
};
}
}
......
......@@ -14,6 +14,7 @@ import {
} from '../../../common/binary-cif'
import { Field, Category, Encoder } from '../encoder'
import Writer from '../../writer'
import { getIncludedFields } from './util';
export default class BinaryEncoder implements Encoder<Uint8Array> {
private data: EncodedFile;
......@@ -57,12 +58,17 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
const first = categories[0]!;
const cat: EncodedCategory = { name: '_' + first.name, columns: [], rowCount: count };
const data = categories.map(c => ({ data: c.data, keys: () => c.keys ? c.keys() : Iterator.Range(0, c.rowCount - 1) }));
for (const f of first.fields) {
const fields = getIncludedFields(first);
for (const f of fields) {
if (!this.filter.includeField(first.name, f.name)) continue;
const format = this.formatter.getFormat(first.name, f.name);
cat.columns.push(encodeField(f, data, count, getArrayCtor(f, format), getEncoder(f, format)));
}
// no columns included.
if (!cat.columns.length) return;
this.dataBlocks[this.dataBlocks.length - 1].categories.push(cat);
}
......
......@@ -11,6 +11,7 @@ import { Column } from 'mol-data/db'
import StringBuilder from 'mol-util/string-builder'
import { Category, Field, Encoder } from '../encoder'
import Writer from '../../writer'
import { getFieldDigitCount, getIncludedFields } from './util';
export default class TextEncoder implements Encoder<string> {
private builder = StringBuilder.create();
......@@ -102,13 +103,13 @@ function getFloatPrecisions(categoryName: string, fields: Field[], formatter: Ca
for (const f of fields) {
const format = formatter.getFormat(categoryName, f.name);
if (format && typeof format.digitCount !== 'undefined') ret[ret.length] = f.type === Field.Type.Float ? Math.pow(10, Math.max(0, Math.min(format.digitCount, 15))) : 0;
else ret[ret.length] = f.type === Field.Type.Float ? Math.pow(10, Field.getDigitCount(f)) : 0;
else ret[ret.length] = f.type === Field.Type.Float ? Math.pow(10, getFieldDigitCount(f)) : 0;
}
return ret;
}
function writeCifSingleRecord(category: Category<any>, builder: StringBuilder, filter: Category.Filter, formatter: Category.Formatter) {
const fields = category.fields;
const fields = getIncludedFields(category);
const data = category.data;
let width = fields.reduce((w, f) => filter.includeField(category.name, f.name) ? Math.max(w, f.name.length) : 0, 0);
......@@ -134,8 +135,11 @@ function writeCifSingleRecord(category: Category<any>, builder: StringBuilder, f
function writeCifLoop(categories: Category[], builder: StringBuilder, filter: Category.Filter, formatter: Category.Formatter) {
const first = categories[0];
const fields = filter === Category.DefaultFilter ? first.fields : first.fields.filter(f => filter.includeField(first.name, f.name));
const fieldSource = getIncludedFields(first);
const fields = filter === Category.DefaultFilter ? fieldSource : fieldSource.filter(f => filter.includeField(first.name, f.name));
const fieldCount = fields.length;
if (fieldCount === 0) return;
const precisions = getFloatPrecisions(first.name, fields, formatter);
writeLine(builder, 'loop_');
......
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Field, Category } from '../encoder';
export function getFieldDigitCount(field: Field) {
if (field.defaultFormat && typeof field.defaultFormat.digitCount !== 'undefined') return Math.max(0, Math.min(field.defaultFormat.digitCount, 16));
return 6;
}
export function getIncludedFields(category: Category<any, any>) {
return category.fields.some(f => !!f.shouldInclude)
? category.fields.filter(f => !f.shouldInclude || f.shouldInclude(category.data))
: category.fields;
}
\ No newline at end of file
......@@ -18,7 +18,7 @@ interface SymmetryOperator {
}
namespace SymmetryOperator {
export const DefaultName = 'identity'
export const DefaultName = '1_555'
export const Default: SymmetryOperator = create(DefaultName, Mat4.identity());
const RotationEpsilon = 0.0001;
......
......@@ -47,7 +47,9 @@ const atom_site_fields: CifField<Element.Location>[] = [
CifField.str('auth_asym_id', P.chain.auth_asym_id),
CifField.int('pdbx_PDB_model_num', P.unit.model_num, { encoder: E.deltaRLE }),
CifField.str('operator_name', P.unit.operator_name)
CifField.str<Element.Location, Structure>('operator_name', P.unit.operator_name, {
shouldInclude: structure => structure.units.some(u => !u.conformation.operator.isIdentity)
})
];
function copy_mmCif_cat(name: keyof mmCIF_Schema) {
......@@ -66,7 +68,7 @@ function _entity({ model, structure }: Context): CifCategory {
function _atom_site({ structure }: Context): CifCategory {
return {
data: void 0,
data: structure,
name: 'atom_site',
fields: atom_site_fields,
rowCount: structure.elementCount,
......
......@@ -12,6 +12,7 @@ import { idFactory } from 'mol-util/id-factory';
import { IntraUnitBonds, computeIntraUnitBonds } from './unit/bonds'
import { CoarseElements, CoarseSphereConformation, CoarseGaussianConformation } from '../model/properties/coarse';
import { ValueRef } from 'mol-util';
import { UnitRings } from './unit/rings';
// A building block of a structure that corresponds to an atomic or a coarse grained representation
// 'conveniently grouped together'.
......@@ -105,6 +106,12 @@ namespace Unit {
return this.props.bonds.ref;
}
get rings() {
if (this.props.rings.ref) return this.props.rings.ref;
this.props.rings.ref = UnitRings.create(this);
return this.props.rings.ref;
}
constructor(id: number, invariantId: number, model: Model, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping, props: AtomicProperties) {
this.id = id;
this.invariantId = invariantId;
......@@ -121,10 +128,11 @@ namespace Unit {
interface AtomicProperties {
lookup3d: ValueRef<Lookup3D | undefined>,
bonds: ValueRef<IntraUnitBonds | undefined>,
rings: ValueRef<UnitRings | undefined>
}
function AtomicProperties() {
return { lookup3d: ValueRef.create(void 0), bonds: ValueRef.create(void 0) };
return { lookup3d: ValueRef.create(void 0), bonds: ValueRef.create(void 0), rings: ValueRef.create(void 0) };
}
class Coarse<K extends Kind.Gaussians | Kind.Spheres, C extends CoarseSphereConformation | CoarseGaussianConformation> implements Base {
......
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import computeRings from './rings/compute'
import Unit from '../unit';
interface UnitRings {
/** Each ring is specified as an array of indices in Unit.elements. */
readonly all: ReadonlyArray<ReadonlyArray<number>>,
readonly byFingerprint: Map<string, ReadonlyArray<number>>
}
namespace UnitRings {
export function getRingFingerprint(unit: Unit.Atomic, ring: ArrayLike<number>) {
const { elements } = unit;
const { type_symbol } = unit.model.atomicHierarchy.atoms;
const symbols: string[] = [];
for (let i = 0, _i = ring.length; i < _i; i++) symbols[symbols.length] = type_symbol.value(elements[ring[i]]) as String as string;
return getFingerprint(symbols);
}
export function create(unit: Unit.Atomic): UnitRings {
const rings = computeRings(unit);
const byFingerprint = new Map<string, number[]>();
let idx = 0;
for (const r of rings) {
const fp = getRingFingerprint(unit, r);
if (byFingerprint.has(fp)) byFingerprint.get(fp)!.push(idx);
else byFingerprint.set(fp, [idx]);
idx++;
}
return { all: rings, byFingerprint };
}
}
export { UnitRings }
function getFingerprint(elements: string[]) {
const len = elements.length;
const reversed: string[] = new Array(len);
for (let i = 0; i < len; i++) reversed[i] = elements[len - i - 1];
const rotNormal = getMinimalRotation(elements);
const rotReversed = getMinimalRotation(reversed);
let isNormalSmaller = false;
for (let i = 0; i < len; i++) {
const u = elements[(i + rotNormal) % len], v = reversed[(i + rotReversed) % len];
if (u !== v) {
isNormalSmaller = u < v;
break;
}
}
if (isNormalSmaller) return buildFinderprint(elements, rotNormal);
return buildFinderprint(reversed, rotReversed);
}
function getMinimalRotation(elements: string[]) {
// adapted from http://en.wikipedia.org/wiki/Lexicographically_minimal_string_rotation
const len = elements.length;
const f = new Int32Array(len * 2);
for (let i = 0; i < f.length; i++) f[i] = -1;
let u = '', v = '', k = 0;
for (let j = 1; j < f.length; j++) {
let i = f[j - k - 1];
while (i !== -1) {
u = elements[j % len]; v = elements[(k + i + 1) % len];
if (u === v) break;
if (u < v) k = j - i - 1;
i = f[i];
}
if (i === -1) {
u = elements[j % len]; v = elements[(k + i + 1) % len];
if (u !== v) {
if (u < v) k = j;
f[j - k] = -1;
} else f[j - k] = i + 1;
} else f[j - k] = i + 1;
}
return k;
}
function buildFinderprint(elements: string[], offset: number) {
const len = elements.length;
const ret: string[] = [];
let i;
for (i = 0; i < len - 1; i++) {
ret.push(elements[(i + offset) % len]);
ret.push('-');
}
ret.push(elements[(i + offset) % len]);
return ret.join('');
}
\ No newline at end of file
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Unit from '../../unit';
import { IntraUnitBonds } from '../bonds/intra-data';
import { Segmentation } from 'mol-data/int';
export default function computeRings(unit: Unit.Atomic) {
const size = largestResidue(unit);
const state = State(unit, size);
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueSegments, unit.elements);
while (residuesIt.hasNext) {
const seg = residuesIt.move();
processResidue(state, seg.start, seg.end);
}
return state.rings;
}
const enum Constants {
MaxDepth = 4
}
interface State {
startVertex: number,
endVertex: number,
count: number,
visited: Int32Array,
queue: Int32Array,
color: Int32Array,
pred: Int32Array,
left: Int32Array,
right: Int32Array,
currentColor: number,
rings: number[][],
bonds: IntraUnitBonds,
unit: Unit.Atomic
}
function State(unit: Unit.Atomic, capacity: number): State {
return {
startVertex: 0,
endVertex: 0,
count: 0,
visited: new Int32Array(capacity),
queue: new Int32Array(capacity),
pred: new Int32Array(capacity),
left: new Int32Array(Constants.MaxDepth),
right: new Int32Array(Constants.MaxDepth),
color: new Int32Array(capacity),
currentColor: 0,
rings: [],
unit,
bonds: unit.bonds
};
}
function resetState(state: State) {
state.count = state.endVertex - state.startVertex;
const { visited, pred, color } = state;
for (let i = 0; i < state.count; i++) {
visited[i] = -1;
pred[i] = -1;
color[i] = 0;
}
state.currentColor = 0;
}
function largestResidue(unit: Unit.Atomic) {
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueSegments, unit.elements);
let size = 0;
while (residuesIt.hasNext) {
const seg = residuesIt.move();
size = Math.max(size, seg.end - seg.start);
}
return size;
}
function processResidue(state: State, start: number, end: number) {
const { visited } = state;
state.startVertex = start;
state.endVertex = end;
// no two atom rings
if (state.endVertex - state.startVertex < 3) return;
resetState(state);
for (let i = 0; i < state.count; i++) {
if (visited[i] >= 0) continue;
findRings(state, i);
}
}
function addRing(state: State, a: number, b: number) {
// only "monotonous" rings
if (b < a) return;
const { pred, color, left, right } = state;
const nc = ++state.currentColor;
let current = a;
for (let t = 0; t < Constants.MaxDepth; t++) {
color[current] = nc;
current = pred[current];
if (current < 0) break;
}
let leftOffset = 0, rightOffset = 0;
let found = false, target = 0;
current = b;
for (let t = 0; t < Constants.MaxDepth; t++) {
if (color[current] === nc) {
target = current;
found = true;
break;
}
right[rightOffset++] = current;
current = pred[current];
if (current < 0) break;
}
if (!found) return;
current = a;
for (let t = 0; t < Constants.MaxDepth; t++) {
left[leftOffset++] = current;
if (target === current) break;
current = pred[current];
if (current < 0) break;
}
const ring = new Int32Array(leftOffset + rightOffset);
let ringOffset = 0;
for (let t = 0; t < leftOffset; t++) ring[ringOffset++] = state.startVertex + left[t];
for (let t = rightOffset - 1; t >= 0; t--) ring[ringOffset++] = state.startVertex + right[t];
state.rings.push(ring as any as number[]);
}
function findRings(state: State, from: number) {
const { bonds, startVertex, endVertex, visited, queue, pred } = state;
const { b: neighbor, flags: bondFlags, offset } = bonds;
visited[from] = 1;
queue[0] = from;
let head = 0, size = 1;
while (head < size) {
const top = queue[head++];
const a = startVertex + top;
const start = offset[a], end = offset[a + 1];
for (let i = start; i < end; i++) {
const b = neighbor[i];
if (b < startVertex || b >= endVertex || !IntraUnitBonds.isCovalent(bondFlags[i])) continue;
const other = b - startVertex;
if (visited[other] > 0) {
if (pred[other] !== top && pred[top] !== other) addRing(state, top, other);
continue;
}
visited[other] = 1;
queue[size++] = other;
pred[other] = top;
}
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment