diff --git a/src/mol-data/util/_spec/buckets.spec.ts b/src/mol-data/util/_spec/buckets.spec.ts
index 64046ce37c0f27d22decc776da511fc5de018654..e4e3d1857761950d7ad3ded5646a8befac515f95 100644
--- a/src/mol-data/util/_spec/buckets.spec.ts
+++ b/src/mol-data/util/_spec/buckets.spec.ts
@@ -18,7 +18,7 @@ describe('buckets', () => {
     it('full range', () => {
         const xs = [1, 1, 2, 2, 3, 1];
         const range = createRangeArray(0, xs.length - 1);
-        const bs = makeBuckets(range, i => xs[i], false);
+        const bs = makeBuckets(range, i => xs[i]);
 
         expect(reorder(range, xs)).toEqual([1, 1, 1, 2, 2, 3]);
         expect(Array.from(bs)).toEqual([0, 3, 5, 6]);
@@ -27,7 +27,7 @@ describe('buckets', () => {
     it('sort', () => {
         const xs = [3, 1, 2, 1, 2, 3];
         const range = createRangeArray(0, xs.length - 1);
-        makeBuckets(range, i => xs[i], true);
+        makeBuckets(range, i => xs[i], { sort: true });
 
         expect(reorder(range, xs)).toEqual([1, 1, 2, 2, 3, 3]);
     });
@@ -35,7 +35,7 @@ describe('buckets', () => {
     it('subrange', () => {
         const xs = [2, 1, 2, 1, 2, 3, 1];
         const range = createRangeArray(0, xs.length - 1);
-        const bs = makeBuckets(range, i => xs[i], false, 1, 5);
+        const bs = makeBuckets(range, i => xs[i], { sort: false, start: 1, end: 5 });
 
         expect(reorder(range, xs)).toEqual([2, 1, 1, 2, 2, 3, 1]);
         expect(Array.from(bs)).toEqual([1, 3, 5]);
diff --git a/src/mol-data/util/buckets.ts b/src/mol-data/util/buckets.ts
index 33bbc3d79b7c874454242c14ddab1e09471f1ea2..de409570912144fa1176004b45ed42161c6b3c01 100644
--- a/src/mol-data/util/buckets.ts
+++ b/src/mol-data/util/buckets.ts
@@ -4,13 +4,19 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
+import { sort, arraySwap } from './sort';
+
 type Bucket = {
     key: any,
     count: number,
     offset: number
 }
 
-function _makeBuckets(indices: Helpers.ArrayLike<number>, getKey: (i: number) => any, sort: boolean, start: number, end: number) {
+function sortAsc(bs: Bucket[], i: number, j: number) { return bs[i].key < bs[j].key ? -1 : 1; }
+
+function _makeBuckets(indices: Helpers.ArrayLike<number>,
+    getKey: (i: number) => any, sortBuckets: boolean, start: number, end: number) {
+
     const buckets = new Map<any, Bucket>();
     const bucketList: Bucket[] = [];
 
@@ -33,7 +39,7 @@ function _makeBuckets(indices: Helpers.ArrayLike<number>, getKey: (i: number) =>
     bucketOffsets[bucketList.length] = end;
 
     let sorted = true;
-    if (sort) {
+    if (sortBuckets) {
         for (let i = 1, _i = bucketList.length; i < _i; i++) {
             if (bucketList[i - 1].key > bucketList[i].key) {
                 sorted = false;
@@ -47,8 +53,8 @@ function _makeBuckets(indices: Helpers.ArrayLike<number>, getKey: (i: number) =>
         return bucketOffsets;
     }
 
-    if (sort && !sorted) {
-        bucketList.sort((x, y) => x.key <= y.key ? -1 : 1);
+    if (sortBuckets && !sorted) {
+        sort(bucketList, start, end, sortAsc, arraySwap);
     }
 
     let offset = 0;
@@ -75,15 +81,24 @@ function _makeBuckets(indices: Helpers.ArrayLike<number>, getKey: (i: number) =>
     return bucketOffsets;
 }
 
+export interface MakeBucketsOptions<K> {
+    // If specified, will be sorted
+    sort?: boolean,
+    // inclusive start indidex
+    start?: number,
+    // exclusive end index
+    end?: number
+}
+
 /**
  * Reorders indices so that the same keys are next to each other, [start, end)
  * Returns the offsets of buckets. So that [offsets[i], offsets[i + 1]) determines the range.
  */
-export function makeBuckets<T>(indices: Helpers.ArrayLike<number>, getKey: (i: number) => string | number, sort: boolean, start?: number, end?: number): ArrayLike<number> {
-    const s = start || 0;
-    const e = typeof end === 'undefined' ? indices.length : end;
-
+export function makeBuckets<K extends string | number>(
+    indices: Helpers.ArrayLike<number>, getKey: (i: number) => K, options?: MakeBucketsOptions<K>): ArrayLike<number> {
+    const s = (options && options.start) || 0;
+    const e = (options && options.end) || indices.length;
     if (e - s <= 0) throw new Error('Can only bucket non-empty collections.');
 
-    return _makeBuckets(indices, getKey, sort, s, e);
+    return _makeBuckets(indices, getKey, !!(options && options.sort), s, e);
 }
\ No newline at end of file
diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts
index 97a22d6f091628b20225e3b9fba676944cc7b248..586b86a60c124cf6e69c84c764ab95f8c32c7f32 100644
--- a/src/mol-model/structure/model/formats/mmcif.ts
+++ b/src/mol-model/structure/model/formats/mmcif.ts
@@ -27,13 +27,13 @@ import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif';
 import mmCIF_Format = Format.mmCIF
 type AtomSite = mmCIF_Database['atom_site']
 
-function findModelBounds({ data }: mmCIF_Format, startIndex: number) {
+function findModelEnd({ data }: mmCIF_Format, startIndex: number) {
     const num = data.atom_site.pdbx_PDB_model_num;
     const atomCount = num.rowCount;
-    if (!num.isDefined) return Interval.ofBounds(startIndex, atomCount);
+    if (!num.isDefined) return atomCount;
     let endIndex = startIndex + 1;
     while (endIndex < atomCount && num.areValuesEqual(startIndex, endIndex)) endIndex++;
-    return Interval.ofBounds(startIndex, endIndex);
+    return endIndex;
 }
 
 function findHierarchyOffsets(atom_site: AtomSite) {
@@ -194,12 +194,11 @@ function buildModels(format: mmCIF_Format): Task<ReadonlyArray<Model>> {
         const models: Model[] = [];
         let modelStart = 0;
         while (modelStart < atomCount) {
-            const bounds = findModelBounds(format, modelStart);
-
-            const atom_site = await sortAtomSite(ctx, format.data.atom_site, 0, Interval.end(bounds));
+            const modelEnd = findModelEnd(format, modelStart);
+            const atom_site = await sortAtomSite(ctx, format.data.atom_site, modelStart, modelEnd);
             const model = createModel(format, atom_site, models.length > 0 ? models[models.length - 1] : void 0);
             models.push(model);
-            modelStart = Interval.end(bounds);
+            modelStart = modelEnd;
         }
         return models;
     });
diff --git a/src/mol-model/structure/model/formats/mmcif/sort.ts b/src/mol-model/structure/model/formats/mmcif/sort.ts
index 6809d48119a2ba88860ecc64a485e6b1e46f6902..7cbee9e5b102854bb2c80120fbc4fbfe2baa57bf 100644
--- a/src/mol-model/structure/model/formats/mmcif/sort.ts
+++ b/src/mol-model/structure/model/formats/mmcif/sort.ts
@@ -20,16 +20,16 @@ export async function sortAtomSite(ctx: RuntimeContext, atom_site: mmCIF_Databas
     const indices = createRangeArray(start, end - 1);
 
     const { label_entity_id, label_asym_id, label_seq_id } = atom_site;
-    const entityBuckets = makeBuckets(indices, label_entity_id.value, false);
+    const entityBuckets = makeBuckets(indices, label_entity_id.value);
     if (ctx.shouldUpdate) await ctx.update();
     for (let ei = 0, _eI = entityBuckets.length - 1; ei < _eI; ei++) {
-        const chainBuckets = makeBuckets(indices, label_asym_id.value, false, entityBuckets[ei], entityBuckets[ei + 1]);
+        const chainBuckets = makeBuckets(indices, label_asym_id.value, { start: entityBuckets[ei], end: entityBuckets[ei + 1] });
         for (let cI = 0, _cI = chainBuckets.length - 1; cI < _cI; cI++) {
             const aI = chainBuckets[cI];
             // are we in HETATM territory?
             if (label_seq_id.valueKind(aI) !== Column.ValueKind.Present) continue;
 
-            makeBuckets(indices, label_seq_id.value, true, aI, chainBuckets[cI + 1]);
+            makeBuckets(indices, label_seq_id.value, { sort: true, start: aI, end: chainBuckets[cI + 1] });
             if (ctx.shouldUpdate) await ctx.update();
         }
         if (ctx.shouldUpdate) await ctx.update();
diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts
index 799bb664363c9dd7d90d11946a0ba0023e49452f..6ade4fe45b03ca1b3642693475aadf7603e2b4e4 100644
--- a/src/mol-model/structure/model/model.ts
+++ b/src/mol-model/structure/model/model.ts
@@ -43,6 +43,8 @@ interface Model extends Readonly<{
         [customName: string]: any
     },
 
+    // TODO: separate properties to "static" (propagated with trajectory) and "dynamic" (computed for each frame separately)
+
     coarseHierarchy: CoarseHierarchy,
     coarseConformation: CoarseConformation
 }> {
@@ -56,6 +58,18 @@ namespace Model {
             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
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/atomic/hierarchy.ts b/src/mol-model/structure/model/properties/atomic/hierarchy.ts
index 4327d22d406451be93e6d9116bb58526cf4fb9d2..9b04d0f366b1d06940ff3ef98d4624c366a1c4c2 100644
--- a/src/mol-model/structure/model/properties/atomic/hierarchy.ts
+++ b/src/mol-model/structure/model/properties/atomic/hierarchy.ts
@@ -49,13 +49,12 @@ export interface AtomicData {
 export interface AtomicSegments {
     residueSegments: Segmentation,
     chainSegments: Segmentation
+    // TODO: include entity segments?
 }
 
 export interface AtomicKeys {
-    // indicate whether the keys form an increasing sequence and within each chain, sequence numbers
-    // are in increasing order.
-    // monotonous sequences enable for example faster secondary structure assignment.
-    isMonotonous: boolean,
+    // 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>,
diff --git a/src/mol-model/structure/model/properties/coarse/hierarchy.ts b/src/mol-model/structure/model/properties/coarse/hierarchy.ts
index fedb463c9e7b015de3485ef2c2a90b2798df71ec..52748b36710e7ca23d373d991a3ebf2630d114f2 100644
--- a/src/mol-model/structure/model/properties/coarse/hierarchy.ts
+++ b/src/mol-model/structure/model/properties/coarse/hierarchy.ts
@@ -9,11 +9,6 @@ import { Column } from 'mol-data/db'
 import { Segmentation } from 'mol-data/int';
 
 export interface CoarsedElementKeys {
-    // indicate whether the keys form an increasing sequence and within each chain, sequence numbers
-    // are in increasing order.
-    // monotonous sequences enable for example faster secondary structure assignment.
-    isMonotonous: boolean,
-
     // assign a key to each element
     chainKey: ArrayLike<number>,
     // assign a key to each element, index to the Model.entities.data table
diff --git a/src/mol-model/structure/model/properties/utils/atomic-keys.ts b/src/mol-model/structure/model/properties/utils/atomic-keys.ts
index 1cfec2f0348e902f39787c3f2c0510687caef256..6280132880b0e7bd77caa1867f98c86754a9f470 100644
--- a/src/mol-model/structure/model/properties/utils/atomic-keys.ts
+++ b/src/mol-model/structure/model/properties/utils/atomic-keys.ts
@@ -48,15 +48,6 @@ function createLookUp(entities: Entities, chain: Map<number, Map<string, number>
     return { findChainKey, findResidueKey };
 }
 
-function checkMonotonous(xs: ArrayLike<number>) {
-    for (let i = 1, _i = xs.length; i < _i; i++) {
-        if (xs[i] < xs[i - 1]) {
-            return false;
-        }
-    }
-    return true;
-}
-
 function missingEntity(k: string) {
     throw new Error(`Missing entity entry for entity id '${k}'.`);
 }
@@ -76,8 +67,6 @@ export function getAtomicKeys(data: AtomicData, entities: Entities, segments: At
 
     const atomSet = Interval.ofBounds(0, data.atoms._rowCount);
 
-    let isMonotonous = true;
-
     const chainsIt = Segmentation.transientSegments(segments.chainSegments, atomSet);
     while (chainsIt.hasNext) {
         const chainSegment = chainsIt.move();
@@ -93,13 +82,9 @@ export function getAtomicKeys(data: AtomicData, entities: Entities, segments: At
 
         const residueMap = getElementSubstructureKeyMap(residueMaps, cKey);
         const residuesIt = Segmentation.transientSegments(segments.residueSegments, atomSet, chainSegment);
-        let last_seq_id = Number.NEGATIVE_INFINITY;
         while (residuesIt.hasNext) {
             const residueSegment = residuesIt.move();
             const rI = residueSegment.index;
-            const seq_id = auth_seq_id.value(rI);
-            if (seq_id < last_seq_id) isMonotonous = false;
-            last_seq_id = seq_id;
             const residueId = getResidueId(label_comp_id.value(rI), auth_seq_id.value(rI), pdbx_PDB_ins_code.value(rI));
             residueKey[rI] = getElementKey(residueMap, residueId, residueCounter);
         }
@@ -108,7 +93,6 @@ export function getAtomicKeys(data: AtomicData, entities: Entities, segments: At
     const { findChainKey, findResidueKey } = createLookUp(entities, chainMaps, residueMaps);
 
     return {
-        isMonotonous: isMonotonous && checkMonotonous(entityKey) && checkMonotonous(chainKey) && checkMonotonous(residueKey),
         residueKey: residueKey,
         chainKey: chainKey,
         entityKey: entityKey,
diff --git a/src/mol-model/structure/model/properties/utils/coarse-keys.ts b/src/mol-model/structure/model/properties/utils/coarse-keys.ts
index 6ef72039a5f3fa35705364b38d24d2e4bef00553..a67d345802a20fe03c4496ea7ee282642989017e 100644
--- a/src/mol-model/structure/model/properties/utils/coarse-keys.ts
+++ b/src/mol-model/structure/model/properties/utils/coarse-keys.ts
@@ -33,15 +33,6 @@ function createLookUp(entities: Entities, chain: Map<number, Map<string, number>
     return { findChainKey };
 }
 
-function checkMonotonous(xs: ArrayLike<number>) {
-    for (let i = 1, _i = xs.length; i < _i; i++) {
-        if (xs[i] < xs[i - 1]) {
-            return false;
-        }
-    }
-    return true;
-}
-
 function missingEntity(k: string) {
     throw new Error(`Missing entity entry for entity id '${k}'.`);
 }
@@ -75,7 +66,6 @@ export function getCoarseKeys(data: CoarseElementData, modelIndex: (id: number)
     const { findChainKey } = createLookUp(entities, chainMaps);
 
     return {
-        isMonotonous: checkMonotonous(entityKey) && checkMonotonous(chainKey),
         chainKey: chainKey,
         entityKey: entityKey,
         modelKey: modelKey,
diff --git a/src/mol-model/structure/structure/element.ts b/src/mol-model/structure/structure/element.ts
index af6a4cdb275202f29a794c3a4803a0d60359bd2b..bacc0c4cd72aaefa9eb1f263d3bf29b61c5349e3 100644
--- a/src/mol-model/structure/structure/element.ts
+++ b/src/mol-model/structure/structure/element.ts
@@ -23,8 +23,8 @@ namespace Element {
     export function createEmptyArray(n: number): Element[] { return new Float64Array(n) as any; }
 
     /** All the information required to access element properties */
-    export interface Location {
-        unit: Unit,
+    export interface Location<U = Unit> {
+        unit: U,
         /** Index into element (atomic/coarse) properties of unit.model */
         element: number
     }
@@ -40,6 +40,15 @@ namespace Element {
 
     export function property<T>(p: Property<T>) { return p; }
 
+    function _wrongUnitKind(kind: string) { throw new Error(`Property only available for ${kind} models.`); }
+    export function atomicProperty<T>(p: (location: Location<Unit.Atomic>) => T) {
+        return property(l => Unit.isAtomic(l.unit) ? p(l as Location<Unit.Atomic>) : _wrongUnitKind('atomic') );
+    }
+
+    export function coarseProperty<T>(p: (location: Location<Unit.Spheres | Unit.Gaussians>) => T) {
+        return property(l => Unit.isCoarse(l.unit) ? p(l as Location<Unit.Spheres | Unit.Gaussians>) : _wrongUnitKind('coarse') );
+    }
+
     /** Represents multiple element index locations */
     export interface Loci {
         readonly kind: 'element-loci',