def test_deviation(): n1, n2 = symbols('n1, n2') r1 = Ray3D(Point3D(-1, -1, 1), Point3D(0, 0, 0)) n = Matrix([0, 0, 1]) i = Matrix([-1, -1, -1]) normal_ray = Ray3D(Point3D(0, 0, 0), Point3D(0, 0, 1)) P = Plane(Point3D(0, 0, 0), normal_vector=[0, 0, 1]) assert deviation(r1, 1, 1, normal=n) == 0 assert deviation(r1, 1, 1, plane=P) == 0 assert deviation(r1, 1, 1.1, plane=P).evalf(3) + 0.119 < 1e-3 assert deviation(i, 1, 1.1, normal=normal_ray).evalf(3) + 0.119 < 1e-3 assert deviation(r1, 1.33, 1, plane=P) is None # TIR assert deviation(r1, 1, 1, normal=[0, 0, 1]) == 0 assert deviation([-1, -1, -1], 1, 1, normal=[0, 0, 1]) == 0
def test_refraction_angle(): n1, n2 = symbols('n1, n2') m1 = Medium('m1') m2 = Medium('m2') r1 = Ray3D(Point3D(-1, -1, 1), Point3D(0, 0, 0)) i = Matrix([1, 1, 1]) n = Matrix([0, 0, 1]) normal_ray = Ray3D(Point3D(0, 0, 0), Point3D(0, 0, 1)) P = Plane(Point3D(0, 0, 0), normal_vector=[0, 0, 1]) assert refraction_angle(r1, 1, 1, n) == Matrix([[1], [1], [-1]]) assert refraction_angle([1, 1, 1], 1, 1, n) == Matrix([[1], [1], [-1]]) assert refraction_angle((1, 1, 1), 1, 1, n) == Matrix([[1], [1], [-1]]) assert refraction_angle(i, 1, 1, [0, 0, 1]) == Matrix([[1], [1], [-1]]) assert refraction_angle(i, 1, 1, (0, 0, 1)) == Matrix([[1], [1], [-1]]) assert refraction_angle(i, 1, 1, normal_ray) == Matrix([[1], [1], [-1]]) assert refraction_angle(i, 1, 1, plane=P) == Matrix([[1], [1], [-1]]) assert refraction_angle(r1, 1, 1, plane=P) == \ Ray3D(Point3D(0, 0, 0), Point3D(1, 1, -1)) assert refraction_angle(r1, m1, 1.33, plane=P) == \ Ray3D(Point3D(0, 0, 0), Point3D(Rational(100, 133), Rational(100, 133), -789378201649271*sqrt(3)/1000000000000000)) assert refraction_angle(r1, 1, m2, plane=P) == \ Ray3D(Point3D(0, 0, 0), Point3D(1, 1, -1)) assert refraction_angle(r1, n1, n2, plane=P) == \ Ray3D(Point3D(0, 0, 0), Point3D(n1/n2, n1/n2, -sqrt(3)*sqrt(-2*n1**2/(3*n2**2) + 1))) assert refraction_angle(r1, 1.33, 1, plane=P) == 0 # TIR assert refraction_angle(r1, 1, 1, normal_ray) == \ Ray3D(Point3D(0, 0, 0), direction_ratio=[1, 1, -1]) assert ae(refraction_angle(0.5, 1, 2), 0.24207, 5) assert ae(refraction_angle(0.5, 2, 1), 1.28293, 5) raises(ValueError, lambda: refraction_angle(r1, m1, m2, normal_ray, P)) raises(TypeError, lambda: refraction_angle(m1, m1, m2) ) # can add other values for arg[0] raises(TypeError, lambda: refraction_angle(r1, m1, m2, None, i)) raises(TypeError, lambda: refraction_angle(r1, m1, m2, m2))
def test_refraction_angle(): n1, n2 = symbols('n1, n2') m1 = Medium('m1') m2 = Medium('m2') r1 = Ray3D(Point3D(-1, -1, 1), Point3D(0, 0, 0)) i = Matrix([1, 1, 1]) n = Matrix([0, 0, 1]) normal_ray = Ray3D(Point3D(0, 0, 0), Point3D(0, 0, 1)) P = Plane(Point3D(0, 0, 0), normal_vector=[0, 0, 1]) assert refraction_angle(r1, 1, 1, n) == Matrix([[1], [1], [-1]]) assert refraction_angle([1, 1, 1], 1, 1, n) == Matrix([[1], [1], [-1]]) assert refraction_angle((1, 1, 1), 1, 1, n) == Matrix([[1], [1], [-1]]) assert refraction_angle(i, 1, 1, [0, 0, 1]) == Matrix([[1], [1], [-1]]) assert refraction_angle(i, 1, 1, (0, 0, 1)) == Matrix([[1], [1], [-1]]) assert refraction_angle(i, 1, 1, normal_ray) == Matrix([[1], [1], [-1]]) assert refraction_angle(i, 1, 1, plane=P) == Matrix([[1], [1], [-1]]) assert refraction_angle(r1, 1, 1, plane=P) == \ Ray3D(Point3D(0, 0, 0), Point3D(1, 1, -1)) assert refraction_angle(r1, m1, 1.33, plane=P) == \ Ray3D(Point3D(0, 0, 0), Point3D(100/133, 100/133, -789378201649271*sqrt(3)/1000000000000000)) assert refraction_angle(r1, 1, m2, plane=P) == \ Ray3D(Point3D(0, 0, 0), Point3D(1, 1, -1)) assert refraction_angle(r1, n1, n2, plane=P) == \ Ray3D(Point3D(0, 0, 0), Point3D(n1/n2, n1/n2, -sqrt(3)*sqrt(-2*n1**2/(3*n2**2) + 1))) assert refraction_angle(r1, 1.33, 1, plane=P) == 0 # TIR assert refraction_angle(r1, 1, 1, normal_ray) == \ Ray3D(Point3D(0, 0, 0), direction_ratio=[1, 1, -1])
def refraction_angle(incident, medium1, medium2, normal=None, plane=None): """ This function calculates transmitted vector after refraction at planar surface. ``medium1`` and ``medium2`` can be ``Medium`` or any sympifiable object. If ``incident`` is a number then treated as angle of incidence (in radians) in which case refraction angle is returned. If ``incident`` is an object of `Ray3D`, `normal` also has to be an instance of `Ray3D` in order to get the output as a `Ray3D`. Please note that if plane of separation is not provided and normal is an instance of `Ray3D`, ``normal`` will be assumed to be intersecting incident ray at the plane of separation. This will not be the case when `normal` is a `Matrix` or any other sequence. If ``incident`` is an instance of `Ray3D` and `plane` has not been provided and ``normal`` is not `Ray3D`, output will be a `Matrix`. Parameters ========== incident : Matrix, Ray3D, sequence or a number Incident vector or angle of incidence medium1 : sympy.physics.optics.medium.Medium or sympifiable Medium 1 or its refractive index medium2 : sympy.physics.optics.medium.Medium or sympifiable Medium 2 or its refractive index normal : Matrix, Ray3D, or sequence Normal vector plane : Plane Plane of separation of the two media. Returns ======= Returns an angle of refraction or a refracted ray depending on inputs. Examples ======== >>> from sympy.physics.optics import refraction_angle >>> from sympy.geometry import Point3D, Ray3D, Plane >>> from sympy.matrices import Matrix >>> from sympy import symbols, pi >>> n = Matrix([0, 0, 1]) >>> P = Plane(Point3D(0, 0, 0), normal_vector=[0, 0, 1]) >>> r1 = Ray3D(Point3D(-1, -1, 1), Point3D(0, 0, 0)) >>> refraction_angle(r1, 1, 1, n) Matrix([ [ 1], [ 1], [-1]]) >>> refraction_angle(r1, 1, 1, plane=P) Ray3D(Point3D(0, 0, 0), Point3D(1, 1, -1)) With different index of refraction of the two media >>> n1, n2 = symbols('n1, n2') >>> refraction_angle(r1, n1, n2, n) Matrix([ [ n1/n2], [ n1/n2], [-sqrt(3)*sqrt(-2*n1**2/(3*n2**2) + 1)]]) >>> refraction_angle(r1, n1, n2, plane=P) Ray3D(Point3D(0, 0, 0), Point3D(n1/n2, n1/n2, -sqrt(3)*sqrt(-2*n1**2/(3*n2**2) + 1))) >>> round(refraction_angle(pi/6, 1.2, 1.5), 5) 0.41152 """ n1 = refractive_index_of_medium(medium1) n2 = refractive_index_of_medium(medium2) # check if an incidence angle was supplied instead of a ray try: angle_of_incidence = float(incident) except TypeError: angle_of_incidence = None try: critical_angle_ = critical_angle(medium1, medium2) except (ValueError, TypeError): critical_angle_ = None if angle_of_incidence is not None: if normal is not None or plane is not None: raise ValueError('Normal/plane not allowed if incident is an angle') if not 0.0 <= angle_of_incidence < pi*0.5: raise ValueError('Angle of incidence not in range [0:pi/2)') if critical_angle_ and angle_of_incidence > critical_angle_: raise ValueError('Ray undergoes total internal reflection') return asin(n1*sin(angle_of_incidence)/n2) # Treat the incident as ray below # A flag to check whether to return Ray3D or not return_ray = False if plane is not None and normal is not None: raise ValueError("Either plane or normal is acceptable.") if not isinstance(incident, Matrix): if is_sequence(incident): _incident = Matrix(incident) elif isinstance(incident, Ray3D): _incident = Matrix(incident.direction_ratio) else: raise TypeError( "incident should be a Matrix, Ray3D, or sequence") else: _incident = incident # If plane is provided, get direction ratios of the normal # to the plane from the plane else go with `normal` param. if plane is not None: if not isinstance(plane, Plane): raise TypeError("plane should be an instance of geometry.plane.Plane") # If we have the plane, we can get the intersection # point of incident ray and the plane and thus return # an instance of Ray3D. if isinstance(incident, Ray3D): return_ray = True intersection_pt = plane.intersection(incident)[0] _normal = Matrix(plane.normal_vector) else: if not isinstance(normal, Matrix): if is_sequence(normal): _normal = Matrix(normal) elif isinstance(normal, Ray3D): _normal = Matrix(normal.direction_ratio) if isinstance(incident, Ray3D): intersection_pt = intersection(incident, normal) if len(intersection_pt) == 0: raise ValueError( "Normal isn't concurrent with the incident ray.") else: return_ray = True intersection_pt = intersection_pt[0] else: raise TypeError( "Normal should be a Matrix, Ray3D, or sequence") else: _normal = normal eta = n1/n2 # Relative index of refraction # Calculating magnitude of the vectors mag_incident = sqrt(sum([i**2 for i in _incident])) mag_normal = sqrt(sum([i**2 for i in _normal])) # Converting vectors to unit vectors by dividing # them with their magnitudes _incident /= mag_incident _normal /= mag_normal c1 = -_incident.dot(_normal) # cos(angle_of_incidence) cs2 = 1 - eta**2*(1 - c1**2) # cos(angle_of_refraction)**2 if cs2.is_negative: # This is the case of total internal reflection(TIR). return 0 drs = eta*_incident + (eta*c1 - sqrt(cs2))*_normal # Multiplying unit vector by its magnitude drs = drs*mag_incident if not return_ray: return drs else: return Ray3D(intersection_pt, direction_ratio=drs)
def refraction_angle(incident, medium1, medium2, normal=None, plane=None): """ This function calculates transmitted vector after refraction at planar surface. `medium1` and `medium2` can be `Medium` or any sympifiable object. If `incident` is an object of `Ray3D`, `normal` also has to be an instance of `Ray3D` in order to get the output as a `Ray3D`. Please note that if plane of separation is not provided and normal is an instance of `Ray3D`, normal will be assumed to be intersecting incident ray at the plane of separation. This will not be the case when `normal` is a `Matrix` or any other sequence. If `incident` is an instance of `Ray3D` and `plane` has not been provided and `normal` is not `Ray3D`, output will be a `Matrix`. Parameters ========== incident : Matrix, Ray3D, or sequence Incident vector medium1 : sympy.physics.optics.medium.Medium or sympifiable Medium 1 or its refractive index medium2 : sympy.physics.optics.medium.Medium or sympifiable Medium 2 or its refractive index normal : Matrix, Ray3D, or sequence Normal vector plane : Plane Plane of separation of the two media. Examples ======== >>> from sympy.physics.optics import refraction_angle >>> from sympy.geometry import Point3D, Ray3D, Plane >>> from sympy.matrices import Matrix >>> from sympy import symbols >>> n = Matrix([0, 0, 1]) >>> P = Plane(Point3D(0, 0, 0), normal_vector=[0, 0, 1]) >>> r1 = Ray3D(Point3D(-1, -1, 1), Point3D(0, 0, 0)) >>> refraction_angle(r1, 1, 1, n) Matrix([ [ 1], [ 1], [-1]]) >>> refraction_angle(r1, 1, 1, plane=P) Ray3D(Point3D(0, 0, 0), Point3D(1, 1, -1)) With different index of refraction of the two media >>> n1, n2 = symbols('n1, n2') >>> refraction_angle(r1, n1, n2, n) Matrix([ [ n1/n2], [ n1/n2], [-sqrt(3)*sqrt(-2*n1**2/(3*n2**2) + 1)]]) >>> refraction_angle(r1, n1, n2, plane=P) Ray3D(Point3D(0, 0, 0), Point3D(n1/n2, n1/n2, -sqrt(3)*sqrt(-2*n1**2/(3*n2**2) + 1))) """ # A flag to check whether to return Ray3D or not return_ray = False if plane is not None and normal is not None: raise ValueError("Either plane or normal is acceptable.") if not isinstance(incident, Matrix): if is_sequence(incident): _incident = Matrix(incident) elif isinstance(incident, Ray3D): _incident = Matrix(incident.direction_ratio) else: raise TypeError( "incident should be a Matrix, Ray3D, or sequence") else: _incident = incident # If plane is provided, get direction ratios of the normal # to the plane from the plane else go with `normal` param. if plane is not None: if not isinstance(plane, Plane): raise TypeError("plane should be an instance of geometry.plane.Plane") # If we have the plane, we can get the intersection # point of incident ray and the plane and thus return # an instance of Ray3D. if isinstance(incident, Ray3D): return_ray = True intersection_pt = plane.intersection(incident)[0] _normal = Matrix(plane.normal_vector) else: if not isinstance(normal, Matrix): if is_sequence(normal): _normal = Matrix(normal) elif isinstance(normal, Ray3D): _normal = Matrix(normal.direction_ratio) if isinstance(incident, Ray3D): intersection_pt = intersection(incident, normal) if len(intersection_pt) == 0: raise ValueError( "Normal isn't concurrent with the incident ray.") else: return_ray = True intersection_pt = intersection_pt[0] else: raise TypeError( "Normal should be a Matrix, Ray3D, or sequence") else: _normal = normal n1, n2 = None, None if isinstance(medium1, Medium): n1 = medium1.refractive_index else: n1 = sympify(medium1) if isinstance(medium2, Medium): n2 = medium2.refractive_index else: n2 = sympify(medium2) eta = n1/n2 # Relative index of refraction # Calculating magnitude of the vectors mag_incident = sqrt(sum([i**2 for i in _incident])) mag_normal = sqrt(sum([i**2 for i in _normal])) # Converting vectors to unit vectors by dividing # them with their magnitudes _incident /= mag_incident _normal /= mag_normal c1 = -_incident.dot(_normal) # cos(angle_of_incidence) cs2 = 1 - eta**2*(1 - c1**2) # cos(angle_of_refraction)**2 if cs2.is_negative: # This is the case of total internal reflection(TIR). return 0 drs = eta*_incident + (eta*c1 - sqrt(cs2))*_normal # Multiplying unit vector by its magnitude drs = drs*mag_incident if not return_ray: return drs else: return Ray3D(intersection_pt, direction_ratio=drs)