Bladeren bron

Merge branch 'master' into cartoon-repr

# Conflicts:
#	src/mol-data/int/impl/segmentation.ts
#	src/mol-data/int/segmentation.ts
#	src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts
#	src/mol-geo/representation/structure/visual/util/polymer.ts
#	src/mol-model/structure/model/formats/mmcif.ts
#	src/mol-model/structure/model/formats/mmcif/atomic.ts
#	src/mol-model/structure/model/formats/mmcif/ihm.ts
#	src/mol-model/structure/model/properties/atomic/hierarchy.ts
#	src/mol-model/structure/model/properties/coarse/hierarchy.ts
Alexander Rose 6 jaren geleden
bovenliggende
commit
63a9b68049
65 gewijzigde bestanden met toevoegingen van 841 en 735 verwijderingen
  1. 9 0
      data/mmcif-field-names.csv
  2. 5 9
      src/apps/cif2bcif/converter.ts
  3. 4 4
      src/apps/domain-annotation-server/mapping.ts
  4. 0 37
      src/apps/domain-annotation-server/utils.ts
  5. 8 8
      src/apps/structure-info/model.ts
  6. 2 2
      src/mol-data/db/column.ts
  7. 2 2
      src/mol-data/int/_spec/segmentation.spec.ts
  8. 20 20
      src/mol-data/int/impl/segmentation.ts
  9. 13 11
      src/mol-data/int/segmentation.ts
  10. 8 8
      src/mol-data/int/sorted-ranges.ts
  11. 6 6
      src/mol-data/util/interval-iterator.ts
  12. 3 3
      src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts
  13. 7 7
      src/mol-geo/representation/structure/visual/util/element.ts
  14. 36 34
      src/mol-geo/representation/structure/visual/util/polymer.ts
  15. 7 7
      src/mol-geo/theme/structure/color/chain-id.ts
  16. 3 3
      src/mol-geo/theme/structure/size/physical.ts
  17. 15 5
      src/mol-io/reader/cif/schema/mmcif.ts
  18. 13 13
      src/mol-io/writer/cif/encoder.ts
  19. 12 12
      src/mol-io/writer/cif/encoder/binary.ts
  20. 24 23
      src/mol-io/writer/cif/encoder/text.ts
  21. 1 1
      src/mol-io/writer/cif/encoder/util.ts
  22. 2 2
      src/mol-model/loci.ts
  23. 13 11
      src/mol-model/structure/export/categories/atom_site.ts
  24. 59 0
      src/mol-model/structure/export/categories/modified-residues.ts
  25. 30 30
      src/mol-model/structure/export/categories/secondary-structure.ts
  26. 29 15
      src/mol-model/structure/export/mmcif.ts
  27. 2 1
      src/mol-model/structure/model.ts
  28. 13 11
      src/mol-model/structure/model/formats/mmcif.ts
  29. 8 8
      src/mol-model/structure/model/formats/mmcif/atomic.ts
  30. 20 20
      src/mol-model/structure/model/formats/mmcif/bonds/comp.ts
  31. 32 31
      src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts
  32. 4 4
      src/mol-model/structure/model/formats/mmcif/ihm.ts
  33. 13 12
      src/mol-model/structure/model/formats/mmcif/pair-restraints/cross-links.ts
  34. 10 9
      src/mol-model/structure/model/formats/mmcif/secondary-structure.ts
  35. 7 7
      src/mol-model/structure/model/formats/mmcif/util.ts
  36. 10 0
      src/mol-model/structure/model/indexing.ts
  37. 7 18
      src/mol-model/structure/model/model.ts
  38. 36 18
      src/mol-model/structure/model/properties/atomic/hierarchy.ts
  39. 6 6
      src/mol-model/structure/model/properties/coarse/hierarchy.ts
  40. 2 1
      src/mol-model/structure/model/properties/common.ts
  41. 1 3
      src/mol-model/structure/model/properties/custom/descriptor.ts
  42. 7 6
      src/mol-model/structure/model/properties/sequence.ts
  43. 11 10
      src/mol-model/structure/model/properties/utils/atomic-keys.ts
  44. 5 5
      src/mol-model/structure/model/properties/utils/atomic-ranges.ts
  45. 12 11
      src/mol-model/structure/model/properties/utils/coarse-keys.ts
  46. 4 4
      src/mol-model/structure/model/properties/utils/coarse-ranges.ts
  47. 22 22
      src/mol-model/structure/query/generators.ts
  48. 3 3
      src/mol-model/structure/query/modifiers.ts
  49. 9 9
      src/mol-model/structure/query/predicates.ts
  50. 5 5
      src/mol-model/structure/query/selection.ts
  51. 4 3
      src/mol-model/structure/query/utils/builders.ts
  52. 3 3
      src/mol-model/structure/query/utils/structure.ts
  53. 2 2
      src/mol-model/structure/structure.ts
  54. 20 20
      src/mol-model/structure/structure/element.ts
  55. 52 52
      src/mol-model/structure/structure/properties.ts
  56. 18 18
      src/mol-model/structure/structure/structure.ts
  57. 17 16
      src/mol-model/structure/structure/unit.ts
  58. 2 2
      src/mol-model/structure/structure/unit/rings/compute.ts
  59. 9 8
      src/mol-model/structure/structure/util/subset-builder.ts
  60. 6 6
      src/mol-view/label.ts
  61. 40 14
      src/perf-tests/cif-encoder.ts
  62. 3 3
      src/perf-tests/structure.ts
  63. 13 13
      src/servers/model/server/api.ts
  64. 26 29
      src/servers/model/server/query.ts
  65. 46 49
      src/servers/volume/server/query/encode.ts

+ 9 - 0
data/mmcif-field-names.csv

@@ -62,6 +62,15 @@ entity.pdbx_mutation
 entity.pdbx_fragment
 entity.pdbx_ec
 
+entity_poly.entity_id
+entity_poly.type
+entity_poly.nstd_linkage
+entity_poly.nstd_monomer
+entity_poly.pdbx_seq_one_letter_code  
+entity_poly.pdbx_seq_one_letter_code_can 
+entity_poly.pdbx_strand_id
+entity_poly.pdbx_target_identifier
+
 entity_poly_seq.entity_id
 entity_poly_seq.num
 entity_poly_seq.mon_id

+ 5 - 9
src/apps/cif2bcif/converter.ts

@@ -24,15 +24,11 @@ async function getCIF(ctx: RuntimeContext, path: string) {
     return parsed.result;
 }
 
