diff --git a/src/mol-model/structure/model/properties/symmetry.ts b/src/mol-model/structure/model/properties/symmetry.ts
index 3b2659e9e49541c16c1e47f8ae229905a0d1ca92..883c3594402cdcf5df54b9698b9765ced253b1a4 100644
--- a/src/mol-model/structure/model/properties/symmetry.ts
+++ b/src/mol-model/structure/model/properties/symmetry.ts
@@ -44,7 +44,10 @@ export namespace Assembly {
 interface ModelSymmetry {
     readonly assemblies: ReadonlyArray<Assembly>,
     readonly spacegroup: Spacegroup,
-    readonly isNonStandardCrytalFrame: boolean
+    readonly isNonStandardCrytalFrame: boolean,
+
+    // optionally cached operators from [-3, -3, -3] to [3, 3, 3]
+    _operators_333?: SymmetryOperator[]
 }
 
 namespace ModelSymmetry {
diff --git a/src/mol-model/structure/structure/symmetry.ts b/src/mol-model/structure/structure/symmetry.ts
index c46a2df5e19725c2cb8bd840b56330dc468b0fbd..bd836b5d497cb309b8f1837b09b6a5d631190b3e 100644
--- a/src/mol-model/structure/structure/symmetry.ts
+++ b/src/mol-model/structure/structure/symmetry.ts
@@ -7,7 +7,7 @@
 import Structure from './structure'
 import { Selection } from '../query'
 import { ModelSymmetry } from '../model'
-import { Task } from 'mol-task';
+import { Task, RuntimeContext } from 'mol-task';
 import { SortedArray } from 'mol-data/int';
 import Unit from './unit';
 import { EquivalenceClasses, hash2 } from 'mol-data/util';
@@ -44,41 +44,11 @@ namespace StructureSymmetry {
     }
 
     export function builderSymmetryMates(structure: Structure, radius: number) {
-        // TODO: do it properly
-        return buildSymmetryRange(structure, Vec3.create(-3, -3, -3), Vec3.create(3, 3, 3));
+        return Task.create('Find Symmetry Mates', ctx => findMatesRadius(ctx, structure, radius));
     }
 
     export function buildSymmetryRange(structure: Structure, ijkMin: Vec3, ijkMax: Vec3) {
-        return Task.create('Build Assembly', async ctx => {
-            const models = Structure.getModels(structure);
-            if (models.length !== 1) throw new Error('Can only build symmetries from structures based on 1 model.');
-
-            const { spacegroup } = models[0].symmetry;
-            if (SpacegroupCell.isZero(spacegroup.cell)) return structure;
-
-            const operators: SymmetryOperator[] = [];
-            for (let op = 0; op < spacegroup.operators.length; op++) {
-                for (let i = ijkMin[0]; i < ijkMax[0]; i++) {
-                    for (let j = ijkMin[1]; j < ijkMax[1]; j++) {
-                        for (let k = ijkMin[2]; k < ijkMax[2]; k++) {
-                            operators[operators.length] = Spacegroup.getSymmetryOperator(spacegroup, op, i, j, k);
-                        }
-                    }
-                }
-            }
-
-            const assembler = Structure.Builder();
-
-            const { units } = structure;
-            for (const oper of operators) {
-                for (const unit of units) {
-                    assembler.addWithOperator(unit, oper);
-                }
-            }
-
-
-            return assembler.getStructure();
-        });
+        return Task.create('Build Symmetry', ctx => findSymmetryRange(ctx, structure, ijkMin, ijkMax));
     }
 
     function hashUnit(u: Unit) {
@@ -107,4 +77,81 @@ namespace StructureSymmetry {
     }
 }
 
+function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3) {
+    const operators: SymmetryOperator[] = symmetry._operators_333 || [];
+    const { spacegroup } = symmetry;
+    if (operators.length === 0) {
+        operators[0] = Spacegroup.getSymmetryOperator(spacegroup, 0, 0, 0, 0)
+        for (let op = 0; op < spacegroup.operators.length; op++) {
+            for (let i = ijkMin[0]; i < ijkMax[0]; i++) {
+                for (let j = ijkMin[1]; j < ijkMax[1]; j++) {
+                    for (let k = ijkMin[2]; k < ijkMax[2]; k++) {
+                        // we have added identity as the 1st operator.
+                        if (op === 0 && i === 0 && j === 0 && k === 0) continue;
+                        operators[operators.length] = Spacegroup.getSymmetryOperator(spacegroup, op, i, j, k);
+                    }
+                }
+            }
+        }
+        symmetry._operators_333 = operators;
+    }
+    return operators;
+}
+
+async function findSymmetryRange(ctx: RuntimeContext, structure: Structure, ijkMin: Vec3, ijkMax: Vec3) {
+    const models = Structure.getModels(structure);
+    if (models.length !== 1) throw new Error('Can only build symmetries from structures based on 1 model.');
+
+    const { spacegroup } = models[0].symmetry;
+    if (SpacegroupCell.isZero(spacegroup.cell)) return structure;
+
+    const operators = getOperators(models[0].symmetry, ijkMin, ijkMax);
+    const assembler = Structure.Builder();
+
+    const { units } = structure;
+    for (const oper of operators) {
+        for (const unit of units) {
+            assembler.addWithOperator(unit, oper);
+        }
+        if (ctx.shouldUpdate) await ctx.update('Building symmetry...');
+    }
+
+    return assembler.getStructure();
+}
+
+async function findMatesRadius(ctx: RuntimeContext, structure: Structure, radius: number) {
+    const models = Structure.getModels(structure);
+    if (models.length !== 1) throw new Error('Can only build symmetries from structures based on 1 model.');
+
+    const symmetry = models[0].symmetry;
+    const { spacegroup } = symmetry;
+    if (SpacegroupCell.isZero(spacegroup.cell)) return structure;
+
+    if (ctx.shouldUpdate) await ctx.update('Initialing...');
+    const operators = getOperators(symmetry, Vec3.create(-3, -3, -3), Vec3.create(3, 3, 3));
+    const lookup = structure.lookup3d;
+
+    const assembler = Structure.Builder();
+
+    const { units } = structure;
+    const center = Vec3.zero();
+    for (const oper of operators) {
+        for (const unit of units) {
+            const boundingSphere = unit.lookup3d.boundary.sphere;
+            Vec3.transformMat4(center, boundingSphere.center, oper.matrix);
+
+            const closeUnits = lookup.findUnitIndices(center[0], center[1], center[2], boundingSphere.radius + radius);
+            for (let uI = 0, _uI = closeUnits.count; uI < _uI; uI++) {
+                const closeUnit = units[closeUnits.indices[uI]];
+                if (!closeUnit.lookup3d.check(center[0], center[1], center[2], boundingSphere.radius + radius)) continue;
+                assembler.addWithOperator(unit, oper);
+            }
+        }
+        if (ctx.shouldUpdate) await ctx.update('Building symmetry...');
+    }
+
+
+    return assembler.getStructure();
+}
+
 export default StructureSymmetry;
\ No newline at end of file
diff --git a/src/mol-task/execution/observable.ts b/src/mol-task/execution/observable.ts
index 8162bcdf62bc9e7f4e9aade8ac4902ad120a5a9d..5b8e82f8991fead4f667dde12a18840ea63ff0d5 100644
--- a/src/mol-task/execution/observable.ts
+++ b/src/mol-task/execution/observable.ts
@@ -133,9 +133,9 @@ function abortTree(root: Progress.Node) {
     for (const c of root.children) abortTree(c);
 }
 
-function shouldNotify(info: ProgressInfo, time: number) {
-    return time - info.lastNotified > info.updateRateMs;
-}
+// function shouldNotify(info: ProgressInfo, time: number) {
+//     return time - info.lastNotified > info.updateRateMs;
+// }
 
 function notifyObserver(info: ProgressInfo, time: number) {
     info.lastNotified = time;
@@ -194,6 +194,7 @@ class ObservableRuntimeContext implements RuntimeContext {
         this.lastUpdatedTime = now();
         this.updateProgress(progress);
 
+        // TODO: do the shouldNotify check here?
         if (!!dontNotify /*|| !shouldNotify(this.info, this.lastUpdatedTime)*/) return;
 
         notifyObserver(this.info, this.lastUpdatedTime);