diff --git a/src/mol-math/geometry/symmetry-operator.ts b/src/mol-math/geometry/symmetry-operator.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1e3a57de38f5a782220061e838ca5ee2dafcdc28
--- /dev/null
+++ b/src/mol-math/geometry/symmetry-operator.ts
@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Vec3, Mat4 } from '../linear-algebra/3d'
+
+interface SymmetryOperator {
+    readonly name: string,
+    readonly hkl: Vec3,
+
+    readonly matrix: Mat4,
+    // cache the inverse of the transform
+    readonly inverse: Mat4,
+    // optimize the identity case
+    readonly isIdentity: boolean
+}
+
+namespace SymmetryOperator {
+    export const DefaultName = '1_555'
+    export const Default: SymmetryOperator = create(DefaultName, Mat4.identity());
+
+    export function create(name: string, matrix: Mat4, hkl?: number[]): SymmetryOperator {
+        const _hkl = hkl ? Vec3.create(hkl[0], hkl[1], hkl[2]) : Vec3.zero();
+        if (Mat4.isIdentity(matrix)) return { name, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl };
+        if (!Mat4.isRotationAndTranslation(matrix)) throw new Error('Symmetry operator must be a composition of rotation and translation.');
+        return { name, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl };
+    }
+
+    export interface CoordinateMapper { (index: number, slot: Vec3): Vec3 }
+    export interface ArrayMapping {
+        readonly invariantPosition: CoordinateMapper,
+        readonly position: CoordinateMapper,
+        x(index: number): number,
+        y(index: number): number,
+        z(index: number): number
+    }
+
+    export interface Coordinates { x: ArrayLike<number>, y: ArrayLike<number>, z: ArrayLike<number> }
+
+    export function createMapping(operator: SymmetryOperator, coords: Coordinates) {
+        const invariantPosition = SymmetryOperator.createCoordinateMapper(SymmetryOperator.Default, coords);
+        const position = operator.isIdentity ? invariantPosition : SymmetryOperator.createCoordinateMapper(operator, coords);
+        const { x, y, z } = createProjections(operator, coords);
+        return { invariantPosition, position, x, y, z };
+    }
+
+    export function createCoordinateMapper(t: SymmetryOperator, coords: Coordinates): CoordinateMapper {
+        if (t.isIdentity) return identityPosition(coords);
+        return generalPosition(t, coords);
+    }
+}
+
+export default SymmetryOperator
+
+interface Projections { x(index: number): number, y(index: number): number, z(index: number): number }
+
+function createProjections(t: SymmetryOperator, coords: SymmetryOperator.Coordinates): Projections {
+    if (t.isIdentity) return { x: projectCoord(coords.x), y: projectCoord(coords.y), z: projectCoord(coords.z) };
+    return { x: projectX(t, coords), y: projectY(t, coords), z: projectZ(t, coords) };
+}
+
+function projectCoord(xs: ArrayLike<number>) {
+    return (i: number) => xs[i];
+}
+
+function isW1(m: Mat4) {
+    return m[3] === 0 && m[7] === 0 && m[11] === 0 && m[15] === 1;
+}
+
+function projectX({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs}: SymmetryOperator.Coordinates) {
+    const xx = m[0], yy = m[4], zz = m[8], tx = m[12];
+
+    if (isW1(m)) {
+        // this should always be the case.
+        return (i: number) => xx * xs[i] + yy * ys[i] + zz * zs[i] + tx;
+    }
+
+    return (i: number) => {
+        const x = xs[i], y = ys[i], z = zs[i], w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0;
+        return (xx * x + yy * y + zz * z + tx) / w;
+    }
+}
+
+function projectY({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs}: SymmetryOperator.Coordinates) {
+    const xx = m[1], yy = m[5], zz = m[9], ty = m[13];
+
+    if (isW1(m)) {
+        // this should always be the case.
+        return (i: number) => xx * xs[i] + yy * ys[i] + zz * zs[i] + ty;
+    }
+
+    return (i: number) => {
+        const x = xs[i], y = ys[i], z = zs[i], w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0;
+        return (xx * x + yy * y + zz * z + ty) / w;
+    }
+}
+
+function projectZ({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs}: SymmetryOperator.Coordinates) {
+    const xx = m[2], yy = m[6], zz = m[10], tz = m[14];
+
+    if (isW1(m)) {
+        // this should always be the case.
+        return (i: number) => xx * xs[i] + yy * ys[i] + zz * zs[i] + tz;
+    }
+
+    return (i: number) => {
+        const x = xs[i], y = ys[i], z = zs[i], w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0;
+        return (xx * x + yy * y + zz * z + tz) / w;
+    }
+}
+
+function identityPosition({ x, y, z }: SymmetryOperator.Coordinates): SymmetryOperator.CoordinateMapper {
+    return (i, s) => {
+        s[0] = x[i];
+        s[1] = y[i];
+        s[2] = z[i];
+        return s;
+    }
+}
+
+function generalPosition({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs }: SymmetryOperator.Coordinates) {
+    if (isW1(m)) {
+        // this should always be the case.
+        return (i: number, r: Vec3): Vec3 => {
+            const x = xs[i], y = ys[i], z = zs[i];
+            r[0] = m[0] * x + m[4] * y + m[8] * z + m[12];
+            r[1] = m[1] * x + m[5] * y + m[9] * z + m[13];
+            r[2] = m[2] * x + m[6] * y + m[10] * z + m[14];
+            return r;
+        }
+    }
+    return (i: number, r: Vec3): Vec3 => {
+        r[0] = xs[i];
+        r[1] = ys[i];
+        r[2] = zs[i];
+        Vec3.transformMat4(r, r, m);
+        return r;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-math/geometry/transform.ts b/src/mol-math/geometry/transform.ts
deleted file mode 100644
index cf9aaa6d937b378c12edb14b9ae98376a23f8e99..0000000000000000000000000000000000000000
--- a/src/mol-math/geometry/transform.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { Mat4 } from '../linear-algebra/3d'
-
-interface Transform extends Readonly<{
-    transform: Mat4,
-    // cache the inverse of the transform
-    inverse: Mat4,
-    // optimize the identity case
-    isIdentity: boolean
-}> { }
-
-namespace Transform {
-    export function isIdentity(m: Mat4) {
-        throw 'nyi'
-    }
-
-    export function isRotationAndTranslation(m: Mat4) {
-        throw 'nyi'
-    }
-}
-
-export default Transform
\ No newline at end of file
diff --git a/src/mol-math/linear-algebra/3d.ts b/src/mol-math/linear-algebra/3d.ts
index fccdb52b784f181d6d856963f875940a96447646..f989a61c08071c119239843a33b756dea1cbd267 100644
--- a/src/mol-math/linear-algebra/3d.ts
+++ b/src/mol-math/linear-algebra/3d.ts
@@ -152,7 +152,8 @@ export namespace Mat4 {
         let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
 
         if (!det) {
-            return Number.NaN;
+            console.warn('non-invertible matrix.', a);
+            return out;
         }
         det = 1.0 / det;
 
@@ -430,6 +431,18 @@ export namespace Mat4 {
         // Calculate the determinant
         return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
     }
+
+    export function isRotationAndTranslation(a: Mat4) {
+        const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
+            a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
+            a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
+            /* a30 = a[12], a31 = a[13], a32 = a[14],*/ a33 = a[15];
+
+        if (a33 !== 1 || a03 !== 0 || a13 !== 0 || a23 !== 0) return false;
+        const det3x3 = a00 * (a11 * a22 - a21 * a21) - a01 * (a10 * a22 - a12 * a20) + a02 * (a10 * a21 - a11 * a20);
+        if (det3x3 < 1 - EPSILON.Value || det3x3 > 1 + EPSILON.Value) return false;
+        return true;
+    }
 }
 
 export namespace Vec3 {
diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts
index dee0fddc23278dbb9ec3b999c3c8ff2670eb9037..aca855e52a55d203f85d7038679c1925ed68385e 100644
--- a/src/mol-model/structure/model/formats/mmcif.ts
+++ b/src/mol-model/structure/model/formats/mmcif.ts
@@ -70,9 +70,9 @@ function getConformation({ data }: mmCIF_Format, bounds: Interval): Conformation
         atomId: Column.window(atom_site.id, start, end),
         occupancy: Column.window(atom_site.occupancy, start, end),
         B_iso_or_equiv: Column.window(atom_site.B_iso_or_equiv, start, end),
-        __x: atom_site.Cartn_x.toArray({ array: Float32Array, start, end }),
-        __y: atom_site.Cartn_y.toArray({ array: Float32Array, start, end }),
-        __z: atom_site.Cartn_z.toArray({ array: Float32Array, start, end }),
+        x: atom_site.Cartn_x.toArray({ array: Float32Array, start, end }),
+        y: atom_site.Cartn_y.toArray({ array: Float32Array, start, end }),
+        z: atom_site.Cartn_z.toArray({ array: Float32Array, start, end }),
     }
 }
 
diff --git a/src/mol-model/structure/model/properties/conformation.ts b/src/mol-model/structure/model/properties/conformation.ts
index 66a975d62bf73b3b17c7873544fea784a959a04f..ce94b8597d2d1920b469f86d3942898e403fa5de 100644
--- a/src/mol-model/structure/model/properties/conformation.ts
+++ b/src/mol-model/structure/model/properties/conformation.ts
@@ -20,9 +20,9 @@ interface Conformation {
 
     // Coordinates. Generally, not to be accessed directly because the coordinate might be
     // transformed by an operator. Use Unit.getPosition instead.
-    __x: ArrayLike<number>,
-    __y: ArrayLike<number>,
-    __z: ArrayLike<number>
+    x: ArrayLike<number>,
+    y: ArrayLike<number>,
+    z: ArrayLike<number>
 }
 
 export default Conformation
\ No newline at end of file
diff --git a/src/mol-model/structure/structure.ts b/src/mol-model/structure/structure.ts
index 1ceba8e5a8e0486cd28a7186f41ad85df1ae9987..4fe835cfaffe28eeb60b503fc14b6e713f2074b5 100644
--- a/src/mol-model/structure/structure.ts
+++ b/src/mol-model/structure/structure.ts
@@ -7,7 +7,6 @@
 import Atom from './structure/atom'
 import AtomSet from './structure/atom/set'
 import Structure from './structure/structure'
-import Operator from './structure/operator'
 import Unit from './structure/unit'
 
-export { Atom, AtomSet, Structure, Operator, Unit }
\ No newline at end of file
+export { Atom, AtomSet, Structure, Unit }
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/operator.ts b/src/mol-model/structure/structure/operator.ts
deleted file mode 100644
index 10ba47cb8c94e46f587f5e39cb7958e980d2bcbb..0000000000000000000000000000000000000000
--- a/src/mol-model/structure/structure/operator.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { Mat4 } from 'mol-math/linear-algebra'
-
-interface Operator extends Readonly<{
-    name: string,
-    hkl: number[], // defaults to [0, 0, 0] for non symmetry entries
-    matrix: Mat4,
-    // cache the inverse of the transform
-    inverse: Mat4,
-    // optimize the identity case
-    isIdentity: boolean
-}> { }
-
-namespace Operator {
-    export const Identity: Operator = { name: '1_555', hkl: [0, 0, 0], matrix: Mat4.identity(), inverse: Mat4.identity(), isIdentity: true };
-}
-
-export default Operator
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts
index 0d09fa4db4b252e52620feaac8050b1c1fb60aea..0d262041252f50b998b7465ce7625921028560c0 100644
--- a/src/mol-model/structure/structure/structure.ts
+++ b/src/mol-model/structure/structure/structure.ts
@@ -6,9 +6,9 @@
 
 import { OrderedSet, Iterator } from 'mol-data/int'
 import UniqueArray from 'mol-data/util/unique-array'
+import SymmetryOperator from 'mol-math/geometry/symmetry-operator'
 import { Model, Format } from '../model'
 import Unit from './unit'
-import Operator from './operator'
 import AtomSet from './atom/set'
 import Atom from './atom'
 
@@ -35,7 +35,7 @@ namespace Structure {
         const builder = Builder();
 
         for (let c = 0; c < chains.count; c++) {
-            const unit = Unit.create(model, Operator.Identity);
+            const unit = Unit.create(model, SymmetryOperator.Default);
             builder.addUnit(unit);
             builder.addAtoms(unit.id, OrderedSet.ofBounds(chains.segments[c], chains.segments[c + 1]));
         }
diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts
index c819836d99d6fe8e471ded60f6509d6c6dcf04b5..be83164a38f62a4d8dc99d4c190aa27d04c58343 100644
--- a/src/mol-model/structure/structure/unit.ts
+++ b/src/mol-model/structure/structure/unit.ts
@@ -4,45 +4,34 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
+import SymmetryOperator from 'mol-math/geometry/symmetry-operator'
 import { Model } from '../model'
-import Operator from './operator'
-import { Vec3, Mat4 } from 'mol-math/linear-algebra'
 
-interface Unit extends Readonly<{
+interface Unit extends SymmetryOperator.ArrayMapping {
     // Structure-level unique identifier of the unit.
-    id: number,
+    readonly id: number,
 
     // Provides access to the underlying data.
-    model: Model,
+    readonly model: Model,
 
     // Determines the operation applied to this unit.
     // The transform and and inverse are baked into the "getPosition" function
-    operator: Operator,
+    readonly operator: SymmetryOperator,
 
     // Reference some commonly accessed things for faster access.
-    residueIndex: ArrayLike<number>,
-    chainIndex: ArrayLike<number>,
-    hierarchy: Model['hierarchy'],
-    conformation: Model['conformation']
-}> {
-    // returns the untransformed position. Used for spatial queries.
-    getInvariantPosition(atom: number, slot: Vec3): Vec3,
+    readonly residueIndex: ArrayLike<number>,
+    readonly chainIndex: ArrayLike<number>,
+    readonly hierarchy: Model['hierarchy'],
+    readonly conformation: Model['conformation']
 
-    // gets the transformed position of the specified atom.
-    getPosition(atom: number, slot: Vec3): Vec3,
-
-    // optimized x/y/z coordinate projections for your query needs.
-    x(atom: number): number,
-    y(atom: number): number,
-    z(atom: number): number
+    // TODO: add velocity?
 }
 
 namespace Unit {
-    export function create(model: Model, operator: Operator): Unit {
+    export function create(model: Model, operator: SymmetryOperator): Unit {
         const h = model.hierarchy;
-        const { __x: x, __y: y, __z: z } = model.conformation;
-        const getInvariantPosition = makeGetInvariantPosition(x, y, z);
-        const getPosition = operator.isIdentity ? getInvariantPosition : makeGetPosition(operator.matrix, x, y, z);
+        const { invariantPosition, position, x, y, z } = SymmetryOperator.createMapping(operator, model.conformation);
+
         return {
             id: nextUnitId(),
             model,
@@ -51,11 +40,9 @@ namespace Unit {
             chainIndex: h.chainSegments.segmentMap,
             hierarchy: model.hierarchy,
             conformation: model.conformation,
-            getInvariantPosition,
-            getPosition,
-            x: operator.isIdentity ? makeGetCoord(x) : makeX(operator.matrix, x, y, z),
-            y: operator.isIdentity ? makeGetCoord(y) : makeY(operator.matrix, x, y, z),
-            z: operator.isIdentity ? makeGetCoord(z) : makeZ(operator.matrix, x, y, z)
+            invariantPosition,
+            position,
+            x, y, z
         };
     }
 }
@@ -67,73 +54,4 @@ function nextUnitId() {
     const ret = _id;
     _id = (_id + 1) % 0x3fffffff;
     return ret;
-}
-
-function makeGetInvariantPosition(x: ArrayLike<number>, y: ArrayLike<number>, z: ArrayLike<number>) {
-    return (a: number, r: Vec3): Vec3 => {
-        r[0] = x[a];
-        r[1] = y[a];
-        r[2] = z[a];
-        return r;
-    }
-}
-
-function makeGetCoord(xs: ArrayLike<number>) {
-    return (i: number) => xs[i];
-}
-
-function isWConst(m: Mat4) {
-    return m[3] === 0 && m[7] === 0 && m[11] === 0;
-}
-
-function makeX(m: Mat4, xs: ArrayLike<number>, ys: ArrayLike<number>, zs: ArrayLike<number>) {
-    const xx = m[0], yy = m[4], zz = m[8], ww = m[12];
-
-    if (isWConst(m)) {
-        const w = m[15] || 1.0;
-        return (i: number) => (xx * xs[i] + yy * ys[i] + zz * zs[i] + ww) / w;
-    }
-
-    return (i: number) => {
-        const x = xs[i], y = ys[i], z = zs[i], w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0;
-        return (xx * x + yy * y + zz * z + ww) / w;
-    }
-}
-
-function makeY(m: Mat4, xs: ArrayLike<number>, ys: ArrayLike<number>, zs: ArrayLike<number>) {
-    const xx = m[1], yy = m[5], zz = m[9], ww = m[13];
-
-    if (isWConst(m)) {
-        const w = m[15] || 1.0;
-        return (i: number) => (xx * xs[i] + yy * ys[i] + zz * zs[i] + ww) / w;
-    }
-
-    return (i: number) => {
-        const x = xs[i], y = ys[i], z = zs[i], w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0;
-        return (xx * x + yy * y + zz * z + ww) / w;
-    }
-}
-
-function makeZ(m: Mat4, xs: ArrayLike<number>, ys: ArrayLike<number>, zs: ArrayLike<number>) {
-    const xx = m[2], yy = m[6], zz = m[10], ww = m[14];
-
-    if (isWConst(m)) {
-        const w = m[15] || 1.0;
-        return (i: number) => (xx * xs[i] + yy * ys[i] + zz * zs[i] + ww) / w;
-    }
-
-    return (i: number) => {
-        const x = xs[i], y = ys[i], z = zs[i], w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0;
-        return (xx * x + yy * y + zz * z + ww) / w;
-    }
-}
-
-function makeGetPosition(m: Mat4, x: ArrayLike<number>, y: ArrayLike<number>, z: ArrayLike<number>) {
-    return (a: number, r: Vec3): Vec3 => {
-        r[0] = x[a];
-        r[1] = y[a];
-        r[2] = z[a];
-        Vec3.transformMat4(r, r, m);
-        return r;
-    }
 }
\ No newline at end of file
diff --git a/src/perf-tests/structure.ts b/src/perf-tests/structure.ts
index 3ae723fc755aa4eae7250d397a0bed8c21113a44..156d441f8393095f4d3236f42e561262d88b7719 100644
--- a/src/perf-tests/structure.ts
+++ b/src/perf-tests/structure.ts
@@ -242,8 +242,8 @@ export namespace PropertyAccess {
     }
 
     export async function run() {
-        //const { structures, models } = await readCIF('./examples/1cbs_full.bcif');
-        const { structures, models, mmcif } = await readCIF('e:/test/quick/3j3q_full.bcif');
+        const { structures, models, mmcif } = await readCIF('./examples/1cbs_full.bcif');
+        //const { structures, models, mmcif } = await readCIF('e:/test/quick/3j3q_full.bcif');
         //const { structures, models, mmcif } = await readCIF('e:/test/quick/1cbs_updated.cif');
 
         console.log(mmcif.pdbx_struct_oper_list.matrix.toArray());
@@ -272,7 +272,7 @@ export namespace PropertyAccess {
         console.log('atom.x', sumProperty(structures[0], Q.props.atom.x));
         console.timeEnd('atom.x');
         console.time('__x')
-        console.log('__x', sumProperty(structures[0], l => l.unit.conformation.__x[l.atom]));
+        console.log('__x', sumProperty(structures[0], l => l.unit.conformation.x[l.atom]));
         console.timeEnd('__x')
 
         //const authSeqId = Atom.property(l => l.unit.hierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.atom]));