Repository: DCC-Lab/RayTracing

Issue:
Matrix: hasPower returns True when it should be False
When we want to know if a `Matrix` has power, we check if `self.C != 0`. In some cases, computational errors occurs due to the fact that we are handling floats and `self.C` is really close to 0. Example:
```python
d1 = 1.0000000000000017 # Some computation errors tend to add digits at the end of the number
d2 = 1.0000000000000017 * 2.05
matrixGroup = System4f(d1, d2) # We use a 4f system because it is a real physical system
# Any matrix with C close but not 0 will do
print(matrixGroup.C) # We check what is the value of the C component of the ewuivalent matrix
```
The output is:
```python
1.1102230246251565e-16
```
Which is **VERY** small, but still not 0. Obviously, a 4f system has no power, but with computational errors, the code considers it has power.

Discussion:

Patch:
diff --git a/raytracing/matrix.py b/raytracing/matrix.py
index 43f9790..c84cede 100644
--- a/raytracing/matrix.py
+++ b/raytracing/matrix.py
@@ -846,7 +846,8 @@ class Matrix(object):
 
     @property
     def hasPower(self):
-        """ If True, then there is a non-null focal length because C!=0
+        """ If True, then there is a non-null focal length because C!=0. We compare to an epsilon value, because
+        computational errors can occur and lead to C being very small, but not 0.
 
         Examples
         --------
@@ -861,7 +862,7 @@ class Matrix(object):
         >>> print('hasPower:' , M2.hasPower)
         hasPower: False
         """
-        return self.C != 0
+        return abs(self.C) > Matrix.__epsilon__
 
     def pointsOfInterest(self, z):
         """ Any points of interest for this matrix (focal points,


Test patch:
diff --git a/raytracing/tests/testsMatrix.py b/raytracing/tests/testsMatrix.py
index 9c75340..886a9b4 100644
--- a/raytracing/tests/testsMatrix.py
+++ b/raytracing/tests/testsMatrix.py
@@ -344,6 +344,15 @@ class TestMatrix(unittest.TestCase):
         m2 = Matrix(A=1, B=1, C=3, D=4)
         self.assertFalse(m2.isImaging)
 
+    def testHasNoPower(self):
+        f1 = 1.0000000000000017
+        f2 = 2.05 * f1
+
+        # This simulates a 4f system (since we test Matrix, we should only use basic matrices)
+        m = Matrix(1, f1, 0, 1) * Matrix(1, 0, -1 / f1, 1) * Matrix(1, f1, 0, 1) * Matrix(1, f2, 0, 1)
+        m = m * Matrix(1, 0, -1 / f2, 1) * Matrix(1, f1, 0, 1)
+        self.assertFalse(m.hasPower)
+
     def testEffectiveFocalLengthsHasPower(self):
         m = Matrix(1, 2, 3, 4)
         focalLengths = (-1 / 3, -1 / 3)