diff --git a/src/mol-data/util/equivalence-classes.ts b/src/mol-data/util/equivalence-classes.ts
index e40456499bcc6315d01d6ce17bca83ccd243323a..cfad84f041f3daeffaedb9b340dde3e3801a8d52 100644
--- a/src/mol-data/util/equivalence-classes.ts
+++ b/src/mol-data/util/equivalence-classes.ts
@@ -17,6 +17,7 @@ class EquivalenceClassesImpl<K, V> {
         return { id, keys, value };
     }
 
+    // Return the group representative.
     add(key: K, a: V) {
         const hash = this.getHash(a);
         if (this.byHash.has(hash)) {
@@ -25,16 +26,16 @@ class EquivalenceClassesImpl<K, V> {
                 const group = groups[i];
                 if (this.areEqual(a, group.value)) {
                     group.keys[group.keys.length] = key;
-                    return group.id;
+                    return group.value;
                 }
             }
             const group = this.createGroup(key, a);
             groups[groups.length] = group;
-            return group.id;
+            return group.value;
         } else {
             const group = this.createGroup(key, a);
             this.byHash.set(hash, [group]);
-            return group.id;
+            return group.value;
         }
     }
 
diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts
index 9caee1437f431b73ac4359d70aadee7ad8f1c966..774ea8b077f18059c09e50f7563221a9392d4540 100644
--- a/src/mol-geo/representation/structure/index.ts
+++ b/src/mol-geo/representation/structure/index.ts
@@ -5,7 +5,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Structure, StructureSymmetry } from 'mol-model/structure';
+import { Structure, StructureSymmetry, Unit } from 'mol-model/structure';
 import { Task } from 'mol-task'
 import { RenderObject } from 'mol-gl/scene';
 import { Representation, RepresentationProps } from '..';
@@ -14,7 +14,7 @@ import { Representation, RepresentationProps } from '..';
 
 export interface UnitsRepresentation<P> {
     renderObjects: ReadonlyArray<RenderObject>
-    create: (group: StructureSymmetry.UnitGroup, props: P) => Task<void>
+    create: (group: Unit.SymmetryGroup, props: P) => Task<void>
     update: (props: P) => Task<boolean>
 }
 
