diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts index 6ffb3c324ebf39d80cb4b4a77423bf22b4dee6ec..c5888bd12fa548b52ceb8bae489bf00dc3a9c068 100644 --- a/src/apps/structure-info/model.ts +++ b/src/apps/structure-info/model.ts @@ -146,7 +146,7 @@ export function printRings(structure: Structure) { export function printUnits(structure: Structure) { console.log('\nUnits\n============='); - const l = StructureElement.create(); + const l = StructureElement.Location.create(); for (const unit of structure.units) { l.unit = unit; diff --git a/src/examples/proteopedia-wrapper/coloring.ts b/src/examples/proteopedia-wrapper/coloring.ts index 829a28b5323f015379cf7836b3bf71fbdba8edb3..b6f2158de0617f0fbee4e7cb9ed4f3d4d7a0752a 100644 --- a/src/examples/proteopedia-wrapper/coloring.ts +++ b/src/examples/proteopedia-wrapper/coloring.ts @@ -54,7 +54,7 @@ export function createProteopediaCustomTheme(colors: number[]) { const colors = props.colors, colorCount = colors.length, defaultColor = colors[0].color; if (ctx.structure) { - const l = StructureElement.create() + const l = StructureElement.Location.create() const { models } = ctx.structure const asymIdSerialMap = new Map<string, number>() for (let i = 0, il = models.length; i < il; ++i) { @@ -67,7 +67,7 @@ export function createProteopediaCustomTheme(colors: number[]) { } color = (location: Location): Color => { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { const asym_id = getAsymId(location.unit); const o = asymIdSerialMap.get(asym_id(location)) || 0; return colors[o % colorCount].color; diff --git a/src/mol-model-formats/structure/mmcif/bonds/comp.ts b/src/mol-model-formats/structure/mmcif/bonds/comp.ts index d2a813ea7a489ff0b1b864cbe12eedc8ce05b521..96e1288a80077dc011f00d48d31dce3feb38dd68 100644 --- a/src/mol-model-formats/structure/mmcif/bonds/comp.ts +++ b/src/mol-model-formats/structure/mmcif/bonds/comp.ts @@ -148,7 +148,7 @@ export namespace ComponentBond { function getUniqueResidueNames(s: Structure) { const prop = StructureProperties.residue.label_comp_id; const names = new Set<string>(); - const loc = StructureElement.create(); + const loc = StructureElement.Location.create(); for (const unit of s.units) { if (!Unit.isAtomic(unit)) continue; const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements); diff --git a/src/mol-model-props/common/custom-element-property.ts b/src/mol-model-props/common/custom-element-property.ts index 77d4bad48aff7d69089ad9894d46d7fab50f4450..96e1725aea6b878e92d8a4e3f93e6021c8bd5995 100644 --- a/src/mol-model-props/common/custom-element-property.ts +++ b/src/mol-model-props/common/custom-element-property.ts @@ -64,8 +64,8 @@ namespace CustomElementProperty { }) } - function getStatic(e: StructureElement) { return e.unit.model._staticPropertyData[name].get(e.element); } - function getDynamic(e: StructureElement) { return e.unit.model._staticPropertyData[name].get(e.element); } + function getStatic(e: StructureElement.Location) { return e.unit.model._staticPropertyData[name].get(e.element); } + function getDynamic(e: StructureElement.Location) { return e.unit.model._staticPropertyData[name].get(e.element); } const propertyProvider: CustomPropertyRegistry.ModelProvider = { option: [name, params.display], @@ -86,7 +86,7 @@ namespace CustomElementProperty { if (ctx.structure && !ctx.structure.isEmpty && has(ctx.structure.models[0])) { color = (location: Location) => { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { const e = get(location); if (typeof e !== 'undefined') return getColor(e); } @@ -118,7 +118,7 @@ namespace CustomElementProperty { if (loci.kind === 'element-loci') { const e = loci.elements[0]; if (!has(e.unit.model)) return void 0; - return params.format!(get(StructureElement.create(e.unit, e.unit.elements[OrderedSet.getAt(e.indices, 0)]))); + return params.format!(get(StructureElement.Location.create(e.unit, e.unit.elements[OrderedSet.getAt(e.indices, 0)]))); } return void 0; } diff --git a/src/mol-model-props/pdbe/structure-quality-report.ts b/src/mol-model-props/pdbe/structure-quality-report.ts index 997a971f7161d6a55bd4ddd8b86df9e079e60086..ada52bbb55e464dd78c8a22338f916f3e6ad5d58 100644 --- a/src/mol-model-props/pdbe/structure-quality-report.ts +++ b/src/mol-model-props/pdbe/structure-quality-report.ts @@ -145,7 +145,7 @@ export namespace StructureQualityReport { } const _emptyArray: string[] = []; - export function getIssues(e: StructureElement) { + export function getIssues(e: StructureElement.Location) { if (!Unit.isAtomic(e.unit)) return _emptyArray; const prop = StructureQualityReport.get(e.unit.model); if (!prop || !prop.data) return _emptyArray; @@ -162,7 +162,7 @@ const _structure_quality_report_issues_fields = CifWriter.fields<number, ReportE interface ReportExportContext { models: { - elements: StructureElement[], + elements: StructureElement.Location[], groupId: number[] }[], info: PropertyWrapper.Info, diff --git a/src/mol-model-props/pdbe/themes/structure-quality-report.ts b/src/mol-model-props/pdbe/themes/structure-quality-report.ts index ce9915af9c1592d65df9bcaa37cd3ef71fdd1eae..f71be143439fd657a5134a812736e792d078b345 100644 --- a/src/mol-model-props/pdbe/themes/structure-quality-report.ts +++ b/src/mol-model-props/pdbe/themes/structure-quality-report.ts @@ -34,7 +34,7 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: { if (ctx.structure && !ctx.structure.isEmpty && ctx.structure.models[0].customProperties.has(StructureQualityReport.Descriptor)) { const getIssues = StructureQualityReport.getIssues; color = (location: Location) => { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { return ValidationColors[Math.min(3, getIssues(location).length) + 1]; } return ValidationColors[0]; diff --git a/src/mol-model-props/rcsb/themes/assembly-symmetry-cluster.ts b/src/mol-model-props/rcsb/themes/assembly-symmetry-cluster.ts index 4f4388110b54197aa3f81ddab78accabd6b8fae6..565a01dc44936762ef0634c1a1b4afaf3150ba20 100644 --- a/src/mol-model-props/rcsb/themes/assembly-symmetry-cluster.ts +++ b/src/mol-model-props/rcsb/themes/assembly-symmetry-cluster.ts @@ -75,7 +75,7 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props: legend = palette.legend color = (location: Location): Color => { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { const { assembly } = location.unit.conformation.operator if (assembly && assembly.id === symmetry.assembly_id) { const asymId = getAsymId(location.unit)(location) diff --git a/src/mol-model/location.ts b/src/mol-model/location.ts index d5006c61710d8684b26c982e1529703a3cfcceda..30ca29d8fdeff4bffe723e8adf949eed81ffa32e 100644 --- a/src/mol-model/location.ts +++ b/src/mol-model/location.ts @@ -15,4 +15,4 @@ export function isNullLocation(x: any): x is NullLocation { return !!x && x.kind === 'null-location'; } -export type Location = StructureElement | Link.Location | ShapeGroup.Location | NullLocation \ No newline at end of file +export type Location = StructureElement.Location | Link.Location | ShapeGroup.Location | NullLocation \ No newline at end of file diff --git a/src/mol-model/loci.ts b/src/mol-model/loci.ts index 7cd07f2575d431b527b21384989087e2b9644f19..3def8af3a87fb27038b5b6e2999f758e0e342f47 100644 --- a/src/mol-model/loci.ts +++ b/src/mol-model/loci.ts @@ -58,8 +58,8 @@ namespace Loci { if (Structure.isLoci(lociA) && Structure.isLoci(lociB)) { return Structure.areLociEqual(lociA, lociB) } - if (StructureElement.isLoci(lociA) && StructureElement.isLoci(lociB)) { - return StructureElement.areLociEqual(lociA, lociB) + if (StructureElement.Loci.is(lociA) && StructureElement.Loci.is(lociB)) { + return StructureElement.Loci.areEqual(lociA, lociB) } if (Link.isLoci(lociA) && Link.isLoci(lociB)) { return Link.areLociEqual(lociA, lociB) diff --git a/src/mol-model/structure/export/categories/atom_site.ts b/src/mol-model/structure/export/categories/atom_site.ts index 5dedf013be0c9db391e13f960316fef28deaf1eb..9453afc4ae2037096303252fbed4b31401bdc96a 100644 --- a/src/mol-model/structure/export/categories/atom_site.ts +++ b/src/mol-model/structure/export/categories/atom_site.ts @@ -12,7 +12,7 @@ import CifField = CifWriter.Field import CifCategory = CifWriter.Category import E = CifWriter.Encodings -const atom_site_fields = CifWriter.fields<StructureElement, Structure>() +const atom_site_fields = CifWriter.fields<StructureElement.Location, Structure>() .str('group_PDB', P.residue.group_PDB) .index('id') .str('type_symbol', P.atom.type_symbol as any) @@ -70,11 +70,11 @@ function prepostfixed(prefix: string | undefined, postfix: string | undefined, n return name; } -function mappedProp<K, D>(loc: (key: K, data: D) => StructureElement, prop: (e: StructureElement) => any) { +function mappedProp<K, D>(loc: (key: K, data: D) => StructureElement.Location, prop: (e: StructureElement.Location) => any) { return (k: K, d: D) => prop(loc(k, d)); } -function addModelNum<K, D>(fields: CifWriter.Field.Builder<K, D>, getLocation: (key: K, data: D) => StructureElement, options?: IdFieldsOptions) { +function addModelNum<K, D>(fields: CifWriter.Field.Builder<K, D>, getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions) { if (options && options.includeModelNum) { fields.int('pdbx_PDB_model_num', mappedProp(getLocation, P.unit.model_num)); } @@ -86,7 +86,7 @@ export interface IdFieldsOptions { includeModelNum?: boolean } -export function residueIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement, options?: IdFieldsOptions): CifField<K, D>[] { +export function residueIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] { const prefix = options && options.prefix, postfix = options && options.postfix; const ret = CifWriter.fields<K, D>() .str(prepostfixed(prefix, postfix, `label_comp_id`), mappedProp(getLocation, P.residue.label_comp_id)) @@ -111,7 +111,7 @@ export function residueIdFields<K, D>(getLocation: (key: K, data: D) => Structur return ret.getFields(); } -export function chainIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement, options?: IdFieldsOptions): CifField<K, D>[] { +export function chainIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] { const prefix = options && options.prefix, postfix = options && options.postfix; const ret = CifField.build<K, D>() .str(prepostfixed(prefix, postfix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id)) @@ -122,7 +122,7 @@ export function chainIdFields<K, D>(getLocation: (key: K, data: D) => StructureE return ret.getFields(); } -export function entityIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement, options?: IdFieldsOptions): CifField<K, D>[] { +export function entityIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] { const prefix = options && options.prefix, postfix = options && options.postfix; const ret = CifField.build<K, D>() .str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id)) @@ -131,7 +131,7 @@ export function entityIdFields<K, D>(getLocation: (key: K, data: D) => Structure return ret.getFields(); } -export function atomIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement, options?: IdFieldsOptions): CifField<K, D>[] { +export function atomIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] { const prefix = options && options.prefix, postfix = options && options.postfix; const ret = CifWriter.fields<K, D>() .str(prepostfixed(prefix, postfix, `label_atom_id`), mappedProp(getLocation, P.atom.label_atom_id)) diff --git a/src/mol-model/structure/export/categories/modified-residues.ts b/src/mol-model/structure/export/categories/modified-residues.ts index 39eb324f85c6834fde2c6f01c8b11907abeca371..e19e33636c6c1b7531fbd22876517e517e18686a 100644 --- a/src/mol-model/structure/export/categories/modified-residues.ts +++ b/src/mol-model/structure/export/categories/modified-residues.ts @@ -13,7 +13,7 @@ import { CifExportContext } from '../mmcif'; import CifField = CifWriter.Field import CifCategory = CifWriter.Category -const pdbx_struct_mod_residue_fields: CifField<number, StructureElement[]>[] = [ +const pdbx_struct_mod_residue_fields: CifField<number, StructureElement.Location[]>[] = [ CifField.index('id'), CifField.str(`label_comp_id`, (i, xs) => P.residue.label_comp_id(xs[i])), CifField.int(`label_seq_id`, (i, xs) => P.residue.label_seq_id(xs[i])), @@ -23,11 +23,11 @@ const pdbx_struct_mod_residue_fields: CifField<number, StructureElement[]>[] = [ CifField.str(`auth_comp_id`, (i, xs) => P.residue.auth_comp_id(xs[i])), CifField.int(`auth_seq_id`, (i, xs) => P.residue.auth_seq_id(xs[i])), CifField.str(`auth_asym_id`, (i, xs) => P.chain.auth_asym_id(xs[i])), - CifField.str<number, StructureElement[]>('parent_comp_id', (i, xs) => xs[i].unit.model.properties.modifiedResidues.parentId.get(P.residue.label_comp_id(xs[i]))!), + CifField.str<number, StructureElement.Location[]>('parent_comp_id', (i, xs) => xs[i].unit.model.properties.modifiedResidues.parentId.get(P.residue.label_comp_id(xs[i]))!), CifField.str('details', (i, xs) => xs[i].unit.model.properties.modifiedResidues.details.get(P.residue.label_comp_id(xs[i]))!) ]; -function getModifiedResidues({ structures }: CifExportContext): StructureElement[] { +function getModifiedResidues({ structures }: CifExportContext): StructureElement.Location[] { // TODO: can different models (in the same mmCIF file) have different modified residues? const structure = structures[0], model = structure.model; const map = model.properties.modifiedResidues.parentId; @@ -35,7 +35,7 @@ function getModifiedResidues({ structures }: CifExportContext): StructureElement const ret = []; const prop = P.residue.label_comp_id; - const loc = StructureElement.create(); + const loc = StructureElement.Location.create(); for (const unit of structure.units) { if (!Unit.isAtomic(unit) || !unit.conformation.operator.isIdentity) continue; const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements); @@ -45,7 +45,7 @@ function getModifiedResidues({ structures }: CifExportContext): StructureElement loc.element = unit.elements[seg.start]; const name = prop(loc); if (map.has(name)) { - ret[ret.length] = StructureElement.create(loc.unit, loc.element); + ret[ret.length] = StructureElement.Location.create(loc.unit, loc.element); } } } diff --git a/src/mol-model/structure/export/categories/secondary-structure.ts b/src/mol-model/structure/export/categories/secondary-structure.ts index bf594b3d03495d5d7bb2fab5c64cdc4d43d0d00a..a5bc27ce4b6d9cf74350fd1a705eeef8bcc4c0ff 100644 --- a/src/mol-model/structure/export/categories/secondary-structure.ts +++ b/src/mol-model/structure/export/categories/secondary-structure.ts @@ -62,8 +62,8 @@ const struct_sheet_range_fields: CifField[] = [ ]; interface SSElement<T extends SecondaryStructure.Element> { - start: StructureElement, - end: StructureElement, + start: StructureElement.Location, + end: StructureElement.Location, length: number, element: T } @@ -100,8 +100,8 @@ function findElements<T extends SecondaryStructure.Element>(ctx: CifExportContex if (startIdx !== key[current.index]) { move = false; ssElements[ssElements.length] = { - start: StructureElement.create(unit, segs.offsets[start]), - end: StructureElement.create(unit, segs.offsets[prev]), + start: StructureElement.Location.create(unit, segs.offsets[start]), + end: StructureElement.Location.create(unit, segs.offsets[prev]), length: prev - start + 1, element } diff --git a/src/mol-model/structure/model/properties/custom/indexed.ts b/src/mol-model/structure/model/properties/custom/indexed.ts index 443984e6f95a31ec02d2d1635e0cf15487198b24..4e8aa7ef39895112531941eee44bbab9b52cd24f 100644 --- a/src/mol-model/structure/model/properties/custom/indexed.ts +++ b/src/mol-model/structure/model/properties/custom/indexed.ts @@ -25,7 +25,7 @@ export namespace IndexedCustomProperty { export type Level = 'atom' | 'residue' | 'chain' | 'entity' export interface Elements<T> { - elements: StructureElement[], + elements: StructureElement.Location[], property(index: number): T } @@ -94,7 +94,7 @@ class SegmentedMappedIndexedCustomProperty<Idx extends IndexedCustomProperty.Ind const seenIndices = new Set<Idx>(); const unitGroups = structure.unitSymmetryGroups; - const loci: StructureElement[] = []; + const loci: StructureElement.Location[] = []; const segments = this.segmentGetter(models[0]); @@ -109,7 +109,7 @@ class SegmentedMappedIndexedCustomProperty<Idx extends IndexedCustomProperty.Ind const seg = chains.move(); if (!this.has(seg.index) || seenIndices.has(seg.index)) continue; seenIndices.add(seg.index); - loci[loci.length] = StructureElement.create(unit, unit.elements[seg.start]); + loci[loci.length] = StructureElement.Location.create(unit, unit.elements[seg.start]); } } @@ -141,7 +141,7 @@ class ElementMappedCustomProperty<T = any> implements IndexedCustomProperty<Elem const seenIndices = new Set<ElementIndex>(); const unitGroups = structure.unitSymmetryGroups; - const loci: StructureElement[] = []; + const loci: StructureElement.Location[] = []; for (const unitGroup of unitGroups) { const unit = unitGroup.units[0]; @@ -154,7 +154,7 @@ class ElementMappedCustomProperty<T = any> implements IndexedCustomProperty<Elem const e = elements[i]; if (!this.has(e) || seenIndices.has(e)) continue; seenIndices.add(elements[i]); - loci[loci.length] = StructureElement.create(unit, e); + loci[loci.length] = StructureElement.Location.create(unit, e); } } @@ -186,7 +186,7 @@ class EntityMappedCustomProperty<T = any> implements IndexedCustomProperty<Entit const index = models[0].atomicHierarchy.index; const seenIndices = new Set<EntityIndex>(); const unitGroups = structure.unitSymmetryGroups; - const loci: StructureElement[] = []; + const loci: StructureElement.Location[] = []; const segments = models[0].atomicHierarchy.chainAtomSegments; @@ -202,7 +202,7 @@ class EntityMappedCustomProperty<T = any> implements IndexedCustomProperty<Entit const eI = index.getEntityFromChain(seg.index); if (!this.has(eI) || seenIndices.has(eI)) continue; seenIndices.add(eI); - loci[loci.length] = StructureElement.create(unit, unit.elements[seg.start]); + loci[loci.length] = StructureElement.Location.create(unit, unit.elements[seg.start]); } } diff --git a/src/mol-model/structure/query/context.ts b/src/mol-model/structure/query/context.ts index 019624412b20a90cf3a29ce183ea252d513b0144..3af65708d8610453f44166b65c485742d54cb426 100644 --- a/src/mol-model/structure/query/context.ts +++ b/src/mol-model/structure/query/context.ts @@ -11,12 +11,12 @@ import { Link } from '../structure/unit/links'; import { LinkType } from '../model/types'; export interface QueryContextView { - readonly element: StructureElement; + readonly element: StructureElement.Location; readonly currentStructure: Structure; } export class QueryContext implements QueryContextView { - private currentElementStack: StructureElement[] = []; + private currentElementStack: StructureElement.Location[] = []; private currentAtomicLinkStack: QueryContextLinkInfo<Unit.Atomic>[] = []; private currentStructureStack: Structure[] = []; private inputStructureStack: Structure[] = []; @@ -27,7 +27,7 @@ export class QueryContext implements QueryContextView { readonly inputStructure: Structure; /** Current element */ - readonly element: StructureElement = StructureElement.create(); + readonly element = StructureElement.Location.create(); currentStructure: Structure = void 0 as any; /** Current link between atoms */ @@ -38,14 +38,14 @@ export class QueryContext implements QueryContextView { this.element.element = e; } - pushCurrentElement(): StructureElement { + pushCurrentElement(): StructureElement.Location { this.currentElementStack[this.currentElementStack.length] = this.element; - (this.element as StructureElement) = StructureElement.create(); + (this.element as StructureElement.Location) = StructureElement.Location.create(); return this.element; } popCurrentElement() { - (this.element as StructureElement) = this.currentElementStack.pop()!; + (this.element as StructureElement.Location) = this.currentElementStack.pop()!; } pushCurrentLink() { diff --git a/src/mol-model/structure/query/queries/filters.ts b/src/mol-model/structure/query/queries/filters.ts index 3b4c0c7ebd9bf4cef9c384415ec5a38ee2497089..e65da32b899a1b7342d2a24a380f79317bd6a050 100644 --- a/src/mol-model/structure/query/queries/filters.ts +++ b/src/mol-model/structure/query/queries/filters.ts @@ -233,7 +233,7 @@ interface IsConnectedToCtx { input: Structure, target: Structure, linkTest: QueryFn<boolean>, - tElement: StructureElement + tElement: StructureElement.Location } function checkConnected(ctx: IsConnectedToCtx, structure: Structure) { @@ -313,7 +313,7 @@ export function isConnectedTo({ query, target, disjunct, invert, linkTest }: IsC input: ctx.inputStructure, target: StructureSelection.unionStructure(targetSel), linkTest: linkTest || defaultLinkTest, - tElement: StructureElement.create() + tElement: StructureElement.Location.create() } const ret = StructureSelection.LinearBuilder(ctx.inputStructure); diff --git a/src/mol-model/structure/query/queries/internal.ts b/src/mol-model/structure/query/queries/internal.ts index 9b7bce40613d9fe5cc29450ab5f85f55081a5738..eba048265c342b9b1363859ce40395669471076a 100644 --- a/src/mol-model/structure/query/queries/internal.ts +++ b/src/mol-model/structure/query/queries/internal.ts @@ -20,7 +20,7 @@ export function defaultLinkTest(ctx: QueryContext) { export function atomicSequence(): StructureQuery { return ctx => { const { inputStructure } = ctx; - const l = StructureElement.create(); + const l = StructureElement.Location.create(); const units: Unit[] = []; for (const unit of inputStructure.units) { @@ -48,7 +48,7 @@ export function atomicSequence(): StructureQuery { export function water(): StructureQuery { return ctx => { const { inputStructure } = ctx; - const l = StructureElement.create(); + const l = StructureElement.Location.create(); const units: Unit[] = []; for (const unit of inputStructure.units) { @@ -67,7 +67,7 @@ export function water(): StructureQuery { export function atomicHet(): StructureQuery { return ctx => { const { inputStructure } = ctx; - const l = StructureElement.create(); + const l = StructureElement.Location.create(); const units: Unit[] = []; for (const unit of inputStructure.units) { diff --git a/src/mol-model/structure/query/utils/builders.ts b/src/mol-model/structure/query/utils/builders.ts index e4b338df88de165e5cdbd9d31539d39cf46fbff3..c7d0c6ceeff8abcecbc2aa94e138a1df320e19c7 100644 --- a/src/mol-model/structure/query/utils/builders.ts +++ b/src/mol-model/structure/query/utils/builders.ts @@ -56,7 +56,7 @@ export class LinearGroupingBuilder { private singletonSelection(): StructureSelection { const builder = this.source.subsetBuilder(true); - const loc = StructureElement.create(); + const loc = StructureElement.Location.create(); for (let i = 0, _i = this.builders.length; i < _i; i++) { this.builders[i].setSingletonLocation(loc); builder.addToUnit(loc.unit.id, loc.element); diff --git a/src/mol-model/structure/structure/element.ts b/src/mol-model/structure/structure/element.ts index 8fcb7e512d4b1cfd6cc7dee866ad8e68c2b81a53..6b71476f0181f54176cf34a98f358799a3f50aae 100644 --- a/src/mol-model/structure/structure/element.ts +++ b/src/mol-model/structure/structure/element.ts @@ -5,800 +5,6 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { UniqueArray } from '../../../mol-data/generic'; -import { OrderedSet, SortedArray, Interval } from '../../../mol-data/int'; -import { BoundaryHelper } from '../../../mol-math/geometry/boundary-helper'; -import { Vec3 } from '../../../mol-math/linear-algebra'; -import { MolScriptBuilder as MS } from '../../../mol-script/language/builder'; -import { ElementIndex } from '../model'; -import { ChainIndex, ResidueIndex } from '../model/indexing'; -import Structure from './structure'; -import Unit from './unit'; -import { Boundary } from './util/boundary'; -import { StructureProperties } from '../structure'; -import { sortArray, hashFnv32a, hash2 } from '../../../mol-data/util'; -import Expression from '../../../mol-script/language/expression'; -import SortedRanges from '../../../mol-data/int/sorted-ranges'; - -interface StructureElement<U = Unit> { - readonly kind: 'element-location', - unit: U, - /** Index into element (atomic/coarse) properties of unit.model */ - element: ElementIndex -} - -namespace StructureElement { - export function create(unit?: Unit, element?: ElementIndex): StructureElement { - return { kind: 'element-location', unit: unit!, element: element || (0 as ElementIndex) }; - } - - export function set(a: StructureElement, unit?: Unit, element?: ElementIndex): StructureElement { - if (unit) a.unit = unit - if (element !== undefined) a.element = element - return a; - } - - export function copy(out: StructureElement, a: StructureElement): StructureElement { - out.unit = a.unit - out.element = a.element - return out - } - - // TODO: when nominal types are available, make this indexed by UnitIndex - export type Set = SortedArray<ElementIndex> - - /** Index into Unit.elements */ - export type UnitIndex = { readonly '@type': 'structure-element-index' } & number - - export interface Property<T> { (location: StructureElement): T } - export interface Predicate extends Property<boolean> { } - - export function property<T>(p: Property<T>) { return p; } - - function _wrongUnitKind(kind: string) { throw new Error(`Property only available for ${kind} models.`); } - export function atomicProperty<T>(p: (location: StructureElement<Unit.Atomic>) => T) { - return property(l => Unit.isAtomic(l.unit) ? p(l as StructureElement<Unit.Atomic>) : _wrongUnitKind('atomic')); - } - - export function coarseProperty<T>(p: (location: StructureElement<Unit.Spheres | Unit.Gaussians>) => T) { - return property(l => Unit.isCoarse(l.unit) ? p(l as StructureElement<Unit.Spheres | Unit.Gaussians>) : _wrongUnitKind('coarse')); - } - - /** Represents multiple element index locations */ - export interface Loci { - readonly kind: 'element-loci', - readonly structure: Structure, - /** Access i-th element as unit.elements[indices[i]] */ - readonly elements: ReadonlyArray<{ - unit: Unit, - /** - * Indices into the unit.elements array. - * Can use OrderedSet.forEach to iterate (or OrderedSet.size + OrderedSet.getAt) - */ - indices: OrderedSet<UnitIndex> - }> - } - - export function Loci(structure: Structure, elements: ArrayLike<{ unit: Unit, indices: OrderedSet<UnitIndex> }>): Loci { - return { kind: 'element-loci', structure, elements: elements as Loci['elements'] }; - } - - export function isLoci(x: any): x is Loci { - return !!x && x.kind === 'element-loci'; - } - - export function areLociEqual(a: Loci, b: Loci) { - if (a.elements.length !== b.elements.length) return false - for (let i = 0, il = a.elements.length; i < il; ++i) { - const elementA = a.elements[i] - const elementB = b.elements[i] - if (elementA.unit.id !== elementB.unit.id) return false - if (!OrderedSet.areEqual(elementA.indices, elementB.indices)) return false - } - return true - } - - export function isLocation(x: any): x is StructureElement { - return !!x && x.kind === 'element-location'; - } - - export function residueIndex(e: StructureElement) { - if (Unit.isAtomic(e.unit)) { - return e.unit.residueIndex[e.element]; - } else { - // TODO: throw error instead? - return -1 as ResidueIndex; - } - } - - export function chainIndex(e: StructureElement) { - if (Unit.isAtomic(e.unit)) { - return e.unit.chainIndex[e.element]; - } else { - // TODO: throw error instead? - return -1 as ChainIndex; - } - } - - export function entityIndex(l: StructureElement) { - return StructureProperties.entity.key(l) - } - - export namespace Loci { - export function size(loci: Loci) { - let s = 0; - for (const u of loci.elements) s += OrderedSet.size(u.indices); - return s; - } - - export function all(structure: Structure): Loci { - return Loci(structure, structure.units.map(unit => ({ - unit, - indices: OrderedSet.ofBounds<UnitIndex>(0 as UnitIndex, unit.elements.length as UnitIndex) - }))); - } - - export function none(structure: Structure): Loci { - return Loci(structure, []); - } - - export function remap(loci: Loci, structure: Structure): Loci { - if (structure === loci.structure) return loci - - const elements: Loci['elements'][0][] = []; - loci.elements.forEach(e => { - if (!structure.unitMap.has(e.unit.id)) return - const unit = structure.unitMap.get(e.unit.id) - - if (SortedArray.areEqual(e.unit.elements, unit.elements)) { - elements.push({ unit, indices: e.indices }) - } else { - const _indices: UnitIndex[] = [] - const end = unit.elements.length - let start = 0 - for (let i = 0; i < OrderedSet.size(e.indices); ++i) { - const v = OrderedSet.getAt(e.indices, i) - const eI = e.unit.elements[v] - const uI = SortedArray.indexOfInRange(unit.elements, eI, start, end) as UnitIndex | -1 - if (uI !== -1) { - _indices.push(uI) - start = uI - } - } - - let indices: OrderedSet<UnitIndex> - if (_indices.length > 12 && _indices[_indices.length - 1] - _indices[0] === _indices.length - 1) { - indices = Interval.ofRange(_indices[0], _indices[_indices.length - 1]) - } else { - indices = SortedArray.ofSortedArray(_indices) - } - - elements.push({ unit, indices }) - } - }); - - return Loci(structure, elements); - } - - /** Create union of `xs` and `ys` */ - export function union(xs: Loci, ys: Loci): Loci { - if (xs.elements.length > ys.elements.length) return union(ys, xs); - if (xs.elements.length === 0) return ys; - - const map = new Map<number, OrderedSet<UnitIndex>>(); - - for (const e of xs.elements) map.set(e.unit.id, e.indices); - - const elements: Loci['elements'][0][] = []; - for (const e of ys.elements) { - if (map.has(e.unit.id)) { - elements[elements.length] = { unit: e.unit, indices: OrderedSet.union(map.get(e.unit.id)!, e.indices) }; - map.delete(e.unit.id) - } else { - elements[elements.length] = e; - } - } - - map.forEach((indices, id) => { - elements[elements.length] = { unit: xs.structure.unitMap.get(id)!, indices }; - }); - - return Loci(xs.structure, elements); - } - - /** Subtract `ys` from `xs` */ - export function subtract(xs: Loci, ys: Loci): Loci { - const map = new Map<number, OrderedSet<UnitIndex>>(); - for (const e of ys.elements) map.set(e.unit.id, e.indices); - - const elements: Loci['elements'][0][] = []; - for (const e of xs.elements) { - if (map.has(e.unit.id)) { - const indices = OrderedSet.subtract(e.indices, map.get(e.unit.id)!); - if (OrderedSet.size(indices) === 0) continue; - elements[elements.length] = { unit: e.unit, indices }; - } else { - elements[elements.length] = e; - } - } - - return Loci(xs.structure, elements); - } - - export function areIntersecting(xs: Loci, ys: Loci): boolean { - if (xs.elements.length > ys.elements.length) return areIntersecting(ys, xs); - if (xs.elements.length === 0) return ys.elements.length === 0; - - const map = new Map<number, OrderedSet<UnitIndex>>(); - - for (const e of xs.elements) map.set(e.unit.id, e.indices); - for (const e of ys.elements) { - if (!map.has(e.unit.id)) continue; - if (OrderedSet.areIntersecting(map.get(e.unit.id)!, e.indices)) return true; - } - - return false; - } - - export function extendToWholeResidues(loci: Loci): Loci { - const elements: Loci['elements'][0][] = []; - - for (const lociElement of loci.elements) { - if (lociElement.unit.kind === Unit.Kind.Atomic) { - const unitElements = lociElement.unit.elements; - const h = lociElement.unit.model.atomicHierarchy; - - const { index: residueIndex, offsets: residueOffsets } = h.residueAtomSegments; - - const newIndices: UnitIndex[] = []; - const indices = lociElement.indices, len = OrderedSet.size(indices); - let i = 0; - while (i < len) { - const rI = residueIndex[unitElements[OrderedSet.getAt(indices, i)]]; - i++; - while (i < len && residueIndex[unitElements[OrderedSet.getAt(indices, i)]] === rI) { - i++; - } - - for (let j = residueOffsets[rI], _j = residueOffsets[rI + 1]; j < _j; j++) { - const idx = OrderedSet.indexOf(unitElements, j); - if (idx >= 0) newIndices[newIndices.length] = idx as UnitIndex; - } - } - - elements[elements.length] = { unit: lociElement.unit, indices: SortedArray.ofSortedArray(newIndices) }; - } else { - // coarse elements are already by-residue - elements[elements.length] = lociElement; - } - } - - return Loci(loci.structure, elements); - } - - function getChainSegments(unit: Unit) { - switch (unit.kind) { - case Unit.Kind.Atomic: return unit.model.atomicHierarchy.chainAtomSegments - case Unit.Kind.Spheres: return unit.model.coarseHierarchy.spheres.chainElementSegments - case Unit.Kind.Gaussians: return unit.model.coarseHierarchy.gaussians.chainElementSegments - } - } - - export function extendToWholeChains(loci: Loci): Loci { - const elements: Loci['elements'][0][] = []; - - for (const lociElement of loci.elements) { - const _newIndices: UnitIndex[] = []; - const unitElements = lociElement.unit.elements; - - const { index: chainIndex, offsets: chainOffsets } = getChainSegments(lociElement.unit) - - const indices = lociElement.indices, len = OrderedSet.size(indices); - let i = 0; - while (i < len) { - const cI = chainIndex[unitElements[OrderedSet.getAt(indices, i)]]; - i++; - while (i < len && chainIndex[unitElements[OrderedSet.getAt(indices, i)]] === cI) { - i++; - } - - for (let j = chainOffsets[cI], _j = chainOffsets[cI + 1]; j < _j; j++) { - const idx = OrderedSet.indexOf(unitElements, j); - if (idx >= 0) _newIndices[_newIndices.length] = idx as UnitIndex; - } - } - - let newIndices: OrderedSet<UnitIndex> - if (_newIndices.length > 12 && _newIndices[_newIndices.length - 1] - _newIndices[0] === _newIndices.length - 1) { - newIndices = Interval.ofRange(_newIndices[0], _newIndices[_newIndices.length - 1]) - } else { - newIndices = SortedArray.ofSortedArray(_newIndices) - } - - elements[elements.length] = { unit: lociElement.unit, indices: newIndices }; - } - - return Loci(loci.structure, elements); - } - - const boundaryHelper = new BoundaryHelper(), tempPos = Vec3.zero(); - export function getBoundary(loci: Loci): Boundary { - boundaryHelper.reset(0); - - for (const e of loci.elements) { - const { indices } = e; - const pos = e.unit.conformation.position, r = e.unit.conformation.r; - const { elements } = e.unit; - for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) { - const eI = elements[OrderedSet.getAt(indices, i)]; - pos(eI, tempPos); - boundaryHelper.boundaryStep(tempPos, r(eI)); - } - } - boundaryHelper.finishBoundaryStep(); - for (const e of loci.elements) { - const { indices } = e; - const pos = e.unit.conformation.position, r = e.unit.conformation.r; - const { elements } = e.unit; - for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) { - const eI = elements[OrderedSet.getAt(indices, i)]; - pos(eI, tempPos); - boundaryHelper.extendStep(tempPos, r(eI)); - } - } - - return { box: boundaryHelper.getBox(), sphere: boundaryHelper.getSphere() }; - } - - export function toScriptExpression(loci: Loci) { - if (loci.elements.length === 0) return MS.struct.generator.empty(); - - const models = loci.structure.models; - const sourceIndexMap = new Map<string, { modelLabel: string, modelIndex: number, xs: UniqueArray<number, number> }>(); - const el = StructureElement.create(), p = StructureProperties.atom.sourceIndex; - for (const e of loci.elements) { - const { indices } = e; - const { elements } = e.unit; - - const key = models.length === 1 - ? e.unit.conformation.operator.name - : `${e.unit.conformation.operator.name} ${e.unit.model.label} ${e.unit.model.modelNum}`; - - let sourceIndices: UniqueArray<number, number>; - if (sourceIndexMap.has(key)) sourceIndices = sourceIndexMap.get(key)!.xs; - else { - sourceIndices = UniqueArray.create<number, number>(); - sourceIndexMap.set(key, { modelLabel: e.unit.model.label, modelIndex: e.unit.model.modelNum, xs: sourceIndices }); - } - - el.unit = e.unit; - for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) { - el.element = elements[OrderedSet.getAt(indices, i)]; - const idx = p(el); - UniqueArray.add(sourceIndices, idx, idx); - } - } - - const opData: OpData[] = []; - const keys = sourceIndexMap.keys(); - while (true) { - const k = keys.next(); - if (k.done) break; - const e = sourceIndexMap.get(k.value)!; - opData.push(getOpData(k.value, e.xs.array, models.length > 1, e.modelLabel, e.modelIndex)); - } - - const opGroups = new Map<string, OpData>(); - for (let i = 0, il = opData.length; i < il; ++i) { - const d = opData[i] - const hash = hash2(hashFnv32a(d.atom.ranges), hashFnv32a(d.atom.set)) - const key = `${hash}|${d.entity ? (d.entity.modelLabel + d.entity.modelIndex) : ''}` - if (opGroups.has(key)) { - opGroups.get(key)!.chain.opName.push(...d.chain.opName) - } else { - opGroups.set(key, d) - } - } - - const opQueries: Expression[] = []; - opGroups.forEach(d => { - const { ranges, set } = d.atom - const { opName } = d.chain - - const opProp = MS.struct.atomProperty.core.operatorName() - const siProp = MS.struct.atomProperty.core.sourceIndex(); - const tests: Expression[] = []; - - // TODO: add set.ofRanges constructor to MolQL??? - if (set.length > 0) { - tests[tests.length] = MS.core.set.has([MS.core.type.set(set), siProp]); - } - for (let rI = 0, _rI = ranges.length / 2; rI < _rI; rI++) { - tests[tests.length] = MS.core.rel.inRange([siProp, ranges[2 * rI], ranges[2 * rI + 1]]); - } - - if (d.entity) { - const { modelLabel, modelIndex } = d.entity - opQueries.push(MS.struct.generator.atomGroups({ - 'atom-test': tests.length > 1 ? MS.core.logic.or(tests) : tests[0], - 'chain-test': opName.length > 1 - ? MS.core.set.has([MS.core.type.set(opName), opProp]) - : MS.core.rel.eq([opProp, opName[0]]), - 'entity-test': MS.core.logic.and([ - MS.core.rel.eq([MS.struct.atomProperty.core.modelLabel(), modelLabel]), - MS.core.rel.eq([MS.struct.atomProperty.core.modelIndex(), modelIndex]), - ]) - })) - } else { - opQueries.push(MS.struct.generator.atomGroups({ - 'atom-test': tests.length > 1 ? MS.core.logic.or(tests) : tests[0], - 'chain-test': opName.length > 1 - ? MS.core.set.has([MS.core.type.set(opName), opProp]) - : MS.core.rel.eq([opProp, opName[0]]) - })) - } - }) - - return MS.struct.modifier.union([ - opQueries.length === 1 - ? opQueries[0] - // Need to union before merge for fast performance - : MS.struct.combinator.merge(opQueries.map(q => MS.struct.modifier.union([ q ]))) - ]); - } - - type OpData = { - atom: { set: number[], ranges: number[] }, - chain: { opName: string[] }, - entity?: { modelLabel: string, modelIndex: number } - } - - function getOpData(opName: string, xs: number[], multimodel: boolean, modelLabel: string, modelIndex: number): OpData { - sortArray(xs); - - const ranges: number[] = []; - const set: number[] = []; - - let i = 0, len = xs.length; - while (i < len) { - const start = i; - i++; - while (i < len && xs[i - 1] + 1 === xs[i]) i++; - const end = i; - // TODO: is this a good value? - if (end - start > 12) { - ranges[ranges.length] = xs[start]; - ranges[ranges.length] = xs[end - 1]; - } else { - for (let j = start; j < end; j++) { - set[set.length] = xs[j]; - } - } - } - - return multimodel - ? { - atom: { set, ranges }, - chain: { opName: [ opName ] }, - entity: { modelLabel, modelIndex } - } - : { - atom: { set, ranges }, - chain: { opName: [ opName ] }, - } - } - } - - // - - interface QueryElement { - /** - * Array (sorted by first element in sub-array) of - * arrays of `Unit.id`s that share the same `Unit.invariantId` - */ - groupedUnits: SortedArray<number>[], - set: SortedArray<UnitIndex> - ranges: SortedRanges<UnitIndex> - } - export interface Query { - /** Hash of the structure to which the query can be applied */ - readonly hash: number - /** Query elements */ - readonly elements: ReadonlyArray<Readonly<QueryElement>> - } - - export namespace Query { - export const Empty: Query = { hash: -1, elements: [] } - - export function fromLoci(loci: StructureElement.Loci): Query { - const _elements: { - unit: Unit - set: SortedArray<UnitIndex> - ranges: SortedRanges<UnitIndex> - }[] = [] - for (const e of loci.elements) { - const { unit, indices } = e - if (OrderedSet.size(indices) === 0) continue - - const ranges: UnitIndex[] = []; - const set: UnitIndex[] = []; - - if (OrderedSet.isInterval(indices)) { - if (OrderedSet.size(indices) === 1) { - set.push(Interval.min(indices)) - } else { - ranges.push(Interval.min(indices), Interval.max(indices)) - } - } else { - let i = 0, len = indices.length; - while (i < len) { - const start = i; - i++; - while (i < len && indices[i - 1] + 1 === indices[i]) i++; - const end = i; - if (end - start > 2) { - ranges.push(indices[start], indices[end - 1]) - } else { - for (let j = start; j < end; j++) { - set[set.length] = indices[j] - } - } - } - } - - _elements.push({ - unit, - set: SortedArray.ofSortedArray(set), - ranges: SortedRanges.ofSortedRanges(ranges) - }) - } - - const elementGroups = new Map<number, { - groupedUnits: Map<number, number[]> - set: SortedArray<UnitIndex> - ranges: SortedRanges<UnitIndex> - }>(); - for (let i = 0, il = _elements.length; i < il; ++i) { - const e = _elements[i] - const key = hash2(hashFnv32a(e.ranges), hashFnv32a(e.set)) - if (elementGroups.has(key)) { - const { groupedUnits } = elementGroups.get(key)! - if (groupedUnits.has(e.unit.invariantId)) { - groupedUnits.get(e.unit.invariantId)!.push(e.unit.id) - } else { - groupedUnits.set(e.unit.invariantId, [e.unit.id]) - } - } else { - const groupedUnits = new Map<number, number[]>() - groupedUnits.set(e.unit.invariantId, [e.unit.id]) - elementGroups.set(key, { groupedUnits, set: e.set, ranges: e.ranges }) - } - } - - const elements: QueryElement[] = [] - elementGroups.forEach(e => { - const groupedUnits: SortedArray<number>[] = [] - e.groupedUnits.forEach(g => groupedUnits.push(SortedArray.ofUnsortedArray(g))) - groupedUnits.sort((a, b) => a[0] - b[0]) // sort by first unit id of each group - elements.push({ groupedUnits, set: e.set, ranges: e.ranges }) - }) - - return { hash: loci.structure.root.hashCode, elements } - } - - function getUnitsFromIds(unitIds: ArrayLike<number>, structure: Structure) { - const units: Unit[] = [] - for (let i = 0, il = unitIds.length; i < il; ++i) { - const unitId = unitIds[i] - if (structure.unitMap.has(unitId)) units.push(structure.unitMap.get(unitId)) - } - return units - } - - export function toLoci(query: Query, parent: Structure): Loci { - if (query.hash !== -1 && query.hash !== parent.root.hashCode) { - new Error('Query not compatible with given structure') - } - const elements: Loci['elements'][0][] = [] - for (const e of query.elements) { - for (const g of e.groupedUnits) { - const units = getUnitsFromIds(g, parent) - if (units.length === 0) continue - - let indices: OrderedSet<UnitIndex> - if (e.ranges.length === 0) { - indices = e.set - } else if (e.set.length === 0) { - if (e.ranges.length === 2) { - indices = Interval.ofRange(e.ranges[0], e.ranges[1]) - } else { - const _indices = new Int32Array(SortedRanges.size(e.ranges)) - SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = v) - indices = SortedArray.ofSortedArray(_indices) - } - } else { - const rangesSize = SortedRanges.size(e.ranges) - const _indices = new Int32Array(e.set.length + rangesSize) - SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = v) - _indices.set(e.set, rangesSize) - indices = SortedArray.ofUnsortedArray(_indices) // requires sort - } - - for (const unit of units) { - elements.push({ unit, indices }) - } - } - } - return Loci(parent, elements) - } - - export function toStructure(query: Query, parent: Structure): Structure { - if (query.hash !== -1 && query.hash !== parent.root.hashCode) { - new Error('Query not compatible with given structure') - } - const units: Unit[] = [] - for (const e of query.elements) { - for (const g of e.groupedUnits) { - const _units = getUnitsFromIds(g, parent) - if (_units.length === 0) continue - - const unit = _units[0] // the elements are grouped by unit.invariantId - const rangesSize = SortedRanges.size(e.ranges) - const _indices = new Int32Array(e.set.length + rangesSize) - let indices: SortedArray<ElementIndex> - if (e.ranges.length === 0) { - for (let i = 0, il = e.set.length; i < il; ++i) { - _indices[i] = unit.elements[e.set[i]] - } - indices = SortedArray.ofSortedArray(_indices) - } else if (e.set.length === 0) { - SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = unit.elements[v]) - indices = SortedArray.ofSortedArray(_indices) - } else { - const rangesSize = SortedRanges.size(e.ranges) - SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = unit.elements[v]) - SortedRanges.forEach(e.set, (v, i) => _indices[i + rangesSize] = unit.elements[v]) - indices = SortedArray.ofUnsortedArray(_indices) // requires sort - } - - for (const unit of _units) { - units.push(unit.getChild(indices)) - } - } - } - return Structure.create(units, { parent }) - } - - export function areEqual(a: Query, b: Query) { - if (a.elements.length !== b.elements.length) return false - for (let i = 0, il = a.elements.length; i < il; ++i) { - const elementA = a.elements[i], elementB = b.elements[i] - if (elementA.groupedUnits.length !== elementB.groupedUnits.length) return false - for (let j = 0, jl = elementB.groupedUnits.length; j < jl; ++j) { - if (!SortedArray.areEqual(elementA.groupedUnits[j], elementB.groupedUnits[j])) return false - } - if (!SortedArray.areEqual(elementA.set, elementB.set)) return false - if (!SortedRanges.areEqual(elementA.ranges, elementB.ranges)) return false - } - return true - } - } - - // - - export interface Stats { - elementCount: number - residueCount: number - unitCount: number - - firstElementLoc: StructureElement - firstResidueLoc: StructureElement - firstUnitLoc: StructureElement - } - - export namespace Stats { - export function create(): Stats { - return { - elementCount: 0, - residueCount: 0, - unitCount: 0, - - firstElementLoc: StructureElement.create(), - firstResidueLoc: StructureElement.create(), - firstUnitLoc: StructureElement.create(), - } - } - - function handleElement(stats: Stats, element: StructureElement.Loci['elements'][0]) { - const { indices, unit } = element - const { elements } = unit - const size = OrderedSet.size(indices) - if (size === 1) { - stats.elementCount += 1 - if (stats.elementCount === 1) { - StructureElement.set(stats.firstElementLoc, unit, elements[OrderedSet.start(indices)]) - } - } else if (size === elements.length) { - stats.unitCount += 1 - if (stats.unitCount === 1) { - StructureElement.set(stats.firstUnitLoc, unit, elements[OrderedSet.start(indices)]) - } - } else { - if (Unit.isAtomic(unit)) { - const { index, offsets } = unit.model.atomicHierarchy.residueAtomSegments - let i = 0 - while (i < size) { - const eI = elements[OrderedSet.getAt(indices, i)] - const rI = index[eI] - if (offsets[rI] !== eI) { - // partial residue, start missing - ++i - stats.elementCount += 1 - while (i < size && index[elements[OrderedSet.getAt(indices, i)]] === rI) { - ++i - stats.elementCount += 1 - } - } else { - ++i - while (i < size && index[elements[OrderedSet.getAt(indices, i)]] === rI) { - ++i - } - - if (offsets[rI + 1] - 1 === elements[OrderedSet.getAt(indices, i - 1)]) { - // full residue - stats.residueCount += 1 - if (stats.residueCount === 1) { - StructureElement.set(stats.firstResidueLoc, unit, elements[OrderedSet.start(indices)]) - } - } else { - // partial residue, end missing - stats.elementCount += offsets[rI + 1] - 1 - elements[OrderedSet.getAt(indices, i - 1)] - } - } - } - } else { - // TODO - stats.elementCount += size - if (stats.elementCount === 1) { - StructureElement.set(stats.firstElementLoc, unit, elements[OrderedSet.start(indices)]) - } - } - } - } - - export function ofLoci(loci: StructureElement.Loci) { - const stats = create() - if (loci.elements.length > 0) { - for (const e of loci.elements) handleElement(stats, e) - } - return stats - } - - export function add(out: Stats, a: Stats, b: Stats) { - if (a.elementCount === 1 && b.elementCount === 0) { - StructureElement.copy(out.firstElementLoc, a.firstElementLoc) - } else if (a.elementCount === 0 && b.elementCount === 1) { - StructureElement.copy(out.firstElementLoc, b.firstElementLoc) - } - - if (a.residueCount === 1 && b.residueCount === 0) { - StructureElement.copy(out.firstResidueLoc, a.firstResidueLoc) - } else if (a.residueCount === 0 && b.residueCount === 1) { - StructureElement.copy(out.firstResidueLoc, b.firstResidueLoc) - } - - if (a.unitCount === 1 && b.unitCount === 0) { - StructureElement.copy(out.firstUnitLoc, a.firstUnitLoc) - } else if (a.unitCount === 0 && b.unitCount === 1) { - StructureElement.copy(out.firstUnitLoc, b.firstUnitLoc) - } - - out.elementCount = a.elementCount + b.elementCount - out.residueCount = a.residueCount + b.residueCount - out.unitCount = a.unitCount + b.unitCount - return out - } - } -} +import * as StructureElement from './element/index'; export default StructureElement \ No newline at end of file diff --git a/src/mol-model/structure/structure/element/element.ts b/src/mol-model/structure/structure/element/element.ts new file mode 100644 index 0000000000000000000000000000000000000000..e15c7dd4d1d56ac4a0c873471857094d7d144095 --- /dev/null +++ b/src/mol-model/structure/structure/element/element.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { SortedArray } from '../../../../mol-data/int'; +import { ElementIndex, ResidueIndex, ChainIndex } from '../../model'; +import Unit from '../unit'; +import { Location } from './location'; +import StructureProperties from '../properties'; + +// TODO: when nominal types are available, make this indexed by UnitIndex +export type Set = SortedArray<ElementIndex> + +/** Index into Unit.elements */ +export type UnitIndex = { readonly '@type': 'structure-element-index' } & number + +export interface Property<T> { (location: Location): T } +export interface Predicate extends Property<boolean> { } + +export function property<T>(p: Property<T>) { return p; } + +function _wrongUnitKind(kind: string) { throw new Error(`Property only available for ${kind} models.`); } +export function atomicProperty<T>(p: (location: Location<Unit.Atomic>) => T) { + return property(l => Unit.isAtomic(l.unit) ? p(l as Location<Unit.Atomic>) : _wrongUnitKind('atomic')); +} + +export function coarseProperty<T>(p: (location: Location<Unit.Spheres | Unit.Gaussians>) => T) { + return property(l => Unit.isCoarse(l.unit) ? p(l as Location<Unit.Spheres | Unit.Gaussians>) : _wrongUnitKind('coarse')); +} + +export function residueIndex(e: Location) { + if (Unit.isAtomic(e.unit)) { + return e.unit.residueIndex[e.element]; + } else { + // TODO: throw error instead? + return -1 as ResidueIndex; + } +} + +export function chainIndex(e: Location) { + if (Unit.isAtomic(e.unit)) { + return e.unit.chainIndex[e.element]; + } else { + // TODO: throw error instead? + return -1 as ChainIndex; + } +} + +export function entityIndex(l: Location) { + return StructureProperties.entity.key(l) +} \ No newline at end of file diff --git a/src/mol-model/structure/structure/element/index.ts b/src/mol-model/structure/structure/element/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..d59dfce564dff194e4843e7b1c24c44d8d3188f0 --- /dev/null +++ b/src/mol-model/structure/structure/element/index.ts @@ -0,0 +1,12 @@ +/** + * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +export * from './location' +export * from './loci' +export * from './query' +export * from './stats' +export * from './element' \ No newline at end of file diff --git a/src/mol-model/structure/structure/element/location.ts b/src/mol-model/structure/structure/element/location.ts new file mode 100644 index 0000000000000000000000000000000000000000..f60d677475624a61977050e479004bd766831f49 --- /dev/null +++ b/src/mol-model/structure/structure/element/location.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ElementIndex } from '../../model'; +import Unit from '../unit'; + +export { Location } + +interface Location<U = Unit> { + readonly kind: 'element-location', + unit: U, + /** Index into element (atomic/coarse) properties of unit.model */ + element: ElementIndex +} + +namespace Location { + export function create(unit?: Unit, element?: ElementIndex): Location { + return { kind: 'element-location', unit: unit!, element: element || (0 as ElementIndex) }; + } + + export function set(a: Location, unit?: Unit, element?: ElementIndex): Location { + if (unit) a.unit = unit + if (element !== undefined) a.element = element + return a; + } + + export function copy(out: Location, a: Location): Location { + out.unit = a.unit + out.element = a.element + return out + } + + export function is(x: any): x is Location { + return !!x && x.kind === 'element-location'; + } +} \ No newline at end of file diff --git a/src/mol-model/structure/structure/element/loci.ts b/src/mol-model/structure/structure/element/loci.ts new file mode 100644 index 0000000000000000000000000000000000000000..5006485b8d94681951a2810bab0f0f939a50cd02 --- /dev/null +++ b/src/mol-model/structure/structure/element/loci.ts @@ -0,0 +1,422 @@ +/** + * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { UniqueArray } from '../../../../mol-data/generic'; +import { OrderedSet, SortedArray, Interval } from '../../../../mol-data/int'; +import { BoundaryHelper } from '../../../../mol-math/geometry/boundary-helper'; +import { Vec3 } from '../../../../mol-math/linear-algebra'; +import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder'; +import Structure from '../structure'; +import Unit from '../unit'; +import { Boundary } from '../util/boundary'; +import { sortArray, hashFnv32a, hash2 } from '../../../../mol-data/util'; +import Expression from '../../../../mol-script/language/expression'; +import { ElementIndex } from '../../model'; +import { UnitIndex } from './element'; + +/** Represents multiple element index locations */ +export interface Loci { + readonly kind: 'element-loci', + readonly structure: Structure, + /** Access i-th element as unit.elements[indices[i]] */ + readonly elements: ReadonlyArray<{ + unit: Unit, + /** + * Indices into the unit.elements array. + * Can use OrderedSet.forEach to iterate (or OrderedSet.size + OrderedSet.getAt) + */ + indices: OrderedSet<UnitIndex> + }> +} + +export function Loci(structure: Structure, elements: ArrayLike<{ unit: Unit, indices: OrderedSet<UnitIndex> }>): Loci { + return { kind: 'element-loci', structure, elements: elements as Loci['elements'] }; +} + +export namespace Loci { + export function is(x: any): x is Loci { + return !!x && x.kind === 'element-loci'; + } + + export function areEqual(a: Loci, b: Loci) { + if (a.elements.length !== b.elements.length) return false + for (let i = 0, il = a.elements.length; i < il; ++i) { + const elementA = a.elements[i] + const elementB = b.elements[i] + if (elementA.unit.id !== elementB.unit.id) return false + if (!OrderedSet.areEqual(elementA.indices, elementB.indices)) return false + } + return true + } + + export function size(loci: Loci) { + let s = 0; + for (const u of loci.elements) s += OrderedSet.size(u.indices); + return s; + } + + export function all(structure: Structure): Loci { + return Loci(structure, structure.units.map(unit => ({ + unit, + indices: OrderedSet.ofBounds<UnitIndex>(0 as UnitIndex, unit.elements.length as UnitIndex) + }))); + } + + export function none(structure: Structure): Loci { + return Loci(structure, []); + } + + export function remap(loci: Loci, structure: Structure): Loci { + if (structure === loci.structure) return loci + + const elements: Loci['elements'][0][] = []; + loci.elements.forEach(e => { + if (!structure.unitMap.has(e.unit.id)) return + const unit = structure.unitMap.get(e.unit.id) + + if (SortedArray.areEqual(e.unit.elements, unit.elements)) { + elements.push({ unit, indices: e.indices }) + } else { + const _indices: UnitIndex[] = [] + const end = unit.elements.length + let start = 0 + for (let i = 0; i < OrderedSet.size(e.indices); ++i) { + const v = OrderedSet.getAt(e.indices, i) + const eI = e.unit.elements[v] + const uI = SortedArray.indexOfInRange(unit.elements, eI, start, end) as UnitIndex | -1 + if (uI !== -1) { + _indices.push(uI) + start = uI + } + } + + let indices: OrderedSet<UnitIndex> + if (_indices.length > 12 && _indices[_indices.length - 1] - _indices[0] === _indices.length - 1) { + indices = Interval.ofRange(_indices[0], _indices[_indices.length - 1]) + } else { + indices = SortedArray.ofSortedArray(_indices) + } + + elements.push({ unit, indices }) + } + }); + + return Loci(structure, elements); + } + + /** Create union of `xs` and `ys` */ + export function union(xs: Loci, ys: Loci): Loci { + if (xs.elements.length > ys.elements.length) return union(ys, xs); + if (xs.elements.length === 0) return ys; + + const map = new Map<number, OrderedSet<UnitIndex>>(); + + for (const e of xs.elements) map.set(e.unit.id, e.indices); + + const elements: Loci['elements'][0][] = []; + for (const e of ys.elements) { + if (map.has(e.unit.id)) { + elements[elements.length] = { unit: e.unit, indices: OrderedSet.union(map.get(e.unit.id)!, e.indices) }; + map.delete(e.unit.id) + } else { + elements[elements.length] = e; + } + } + + map.forEach((indices, id) => { + elements[elements.length] = { unit: xs.structure.unitMap.get(id)!, indices }; + }); + + return Loci(xs.structure, elements); + } + + /** Subtract `ys` from `xs` */ + export function subtract(xs: Loci, ys: Loci): Loci { + const map = new Map<number, OrderedSet<UnitIndex>>(); + for (const e of ys.elements) map.set(e.unit.id, e.indices); + + const elements: Loci['elements'][0][] = []; + for (const e of xs.elements) { + if (map.has(e.unit.id)) { + const indices = OrderedSet.subtract(e.indices, map.get(e.unit.id)!); + if (OrderedSet.size(indices) === 0) continue; + elements[elements.length] = { unit: e.unit, indices }; + } else { + elements[elements.length] = e; + } + } + + return Loci(xs.structure, elements); + } + + export function areIntersecting(xs: Loci, ys: Loci): boolean { + if (xs.elements.length > ys.elements.length) return areIntersecting(ys, xs); + if (xs.elements.length === 0) return ys.elements.length === 0; + + const map = new Map<number, OrderedSet<UnitIndex>>(); + + for (const e of xs.elements) map.set(e.unit.id, e.indices); + for (const e of ys.elements) { + if (!map.has(e.unit.id)) continue; + if (OrderedSet.areIntersecting(map.get(e.unit.id)!, e.indices)) return true; + } + + return false; + } + + export function extendToWholeResidues(loci: Loci): Loci { + const elements: Loci['elements'][0][] = []; + + for (const lociElement of loci.elements) { + if (lociElement.unit.kind === Unit.Kind.Atomic) { + const unitElements = lociElement.unit.elements; + const h = lociElement.unit.model.atomicHierarchy; + + const { index: residueIndex, offsets: residueOffsets } = h.residueAtomSegments; + + const newIndices: UnitIndex[] = []; + const indices = lociElement.indices, len = OrderedSet.size(indices); + let i = 0; + while (i < len) { + const rI = residueIndex[unitElements[OrderedSet.getAt(indices, i)]]; + i++; + while (i < len && residueIndex[unitElements[OrderedSet.getAt(indices, i)]] === rI) { + i++; + } + + for (let j = residueOffsets[rI], _j = residueOffsets[rI + 1]; j < _j; j++) { + const idx = OrderedSet.indexOf(unitElements, j); + if (idx >= 0) newIndices[newIndices.length] = idx as UnitIndex; + } + } + + elements[elements.length] = { unit: lociElement.unit, indices: SortedArray.ofSortedArray(newIndices) }; + } else { + // coarse elements are already by-residue + elements[elements.length] = lociElement; + } + } + + return Loci(loci.structure, elements); + } + + function getChainSegments(unit: Unit) { + switch (unit.kind) { + case Unit.Kind.Atomic: return unit.model.atomicHierarchy.chainAtomSegments + case Unit.Kind.Spheres: return unit.model.coarseHierarchy.spheres.chainElementSegments + case Unit.Kind.Gaussians: return unit.model.coarseHierarchy.gaussians.chainElementSegments + } + } + + export function extendToWholeChains(loci: Loci): Loci { + const elements: Loci['elements'][0][] = []; + + for (const lociElement of loci.elements) { + const _newIndices: UnitIndex[] = []; + const unitElements = lociElement.unit.elements; + + const { index: chainIndex, offsets: chainOffsets } = getChainSegments(lociElement.unit) + + const indices = lociElement.indices, len = OrderedSet.size(indices); + let i = 0; + while (i < len) { + const cI = chainIndex[unitElements[OrderedSet.getAt(indices, i)]]; + i++; + while (i < len && chainIndex[unitElements[OrderedSet.getAt(indices, i)]] === cI) { + i++; + } + + for (let j = chainOffsets[cI], _j = chainOffsets[cI + 1]; j < _j; j++) { + const idx = OrderedSet.indexOf(unitElements, j); + if (idx >= 0) _newIndices[_newIndices.length] = idx as UnitIndex; + } + } + + let newIndices: OrderedSet<UnitIndex> + if (_newIndices.length > 12 && _newIndices[_newIndices.length - 1] - _newIndices[0] === _newIndices.length - 1) { + newIndices = Interval.ofRange(_newIndices[0], _newIndices[_newIndices.length - 1]) + } else { + newIndices = SortedArray.ofSortedArray(_newIndices) + } + + elements[elements.length] = { unit: lociElement.unit, indices: newIndices }; + } + + return Loci(loci.structure, elements); + } + + const boundaryHelper = new BoundaryHelper(), tempPos = Vec3.zero(); + export function getBoundary(loci: Loci): Boundary { + boundaryHelper.reset(0); + + for (const e of loci.elements) { + const { indices } = e; + const pos = e.unit.conformation.position, r = e.unit.conformation.r; + const { elements } = e.unit; + for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) { + const eI = elements[OrderedSet.getAt(indices, i)]; + pos(eI, tempPos); + boundaryHelper.boundaryStep(tempPos, r(eI)); + } + } + boundaryHelper.finishBoundaryStep(); + for (const e of loci.elements) { + const { indices } = e; + const pos = e.unit.conformation.position, r = e.unit.conformation.r; + const { elements } = e.unit; + for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) { + const eI = elements[OrderedSet.getAt(indices, i)]; + pos(eI, tempPos); + boundaryHelper.extendStep(tempPos, r(eI)); + } + } + + return { box: boundaryHelper.getBox(), sphere: boundaryHelper.getSphere() }; + } + + function sourceIndex(unit: Unit, element: ElementIndex) { + return Unit.isAtomic(unit) + ? unit.model.atomicHierarchy.atoms.sourceIndex.value(element) + // TODO: when implemented, this should map to the source index. + : element + } + + export function toScriptExpression(loci: Loci) { + if (loci.elements.length === 0) return MS.struct.generator.empty(); + + const models = loci.structure.models; + const sourceIndexMap = new Map<string, { modelLabel: string, modelIndex: number, xs: UniqueArray<number, number> }>(); + for (const e of loci.elements) { + const { indices } = e; + const { elements } = e.unit; + + const key = models.length === 1 + ? e.unit.conformation.operator.name + : `${e.unit.conformation.operator.name} ${e.unit.model.label} ${e.unit.model.modelNum}`; + + let sourceIndices: UniqueArray<number, number>; + if (sourceIndexMap.has(key)) sourceIndices = sourceIndexMap.get(key)!.xs; + else { + sourceIndices = UniqueArray.create<number, number>(); + sourceIndexMap.set(key, { modelLabel: e.unit.model.label, modelIndex: e.unit.model.modelNum, xs: sourceIndices }); + } + + for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) { + const idx = sourceIndex(e.unit, elements[OrderedSet.getAt(indices, i)]); + UniqueArray.add(sourceIndices, idx, idx); + } + } + + const opData: OpData[] = []; + const keys = sourceIndexMap.keys(); + while (true) { + const k = keys.next(); + if (k.done) break; + const e = sourceIndexMap.get(k.value)!; + opData.push(getOpData(k.value, e.xs.array, models.length > 1, e.modelLabel, e.modelIndex)); + } + + const opGroups = new Map<string, OpData>(); + for (let i = 0, il = opData.length; i < il; ++i) { + const d = opData[i] + const hash = hash2(hashFnv32a(d.atom.ranges), hashFnv32a(d.atom.set)) + const key = `${hash}|${d.entity ? (d.entity.modelLabel + d.entity.modelIndex) : ''}` + if (opGroups.has(key)) { + opGroups.get(key)!.chain.opName.push(...d.chain.opName) + } else { + opGroups.set(key, d) + } + } + + const opQueries: Expression[] = []; + opGroups.forEach(d => { + const { ranges, set } = d.atom + const { opName } = d.chain + + const opProp = MS.struct.atomProperty.core.operatorName() + const siProp = MS.struct.atomProperty.core.sourceIndex(); + const tests: Expression[] = []; + + // TODO: add set.ofRanges constructor to MolQL??? + if (set.length > 0) { + tests[tests.length] = MS.core.set.has([MS.core.type.set(set), siProp]); + } + for (let rI = 0, _rI = ranges.length / 2; rI < _rI; rI++) { + tests[tests.length] = MS.core.rel.inRange([siProp, ranges[2 * rI], ranges[2 * rI + 1]]); + } + + if (d.entity) { + const { modelLabel, modelIndex } = d.entity + opQueries.push(MS.struct.generator.atomGroups({ + 'atom-test': tests.length > 1 ? MS.core.logic.or(tests) : tests[0], + 'chain-test': opName.length > 1 + ? MS.core.set.has([MS.core.type.set(opName), opProp]) + : MS.core.rel.eq([opProp, opName[0]]), + 'entity-test': MS.core.logic.and([ + MS.core.rel.eq([MS.struct.atomProperty.core.modelLabel(), modelLabel]), + MS.core.rel.eq([MS.struct.atomProperty.core.modelIndex(), modelIndex]), + ]) + })) + } else { + opQueries.push(MS.struct.generator.atomGroups({ + 'atom-test': tests.length > 1 ? MS.core.logic.or(tests) : tests[0], + 'chain-test': opName.length > 1 + ? MS.core.set.has([MS.core.type.set(opName), opProp]) + : MS.core.rel.eq([opProp, opName[0]]) + })) + } + }) + + return MS.struct.modifier.union([ + opQueries.length === 1 + ? opQueries[0] + // Need to union before merge for fast performance + : MS.struct.combinator.merge(opQueries.map(q => MS.struct.modifier.union([ q ]))) + ]); + } + + type OpData = { + atom: { set: number[], ranges: number[] }, + chain: { opName: string[] }, + entity?: { modelLabel: string, modelIndex: number } + } + + function getOpData(opName: string, xs: number[], multimodel: boolean, modelLabel: string, modelIndex: number): OpData { + sortArray(xs); + + const ranges: number[] = []; + const set: number[] = []; + + let i = 0, len = xs.length; + while (i < len) { + const start = i; + i++; + while (i < len && xs[i - 1] + 1 === xs[i]) i++; + const end = i; + // TODO: is this a good value? + if (end - start > 12) { + ranges[ranges.length] = xs[start]; + ranges[ranges.length] = xs[end - 1]; + } else { + for (let j = start; j < end; j++) { + set[set.length] = xs[j]; + } + } + } + + return multimodel + ? { + atom: { set, ranges }, + chain: { opName: [ opName ] }, + entity: { modelLabel, modelIndex } + } + : { + atom: { set, ranges }, + chain: { opName: [ opName ] }, + } + } +} \ No newline at end of file diff --git a/src/mol-model/structure/structure/element/query.ts b/src/mol-model/structure/structure/element/query.ts new file mode 100644 index 0000000000000000000000000000000000000000..7093ff9f0d1e8c51d4b693d50a7e08ef75d27f82 --- /dev/null +++ b/src/mol-model/structure/structure/element/query.ts @@ -0,0 +1,208 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { OrderedSet, SortedArray, Interval } from '../../../../mol-data/int'; +import { ElementIndex } from '../../model'; +import Structure from '../structure'; +import Unit from '../unit'; +import { hashFnv32a, hash2 } from '../../../../mol-data/util'; +import SortedRanges from '../../../../mol-data/int/sorted-ranges'; +import { UnitIndex } from './element'; +import { Loci } from './loci'; + +interface QueryElement { + /** + * Array (sorted by first element in sub-array) of + * arrays of `Unit.id`s that share the same `Unit.invariantId` + */ + groupedUnits: SortedArray<number>[], + set: SortedArray<UnitIndex> + ranges: SortedRanges<UnitIndex> +} + +export interface Query { + /** Hash of the structure to which the query can be applied */ + readonly hash: number + /** Query elements */ + readonly elements: ReadonlyArray<Readonly<QueryElement>> +} + +export namespace Query { + export const Empty: Query = { hash: -1, elements: [] } + + export function fromLoci(loci: Loci): Query { + const _elements: { + unit: Unit + set: SortedArray<UnitIndex> + ranges: SortedRanges<UnitIndex> + }[] = [] + for (const e of loci.elements) { + const { unit, indices } = e + if (OrderedSet.size(indices) === 0) continue + + const ranges: UnitIndex[] = []; + const set: UnitIndex[] = []; + + if (OrderedSet.isInterval(indices)) { + if (OrderedSet.size(indices) === 1) { + set.push(Interval.min(indices)) + } else { + ranges.push(Interval.min(indices), Interval.max(indices)) + } + } else { + let i = 0, len = indices.length; + while (i < len) { + const start = i; + i++; + while (i < len && indices[i - 1] + 1 === indices[i]) i++; + const end = i; + if (end - start > 2) { + ranges.push(indices[start], indices[end - 1]) + } else { + for (let j = start; j < end; j++) { + set[set.length] = indices[j] + } + } + } + } + + _elements.push({ + unit, + set: SortedArray.ofSortedArray(set), + ranges: SortedRanges.ofSortedRanges(ranges) + }) + } + + const elementGroups = new Map<number, { + groupedUnits: Map<number, number[]> + set: SortedArray<UnitIndex> + ranges: SortedRanges<UnitIndex> + }>(); + for (let i = 0, il = _elements.length; i < il; ++i) { + const e = _elements[i] + const key = hash2(hashFnv32a(e.ranges), hashFnv32a(e.set)) + if (elementGroups.has(key)) { + const { groupedUnits } = elementGroups.get(key)! + if (groupedUnits.has(e.unit.invariantId)) { + groupedUnits.get(e.unit.invariantId)!.push(e.unit.id) + } else { + groupedUnits.set(e.unit.invariantId, [e.unit.id]) + } + } else { + const groupedUnits = new Map<number, number[]>() + groupedUnits.set(e.unit.invariantId, [e.unit.id]) + elementGroups.set(key, { groupedUnits, set: e.set, ranges: e.ranges }) + } + } + + const elements: QueryElement[] = [] + elementGroups.forEach(e => { + const groupedUnits: SortedArray<number>[] = [] + e.groupedUnits.forEach(g => groupedUnits.push(SortedArray.ofUnsortedArray(g))) + groupedUnits.sort((a, b) => a[0] - b[0]) // sort by first unit id of each group + elements.push({ groupedUnits, set: e.set, ranges: e.ranges }) + }) + + return { hash: loci.structure.root.hashCode, elements } + } + + function getUnitsFromIds(unitIds: ArrayLike<number>, structure: Structure) { + const units: Unit[] = [] + for (let i = 0, il = unitIds.length; i < il; ++i) { + const unitId = unitIds[i] + if (structure.unitMap.has(unitId)) units.push(structure.unitMap.get(unitId)) + } + return units + } + + export function toLoci(query: Query, parent: Structure): Loci { + if (query.hash !== -1 && query.hash !== parent.root.hashCode) { + new Error('Query not compatible with given structure') + } + const elements: Loci['elements'][0][] = [] + for (const e of query.elements) { + for (const g of e.groupedUnits) { + const units = getUnitsFromIds(g, parent) + if (units.length === 0) continue + + let indices: OrderedSet<UnitIndex> + if (e.ranges.length === 0) { + indices = e.set + } else if (e.set.length === 0) { + if (e.ranges.length === 2) { + indices = Interval.ofRange(e.ranges[0], e.ranges[1]) + } else { + const _indices = new Int32Array(SortedRanges.size(e.ranges)) + SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = v) + indices = SortedArray.ofSortedArray(_indices) + } + } else { + const rangesSize = SortedRanges.size(e.ranges) + const _indices = new Int32Array(e.set.length + rangesSize) + SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = v) + _indices.set(e.set, rangesSize) + indices = SortedArray.ofUnsortedArray(_indices) // requires sort + } + + for (const unit of units) { + elements.push({ unit, indices }) + } + } + } + return Loci(parent, elements) + } + + export function toStructure(query: Query, parent: Structure): Structure { + if (query.hash !== -1 && query.hash !== parent.root.hashCode) { + new Error('Query not compatible with given structure') + } + const units: Unit[] = [] + for (const e of query.elements) { + for (const g of e.groupedUnits) { + const _units = getUnitsFromIds(g, parent) + if (_units.length === 0) continue + + const unit = _units[0] // the elements are grouped by unit.invariantId + const rangesSize = SortedRanges.size(e.ranges) + const _indices = new Int32Array(e.set.length + rangesSize) + let indices: SortedArray<ElementIndex> + if (e.ranges.length === 0) { + for (let i = 0, il = e.set.length; i < il; ++i) { + _indices[i] = unit.elements[e.set[i]] + } + indices = SortedArray.ofSortedArray(_indices) + } else if (e.set.length === 0) { + SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = unit.elements[v]) + indices = SortedArray.ofSortedArray(_indices) + } else { + const rangesSize = SortedRanges.size(e.ranges) + SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = unit.elements[v]) + SortedRanges.forEach(e.set, (v, i) => _indices[i + rangesSize] = unit.elements[v]) + indices = SortedArray.ofUnsortedArray(_indices) // requires sort + } + + for (const unit of _units) { + units.push(unit.getChild(indices)) + } + } + } + return Structure.create(units, { parent }) + } + + export function areEqual(a: Query, b: Query) { + if (a.elements.length !== b.elements.length) return false + for (let i = 0, il = a.elements.length; i < il; ++i) { + const elementA = a.elements[i], elementB = b.elements[i] + if (elementA.groupedUnits.length !== elementB.groupedUnits.length) return false + for (let j = 0, jl = elementB.groupedUnits.length; j < jl; ++j) { + if (!SortedArray.areEqual(elementA.groupedUnits[j], elementB.groupedUnits[j])) return false + } + if (!SortedArray.areEqual(elementA.set, elementB.set)) return false + if (!SortedRanges.areEqual(elementA.ranges, elementB.ranges)) return false + } + return true + } +} \ No newline at end of file diff --git a/src/mol-model/structure/structure/element/stats.ts b/src/mol-model/structure/structure/element/stats.ts new file mode 100644 index 0000000000000000000000000000000000000000..2d53df6001a0f719b2f459c38d18f5f402d8b465 --- /dev/null +++ b/src/mol-model/structure/structure/element/stats.ts @@ -0,0 +1,125 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { OrderedSet } from '../../../../mol-data/int'; +import Unit from '../unit'; +import { Loci } from './loci'; +import { Location } from './location'; + + +export interface Stats { + elementCount: number + residueCount: number + unitCount: number + + firstElementLoc: Location + firstResidueLoc: Location + firstUnitLoc: Location +} + +export namespace Stats { + export function create(): Stats { + return { + elementCount: 0, + residueCount: 0, + unitCount: 0, + + firstElementLoc: Location.create(), + firstResidueLoc: Location.create(), + firstUnitLoc: Location.create(), + } + } + + function handleElement(stats: Stats, element: Loci['elements'][0]) { + const { indices, unit } = element + const { elements } = unit + const size = OrderedSet.size(indices) + if (size === 1) { + stats.elementCount += 1 + if (stats.elementCount === 1) { + Location.set(stats.firstElementLoc, unit, elements[OrderedSet.start(indices)]) + } + } else if (size === elements.length) { + stats.unitCount += 1 + if (stats.unitCount === 1) { + Location.set(stats.firstUnitLoc, unit, elements[OrderedSet.start(indices)]) + } + } else { + if (Unit.isAtomic(unit)) { + const { index, offsets } = unit.model.atomicHierarchy.residueAtomSegments + let i = 0 + while (i < size) { + const eI = elements[OrderedSet.getAt(indices, i)] + const rI = index[eI] + if (offsets[rI] !== eI) { + // partial residue, start missing + ++i + stats.elementCount += 1 + while (i < size && index[elements[OrderedSet.getAt(indices, i)]] === rI) { + ++i + stats.elementCount += 1 + } + } else { + ++i + while (i < size && index[elements[OrderedSet.getAt(indices, i)]] === rI) { + ++i + } + + if (offsets[rI + 1] - 1 === elements[OrderedSet.getAt(indices, i - 1)]) { + // full residue + stats.residueCount += 1 + if (stats.residueCount === 1) { + Location.set(stats.firstResidueLoc, unit, elements[OrderedSet.start(indices)]) + } + } else { + // partial residue, end missing + stats.elementCount += offsets[rI + 1] - 1 - elements[OrderedSet.getAt(indices, i - 1)] + } + } + } + } else { + // TODO + stats.elementCount += size + if (stats.elementCount === 1) { + Location.set(stats.firstElementLoc, unit, elements[OrderedSet.start(indices)]) + } + } + } + } + + export function ofLoci(loci: Loci) { + const stats = create() + if (loci.elements.length > 0) { + for (const e of loci.elements) handleElement(stats, e) + } + return stats + } + + export function add(out: Stats, a: Stats, b: Stats) { + if (a.elementCount === 1 && b.elementCount === 0) { + Location.copy(out.firstElementLoc, a.firstElementLoc) + } else if (a.elementCount === 0 && b.elementCount === 1) { + Location.copy(out.firstElementLoc, b.firstElementLoc) + } + + if (a.residueCount === 1 && b.residueCount === 0) { + Location.copy(out.firstResidueLoc, a.firstResidueLoc) + } else if (a.residueCount === 0 && b.residueCount === 1) { + Location.copy(out.firstResidueLoc, b.firstResidueLoc) + } + + if (a.unitCount === 1 && b.unitCount === 0) { + Location.copy(out.firstUnitLoc, a.firstUnitLoc) + } else if (a.unitCount === 0 && b.unitCount === 1) { + Location.copy(out.firstUnitLoc, b.firstUnitLoc) + } + + out.elementCount = a.elementCount + b.elementCount + out.residueCount = a.residueCount + b.residueCount + out.unitCount = a.unitCount + b.unitCount + return out + } +} \ No newline at end of file diff --git a/src/mol-model/structure/structure/properties.ts b/src/mol-model/structure/structure/properties.ts index 4a83b97bc063949aa579946be8bf431a7da86e6a..046c32f78fe03fda22f9f4c9b1878bf49550b5d3 100644 --- a/src/mol-model/structure/structure/properties.ts +++ b/src/mol-model/structure/structure/properties.ts @@ -53,7 +53,7 @@ const atom = { vdw_radius: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element))), } -function compId(l: StructureElement) { +function compId(l: StructureElement.Location) { return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element]) } @@ -106,7 +106,7 @@ const coarse = { gaussian_covariance_matrix: p(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.covariance_matrix[l.element]) } -function eK(l: StructureElement) { +function eK(l: StructureElement.Location) { switch (l.unit.kind) { case Unit.Kind.Atomic: return l.unit.model.atomicHierarchy.index.getEntityFromChain(l.unit.chainIndex[l.element]) diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index a75d49e2d0a511d89772f91cdbd802807dd4a760..d5575c999a4731e100788c31e230e510b18892e4 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -145,7 +145,7 @@ class Structure { } /** Returns a new element location iterator */ - elementLocations(): Iterator<StructureElement> { + elementLocations(): Iterator<StructureElement.Location> { return new Structure.ElementLocationIterator(this); } @@ -256,7 +256,7 @@ class Structure { return this._props.representativeModel } - hasElement(e: StructureElement) { + hasElement(e: StructureElement.Location) { if (!this.unitMap.has(e.unit.id)) return false; return SortedArray.has(this.unitMap.get(e.unit.id).elements, e.element); } @@ -325,7 +325,7 @@ function getModels(s: Structure) { function getUniqueResidueNames(s: Structure) { const prop = StructureProperties.residue.label_comp_id; const names = new Set<string>(); - const loc = StructureElement.create(); + const loc = StructureElement.Location.create(); for (const unit of s.units) { // TODO: support coarse unit? if (!Unit.isAtomic(unit)) continue; @@ -342,7 +342,7 @@ function getUniqueResidueNames(s: Structure) { function getEntityIndices(structure: Structure): ReadonlyArray<EntityIndex> { const { units } = structure; - const l = StructureElement.create(); + const l = StructureElement.Location.create(); const keys = UniqueArray.create<number, EntityIndex>(); for (const unit of units) { @@ -697,15 +697,15 @@ namespace Structure { return a.root === b.root } - export class ElementLocationIterator implements Iterator<StructureElement> { - private current = StructureElement.create(); + export class ElementLocationIterator implements Iterator<StructureElement.Location> { + private current = StructureElement.Location.create(); private unitIndex = 0; private elements: StructureElement.Set; private maxIdx = 0; private idx = -1; hasNext: boolean; - move(): StructureElement { + move(): StructureElement.Location { this.advance(); this.current.element = this.elements[this.idx]; return this.current; diff --git a/src/mol-model/structure/structure/util/lookup3d.ts b/src/mol-model/structure/structure/util/lookup3d.ts index eefc39f0120ce8ba2b3f06d383ec331f10147866..1a3b8d5cd31e529e64a32179989fa875d15e42d3 100644 --- a/src/mol-model/structure/structure/util/lookup3d.ts +++ b/src/mol-model/structure/structure/util/lookup3d.ts @@ -89,7 +89,7 @@ export class StructureLookup3D { const closeUnits = this.unitLookup.find(x, y, z, radius); if (closeUnits.count === 0) return; - const se = StructureElement.create(); + const se = StructureElement.Location.create(); const queryRadius = pivotR + maxRadius + radius; for (let t = 0, _t = closeUnits.count; t < _t; t++) { diff --git a/src/mol-model/structure/structure/util/subset-builder.ts b/src/mol-model/structure/structure/util/subset-builder.ts index 75f1127f3e4441badb6fc5b69caca48bed290b52..56f1d14c656537a228f79861e535ef3d4c67e6c5 100644 --- a/src/mol-model/structure/structure/util/subset-builder.ts +++ b/src/mol-model/structure/structure/util/subset-builder.ts @@ -101,7 +101,7 @@ export class StructureSubsetBuilder { return this._getStructure(true); } - setSingletonLocation(location: StructureElement) { + setSingletonLocation(location: StructureElement.Location) { const id = this.ids[0]; location.unit = this.parent.unitMap.get(id); location.element = this.unitMap.get(id)[0]; diff --git a/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts b/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts index 77cceda3d9039a2abc86d4dc9749638c17eef960..91bf463a64fdc79a31f5f6159d49759d1a402a89 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts +++ b/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts @@ -71,7 +71,7 @@ function labelPDBeValidation(loci: Loci): string | undefined { const u = e.unit; if (!u.model.customProperties.has(StructureQualityReport.Descriptor)) return void 0; - const se = StructureElement.create(u, u.elements[OrderedSet.getAt(e.indices, 0)]); + const se = StructureElement.Location.create(u, u.elements[OrderedSet.getAt(e.indices, 0)]); const issues = StructureQualityReport.getIssues(se); if (issues.length === 0) return 'PDBe Validation: No Issues'; return `PDBe Validation: ${issues.join(', ')}`; diff --git a/src/mol-plugin/behavior/dynamic/labels.ts b/src/mol-plugin/behavior/dynamic/labels.ts index 7873a7efd3e96eee028903a2ee13f0b4745db4f2..e559d04cf7a19443d4a99a99820619d863fba560 100644 --- a/src/mol-plugin/behavior/dynamic/labels.ts +++ b/src/mol-plugin/behavior/dynamic/labels.ts @@ -131,7 +131,7 @@ export const SceneLabels = PluginBehavior.create<SceneLabelsProps>({ } private updateLabels(p: SceneLabelsProps) { - const l = StructureElement.create() + const l = StructureElement.Location.create() const { texts, positions, sizes, depths } = this.data texts.length = 0 diff --git a/src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts b/src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts index e585d317da9a2ea61d3dd2b422680855507a294d..afa1181872b5aa1b2de7dfbeed767e5f3946b380 100644 --- a/src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts +++ b/src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts @@ -132,7 +132,7 @@ export class StructureRepresentationInteractionBehavior extends PluginBehavior.W } // TODO: support link loci as well? - if (!StructureElement.isLoci(current.loci)) return; + if (!StructureElement.Loci.is(current.loci)) return; const parent = this.plugin.helpers.substructureParent.get(current.loci.structure); if (!parent || !parent.obj) return; diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts index 5eb0071828a1283ed66247e3cf00a9dff099252b..efbf984c8b414e94571038c3253d63ec4597f92b 100644 --- a/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts +++ b/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts @@ -207,7 +207,7 @@ export namespace VolumeStreaming { // TODO: support link loci as well? // Perhaps structure loci too? - if (!StructureElement.isLoci(current.loci)) return; + if (!StructureElement.Loci.is(current.loci)) return; const parent = this.plugin.helpers.substructureParent.get(current.loci.structure); if (!parent) return; diff --git a/src/mol-plugin/ui/sequence.tsx b/src/mol-plugin/ui/sequence.tsx index d6208b12898a8c422f12dbdff1d6aaeaea94ccdc..5eaaf0edaeeb97febf91c8694bb2eaa8c691c7c4 100644 --- a/src/mol-plugin/ui/sequence.tsx +++ b/src/mol-plugin/ui/sequence.tsx @@ -19,7 +19,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { HeteroSequenceWrapper } from './sequence/hetero'; import { State, StateSelection } from '../../mol-state'; -function opKey(l: StructureElement) { +function opKey(l: StructureElement.Location) { const ids = SP.unit.pdbx_struct_oper_list_ids(l) const ncs = SP.unit.struct_ncs_oper_id(l) const hkl = SP.unit.hkl(l) @@ -29,9 +29,9 @@ function opKey(l: StructureElement) { function getSequenceWrapper(state: SequenceViewState, structureSelection: StructureElementSelectionManager): SequenceWrapper.Any | undefined { const { structure, entityId, invariantUnitId, operatorKey } = state - const l = StructureElement.create() + const l = StructureElement.Location.create() for (const unit of structure.units) { - StructureElement.set(l, unit, unit.elements[0]) + StructureElement.Location.set(l, unit, unit.elements[0]) if (SP.entity.id(l) !== entityId) continue if (unit.invariantId !== invariantUnitId) continue if (opKey(l) !== operatorKey) continue @@ -45,11 +45,11 @@ function getSequenceWrapper(state: SequenceViewState, structureSelection: Struct function getEntityOptions(structure: Structure) { const options: [string, string][] = [] - const l = StructureElement.create() + const l = StructureElement.Location.create() const seen = new Set<string>() for (const unit of structure.units) { - StructureElement.set(l, unit, unit.elements[0]) + StructureElement.Location.set(l, unit, unit.elements[0]) const id = SP.entity.id(l) if (seen.has(id)) continue @@ -64,12 +64,12 @@ function getEntityOptions(structure: Structure) { function getUnitOptions(structure: Structure, entityId: string) { const options: [number, string][] = [] - const l = StructureElement.create() + const l = StructureElement.Location.create() const seen = new Set<number>() const water = new Map<string, number>() for (const unit of structure.units) { - StructureElement.set(l, unit, unit.elements[0]) + StructureElement.Location.set(l, unit, unit.elements[0]) if (SP.entity.id(l) !== entityId) continue const id = unit.invariantId @@ -100,11 +100,11 @@ function getUnitOptions(structure: Structure, entityId: string) { function getOperatorOptions(structure: Structure, entityId: string, invariantUnitId: number) { const options: [string, string][] = [] - const l = StructureElement.create() + const l = StructureElement.Location.create() const seen = new Set<string>() for (const unit of structure.units) { - StructureElement.set(l, unit, unit.elements[0]) + StructureElement.Location.set(l, unit, unit.elements[0]) if (SP.entity.id(l) !== entityId) continue if (unit.invariantId !== invariantUnitId) continue diff --git a/src/mol-plugin/ui/sequence/hetero.ts b/src/mol-plugin/ui/sequence/hetero.ts index edf2ee50c5822ae664c7558e431b976ea59c812e..b904320c011e2a9b8df2c6526c5f823b6819c4eb 100644 --- a/src/mol-plugin/ui/sequence/hetero.ts +++ b/src/mol-plugin/ui/sequence/hetero.ts @@ -25,7 +25,7 @@ export class HeteroSequenceWrapper extends SequenceWrapper<StructureUnit> { eachResidue(loci: Loci, apply: (set: OrderedSet) => boolean) { let changed = false const { structure, unit } = this.data - if (StructureElement.isLoci(loci)) { + if (StructureElement.Loci.is(loci)) { if (!Structure.areRootsEquivalent(loci.structure, structure)) return false loci = StructureElement.Loci.remap(loci, structure) diff --git a/src/mol-plugin/ui/sequence/polymer.ts b/src/mol-plugin/ui/sequence/polymer.ts index fa25a3255f229c4a0d8d89cef2a5ae42db302c2a..641e8288b0f5bc9f9c8872e9db762be710394bfc 100644 --- a/src/mol-plugin/ui/sequence/polymer.ts +++ b/src/mol-plugin/ui/sequence/polymer.ts @@ -36,7 +36,7 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> { eachResidue(loci: Loci, apply: (set: OrderedSet) => boolean) { let changed = false const { structure, unit } = this.data - if (StructureElement.isLoci(loci)) { + if (StructureElement.Loci.is(loci)) { if (!Structure.areRootsEquivalent(loci.structure, structure)) return false loci = StructureElement.Loci.remap(loci, structure) @@ -62,7 +62,7 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> { } constructor(data: StructureUnit) { - const l = StructureElement.create(data.unit, data.unit.elements[0]) + const l = StructureElement.Location.create(data.unit, data.unit.elements[0]) const sequence = data.unit.model.sequence.byEntityKey[SP.entity.key(l)].sequence const length = sequence.sequence.length const markerArray = new Uint8Array(length) diff --git a/src/mol-plugin/util/interactivity.ts b/src/mol-plugin/util/interactivity.ts index 31babee35563feeb2b201004865e88f0fa347d20..c803b0dae2c327fadd26da80fb91f06983d77693 100644 --- a/src/mol-plugin/util/interactivity.ts +++ b/src/mol-plugin/util/interactivity.ts @@ -54,9 +54,9 @@ namespace Interactivity { const Granularity = { 'element': (loci: ModelLoci) => loci, - 'residue': (loci: ModelLoci) => SE.isLoci(loci) ? SE.Loci.extendToWholeResidues(loci) : loci, - 'chain': (loci: ModelLoci) => SE.isLoci(loci) ? SE.Loci.extendToWholeChains(loci) : loci, - 'structure': (loci: ModelLoci) => SE.isLoci(loci) ? Structure.Loci(loci.structure) : loci + 'residue': (loci: ModelLoci) => SE.Loci.is(loci) ? SE.Loci.extendToWholeResidues(loci) : loci, + 'chain': (loci: ModelLoci) => SE.Loci.is(loci) ? SE.Loci.extendToWholeChains(loci) : loci, + 'structure': (loci: ModelLoci) => SE.Loci.is(loci) ? Structure.Loci(loci.structure) : loci } type Granularity = keyof typeof Granularity const GranularityOptions = Object.keys(Granularity).map(n => [n, capitalize(n)]) as [Granularity, string][] @@ -103,7 +103,7 @@ namespace Interactivity { // convert to StructureElement.Loci of root structure loci = Structure.toStructureElementLoci(Structure.Loci(loci.structure.root)) } - if (StructureElement.isLoci(loci) && loci.structure.parent) { + if (StructureElement.Loci.is(loci) && loci.structure.parent) { // ensure the root structure is used loci = StructureElement.Loci.remap(loci, loci.structure.parent) } @@ -129,7 +129,7 @@ namespace Interactivity { const { current, modifiers } = e const normalized: Loci<ModelLoci> = this.normalizedLoci(current) - if (StructureElement.isLoci(normalized.loci)) { + if (StructureElement.Loci.is(normalized.loci)) { let loci: StructureElement.Loci = normalized.loci; if (modifiers && modifiers.shift) { loci = this.sel.tryGetRange(loci) || loci; @@ -175,7 +175,7 @@ namespace Interactivity { const sels = this.sel.clear(); for (const s of sels) this.mark({ loci: s }, MarkerAction.Deselect); } - } else if (StructureElement.isLoci(normalized.loci)) { + } else if (StructureElement.Loci.is(normalized.loci)) { if (modifiers.control && buttons === ButtonsType.Flag.Secondary) { // select only the current element on Ctrl + Right-Click const old = this.sel.get(normalized.loci.structure); diff --git a/src/mol-plugin/util/structure-element-selection.ts b/src/mol-plugin/util/structure-element-selection.ts index 441b7c51c6fcb3b80819c4d8c818f0dda4d7b3a7..bf82095a7158f553b5e547ae3b6201a3a9625275 100644 --- a/src/mol-plugin/util/structure-element-selection.ts +++ b/src/mol-plugin/util/structure-element-selection.ts @@ -52,7 +52,7 @@ class StructureElementSelectionManager { } add(loci: Loci): Loci { - if (StructureElement.isLoci(loci)) { + if (StructureElement.Loci.is(loci)) { const entry = this.getEntry(loci.structure); if (entry) { entry.selection = StructureElement.Loci.union(entry.selection, loci); @@ -64,7 +64,7 @@ class StructureElementSelectionManager { } remove(loci: Loci): Loci { - if (StructureElement.isLoci(loci)) { + if (StructureElement.Loci.is(loci)) { const entry = this.getEntry(loci.structure); if (entry) { entry.selection = StructureElement.Loci.subtract(entry.selection, loci); @@ -76,7 +76,7 @@ class StructureElementSelectionManager { } set(loci: Loci): Loci { - if (StructureElement.isLoci(loci)) { + if (StructureElement.Loci.is(loci)) { const entry = this.getEntry(loci.structure); if (entry) { entry.selection = loci; @@ -108,7 +108,7 @@ class StructureElementSelectionManager { } has(loci: Loci) { - if (StructureElement.isLoci(loci)) { + if (StructureElement.Loci.is(loci)) { const entry = this.getEntry(loci.structure); if (entry) { return StructureElement.Loci.areIntersecting(loci, entry.selection); @@ -118,7 +118,7 @@ class StructureElementSelectionManager { } tryGetRange(loci: Loci): StructureElement.Loci | undefined { - if (!StructureElement.isLoci(loci)) return; + if (!StructureElement.Loci.is(loci)) return; if (loci.elements.length !== 1) return; const entry = this.getEntry(loci.structure); if (!entry) return; @@ -139,7 +139,7 @@ class StructureElementSelectionManager { private prevHighlight: StructureElement.Loci | undefined = void 0; accumulateInteractiveHighlight(loci: Loci) { - if (StructureElement.isLoci(loci)) { + if (StructureElement.Loci.is(loci)) { if (this.prevHighlight) { this.prevHighlight = StructureElement.Loci.union(this.prevHighlight, loci); } else { diff --git a/src/mol-plugin/util/structure-labels.ts b/src/mol-plugin/util/structure-labels.ts index 3e315953fd9f2fafc260cf4407f9b16b7ebbf4b1..bad1eb23e6b093cc86a4cca893dca3043f8c2d84 100644 --- a/src/mol-plugin/util/structure-labels.ts +++ b/src/mol-plugin/util/structure-labels.ts @@ -75,7 +75,7 @@ function getLabelDataStatic(structure: Structure, text: string, size: number, po function getLabelDataComputed(structure: Structure, level: 'elements' | 'residues'): LabelsData { const data: LabelsData = { texts: [], positions: [], sizes: [], depths: [] }; - const l = StructureElement.create(); + const l = StructureElement.Location.create(); const { units } = structure; const { label_atom_id } = StructureProperties.atom; diff --git a/src/mol-repr/structure/complex-representation.ts b/src/mol-repr/structure/complex-representation.ts index 120e7332dc1569eabbc2dc60c513007994a16aae..4ffd090a3693850cf6bd409b1ef337c750ffcc81 100644 --- a/src/mol-repr/structure/complex-representation.ts +++ b/src/mol-repr/structure/complex-representation.ts @@ -56,9 +56,9 @@ export function ComplexRepresentation<P extends StructureParams>(label: string, function mark(loci: Loci, action: MarkerAction) { if (!_structure) return false - if (!StructureElement.isLoci(loci) && !Link.isLoci(loci)) return false + if (!StructureElement.Loci.is(loci) && !Link.isLoci(loci)) return false if (!Structure.areRootsEquivalent(loci.structure, _structure)) return false - if (StructureElement.isLoci(loci)) { + if (StructureElement.Loci.is(loci)) { loci = StructureElement.Loci.remap(loci, _structure) } else if (Link.isLoci(loci)) { loci = Link.remapLoci(loci, _structure) diff --git a/src/mol-repr/structure/units-representation.ts b/src/mol-repr/structure/units-representation.ts index 9bc1adc3e3865225057c07533e2d87f5e5746d8e..6eb6ba7a96985b31519468f58be4b3f989061847 100644 --- a/src/mol-repr/structure/units-representation.ts +++ b/src/mol-repr/structure/units-representation.ts @@ -165,9 +165,9 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: R function mark(loci: Loci, action: MarkerAction) { let changed = false if (!_structure) return false - if (!StructureElement.isLoci(loci) && !Link.isLoci(loci)) return false + if (!StructureElement.Loci.is(loci) && !Link.isLoci(loci)) return false if (!Structure.areRootsEquivalent(loci.structure, _structure)) return false - if (StructureElement.isLoci(loci)) { + if (StructureElement.Loci.is(loci)) { loci = StructureElement.Loci.remap(loci, _structure) if (loci.elements.length === 0) return false } else if (Link.isLoci(loci)) { diff --git a/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts b/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts index 382adbf8f6f1c9fadf3e5f78db670377906d2e98..d52daa29792d84bd29a0ac27de488d261dbb0369 100644 --- a/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts +++ b/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts @@ -25,7 +25,7 @@ function createCarbohydrateLinkCylinderMesh(ctx: VisualContext, structure: Struc const { links, elements } = structure.carbohydrates const { linkSizeFactor } = props - const location = StructureElement.create() + const location = StructureElement.Location.create() const builderProps = { linkCount: links.length, @@ -128,7 +128,7 @@ function eachCarbohydrateLink(loci: Loci, structure: Structure, apply: (interval if (apply(Interval.ofSingleton(idx))) changed = true } } - } else if (StructureElement.isLoci(loci)) { + } else if (StructureElement.Loci.is(loci)) { if (!Structure.areEquivalent(loci.structure, structure)) return false // TODO mark link only when both of the link elements are in a StructureElement.Loci const { getElementIndex, getLinkIndices, elements } = structure.carbohydrates diff --git a/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts b/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts index db874c3fd941b8318f9bd7f96ecbf08a87ae2e82..93665ab844f151c4cf563ccaaac47ab17d1d4136 100644 --- a/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts +++ b/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts @@ -51,7 +51,7 @@ function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Structure, const carbohydrates = structure.carbohydrates const n = carbohydrates.elements.length - const l = StructureElement.create() + const l = StructureElement.Location.create() for (let i = 0; i < n; ++i) { const c = carbohydrates.elements[i]; @@ -168,7 +168,7 @@ function CarbohydrateElementIterator(structure: Structure): LocationIterator { const carbElements = structure.carbohydrates.elements const groupCount = carbElements.length * 2 const instanceCount = 1 - const location = StructureElement.create() + const location = StructureElement.Location.create() function getLocation (groupIndex: number, instanceIndex: number) { const carb = carbElements[Math.floor(groupIndex / 2)] location.unit = carb.unit @@ -195,7 +195,7 @@ function getCarbohydrateLoci(pickingId: PickingId, structure: Structure, id: num function eachCarbohydrate(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) { const { getElementIndex, getAnomericCarbons } = structure.carbohydrates let changed = false - if (!StructureElement.isLoci(loci)) return false + if (!StructureElement.Loci.is(loci)) return false if (!Structure.areEquivalent(loci.structure, structure)) return false for (const e of loci.elements) { // TODO make more efficient by handling/grouping `e.indices` by residue index diff --git a/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts b/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts index b0c92d1f7dd6fe23f4456ee1c2c1406d55f50ac0..0dbb6c91bf8e9542d9e54011c4a7cde4c55c31f7 100644 --- a/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts +++ b/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts @@ -25,7 +25,7 @@ function createCarbohydrateTerminalLinkCylinderMesh(ctx: VisualContext, structur const { terminalLinks, elements } = structure.carbohydrates const { linkSizeFactor } = props - const location = StructureElement.create() + const location = StructureElement.Location.create() const builderProps = { linkCount: terminalLinks.length, @@ -141,7 +141,7 @@ function eachTerminalLink(loci: Loci, structure: Structure, apply: (interval: In if (apply(Interval.ofSingleton(idx))) changed = true } } - } else if (StructureElement.isLoci(loci)) { + } else if (StructureElement.Loci.is(loci)) { if (!Structure.areEquivalent(loci.structure, structure)) return false // TODO mark link only when both of the link elements are in a StructureElement.Loci const { getElementIndex, getTerminalLinkIndices, elements } = structure.carbohydrates diff --git a/src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts b/src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts index b215e9e039ca926350214e740552801856b1c4b7..53dfc5a29f5d257591196632ebcde21430255418 100644 --- a/src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts +++ b/src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts @@ -26,7 +26,7 @@ function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structure: Str if (!crossLinks.count) return Mesh.createEmpty(mesh) const { sizeFactor } = props - const location = StructureElement.create() + const location = StructureElement.Location.create() const builderProps = { linkCount: crossLinks.count, diff --git a/src/mol-repr/structure/visual/inter-unit-link-cylinder.ts b/src/mol-repr/structure/visual/inter-unit-link-cylinder.ts index 615f29cb4165f4d341490817282a799e91c7d91f..0ca86e11c7da0ad4fb5f9f6b24cfdbb135eaac8d 100644 --- a/src/mol-repr/structure/visual/inter-unit-link-cylinder.ts +++ b/src/mol-repr/structure/visual/inter-unit-link-cylinder.ts @@ -26,7 +26,7 @@ function createInterUnitLinkCylinderMesh(ctx: VisualContext, structure: Structur if (!bondCount) return Mesh.createEmpty(mesh) - const location = StructureElement.create() + const location = StructureElement.Location.create() const builderProps = { linkCount: bondCount, @@ -112,7 +112,7 @@ function eachLink(loci: Loci, structure: Structure, apply: (interval: Interval) if (apply(Interval.ofSingleton(idx))) changed = true } } - } else if (StructureElement.isLoci(loci)) { + } else if (StructureElement.Loci.is(loci)) { if (!Structure.areEquivalent(loci.structure, structure)) return false // TODO mark link only when both of the link elements are in a StructureElement.Loci for (const e of loci.elements) { diff --git a/src/mol-repr/structure/visual/intra-unit-link-cylinder.ts b/src/mol-repr/structure/visual/intra-unit-link-cylinder.ts index 4ead21c9529e610dc50452a0267cbc4997dd55a6..238527bf8f72d2fd5b783a17df3b439841610f8c 100644 --- a/src/mol-repr/structure/visual/intra-unit-link-cylinder.ts +++ b/src/mol-repr/structure/visual/intra-unit-link-cylinder.ts @@ -23,7 +23,7 @@ import { isHydrogen } from './util/common'; function createIntraUnitLinkCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<IntraUnitLinkParams>, mesh?: Mesh) { if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh) - const location = StructureElement.create(unit) + const location = StructureElement.Location.create(unit) const elements = unit.elements; const links = unit.links @@ -140,7 +140,7 @@ function eachLink(loci: Loci, structureGroup: StructureGroup, apply: (interval: } } } - } else if (StructureElement.isLoci(loci)) { + } else if (StructureElement.Loci.is(loci)) { const { structure, group } = structureGroup if (!Structure.areEquivalent(loci.structure, structure)) return false const unit = group.units[0] diff --git a/src/mol-repr/structure/visual/util/common.ts b/src/mol-repr/structure/visual/util/common.ts index 40041e13cb7dcf646c855d238ab5090289dff78d..d38dc7d08ba8a152d84cb13313b0277546dac8b2 100644 --- a/src/mol-repr/structure/visual/util/common.ts +++ b/src/mol-repr/structure/visual/util/common.ts @@ -127,7 +127,7 @@ export function getUnitConformationAndRadius(unit: Unit, ignoreHydrogens = false id } - const l = StructureElement.create(unit) + const l = StructureElement.Location.create(unit) const sizeTheme = PhysicalSizeTheme({}, {}) const radius = (index: number) => { l.element = index as ElementIndex @@ -138,7 +138,7 @@ export function getUnitConformationAndRadius(unit: Unit, ignoreHydrogens = false } export function getStructureConformationAndRadius(structure: Structure, ignoreHydrogens = false) { - const l = StructureElement.create() + const l = StructureElement.Location.create() const sizeTheme = PhysicalSizeTheme({}, {}) let xs: ArrayLike<number> diff --git a/src/mol-repr/structure/visual/util/element.ts b/src/mol-repr/structure/visual/util/element.ts index d70d4fa10545dbfd2d27a903629b50a5db7576ff..9213738fa6fae894309d2273eb3cbbadbaef4247 100644 --- a/src/mol-repr/structure/visual/util/element.ts +++ b/src/mol-repr/structure/visual/util/element.ts @@ -37,7 +37,7 @@ export function createElementSphereMesh(ctx: VisualContext, unit: Unit, structur const v = Vec3.zero() const pos = unit.conformation.invariantPosition - const l = StructureElement.create() + const l = StructureElement.Location.create() l.unit = unit for (let i = 0; i < elementCount; i++) { @@ -78,7 +78,7 @@ export function createElementSphereImpostor(ctx: VisualContext, unit: Unit, stru export function eachElement(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) { let changed = false - if (!StructureElement.isLoci(loci)) return false + if (!StructureElement.Loci.is(loci)) return false const { structure, group } = structureGroup if (!Structure.areEquivalent(loci.structure, structure)) return false const elementCount = group.elements.length @@ -115,7 +115,7 @@ export function getElementLoci(pickingId: PickingId, structureGroup: StructureGr export function eachSerialElement(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) { let changed = false - if (!StructureElement.isLoci(loci)) return false + if (!StructureElement.Loci.is(loci)) return false if (!Structure.areEquivalent(loci.structure, structure)) return false const { unitElementCount } = structure.serialMapping for (const e of loci.elements) { @@ -155,7 +155,7 @@ export namespace ElementIterator { export function fromGroup(group: Unit.SymmetryGroup): LocationIterator { const groupCount = group.elements.length const instanceCount = group.units.length - const location = StructureElement.create() + const location = StructureElement.Location.create() const getLocation = (groupIndex: number, instanceIndex: number) => { const unit = group.units[instanceIndex] location.unit = unit @@ -170,7 +170,7 @@ export namespace ElementIterator { const groupCount = elementCount const instanceCount = 1 const { unitIndices, elementIndices } = structure.serialMapping - const location = StructureElement.create() + const location = StructureElement.Location.create() const getLocation = (groupIndex: number) => { location.unit = units[unitIndices[groupIndex]] location.element = elementIndices[groupIndex] diff --git a/src/mol-repr/structure/visual/util/link.ts b/src/mol-repr/structure/visual/util/link.ts index 2f913a312573fffdc252261dc1e87db96f48f30a..c3799fc74868346c7a4793dbbaa2ce5ee051f13f 100644 --- a/src/mol-repr/structure/visual/util/link.ts +++ b/src/mol-repr/structure/visual/util/link.ts @@ -130,7 +130,7 @@ export namespace LinkIterator { const unit = group.units[0] const groupCount = Unit.isAtomic(unit) ? unit.links.edgeCount * 2 : 0 const instanceCount = group.units.length - const location = StructureElement.create() + const location = StructureElement.Location.create() const getLocation = (groupIndex: number, instanceIndex: number) => { const unit = group.units[instanceIndex] location.unit = unit diff --git a/src/mol-repr/structure/visual/util/nucleotide.ts b/src/mol-repr/structure/visual/util/nucleotide.ts index 9d9787fb63382ba5f69e82dd17ce5e639226808a..7bc2400ddb4609887f8e4b789cd9216d0484dcec 100644 --- a/src/mol-repr/structure/visual/util/nucleotide.ts +++ b/src/mol-repr/structure/visual/util/nucleotide.ts @@ -18,7 +18,7 @@ export namespace NucleotideLocationIterator { const nucleotideElementIndices = Unit.isAtomic(u) ? u.nucleotideElements : [] const groupCount = nucleotideElementIndices.length const instanceCount = group.units.length - const location = StructureElement.create() + const location = StructureElement.Location.create() const getLocation = (groupIndex: number, instanceIndex: number) => { const unit = group.units[instanceIndex] location.unit = unit @@ -47,7 +47,7 @@ export function getNucleotideElementLoci(pickingId: PickingId, structureGroup: S */ export function eachNucleotideElement(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) { let changed = false - if (!StructureElement.isLoci(loci)) return false + if (!StructureElement.Loci.is(loci)) return false const { structure, group } = structureGroup if (!Structure.areEquivalent(loci.structure, structure)) return false const unit = group.units[0] diff --git a/src/mol-repr/structure/visual/util/polymer.ts b/src/mol-repr/structure/visual/util/polymer.ts index 61f369720ae7a67976af5345429102d28ca14eb0..927bb2dc199f4a519265941ec3ad7fba6add65a8 100644 --- a/src/mol-repr/structure/visual/util/polymer.ts +++ b/src/mol-repr/structure/visual/util/polymer.ts @@ -39,7 +39,7 @@ export namespace PolymerLocationIterator { const polymerElements = group.units[0].polymerElements const groupCount = polymerElements.length const instanceCount = group.units.length - const location = StructureElement.create() + const location = StructureElement.Location.create() const getLocation = (groupIndex: number, instanceIndex: number) => { const unit = group.units[instanceIndex] location.unit = unit @@ -55,7 +55,7 @@ export namespace PolymerGapLocationIterator { const gapElements = group.units[0].gapElements const groupCount = gapElements.length const instanceCount = group.units.length - const location = StructureElement.create() + const location = StructureElement.Location.create() const getLocation = (groupIndex: number, instanceIndex: number) => { const unit = group.units[instanceIndex] location.unit = unit @@ -93,7 +93,7 @@ export function getPolymerElementLoci(pickingId: PickingId, structureGroup: Stru */ export function eachPolymerElement(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) { let changed = false - if (!StructureElement.isLoci(loci)) return false + if (!StructureElement.Loci.is(loci)) return false const { structure, group } = structureGroup if (!Structure.areEquivalent(loci.structure, structure)) return false const { polymerElements, model, elements } = group.units[0] @@ -175,7 +175,7 @@ export function eachPolymerGapElement(loci: Loci, structureGroup: StructureGroup } } } - } else if (StructureElement.isLoci(loci)) { + } else if (StructureElement.Loci.is(loci)) { const { structure, group } = structureGroup if (!Structure.areRootsEquivalent(loci.structure, structure)) return false loci = StructureElement.Loci.remap(loci, structure) diff --git a/src/mol-repr/structure/visual/util/polymer/backbone-iterator.ts b/src/mol-repr/structure/visual/util/polymer/backbone-iterator.ts index dcba744057537dabb3a73fd098ab1f5e02dad15a..42db2945f5522c3f781c3b81613711d0fbdbd0c4 100644 --- a/src/mol-repr/structure/visual/util/polymer/backbone-iterator.ts +++ b/src/mol-repr/structure/visual/util/polymer/backbone-iterator.ts @@ -21,14 +21,14 @@ export function PolymerBackboneIterator(unit: Unit): Iterator<PolymerBackbonePai } interface PolymerBackbonePair { - centerA: StructureElement - centerB: StructureElement + centerA: StructureElement.Location + centerB: StructureElement.Location } function createPolymerBackbonePair (unit: Unit) { return { - centerA: StructureElement.create(unit), - centerB: StructureElement.create(unit), + centerA: StructureElement.Location.create(unit), + centerB: StructureElement.Location.create(unit), } } diff --git a/src/mol-repr/structure/visual/util/polymer/gap-iterator.ts b/src/mol-repr/structure/visual/util/polymer/gap-iterator.ts index 11e6311543614b58269bc7dd7309669498bdec47..5b8208f57bc07ebae58b2ea0ab4610d89279ad2a 100644 --- a/src/mol-repr/structure/visual/util/polymer/gap-iterator.ts +++ b/src/mol-repr/structure/visual/util/polymer/gap-iterator.ts @@ -20,14 +20,14 @@ export function PolymerGapIterator(unit: Unit): Iterator<PolymerGapPair> { } interface PolymerGapPair { - centerA: StructureElement - centerB: StructureElement + centerA: StructureElement.Location + centerB: StructureElement.Location } function createPolymerGapPair (unit: Unit) { return { - centerA: StructureElement.create(unit), - centerB: StructureElement.create(unit), + centerA: StructureElement.Location.create(unit), + centerB: StructureElement.Location.create(unit), } } diff --git a/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts b/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts index d75cf4bd36ac28a166a938fad48533a54da691de..390b63bad83e32b208c315f3f3dbac1f80d1f96b 100644 --- a/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts +++ b/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts @@ -30,9 +30,9 @@ export function PolymerTraceIterator(unit: Unit, structure: Structure): Iterator } interface PolymerTraceElement { - center: StructureElement - centerPrev: StructureElement - centerNext: StructureElement + center: StructureElement.Location + centerPrev: StructureElement.Location + centerNext: StructureElement.Location first: boolean, last: boolean secStrucFirst: boolean, secStrucLast: boolean secStrucType: SecondaryStructureType @@ -48,9 +48,9 @@ const SecStrucTypeNA = SecondaryStructureType.create(SecondaryStructureType.Flag function createPolymerTraceElement (unit: Unit): PolymerTraceElement { return { - center: StructureElement.create(unit), - centerPrev: StructureElement.create(unit), - centerNext: StructureElement.create(unit), + center: StructureElement.Location.create(unit), + centerPrev: StructureElement.Location.create(unit), + centerNext: StructureElement.Location.create(unit), first: false, last: false, secStrucFirst: false, secStrucLast: false, secStrucType: SecStrucTypeNA, diff --git a/src/mol-script/runtime/query/table.ts b/src/mol-script/runtime/query/table.ts index 946373438ca730c319baae1a9de1e6f2250b5ff1..bf45a8836896209acf9bb1f08b4e8fc490cc2d0c 100644 --- a/src/mol-script/runtime/query/table.ts +++ b/src/mol-script/runtime/query/table.ts @@ -322,7 +322,7 @@ const symbols = [ D(MolScript.structureQuery.linkProperty.flags, (ctx, xs) => ctx.atomicLink.type), ]; -function atomProp(p: (e: StructureElement) => any): (ctx: QueryContext, _: any) => any { +function atomProp(p: (e: StructureElement.Location) => any): (ctx: QueryContext, _: any) => any { return (ctx, _) => p(ctx.element); } diff --git a/src/mol-theme/color/carbohydrate-symbol.ts b/src/mol-theme/color/carbohydrate-symbol.ts index accf9f6f00b7a9099d37e249aa61da548a0844a6..6f338108a6161ed12bb2cdfc87ee5448e35ee59f 100644 --- a/src/mol-theme/color/carbohydrate-symbol.ts +++ b/src/mol-theme/color/carbohydrate-symbol.ts @@ -42,7 +42,7 @@ export function CarbohydrateSymbolColorTheme(ctx: ThemeDataContext, props: PD.Va if (isSecondary) { return SaccharideColors.Secondary } else { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { return getColor(location.unit, location.element) } else if (Link.isLocation(location)) { return getColor(location.aUnit, location.aUnit.elements[location.aIndex]) diff --git a/src/mol-theme/color/chain-id.ts b/src/mol-theme/color/chain-id.ts index 296a8f5cb23769337f46b19bf1bfddfbd07ef430..e03b204091dd055508feca7cad06082e26c71732 100644 --- a/src/mol-theme/color/chain-id.ts +++ b/src/mol-theme/color/chain-id.ts @@ -77,7 +77,7 @@ export function ChainIdColorTheme(ctx: ThemeDataContext, props: PD.Values<ChainI let legend: ScaleLegend | TableLegend | undefined if (ctx.structure) { - const l = StructureElement.create() + const l = StructureElement.Location.create() const asymIdSerialMap = getAsymIdSerialMap(ctx.structure.root) const palette = getPalette(asymIdSerialMap.size, props) @@ -85,7 +85,7 @@ export function ChainIdColorTheme(ctx: ThemeDataContext, props: PD.Values<ChainI color = (location: Location): Color => { let serial: number | undefined = undefined - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { const asym_id = getAsymId(location.unit) serial = asymIdSerialMap.get(asym_id(location)) } else if (Link.isLocation(location)) { diff --git a/src/mol-theme/color/element-index.ts b/src/mol-theme/color/element-index.ts index c3ad6db92f6308eff26c79ea9816dd473f4a87b9..66146728d08feaf9a3929004a553e165a33b6ea5 100644 --- a/src/mol-theme/color/element-index.ts +++ b/src/mol-theme/color/element-index.ts @@ -47,7 +47,7 @@ export function ElementIndexColorTheme(ctx: ThemeDataContext, props: PD.Values<E legend = palette.legend color = (location: Location): Color => { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { const unitIndex = unitIdIndex.get(location.unit.id)! const unitElementIndex = OrderedSet.findPredecessorIndex(units[unitIndex].elements, location.element) return palette.color(cummulativeElementCount.get(unitIndex)! + unitElementIndex) diff --git a/src/mol-theme/color/element-symbol.ts b/src/mol-theme/color/element-symbol.ts index 208c56df90566832bac934c4456a39ad2ba72b07..46ca5dd74ff05741f44c7a7237be70b7c8289a05 100644 --- a/src/mol-theme/color/element-symbol.ts +++ b/src/mol-theme/color/element-symbol.ts @@ -41,7 +41,7 @@ export function ElementSymbolColorTheme(ctx: ThemeDataContext, props: PD.Values< const colorMap = getAdjustedColorMap(ElementSymbolColors, props.saturation, props.lightness) function color(location: Location): Color { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { if (Unit.isAtomic(location.unit)) { const { type_symbol } = location.unit.model.atomicHierarchy.atoms return elementSymbolColor(colorMap, type_symbol.value(location.element)) diff --git a/src/mol-theme/color/entity-source.ts b/src/mol-theme/color/entity-source.ts index e86948bc6f469a12fd8d5540f98c7e4dff422839..ac8ebd3c86d4e8d81d6578e5021c6032626ebc86 100644 --- a/src/mol-theme/color/entity-source.ts +++ b/src/mol-theme/color/entity-source.ts @@ -105,14 +105,14 @@ export function EntitySourceColorTheme(ctx: ThemeDataContext, props: PD.Values<E let legend: ScaleLegend | TableLegend | undefined if (ctx.structure) { - const l = StructureElement.create() + const l = StructureElement.Location.create() const { models } = ctx.structure.root const { seqToSrcByModelEntity, srcKeySerialMap } = getMaps(models) const palette = getPalette(srcKeySerialMap.size + 1, props) legend = palette.legend - const getSrcColor = (location: StructureElement) => { + const getSrcColor = (location: StructureElement.Location) => { const modelIndex = models.indexOf(location.unit.model) const entityId = StructureProperties.entity.id(location) const mK = modelEntityKey(modelIndex, entityId) @@ -126,7 +126,7 @@ export function EntitySourceColorTheme(ctx: ThemeDataContext, props: PD.Values<E } color = (location: Location): Color => { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { return getSrcColor(location) } else if (Link.isLocation(location)) { l.unit = location.aUnit diff --git a/src/mol-theme/color/hydrophobicity.ts b/src/mol-theme/color/hydrophobicity.ts index 88306bab0f31cb99fd8d1cbaded9417a2eb9eb26..f0db44b7f3f749f6eb23e6266a220ece15251aaf 100644 --- a/src/mol-theme/color/hydrophobicity.ts +++ b/src/mol-theme/color/hydrophobicity.ts @@ -72,7 +72,7 @@ export function HydrophobicityColorTheme(ctx: ThemeDataContext, props: PD.Values function color(location: Location): Color { let compId: string | undefined - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { if (Unit.isAtomic(location.unit)) { compId = getAtomicCompId(location.unit, location.element) } else { diff --git a/src/mol-theme/color/illustrative.ts b/src/mol-theme/color/illustrative.ts index 70a50f9077cef33b09456c9ae3177901b78f80e9..c0cbaf92faf8fc7cd8a68b0adf7e8c5096bf7e85 100644 --- a/src/mol-theme/color/illustrative.ts +++ b/src/mol-theme/color/illustrative.ts @@ -47,7 +47,7 @@ export function IllustrativeColorTheme(ctx: ThemeDataContext, props: PD.Values<I const colorMap = getAdjustedColorMap(ElementSymbolColors, 0, 0.7) function color(location: Location): Color { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { if (Unit.isAtomic(location.unit)) { const moleculeType = location.unit.model.atomicHierarchy.derived.residue.moleculeType[location.unit.residueIndex[location.element]] const typeSymbol = location.unit.model.atomicHierarchy.atoms.type_symbol.value(location.element) diff --git a/src/mol-theme/color/model-index.ts b/src/mol-theme/color/model-index.ts index d518b8c4b444aca0f0c82a1cb241c4bbeb7682d3..faf151815d4110dacb2502b15188ca26694dd7cd 100644 --- a/src/mol-theme/color/model-index.ts +++ b/src/mol-theme/color/model-index.ts @@ -39,7 +39,7 @@ export function ModelIndexColorTheme(ctx: ThemeDataContext, props: PD.Values<Mod } color = (location: Location): Color => { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { return modelColor.get(location.unit.model.id)! } else if (Link.isLocation(location)) { return modelColor.get(location.aUnit.model.id)! diff --git a/src/mol-theme/color/molecule-type.ts b/src/mol-theme/color/molecule-type.ts index 6054175fb559835aa75f25f24cb80d3e6faac913..7bc5eca8637e557c33eab3033ddb984ba02ec988 100644 --- a/src/mol-theme/color/molecule-type.ts +++ b/src/mol-theme/color/molecule-type.ts @@ -56,7 +56,7 @@ export function MoleculeTypeColorTheme(ctx: ThemeDataContext, props: PD.Values<M const colorMap = getAdjustedColorMap(MoleculeTypeColors, props.saturation, props.lightness) function color(location: Location): Color { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { return moleculeTypeColor(colorMap, location.unit, location.element) } else if (Link.isLocation(location)) { return moleculeTypeColor(colorMap, location.aUnit, location.aUnit.elements[location.aIndex]) diff --git a/src/mol-theme/color/polymer-id.ts b/src/mol-theme/color/polymer-id.ts index 4501be00ad50411ae77101d63e4c8122134ae063..3910089d83cc8e80665627d1a0b21fd888ddc707 100644 --- a/src/mol-theme/color/polymer-id.ts +++ b/src/mol-theme/color/polymer-id.ts @@ -86,7 +86,7 @@ export function PolymerIdColorTheme(ctx: ThemeDataContext, props: PD.Values<Poly let legend: ScaleLegend | TableLegend | undefined if (ctx.structure) { - const l = StructureElement.create() + const l = StructureElement.Location.create() const polymerAsymIdSerialMap = getPolymerAsymIdSerialMap(ctx.structure.root) const palette = getPalette(polymerAsymIdSerialMap.size, props) @@ -94,7 +94,7 @@ export function PolymerIdColorTheme(ctx: ThemeDataContext, props: PD.Values<Poly color = (location: Location): Color => { let serial: number | undefined = undefined - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { const asym_id = getAsymId(location.unit) serial = polymerAsymIdSerialMap.get(asym_id(location)) } else if (Link.isLocation(location)) { diff --git a/src/mol-theme/color/polymer-index.ts b/src/mol-theme/color/polymer-index.ts index 5e5371e2465dcb2a5d9ea7d8ba57df66f5d54e7f..1dc4d59fcca5fedd5d361c3444fee59a09107587 100644 --- a/src/mol-theme/color/polymer-index.ts +++ b/src/mol-theme/color/polymer-index.ts @@ -61,7 +61,7 @@ export function PolymerIndexColorTheme(ctx: ThemeDataContext, props: PD.Values<P color = (location: Location): Color => { let color: Color | undefined - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { color = unitIdColor.get(location.unit.id) } else if (Link.isLocation(location)) { color = unitIdColor.get(location.aUnit.id) diff --git a/src/mol-theme/color/residue-name.ts b/src/mol-theme/color/residue-name.ts index f3014fa18c2e7c030ea179853346522b9bd171b1..06151d63dae8a5f7521d9c3e37699ec1cbaf0771 100644 --- a/src/mol-theme/color/residue-name.ts +++ b/src/mol-theme/color/residue-name.ts @@ -102,7 +102,7 @@ export function ResidueNameColorTheme(ctx: ThemeDataContext, props: PD.Values<Re const colorMap = getAdjustedColorMap(ResidueNameColors, props.saturation, props.lightness) function color(location: Location): Color { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { if (Unit.isAtomic(location.unit)) { const compId = getAtomicCompId(location.unit, location.element) return residueNameColor(colorMap, compId) diff --git a/src/mol-theme/color/secondary-structure.ts b/src/mol-theme/color/secondary-structure.ts index 6249f79a39270a45f8a790c177a36f069d4cde3e..afa12534c3464d0e36b0e1f49e61d731f18177ee 100644 --- a/src/mol-theme/color/secondary-structure.ts +++ b/src/mol-theme/color/secondary-structure.ts @@ -92,7 +92,7 @@ export function SecondaryStructureColorTheme(ctx: ThemeDataContext, props: PD.Va const colorMap = getAdjustedColorMap(SecondaryStructureColors, props.saturation, props.lightness) function color(location: Location): Color { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { return secondaryStructureColor(colorMap, location.unit, location.element, computedSecondaryStructure) } else if (Link.isLocation(location)) { return secondaryStructureColor(colorMap, location.aUnit, location.aUnit.elements[location.aIndex], computedSecondaryStructure) diff --git a/src/mol-theme/color/sequence-id.ts b/src/mol-theme/color/sequence-id.ts index c27e9174f03028d597353c9beee4b3bed6291493..35d7f31c944192fe9744dbe3720f4747e2bd50e9 100644 --- a/src/mol-theme/color/sequence-id.ts +++ b/src/mol-theme/color/sequence-id.ts @@ -71,7 +71,7 @@ export function SequenceIdColorTheme(ctx: ThemeDataContext, props: PD.Values<Seq maxLabel: 'End', }) const color = (location: Location): Color => { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { const { unit, element } = location const seq_id = getSeqId(unit, element) if (seq_id > 0) { diff --git a/src/mol-theme/color/uncertainty.ts b/src/mol-theme/color/uncertainty.ts index 47076c16333d980e8c86229a155455d4b755de4b..1b2bf9315490d68b1d7420d326fcd57a5623a279 100644 --- a/src/mol-theme/color/uncertainty.ts +++ b/src/mol-theme/color/uncertainty.ts @@ -44,7 +44,7 @@ export function UncertaintyColorTheme(ctx: ThemeDataContext, props: PD.Values<Un // TODO calc domain based on data, set min/max as 10/90 percentile to be robust against outliers function color(location: Location): Color { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { return scale.color(getUncertainty(location.unit, location.element)) } else if (Link.isLocation(location)) { return scale.color(getUncertainty(location.aUnit, location.aUnit.elements[location.aIndex])) diff --git a/src/mol-theme/color/unit-index.ts b/src/mol-theme/color/unit-index.ts index ccf8a05d9af3b78fca4880bfa5133d8d681e1106..a57d648762fe13559b8526e850c978eb3a3cd172 100644 --- a/src/mol-theme/color/unit-index.ts +++ b/src/mol-theme/color/unit-index.ts @@ -46,7 +46,7 @@ export function UnitIndexColorTheme(ctx: ThemeDataContext, props: PD.Values<Unit } color = (location: Location): Color => { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { return unitIdColor.get(location.unit.id)! } else if (Link.isLocation(location)) { return unitIdColor.get(location.aUnit.id)! diff --git a/src/mol-theme/label.ts b/src/mol-theme/label.ts index 3724e4106ee360c0a13956bbdb1b6a1c68d0a0e4..338eb8e6b25899d6540aa30e22bb5d59daee2305 100644 --- a/src/mol-theme/label.ts +++ b/src/mol-theme/label.ts @@ -10,10 +10,10 @@ import { Loci } from '../mol-model/loci'; import { OrderedSet } from '../mol-data/int'; // for `labelFirst`, don't create right away to avoid problems with circular dependencies/imports -let elementLocA: StructureElement -let elementLocB: StructureElement +let elementLocA: StructureElement.Location +let elementLocB: StructureElement.Location -function setElementLocation(loc: StructureElement, unit: Unit, index: StructureElement.UnitIndex) { +function setElementLocation(loc: StructureElement.Location, unit: Unit, index: StructureElement.UnitIndex) { loc.unit = unit loc.element = unit.elements[index] } @@ -75,8 +75,8 @@ export function structureElementStatsLabel(stats: StructureElement.Stats, counts } export function linkLabel(link: Link.Location) { - if (!elementLocA) elementLocA = StructureElement.create() - if (!elementLocB) elementLocB = StructureElement.create() + if (!elementLocA) elementLocA = StructureElement.Location.create() + if (!elementLocB) elementLocB = StructureElement.Location.create() setElementLocation(elementLocA, link.aUnit, link.aIndex) setElementLocation(elementLocB, link.bUnit, link.bIndex) return `${elementLabel(elementLocA)} - ${elementLabel(elementLocB)}` @@ -84,15 +84,15 @@ export function linkLabel(link: Link.Location) { export type LabelGranularity = 'element' | 'residue' | 'chain' | 'structure' -export function elementLabel(location: StructureElement, granularity: LabelGranularity = 'element') { +export function elementLabel(location: StructureElement.Location, granularity: LabelGranularity = 'element') { const model = location.unit.model.entry const instance = location.unit.conformation.operator.name const label = [model, instance] if (Unit.isAtomic(location.unit)) { - label.push(atomicElementLabel(location as StructureElement<Unit.Atomic>, granularity)) + label.push(atomicElementLabel(location as StructureElement.Location<Unit.Atomic>, granularity)) } else if (Unit.isCoarse(location.unit)) { - label.push(coarseElementLabel(location as StructureElement<Unit.Spheres | Unit.Gaussians>, granularity)) + label.push(coarseElementLabel(location as StructureElement.Location<Unit.Spheres | Unit.Gaussians>, granularity)) } else { label.push('Unknown') } @@ -100,7 +100,7 @@ export function elementLabel(location: StructureElement, granularity: LabelGranu return label.join(' | ') } -export function atomicElementLabel(location: StructureElement<Unit.Atomic>, granularity: LabelGranularity) { +export function atomicElementLabel(location: StructureElement.Location<Unit.Atomic>, granularity: LabelGranularity) { const label_asym_id = Props.chain.label_asym_id(location) const auth_asym_id = Props.chain.auth_asym_id(location) const seq_id = location.unit.model.atomicHierarchy.residues.auth_seq_id.isDefined ? Props.residue.auth_seq_id(location) : Props.residue.label_seq_id(location) @@ -122,7 +122,7 @@ export function atomicElementLabel(location: StructureElement<Unit.Atomic>, gran return label.reverse().join(' | ') } -export function coarseElementLabel(location: StructureElement<Unit.Spheres | Unit.Gaussians>, granularity: LabelGranularity) { +export function coarseElementLabel(location: StructureElement.Location<Unit.Spheres | Unit.Gaussians>, granularity: LabelGranularity) { // TODO handle granularity const asym_id = Props.coarse.asym_id(location) const seq_id_begin = Props.coarse.seq_id_begin(location) diff --git a/src/mol-theme/size/physical.ts b/src/mol-theme/size/physical.ts index d43551ac77662a70c739fa322634bc9d18defa56..d023e2f8a48750ca515f2a2ed9a1db399be6e521 100644 --- a/src/mol-theme/size/physical.ts +++ b/src/mol-theme/size/physical.ts @@ -37,7 +37,7 @@ export function getPhysicalRadius(unit: Unit, element: ElementIndex): number { export function PhysicalSizeTheme(ctx: ThemeDataContext, props: PD.Values<PhysicalSizeThemeParams>): SizeTheme<PhysicalSizeThemeParams> { function size(location: Location): number { let size: number - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { size = getPhysicalRadius(location.unit, location.element) } else if (Link.isLocation(location)) { size = getPhysicalRadius(location.aUnit, location.aUnit.elements[location.aIndex]) diff --git a/src/mol-theme/size/uncertainty.ts b/src/mol-theme/size/uncertainty.ts index c0d9b8eb0994f840bd0137b1ec028837666aaba3..d6ac5a0a2a0b228a7c0070cd7c2847bc9235c4b6 100644 --- a/src/mol-theme/size/uncertainty.ts +++ b/src/mol-theme/size/uncertainty.ts @@ -35,7 +35,7 @@ export function getUncertainty(unit: Unit, element: ElementIndex, props: PD.Valu export function UncertaintySizeTheme(ctx: ThemeDataContext, props: PD.Values<UncertaintySizeThemeParams>): SizeTheme<UncertaintySizeThemeParams> { function size(location: Location): number { let size = props.baseSize - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { size += getUncertainty(location.unit, location.element, props) } else if (Link.isLocation(location)) { size += getUncertainty(location.aUnit, location.aUnit.elements[location.aIndex], props) diff --git a/src/perf-tests/structure.ts b/src/perf-tests/structure.ts index 954990c08057f68dc5f0e20323d437bc85df2f34..1b619cced938a6fd9a3bbba5f77cb39ba0131f1b 100644 --- a/src/perf-tests/structure.ts +++ b/src/perf-tests/structure.ts @@ -117,7 +117,7 @@ export namespace PropertyAccess { } function sumProperty(structure: Structure, p: StructureElement.Property<number>) { - const l = StructureElement.create(); + const l = StructureElement.Location.create(); let s = 0; for (const unit of structure.units) { diff --git a/src/tests/browser/render-asa.ts b/src/tests/browser/render-asa.ts index fd284c675401d938ac7f65c47ca37b73cd347e08..99934959d95fac220b78f97b3c99763e4b354ba1 100644 --- a/src/tests/browser/render-asa.ts +++ b/src/tests/browser/render-asa.ts @@ -117,7 +117,7 @@ export function AccessibleSurfaceAreaColorTheme(ctx: ThemeDataContext, props: PD domain: [0.0, 1.0] }) color = (location: Location): Color => { - if (StructureElement.isLocation(location)) { + if (StructureElement.Location.is(location)) { if (Unit.isAtomic(location.unit)) { const value = accessibleSurfaceArea.relativeAccessibleSurfaceArea![location.unit.residueIndex[location.element]]; return value !== AccessibleSurfaceArea.VdWLookup[0] /* signals missing value */ ? scale.color(value) : DefaultColor;