diff --git a/src/mol-data/int/impl/ordered-set.ts b/src/mol-data/int/impl/ordered-set.ts
index baa6bef0ea643ab3d72601cf5240dc84caa9682e..5c48cb5b8407a2841a505477667c8a12852a6a5a 100644
--- a/src/mol-data/int/impl/ordered-set.ts
+++ b/src/mol-data/int/impl/ordered-set.ts
@@ -132,8 +132,8 @@ function unionII(a: I, b: I) {
     if (I.areEqual(a, b)) return a;
 
     const sizeA = I.size(a), sizeB = I.size(b);
-    if (!sizeA) return b;
     if (!sizeB) return a;
+    if (!sizeA) return b;
     const minA = I.min(a), minB = I.min(b);
     if (areRangesIntersecting(a, b)) return I.ofRange(Math.min(minA, minB), Math.max(I.max(a), I.max(b)));
     let lSize, lMin, rSize, rMin;
diff --git a/src/mol-model/structure/_spec/atom-set.1.spec.ts b/src/mol-model/structure/_spec/atom-set.1.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..917d69bf59ff94fd416e580d9ae8aa690400eb5c
--- /dev/null
+++ b/src/mol-model/structure/_spec/atom-set.1.spec.ts
@@ -0,0 +1,193 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { OrderedSet } from 'mol-data/int'
+import AtomSet from '../structure/atom/set.1'
+import Atom from '../structure/atom'
+import AtomGroup from '../structure/atom/group'
+
+describe('atom set', () => {
+    const p = (i: number, j: number) => Atom.create(i, j);
+
+    function setToPairs(set: AtomSet): ArrayLike<Atom> {
+        const ret: Atom[] = [];
+        const it = AtomSet.atoms(set);
+        while (it.hasNext) {
+            ret[ret.length] = it.move();
+        }
+        return ret;
+    }
+
+    it('singleton pair', () => {
+        const set = AtomSet.ofAtoms([p(10, 11)], AtomSet.Empty);
+        expect(setToPairs(set)).toEqual([p(10, 11)]);
+        expect(AtomSet.atomHas(set, p(10, 11))).toBe(true);
+        expect(AtomSet.atomHas(set, p(11, 11))).toBe(false);
+        expect(AtomSet.atomGetAt(set, 0)).toBe(p(10, 11));
+        expect(AtomSet.atomCount(set)).toBe(1);
+    });
+
+    it('multi', () => {
+        const gen = AtomSet.Generator();
+        gen.add(1, AtomGroup.createNew(OrderedSet.ofSortedArray([4, 6, 7])));
+        gen.add(3, AtomGroup.createNew(OrderedSet.ofRange(0, 1)));
+        const set = gen.getSet();
+        const ret = [p(1, 4), p(1, 6), p(1, 7), p(3, 0), p(3, 1)];
+        expect(AtomSet.atomCount(set)).toBe(ret.length);
+        expect(setToPairs(set)).toEqual([p(1, 4), p(1, 6), p(1, 7), p(3, 0), p(3, 1)]);
+        expect(AtomSet.atomHas(set, p(10, 11))).toBe(false);
+        expect(AtomSet.atomHas(set, p(3, 0))).toBe(true);
+        expect(AtomSet.atomHas(set, p(1, 7))).toBe(true);
+        for (let i = 0; i < AtomSet.atomCount(set); i++) {
+            expect(Atom.areEqual(AtomSet.atomGetAt(set, i), ret[i])).toBe(true);
+        }
+    });
+
+    it('template', () => {
+        const template = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty)
+        const gen = AtomSet.TemplateGenerator(template);
+        gen.add(0, AtomGroup.createNew(OrderedSet.ofSortedArray([1, 2, 6])));
+        gen.add(1, AtomGroup.createNew(OrderedSet.ofSingleton(3)));
+        const set = gen.getSet();
+
+        expect(AtomSet.unitGetById(set, 0)).toBe(AtomSet.unitGetById(template, 0));
+        expect(AtomSet.unitGetById(set, 1)).toBe(AtomSet.unitGetById(template, 1));
+        expect(set).toBe(template);
+    });
+
+    it('template 1', () => {
+        const template = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty)
+        const gen = AtomSet.TemplateGenerator(template);
+        gen.add(0, AtomGroup.createNew(OrderedSet.ofSortedArray([1, 2, 6])));
+        gen.add(1, AtomGroup.createNew(OrderedSet.ofSingleton(4)));
+        const set = gen.getSet();
+
+        expect(AtomSet.unitGetById(set, 0)).toBe(AtomSet.unitGetById(template, 0));
+        expect(AtomSet.unitGetById(set, 1) === AtomSet.unitGetById(template, 1)).toBe(false);
+        expect(set === template).toBe(false);
+    });
+
+    it('template union', () => {
+        const template = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty)
+
+        const p13 = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
+        const p01 = AtomSet.ofAtoms([p(0, 1)], AtomSet.Empty);
+        const p02 = AtomSet.ofAtoms([p(0, 2)], AtomSet.Empty);
+        const p06 = AtomSet.ofAtoms([p(0, 6)], AtomSet.Empty);
+
+        const u0 = AtomSet.union([p01, p02, p06], template);
+        const u1 = AtomSet.union([p01, p02, p06, p13], template);
+        expect(AtomSet.unitGetById(u0, 0)).toBe(AtomSet.unitGetById(template, 0));
+        expect(AtomSet.unitGetById(u1, 0)).toBe(AtomSet.unitGetById(template, 0));
+        expect(AtomSet.unitGetById(u1, 1)).toBe(AtomSet.unitGetById(template, 1));
+        expect(u1).toBe(template);
+    });
+
+    it('element at / index of', () => {
+        const control: Atom[] = [];
+        const gen = AtomSet.Generator();
+        for (let i = 1; i < 10; i++) {
+            const set = [];
+            for (let j = 1; j < 7; j++) {
+                control[control.length] = p(i * i, j * j + 1);
+                set[set.length] = j * j + 1;
+            }
+            gen.add(i * i, AtomGroup.createNew(OrderedSet.ofSortedArray(set)));
+        }
+        const ms = gen.getSet();
+        for (let i = 0; i < control.length; i++) {
+            expect(Atom.areEqual(AtomSet.atomGetAt(ms, i), control[i])).toBe(true);
+        }
+
+        for (let i = 0; i < control.length; i++) {
+            expect(AtomSet.atomIndexOf(ms, control[i])).toBe(i);
+        }
+    });
+
+    it('packed pairs', () => {
+        const set = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
+        expect(setToPairs(set)).toEqual([p(0, 1), p(0, 2), p(0, 6), p(1, 3)]);
+    });
+
+    it('equality', () => {
+        const a = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const b = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const c = AtomSet.ofAtoms([p(1, 3), p(0, 4), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const d = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
+        const e = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
+        const f = AtomSet.ofAtoms([p(3, 3)], AtomSet.Empty);
+
+        expect(AtomSet.areEqual(a, a)).toBe(true);
+        expect(AtomSet.areEqual(a, b)).toBe(true);
+        expect(AtomSet.areEqual(a, c)).toBe(false);
+        expect(AtomSet.areEqual(a, d)).toBe(false);
+        expect(AtomSet.areEqual(d, d)).toBe(true);
+        expect(AtomSet.areEqual(d, e)).toBe(true);
+        expect(AtomSet.areEqual(d, f)).toBe(false);
+    });
+
+    it('are intersecting', () => {
+        const a = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const b = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const c = AtomSet.ofAtoms([p(1, 3), p(0, 4), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const d = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
+        const e = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
+        const f = AtomSet.ofAtoms([p(3, 3)], AtomSet.Empty);
+        const g = AtomSet.ofAtoms([p(10, 3), p(8, 1), p(7, 6), p(3, 2)], AtomSet.Empty);
+
+        expect(AtomSet.areIntersecting(a, a)).toBe(true);
+        expect(AtomSet.areIntersecting(a, b)).toBe(true);
+        expect(AtomSet.areIntersecting(a, c)).toBe(true);
+        expect(AtomSet.areIntersecting(a, d)).toBe(true);
+        expect(AtomSet.areIntersecting(a, g)).toBe(false);
+        expect(AtomSet.areIntersecting(d, d)).toBe(true);
+        expect(AtomSet.areIntersecting(d, e)).toBe(true);
+        expect(AtomSet.areIntersecting(d, f)).toBe(false);
+    });
+
+    it('intersection', () => {
+        const a = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const b = AtomSet.ofAtoms([p(10, 3), p(0, 1), p(0, 6), p(4, 2)], AtomSet.Empty);
+        const c = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
+        const d = AtomSet.ofAtoms([p(2, 3)], AtomSet.Empty);
+        expect(AtomSet.intersect(a, a)).toBe(a);
+        expect(setToPairs(AtomSet.intersect(a, b))).toEqual([p(0, 1), p(0, 6)]);
+        expect(setToPairs(AtomSet.intersect(a, c))).toEqual([p(1, 3)]);
+        expect(setToPairs(AtomSet.intersect(c, d))).toEqual([]);
+    });
+
+    it('subtract', () => {
+        const a = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const a1 = AtomSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], AtomSet.Empty);
+        const b = AtomSet.ofAtoms([p(10, 3), p(0, 1), p(0, 6), p(4, 2)], AtomSet.Empty);
+        const c = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
+        const d = AtomSet.ofAtoms([p(2, 3)], AtomSet.Empty);
+        const e = AtomSet.ofAtoms([p(0, 2)], AtomSet.Empty);
+        expect(setToPairs(AtomSet.subtract(a, a))).toEqual([]);
+        expect(setToPairs(AtomSet.subtract(a, a1))).toEqual([]);
+        expect(setToPairs(AtomSet.subtract(a, b))).toEqual([p(0, 2), p(1, 3)]);
+        expect(setToPairs(AtomSet.subtract(c, d))).toEqual([p(1, 3)]);
+        expect(setToPairs(AtomSet.subtract(a, c))).toEqual([p(0, 1), p(0, 2), p(0, 6)]);
+        expect(setToPairs(AtomSet.subtract(c, a))).toEqual([]);
+        expect(setToPairs(AtomSet.subtract(d, a))).toEqual([p(2, 3)]);
+        expect(setToPairs(AtomSet.subtract(a, e))).toEqual([p(0, 1), p(0, 6), p(1, 3)]);
+    });
+
+    it('union', () => {
+        const a = AtomSet.ofAtoms([p(1, 3), p(0, 1)], AtomSet.Empty);
+        const a1 = AtomSet.ofAtoms([p(1, 3), p(0, 1)], AtomSet.Empty);
+        const b = AtomSet.ofAtoms([p(10, 3), p(0, 1)], AtomSet.Empty);
+        const c = AtomSet.ofAtoms([p(1, 3)], AtomSet.Empty);
+        const d = AtomSet.ofAtoms([p(2, 3)], AtomSet.Empty);
+        expect(AtomSet.union([a], AtomSet.Empty)).toBe(a);
+        expect(AtomSet.union([a, a], AtomSet.Empty)).toBe(a);
+        expect(setToPairs(AtomSet.union([a, a], AtomSet.Empty))).toEqual([p(0, 1), p(1, 3)]);
+        expect(setToPairs(AtomSet.union([a, a1], AtomSet.Empty))).toEqual([p(0, 1), p(1, 3)]);
+        expect(setToPairs(AtomSet.union([a, b], AtomSet.Empty))).toEqual([p(0, 1), p(1, 3), p(10, 3)]);
+        expect(setToPairs(AtomSet.union([c, d], AtomSet.Empty))).toEqual([p(1, 3), p(2, 3)]);
+        expect(setToPairs(AtomSet.union([a, b, c, d], AtomSet.Empty))).toEqual([p(0, 1), p(1, 3), p(2, 3), p(10, 3)]);
+    });
+});
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/atom/impl/builder.ts b/src/mol-model/structure/structure/atom/impl/builder.ts
index 798283debda5fb92b0de7978f8f90f38059123cf..cf9d2b8be70a5429c293b226da4eb723499e7a7a 100644
--- a/src/mol-model/structure/structure/atom/impl/builder.ts
+++ b/src/mol-model/structure/structure/atom/impl/builder.ts
@@ -45,7 +45,7 @@ export class Builder {
             const l = unit.length;
             if (!this.sorted && l > 1) sortArray(unit);
 
-            const set = l === 1 ? OrderedSet.ofSingleton(unit[0]) : OrderedSet.ofSortedArray(unit);
+            const set = OrderedSet.ofSortedArray(unit);
             const parentSet = AtomSet.unitGetById(this.parent, k);
             if (OrderedSet.areEqual(set, parentSet)) {
                 generator.add(k, parentSet);
diff --git a/src/mol-model/structure/structure/atom/impl/set.1.ts b/src/mol-model/structure/structure/atom/impl/set.1.ts
index 86b48af759e7d0a738c74f80e32400aa91d40e6e..34ec544afa3b1005a02b0cbeb592cdacf5eadd5d 100644
--- a/src/mol-model/structure/structure/atom/impl/set.1.ts
+++ b/src/mol-model/structure/structure/atom/impl/set.1.ts
@@ -218,18 +218,17 @@ export class TemplateAtomSetGenerator {
     add(unit: number, group: AtomGroup) {
         if (AtomGroup.size(group) === 0) return;
         this.keys[this.keys.length] = unit;
-        const templ = this.templateGroups.get(unit);
-        if (AtomGroup.areEqual(templ, group)) {
-            this.groups.set(unit, templ);
+        let t: AtomGroup;
+        if (this.templateGroups.has(unit) && AtomGroup.areEqual(t = this.templateGroups.get(unit), group)) {
+            this.groups.set(unit, t);
             this.equalGroups++;
         } else {
             this.groups.set(unit, group);
         }
-
     }
 
     getSet(): AtomSetImpl {
-        if (this.equalGroups === this.template.keys.length) {
+        if (this.equalGroups === this.template.keys.length && this.equalGroups === this.keys.length) {
             return this.template;
         }
         return create(this.keys, this.groups);
@@ -401,16 +400,15 @@ function findUnion(sets: ArrayLike<AtomSetImpl>, template: AtomSetImpl) {
 
 function normalizeUnion(keys: number[], groups: IntMap.Mutable<AtomGroup>, template: AtomSetImpl) {
     let equalCount = 0;
-    let tg = template.groups;
+    let tg = template.groups, a: AtomGroup, t: AtomGroup;
     for (let i = 0, _i = keys.length; i < _i; i++) {
         const k = keys[i];
-        const a = groups.get(k), t = tg.get(k);
-        if (AtomGroup.areEqual(a, t)) {
+        if (tg.has(k) && AtomGroup.areEqual(a = groups.get(k), t = tg.get(k))) {
             groups.set(k, t);
             equalCount++;
         }
     }
-    return equalCount === template.keys.length ? template : create(keys, groups);
+    return equalCount === template.keys.length && equalCount === keys.length ? template : create(keys, groups);
 }
 
 function unionInto(keys: number[], groups: IntMap.Mutable<AtomGroup>, a: AtomSetImpl) {
@@ -421,6 +419,7 @@ function unionInto(keys: number[], groups: IntMap.Mutable<AtomGroup>, a: AtomSet
         if (groups.has(k)) {
             groups.set(k, AtomGroup.union(aG.get(k), groups.get(k)))
         } else {
+            keys[keys.length] = k;
             groups.set(k, aG.get(k));
         }
     }
diff --git a/src/mol-model/structure/structure/atom/set.1.ts b/src/mol-model/structure/structure/atom/set.1.ts
new file mode 100644
index 0000000000000000000000000000000000000000..16431f544f25461ae555b1d3619e67c8adf5e48b
--- /dev/null
+++ b/src/mol-model/structure/structure/atom/set.1.ts
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { SortedArray, Iterator } from 'mol-data/int'
+import Atom from '../atom'
+import AtomGroup from './group'
+import * as Impl from './impl/set.1'
+import * as Builders from './impl/builder'
+
+/** A map-like representation of grouped atom set */
+namespace AtomSet {
+    export const Empty: AtomSet = Impl.Empty as any;
+
+    export const ofAtoms: (atoms: ArrayLike<Atom>, template: AtomSet) => AtomSet = Impl.ofAtoms as any;
+
+    export const unitCount: (set: AtomSet) => number = Impl.keyCount as any;
+    export const unitIds: (set: AtomSet) => SortedArray = Impl.getKeys as any;
+    export const unitHas: (set: AtomSet, id: number) => boolean = Impl.hasKey as any;
+    export const unitGetId: (set: AtomSet, i: number) => number = Impl.getKey as any;
+
+    export const unitGetById: (set: AtomSet, key: number) => AtomGroup = Impl.getByKey as any;
+    export const unitGetByIndex: (set: AtomSet, i: number) => AtomGroup = Impl.getByIndex as any;
+
+    export const atomCount: (set: AtomSet) => number = Impl.size as any;
+    export const atomHas: (set: AtomSet, x: Atom) => boolean = Impl.hasAtom as any;
+    export const atomIndexOf: (set: AtomSet, x: Atom) => number = Impl.indexOf as any;
+    export const atomGetAt: (set: AtomSet, i: number) => Atom = Impl.getAt as any;
+    export const atoms: (set: AtomSet) => Iterator<Atom> = Impl.values as any;
+
+    export const hashCode: (set: AtomSet) => number = Impl.hashCode as any;
+    export const areEqual: (a: AtomSet, b: AtomSet) => boolean = Impl.areEqual as any;
+    export const areIntersecting: (a: AtomSet, b: AtomSet) => boolean = Impl.areIntersecting as any;
+
+    export const union: (sets: AtomSet[], template: AtomSet) => AtomSet = Impl.unionMany as any;
+    export const intersect: (a: AtomSet, b: AtomSet) => AtomSet = Impl.intersect as any;
+    export const subtract: (a: AtomSet, b: AtomSet) => AtomSet = Impl.subtract as any;
+
+    export type Builder = Builders.Builder
+    export const LinearBuilder = Builders.LinearBuilder
+    export const UnsortedBuilder = Builders.UnsortedBuilder
+
+    export interface Generator { add(unit: number, set: AtomGroup): void, getSet(): AtomSet }
+    export const Generator: () => Generator = Impl.Generator as any
+    export const TemplateGenerator: (template: AtomSet) => Generator = Impl.TemplateGenerator as any
+
+    // TODO: bounding sphere
+    // TODO: distance, areWithIn?
+    // TODO: check connected
+    // TODO: add "parent" property? how to avoid using too much memory? Transitive parents? Parent unlinking?
+}
+
+interface AtomSet { '@type': 'atom-set' | Atom['@type'] }
+
+export default AtomSet
\ No newline at end of file