@@ -26,7 +26,7 @@ export interface StructureRepresentation<P extends RepresentationProps = {}> ext
 
 interface GroupRepresentation<T> {
     repr: UnitsRepresentation<T>
-    group: StructureSymmetry.UnitGroup
+    group: Unit.SymmetryGroup
 }
 
 export function StructureRepresentation<P>(reprCtor: () => UnitsRepresentation<P>): StructureRepresentation<P> {
diff --git a/src/mol-geo/representation/structure/point.ts b/src/mol-geo/representation/structure/point.ts
index becee521ffe39a131950a7aa6cffdb3e908e3f22..5828e518c5fe5f325a0fcdf477c28ef83150eea9 100644
--- a/src/mol-geo/representation/structure/point.ts
+++ b/src/mol-geo/representation/structure/point.ts
@@ -7,7 +7,7 @@
 
 import { ValueCell } from 'mol-util/value-cell'
 import { createPointRenderObject, RenderObject, PointRenderObject } from 'mol-gl/scene'
-import { Unit, Element, StructureSymmetry } from 'mol-model/structure';
+import { Unit, Element } from 'mol-model/structure';
 import { Task } from 'mol-task'
 import { fillSerial } from 'mol-gl/renderable/util';
 
@@ -55,7 +55,7 @@ export default function Point(): UnitsRepresentation<PointProps> {
 
     return {
         renderObjects,
-        create(group: StructureSymmetry.UnitGroup, props: PointProps = {}) {
+        create(group: Unit.SymmetryGroup, props: PointProps = {}) {
             return Task.create('Point.create', async ctx => {
                 renderObjects.length = 0 // clear
                 curProps = { ...DefaultPointProps, ...props }
diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts
index 5dd62f7f91ec000627ad5ac33bcb99bb9b35c84e..1946f810388add6a316106575e1393a5c3798d44 100644
--- a/src/mol-geo/representation/structure/spacefill.ts
+++ b/src/mol-geo/representation/structure/spacefill.ts
@@ -10,7 +10,7 @@ import { ValueCell } from 'mol-util/value-cell'
 import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/scene'
 // import { createColorTexture } from 'mol-gl/util';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra'
-import { Unit, Element, Queries, StructureSymmetry } from 'mol-model/structure';
+import { Unit, Element, Queries } from 'mol-model/structure';
 import { UnitsRepresentation } from './index';
 import { Task } from 'mol-task'
 import { MeshBuilder } from '../../shape/mesh-builder';
@@ -77,7 +77,7 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
 
     return {
         renderObjects,
-        create(group: StructureSymmetry.UnitGroup, props: SpacefillProps = {}) {
+        create(group: Unit.SymmetryGroup, props: SpacefillProps = {}) {
             return Task.create('Spacefill.create', async ctx => {
                 renderObjects.length = 0 // clear
 
diff --git a/src/mol-geo/representation/structure/utils.ts b/src/mol-geo/representation/structure/utils.ts
index 920159d1a807b6435b58d590d1f5f0c9ae808092..c7cd81d17c60d684d5c7bb485dc91d25825c6bba 100644
--- a/src/mol-geo/representation/structure/utils.ts
+++ b/src/mol-geo/representation/structure/utils.ts
@@ -5,7 +5,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { StructureSymmetry } from 'mol-model/structure';
+import { Unit } from 'mol-model/structure';
 import { Mat4 } from 'mol-math/linear-algebra'
 
 import { createUniformColor } from '../../util/color-data';
@@ -15,7 +15,7 @@ import VertexMap from '../../shape/vertex-map';
 import { ColorTheme, SizeTheme } from '../../theme';
 import { elementIndexColorData, elementSymbolColorData, instanceIndexColorData, chainIdColorData } from '../../theme/structure/color';
 
-export function createTransforms({ units }: StructureSymmetry.UnitGroup) {
+export function createTransforms({ units }: Unit.SymmetryGroup) {
     const unitCount = units.length
     const transforms = new Float32Array(unitCount * 16)
     for (let i = 0; i < unitCount; i++) {
@@ -24,7 +24,7 @@ export function createTransforms({ units }: StructureSymmetry.UnitGroup) {
     return transforms
 }
 
-export function createColors(group: StructureSymmetry.UnitGroup, vertexMap: VertexMap, props: ColorTheme) {
+export function createColors(group: Unit.SymmetryGroup, vertexMap: VertexMap, props: ColorTheme) {
     switch (props.name) {
         case 'atom-index':
             return elementIndexColorData({ group, vertexMap })
@@ -39,7 +39,7 @@ export function createColors(group: StructureSymmetry.UnitGroup, vertexMap: Vert
     }
 }
 
-export function createSizes(group: StructureSymmetry.UnitGroup, vertexMap: VertexMap, props: SizeTheme) {
+export function createSizes(group: Unit.SymmetryGroup, vertexMap: VertexMap, props: SizeTheme) {
     switch (props.name) {
         case 'uniform':
             return createUniformSize(props)
diff --git a/src/mol-geo/theme/structure/color/index.ts b/src/mol-geo/theme/structure/color/index.ts
index f03f0e9e5ef9038ad699c0773d311955eb64fd38..9e0d0318dc439923c33f6f164e660357520e8b0a 100644
--- a/src/mol-geo/theme/structure/color/index.ts
+++ b/src/mol-geo/theme/structure/color/index.ts
@@ -4,11 +4,11 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { StructureSymmetry } from 'mol-model/structure';
+import { Unit } from 'mol-model/structure';
 import VertexMap from '../../../shape/vertex-map';
 
 export interface StructureColorDataProps {
-    group: StructureSymmetry.UnitGroup,
+    group: Unit.SymmetryGroup,
     vertexMap: VertexMap
 }
 
diff --git a/src/mol-geo/theme/structure/size/index.ts b/src/mol-geo/theme/structure/size/index.ts
index 325633c19204e2f927acacf8f753dfe28ba11cfc..0c18ca93ee04603b9334f682790ff56e64b21591 100644
--- a/src/mol-geo/theme/structure/size/index.ts
+++ b/src/mol-geo/theme/structure/size/index.ts
@@ -4,11 +4,11 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { StructureSymmetry } from 'mol-model/structure';
+import { Unit } from 'mol-model/structure';
 import VertexMap from '../../../shape/vertex-map';
 
 export interface StructureSizeDataProps {
-    group: StructureSymmetry.UnitGroup,
+    group: Unit.SymmetryGroup,
     vertexMap: VertexMap
 }
 
diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts
index 8c7c500ab53c09b25d44b6f54c7705b018c59415..c2a2ecd38aea3292c2adc63de607733040cd0aa7 100644
--- a/src/mol-model/structure/structure/structure.ts
+++ b/src/mol-model/structure/structure/structure.ts
@@ -12,6 +12,7 @@ import { sortArray, sort, arraySwap, hash1 } from 'mol-data/util';
 import Element from './element'
 import Unit from './unit'
 import { StructureLookup3D } from './util/lookup3d';
+import StructureSymmetry from './symmetry';
 
 class Structure {
     readonly unitMap: IntMap<Unit>;
@@ -184,6 +185,8 @@ namespace Structure {
             const newUnits: Unit[] = [];
             sortArray(this.ids);
 
+            const symmGroups = StructureSymmetry.UnitEquivalenceBuilder();
+
             for (let i = 0, _i = this.ids.length; i < _i; i++) {
                 const id = this.ids[i];
                 const parent = this.parent.unitMap.get(id);
@@ -194,13 +197,16 @@ namespace Structure {
                 // if the length is the same, just copy the old unit.
                 if (unit.length === parent.elements.length) {
                     newUnits[newUnits.length] = parent;
+                    symmGroups.add(parent.id, parent);
                     continue;
                 }
 
                 if (!this.isSorted && l > 1) sortArray(unit);
 
-                // TODO: checking if two units are equal in the same symmetry group to reuse computed properties.
-                newUnits[newUnits.length] = parent.getChild(SortedArray.ofSortedArray(unit));
+                let child = parent.getChild(SortedArray.ofSortedArray(unit));
+                const pivot = symmGroups.add(child.id, child);
+                if (child !== pivot) child = pivot.applyOperator(child.id, child.conformation.operator, true);
+                newUnits[newUnits.length] = child;
             }
 
             return create(newUnits);
diff --git a/src/mol-model/structure/structure/symmetry.ts b/src/mol-model/structure/structure/symmetry.ts
index b78827a9c7a7c4f1aec81c47c438b3f560ad5df0..2bf71bda6742746edf52621bb930f204ac70418d 100644
--- a/src/mol-model/structure/structure/symmetry.ts
+++ b/src/mol-model/structure/structure/symmetry.ts
@@ -10,13 +10,9 @@ import { ModelSymmetry } from '../model'
 import { Task } from 'mol-task';
 import { SortedArray } from 'mol-data/int';
 import Unit from './unit';
-import { EquivalenceClasses } from 'mol-data/util';
+import { EquivalenceClasses, hash2 } from 'mol-data/util';
 
 namespace StructureSymmetry {
-    // Units that have the same elements but differ with operator only.
-    export type UnitGroup = { readonly elements: SortedArray, readonly units: ReadonlyArray<Unit> }
-    export type TransformGroups = ReadonlyArray<UnitGroup>
-
     export function buildAssembly(structure: Structure, name: string) {
         return Task.create('Build Symmetry', async ctx => {
             const models = Structure.getModels(structure);
@@ -45,25 +41,26 @@ namespace StructureSymmetry {
         });
     }
 
-    export function getTransformGroups(s: Structure): StructureSymmetry.TransformGroups {
-        // group everything by the "invariantId"
-        const invariantGroups = EquivalenceClasses<number, Unit>(u => u.invariantId, (a, b) => a.invariantId === b.invariantId && a.model.id === b.model.id);
-        for (const u of s.units) invariantGroups.add(u.id, u);
+    function hashUnit(u: Unit) {
+        return hash2(u.invariantId, SortedArray.hashCode(u.elements));
+    }
 
-        const ret: UnitGroup[] = [];
-        // group everything by the "element array"
-        for (const group of invariantGroups.groups) {
-            const setGrouping = EquivalenceClasses<number, Unit>(u => SortedArray.hashCode(u.elements), (a, b) => SortedArray.areEqual(a.elements, b.elements));
+    function areUnitsEquivalent(a: Unit, b: Unit) {
+        return a.invariantId === b.invariantId && a.model.id === b.model.id && SortedArray.areEqual(a.elements, b.elements);
+    }
 
-            for (const id of group) {
-                const unit = s.unitMap.get(id);
-                setGrouping.add(unit.id, unit);
-            }
+    export function UnitEquivalenceBuilder() {
+        return EquivalenceClasses<number, Unit>(hashUnit, areUnitsEquivalent);
+    }
 
-            for (const eqUnits of setGrouping.groups) {
-                const first = s.unitMap.get(eqUnits[0]);
-                ret.push({ elements: first.elements, units: eqUnits.map(id => s.unitMap.get(id)) });
-            }
+    export function getTransformGroups(s: Structure): ReadonlyArray<Unit.SymmetryGroup> {
+        const groups = UnitEquivalenceBuilder();
+        for (const u of s.units) groups.add(u.id, u);
+
+        const ret: Unit.SymmetryGroup[] = [];
+        for (const eqUnits of groups.groups) {
+            const first = s.unitMap.get(eqUnits[0]);
+            ret.push({ elements: first.elements, units: eqUnits.map(id => s.unitMap.get(id)) });
         }
 
         return ret;
diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts
index 87bff567208083f4be090dd0f90224fa5b310195..15a85c6462503ff99715ce44c60b5319fab5123c 100644
--- a/src/mol-model/structure/structure/unit.ts
+++ b/src/mol-model/structure/structure/unit.ts
@@ -32,9 +32,8 @@ namespace Unit {
         }
     }
 
-    export function applyOperator(id: number, unit: Unit, operator: SymmetryOperator): Unit {
-        return create(id, unit.kind, unit.model, SymmetryOperator.compose(unit.conformation.operator, operator), unit.elements);
-    }
+    // A group of units that differ only by symmetry operators.
+    export type SymmetryGroup = { readonly elements: SortedArray, readonly units: ReadonlyArray<Unit> }
 
     export interface Base {
         readonly id: number,
@@ -45,7 +44,7 @@ namespace Unit {
         readonly conformation: SymmetryOperator.ArrayMapping,
 
         getChild(elements: SortedArray): Unit,
-        applyOperator(id: number, operator: SymmetryOperator): Unit,
+        applyOperator(id: number, operator: SymmetryOperator, dontCompose?: boolean /* = false */): Unit,
 
         readonly lookup3d: Lookup3D
     }
@@ -77,8 +76,8 @@ namespace Unit {
             return new Atomic(this.id, this.invariantId, this.model, elements, this.conformation);
         }
 
-        applyOperator(id: number, operator: SymmetryOperator): Unit {
-            const op = SymmetryOperator.compose(this.conformation.operator, operator);
+        applyOperator(id: number, operator: SymmetryOperator, dontCompose = false): Unit {
+            const op = dontCompose ? operator : SymmetryOperator.compose(this.conformation.operator, operator);
             return new Atomic(id, this.invariantId, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomSiteConformation));
         }
 
@@ -129,8 +128,8 @@ namespace Unit {
             return createCoarse(this.id, this.invariantId, this.model, this.kind, this.sites, elements, this.conformation);
         }
 
-        applyOperator(id: number, operator: SymmetryOperator): Unit {
-            const op = SymmetryOperator.compose(this.conformation.operator, operator);
+        applyOperator(id: number, operator: SymmetryOperator, dontCompose = false): Unit {
+            const op = dontCompose ? operator : SymmetryOperator.compose(this.conformation.operator, operator);
             return createCoarse(id, this.invariantId, this.model, this.kind, this.sites, this.elements, SymmetryOperator.createMapping(op, this.sites));
         }
 
diff --git a/src/perf-tests/structure.ts b/src/perf-tests/structure.ts
index dde9d8f7316ab84ee47203382f29479ff66b9ef9..d8e1037a964b2ba6083c2e33ab07365c1c1abdbd 100644
--- a/src/perf-tests/structure.ts
+++ b/src/perf-tests/structure.ts
@@ -292,8 +292,13 @@ export namespace PropertyAccess {
     export async function testAssembly(id: string, s: Structure) {
         console.time('assembly')
         const a = await Run(StructureSymmetry.buildAssembly(s, '1'));
+        //const auth_comp_id = Q.props.residue.auth_comp_id;
+        //const q1 = Query(Q.generators.atoms({ residueTest: l => auth_comp_id(l) === 'ALA' }));
+        //const alas = await query(q1, a);
+
         console.timeEnd('assembly')
         fs.writeFileSync(`${DATA_DIR}/${id}_assembly.bcif`, to_mmCIF(id, a, true));
+        //fs.writeFileSync(`${DATA_DIR}/${id}_assembly.bcif`, to_mmCIF(id, Selection.unionStructure(alas), true));
         console.log('exported');
     }
 
@@ -404,8 +409,8 @@ export namespace PropertyAccess {
             //.add('test q', () => q1(structures[0]))
             //.add('test q', () => q(structures[0]))
             .add('test int', () => sumProperty(structures[0], l => col(l.element)))
-            .add('test q1', async () => await q1(structures[0]))
-            .add('test q3', async () => await q3(structures[0]))
+            .add('test q1', async () => await query(q1, structures[0]))
+            .add('test q3', async () => await query(q3, structures[0]))
             // .add('sum residue', () => sumPropertyResidue(structures[0], l => l.unit.hierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.atom])))
 
             // .add('baseline', () =>  baseline(models[0]))