Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions raytracing/matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from .interface import *
from .utils import *

from enum import Enum
from typing import List
import multiprocessing
import sys
Expand Down Expand Up @@ -35,6 +36,25 @@ class Conjugate(NamedTuple):
d: float = None
transferMatrix:'Matrix' = None


class Conjugation(Enum):
"""The four conjugation types of an ABCD transfer matrix,
each corresponding to one matrix element being zero.

y₂ = A·y₁ + B·θ₁
θ₂ = C·y₁ + D·θ₁

FiniteFinite (B=0): y₂ depends only on y₁
InfiniteFinite (A=0): y₂ depends only on θ₁
FiniteInfinite (D=0): θ₂ depends only on y₁
Afocal (C=0): θ₂ depends only on θ₁
"""
FiniteFinite = "FiniteFinite"
InfiniteFinite = "InfiniteFinite"
FiniteInfinite = "FiniteInfinite"
Afocal = "Afocal"


# todo: fix docstrings since draw-related methods were removed


Expand Down Expand Up @@ -1081,10 +1101,84 @@ def isImaging(self):
A = transverse magnification
D = angular magnification
And as usual, C = -1/f (always).

See Also
--------
conjugation : Returns the full Conjugation enum type (FiniteFinite,
InfiniteFinite, FiniteInfinite, or Afocal).
"""

return isAlmostZero(self.B, self.__epsilon__)

@property
def conjugation(self):
"""Returns the conjugation type of this matrix as a Conjugation enum,
or None if no ABCD element is zero.

The four types correspond to one matrix element being zero:
- FiniteFinite (B=0): object and image are at finite distances
- InfiniteFinite (A=0): object at infinity, image at finite distance
- FiniteInfinite (D=0): object at finite distance, image at infinity
- Afocal (C=0): both object and image at infinity

Priority order when multiple elements are zero: B, A, D, C.

Examples
--------
>>> from raytracing import *
>>> m = Space(10) * Lens(5) * Space(10)
>>> m.conjugation
<Conjugation.FiniteFinite: 'FiniteFinite'>
"""
if isAlmostZero(self.B, self.__epsilon__):
return Conjugation.FiniteFinite
elif isAlmostZero(self.A, self.__epsilon__):
return Conjugation.InfiniteFinite
elif isAlmostZero(self.D, self.__epsilon__):
return Conjugation.FiniteInfinite
elif isAlmostZero(self.C, self.__epsilon__):
return Conjugation.Afocal
return None

@property
def isAfocal(self):
"""True if C=0 (no optical power), meaning the system is afocal
(e.g. a telescope). This is the complement of hasPower.

Examples
--------
>>> from raytracing import *
>>> System4f(10, 5).isAfocal
True
"""
return isAlmostZero(self.C, self.__epsilon__)

@property
def isInfiniteFiniteConjugate(self):
"""True if A=0, meaning an object at infinity is imaged at a
finite distance (e.g. a single lens with object at infinity).

Examples
--------
>>> from raytracing import *
>>> System2f(10).isInfiniteFiniteConjugate
True
"""
return isAlmostZero(self.A, self.__epsilon__)

@property
def isFiniteInfiniteConjugate(self):
"""True if D=0, meaning an object at a finite distance produces
a collimated output (e.g. object at focal point of a lens).

Examples
--------
>>> from raytracing import *
>>> (Lens(10) * Space(10)).isFiniteInfiniteConjugate
True
"""
return isAlmostZero(self.D, self.__epsilon__)

@property
def hasPower(self):
""" If True, then there is a non-null focal length because C!=0. We compare to an epsilon value, because
Expand Down
5 changes: 5 additions & 0 deletions raytracing/tests/testsComponents.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,10 @@ def test4fIsTwo2f(self):
self.assertEqual(composed4fSystem.frontVertex, system4f.frontVertex)


def testTelescopeIsAfocal(self):
system = System4f(10, 5)
self.assertTrue(system.isAfocal)


if __name__ == '__main__':
envtest.main()
73 changes: 73 additions & 0 deletions raytracing/tests/testsMatrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -1160,5 +1160,78 @@ def testTraceManyOpenCLBlockedRay(self):
self.assertTrue(traces[1][-1].isBlocked)


class TestConjugation(envtest.RaytracingTestCase):

def testConjugationFiniteFiniteBZero(self):
m = Space(10) * Lens(5) * Space(10)
self.assertEqual(m.conjugation, Conjugation.FiniteFinite)

def testConjugationInfiniteFiniteAZero(self):
m = System2f(10)
self.assertEqual(m.conjugation, Conjugation.InfiniteFinite)

def testConjugationFiniteInfiniteDZero(self):
m = Lens(10) * Space(10)
self.assertEqual(m.conjugation, Conjugation.FiniteInfinite)

def testConjugationAfocalCZero(self):
m = Space(10) # free space: A=1, B=10, C=0, D=1
self.assertEqual(m.conjugation, Conjugation.Afocal)

def testConjugationNoneWhenNoElementZero(self):
m = Matrix(2, 1, 1, 1) # det = 2*1 - 1*1 = 1, no element is zero
self.assertIsNone(m.conjugation)

def testConjugationPriorityIdentityMatrix(self):
m = Matrix(1, 0, 0, 1)
self.assertEqual(m.conjugation, Conjugation.FiniteFinite)

def testConjugationConsistencyWithIsImaging(self):
m = Space(10) * Lens(5) * Space(10)
self.assertEqual(m.conjugation == Conjugation.FiniteFinite, m.isImaging)

def testConjugationConsistencyWithIsImagingFalse(self):
m = Matrix(2, 1, 1, 1) # det=1, no B=0
self.assertEqual(m.conjugation == Conjugation.FiniteFinite, m.isImaging)

def testIsAfocalTrue(self):
m = System4f(10, 5)
self.assertTrue(m.isAfocal)

def testIsAfocalFalse(self):
m = Lens(10)
self.assertFalse(m.isAfocal)

def testIsAfocalConsistencyWithHasPower(self):
m = System4f(10, 5)
self.assertEqual(m.isAfocal, not m.hasPower)

def testIsAfocalConsistencyWithHasPowerLens(self):
m = Lens(10)
self.assertEqual(m.isAfocal, not m.hasPower)

def testIsInfiniteFiniteConjugateTrue(self):
m = System2f(10)
self.assertTrue(m.isInfiniteFiniteConjugate)

def testIsInfiniteFiniteConjugateFalse(self):
m = Matrix(1, 2, 0, 1)
self.assertFalse(m.isInfiniteFiniteConjugate)

def testIsFiniteInfiniteConjugateTrue(self):
m = Lens(10) * Space(10)
self.assertTrue(m.isFiniteInfiniteConjugate)

def testIsFiniteInfiniteConjugateFalse(self):
m = Matrix(1, 0, 0, 1)
self.assertFalse(m.isFiniteInfiniteConjugate)

def testConjugationLensAt2f(self):
f = 10
m = Space(2 * f) * Lens(f) * Space(2 * f)
self.assertEqual(m.conjugation, Conjugation.FiniteFinite)
self.assertTrue(m.isImaging)


if __name__ == '__main__':
envtest.main()