-function getCategoryInstanceProvider(cat: CifCategory, fields: CifWriter.Field[]): CifWriter.Category.Provider {
-    return function (ctx: any) {
-        return {
-            data: cat,
-            name: cat.name,
-            fields,
-            rowCount: cat.rowCount
-        };
-    }
+function getCategoryInstanceProvider(cat: CifCategory, fields: CifWriter.Field[]): CifWriter.Category {
+    return {
+        name: cat.name,
+        instance: () => ({ data: cat, fields, rowCount: cat.rowCount })
+    };
 }
 
 export default function convert(path: string, asText = false) {

+ 4 - 4
src/apps/domain-annotation-server/mapping.ts

@@ -7,7 +7,7 @@
 import { Table } from 'mol-data/db'
 import { CifWriter } from 'mol-io/writer/cif'
 import * as S from './schemas'
-import { getCategoryInstanceProvider } from './utils'
+//import { getCategoryInstanceProvider } from './utils'
 
 export default function create(allData: any) {
     const mols = Object.keys(allData);
@@ -21,7 +21,7 @@ export default function create(allData: any) {
     const sources = getSources(data);
     if (!sources._rowCount) return enc.getData();
 
-    enc.writeCategory(getCategoryInstanceProvider(`pdbx_domain_annotation_sources`, sources));
+    enc.writeCategory({ name: `pdbx_domain_annotation_sources`, instance: () => CifWriter.Category.ofTable(sources) });
 
     for (const cat of Object.keys(S.categories)) {
         writeDomain(enc, getDomain(cat, (S.categories as any)[cat], data));
@@ -38,8 +38,8 @@ type MappingRow = Table.Row<S.mapping>;
 
 function writeDomain(enc: CifWriter.Encoder, domain: DomainAnnotation | undefined) {
     if (!domain) return;
-    enc.writeCategory(getCategoryInstanceProvider(`pdbx_${domain.name}_domain_annotation`, domain.domains));
-    enc.writeCategory(getCategoryInstanceProvider(`pdbx_${domain.name}_domain_mapping`, domain.mappings));
+    enc.writeCategory({ name: `pdbx_${domain.name}_domain_annotation`, instance: () =>  CifWriter.Category.ofTable(domain.domains) });
+    enc.writeCategory({ name: `pdbx_${domain.name}_domain_mapping`, instance: () => CifWriter.Category.ofTable(domain.mappings) });
 }
 
 function getSources(data: any): Table<S.Sources> {

+ 0 - 37
src/apps/domain-annotation-server/utils.ts

@@ -1,37 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { Table } from 'mol-data/db'
-import { CifWriter } from 'mol-io/writer/cif'
-
-function columnValue(k: string) {
-    return (i: number, d: any) => d[k].value(i);
-}
-
-function columnValueKind(k: string) {
-    return (i: number, d: any) => d[k].valueKind(i);
-}
-
-function ofSchema(schema: Table.Schema) {
-    const fields: CifWriter.Field[] = [];
-    for (const k of Object.keys(schema)) {
-        const t = schema[k];
-        const type: any = t.valueType === 'str' ? CifWriter.Field.Type.Str : t.valueType === 'int' ? CifWriter.Field.Type.Int : CifWriter.Field.Type.Float;
-        fields.push({ name: k, type, value: columnValue(k), valueKind: columnValueKind(k) })
-    }
-    return fields;
-}
-
-export function getCategoryInstanceProvider(name: string, table: Table<any>): CifWriter.Category.Provider {
-    return () => {
-        return {
-            data: table,
-            name,
-            fields: ofSchema(table._schema),
-            rowCount: table._rowCount
-        };
-    }
-}

+ 8 - 8
src/apps/structure-info/model.ts

@@ -9,7 +9,7 @@ import * as argparse from 'argparse'
 require('util.promisify').shim();
 
 import { CifFrame } from 'mol-io/reader/cif'
-import { Model, Structure, Element, Unit, Format, StructureProperties } from 'mol-model/structure'
+import { Model, Structure, StructureElement, Unit, Format, StructureProperties } from 'mol-model/structure'
 // import { Run, Progress } from 'mol-task'
 import { OrderedSet } from 'mol-data/int';
 import { openCif, downloadCif } from './helpers';
@@ -29,20 +29,20 @@ async function readPdbFile(path: string) {
 }
 
 export function atomLabel(model: Model, aI: number) {
-    const { atoms, residues, chains, residueSegments, chainSegments } = model.atomicHierarchy
+    const { atoms, residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy
     const { label_atom_id } = atoms
     const { label_comp_id, label_seq_id } = residues
     const { label_asym_id } = chains
-    const rI = residueSegments.segmentMap[aI]
-    const cI = chainSegments.segmentMap[aI]
+    const rI = residueAtomSegments.index[aI]
+    const cI = chainAtomSegments.index[aI]
     return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)} ${label_atom_id.value(aI)}`
 }
 
 export function residueLabel(model: Model, rI: number) {
-    const { residues, chains, residueSegments, chainSegments } = model.atomicHierarchy
+    const { residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy
     const { label_comp_id, label_seq_id } = residues
     const { label_asym_id } = chains
-    const cI = chainSegments.segmentMap[residueSegments.segments[rI]]
+    const cI = chainAtomSegments.index[residueAtomSegments.offsets[rI]]
     return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}`
 }
 
@@ -119,7 +119,7 @@ export function printSequence(model: Model) {
 
 export function printModRes(model: Model) {
     console.log('\nModified Residues\n=============');
-    const map = model.properties.modifiedResidueNameMap;
+    const map = model.properties.modifiedResidues.parentId;
     const { label_comp_id, _rowCount } = model.atomicHierarchy.residues;
     for (let i = 0; i < _rowCount; i++) {
         const comp_id = label_comp_id.value(i);
@@ -146,7 +146,7 @@ export function printRings(structure: Structure) {
 
 export function printUnits(structure: Structure) {
     console.log('\nUnits\n=============');
-    const l = Element.Location();
+    const l = StructureElement.create();
 
     for (const unit of structure.units) {
         l.unit = unit;

+ 2 - 2
src/mol-data/db/column.ts

@@ -141,8 +141,8 @@ namespace Column {
         return createFirstIndexMapOfColumn(column);
     }
 
-    export function createIndexer<T>(column: Column<T>) {
-        return createIndexerOfColumn(column);
+    export function createIndexer<T, R extends number = number>(column: Column<T>) {
+        return createIndexerOfColumn(column) as ((e: T) => R);
     }
 
     export function mapToArray<T, S>(column: Column<T>, f: (v: T) => S, ctor?: ArrayCtor<S>): ArrayLike<S> {

+ 2 - 2
src/mol-data/int/_spec/segmentation.spec.ts

@@ -21,12 +21,12 @@ describe('segments', () => {
 
     it('ofOffsetts', () => {
         const p = Segmentation.ofOffsets([10, 12], Interval.ofBounds(10, 14));
-        expect(p.segments).toEqual(new Int32Array([0, 2, 4]))
+        expect(p.offsets).toEqual(new Int32Array([0, 2, 4]))
     });
 
     it('map', () => {
         const segs = Segmentation.create([0, 1, 2]);
-        expect(segs.segmentMap).toEqual(new Int32Array([0, 1]));
+        expect(segs.index).toEqual(new Int32Array([0, 1]));
         expect(Segmentation.getSegment(segs, 0)).toBe(0);
         expect(Segmentation.getSegment(segs, 1)).toBe(1);
     });

+ 20 - 20
src/mol-data/int/impl/segmentation.ts

@@ -12,23 +12,23 @@ import Segs from '../segmentation'
 
 interface Segmentation {
     /** Segments stored as a sorted array */
-    segments: SortedArray,
+    offsets: SortedArray,
     /** Mapping of values to segments */
-    segmentMap: Int32Array,
+    index: Int32Array,
     /** Number of segments */
     count: number
 }
 
 export function create(values: ArrayLike<number>): Segmentation {
-    const segments = SortedArray.ofSortedArray(values);
-    const max = SortedArray.max(segments);
-    const segmentMap = new Int32Array(max);
+    const offsets = SortedArray.ofSortedArray(values);
+    const max = SortedArray.max(offsets);
+    const index = new Int32Array(max);
     for (let i = 0, _i = values.length - 1; i < _i; i++) {
         for (let j = values[i], _j = values[i + 1]; j < _j; j++) {
-            segmentMap[j] = i;
+            index[j] = i;
         }
     }
-    return { segments, segmentMap, count: values.length - 1 };
+    return { offsets, index, count: values.length - 1 };
 }
 
 export function ofOffsets(offsets: ArrayLike<number>, bounds: Interval): Segmentation {
@@ -43,26 +43,26 @@ export function ofOffsets(offsets: ArrayLike<number>, bounds: Interval): Segment
 
 /** Get number of segments in a segmentation */
 export function count({ count }: Segmentation) { return count; }
-export function getSegment({ segmentMap }: Segmentation, value: number) { return segmentMap[value]; }
+export function getSegment({ index }: Segmentation, value: number) { return index[value]; }
 
-export function projectValue({ segments }: Segmentation, set: OrderedSet, value: number): Interval {
-    const last = OrderedSet.max(segments);
-    const idx = value >= last ? -1 : OrderedSet.findPredecessorIndex(segments, value - 1);
-    return OrderedSet.findRange(set, OrderedSet.getAt(segments, idx), OrderedSet.getAt(segments, idx + 1) - 1);
+export function projectValue({ offsets }: Segmentation, set: OrderedSet, value: number): Interval {
+    const last = OrderedSet.max(offsets);
+    const idx = value >= last ? -1 : OrderedSet.findPredecessorIndex(offsets, value - 1);
+    return OrderedSet.findRange(set, OrderedSet.getAt(offsets, idx), OrderedSet.getAt(offsets, idx + 1) - 1);
 }
 
-export class SegmentIterator<T extends number = number> implements Iterator<Segs.Segment<T>> {
-    private segmentMax = 0;
+export class SegmentIterator<I extends number = number> implements Iterator<Segs.Segment<I>> {
     private segmentMin = 0;
+    private segmentMax = 0;
     private setRange = Interval.Empty;
-    private value: Segs.Segment<T> = { index: 0, start: 0 as T, end: 0 as T };
+    private value: Segs.Segment<I> = { index: 0 as I, start: 0, end: 0 };
 
     hasNext: boolean = false;
 
     move() {
         while (this.hasNext) {
             if (this.updateValue()) {
-                this.value.index = this.segmentMin++;
+                this.value.index = this.segmentMin++ as I;
                 this.hasNext = this.segmentMax >= this.segmentMin && Interval.size(this.setRange) > 0;
                 break;
             } else {
@@ -76,8 +76,8 @@ export class SegmentIterator<T extends number = number> implements Iterator<Segs
         const segmentEnd = this.segments[this.segmentMin + 1];
         // TODO: add optimized version for interval and array?
         const setEnd = OrderedSet.findPredecessorIndexInInterval(this.set, segmentEnd, this.setRange);
-        this.value.start = Interval.start(this.setRange) as T;
-        this.value.end = setEnd as T;
+        this.value.start = Interval.start(this.setRange);
+        this.value.end = setEnd;
         this.setRange = Interval.ofBounds(setEnd, Interval.end(this.setRange));
         return setEnd > this.value.start;
     }
@@ -93,7 +93,7 @@ export class SegmentIterator<T extends number = number> implements Iterator<Segs
         this.hasNext = this.segmentMax >= this.segmentMin;
     }
 
-    setSegment(segment: Segs.Segment<T>) {
+    setSegment(segment: Segs.Segment<number>) {
         this.setRange = Interval.ofBounds(segment.start, segment.end);
         this.updateSegmentRange();
     }
@@ -106,5 +106,5 @@ export class SegmentIterator<T extends number = number> implements Iterator<Segs
 
 export function segments(segs: Segmentation, set: OrderedSet, segment?: Segs.Segment) {
     const int = typeof segment !== 'undefined' ? Interval.ofBounds(segment.start, segment.end) : Interval.ofBounds(0, OrderedSet.size(set));
-    return new SegmentIterator(segs.segments, segs.segmentMap, set, int);
+    return new SegmentIterator(segs.offsets, segs.index, set, int);
 }

+ 13 - 11
src/mol-data/int/segmentation.ts

@@ -9,25 +9,27 @@ import OrderedSet from './ordered-set'
 import * as Impl from './impl/segmentation'
 
 namespace Segmentation {
-    export interface Segment<T extends number = number> { index: number, start: T, end: T }
+    export interface Segment<I extends number = number> { index: I, start: number, end: number }
 
-    export const create: <T extends number = number>(segs: ArrayLike<T>) => Segmentation<T> = Impl.create as any;
-    export const ofOffsets: <T extends number = number>(offsets: ArrayLike<T>, bounds: Interval) => Segmentation<T> = Impl.ofOffsets as any;
+    export const create: <T extends number = number, I extends number = number>(segs: ArrayLike<T>) => Segmentation<T, I> = Impl.create as any;
+    export const ofOffsets: <T extends number = number, I extends number = number>(offsets: ArrayLike<T>, bounds: Interval) => Segmentation<T, I> = Impl.ofOffsets as any;
 
-    export const count: <T extends number = number>(segs: Segmentation<T>) => number = Impl.count as any;
-    export const getSegment: <T extends number = number>(segs: Segmentation<T>, value: T) => number = Impl.getSegment as any;
-    export const projectValue: <T extends number = number>(segs: Segmentation<T>, set: OrderedSet<T>, value: T) => Interval = Impl.projectValue as any;
+    export const count: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>) => number = Impl.count as any;
+    export const getSegment: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, value: T) => number = Impl.getSegment as any;
+    export const projectValue: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, set: OrderedSet<T>, value: T) => Interval = Impl.projectValue as any;
 
     // Segment iterator that mutates a single segment object to mark all the segments.
-    export const transientSegments: <T extends number = number>(segs: Segmentation<T>, set: OrderedSet<T>, segment?: Segment<T>) => Impl.SegmentIterator<T> = Impl.segments as any;
+    export const transientSegments: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, set: OrderedSet<T>, segment?: Segment) => Impl.SegmentIterator<I> = Impl.segments as any;
 
-    export type Iterator<T extends number = number> = Impl.SegmentIterator<T>
+    export type SegmentIterator<I extends number = number> = Impl.SegmentIterator<I>
 }
 
-interface Segmentation<T extends number = number> {
+interface Segmentation<T extends number = number, I extends number = number> {
     '@type': 'segmentation',
-    readonly segments: ArrayLike<T>,
-    readonly segmentMap: ArrayLike<number>,
+    /** All segments are defined by offsets [offsets[i], offsets[i + 1]) for i \in [0, count - 1] */
+    readonly offsets: ArrayLike<T>,
+    /** Segment index of the i-th element */
+    readonly index: ArrayLike<I>,
     readonly count: number
 }
 

+ 8 - 8
src/mol-data/int/sorted-ranges.ts

@@ -28,13 +28,13 @@ namespace SortedRanges {
         return new Iterator<T>(ranges, set)
     }
 
-    export class Iterator<T extends number = number> implements _Iterator<Segmentation.Segment<T>> {
-        private value: Segmentation.Segment<T> = { index: 0, start: 0 as T, end: 0 as T }
+    export class Iterator<I extends number = number> implements _Iterator<Segmentation.Segment<I>> {
+        private value: Segmentation.Segment<I> = { index: 0 as I, start: 0, end: 0 }
     
         private curIndex = 0
         private maxIndex = 0
-        private interval: Interval<T>
-        private curMin: T = 0 as T
+        private interval: Interval<I>
+        private curMin: I = 0 as I
     
         hasNext: boolean = false;
     
@@ -43,9 +43,9 @@ namespace SortedRanges {
         }
     
         updateValue() {
-            this.value.index = this.curIndex / 2
-            this.value.start = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex]) as T
-            this.value.end = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex + 1]) + 1 as T
+            this.value.index = this.curIndex / 2 as I
+            this.value.start = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex])
+            this.value.end = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex + 1]) + 1
         }
 
         move() {
@@ -67,7 +67,7 @@ namespace SortedRanges {
             return (index % 2 === 1) ? index - 1 : index
         }
     
-        constructor(private ranges: SortedRanges<T>, private set: OrderedSet<T>) {
+        constructor(private ranges: SortedRanges<I>, private set: OrderedSet<I>) {
             if (ranges.length) {
                 this.curIndex = this.getRangeIndex(OrderedSet.min(set))
                 this.maxIndex = Math.min(ranges.length - 2, this.getRangeIndex(OrderedSet.max(set)))

+ 6 - 6
src/mol-data/util/interval-iterator.ts

@@ -8,8 +8,8 @@
 import { OrderedSet, Interval, Segmentation } from '../int';
 
 /** Emits a segment of length one for each element in the interval that is also in the set */
-export class IntervalIterator<T extends number = number> implements Iterator<Segmentation.Segment<T>> {
-    private value: Segmentation.Segment<T> = { index: 0, start: 0 as T, end: 0 as T }
+export class IntervalIterator<I extends number = number> implements Iterator<Segmentation.Segment<I>> {
+    private value: Segmentation.Segment<I> = { index: 0 as I, start: 0, end: 0 }
 
     private curIndex = 0
     private maxIndex = 0
@@ -17,9 +17,9 @@ export class IntervalIterator<T extends number = number> implements Iterator<Seg
     hasNext: boolean = false;
 
     updateValue() {
-        this.value.index = this.curIndex
-        this.value.start = OrderedSet.findPredecessorIndex(this.set, Interval.getAt(this.interval, this.curIndex)) as  T
-        this.value.end = this.value.start + 1 as T 
+        this.value.index = this.curIndex as I
+        this.value.start = OrderedSet.findPredecessorIndex(this.set, Interval.getAt(this.interval, this.curIndex))
+        this.value.end = this.value.start + 1
     }
 
     move() {
@@ -34,7 +34,7 @@ export class IntervalIterator<T extends number = number> implements Iterator<Seg
         return this.value;
     }
 
-    constructor(private interval: Interval<T>, private set: OrderedSet<T>) {
+    constructor(private interval: Interval<I>, private set: OrderedSet<I>) {
         if (Interval.size(interval)) {
             this.curIndex = Interval.findPredecessorIndex(interval, OrderedSet.min(set))
             this.maxIndex = Interval.findPredecessorIndex(interval, OrderedSet.max(set))

+ 3 - 3
src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts

@@ -7,7 +7,7 @@
 import { ValueCell } from 'mol-util/value-cell'
 
 import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
-import { Unit, Element } from 'mol-model/structure';
+import { Unit, StructureElement } from 'mol-model/structure';
 import { DefaultStructureProps, UnitsVisual } from '..';
 import { RuntimeContext } from 'mol-task'
 import { createTransforms, createColors } from './util/common';
@@ -284,8 +284,8 @@ export function PolymerTraceVisual(): UnitsVisual<PolymerTraceProps> {
             const { objectId, instanceId, elementId } = pickingId
             if (renderObject.id === objectId) {
                 const unit = currentGroup.units[instanceId]
-                const indices = OrderedSet.ofSingleton(elementId as Element.Index);
-                return Element.Loci([{ unit, indices }])
+                const indices = OrderedSet.ofSingleton(elementId as StructureElement.UnitIndex);
+                return StructureElement.Loci([{ unit, indices }])
             }
             return EmptyLoci
         },

+ 7 - 7
src/mol-geo/representation/structure/visual/util/element.ts

@@ -5,7 +5,7 @@
  */
 
 import { Vec3 } from 'mol-math/linear-algebra';
-import { Unit, Element } from 'mol-model/structure';
+import { Unit, StructureElement } from 'mol-model/structure';
 import { SizeTheme } from '../../../../theme';
 import { RuntimeContext } from 'mol-task';
 import { icosahedronVertexCount } from '../../../../primitive/icosahedron';
@@ -19,7 +19,7 @@ import { Interval, OrderedSet } from 'mol-data/int';
 import { getPhysicalRadius } from '../../../../theme/structure/size/physical';
 import { PickingId } from '../../../../util/picking';
 
-export function getElementRadius(unit: Unit, props: SizeTheme): Element.Property<number> {
+export function getElementRadius(unit: Unit, props: SizeTheme): StructureElement.Property<number> {
     switch (props.name) {
         case 'uniform':
             return () => props.value
@@ -30,7 +30,7 @@ export function getElementRadius(unit: Unit, props: SizeTheme): Element.Property
     }
 }
 
-export async function createElementSphereMesh(ctx: RuntimeContext, unit: Unit, radius: Element.Property<number>, detail: number, mesh?: Mesh) {
+export async function createElementSphereMesh(ctx: RuntimeContext, unit: Unit, radius: StructureElement.Property<number>, detail: number, mesh?: Mesh) {
     const { elements } = unit;
     const elementCount = elements.length;
     const vertexCount = elementCount * icosahedronVertexCount(detail)
@@ -38,7 +38,7 @@ export async function createElementSphereMesh(ctx: RuntimeContext, unit: Unit, r
 
     const v = Vec3.zero()
     const pos = unit.conformation.invariantPosition
-    const l = Element.Location()
+    const l = StructureElement.create()
     l.unit = unit
 
     for (let i = 0; i < elementCount; i++) {
@@ -64,7 +64,7 @@ export function markElement(tMarker: ValueCell<TextureImage>, group: Unit.Symmet
     if (isEveryLoci(loci)) {
         applyMarkerAction(array, 0, elementCount * instanceCount, action)
         changed = true
-    } else if (Element.isLoci(loci)) {
+    } else if (StructureElement.isLoci(loci)) {
         for (const e of loci.elements) {
             const unitIdx = Unit.findUnitById(e.unit.id, group.units)
             if (unitIdx !== -1) {
@@ -96,8 +96,8 @@ export function getElementLoci(id: number, group: Unit.SymmetryGroup, pickingId:
     const { objectId, instanceId, elementId } = pickingId
     if (id === objectId) {
         const unit = group.units[instanceId]
-        const indices = OrderedSet.ofSingleton(elementId as Element.Index);
-        return Element.Loci([{ unit, indices }])
+        const indices = OrderedSet.ofSingleton(elementId as StructureElement.UnitIndex);
+        return StructureElement.Loci([{ unit, indices }])
     }
     return EmptyLoci
 }

+ 36 - 34
src/mol-geo/representation/structure/visual/util/polymer.ts

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Unit, Element, StructureProperties, Model } from 'mol-model/structure';
+import { Unit, StructureElement, StructureProperties, Model, ElementIndex } from 'mol-model/structure';
 import { Segmentation, OrderedSet, Interval } from 'mol-data/int';
 import { MoleculeType, SecondaryStructureType } from 'mol-model/structure/model/types';
 import Iterator from 'mol-data/iterator';
@@ -26,7 +26,7 @@ export function getPolymerElementCount(unit: Unit) {
     const polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), elements)
     switch (unit.kind) {
         case Unit.Kind.Atomic:
-            const residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueSegments, elements)
+            const residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements)
             while (polymerIt.hasNext) {
                 const polymerSegment = polymerIt.move()
                 residueIt.setSegment(polymerSegment)
@@ -49,7 +49,7 @@ export function getPolymerElementCount(unit: Unit) {
     return count
 }
 
-function getTraceName(l: Element.Location) {
+function getTraceName(l: StructureElement) {
     const compId = StructureProperties.residue.label_comp_id(l)
     const chemCompMap = l.unit.model.properties.chemicalComponentMap
     const cc = chemCompMap.get(compId)
@@ -63,7 +63,7 @@ function getTraceName(l: Element.Location) {
     return traceName
 }
 
-function setTraceElement(l: Element.Location, residueSegment: Segmentation.Segment<Element>) {
+function setTraceElement(l: StructureElement, residueSegment: Segmentation.Segment) {
     const elements = l.unit.elements
     l.element = elements[residueSegment.start]
     const traceName = getTraceName(l)
@@ -94,7 +94,8 @@ function getTraceName2(model: Model, residueModelIndex: number) {
     return traceName
 }
 
-function getTraceElement2(model: Model, residueModelSegment: Segmentation.Segment<Element>) {
+// TODO fix type
+function getTraceElement2(model: Model, residueModelSegment: Segmentation.Segment<number>) {
     const traceName = getTraceName2(model, residueModelSegment.index)
 
     for (let j = residueModelSegment.start, _j = residueModelSegment.end; j < _j; j++) {
@@ -130,7 +131,8 @@ function getDirectionName2(model: Model, residueModelIndex: number) {
 //     return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}`
 // }
 
-function getDirectionElement2(model: Model, residueModelSegment: Segmentation.Segment<Element>) {
+// TODO fix type
+function getDirectionElement2(model: Model, residueModelSegment: Segmentation.Segment<number>) {
     const traceName = getDirectionName2(model, residueModelSegment.index)
 
     for (let j = residueModelSegment.start, _j = residueModelSegment.end; j < _j; j++) {
@@ -152,8 +154,8 @@ export function PolymerBackboneIterator(unit: Unit): Iterator<PolymerBackbonePai
 }
 
 interface PolymerBackbonePair {
-    centerA: Element.Location
-    centerB: Element.Location
+    centerA: StructureElement
+    centerB: StructureElement
     indexA: number
     indexB: number
     posA: Vec3
@@ -162,8 +164,8 @@ interface PolymerBackbonePair {
 
 function createPolymerBackbonePair (unit: Unit) {
     return {
-        centerA: Element.Location(unit),
-        centerB: Element.Location(unit),
+        centerA: StructureElement.create(unit),
+        centerB: StructureElement.create(unit),
         indexA: 0,
         indexB: 0,
         posA: Vec3.zero(),
@@ -176,9 +178,9 @@ const enum AtomicPolymerBackboneIteratorState { nextPolymer, firstResidue, nextR
 export class AtomicPolymerBackboneIterator<T extends number = number> implements Iterator<PolymerBackbonePair> {
     private value: PolymerBackbonePair
 
-    private polymerIt: SortedRanges.Iterator<Element>
-    private residueIt: Segmentation.Iterator<Element>
-    private polymerSegment: Segmentation.Segment<Element>
+    private polymerIt: SortedRanges.Iterator<ElementIndex>
+    private residueIt: Segmentation.SegmentIterator<number> // TODO specific type
+    private polymerSegment: Segmentation.Segment<ElementIndex>
     private state: AtomicPolymerBackboneIteratorState = AtomicPolymerBackboneIteratorState.nextPolymer
     private pos: SymmetryOperator.CoordinateMapper
 
@@ -228,10 +230,10 @@ export class AtomicPolymerBackboneIterator<T extends number = number> implements
     }
 
     constructor(unit: Unit.Atomic) {
-        const { residueSegments } = unit.model.atomicHierarchy
+        const { residueAtomSegments } = unit.model.atomicHierarchy
         // console.log('unit.elements', OrderedSet.toArray(unit.elements))
         this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements)
-        this.residueIt = Segmentation.transientSegments(residueSegments, unit.elements)
+        this.residueIt = Segmentation.transientSegments(residueAtomSegments, unit.elements)
         this.pos = unit.conformation.invariantPosition
         this.value = createPolymerBackbonePair(unit)
 
@@ -244,8 +246,8 @@ const enum CoarsePolymerBackboneIteratorState { nextPolymer, nextElement }
 export class CoarsePolymerBackboneIterator<T extends number = number> implements Iterator<PolymerBackbonePair> {
     private value: PolymerBackbonePair
 
-    private polymerIt: SortedRanges.Iterator<Element>
-    private polymerSegment: Segmentation.Segment<Element>
+    private polymerIt: SortedRanges.Iterator<ElementIndex>
+    private polymerSegment: Segmentation.Segment<ElementIndex>
     private state: CoarsePolymerBackboneIteratorState = CoarsePolymerBackboneIteratorState.nextPolymer
     private pos: SymmetryOperator.CoordinateMapper
     private elementIndex: number
@@ -321,7 +323,7 @@ export function PolymerTraceIterator(unit: Unit): Iterator<PolymerTraceElement>
 }
 
 interface PolymerTraceElement {
-    center: Element.Location
+    center: StructureElement
     index: number
     first: boolean
     last: boolean
@@ -339,7 +341,7 @@ interface PolymerTraceElement {
 
 function createPolymerTraceElement (unit: Unit): PolymerTraceElement {
     return {
-        center: Element.Location(unit),
+        center: StructureElement.create(unit),
         index: 0,
         first: false,
         last: false,
@@ -358,13 +360,13 @@ function createPolymerTraceElement (unit: Unit): PolymerTraceElement {
 
 const enum AtomicPolymerTraceIteratorState { nextPolymer, nextResidue }
 
-function setSegment (outSegment: Segmentation.Segment<Element>, index: number, segments: Segmentation<Element>, min: number, max: number): Segmentation.Segment<Element> {
+function setSegment (outSegment: Segmentation.Segment<number>, index: number, segments: Segmentation<number>, min: number, max: number): Segmentation.Segment<number> {
     // index = Math.min(Math.max(0, index), segments.segments.length - 2)
     const _index = Math.min(Math.max(min, index), max)
     if (isNaN(_index)) console.log(_index, index, min, max)
     outSegment.index = _index
-    outSegment.start = segments.segments[_index]
-    outSegment.end = segments.segments[_index + 1]
+    outSegment.start = segments.offsets[_index]
+    outSegment.end = segments.offsets[_index + 1]
     // console.log(index, {...outSegment}, {...boundingSegment}, segments.segments[boundingSegment.index])
     return outSegment
 }
@@ -372,14 +374,14 @@ function setSegment (outSegment: Segmentation.Segment<Element>, index: number, s
 export class AtomicPolymerTraceIterator<T extends number = number> implements Iterator<PolymerTraceElement> {
     private value: PolymerTraceElement
 
-    private polymerIt: SortedRanges.Iterator<Element>
-    private residueIt: Segmentation.Iterator<Element>
+    private polymerIt: SortedRanges.Iterator<ElementIndex>
+    private residueIt: Segmentation.SegmentIterator<number> // TODO specialize type
     private residueSegmentMin: number
     private residueSegmentMax: number
     private state: AtomicPolymerTraceIteratorState = AtomicPolymerTraceIteratorState.nextPolymer
-    private residueSegments: Segmentation<Element>
+    private residueSegments: Segmentation<ElementIndex>
 
-    private tmpSegment: Segmentation.Segment<Element>
+    private tmpSegment: Segmentation.Segment<number>
 
     private unit: Unit.Atomic
 
@@ -391,12 +393,12 @@ export class AtomicPolymerTraceIterator<T extends number = number> implements It
         target[2] = this.unit.model.atomicConformation.z[index]
     }
 
-    updateResidueSegmentRange(polymerSegment: Segmentation.Segment<Element>) {
-        const { polymerRanges, residueSegments } = this.unit.model.atomicHierarchy
+    updateResidueSegmentRange(polymerSegment: Segmentation.Segment<ElementIndex>) {
+        const { polymerRanges, residueAtomSegments } = this.unit.model.atomicHierarchy
         const sMin = polymerRanges[polymerSegment.index * 2]
         const sMax = polymerRanges[polymerSegment.index * 2 + 1]
-        this.residueSegmentMin = residueSegments.segmentMap[sMin]
-        this.residueSegmentMax = residueSegments.segmentMap[sMax]
+        this.residueSegmentMin = residueAtomSegments.index[sMin]
+        this.residueSegmentMax = residueAtomSegments.index[sMax]
     }
 
     move() {
@@ -454,14 +456,14 @@ export class AtomicPolymerTraceIterator<T extends number = number> implements It
     }
 
     constructor(unit: Unit.Atomic) {
-        const { residueSegments } = unit.model.atomicHierarchy
+        const { residueAtomSegments } = unit.model.atomicHierarchy
         this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements)
-        this.residueIt = Segmentation.transientSegments(residueSegments, unit.elements);
-        this.residueSegments = residueSegments
+        this.residueIt = Segmentation.transientSegments(residueAtomSegments, unit.elements);
+        this.residueSegments = residueAtomSegments
         this.value = createPolymerTraceElement(unit)
         this.hasNext = this.residueIt.hasNext && this.polymerIt.hasNext
 
-        this.tmpSegment = { index: 0, start: 0 as Element, end: 0 as Element }
+        this.tmpSegment = { index: 0, start: 0 as ElementIndex, end: 0 as ElementIndex }
 
         this.unit = unit
     }

+ 7 - 7
src/mol-geo/theme/structure/color/chain-id.ts

@@ -4,13 +4,13 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Unit, StructureProperties, Element } from 'mol-model/structure';
+import { Unit, StructureProperties, StructureElement } from 'mol-model/structure';
 
 import { StructureColorDataProps } from '.';
 import { ColorData, createElementColor, createUniformColor } from '../../../util/color-data';
 import { ColorScale } from 'mol-util/color';
 
-function getAsymId(unit: Unit): Element.Property<string> {
+function getAsymId(unit: Unit): StructureElement.Property<string> {
     switch (unit.kind) {
         case Unit.Kind.Atomic:
             return StructureProperties.chain.label_asym_id
@@ -21,7 +21,7 @@ function getAsymId(unit: Unit): Element.Property<string> {
     throw new Error('unhandled unit kind')
 }
 
-export function chainIdColorData(props: StructureColorDataProps, locationFn: (l: Element.Location, renderElementIdx: number) => void, colorData?: ColorData) {
+export function chainIdColorData(props: StructureColorDataProps, locationFn: (l: StructureElement, renderElementIdx: number) => void, colorData?: ColorData) {
     const { group: { units }, elementCount } = props
     const unit = units[0]
 
@@ -32,7 +32,7 @@ export function chainIdColorData(props: StructureColorDataProps, locationFn: (l:
     const scale = ColorScale.create({ domain })
     const asym_id = getAsymId(unit)
 
-    const l = Element.Location()
+    const l = StructureElement.create()
     l.unit = unit
 
     return createElementColor({
@@ -46,7 +46,7 @@ export function chainIdColorData(props: StructureColorDataProps, locationFn: (l:
 
 export function chainIdElementColorData(props: StructureColorDataProps, colorData?: ColorData) {
     const elements = props.group.units[0].elements
-    function locationFn(l: Element.Location, renderElementIdx: number) {
+    function locationFn(l: StructureElement, renderElementIdx: number) {
         l.element = elements[renderElementIdx]
     }
     return chainIdColorData(props, locationFn, colorData)
@@ -55,11 +55,11 @@ export function chainIdElementColorData(props: StructureColorDataProps, colorDat
 export function chainIdLinkColorData(props: StructureColorDataProps, colorData?: ColorData): ColorData {
     const unit = props.group.units[0]
     const elements = unit.elements
-    let locationFn: (l: Element.Location, renderElementIdx: number) => void
+    let locationFn: (l: StructureElement, renderElementIdx: number) => void
     switch (unit.kind) {
         case Unit.Kind.Atomic:
             const { a } = unit.links
-            locationFn = (l: Element.Location, renderElementIdx: number) => {
+            locationFn = (l: StructureElement, renderElementIdx: number) => {
                 l.element = elements[a[renderElementIdx]]
             }
             return chainIdColorData(props, locationFn, colorData)

+ 3 - 3
src/mol-geo/theme/structure/size/physical.ts

@@ -4,11 +4,11 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Element, Unit, StructureProperties } from 'mol-model/structure';
+import { StructureElement, Unit, StructureProperties } from 'mol-model/structure';
 import { StructureSizeDataProps } from '.';
 import { createAttributeSize } from '../../../util/size-data';
 
-export function getPhysicalRadius(unit: Unit): Element.Property<number> {
+export function getPhysicalRadius(unit: Unit): StructureElement.Property<number> {
     if (Unit.isAtomic(unit)) {
         return StructureProperties.atom.vdw_radius
     } else if (Unit.isSpheres(unit)) {
@@ -27,7 +27,7 @@ export function physicalSizeData(factor: number, props: StructureSizeDataProps)
     const unit = group.units[0]
     const elements = group.elements;
     const radius = getPhysicalRadius(unit)
-    const l = Element.Location()
+    const l = StructureElement.create()
     l.unit = unit
     return createAttributeSize({
         sizeFn: (elementIdx: number) => {

+ 15 - 5
src/mol-io/reader/cif/schema/mmcif.ts

@@ -80,16 +80,26 @@ export const mmCIF_Schema = {
         pdbx_aromatic_flag: Aliased<'Y' | 'N'>(str),
     },
     entity: {
-        details: str,
-        formula_weight: float,
         id: str,
-        src_method: Aliased<'nat' | 'man' | 'syn'>(str),
         type: Aliased<'polymer' | 'non-polymer' | 'macrolide' | 'water'>(str),
+        src_method: Aliased<'nat' | 'man' | 'syn'>(str),
+        formula_weight: float,
         pdbx_description: str,
         pdbx_number_of_molecules: float,
         pdbx_mutation: str,
         pdbx_fragment: str,
         pdbx_ec: List(',', x => x),
+        details: str,
+    },
+    entity_poly: {
+        entity_id: str,
+        type: str,
+        nstd_linkage: Aliased<'no' | 'n' | 'yes' | 'y'>(str),
+        nstd_monomer: Aliased<'no' | 'n' | 'yes' | 'y'>(str),
+        pdbx_seq_one_letter_code: str,
+        pdbx_seq_one_letter_code_can: str,
+        pdbx_strand_id: str,
+        pdbx_target_identifier: str,
     },
     entity_poly_seq: {
         entity_id: str,
@@ -109,11 +119,11 @@ export const mmCIF_Schema = {
         title: str,
     },
     struct_asym: {
-        details: str,
-        entity_id: str,
         id: str,
+        entity_id: str,
         pdbx_modified: str,
         pdbx_blank_PDB_chainid_flag: Aliased<'Y' | 'N'>(str),
+        details: str,
     },
     struct_conf: {
         beg_label_asym_id: str,

+ 13 - 13
src/mol-io/writer/cif/encoder.ts

@@ -69,19 +69,19 @@ export namespace Field {
     }
 }
 
-export interface Category<Key = any, Data = any> {
+export interface Category<Ctx = any> {
     name: string,
-    fields: Field<Key, Data>[],
-    data?: Data,
-    rowCount: number,
-    keys?: () => Iterator<Key>
+    instance(ctx: Ctx): Category.Instance
 }
 
 export namespace Category {
-    export const Empty: Category = { name: 'empty', rowCount: 0, fields: [] };
+    export const Empty: Instance = { fields: [], rowCount: 0 };
 
-    export interface Provider<Ctx = any> {
-        (ctx: Ctx): Category
+    export interface Instance<Key = any, Data = any> {
+        fields: Field[],
+        data?: Data,
+        rowCount: number,
+        keys?: () => Iterator<Key>
     }
 
     export interface Filter {
@@ -102,11 +102,11 @@ export namespace Category {
         getFormat(cat, field) { return void 0; }
     }
 
-    export function ofTable(name: string, table: Table<Table.Schema>, indices?: ArrayLike<number>): Category<number, Table<Table.Schema>> {
+    export function ofTable(table: Table<Table.Schema>, indices?: ArrayLike<number>): Category.Instance {
         if (indices) {
-            return { name, fields: cifFieldsFromTableSchema(table._schema), data: table, rowCount: indices.length, keys: () => Iterator.Array(indices) };
+            return { fields: cifFieldsFromTableSchema(table._schema), data: table, rowCount: indices.length, keys: () => Iterator.Array(indices) };
         }
-        return { name, fields: cifFieldsFromTableSchema(table._schema), data: table, rowCount: table._rowCount };
+        return { fields: cifFieldsFromTableSchema(table._schema), data: table, rowCount: table._rowCount };
     }
 }
 
@@ -115,7 +115,7 @@ export interface Encoder<T = string | Uint8Array> extends EncoderBase {
     setFormatter(formatter?: Category.Formatter): void,
 
     startDataBlock(header: string): void,
-    writeCategory<Ctx>(category: Category.Provider<Ctx>, contexts?: Ctx[]): void,
+    writeCategory<Ctx>(category: Category<Ctx>, contexts?: Ctx[]): void,
     getData(): T
 }
 
@@ -123,7 +123,7 @@ export namespace Encoder {
     export function writeDatabase(encoder: Encoder, name: string, database: Database<Database.Schema>) {
         encoder.startDataBlock(name);
         for (const table of database._tableNames) {
-            encoder.writeCategory(() => Category.ofTable(table, database[table]));
+            encoder.writeCategory({ name: table, instance: () => Category.ofTable(database[table]) });
         }
     }
 

+ 12 - 12
src/mol-io/writer/cif/encoder/binary.ts

@@ -38,7 +38,7 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
         });
     }
 
-    writeCategory<Ctx>(category: Category.Provider<Ctx>, contexts?: Ctx[]) {
+    writeCategory<Ctx>(category: Category<Ctx>, contexts?: Ctx[]) {
         if (!this.data) {
             throw new Error('The writer contents have already been encoded, no more writing.');
         }
@@ -47,23 +47,23 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
             throw new Error('No data block created.');
         }
 
-        const src = !contexts || !contexts.length ? [category(<any>void 0)] : contexts.map(c => category(c));
-        const categories = src.filter(c => c && c.rowCount > 0);
-        if (!categories.length) return;
-        if (!this.filter.includeCategory(categories[0].name)) return;
+        if (!this.filter.includeCategory(category.name)) return;
 
-        const count = categories.reduce((a, c) => a + c.rowCount, 0);
+        const src = !contexts || !contexts.length ? [category.instance(<any>void 0)] : contexts.map(c => category.instance(c));
+        const instances = src.filter(c => c && c.rowCount > 0);
+        if (!instances.length) return;
+
+        const count = instances.reduce((a, c) => a + c.rowCount, 0);
         if (!count) return;
 
-        const first = categories[0]!;
-        const cat: EncodedCategory = { name: '_' + first.name, columns: [], rowCount: count };
-        const data = categories.map(c => ({ data: c.data, keys: () => c.keys ? c.keys() : Iterator.Range(0, c.rowCount - 1) }));
-        const fields = getIncludedFields(first);
+        const cat: EncodedCategory = { name: '_' + category.name, columns: [], rowCount: count };
+        const data = instances.map(c => ({ data: c.data, keys: () => c.keys ? c.keys() : Iterator.Range(0, c.rowCount - 1) }));
+        const fields = getIncludedFields(instances[0]);
 
         for (const f of fields) {
-            if (!this.filter.includeField(first.name, f.name)) continue;
+            if (!this.filter.includeField(category.name, f.name)) continue;
 
-            const format = this.formatter.getFormat(first.name, f.name);
+            const format = this.formatter.getFormat(category.name, f.name);
             cat.columns.push(encodeField(f, data, count, getArrayCtor(f, format), getEncoder(f, format)));
         }
         // no columns included.

+ 24 - 23
src/mol-io/writer/cif/encoder/text.ts

@@ -33,7 +33,7 @@ export default class TextEncoder implements Encoder<string> {
         StringBuilder.write(this.builder, `data_${(header || '').replace(/[ \n\t]/g, '').toUpperCase()}\n#\n`);
     }
 
-    writeCategory<Ctx>(category: Category.Provider<Ctx>, contexts?: Ctx[]) {
+    writeCategory<Ctx>(category: Category<Ctx>, contexts?: Ctx[]) {
         if (this.encoded) {
             throw new Error('The writer contents have already been encoded, no more writing.');
         }
@@ -42,18 +42,20 @@ export default class TextEncoder implements Encoder<string> {
             throw new Error('No data block created.');
         }
 
-        const categories = !contexts || !contexts.length ? [category(<any>void 0)] : contexts.map(c => category(c));
-        if (!categories.length) return;
-        if (!this.filter.includeCategory(categories[0].name)) return;
+        if (!this.filter.includeCategory(category.name)) return;
 
-        const rowCount = categories.reduce((v, c) => v + c.rowCount, 0);
+        const src = !contexts || !contexts.length ? [category.instance(<any>void 0)] : contexts.map(c => category.instance(c));
+        const instances = src.filter(c => c && c.rowCount > 0);
+        if (!instances.length) return;
+
+        const rowCount = instances.reduce((v, c) => v + c.rowCount, 0);
 
         if (rowCount === 0) return;
 
         if (rowCount === 1) {
-            writeCifSingleRecord(categories[0]!, this.builder, this.filter, this.formatter);
+            writeCifSingleRecord(category, instances[0]!, this.builder, this.filter, this.formatter);
         } else {
-            writeCifLoop(categories, this.builder, this.filter, this.formatter);
+            writeCifLoop(category, instances, this.builder, this.filter, this.formatter);
         }
     }
 
@@ -108,19 +110,19 @@ function getFloatPrecisions(categoryName: string, fields: Field[], formatter: Ca
     return ret;
 }
 
-function writeCifSingleRecord(category: Category<any>, builder: StringBuilder, filter: Category.Filter, formatter: Category.Formatter) {
-    const fields = getIncludedFields(category);
-    const data = category.data;
+function writeCifSingleRecord(category: Category, instance: Category.Instance, builder: StringBuilder, filter: Category.Filter, formatter: Category.Formatter) {
+    const fields = getIncludedFields(instance);
+    const data = instance.data;
     let width = fields.reduce((w, f) => filter.includeField(category.name, f.name) ? Math.max(w, f.name.length) : 0, 0);
 
     // this means no field from this category is included.
     if (width === 0) return;
     width += category.name.length + 6;
 
-    const it = category.keys ? category.keys() : Iterator.Range(0, category.rowCount - 1);
+    const it = instance.keys ? instance.keys() : Iterator.Range(0, instance.rowCount - 1);
     const key = it.move();
 
-    const precisions = getFloatPrecisions(category.name, category.fields, formatter);
+    const precisions = getFloatPrecisions(category.name, instance.fields, formatter);
 
     for (let _f = 0; _f < fields.length; _f++) {
         const f = fields[_f];
@@ -133,28 +135,27 @@ function writeCifSingleRecord(category: Category<any>, builder: StringBuilder, f
     StringBuilder.write(builder, '#\n');
 }
 
-function writeCifLoop(categories: Category[], builder: StringBuilder, filter: Category.Filter, formatter: Category.Formatter) {
-    const first = categories[0];
-    const fieldSource = getIncludedFields(first);
-    const fields = filter === Category.DefaultFilter ? fieldSource : fieldSource.filter(f => filter.includeField(first.name, f.name));
+function writeCifLoop(category: Category, instances: Category.Instance[], builder: StringBuilder, filter: Category.Filter, formatter: Category.Formatter) {
+    const fieldSource = getIncludedFields(instances[0]);
+    const fields = filter === Category.DefaultFilter ? fieldSource : fieldSource.filter(f => filter.includeField(category.name, f.name));
     const fieldCount = fields.length;
     if (fieldCount === 0) return;
 
-    const precisions = getFloatPrecisions(first.name, fields, formatter);
+    const precisions = getFloatPrecisions(category.name, fields, formatter);
 
     writeLine(builder, 'loop_');
     for (let i = 0; i < fieldCount; i++) {
-        writeLine(builder, `_${first.name}.${fields[i].name}`);
+        writeLine(builder, `_${category.name}.${fields[i].name}`);
     }
 
     let index = 0;
-    for (let _c = 0; _c < categories.length; _c++) {
-        const category = categories[_c];
-        const data = category.data;
+    for (let _c = 0; _c < instances.length; _c++) {
+        const instance = instances[_c];
+        const data = instance.data;
 
-        if (category.rowCount === 0) continue;
+        if (instance.rowCount === 0) continue;
 
-        const it = category.keys ? category.keys() : Iterator.Range(0, category.rowCount - 1);
+        const it = instance.keys ? instance.keys() : Iterator.Range(0, instance.rowCount - 1);
         while (it.hasNext)  {
             const key = it.move();
 

+ 1 - 1
src/mol-io/writer/cif/encoder/util.ts

@@ -11,7 +11,7 @@ export function getFieldDigitCount(field: Field) {
     return 6;
 }
 
-export function getIncludedFields(category: Category<any, any>) {
+export function getIncludedFields(category: Category.Instance) {
     return category.fields.some(f => !!f.shouldInclude)
         ? category.fields.filter(f => !f.shouldInclude || f.shouldInclude(category.data))
         : category.fields;

+ 2 - 2
src/mol-model/loci.ts

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Element } from './structure'
+import { StructureElement } from './structure'
 import { Link } from './structure/structure/unit/links'
 
 /** A Loci that includes every loci */
@@ -21,4 +21,4 @@ export function isEmptyLoci(x: any): x is EmptyLoci {
     return !!x && x.kind === 'empty-loci';
 }
 
-export type Loci =  Element.Loci | Link.Loci | EveryLoci | EmptyLoci
+export type Loci =  StructureElement.Loci | Link.Loci | EveryLoci | EmptyLoci

+ 13 - 11
src/mol-model/structure/export/categories/atom_site.ts

@@ -6,13 +6,13 @@
  */
 
 import { CifWriter } from 'mol-io/writer/cif';
-import { Element, Structure, StructureProperties as P } from '../../structure';
+import { StructureElement, Structure, StructureProperties as P } from '../../structure';
 import { CifExportContext } from '../mmcif';
 import CifField = CifWriter.Field
 import CifCategory = CifWriter.Category
 import E = CifWriter.Encodings
 
-const atom_site_fields: CifField<Element.Location>[] = [
+const atom_site_fields: CifField<StructureElement>[] = [
     CifField.str('group_PDB', P.residue.group_PDB),
     CifField.index('id'),
     CifField.str('type_symbol', P.atom.type_symbol as any),
@@ -38,17 +38,19 @@ const atom_site_fields: CifField<Element.Location>[] = [
     CifField.str('auth_asym_id', P.chain.auth_asym_id),
 
     CifField.int('pdbx_PDB_model_num', P.unit.model_num, { encoder: E.deltaRLE }),
-    CifField.str<Element.Location, Structure>('operator_name', P.unit.operator_name, {
-        shouldInclude: structure => structure.units.some(u => !u.conformation.operator.isIdentity)
+    CifField.str<StructureElement, Structure>('operator_name', P.unit.operator_name, {
+        shouldInclude: structure => { console.log(!!structure); return structure.units.some(u => !u.conformation.operator.isIdentity) }
     })
 ];
 
-export function _atom_site({ structure }: CifExportContext): CifCategory {
-    return {
-        data: structure,
-        name: 'atom_site',
-        fields: atom_site_fields,
-        rowCount: structure.elementCount,
-        keys: () => structure.elementLocations()
+export const _atom_site: CifCategory<CifExportContext> = {
+    name: 'atom_site',
+    instance({ structure }: CifExportContext) {
+        return {
+            fields: atom_site_fields,
+            data: structure,
+            rowCount: structure.elementCount,
+            keys: () => structure.elementLocations()
+        };
     }
 }

+ 59 - 0
src/mol-model/structure/export/categories/modified-residues.ts

@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2017-2018 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 { Segmentation } from 'mol-data/int';
+import { CifWriter } from 'mol-io/writer/cif';
+import { StructureElement, StructureProperties as P, Unit } from '../../../structure';
+import { CifExportContext } from '../mmcif';
+
+import CifField = CifWriter.Field
+import CifCategory = CifWriter.Category
+
+const pdbx_struct_mod_residue_fields: CifField<number, StructureElement[]>[] = [
+    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])),
+    CifField.str(`pdbx_PDB_ins_code`, (i, xs) => P.residue.pdbx_PDB_ins_code(xs[i])),
+    CifField.str(`label_asym_id`, (i, xs) => P.chain.label_asym_id(xs[i])),
+    CifField.str(`label_entity_id`, (i, xs) => P.chain.label_entity_id(xs[i])),
+    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('details', (i, xs) => xs[i].unit.model.properties.modifiedResidues.details.get(P.residue.label_comp_id(xs[i]))!)
+];
+
+function getModifiedResidues({ model, structure }: CifExportContext): StructureElement[] {
+    const map = model.properties.modifiedResidues.parentId;
+    if (!map.size) return [];
+
+    const ret = [];
+    const prop = P.residue.label_comp_id;
+    const loc = StructureElement.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);
+        loc.unit = unit;
+        while (residues.hasNext) {
+            const seg = residues.move();
+            loc.element = unit.elements[seg.start];
+            const name = prop(loc);
+            if (map.has(name)) {
+                ret[ret.length] = StructureElement.create(loc.unit, loc.element);
+            }
+        }
+    }
+    return ret;
+}
+
+export const _pdbx_struct_mod_residue: CifCategory<CifExportContext> = {
+    name: 'pdbx_struct_mod_residue',
+    instance(ctx) {
+        const residues = getModifiedResidues(ctx);
+        return { fields: pdbx_struct_mod_residue_fields, data: residues, rowCount: residues.length };
+    }
+}

+ 30 - 30
src/mol-model/structure/export/categories/secondary-structure.ts

@@ -7,36 +7,36 @@
 import { Segmentation } from 'mol-data/int';
 import { CifWriter } from 'mol-io/writer/cif';
 import { SecondaryStructure } from '../../model/properties/seconday-structure';
-import { Element, Unit, StructureProperties as P } from '../../structure';
+import { StructureElement, Unit, StructureProperties as P } from '../../structure';
 import { CifExportContext } from '../mmcif';
-
 import CifField = CifWriter.Field
 import CifCategory = CifWriter.Category
 import { Column } from 'mol-data/db';
 
-export function _struct_conf(ctx: CifExportContext): CifCategory {
-    const elements = findElements(ctx, 'helix');
-    return {
-        data: elements,
-        name: 'struct_conf',
-        fields: struct_conf_fields,
-        rowCount: elements.length
-    };
-}
+export const _struct_conf: CifCategory<CifExportContext> = {
+    name: 'struct_conf',
+    instance(ctx) {
+        const elements = findElements(ctx, 'helix');
+        return { fields: struct_conf_fields, data: elements, rowCount: elements.length };
+    }
+};
 
-export function _struct_sheet_range(ctx: CifExportContext): CifCategory {
-    const elements = findElements(ctx, 'sheet');
-    return {
-        data: elements,
-        name: 'struct_sheet_range',
-        fields: struct_sheet_range_fields,
-        rowCount: elements.length
-    };
-}
+export const _struct_sheet_range: CifCategory<CifExportContext> = {
+    name: 'struct_sheet_range',
+    instance(ctx) {
+        const elements = (findElements(ctx, 'sheet') as SSElement<SecondaryStructure.Sheet>[]).sort(compare_ssr);
+        return { fields: struct_sheet_range_fields, data: elements, rowCount: elements.length };
+    }
+};
+
+function compare_ssr(x: SSElement<SecondaryStructure.Sheet>, y: SSElement<SecondaryStructure.Sheet>) {
+    const a = x.element, b = y.element;
+    return a.sheet_id < b.sheet_id ? -1 : a.sheet_id === b.sheet_id ? x.start.element - y.start.element : 1
+};
 
 const struct_conf_fields: CifField[] = [
     CifField.str<number, SSElement<SecondaryStructure.Helix>[]>('conf_type_id', (i, data) => data[i].element.type_id),
-    CifField.str<number, SSElement<SecondaryStructure.Helix>[]>('conf_type_id', (i, data, idx) => `${data[i].element.type_id}${idx + 1}`),
+    CifField.str<number, SSElement<SecondaryStructure.Helix>[]>('id', (i, data, idx) => `${data[i].element.type_id}${idx + 1}`),
     ...residueIdFields('beg_', e => e.start),
     ...residueIdFields('end_', e => e.end),
     CifField.str<number, SSElement<SecondaryStructure.Helix>[]>('pdbx_PDB_helix_class', (i, data) => data[i].element.helix_class),
@@ -47,20 +47,20 @@ const struct_conf_fields: CifField[] = [
 ];
 
 const struct_sheet_range_fields: CifField[] = [
-    CifField.index('id'),
     CifField.str<number, SSElement<SecondaryStructure.Sheet>[]>('sheet_id', (i, data) => data[i].element.sheet_id),
+    CifField.index('id'),
     ...residueIdFields('beg_', e => e.start),
     ...residueIdFields('end_', e => e.end),
     CifField.str('symmetry', (i, data) => '', { valueKind: (i, d) => Column.ValueKind.Unknown })
 ];
 
-function residueIdFields(prefix: string, loc: (e: SSElement<any>) => Element.Location): CifField<number, SSElement<SecondaryStructure.Helix>[]>[] {
+function residueIdFields(prefix: string, loc: (e: SSElement<any>) => StructureElement): CifField<number, SSElement<SecondaryStructure.Helix>[]>[] {
     return [
         CifField.str(`${prefix}label_comp_id`, (i, d) => P.residue.label_comp_id(loc(d[i]))),
         CifField.int(`${prefix}label_seq_id`, (i, d) => P.residue.label_seq_id(loc(d[i]))),
         CifField.str(`pdbx_${prefix}PDB_ins_code`, (i, d) => P.residue.pdbx_PDB_ins_code(loc(d[i]))),
         CifField.str(`${prefix}label_asym_id`, (i, d) => P.chain.label_asym_id(loc(d[i]))),
-        CifField.str(`${prefix}_entity_id`, (i, d) => P.chain.label_entity_id(loc(d[i]))),
+        CifField.str(`${prefix}label_entity_id`, (i, d) => P.chain.label_entity_id(loc(d[i]))),
         CifField.str(`${prefix}auth_comp_id`, (i, d) => P.residue.auth_comp_id(loc(d[i]))),
         CifField.int(`${prefix}auth_seq_id`, (i, d) => P.residue.auth_seq_id(loc(d[i]))),
         CifField.str(`${prefix}auth_asym_id`, (i, d) => P.chain.auth_asym_id(loc(d[i])))
@@ -68,8 +68,8 @@ function residueIdFields(prefix: string, loc: (e: SSElement<any>) => Element.Loc
 }
 
 interface SSElement<T extends SecondaryStructure.Element> {
-    start: Element.Location,
-    end: Element.Location,
+    start: StructureElement,
+    end: StructureElement,
     length: number,
     element: T
 }
@@ -83,10 +83,10 @@ function findElements<T extends SecondaryStructure.Element>(ctx: CifExportContex
         // currently can only support this for "identity" operators.
         if (!Unit.isAtomic(unit) || !unit.conformation.operator.isIdentity) continue;
 
-        const segs = unit.model.atomicHierarchy.residueSegments;
+        const segs = unit.model.atomicHierarchy.residueAtomSegments;
         const residues = Segmentation.transientSegments(segs, unit.elements);
 
-        let current: Segmentation.Segment<Element>, move = true;
+        let current: Segmentation.Segment, move = true;
         while (residues.hasNext) {
             if (move) current = residues.move();
 
@@ -105,8 +105,8 @@ function findElements<T extends SecondaryStructure.Element>(ctx: CifExportContex
                 if (startIdx !== key[current.index]) {
                     move = false;
                     ssElements[ssElements.length] = {
-                        start: Element.Location(unit, segs.segments[start]),
-                        end: Element.Location(unit, segs.segments[prev]),
+                        start: StructureElement.create(unit, segs.offsets[start]),
+                        end: StructureElement.create(unit, segs.offsets[prev]),
                         length: prev - start + 1,
                         element
                     }

+ 29 - 15
src/mol-model/structure/export/mmcif.ts

@@ -10,27 +10,34 @@ import { mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif'
 import { Structure } from '../structure'
 import { Model } from '../model'
 import { _atom_site } from './categories/atom_site';
+import CifCategory = CifWriter.Category
+import { _struct_conf, _struct_sheet_range } from './categories/secondary-structure';
+import { _pdbx_struct_mod_residue } from './categories/modified-residues';
 
 export interface CifExportContext {
     structure: Structure,
-    model: Model
+    model: Model,
+    cache: any
 }
 
-import CifCategory = CifWriter.Category
-import { _struct_conf, _struct_sheet_range } from './categories/secondary-structure';
-
-function copy_mmCif_category(name: keyof mmCIF_Schema) {
-    return ({ model }: CifExportContext) => {
-        if (model.sourceData.kind !== 'mmCIF') return CifCategory.Empty;
-        const table = model.sourceData.data[name];
-        if (!table || !table._rowCount) return CifCategory.Empty;
-        return CifCategory.ofTable(name, table);
+function copy_mmCif_category(name: keyof mmCIF_Schema): CifCategory<CifExportContext> {
+    return {
+        name,
+        instance({ model }) {
+            if (model.sourceData.kind !== 'mmCIF') return CifCategory.Empty;
+            const table = model.sourceData.data[name];
+            if (!table || !table._rowCount) return CifCategory.Empty;
+            return CifCategory.ofTable(table);
+        }
     };
 }
 
-function _entity({ model, structure }: CifExportContext): CifCategory {
-    const keys = Structure.getEntityKeys(structure);
-    return CifCategory.ofTable('entity', model.entities.data, keys);
+const _entity: CifCategory<CifExportContext> = {
+    name: 'entity',
+    instance({ structure, model}) {
+        const keys = Structure.getEntityKeys(structure);
+        return CifCategory.ofTable(model.entities.data, keys);
+    }
 }
 
 const Categories = [
@@ -52,11 +59,18 @@ const Categories = [
     _struct_conf,
     _struct_sheet_range,
 
+    // Sequence
+    copy_mmCif_category('struct_asym'), // TODO: filter only present chains?
+    copy_mmCif_category('entity_poly'),
+    copy_mmCif_category('entity_poly_seq'),
+
     // Misc
     // TODO: filter for actual present residues?
     copy_mmCif_category('chem_comp'),
     copy_mmCif_category('atom_sites'),
 
+    _pdbx_struct_mod_residue,
+
     // Atoms
     _atom_site
 ];
@@ -78,13 +92,13 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structure: S
     if (models.length !== 1) throw 'Can\'t export stucture composed from multiple models.';
     const model = models[0];
 
-    const ctx: CifExportContext[] = [{ structure, model }];
+    const ctx: CifExportContext[] = [{ structure, model, cache: Object.create(null) }];
 
     for (const cat of Categories) {
         encoder.writeCategory(cat, ctx);
     }
     for (const customProp of model.customProperties.all) {
-        const cats = customProp.cifExport.categoryProvider(ctx[0]);
+        const cats = customProp.cifExport.categories;
         for (const cat of cats) {
             encoder.writeCategory(cat, ctx);
         }

+ 2 - 1
src/mol-model/structure/model.ts

@@ -4,10 +4,11 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import Model from './model/model'
+import { Model } from './model/model'
 import * as Types from './model/types'
 import Format from './model/format'
 import { ModelSymmetry } from './model/properties/symmetry'
 import StructureSequence from './model/properties/sequence'
 
+export * from './model/indexing'
 export { Model, Types, Format, ModelSymmetry, StructureSequence }

+ 13 - 11
src/mol-model/structure/model/formats/mmcif.ts

@@ -11,7 +11,7 @@ import { Tensor, Vec3 } from 'mol-math/linear-algebra';
 import { Task, RuntimeContext } from 'mol-task';
 import UUID from 'mol-util/uuid';
 import Format from '../format';
-import Model from '../model';
+import { Model } from '../model';
 import { Entities } from '../properties/common';
 import { CustomProperties } from '../properties/custom';
 import { ModelSymmetry } from '../properties/symmetry';
@@ -70,18 +70,20 @@ function getNcsOperators(format: mmCIF_Format) {
     }
     return opers;
 }
-
-function getModifiedResidueNameMap(format: mmCIF_Format) {
+function getModifiedResidueNameMap(format: mmCIF_Format): Model['properties']['modifiedResidues'] {
     const data = format.data.pdbx_struct_mod_residue;
-    const map = new Map<string, string>();
+    const parentId = new Map<string, string>();
+    const details = new Map<string, string>();
     const comp_id = data.label_comp_id.isDefined ? data.label_comp_id : data.auth_comp_id;
-    const parent_id = data.parent_comp_id;
+    const parent_id = data.parent_comp_id, details_data = data.details;
 
     for (let i = 0; i < data._rowCount; i++) {
-        map.set(comp_id.value(i), parent_id.value(i));
+        const id = comp_id.value(i);
+        parentId.set(id, parent_id.value(i));
+        details.set(id, details_data.value(i));
     }
 
-    return map;
+    return { parentId, details };
 }
 
 function getAsymIdSerialMap(format: mmCIF_Format) {
@@ -123,14 +125,14 @@ function getChemicalComponentMap(format: mmCIF_Format) {
 }
 
 export interface FormatData {
-    modifiedResidueNameMap: Map<string, string>
+    modifiedResidues: Model['properties']['modifiedResidues']
     asymIdSerialMap: Map<string, number>
     chemicalComponentMap: Map<string, ChemicalComponent>
 }
 
 function getFormatData(format: mmCIF_Format): FormatData {
     return {
-        modifiedResidueNameMap: getModifiedResidueNameMap(format),
+        modifiedResidues: getModifiedResidueNameMap(format),
         asymIdSerialMap: getAsymIdSerialMap(format),
         chemicalComponentMap: getChemicalComponentMap(format)
     }
@@ -154,7 +156,7 @@ function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, entities
         modelNum: atom_site.pdbx_PDB_model_num.value(0),
         entities,
         symmetry: getSymmetry(format),
-        sequence: getSequence(format.data, entities, atomic.hierarchy, formatData.modifiedResidueNameMap),
+        sequence: getSequence(format.data, entities, atomic.hierarchy, formatData.modifiedResidues.parentId),
         atomicHierarchy: atomic.hierarchy,
         atomicConformation: atomic.conformation,
         coarseHierarchy: coarse.hierarchy,
@@ -180,7 +182,7 @@ function createModelIHM(format: mmCIF_Format, data: IHMData, formatData: FormatD
         modelNum: data.model_id,
         entities: data.entities,
         symmetry: getSymmetry(format),
-        sequence: getSequence(format.data, data.entities, atomic.hierarchy, formatData.modifiedResidueNameMap),
+        sequence: getSequence(format.data, data.entities, atomic.hierarchy, formatData.modifiedResidues.parentId),
         atomicHierarchy: atomic.hierarchy,
         atomicConformation: atomic.conformation,
         coarseHierarchy: coarse.hierarchy,

+ 8 - 8
src/mol-model/structure/model/formats/mmcif/atomic.ts

@@ -8,9 +8,9 @@ import { Column, Table } from 'mol-data/db';
 import { Interval, Segmentation } from 'mol-data/int';
 import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif';
 import UUID from 'mol-util/uuid';
-import { Element } from '../../../../structure';
+import { ElementIndex } from '../../../../structure';
 import Format from '../../format';
-import Model from '../../model';
+import { Model } from '../../model';
 import { AtomicConformation, AtomicData, AtomicHierarchy, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../../properties/atomic';
 import { getAtomicKeys } from '../../properties/utils/atomic-keys';
 import { ElementSymbol } from '../../types';
@@ -26,11 +26,11 @@ function findHierarchyOffsets(atom_site: AtomSite) {
     if (atom_site._rowCount === 0) return { residues: [], chains: [] };
 
     const start = 0, end = atom_site._rowCount;
-    const residues = [start as Element], chains = [start as Element];
+    const residues = [start as ElementIndex], chains = [start as ElementIndex];
 
     const { label_entity_id, label_asym_id, label_seq_id, auth_seq_id, pdbx_PDB_ins_code, label_comp_id } = atom_site;
 
-    for (let i = start + 1; i < end; i++) {
+    for (let i = start + 1 as ElementIndex; i < end; i++) {
         const newChain = !label_entity_id.areValuesEqual(i - 1, i) || !label_asym_id.areValuesEqual(i - 1, i);
         const newResidue = newChain
             || !label_seq_id.areValuesEqual(i - 1, i)
@@ -38,8 +38,8 @@ function findHierarchyOffsets(atom_site: AtomSite) {
             || !pdbx_PDB_ins_code.areValuesEqual(i - 1, i)
             || !label_comp_id.areValuesEqual(i - 1, i);
 
-        if (newResidue) residues[residues.length] = i as Element;
-        if (newChain) chains[chains.length] = i as Element;
+        if (newResidue) residues[residues.length] = i as ElementIndex;
+        if (newChain) chains[chains.length] = i as ElementIndex;
     }
     return { residues, chains };
 }
@@ -92,8 +92,8 @@ export function getAtomicHierarchyAndConformation(format: mmCIF_Format, atom_sit
     }
 
     const hierarchySegments: AtomicSegments = {
-        residueSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, Interval.ofBounds(0, atom_site._rowCount)),
-        chainSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, Interval.ofBounds(0, atom_site._rowCount)),
+        residueAtomSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, Interval.ofBounds(0, atom_site._rowCount)),
+        chainAtomSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, Interval.ofBounds(0, atom_site._rowCount)),
     }
 
     const hierarchyKeys = getAtomicKeys(hierarchyData, entities, hierarchySegments);

+ 20 - 20
src/mol-model/structure/model/formats/mmcif/bonds/comp.ts

@@ -5,11 +5,11 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import Model from '../../../model'
+import { Model } from '../../../model'
 import { LinkType } from '../../../types'
 import { ModelPropertyDescriptor } from '../../../properties/custom';
 import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif';
-import { Structure, Unit, StructureProperties, Element } from '../../../../structure';
+import { Structure, Unit, StructureProperties, StructureElement } from '../../../../structure';
 import { Segmentation } from 'mol-data/int';
 import { CifWriter } from 'mol-io/writer/cif'
 
@@ -22,22 +22,22 @@ export namespace ComponentBond {
         isStatic: true,
         name: 'chem_comp_bond',
         cifExport: {
-            categoryNames: ['chem_comp_bond'],
-            categoryProvider(ctx) {
-                const chem_comp_bond = getChemCompBond(ctx.model);
-                if (!chem_comp_bond) return [];
-
-                const comp_names = getUniqueResidueNames(ctx.structure);
-                const { comp_id, _rowCount } = chem_comp_bond;
-                const indices: number[] = [];
-                for (let i = 0; i < _rowCount; i++) {
-                    if (comp_names.has(comp_id.value(i))) indices[indices.length] = i;
+            categories: [{
+                name: 'chem_comp_bond',
+                instance(ctx) {
+                    const chem_comp_bond = getChemCompBond(ctx.model);
+                    if (!chem_comp_bond) return CifWriter.Category.Empty;
+
+                    const comp_names = getUniqueResidueNames(ctx.structure);
+                    const { comp_id, _rowCount } = chem_comp_bond;
+                    const indices: number[] = [];
+                    for (let i = 0; i < _rowCount; i++) {
+                        if (comp_names.has(comp_id.value(i))) indices[indices.length] = i;
+                    }
+
+                    return CifWriter.Category.ofTable(chem_comp_bond, indices)
                 }
-
-                return [
-                    () => CifWriter.Category.ofTable('chem_comp_bond', chem_comp_bond, indices)
-                ];
-            }
+            }]
         }
     }
 
@@ -134,14 +134,14 @@ export namespace ComponentBond {
     function getUniqueResidueNames(s: Structure) {
         const prop = StructureProperties.residue.label_comp_id;
         const names = new Set<string>();
-        const loc = Element.Location();
+        const loc = StructureElement.create();
         for (const unit of s.units) {
             if (!Unit.isAtomic(unit)) continue;
-            const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueSegments, unit.elements);
+            const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
             loc.unit = unit;
             while (residues.hasNext) {
                 const seg = residues.move();
-                loc.element = seg.start;
+                loc.element = unit.elements[seg.start];
                 names.add(prop(loc));
             }
         }

+ 32 - 31
src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts

@@ -5,8 +5,8 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import Model from '../../../model'
-import { Element, Structure } from '../../../../structure'
+import { Model } from '../../../model'
+import { Structure } from '../../../../structure'
 import { LinkType } from '../../../types'
 import { findEntityIdByAsymId, findAtomIndexByLabelName } from '../util'
 import { Column } from 'mol-data/db'
@@ -14,6 +14,7 @@ import { ModelPropertyDescriptor } from '../../../properties/custom';
 import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif';
 import { SortedArray } from 'mol-data/int';
 import { CifWriter } from 'mol-io/writer/cif'
+import { ElementIndex } from '../../../indexing';
 
 export interface StructConn {
     getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConn.Entry>,
@@ -26,42 +27,42 @@ export namespace StructConn {
         isStatic: true,
         name: 'struct_conn',
         cifExport: {
-            categoryNames: ['struct_conn'],
-            categoryProvider(ctx) {
-                const struct_conn = getStructConn(ctx.model);
-                if (!struct_conn) return [];
+            categories: [{
+                name: 'struct_conn',
+                instance(ctx) {
+                    const struct_conn = getStructConn(ctx.model);
+                    if (!struct_conn) return CifWriter.Category.Empty;
 
-                const strConn = get(ctx.model);
-                if (!strConn || strConn.entries.length === 0) return [];
+                    const strConn = get(ctx.model);
+                    if (!strConn || strConn.entries.length === 0) return CifWriter.Category.Empty;
 
-                const foundAtoms = new Set<Element>();
-                const indices: number[] = [];
-                for (const entry of strConn.entries) {
-                    const { partners } = entry;
-                    let hasAll = true;
-                    for (let i = 0, _i = partners.length; i < _i; i++) {
-                        const atom = partners[i].atomIndex;
-                        if (foundAtoms.has(atom)) continue;
-                        if (hasAtom(ctx.structure, atom)) {
-                            foundAtoms.add(atom);
-                        } else {
-                            hasAll = false;
-                            break;
+                    const foundAtoms = new Set<ElementIndex>();
+                    const indices: number[] = [];
+                    for (const entry of strConn.entries) {
+                        const { partners } = entry;
+                        let hasAll = true;
+                        for (let i = 0, _i = partners.length; i < _i; i++) {
+                            const atom = partners[i].atomIndex;
+                            if (foundAtoms.has(atom)) continue;
+                            if (hasAtom(ctx.structure, atom)) {
+                                foundAtoms.add(atom);
+                            } else {
+                                hasAll = false;
+                                break;
+                            }
+                        }
+                        if (hasAll) {
+                            indices[indices.length] = entry.rowIndex;
                         }
                     }
-                    if (hasAll) {
-                        indices[indices.length] = entry.rowIndex;
-                    }
-                }
 
-                return [
-                    () => CifWriter.Category.ofTable('struct_conn', struct_conn, indices)
-                ];
-            }
+                    return CifWriter.Category.ofTable(struct_conn, indices);
+                }
+            }]
         }
     }
 
-    function hasAtom({ units }: Structure, element: Element) {
+    function hasAtom({ units }: Structure, element: ElementIndex) {
         for (let i = 0, _i = units.length; i < _i; i++) {
             if (SortedArray.indexOf(units[i].elements, element) >= 0) return true;
         }
@@ -132,7 +133,7 @@ export namespace StructConn {
         distance: number,
         order: number,
         flags: number,
-        partners: { residueIndex: number, atomIndex: Element, symmetry: string }[]
+        partners: { residueIndex: number, atomIndex: ElementIndex, symmetry: string }[]
     }
 
     type StructConnType =

+ 4 - 4
src/mol-model/structure/model/formats/mmcif/ihm.ts

@@ -12,7 +12,7 @@ import { getCoarseKeys } from '../../properties/utils/coarse-keys';
 import { UUID } from 'mol-util';
 import { Segmentation, Interval } from 'mol-data/int';
 import { Mat3, Tensor } from 'mol-math/linear-algebra';
-import { Element } from '../../../structure'
+import { ElementIndex, ChainIndex } from '../../indexing';
 import { getCoarseRanges } from '../../properties/utils/coarse-ranges';
 import { FormatData } from '../mmcif';
 
@@ -85,14 +85,14 @@ function getGaussianConformation(data: mmCIF['ihm_gaussian_obj_site']): CoarseGa
 }
 
 function getSegments(asym_id: Column<string>, seq_id_begin: Column<number>, seq_id_end: Column<number>) {
-    const chainOffsets = [0 as Element];
+    const chainOffsets = [0 as ElementIndex];
     for (let i = 1, _i = asym_id.rowCount; i < _i; i++) {
         const newChain = !asym_id.areValuesEqual(i - 1, i);
-        if (newChain) chainOffsets[chainOffsets.length] = i as Element;
+        if (newChain) chainOffsets[chainOffsets.length] = i as ElementIndex;
     }
 
     return {
-        chainSegments: Segmentation.ofOffsets(chainOffsets, Interval.ofBounds(0, asym_id.rowCount))
+        chainElementSegments: Segmentation.ofOffsets<ElementIndex, ChainIndex>(chainOffsets, Interval.ofBounds(0, asym_id.rowCount))
     }
 }
 

+ 13 - 12
src/mol-model/structure/model/formats/mmcif/pair-restraints/cross-links.ts

@@ -4,21 +4,22 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import Model from '../../../model'
+import { Model } from '../../../model'
 import { Table } from 'mol-data/db'
 import { mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif';
 import { findAtomIndexByLabelName } from '../util';
-import { Element, Unit } from '../../../../structure';
+import { Unit } from '../../../../structure';
+import { ElementIndex } from '../../../indexing';
 
 function findAtomIndex(model: Model, entityId: string, asymId: string, compId: string, seqId: number, atomId: string) {
     if (!model.atomicHierarchy.atoms.auth_atom_id.isDefined) return -1
     const residueIndex = model.atomicHierarchy.findResidueKey(entityId, compId, asymId, seqId, '')
     if (residueIndex < 0) return -1
-    return findAtomIndexByLabelName(model, residueIndex, atomId, '') as Element
+    return findAtomIndexByLabelName(model, residueIndex, atomId, '') as ElementIndex
 }
 
 export interface IHMCrossLinkRestraint {
-    getIndicesByElement: (element: Element, kind: Unit.Kind) => number[]
+    getIndicesByElement: (element: ElementIndex, kind: Unit.Kind) => number[]
     data: Table<mmCIF_Schema['ihm_cross_link_restraint']>
 }
 
@@ -47,7 +48,7 @@ export namespace IHMCrossLinkRestraint {
             atom_id: ihm_cross_link_restraint.atom_id_2,
         }
 
-        function _add(map: Map<Element, number[]>, element: Element, row: number) {
+        function _add(map: Map<ElementIndex, number[]>, element: ElementIndex, row: number) {
             const indices = map.get(element)
             if (indices) indices.push(row)
             else map.set(element, [ row ])
@@ -60,14 +61,14 @@ export namespace IHMCrossLinkRestraint {
 
             if (ihm_cross_link_restraint.model_granularity.value(row) === 'by-atom') {
                 const atomicElement = findAtomIndex(model, entityId, asymId, ps.comp_id.value(row), seqId, ps.atom_id.value(row))
-                if (atomicElement >= 0) _add(atomicElementMap, atomicElement as Element, row)
+                if (atomicElement >= 0) _add(atomicElementMap, atomicElement as ElementIndex, row)
             } else if (model.coarseHierarchy.isDefined) {
                 const sphereElement = model.coarseHierarchy.spheres.findSequenceKey(entityId, asymId, seqId)
                 if (sphereElement >= 0) {
-                    _add(sphereElementMap, sphereElement as Element, row)
+                    _add(sphereElementMap, sphereElement, row)
                 } else {
                     const gaussianElement = model.coarseHierarchy.gaussians.findSequenceKey(entityId, asymId, seqId)
-                    if (gaussianElement >= 0) _add(gaussianElementMap, gaussianElement as Element, row)
+                    if (gaussianElement >= 0) _add(gaussianElementMap, gaussianElement, row)
                 }
             }
         }
@@ -81,11 +82,11 @@ export namespace IHMCrossLinkRestraint {
         }
 
         /** map from atomic element to cross link indices */
-        const atomicElementMap: Map<Element, number[]> = new Map()
+        const atomicElementMap: Map<ElementIndex, number[]> = new Map()
         /** map from sphere element to cross link indices */
-        const sphereElementMap: Map<Element, number[]> = new Map()
+        const sphereElementMap: Map<ElementIndex, number[]> = new Map()
         /** map from gaussian element to cross link indices */
-        const gaussianElementMap: Map<Element, number[]> = new Map()
+        const gaussianElementMap: Map<ElementIndex, number[]> = new Map()
 
         const emptyIndexArray: number[] = [];
 
@@ -95,7 +96,7 @@ export namespace IHMCrossLinkRestraint {
         }
 
         const crossLinkRestraint = {
-            getIndicesByElement: (element: Element, kind: Unit.Kind) => {
+            getIndicesByElement: (element: ElementIndex, kind: Unit.Kind) => {
                 const map = getMapByKind(kind)
                 const idx = map.get(element)
                 return idx !== undefined ? idx : emptyIndexArray

+ 10 - 9
src/mol-model/structure/model/formats/mmcif/secondary-structure.ts

@@ -10,6 +10,7 @@ import { SecondaryStructureType } from '../../types';
 import { AtomicHierarchy } from '../../properties/atomic';
 import { SecondaryStructure } from '../../properties/seconday-structure';
 import { Column } from 'mol-data/db';
+import { ChainIndex, ResidueIndex } from '../../indexing';
 
 export function getSecondaryStructureMmCif(data: mmCIF_Database, hierarchy: AtomicHierarchy): SecondaryStructure {
     const map: SecondaryStructureMap = new Map();
@@ -37,7 +38,7 @@ type SecondaryStructureEntry = {
     key: number
 }
 type SecondaryStructureMap = Map<string, Map<number, SecondaryStructureEntry>>
-type SecondaryStructureData = { type: SecondaryStructureType[], key: number[], elements: SecondaryStructure.Element[]  }
+type SecondaryStructureData = { type: SecondaryStructureType[], key: number[], elements: SecondaryStructure.Element[] }
 
 function addHelices(cat: mmCIF['struct_conf'], map: SecondaryStructureMap, elements: SecondaryStructure.Element[]) {
     if (!cat._rowCount) return;
@@ -47,11 +48,11 @@ function addHelices(cat: mmCIF['struct_conf'], map: SecondaryStructureMap, eleme
     const { pdbx_PDB_helix_class, conf_type_id, details } = cat;
 
     for (let i = 0, _i = cat._rowCount; i < _i; i++) {
-        const type =  SecondaryStructureType.create(pdbx_PDB_helix_class.valueKind(i) === Column.ValueKind.Present
+        const type = SecondaryStructureType.create(pdbx_PDB_helix_class.valueKind(i) === Column.ValueKind.Present
             ? SecondaryStructureType.SecondaryStructurePdb[pdbx_PDB_helix_class.value(i)]
             : conf_type_id.valueKind(i) === Column.ValueKind.Present
-            ? SecondaryStructureType.SecondaryStructureMmcif[conf_type_id.value(i)]
-            : SecondaryStructureType.Flag.NA);
+                ? SecondaryStructureType.SecondaryStructureMmcif[conf_type_id.value(i)]
+                : SecondaryStructureType.Flag.NA);
 
         const element: SecondaryStructure.Helix = {
             kind: 'helix',
@@ -69,6 +70,7 @@ function addHelices(cat: mmCIF['struct_conf'], map: SecondaryStructureMap, eleme
             key: elements.length
         };
 
+
         elements[elements.length] = element;
 
         const asymId = beg_label_asym_id.value(i)!;
@@ -129,7 +131,7 @@ function addSheets(cat: mmCIF['struct_sheet_range'], map: SecondaryStructureMap,
     return;
 }
 
-function assignSecondaryStructureEntry(hierarchy: AtomicHierarchy, entry: SecondaryStructureEntry, resStart: number, resEnd: number, data: SecondaryStructureData) {
+function assignSecondaryStructureEntry(hierarchy: AtomicHierarchy, entry: SecondaryStructureEntry, resStart: ResidueIndex, resEnd: ResidueIndex, data: SecondaryStructureData) {
     const { label_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
     const { endSeqNumber, endInsCode, key, type } = entry;
 
@@ -149,14 +151,13 @@ function assignSecondaryStructureEntry(hierarchy: AtomicHierarchy, entry: Second
 }
 
 function assignSecondaryStructureRanges(hierarchy: AtomicHierarchy, map: SecondaryStructureMap, data: SecondaryStructureData) {
-    const { segments: chainSegments, count: chainCount } = hierarchy.chainSegments;
+    const { count: chainCount } = hierarchy.chainAtomSegments;
     const { label_asym_id } = hierarchy.chains;
     const { label_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
 
-    for (let cI = 0; cI < chainCount; cI++) {
-        const resStart = chainSegments[cI], resEnd = chainSegments[cI + 1];
+    for (let cI = 0 as ChainIndex; cI < chainCount; cI++) {
+        const resStart = AtomicHierarchy.chainStartResidueIndex(hierarchy, cI), resEnd = AtomicHierarchy.chainEndResidueIndexExcl(hierarchy, cI);
         const asymId = label_asym_id.value(cI);
-
         if (map.has(asymId)) {
             const entries = map.get(asymId)!;
 

+ 7 - 7
src/mol-model/structure/model/formats/mmcif/util.ts

@@ -4,8 +4,8 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import Model from '../../model'
-import { Element } from '../../../structure'
+import { Model } from '../../model'
+import { ElementIndex } from '../../indexing';
 
 export function findEntityIdByAsymId(model: Model, asymId: string) {
     if (model.sourceData.kind !== 'mmCIF') return ''
@@ -16,11 +16,11 @@ export function findEntityIdByAsymId(model: Model, asymId: string) {
     return ''
 }
 
-export function findAtomIndexByLabelName(model: Model, residueIndex: number, atomName: string, altLoc: string | null): Element {
-    const { segments } = model.atomicHierarchy.residueSegments;
+export function findAtomIndexByLabelName(model: Model, residueIndex: number, atomName: string, altLoc: string | null): ElementIndex {
+    const { offsets } = model.atomicHierarchy.residueAtomSegments;
     const { label_atom_id, label_alt_id } = model.atomicHierarchy.atoms;
-    for (let i = segments[residueIndex], n = segments[residueIndex + 1]; i < n; ++i) {
-        if (label_atom_id.value(i) === atomName && (!altLoc || label_alt_id.value(i) === altLoc)) return i as Element;
+    for (let i = offsets[residueIndex], n = offsets[residueIndex + 1]; i < n; ++i) {
+        if (label_atom_id.value(i) === atomName && (!altLoc || label_alt_id.value(i) === altLoc)) return i as ElementIndex;
     }
-    return -1 as Element;
+    return -1 as ElementIndex;
 }

+ 10 - 0
src/mol-model/structure/model/indexing.ts

@@ -0,0 +1,10 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+export type ElementIndex = { readonly '@type': 'element-index' } & number
+export type ResidueIndex = { readonly '@type': 'residue-index' } & number
+export type ChainIndex = { readonly '@type': 'chain-index' } & number
+export type EntityIndex = { readonly '@type': 'entity-index' } & number

+ 7 - 18
src/mol-model/structure/model/model.ts

@@ -22,7 +22,7 @@ import { ChemicalComponent } from './properties/chemical-component';
  *
  * "Atoms" are integers in the range [0, atomCount).
  */
-interface Model extends Readonly<{
+export interface Model extends Readonly<{
     id: UUID,
     label: string,
 
@@ -42,7 +42,10 @@ interface Model extends Readonly<{
         /** secondary structure provided by the input file */
         readonly secondaryStructure: SecondaryStructure,
         /** maps modified residue name to its parent */
-        readonly modifiedResidueNameMap: Map<string, string>,
+        readonly modifiedResidues: {
+            parentId: Map<string, string>,
+            details: Map<string, string>
+        },
         /** maps asym id to unique serial number */
         readonly asymIdSerialMap: Map<string, number>
         /** maps residue name to `ChemicalComponent` data */
@@ -64,25 +67,11 @@ interface Model extends Readonly<{
 
 } { }
 
-namespace Model {
+export namespace Model {
     export function create(format: Format) {
         switch (format.kind) {
             // case 'gro': return from_gro(format);
             case 'mmCIF': return from_mmCIF(format);
         }
     }
-
-    // TODO: figure the place to include this?
-    // export interface Property<T, K> { (model: Model, index: number): T, _kind: K }
-    // export interface AtomicProperty<T> extends Property<T, 'atomic'> { }
-    // export interface CoarseProperty<T> extends Property<T, 'coarse'> { }
-    // export interface SphereProperty<T> extends Property<T, 'sphere'> { }
-    // export interface GaussianProperty<T> extends Property<T, 'gaussian'> { }
-
-    // export function atomProp<T>(p: (model: Model, i: number) => T): AtomicProperty<T> { return p as any; }
-    // export function residueProp<T>(p: (model: Model, residueIndex: number) => T): AtomicProperty<T> {
-    //     return p as any;
-    // }
-}
-
-export default Model
+}

+ 36 - 18
src/mol-model/structure/model/properties/atomic/hierarchy.ts

@@ -8,7 +8,7 @@ import { Column, Table } from 'mol-data/db'
 import { Segmentation } from 'mol-data/int'
 import { mmCIF_Schema as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
 import { ElementSymbol } from '../../types'
-import { Element } from '../../../structure'
+import { ChainIndex, EntityIndex, ResidueIndex, ElementIndex } from '../../indexing';
 import SortedRanges from 'mol-data/int/sorted-ranges';
 
 export const AtomsSchema = {
@@ -50,34 +50,52 @@ export interface AtomicData {
 
 export interface AtomicSegments {
     /** Maps residueIndex to a range of atoms [segments[rI], segments[rI + 1]) */
-    residueSegments: Segmentation<Element>,
-    /** Maps chainIndex to a range of atoms [segments[cI], segments[cI + 1]) */
-    chainSegments: Segmentation<Element>,
+    residueAtomSegments: Segmentation<ElementIndex, ResidueIndex>,
+    /**
+     * Maps chainIndex to a range of atoms [segments[cI], segments[cI + 1]),
+     *
+     * residues of i-th chain are accessed like this:
+     * const rI = residueAtomSegments.index, offsets = chainAtomSegments.offsets;
+     * const start = rI[offsets[i]], const end = rI[offsets[i + 1] - 1] + 1;
+     * for (let j = start; j < end; i++) { }
+     */
+    chainAtomSegments: Segmentation<ElementIndex, ChainIndex>,
+
     // TODO: include entity segments?
 }
 
 export interface AtomicKeys {
-    // TODO: since Atoms must be sorted now, get rid of keys
     // TODO: include (lazily computed) "entity/chain/residue" indices?
 
-    // assign a key to each residue index.
-    residueKey: ArrayLike<number>,
-    // assign a key to each chain index
-    chainKey: ArrayLike<number>,
-    // assigne a key to each chain index
-    // also index to the Entities table.
-    entityKey: ArrayLike<number>,
+    /** @returns index or -1 if not present. */
+    getEntityKey(cI: ChainIndex): EntityIndex,
 
-    findChainKey(entityId: string, label_asym_id: string): number,
+    /** @returns index or -1 if not present. */
+    findChainKey(entityId: string, label_asym_id: string): ChainIndex,
 
-    /** Unique number for each of the residue. Also the index of the 1st occurence of this residue. */
-    findResidueKey(entityId: string, label_asym_id: string, label_comp_id: string, auth_seq_id: number, pdbx_PDB_ins_code: string): number
+    /**
+     * Unique number for each of the residue. Also the index of the 1st occurence of this residue.
+     * @returns index or -1 if not present.
+     */
+    findResidueKey(entityId: string, label_asym_id: string, label_comp_id: string, auth_seq_id: number, pdbx_PDB_ins_code: string): ResidueIndex
 }
 
 export interface AtomicRanges {
-    polymerRanges: SortedRanges<Element>
-    gapRanges: SortedRanges<Element>
+    polymerRanges: SortedRanges<ElementIndex>
+    gapRanges: SortedRanges<ElementIndex>
 }
 
 type _Hierarchy = AtomicData & AtomicSegments & AtomicKeys & AtomicRanges
-export interface AtomicHierarchy extends _Hierarchy { }
+export interface AtomicHierarchy extends _Hierarchy { }
+
+export namespace AtomicHierarchy {
+    /** Start residue inclusive */
+    export function chainStartResidueIndex(segs: AtomicSegments, cI: ChainIndex) {
+        return segs.residueAtomSegments.index[segs.chainAtomSegments.offsets[cI]];
+    }
+
+    /** End residue exclusive */
+    export function chainEndResidueIndexExcl(segs: AtomicSegments, cI: ChainIndex) {
+        return segs.residueAtomSegments.index[segs.chainAtomSegments.offsets[cI + 1] - 1] + 1 as ResidueIndex;
+    }
+}

+ 6 - 6
src/mol-model/structure/model/properties/coarse/hierarchy.ts

@@ -7,7 +7,7 @@
 
 import { Column } from 'mol-data/db'
 import { Segmentation } from 'mol-data/int';
-import { Element } from '../../../structure'
+import { ElementIndex, ChainIndex } from '../../indexing';
 import SortedRanges from 'mol-data/int/sorted-ranges';
 
 export interface CoarsedElementKeys {
@@ -17,8 +17,8 @@ export interface CoarsedElementKeys {
     entityKey: ArrayLike<number>,
 
     /** find index of the residue/feature element where seq_id is included */
-    findSequenceKey(entityId: string, asym_id: string, seq_id: number): number
-    findChainKey(entityId: string, asym_id: string): number
+    findSequenceKey(entityId: string, asym_id: string, seq_id: number): ElementIndex
+    findChainKey(entityId: string, asym_id: string): ChainIndex
 }
 
 export interface CoarseElementData {
@@ -28,12 +28,12 @@ export interface CoarseElementData {
     seq_id_begin: Column<number>,
     seq_id_end: Column<number>,
 
-    chainSegments: Segmentation<Element>,
+    chainElementSegments: Segmentation<ElementIndex, ChainIndex>,
 }
 
 export interface CoarseRanges {
-    polymerRanges: SortedRanges<Element>
-    gapRanges: SortedRanges<Element>
+    polymerRanges: SortedRanges<ElementIndex>
+    gapRanges: SortedRanges<ElementIndex>
 }
 
 export type CoarseElements = CoarsedElementKeys & CoarseElementData & CoarseRanges

+ 2 - 1
src/mol-model/structure/model/properties/common.ts

@@ -5,8 +5,9 @@
  */
 
 import { mmCIF_Database as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
+import { EntityIndex } from '../indexing';
 
 export interface Entities {
     data: mmCIF['entity'],
-    getEntityIndex(id: string): number
+    getEntityIndex(id: string): EntityIndex
 }

+ 1 - 3
src/mol-model/structure/model/properties/custom/descriptor.ts

@@ -12,9 +12,7 @@ interface ModelPropertyDescriptor {
     readonly name: string,
 
     cifExport: {
-        /** used category names that can be used for "filtering" by the writer */
-        readonly categoryNames: ReadonlyArray<string>,
-        categoryProvider: (ctx: CifExportContext) => CifWriter.Category.Provider[]
+        categories: CifWriter.Category<CifExportContext>[]
     }
 }
 

+ 7 - 6
src/mol-model/structure/model/properties/sequence.ts

@@ -8,6 +8,7 @@ import { Column } from 'mol-data/db'
 import { AtomicHierarchy } from './atomic/hierarchy';
 import { Entities } from './common';
 import { Sequence } from '../../../sequence';
+import { ChainIndex } from '../indexing';
 
 interface StructureSequence {
     readonly sequences: ReadonlyArray<StructureSequence.Entity>,
@@ -25,25 +26,25 @@ namespace StructureSequence {
 
     export function fromAtomicHierarchy(entities: Entities, hierarchy: AtomicHierarchy, modResMap?: Map<string, string>): StructureSequence {
         const { label_comp_id, label_seq_id } = hierarchy.residues
-        const { chainSegments, residueSegments } = hierarchy
+        const { chainAtomSegments, residueAtomSegments } = hierarchy
 
         const byEntityKey: StructureSequence['byEntityKey'] = { };
         const sequences: StructureSequence.Entity[] = [];
 
-        for (let cI = 0, _cI = hierarchy.chains._rowCount; cI < _cI; cI++) {
-            const entityKey = hierarchy.entityKey[cI];
+        for (let cI = 0 as ChainIndex, _cI = hierarchy.chains._rowCount; cI < _cI; cI++) {
+            const entityKey = hierarchy.getEntityKey(cI);
             // Only for polymers, trying to mirror _entity_poly_seq
             if (byEntityKey[entityKey] !== void 0 || entities.data.type.value(entityKey) !== 'polymer') continue;
 
             let start = cI;
             cI++;
-            while (cI < _cI && entityKey === hierarchy.entityKey[cI] && entities.data.type.value(entityKey) !== 'polymer') {
+            while (cI < _cI && entityKey === hierarchy.getEntityKey(cI) && entities.data.type.value(entityKey) !== 'polymer') {
                 cI++;
             }
             cI--;
 
-            const rStart = residueSegments.segmentMap[chainSegments.segments[start]];
-            const rEnd = residueSegments.segmentMap[chainSegments.segments[cI + 1]];
+            const rStart = residueAtomSegments.index[chainAtomSegments.offsets[start]];
+            const rEnd = residueAtomSegments.index[chainAtomSegments.offsets[cI + 1]];
 
             const compId = Column.window(label_comp_id, rStart, rEnd);
             const num = Column.window(label_seq_id, rStart, rEnd);

+ 11 - 10
src/mol-model/structure/model/properties/utils/atomic-keys.ts

@@ -7,6 +7,7 @@
 import { AtomicData, AtomicSegments, AtomicKeys } from '../atomic'
 import { Interval, Segmentation } from 'mol-data/int'
 import { Entities } from '../common'
+import { ChainIndex, ResidueIndex, EntityIndex } from '../../indexing';
 
 function getResidueId(comp_id: string, seq_id: number, ins_code: string) {
     return `${comp_id} ${seq_id} ${ins_code}`;
@@ -30,20 +31,20 @@ function createLookUp(entities: Entities, chain: Map<number, Map<string, number>
     const getEntKey = entities.getEntityIndex;
     const findChainKey: AtomicKeys['findChainKey'] = (e, c) => {
         let eKey = getEntKey(e);
-        if (eKey < 0) return -1;
+        if (eKey < 0) return -1 as ChainIndex;
         const cm = chain.get(eKey)!;
-        if (!cm.has(c)) return -1;
-        return cm.get(c)!;
+        if (!cm.has(c)) return -1 as ChainIndex;
+        return cm.get(c)! as ChainIndex;
     }
     const findResidueKey: AtomicKeys['findResidueKey'] = (e, c, name, seq, ins) => {
         let eKey = getEntKey(e);
-        if (eKey < 0) return -1;
+        if (eKey < 0) return -1 as ResidueIndex;
         const cm = chain.get(eKey)!;
-        if (!cm.has(c)) return -1;
+        if (!cm.has(c)) return -1 as ResidueIndex;
         const rm = residue.get(cm.get(c)!)!
         const id = getResidueId(name, seq, ins);
-        if (!rm.has(id)) return -1;
-        return rm.get(id)!;
+        if (!rm.has(id)) return -1 as ResidueIndex;
+        return rm.get(id)! as ResidueIndex;
     }
     return { findChainKey, findResidueKey };
 }
@@ -67,7 +68,7 @@ export function getAtomicKeys(data: AtomicData, entities: Entities, segments: At
 
     const atomSet = Interval.ofBounds(0, data.atoms._rowCount);
 
-    const chainsIt = Segmentation.transientSegments(segments.chainSegments, atomSet);
+    const chainsIt = Segmentation.transientSegments(segments.chainAtomSegments, atomSet);
     while (chainsIt.hasNext) {
         const chainSegment = chainsIt.move();
         const cI = chainSegment.index;
@@ -81,7 +82,7 @@ export function getAtomicKeys(data: AtomicData, entities: Entities, segments: At
         entityKey[cI] = eKey;
 
         const residueMap = getElementSubstructureKeyMap(residueMaps, cKey);
-        const residuesIt = Segmentation.transientSegments(segments.residueSegments, atomSet, chainSegment);
+        const residuesIt = Segmentation.transientSegments(segments.residueAtomSegments, atomSet, chainSegment);
         while (residuesIt.hasNext) {
             const residueSegment = residuesIt.move();
             const rI = residueSegment.index;
@@ -92,5 +93,5 @@ export function getAtomicKeys(data: AtomicData, entities: Entities, segments: At
 
     const { findChainKey, findResidueKey } = createLookUp(entities, chainMaps, residueMaps);
 
-    return { residueKey, chainKey, entityKey, findChainKey, findResidueKey };
+    return { getEntityKey: cI => entityKey[cI] as EntityIndex, findChainKey, findResidueKey };
 }

+ 5 - 5
src/mol-model/structure/model/properties/utils/atomic-ranges.ts

@@ -8,15 +8,15 @@ import { AtomicSegments } from '../atomic';
 import { AtomicData, AtomicRanges } from '../atomic/hierarchy';
 import { Segmentation, Interval } from 'mol-data/int';
 import SortedRanges from 'mol-data/int/sorted-ranges';
-import { Element } from '../../../../structure';
 import { ChemicalComponent } from '../chemical-component';
 import { MoleculeType, isPolymer } from '../../types';
+import { ElementIndex } from '../../indexing';
 
 export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, chemicalComponentMap: Map<string, ChemicalComponent>): AtomicRanges {
     const polymerRanges: number[] = []
     const gapRanges: number[] = []
-    const chainIt = Segmentation.transientSegments(segments.chainSegments, Interval.ofBounds(0, data.atoms._rowCount))
-    const residueIt = Segmentation.transientSegments(segments.residueSegments, Interval.ofBounds(0, data.atoms._rowCount))
+    const chainIt = Segmentation.transientSegments(segments.chainAtomSegments, Interval.ofBounds(0, data.atoms._rowCount))
+    const residueIt = Segmentation.transientSegments(segments.residueAtomSegments, Interval.ofBounds(0, data.atoms._rowCount))
     const { label_seq_id, label_comp_id } = data.residues
 
     let prevSeqId: number
@@ -63,7 +63,7 @@ export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, chem
     console.log(polymerRanges, gapRanges)
 
     return {
-        polymerRanges: SortedRanges.ofSortedRanges(polymerRanges as Element[]),
-        gapRanges: SortedRanges.ofSortedRanges(gapRanges as Element[])
+        polymerRanges: SortedRanges.ofSortedRanges(polymerRanges as ElementIndex[]),
+        gapRanges: SortedRanges.ofSortedRanges(gapRanges as ElementIndex[])
     }
 }

+ 12 - 11
src/mol-model/structure/model/properties/utils/coarse-keys.ts

@@ -7,6 +7,7 @@
 
 import { Entities } from '../common';
 import { CoarseElementData, CoarsedElementKeys } from '../coarse';
+import { ChainIndex, ElementIndex } from '../../indexing';
 
 function getElementKey(map: Map<string, number>, key: string, counter: { index: number }) {
     if (map.has(key)) return map.get(key)!;
@@ -26,22 +27,22 @@ function createLookUp(entities: Entities, chain: Map<number, Map<string, number>
     const getEntKey = entities.getEntityIndex;
     const findChainKey: CoarsedElementKeys['findChainKey'] = (e, c) => {
         const eKey = getEntKey(e);
-        if (eKey < 0) return -1;
+        if (eKey < 0) return -1 as ChainIndex;
         const cm = chain.get(eKey)!;
-        if (!cm.has(c)) return -1;
-        return cm.get(c)!;
+        if (!cm.has(c)) return -1 as ChainIndex;
+        return cm.get(c)! as ChainIndex;
     }
     // TODO consider implementing as binary search
     const findSequenceKey: CoarsedElementKeys['findSequenceKey'] = (e, c, s) => {
         const eKey = getEntKey(e);
-        if (eKey < 0) return -1;
+        if (eKey < 0) return -1 as ElementIndex;
         const cm = chain.get(eKey);
-        if (cm === undefined) return -1
+        if (cm === undefined) return -1 as ElementIndex
         const cKey = cm.get(c)
-        if (cKey === undefined) return -1
+        if (cKey === undefined) return -1 as ElementIndex
         const sm = seq.get(cKey)!
-        if (!sm.has(s)) return -1;
-        return sm.get(s)!
+        if (!sm.has(s)) return -1 as ElementIndex;
+        return sm.get(s)! as ElementIndex
     }
     return { findChainKey, findSequenceKey };
 }
@@ -51,7 +52,7 @@ function missingEntity(k: string) {
 }
 
 export function getCoarseKeys(data: CoarseElementData, entities: Entities): CoarsedElementKeys {
-    const { entity_id, asym_id, seq_id_begin, seq_id_end, count, chainSegments } = data;
+    const { entity_id, asym_id, seq_id_begin, seq_id_end, count, chainElementSegments } = data;
 
     const seqMaps = new Map<number, Map<number, number>>();
     const chainMaps = new Map<number, Map<string, number>>(), chainCounter = { index: 0 };
@@ -64,8 +65,8 @@ export function getCoarseKeys(data: CoarseElementData, entities: Entities): Coar
         if (entityKey[i] < 0) missingEntity(entity_id.value(i));
     }
 
-    for (let cI = 0; cI < chainSegments.count; cI++) {
-        const start = chainSegments.segments[cI], end = chainSegments.segments[cI + 1];
+    for (let cI = 0; cI < chainElementSegments.count; cI++) {
+        const start = chainElementSegments.offsets[cI], end = chainElementSegments.offsets[cI + 1];
         const map = getElementSubstructureKeyMap(chainMaps, entityKey[start]);
         const key = getElementKey(map, asym_id.value(start), chainCounter);
         for (let i = start; i < end; i++) chainKey[i] = key;

+ 4 - 4
src/mol-model/structure/model/properties/utils/coarse-ranges.ts

@@ -7,15 +7,15 @@
 import { CoarseRanges, CoarseElementData } from '../coarse/hierarchy';
 import { Segmentation, Interval } from 'mol-data/int';
 import SortedRanges from 'mol-data/int/sorted-ranges';
-import { Element } from '../../../../structure';
 import { ChemicalComponent } from '../chemical-component';
+import { ElementIndex } from '../../indexing';
 
 // TODO assumes all coarse elements are part of a polymer
 
 export function getCoarseRanges(data: CoarseElementData, chemicalComponentMap: Map<string, ChemicalComponent>): CoarseRanges {
     const polymerRanges: number[] = []
     const gapRanges: number[] = []
-    const chainIt = Segmentation.transientSegments(data.chainSegments, Interval.ofBounds(0, data.count))
+    const chainIt = Segmentation.transientSegments(data.chainElementSegments, Interval.ofBounds(0, data.count))
 
     const { seq_id_begin, seq_id_end } = data
 
@@ -46,7 +46,7 @@ export function getCoarseRanges(data: CoarseElementData, chemicalComponentMap: M
     console.log(polymerRanges, gapRanges)
 
     return {
-        polymerRanges: SortedRanges.ofSortedRanges(polymerRanges as Element[]),
-        gapRanges: SortedRanges.ofSortedRanges(gapRanges as Element[])
+        polymerRanges: SortedRanges.ofSortedRanges(polymerRanges as ElementIndex[]),
+        gapRanges: SortedRanges.ofSortedRanges(gapRanges as ElementIndex[])
     }
 }

+ 22 - 22
src/mol-model/structure/query/generators.ts

@@ -6,22 +6,22 @@
 
 import Query from './query'
 import Selection from './selection'
-import { Element, Unit, StructureProperties as P } from '../structure'
-import { OrderedSet, Segmentation } from 'mol-data/int'
+import { StructureElement, Unit, StructureProperties as P } from '../structure'
+import { Segmentation } from 'mol-data/int'
 import { LinearGroupingBuilder } from './utils/builders';
 
 export const all: Query.Provider = async (s, ctx) => Selection.Singletons(s, s);
 
 export interface AtomQueryParams {
-    entityTest: Element.Predicate,
-    chainTest: Element.Predicate,
-    residueTest: Element.Predicate,
-    atomTest: Element.Predicate,
-    groupBy: Element.Property<any>
+    entityTest: StructureElement.Predicate,
+    chainTest: StructureElement.Predicate,
+    residueTest: StructureElement.Predicate,
+    atomTest: StructureElement.Predicate,
+    groupBy: StructureElement.Property<any>
 }
 
 export interface AtomGroupsQueryParams extends AtomQueryParams {
-    groupBy: Element.Property<any>
+    groupBy: StructureElement.Property<any>
 }
 
 export function residues(params?: Partial<AtomQueryParams>) { return atoms({ ...params, groupBy: P.residue.key }); }
@@ -43,10 +43,10 @@ export function atoms(params?: Partial<AtomGroupsQueryParams>): Query.Provider {
     return atomGroupsGrouped(normalized);
 }
 
-function atomGroupsLinear(atomTest: Element.Predicate): Query.Provider {
+function atomGroupsLinear(atomTest: StructureElement.Predicate): Query.Provider {
     return async (structure, ctx) => {
         const { units } = structure;
-        const l = Element.Location();
+        const l = StructureElement.create();
         const builder = structure.subsetBuilder(true);
 
         let progress = 0;
@@ -72,7 +72,7 @@ function atomGroupsLinear(atomTest: Element.Predicate): Query.Provider {
 function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: AtomGroupsQueryParams): Query.Provider {
     return async (structure, ctx) => {
         const { units } = structure;
-        const l = Element.Location();
+        const l = StructureElement.create();
         const builder = structure.subsetBuilder(true);
 
         let progress = 0;
@@ -83,24 +83,24 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A
             const elements = unit.elements;
 
             builder.beginUnit(unit.id);
-            const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainSegments, elements);
-            const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueSegments, elements);
+            const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
+            const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
             while (chainsIt.hasNext) {
                 const chainSegment = chainsIt.move();
-                l.element = OrderedSet.getAt(elements, chainSegment.start);
+                l.element = elements[chainSegment.start];
                 // test entity and chain
                 if (!entityTest(l) || !chainTest(l)) continue;
 
                 residuesIt.setSegment(chainSegment);
                 while (residuesIt.hasNext) {
                     const residueSegment = residuesIt.move();
-                    l.element = OrderedSet.getAt(elements, residueSegment.start);
+                    l.element = elements[residueSegment.start];
 
                     // test residue
                     if (!residueTest(l)) continue;
 
                     for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
-                        l.element = OrderedSet.getAt(elements, j);
+                        l.element = elements[j];
                         if (atomTest(l)) {
                             builder.addElement(l.element);
                         }
@@ -120,7 +120,7 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A
 function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, groupBy }: AtomGroupsQueryParams): Query.Provider {
     return async (structure, ctx) => {
         const { units } = structure;
-        const l = Element.Location();
+        const l = StructureElement.create();
         const builder = new LinearGroupingBuilder(structure);
 
         let progress = 0;
@@ -130,24 +130,24 @@ function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, group
             l.unit = unit;
             const elements = unit.elements;
 
-            const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainSegments, elements);
-            const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueSegments, elements);
+            const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
+            const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
             while (chainsIt.hasNext) {
                 const chainSegment = chainsIt.move();
-                l.element = OrderedSet.getAt(elements, chainSegment.start);
+                l.element = elements[chainSegment.start];
                 // test entity and chain
                 if (!entityTest(l) || !chainTest(l)) continue;
 
                 residuesIt.setSegment(chainSegment);
                 while (residuesIt.hasNext) {
                     const residueSegment = residuesIt.move();
-                    l.element = OrderedSet.getAt(elements, residueSegment.start);
+                    l.element = elements[residueSegment.start];
 
                     // test residue
                     if (!residueTest(l)) continue;
 
                     for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
-                        l.element = OrderedSet.getAt(elements, j);
+                        l.element = elements[j];
                         if (atomTest(l)) builder.add(groupBy(l), unit.id, l.element);
                     }
                 }

+ 3 - 3
src/mol-model/structure/query/modifiers.ts

@@ -21,14 +21,14 @@ function getWholeResidues(ctx: RuntimeContext, source: Structure, structure: Str
             continue;
         }
 
-        const { residueSegments } = unit.model.atomicHierarchy;
+        const { residueAtomSegments } = unit.model.atomicHierarchy;
 
         const elements = unit.elements;
         builder.beginUnit(unit.id);
-        const residuesIt = Segmentation.transientSegments(residueSegments, elements);
+        const residuesIt = Segmentation.transientSegments(residueAtomSegments, elements);
         while (residuesIt.hasNext) {
             const rI = residuesIt.move().index;
-            for (let j = residueSegments.segments[rI], _j = residueSegments.segments[rI + 1]; j < _j; j++) {
+            for (let j = residueAtomSegments.offsets[rI], _j = residueAtomSegments.offsets[rI + 1]; j < _j; j++) {
                 builder.addElement(j);
             }
         }

+ 9 - 9
src/mol-model/structure/query/predicates.ts

@@ -4,19 +4,19 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Element, StructureProperties as P } from '../structure'
+import { StructureElement, StructureProperties as P } from '../structure'
 
 namespace Predicates {
     export interface SetLike<A> { has(v: A): boolean }
     function isSetLike<A>(x: any): x is SetLike<A> { return !!x && !!x.has }
 
-    export function eq<A>(p: Element.Property<A>, value: A): Element.Predicate { return l => p(l) === value; }
-    export function lt<A>(p: Element.Property<A>, value: A): Element.Predicate { return l => p(l) < value; }
-    export function lte<A>(p: Element.Property<A>, value: A): Element.Predicate { return l => p(l) <= value; }
-    export function gt<A>(p: Element.Property<A>, value: A): Element.Predicate { return l => p(l) > value; }
-    export function gte<A>(p: Element.Property<A>, value: A): Element.Predicate { return l => p(l) >= value; }
+    export function eq<A>(p: StructureElement.Property<A>, value: A): StructureElement.Predicate { return l => p(l) === value; }
+    export function lt<A>(p: StructureElement.Property<A>, value: A): StructureElement.Predicate { return l => p(l) < value; }
+    export function lte<A>(p: StructureElement.Property<A>, value: A): StructureElement.Predicate { return l => p(l) <= value; }
+    export function gt<A>(p: StructureElement.Property<A>, value: A): StructureElement.Predicate { return l => p(l) > value; }
+    export function gte<A>(p: StructureElement.Property<A>, value: A): StructureElement.Predicate { return l => p(l) >= value; }
 
-    export function inSet<A>(p: Element.Property<A>, values: SetLike<A> | ArrayLike<A>): Element.Predicate {
+    export function inSet<A>(p: StructureElement.Property<A>, values: SetLike<A> | ArrayLike<A>): StructureElement.Predicate {
         if (isSetLike(values)) {
             return l => values.has(p(l));
         } else {
@@ -27,7 +27,7 @@ namespace Predicates {
         }
     }
 
-    export function and(...ps: Element.Predicate[]): Element.Predicate {
+    export function and(...ps: StructureElement.Predicate[]): StructureElement.Predicate {
         switch (ps.length) {
             case 0: return P.constant.true;
             case 1: return ps[0];
@@ -61,7 +61,7 @@ namespace Predicates {
         }
     }
 
-    export function or(...ps: Element.Predicate[]): Element.Predicate {
+    export function or(...ps: StructureElement.Predicate[]): StructureElement.Predicate {
         switch (ps.length) {
             case 0: return P.constant.false;
             case 1: return ps[0];

+ 5 - 5
src/mol-model/structure/query/selection.ts

@@ -5,7 +5,7 @@
  */
 
 import { HashSet } from 'mol-data/generic'
-import { Structure, Element, Unit } from '../structure'
+import { Structure, StructureElement, Unit } from '../structure'
 import { structureUnion } from './utils/structure';
 import { OrderedSet, SortedArray } from 'mol-data/int';
 
@@ -35,13 +35,13 @@ namespace Selection {
         return structureUnion(sel.source, sel.structures);
     }
 
-    export function toLoci(sel: Selection): Element.Loci {
-        const loci: { unit: Unit, indices: OrderedSet<Element.Index> }[] = [];
+    export function toLoci(sel: Selection): StructureElement.Loci {
+        const loci: { unit: Unit, indices: OrderedSet<StructureElement.UnitIndex> }[] = [];
         const { unitMap } = sel.source;
 
         for (const unit of unionStructure(sel).units) {
             if (unit === unitMap.get(unit.id)) {
-                loci[loci.length] = { unit, indices: OrderedSet.ofBounds(0 as Element.Index, unit.elements.length as Element.Index) };
+                loci[loci.length] = { unit, indices: OrderedSet.ofBounds(0 as StructureElement.UnitIndex, unit.elements.length as StructureElement.UnitIndex) };
             } else {
                 loci[loci.length] = {
                     unit,
@@ -50,7 +50,7 @@ namespace Selection {
             }
         }
 
-        return Element.Loci(loci);
+        return StructureElement.Loci(loci);
     }
 
     export interface Builder {

+ 4 - 3
src/mol-model/structure/query/utils/builders.ts

@@ -4,11 +4,12 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Element, Structure } from '../../structure';
+import { StructureElement, Structure } from '../../structure';
 import Selection from '../selection';
 import { HashSet } from 'mol-data/generic';
 import { structureUnion } from './structure';
 import { StructureSubsetBuilder } from '../../structure/util/subset-builder';
+import { ElementIndex } from '../../model';
 
 export class UniqueStructuresBuilder {
     private set = HashSet(Structure.hashCode, Structure.areEqual);
@@ -36,7 +37,7 @@ export class LinearGroupingBuilder {
     private builders: StructureSubsetBuilder[] = [];
     private builderMap = new Map<string, StructureSubsetBuilder>();
 
-    add(key: any, unit: number, element: Element) {
+    add(key: any, unit: number, element: ElementIndex) {
         let b = this.builderMap.get(key);
         if (!b) {
             b = this.source.subsetBuilder(true);
@@ -55,7 +56,7 @@ export class LinearGroupingBuilder {
 
     private singletonSelection(): Selection {
         const builder = this.source.subsetBuilder(true);
-        const loc = Element.Location();
+        const loc = StructureElement.create();
         for (let i = 0, _i = this.builders.length; i < _i; i++) {
             this.builders[i].setSingletonLocation(loc);
             builder.addToUnit(loc.unit.id, loc.element);

+ 3 - 3
src/mol-model/structure/query/utils/structure.ts

@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Structure, Unit, Element } from '../../structure'
+import { Structure, Unit, StructureElement } from '../../structure'
 import { SortedArray } from 'mol-data/int';
 import { StructureSubsetBuilder } from '../../structure/util/subset-builder';
 
@@ -12,7 +12,7 @@ export function structureUnion(source: Structure, structures: Structure[]) {
     if (structures.length === 0) return Structure.Empty;
     if (structures.length === 1) return structures[0];
 
-    const unitMap = new Map<number, Element.Set>();
+    const unitMap = new Map<number, StructureElement.Set>();
     const fullUnits = new Set<number>();
 
     for (const { units } of structures) {
@@ -36,7 +36,7 @@ export function structureUnion(source: Structure, structures: Structure[]) {
     return builder.getStructure();
 }
 
-function buildUnion(this: StructureSubsetBuilder, elements: Element.Set, id: number) {
+function buildUnion(this: StructureSubsetBuilder, elements: StructureElement.Set, id: number) {
     this.setUnit(id, elements);
 }
 

+ 2 - 2
src/mol-model/structure/structure.ts

@@ -4,11 +4,11 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import Element from './structure/element'
+import StructureElement from './structure/element'
 import Structure from './structure/structure'
 import Unit from './structure/unit'
 import StructureSymmetry from './structure/symmetry'
 import { Link } from './structure/unit/links'
 import StructureProperties from './structure/properties'
 
-export { Element, Link, Structure, Unit, StructureSymmetry, StructureProperties }
+export { StructureElement, Link, Structure, Unit, StructureSymmetry, StructureProperties }

+ 20 - 20
src/mol-model/structure/structure/element.ts

@@ -6,35 +6,35 @@
 
 import { OrderedSet, SortedArray } from 'mol-data/int'
 import Unit from './unit'
+import { ElementIndex } from '../model';
 
-/** Element index in Model */
-type Element = { readonly '@type': 'element' } & number
+interface StructureElement<U = Unit> {
+    unit: U,
+    /** Index into element (atomic/coarse) properties of unit.model */
+    element: ElementIndex
+}
+
+namespace StructureElement {
+    export function create(unit?: Unit, element?: ElementIndex): StructureElement { return { unit: unit as any, element: element || (0 as ElementIndex) }; }
 
-namespace Element {
-    export type Set = SortedArray<Element>
+    // TODO: when nominal types are available, make this indexed by UnitIndex
+    export type Set = SortedArray<ElementIndex>
 
     /** Index into Unit.elements */
-    export type Index = { readonly '@type': 'element-index' } & number
+    export type UnitIndex = { readonly '@type': 'structure-element-index' } & number
 
-    /** All the information required to access element properties */
-    export interface Location<U = Unit> {
-        unit: U,
-        /** Index into element (atomic/coarse) properties of unit.model */
-        element: Element
-    }
-    export function Location(unit?: Unit, element?: Element): Location { return { unit: unit as any, element: element || (0 as Element) }; }
-    export interface Property<T> { (location: Location): T }
+    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: Location<Unit.Atomic>) => T) {
-        return property(l => Unit.isAtomic(l.unit) ? p(l as Location<Unit.Atomic>) : _wrongUnitKind('atomic') );
+    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: 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 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 */
@@ -47,11 +47,11 @@ namespace Element {
              * Indices into the unit.elements array.
              * Can use OrderedSet.forEach to iterate (or OrderedSet.size + OrderedSet.getAt)
              */
-            indices: OrderedSet<Index>
+            indices: OrderedSet<UnitIndex>
         }>
     }
 
-    export function Loci(elements: ArrayLike<{ unit: Unit, indices: OrderedSet<Index> }>): Loci {
+    export function Loci(elements: ArrayLike<{ unit: Unit, indices: OrderedSet<UnitIndex> }>): Loci {
         return { kind: 'element-loci', elements: elements as Loci['elements'] };
     }
 
@@ -60,4 +60,4 @@ namespace Element {
     }
 }
 
-export default Element
+export default StructureElement

+ 52 - 52
src/mol-model/structure/structure/properties.ts

@@ -4,14 +4,14 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import Element from './element'
+import StructureElement from './element'
 import Unit from './unit'
 import { VdwRadius } from '../model/properties/atomic';
 
 const constant = {
-    true: Element.property(l => true),
-    false: Element.property(l => false),
-    zero: Element.property(l => 0)
+    true: StructureElement.property(l => true),
+    false: StructureElement.property(l => false),
+    zero: StructureElement.property(l => 0)
 }
 
 function notAtomic(): never {
@@ -26,73 +26,73 @@ function notCoarse(kind?: string): never {
 // TODO: remove the type checks?
 
 const atom = {
-    key: Element.property(l => l.element),
+    key: StructureElement.property(l => l.element),
 
     // Conformation
-    x: Element.property(l => l.unit.conformation.x(l.element)),
-    y: Element.property(l => l.unit.conformation.y(l.element)),
-    z: Element.property(l => l.unit.conformation.z(l.element)),
-    id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicConformation.atomId.value(l.element)),
-    occupancy: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.model.atomicConformation.occupancy.value(l.element)),
-    B_iso_or_equiv: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.model.atomicConformation.B_iso_or_equiv.value(l.element)),
+    x: StructureElement.property(l => l.unit.conformation.x(l.element)),
+    y: StructureElement.property(l => l.unit.conformation.y(l.element)),
+    z: StructureElement.property(l => l.unit.conformation.z(l.element)),
+    id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicConformation.atomId.value(l.element)),
+    occupancy: StructureElement.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.model.atomicConformation.occupancy.value(l.element)),
+    B_iso_or_equiv: StructureElement.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.model.atomicConformation.B_iso_or_equiv.value(l.element)),
 
     // Hierarchy
-    type_symbol: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element)),
-    label_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_atom_id.value(l.element)),
-    auth_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.auth_atom_id.value(l.element)),
-    label_alt_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_alt_id.value(l.element)),
-    pdbx_formal_charge: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.pdbx_formal_charge.value(l.element)),
+    type_symbol: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element)),
+    label_atom_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_atom_id.value(l.element)),
+    auth_atom_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.auth_atom_id.value(l.element)),
+    label_alt_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_alt_id.value(l.element)),
+    pdbx_formal_charge: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.pdbx_formal_charge.value(l.element)),
 
     // Derived
-    vdw_radius: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element))),
+    vdw_radius: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element))),
 }
 
 const residue = {
-    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residueKey[l.unit.residueIndex[l.element]]),
+    key: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.residueIndex[l.element]),
 
-    group_PDB: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])),
-    label_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])),
-    auth_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_comp_id.value(l.unit.residueIndex[l.element])),
-    label_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_seq_id.value(l.unit.residueIndex[l.element])),
-    auth_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.element])),
-    pdbx_PDB_ins_code: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element])),
+    group_PDB: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])),
+    label_comp_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])),
+    auth_comp_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_comp_id.value(l.unit.residueIndex[l.element])),
+    label_seq_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_seq_id.value(l.unit.residueIndex[l.element])),
+    auth_seq_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.element])),
+    pdbx_PDB_ins_code: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element])),
 
     // Properties
-    secondary_structure_type: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.type[l.unit.residueIndex[l.element]]),
-    secondary_structure_key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.key[l.unit.residueIndex[l.element]]),
+    secondary_structure_type: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.type[l.unit.residueIndex[l.element]]),
+    secondary_structure_key: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.key[l.unit.residueIndex[l.element]]),
 }
 
 const chain = {
-    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chainKey[l.unit.chainIndex[l.element]]),
+    key: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.chainIndex[l.element]),
 
-    label_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])),
-    auth_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.auth_asym_id.value(l.unit.chainIndex[l.element])),
-    label_entity_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_entity_id.value(l.unit.chainIndex[l.element]))
+    label_asym_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])),
+    auth_asym_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.auth_asym_id.value(l.unit.chainIndex[l.element])),
+    label_entity_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_entity_id.value(l.unit.chainIndex[l.element]))
 }
 
 const coarse = {
     key: atom.key,
-    entityKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.entityKey[l.element]),
+    entityKey: StructureElement.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.entityKey[l.element]),
 
     x: atom.x,
     y: atom.y,
     z: atom.z,
 
-    asym_id: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.asym_id.value(l.element)),
-    seq_id_begin: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_begin.value(l.element)),
-    seq_id_end: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_end.value(l.element)),
+    asym_id: StructureElement.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.asym_id.value(l.element)),
+    seq_id_begin: StructureElement.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_begin.value(l.element)),
+    seq_id_end: StructureElement.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_end.value(l.element)),
 
-    sphere_radius: Element.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.radius[l.element]),
-    sphere_rmsf: Element.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.rmsf[l.element]),
+    sphere_radius: StructureElement.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.radius[l.element]),
+    sphere_rmsf: StructureElement.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.rmsf[l.element]),
 
-    gaussian_weight: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.weight[l.element]),
-    gaussian_covariance_matrix: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.covariance_matrix[l.element])
+    gaussian_weight: StructureElement.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.weight[l.element]),
+    gaussian_covariance_matrix: StructureElement.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.covariance_matrix[l.element])
 }
 
-function eK(l: Element.Location) {
+function eK(l: StructureElement) {
     switch (l.unit.kind) {
         case Unit.Kind.Atomic:
-            return l.unit.model.atomicHierarchy.entityKey[l.unit.chainIndex[l.element]]
+            return l.unit.model.atomicHierarchy.getEntityKey(l.unit.chainIndex[l.element])
         case Unit.Kind.Spheres:
             return l.unit.model.coarseHierarchy.spheres.entityKey[l.element]
         case Unit.Kind.Gaussians:
@@ -103,21 +103,21 @@ function eK(l: Element.Location) {
 const entity = {
     key: eK,
 
-    id: Element.property(l => l.unit.model.entities.data.id.value(eK(l))),
-    type: Element.property(l => l.unit.model.entities.data.type.value(eK(l))),
-    src_method: Element.property(l => l.unit.model.entities.data.src_method.value(eK(l))),
-    pdbx_description: Element.property(l => l.unit.model.entities.data.pdbx_description.value(eK(l))),
-    formula_weight: Element.property(l => l.unit.model.entities.data.formula_weight.value(eK(l))),
-    pdbx_number_of_molecules: Element.property(l => l.unit.model.entities.data.pdbx_number_of_molecules.value(eK(l))),
-    details: Element.property(l => l.unit.model.entities.data.details.value(eK(l))),
-    pdbx_mutation: Element.property(l => l.unit.model.entities.data.pdbx_mutation.value(eK(l))),
-    pdbx_fragment: Element.property(l => l.unit.model.entities.data.pdbx_fragment.value(eK(l))),
-    pdbx_ec: Element.property(l => l.unit.model.entities.data.pdbx_ec.value(eK(l)))
+    id: StructureElement.property(l => l.unit.model.entities.data.id.value(eK(l))),
+    type: StructureElement.property(l => l.unit.model.entities.data.type.value(eK(l))),
+    src_method: StructureElement.property(l => l.unit.model.entities.data.src_method.value(eK(l))),
+    pdbx_description: StructureElement.property(l => l.unit.model.entities.data.pdbx_description.value(eK(l))),
+    formula_weight: StructureElement.property(l => l.unit.model.entities.data.formula_weight.value(eK(l))),
+    pdbx_number_of_molecules: StructureElement.property(l => l.unit.model.entities.data.pdbx_number_of_molecules.value(eK(l))),
+    details: StructureElement.property(l => l.unit.model.entities.data.details.value(eK(l))),
+    pdbx_mutation: StructureElement.property(l => l.unit.model.entities.data.pdbx_mutation.value(eK(l))),
+    pdbx_fragment: StructureElement.property(l => l.unit.model.entities.data.pdbx_fragment.value(eK(l))),
+    pdbx_ec: StructureElement.property(l => l.unit.model.entities.data.pdbx_ec.value(eK(l)))
 }
 
 const unit = {
-    operator_name: Element.property(l => l.unit.conformation.operator.name),
-    model_num: Element.property(l => l.unit.model.modelNum)
+    operator_name: StructureElement.property(l => l.unit.conformation.operator.name),
+    model_num: StructureElement.property(l => l.unit.model.modelNum)
 }
 
 const StructureProperties = {

+ 18 - 18
src/mol-model/structure/structure/structure.ts

@@ -7,9 +7,9 @@
 import { IntMap, SortedArray, Iterator, Segmentation } from 'mol-data/int'
 import { UniqueArray } from 'mol-data/generic'
 import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator'
-import { Model } from '../model'
+import { Model, ElementIndex } from '../model'
 import { sort, arraySwap, hash1, sortArray } from 'mol-data/util';
-import Element from './element'
+import StructureElement from './element'
 import Unit from './unit'
 import { StructureLookup3D } from './util/lookup3d';
 import { CoarseElements } from '../model/properties/coarse';
@@ -49,7 +49,7 @@ class Structure {
         return hash;
     }
 
-    elementLocations(): Iterator<Element.Location> {
+    elementLocations(): Iterator<StructureElement> {
         return new Structure.ElementLocationIterator(this);
     }
 
@@ -118,20 +118,20 @@ namespace Structure {
      * of consecutive "single atom chains".
      */
     export function ofModel(model: Model): Structure {
-        const chains = model.atomicHierarchy.chainSegments;
+        const chains = model.atomicHierarchy.chainAtomSegments;
         const builder = new StructureBuilder();
 
         for (let c = 0; c < chains.count; c++) {
-            const start = chains.segments[c];
+            const start = chains.offsets[c];
 
             // merge all consecutive "single atom chains"
             while (c + 1 < chains.count
-                && chains.segments[c + 1] - chains.segments[c] === 1
-                && chains.segments[c + 2] - chains.segments[c + 1] === 1) {
+                && chains.offsets[c + 1] - chains.offsets[c] === 1
+                && chains.offsets[c + 2] - chains.offsets[c + 1] === 1) {
                 c++;
             }
 
-            const elements = SortedArray.ofBounds(start as Element, chains.segments[c + 1] as Element);
+            const elements = SortedArray.ofBounds(start as ElementIndex, chains.offsets[c + 1] as ElementIndex);
             builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements);
         }
 
@@ -149,9 +149,9 @@ namespace Structure {
     }
 
     function addCoarseUnits(builder: StructureBuilder, model: Model, elements: CoarseElements, kind: Unit.Kind) {
-        const { chainSegments } = elements;
-        for (let cI = 0; cI < chainSegments.count; cI++) {
-            const elements = SortedArray.ofBounds(chainSegments.segments[cI] as Element, chainSegments.segments[cI + 1] as Element);
+        const { chainElementSegments } = elements;
+        for (let cI = 0; cI < chainElementSegments.count; cI++) {
+            const elements = SortedArray.ofBounds<ElementIndex>(chainElementSegments.offsets[cI], chainElementSegments.offsets[cI + 1]);
             builder.addUnit(kind, model, SymmetryOperator.Default, elements);
         }
     }
@@ -159,7 +159,7 @@ namespace Structure {
     export class StructureBuilder {
         private units: Unit[] = [];
 
-        addUnit(kind: Unit.Kind, model: Model, operator: SymmetryOperator, elements: Element.Set): Unit {
+        addUnit(kind: Unit.Kind, model: Model, operator: SymmetryOperator, elements: StructureElement.Set): Unit {
             const unit = Unit.create(this.units.length, kind, model, operator, elements);
             this.units.push(unit);
             return unit;
@@ -211,15 +211,15 @@ namespace Structure {
         return true;
     }
 
-    export class ElementLocationIterator implements Iterator<Element.Location> {
-        private current = Element.Location();
+    export class ElementLocationIterator implements Iterator<StructureElement> {
+        private current = StructureElement.create();
         private unitIndex = 0;
-        private elements: Element.Set;
+        private elements: StructureElement.Set;
         private maxIdx = 0;
         private idx = -1;
 
         hasNext: boolean;
-        move(): Element.Location {
+        move(): StructureElement {
             this.advance();
             this.current.element = this.elements[this.idx];
             return this.current;
@@ -257,7 +257,7 @@ namespace Structure {
 
     export function getEntityKeys(structure: Structure) {
         const { units } = structure;
-        const l = Element.Location();
+        const l = StructureElement.create();
         const keys = UniqueArray.create<number, number>();
 
         for (const unit of units) {
@@ -266,7 +266,7 @@ namespace Structure {
             l.unit = unit;
             const elements = unit.elements;
 
-            const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainSegments, elements);
+            const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
             while (chainsIt.hasNext) {
                 const chainSegment = chainsIt.move();
                 l.element = elements[chainSegment.start];

+ 17 - 16
src/mol-model/structure/structure/unit.ts

@@ -12,7 +12,8 @@ import { IntraUnitLinks, computeIntraUnitBonds } from './unit/links'
 import { CoarseElements, CoarseSphereConformation, CoarseGaussianConformation } from '../model/properties/coarse';
 import { ValueRef } from 'mol-util';
 import { UnitRings } from './unit/rings';
-import Element from './element'
+import StructureElement from './element'
+import { ChainIndex, ResidueIndex } from '../model/indexing';
 
 // A building block of a structure that corresponds to an atomic or a coarse grained representation
 // 'conveniently grouped together'.
@@ -26,7 +27,7 @@ namespace Unit {
     export function isSpheres(u: Unit): u is Spheres { return u.kind === Kind.Spheres; }
     export function isGaussians(u: Unit): u is Gaussians { return u.kind === Kind.Gaussians; }
 
-    export function create(id: number, kind: Kind, model: Model, operator: SymmetryOperator, elements: Element.Set): Unit {
+    export function create(id: number, kind: Kind, model: Model, operator: SymmetryOperator, elements: StructureElement.Set): Unit {
         switch (kind) {
             case Kind.Atomic: return new Atomic(id, unitIdFactory(), model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation), AtomicProperties());
             case Kind.Spheres: return createCoarse(id, unitIdFactory(), model, Kind.Spheres, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.spheres));
@@ -36,7 +37,7 @@ namespace Unit {
 
     /** A group of units that differ only by symmetry operators. */
     export type SymmetryGroup = {
-        readonly elements: Element.Set,
+        readonly elements: StructureElement.Set,
         readonly units: ReadonlyArray<Unit>
         readonly hashCode: number
     }
@@ -53,11 +54,11 @@ namespace Unit {
         readonly id: number,
         // invariant ID stays the same even if the Operator/conformation changes.
         readonly invariantId: number,
-        readonly elements: Element.Set,
+        readonly elements: StructureElement.Set,
         readonly model: Model,
         readonly conformation: SymmetryOperator.ArrayMapping,
 
-        getChild(elements: Element.Set): Unit,
+        getChild(elements: StructureElement.Set): Unit,
         applyOperator(id: number, operator: SymmetryOperator, dontCompose?: boolean /* = false */): Unit,
 
         readonly lookup3d: Lookup3D
@@ -77,17 +78,17 @@ namespace Unit {
 
         readonly id: number;
         readonly invariantId: number;
-        readonly elements: Element.Set;
+        readonly elements: StructureElement.Set;
         readonly model: Model;
         readonly conformation: SymmetryOperator.ArrayMapping;
 
         // Reference some commonly accessed things for faster access.
-        readonly residueIndex: ArrayLike<number>;
-        readonly chainIndex: ArrayLike<number>;
+        readonly residueIndex: ArrayLike<ResidueIndex>;
+        readonly chainIndex: ArrayLike<ChainIndex>;
 
         private props: AtomicProperties;
 
-        getChild(elements: Element.Set): Unit {
+        getChild(elements: StructureElement.Set): Unit {
             if (elements.length === this.elements.length) return this;
             return new Atomic(this.id, this.invariantId, this.model, elements, this.conformation, AtomicProperties());
         }
@@ -116,15 +117,15 @@ namespace Unit {
             return this.props.rings.ref;
         }
 
-        constructor(id: number, invariantId: number, model: Model, elements: Element.Set, conformation: SymmetryOperator.ArrayMapping, props: AtomicProperties) {
+        constructor(id: number, invariantId: number, model: Model, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping, props: AtomicProperties) {
             this.id = id;
             this.invariantId = invariantId;
             this.model = model;
             this.elements = elements;
             this.conformation = conformation;
 
-            this.residueIndex = model.atomicHierarchy.residueSegments.segmentMap;
-            this.chainIndex = model.atomicHierarchy.chainSegments.segmentMap;
+            this.residueIndex = model.atomicHierarchy.residueAtomSegments.index;
+            this.chainIndex = model.atomicHierarchy.chainAtomSegments.index;
             this.props = props;
         }
     }
@@ -144,14 +145,14 @@ namespace Unit {
 
         readonly id: number;
         readonly invariantId: number;
-        readonly elements: Element.Set;
+        readonly elements: StructureElement.Set;
         readonly model: Model;
         readonly conformation: SymmetryOperator.ArrayMapping;
 
         readonly coarseElements: CoarseElements;
         readonly coarseConformation: C;
 
-        getChild(elements: Element.Set): Unit {
+        getChild(elements: StructureElement.Set): Unit {
             if (elements.length === this.elements.length) return this as any as Unit /** lets call this an ugly temporary hack */;
             return createCoarse(this.id, this.invariantId, this.model, this.kind, elements, this.conformation);
         }
@@ -176,7 +177,7 @@ namespace Unit {
             return this.kind === Kind.Spheres ? this.model.coarseConformation.spheres : this.model.coarseConformation.gaussians;
         }
 
-        constructor(id: number, invariantId: number, model: Model, kind: K, elements: Element.Set, conformation: SymmetryOperator.ArrayMapping) {
+        constructor(id: number, invariantId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping) {
             this.kind = kind;
             this.id = id;
             this.invariantId = invariantId;
@@ -188,7 +189,7 @@ namespace Unit {
         }
     }
 
-    function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, model: Model, kind: K, elements: Element.Set, conformation: SymmetryOperator.ArrayMapping): Unit {
+    function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping): Unit {
         return new Coarse(id, invariantId, model, kind, elements, conformation) as any as Unit /** lets call this an ugly temporary hack */;
     }
 

+ 2 - 2
src/mol-model/structure/structure/unit/rings/compute.ts

@@ -13,7 +13,7 @@ export default function computeRings(unit: Unit.Atomic) {
     const size = largestResidue(unit);
     const state = State(unit, size);
 
-    const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueSegments, unit.elements);
+    const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
     while (residuesIt.hasNext) {
         const seg = residuesIt.move();
         processResidue(state, seg.start, seg.end);
@@ -75,7 +75,7 @@ function resetState(state: State) {
 }
 
 function largestResidue(unit: Unit.Atomic) {
-    const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueSegments, unit.elements);
+    const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
     let size = 0;
     while (residuesIt.hasNext) {
         const seg = residuesIt.move();

+ 9 - 8
src/mol-model/structure/structure/util/subset-builder.ts

@@ -6,19 +6,20 @@
 
 import { IntMap, SortedArray } from 'mol-data/int';
 import { sortArray } from 'mol-data/util';
-import Element from '../element';
+import StructureElement from '../element';
 import StructureSymmetry from '../symmetry';
 import Unit from '../unit';
 import Structure from '../structure';
+import { ElementIndex } from '../../model';
 
 export class StructureSubsetBuilder {
     private ids: number[] = [];
-    private unitMap = IntMap.Mutable<Element[]>();
+    private unitMap = IntMap.Mutable<ElementIndex[]>();
     private parentId = -1;
-    private currentUnit: Element[] = [];
+    private currentUnit: ElementIndex[] = [];
     elementCount = 0;
 
-    addToUnit(parentId: number, e: Element) {
+    addToUnit(parentId: number, e: ElementIndex) {
         const unit = this.unitMap.get(parentId);
         if (!!unit) { unit[unit.length] = e; }
         else {
@@ -33,7 +34,7 @@ export class StructureSubsetBuilder {
         this.currentUnit = this.currentUnit.length > 0 ? [] : this.currentUnit;
     }
 
-    addElement(e: Element) {
+    addElement(e: ElementIndex) {
         this.currentUnit[this.currentUnit.length] = e;
         this.elementCount++;
     }
@@ -45,9 +46,9 @@ export class StructureSubsetBuilder {
         this.parentId = -1;
     }
 
-    setUnit(parentId: number, elements: ArrayLike<Element>) {
+    setUnit(parentId: number, elements: ArrayLike<ElementIndex>) {
         this.ids[this.ids.length] = parentId;
-        this.unitMap.set(parentId, elements as Element[]);
+        this.unitMap.set(parentId, elements as ElementIndex[]);
         this.elementCount += elements.length;
     }
 
@@ -100,7 +101,7 @@ export class StructureSubsetBuilder {
         return this._getStructure(true);
     }
 
-    setSingletonLocation(location: Element.Location) {
+    setSingletonLocation(location: StructureElement) {
         const id = this.ids[0];
         location.unit = this.parent.unitMap.get(id);
         location.element = this.unitMap.get(id)[0];

+ 6 - 6
src/mol-view/label.ts

@@ -5,14 +5,14 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Unit, Element, StructureProperties as Props } from 'mol-model/structure';
+import { Unit, StructureElement, StructureProperties as Props } from 'mol-model/structure';
 import { Loci } from 'mol-model/loci';
 import { OrderedSet } from 'mol-data/int';
 
-const elementLocA = Element.Location()
-const elementLocB = Element.Location()
+const elementLocA = StructureElement.create()
+const elementLocB = StructureElement.create()
 
-function setElementLocation(loc: Element.Location, unit: Unit, index: number) {
+function setElementLocation(loc: StructureElement, unit: Unit, index: number) {
     loc.unit = unit
     loc.element = unit.elements[index]
 }
@@ -23,7 +23,7 @@ export function labelFirst(loci: Loci): string {
             const e = loci.elements[0]
             if (e) {
                 const el = e.unit.elements[OrderedSet.getAt(e.indices, 0)];
-                return elementLabel(Element.Location(e.unit, el))
+                return elementLabel(StructureElement.create(e.unit, el))
             } else {
                 return 'Unknown'
             }
@@ -43,7 +43,7 @@ export function labelFirst(loci: Loci): string {
     }
 }
 
-export function elementLabel(loc: Element.Location) {
+export function elementLabel(loc: StructureElement) {
     const model = loc.unit.model.label
     const instance = loc.unit.conformation.operator.name
     let element = ''

+ 40 - 14
src/perf-tests/cif-encoder.ts

@@ -5,6 +5,7 @@
  */
 
 import { CifWriter } from 'mol-io/writer/cif'
+import * as fs from 'fs'
 
 const category1fields: CifWriter.Field[] = [
     CifWriter.Field.str('f1', i => 'v' + i),
@@ -18,24 +19,49 @@ const category2fields: CifWriter.Field[] = [
     CifWriter.Field.float('e3', i => Math.random()),
 ];
 
-function getInstance(ctx: { name: string, fields: CifWriter.Field[], rowCount: number }): CifWriter.Category {
+function getCat(name: string): CifWriter.Category {
     return {
-        data: void 0,
-        name: ctx.name,
-        fields: ctx.fields,
-        rowCount: ctx.rowCount
+        name,
+        instance(ctx: { fields: CifWriter.Field[], rowCount: number }) {
+            return { data: void 0, fields: ctx.fields, rowCount: ctx.rowCount };
+        }
+    };
+}
+
+function testText() {
+    const enc = CifWriter.createEncoder();
+
+    const filter: CifWriter.Category.Filter = {
+        includeCategory(cat) { return true; },
+        includeField(cat, field) { return !(cat === 'cat2' && field === 'e2') }
     }
+
+    enc.startDataBlock('test');
+    enc.setFilter(filter);
+    enc.writeCategory(getCat('cat1'), [{ rowCount: 5, fields: category1fields }]);
+    enc.writeCategory(getCat('cat2'), [{ rowCount: 1, fields: category2fields }]);
+    console.log(enc.getData());
 }
 
-const enc = CifWriter.createEncoder();
+testText();
+
+
+function testBinary() {
+    const enc = CifWriter.createEncoder({ binary: true });
+
+    const filter: CifWriter.Category.Filter = {
+        includeCategory(cat) { return true; },
+        includeField(cat, field) { return !(cat === 'cat2' && field === 'e2') }
+    }
 
-const filter: CifWriter.Category.Filter = {
-    includeCategory(cat) { return true; },
-    includeField(cat, field) { return !(cat === 'cat2' && field === 'e2') }
+    enc.startDataBlock('test');
+    enc.setFilter(filter);
+    enc.writeCategory(getCat('cat1'), [{ rowCount: 5, fields: category1fields }]);
+    enc.writeCategory(getCat('cat2'), [{ rowCount: 1, fields: category2fields }]);
+    enc.encode();
+    const data = enc.getData() as Uint8Array;
+    fs.writeFileSync('e:/test/mol-star/test.bcif', new Buffer(data));
+    console.log('written binary');
 }
 
-enc.startDataBlock('test');
-enc.setFilter(filter);
-enc.writeCategory(getInstance, [{ rowCount: 5, name: 'cat1', fields: category1fields }]);
-enc.writeCategory(getInstance, [{ rowCount: 1, name: 'cat2', fields: category2fields  }]);
-console.log(enc.getData());
+testBinary();

+ 3 - 3
src/perf-tests/structure.ts

@@ -11,7 +11,7 @@ import * as fs from 'fs'
 import fetch from 'node-fetch'
 import CIF from 'mol-io/reader/cif'
 
-import { Structure, Model, Queries as Q, Element, Selection, StructureSymmetry, Query, Format, StructureProperties as SP } from 'mol-model/structure'
+import { Structure, Model, Queries as Q, StructureElement, Selection, StructureSymmetry, Query, Format, StructureProperties as SP } from 'mol-model/structure'
 //import { Segmentation, OrderedSet } from 'mol-data/int'
 
 import to_mmCIF from 'mol-model/structure/export/mmcif'
@@ -119,8 +119,8 @@ export namespace PropertyAccess {
         return s;
     }
 
-    function sumProperty(structure: Structure, p: Element.Property<number>) {
-        const l = Element.Location();
+    function sumProperty(structure: Structure, p: StructureElement.Property<number>) {
+        const l = StructureElement.create();
         let s = 0;
 
         for (const unit of structure.units) {

+ 13 - 13
src/servers/model/server/api.ts

@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Query, Queries, Structure, Element, StructureSymmetry, StructureProperties as Props } from 'mol-model/structure';
+import { Query, Queries, Structure, StructureElement, StructureSymmetry, StructureProperties as Props } from 'mol-model/structure';
 
 export enum QueryParamType {
     String,
@@ -51,26 +51,26 @@ const AtomSiteParameters = {
 //     return Element.property(l => p(l) === id);
 // }
 
-function entityTest1_555(params: any): Element.Predicate | undefined {
-    if (typeof params.entity_id === 'undefined') return Element.property(l => l.unit.conformation.operator.isIdentity);
+function entityTest1_555(params: any): StructureElement.Predicate | undefined {
+    if (typeof params.entity_id === 'undefined') return StructureElement.property(l => l.unit.conformation.operator.isIdentity);
     const p = Props.entity.id, id = '' + params.entityId;
-    return Element.property(l => l.unit.conformation.operator.isIdentity && p(l) === id);
+    return StructureElement.property(l => l.unit.conformation.operator.isIdentity && p(l) === id);
 }
 
-function chainTest(params: any): Element.Predicate | undefined {
+function chainTest(params: any): StructureElement.Predicate | undefined {
     if (typeof params.label_asym_id !== 'undefined') {
         const p = Props.chain.label_asym_id, id = '' + params.label_asym_id;
-        return Element.property(l => p(l) === id);
+        return StructureElement.property(l => p(l) === id);
     }
     if (typeof params.auth_asym_id !== 'undefined') {
         const p = Props.chain.auth_asym_id, id = '' + params.auth_asym_id;
-        return Element.property(l => p(l) === id);
+        return StructureElement.property(l => p(l) === id);
     }
     return void 0;
 }
 
-function residueTest(params: any): Element.Predicate | undefined {
-    const props: Element.Property<any>[] = [], values: any[] = [];
+function residueTest(params: any): StructureElement.Predicate | undefined {
+    const props: StructureElement.Property<any>[] = [], values: any[] = [];
 
     if (typeof params.label_seq_id !== 'undefined') {
         props.push(Props.residue.label_seq_id);
@@ -99,12 +99,12 @@ function residueTest(params: any): Element.Predicate | undefined {
 
     switch (props.length) {
         case 0: return void 0;
-        case 1: return Element.property(l => props[0](l) === values[0]);
-        case 2: return Element.property(l => props[0](l) === values[0] && props[1](l) === values[1]);
-        case 3: return Element.property(l => props[0](l) === values[0] && props[1](l) === values[1] && props[2](l) === values[2]);
+        case 1: return StructureElement.property(l => props[0](l) === values[0]);
+        case 2: return StructureElement.property(l => props[0](l) === values[0] && props[1](l) === values[1]);
+        case 3: return StructureElement.property(l => props[0](l) === values[0] && props[1](l) === values[1] && props[2](l) === values[2]);
         default: {
             const len = props.length;
-            return Element.property(l => {
+            return StructureElement.property(l => {
                 for (let i = 0; i < len; i++) if (!props[i](l) !== values[i]) return false;
                 return true;
             });

+ 26 - 29
src/servers/model/server/query.ts

@@ -95,6 +95,7 @@ export async function resolveRequest(req: Request, writer: Writer) {
     };
 
     encoder.writeCategory(_model_server_stats, [stats]);
+    encoder.encode();
 
     encoder.writeTo(writer);
 
@@ -112,9 +113,9 @@ import CifField = CifWriter.Field
 
 function string<T>(name: string, str: (data: T, i: number) => string, isSpecified?: (data: T) => boolean): CifField<number, T> {
     if (isSpecified) {
-        return CifField.str(name,  (i, d) => str(d, i), { valueKind: (i, d) => isSpecified(d) ? Column.ValueKind.Present : Column.ValueKind.NotPresent });
+        return CifField.str(name, (i, d) => str(d, i), { valueKind: (i, d) => isSpecified(d) ? Column.ValueKind.Present : Column.ValueKind.NotPresent });
     }
-    return CifField.str(name,  (i, d) => str(d, i));
+    return CifField.str(name, (i, d) => str(d, i));
 }
 
 function int32<T>(name: string, value: (data: T) => number): CifField<number, T> {
@@ -144,33 +145,29 @@ const _model_server_stats_fields: CifField<number, Stats>[] = [
 ];
 
 
-function _model_server_result(request: Request): CifWriter.Category {
-    return {
-        data: request,
-        name: 'model_server_result',
-        fields: _model_server_result_fields,
-        rowCount: 1
-    };
-}
-
-function _model_server_params(request: Request): CifWriter.Category {
-    const params: string[][] = [];
-    for (const k of Object.keys(request.normalizedParams)) {
-        params.push([k, '' + request.normalizedParams[k]]);
+const _model_server_result: CifWriter.Category<Request> = {
+    name: 'model_server_result',
+    instance: (request) => ({ data: request, fields: _model_server_result_fields, rowCount: 1 })
+};
+
+const _model_server_params: CifWriter.Category<Request> = {
+    name: 'model_server_params',
+    instance(request) {
+        const params: string[][] = [];
+        for (const k of Object.keys(request.normalizedParams)) {
+            params.push([k, '' + request.normalizedParams[k]]);
+        }
+        return {
+            data: params,
+
+            fields: _model_server_params_fields,
+            rowCount: params.length
+        }
     }
-    return {
-        data: params,
-        name: 'model_server_params',
-        fields: _model_server_params_fields,
-        rowCount: params.length
-    };
-}
+};
 
-function _model_server_stats(stats: Stats): CifWriter.Category {
-    return {
-        data: stats,
-        name: 'model_server_stats',
-        fields: _model_server_stats_fields,
-        rowCount: 1
-    };
+
+const _model_server_stats: CifWriter.Category<Stats> = {
+    name: 'model_server_stats',
+    instance: (stats) => ({ data: stats, fields: _model_server_stats_fields, rowCount: 1 })
 }

+ 46 - 49
src/servers/volume/server/query/encode.ts

@@ -28,9 +28,9 @@ interface ResultContext {
 
 function string<T>(name: string, str: (data: T) => string, isSpecified?: (data: T) => boolean): CifWriter.Field<number, T> {
     if (isSpecified) {
-        return CifWriter.Field.str(name,  (i, d) => str(d), { valueKind: (i, d) => isSpecified(d) ? Column.ValueKind.Present : Column.ValueKind.NotPresent });
+        return CifWriter.Field.str(name, (i, d) => str(d), { valueKind: (i, d) => isSpecified(d) ? Column.ValueKind.Present : Column.ValueKind.NotPresent });
     }
-    return CifWriter.Field.str(name,  (i, d) => str(d));
+    return CifWriter.Field.str(name, (i, d) => str(d));
 }
 
 function int32<T>(name: string, value: (data: T) => number): CifWriter.Field<number, T> {
@@ -90,53 +90,54 @@ const _volume_data_3d_info_fields = [
     float64<_vd3d_Ctx>('max_sampled', ctx => ctx.sampledValuesInfo.max)
 ];
 
-function _volume_data_3d_info(result: ResultContext): CifWriter.Category {
-    const ctx: _vd3d_Ctx = {
-        header: result.query.data.header,
-        channelIndex: result.channelIndex,
-        grid: result.query.samplingInfo.gridDomain,
-        sampleRate: result.query.samplingInfo.sampling.rate,
-        globalValuesInfo: result.query.data.header.sampling[0].valuesInfo[result.channelIndex],
-        sampledValuesInfo: result.query.data.header.sampling[result.query.samplingInfo.sampling.index].valuesInfo[result.channelIndex]
-    };
-
-    return {
-        data: ctx,
-        name: 'volume_data_3d_info',
-        fields: _volume_data_3d_info_fields,
-        rowCount: 1
-    };
-}
+const _volume_data_3d_info: CifWriter.Category<ResultContext> = {
+    name: 'volume_data_3d_info',
+    instance(result) {
+        const ctx: _vd3d_Ctx = {
+            header: result.query.data.header,
+            channelIndex: result.channelIndex,
+            grid: result.query.samplingInfo.gridDomain,
+            sampleRate: result.query.samplingInfo.sampling.rate,
+            globalValuesInfo: result.query.data.header.sampling[0].valuesInfo[result.channelIndex],
+            sampledValuesInfo: result.query.data.header.sampling[result.query.samplingInfo.sampling.index].valuesInfo[result.channelIndex]
+        };
+
+        return { data: ctx, fields: _volume_data_3d_info_fields, rowCount: 1 }
+    }
+};
 
 function _volume_data_3d_number(i: number, ctx: DataFormat.ValueArray): number {
     return ctx[i];
 }
 
-function _volume_data_3d(ctx: ResultContext) {
-    const data = ctx.query.values[ctx.channelIndex];
-
-    const E = ArrayEncoding;
-    let encoder: ArrayEncoder;
-    let typedArray: any;
-    if (ctx.query.data.header.valueType === DataFormat.ValueType.Float32 || ctx.query.data.header.valueType === DataFormat.ValueType.Int16) {
-        let min: number, max: number;
-        min = data[0], max = data[0];
-        for (let i = 0, n = data.length; i < n; i++) {
-            let v = data[i];
-            if (v < min) min = v;
-            else if (v > max) max = v;
+const _volume_data_3d: CifWriter.Category<ResultContext> = {
+    name: 'volume_data_3d',
+    instance(ctx) {
+        const data = ctx.query.values[ctx.channelIndex];
+
+        const E = ArrayEncoding;
+        let encoder: ArrayEncoder;
+        let typedArray: any;
+        if (ctx.query.data.header.valueType === DataFormat.ValueType.Float32 || ctx.query.data.header.valueType === DataFormat.ValueType.Int16) {
+            let min: number, max: number;
+            min = data[0], max = data[0];
+            for (let i = 0, n = data.length; i < n; i++) {
+                let v = data[i];
+                if (v < min) min = v;
+                else if (v > max) max = v;
+            }
+            typedArray = Float32Array;
+            // encode into 255 steps and store each value in 1 byte.
+            encoder = E.by(E.intervalQuantizaiton(min, max, 255, Uint8Array)).and(E.byteArray);
+        } else {
+            typedArray = Int8Array;
+            // just encode the bytes
+            encoder = E.by(E.byteArray)
         }
-        typedArray = Float32Array;
-        // encode into 255 steps and store each value in 1 byte.
-        encoder = E.by(E.intervalQuantizaiton(min, max, 255, Uint8Array)).and(E.byteArray);
-    } else {
-        typedArray = Int8Array;
-        // just encode the bytes
-        encoder = E.by(E.byteArray)
-    }
 
-    const fields = [CifWriter.Field.float('values', _volume_data_3d_number, { encoder, typedArray, digitCount: 6 })];
-    return { data, name: 'volume_data_3d', fields, rowCount: data.length };
+        const fields = [CifWriter.Field.float('values', _volume_data_3d_number, { encoder, typedArray, digitCount: 6 })];
+        return { data, fields, rowCount: data.length };
+    }
 }
 
 function pickQueryBoxDimension(ctx: Data.QueryContext, e: 'a' | 'b', d: number) {
@@ -171,13 +172,9 @@ const _density_server_result_fields = [
     queryBoxDimension('b', 2)
 ]
 
-function _density_server_result(ctx: Data.QueryContext): CifWriter.Category {
-    return {
-        data: ctx,
-        name: 'density_server_result',
-        fields: _density_server_result_fields,
-        rowCount: 1
-    };
+const _density_server_result: CifWriter.Category<Data.QueryContext> = {
+    name: 'density_server_result',
+    instance: ctx => ({ data: ctx, fields: _density_server_result_fields, rowCount: 1 })
 }
 
 function write(encoder: CifWriter.Encoder, query: Data.QueryContext) {