Spaces:
Sleeping
Sleeping
""" | |
**Contains** | |
* refraction_angle | |
* fresnel_coefficients | |
* deviation | |
* brewster_angle | |
* critical_angle | |
* lens_makers_formula | |
* mirror_formula | |
* lens_formula | |
* hyperfocal_distance | |
* transverse_magnification | |
""" | |
__all__ = ['refraction_angle', | |
'deviation', | |
'fresnel_coefficients', | |
'brewster_angle', | |
'critical_angle', | |
'lens_makers_formula', | |
'mirror_formula', | |
'lens_formula', | |
'hyperfocal_distance', | |
'transverse_magnification' | |
] | |
from sympy.core.numbers import (Float, I, oo, pi, zoo) | |
from sympy.core.singleton import S | |
from sympy.core.symbol import Symbol | |
from sympy.core.sympify import sympify | |
from sympy.functions.elementary.miscellaneous import sqrt | |
from sympy.functions.elementary.trigonometric import (acos, asin, atan2, cos, sin, tan) | |
from sympy.matrices.dense import Matrix | |
from sympy.polys.polytools import cancel | |
from sympy.series.limits import Limit | |
from sympy.geometry.line import Ray3D | |
from sympy.geometry.util import intersection | |
from sympy.geometry.plane import Plane | |
from sympy.utilities.iterables import is_sequence | |
from .medium import Medium | |
def refractive_index_of_medium(medium): | |
""" | |
Helper function that returns refractive index, given a medium | |
""" | |
if isinstance(medium, Medium): | |
n = medium.refractive_index | |
else: | |
n = sympify(medium) | |
return n | |
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 S.Zero | |
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 fresnel_coefficients(angle_of_incidence, medium1, medium2): | |
""" | |
This function uses Fresnel equations to calculate reflection and | |
transmission coefficients. Those are obtained for both polarisations | |
when the electric field vector is in the plane of incidence (labelled 'p') | |
and when the electric field vector is perpendicular to the plane of | |
incidence (labelled 's'). There are four real coefficients unless the | |
incident ray reflects in total internal in which case there are two complex | |
ones. Angle of incidence is the angle between the incident ray and the | |
surface normal. ``medium1`` and ``medium2`` can be ``Medium`` or any | |
sympifiable object. | |
Parameters | |
========== | |
angle_of_incidence : sympifiable | |
medium1 : Medium or sympifiable | |
Medium 1 or its refractive index | |
medium2 : Medium or sympifiable | |
Medium 2 or its refractive index | |
Returns | |
======= | |
Returns a list with four real Fresnel coefficients: | |
[reflection p (TM), reflection s (TE), | |
transmission p (TM), transmission s (TE)] | |
If the ray is undergoes total internal reflection then returns a | |
list of two complex Fresnel coefficients: | |
[reflection p (TM), reflection s (TE)] | |
Examples | |
======== | |
>>> from sympy.physics.optics import fresnel_coefficients | |
>>> fresnel_coefficients(0.3, 1, 2) | |
[0.317843553417859, -0.348645229818821, | |
0.658921776708929, 0.651354770181179] | |
>>> fresnel_coefficients(0.6, 2, 1) | |
[-0.235625382192159 - 0.971843958291041*I, | |
0.816477005968898 - 0.577377951366403*I] | |
References | |
========== | |
.. [1] https://en.wikipedia.org/wiki/Fresnel_equations | |
""" | |
if not 0 <= 2*angle_of_incidence < pi: | |
raise ValueError('Angle of incidence not in range [0:pi/2)') | |
n1 = refractive_index_of_medium(medium1) | |
n2 = refractive_index_of_medium(medium2) | |
angle_of_refraction = asin(n1*sin(angle_of_incidence)/n2) | |
try: | |
angle_of_total_internal_reflection_onset = critical_angle(n1, n2) | |
except ValueError: | |
angle_of_total_internal_reflection_onset = None | |
if angle_of_total_internal_reflection_onset is None or\ | |
angle_of_total_internal_reflection_onset > angle_of_incidence: | |
R_s = -sin(angle_of_incidence - angle_of_refraction)\ | |
/sin(angle_of_incidence + angle_of_refraction) | |
R_p = tan(angle_of_incidence - angle_of_refraction)\ | |
/tan(angle_of_incidence + angle_of_refraction) | |
T_s = 2*sin(angle_of_refraction)*cos(angle_of_incidence)\ | |
/sin(angle_of_incidence + angle_of_refraction) | |
T_p = 2*sin(angle_of_refraction)*cos(angle_of_incidence)\ | |
/(sin(angle_of_incidence + angle_of_refraction)\ | |
*cos(angle_of_incidence - angle_of_refraction)) | |
return [R_p, R_s, T_p, T_s] | |
else: | |
n = n2/n1 | |
R_s = cancel((cos(angle_of_incidence)-\ | |
I*sqrt(sin(angle_of_incidence)**2 - n**2))\ | |
/(cos(angle_of_incidence)+\ | |
I*sqrt(sin(angle_of_incidence)**2 - n**2))) | |
R_p = cancel((n**2*cos(angle_of_incidence)-\ | |
I*sqrt(sin(angle_of_incidence)**2 - n**2))\ | |
/(n**2*cos(angle_of_incidence)+\ | |
I*sqrt(sin(angle_of_incidence)**2 - n**2))) | |
return [R_p, R_s] | |
def deviation(incident, medium1, medium2, normal=None, plane=None): | |
""" | |
This function calculates the angle of deviation of a ray | |
due to refraction at planar surface. | |
Parameters | |
========== | |
incident : Matrix, Ray3D, sequence or float | |
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 angular deviation between incident and refracted rays | |
Examples | |
======== | |
>>> from sympy.physics.optics import deviation | |
>>> from sympy.geometry import Point3D, Ray3D, Plane | |
>>> from sympy.matrices import Matrix | |
>>> from sympy import symbols | |
>>> n1, n2 = symbols('n1, n2') | |
>>> 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)) | |
>>> deviation(r1, 1, 1, n) | |
0 | |
>>> deviation(r1, n1, n2, plane=P) | |
-acos(-sqrt(-2*n1**2/(3*n2**2) + 1)) + acos(-sqrt(3)/3) | |
>>> round(deviation(0.1, 1.2, 1.5), 5) | |
-0.02005 | |
""" | |
refracted = refraction_angle(incident, | |
medium1, | |
medium2, | |
normal=normal, | |
plane=plane) | |
try: | |
angle_of_incidence = Float(incident) | |
except TypeError: | |
angle_of_incidence = None | |
if angle_of_incidence is not None: | |
return float(refracted) - angle_of_incidence | |
if refracted != 0: | |
if isinstance(refracted, Ray3D): | |
refracted = Matrix(refracted.direction_ratio) | |
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 None: | |
if not isinstance(normal, Matrix): | |
if is_sequence(normal): | |
_normal = Matrix(normal) | |
elif isinstance(normal, Ray3D): | |
_normal = Matrix(normal.direction_ratio) | |
else: | |
raise TypeError( | |
"normal should be a Matrix, Ray3D, or sequence") | |
else: | |
_normal = normal | |
else: | |
_normal = Matrix(plane.normal_vector) | |
mag_incident = sqrt(sum(i**2 for i in _incident)) | |
mag_normal = sqrt(sum(i**2 for i in _normal)) | |
mag_refracted = sqrt(sum(i**2 for i in refracted)) | |
_incident /= mag_incident | |
_normal /= mag_normal | |
refracted /= mag_refracted | |
i = acos(_incident.dot(_normal)) | |
r = acos(refracted.dot(_normal)) | |
return i - r | |
def brewster_angle(medium1, medium2): | |
""" | |
This function calculates the Brewster's angle of incidence to Medium 2 from | |
Medium 1 in radians. | |
Parameters | |
========== | |
medium 1 : Medium or sympifiable | |
Refractive index of Medium 1 | |
medium 2 : Medium or sympifiable | |
Refractive index of Medium 1 | |
Examples | |
======== | |
>>> from sympy.physics.optics import brewster_angle | |
>>> brewster_angle(1, 1.33) | |
0.926093295503462 | |
""" | |
n1 = refractive_index_of_medium(medium1) | |
n2 = refractive_index_of_medium(medium2) | |
return atan2(n2, n1) | |
def critical_angle(medium1, medium2): | |
""" | |
This function calculates the critical angle of incidence (marking the onset | |
of total internal) to Medium 2 from Medium 1 in radians. | |
Parameters | |
========== | |
medium 1 : Medium or sympifiable | |
Refractive index of Medium 1. | |
medium 2 : Medium or sympifiable | |
Refractive index of Medium 1. | |
Examples | |
======== | |
>>> from sympy.physics.optics import critical_angle | |
>>> critical_angle(1.33, 1) | |
0.850908514477849 | |
""" | |
n1 = refractive_index_of_medium(medium1) | |
n2 = refractive_index_of_medium(medium2) | |
if n2 > n1: | |
raise ValueError('Total internal reflection impossible for n1 < n2') | |
else: | |
return asin(n2/n1) | |
def lens_makers_formula(n_lens, n_surr, r1, r2, d=0): | |
""" | |
This function calculates focal length of a lens. | |
It follows cartesian sign convention. | |
Parameters | |
========== | |
n_lens : Medium or sympifiable | |
Index of refraction of lens. | |
n_surr : Medium or sympifiable | |
Index of reflection of surrounding. | |
r1 : sympifiable | |
Radius of curvature of first surface. | |
r2 : sympifiable | |
Radius of curvature of second surface. | |
d : sympifiable, optional | |
Thickness of lens, default value is 0. | |
Examples | |
======== | |
>>> from sympy.physics.optics import lens_makers_formula | |
>>> from sympy import S | |
>>> lens_makers_formula(1.33, 1, 10, -10) | |
15.1515151515151 | |
>>> lens_makers_formula(1.2, 1, 10, S.Infinity) | |
50.0000000000000 | |
>>> lens_makers_formula(1.33, 1, 10, -10, d=1) | |
15.3418463277618 | |
""" | |
if isinstance(n_lens, Medium): | |
n_lens = n_lens.refractive_index | |
else: | |
n_lens = sympify(n_lens) | |
if isinstance(n_surr, Medium): | |
n_surr = n_surr.refractive_index | |
else: | |
n_surr = sympify(n_surr) | |
d = sympify(d) | |
focal_length = 1/((n_lens - n_surr) / n_surr*(1/r1 - 1/r2 + (((n_lens - n_surr) * d) / (n_lens * r1 * r2)))) | |
if focal_length == zoo: | |
return S.Infinity | |
return focal_length | |
def mirror_formula(focal_length=None, u=None, v=None): | |
""" | |
This function provides one of the three parameters | |
when two of them are supplied. | |
This is valid only for paraxial rays. | |
Parameters | |
========== | |
focal_length : sympifiable | |
Focal length of the mirror. | |
u : sympifiable | |
Distance of object from the pole on | |
the principal axis. | |
v : sympifiable | |
Distance of the image from the pole | |
on the principal axis. | |
Examples | |
======== | |
>>> from sympy.physics.optics import mirror_formula | |
>>> from sympy.abc import f, u, v | |
>>> mirror_formula(focal_length=f, u=u) | |
f*u/(-f + u) | |
>>> mirror_formula(focal_length=f, v=v) | |
f*v/(-f + v) | |
>>> mirror_formula(u=u, v=v) | |
u*v/(u + v) | |
""" | |
if focal_length and u and v: | |
raise ValueError("Please provide only two parameters") | |
focal_length = sympify(focal_length) | |
u = sympify(u) | |
v = sympify(v) | |
if u is oo: | |
_u = Symbol('u') | |
if v is oo: | |
_v = Symbol('v') | |
if focal_length is oo: | |
_f = Symbol('f') | |
if focal_length is None: | |
if u is oo and v is oo: | |
return Limit(Limit(_v*_u/(_v + _u), _u, oo), _v, oo).doit() | |
if u is oo: | |
return Limit(v*_u/(v + _u), _u, oo).doit() | |
if v is oo: | |
return Limit(_v*u/(_v + u), _v, oo).doit() | |
return v*u/(v + u) | |
if u is None: | |
if v is oo and focal_length is oo: | |
return Limit(Limit(_v*_f/(_v - _f), _v, oo), _f, oo).doit() | |
if v is oo: | |
return Limit(_v*focal_length/(_v - focal_length), _v, oo).doit() | |
if focal_length is oo: | |
return Limit(v*_f/(v - _f), _f, oo).doit() | |
return v*focal_length/(v - focal_length) | |
if v is None: | |
if u is oo and focal_length is oo: | |
return Limit(Limit(_u*_f/(_u - _f), _u, oo), _f, oo).doit() | |
if u is oo: | |
return Limit(_u*focal_length/(_u - focal_length), _u, oo).doit() | |
if focal_length is oo: | |
return Limit(u*_f/(u - _f), _f, oo).doit() | |
return u*focal_length/(u - focal_length) | |
def lens_formula(focal_length=None, u=None, v=None): | |
""" | |
This function provides one of the three parameters | |
when two of them are supplied. | |
This is valid only for paraxial rays. | |
Parameters | |
========== | |
focal_length : sympifiable | |
Focal length of the mirror. | |
u : sympifiable | |
Distance of object from the optical center on | |
the principal axis. | |
v : sympifiable | |
Distance of the image from the optical center | |
on the principal axis. | |
Examples | |
======== | |
>>> from sympy.physics.optics import lens_formula | |
>>> from sympy.abc import f, u, v | |
>>> lens_formula(focal_length=f, u=u) | |
f*u/(f + u) | |
>>> lens_formula(focal_length=f, v=v) | |
f*v/(f - v) | |
>>> lens_formula(u=u, v=v) | |
u*v/(u - v) | |
""" | |
if focal_length and u and v: | |
raise ValueError("Please provide only two parameters") | |
focal_length = sympify(focal_length) | |
u = sympify(u) | |
v = sympify(v) | |
if u is oo: | |
_u = Symbol('u') | |
if v is oo: | |
_v = Symbol('v') | |
if focal_length is oo: | |
_f = Symbol('f') | |
if focal_length is None: | |
if u is oo and v is oo: | |
return Limit(Limit(_v*_u/(_u - _v), _u, oo), _v, oo).doit() | |
if u is oo: | |
return Limit(v*_u/(_u - v), _u, oo).doit() | |
if v is oo: | |
return Limit(_v*u/(u - _v), _v, oo).doit() | |
return v*u/(u - v) | |
if u is None: | |
if v is oo and focal_length is oo: | |
return Limit(Limit(_v*_f/(_f - _v), _v, oo), _f, oo).doit() | |
if v is oo: | |
return Limit(_v*focal_length/(focal_length - _v), _v, oo).doit() | |
if focal_length is oo: | |
return Limit(v*_f/(_f - v), _f, oo).doit() | |
return v*focal_length/(focal_length - v) | |
if v is None: | |
if u is oo and focal_length is oo: | |
return Limit(Limit(_u*_f/(_u + _f), _u, oo), _f, oo).doit() | |
if u is oo: | |
return Limit(_u*focal_length/(_u + focal_length), _u, oo).doit() | |
if focal_length is oo: | |
return Limit(u*_f/(u + _f), _f, oo).doit() | |
return u*focal_length/(u + focal_length) | |
def hyperfocal_distance(f, N, c): | |
""" | |
Parameters | |
========== | |
f: sympifiable | |
Focal length of a given lens. | |
N: sympifiable | |
F-number of a given lens. | |
c: sympifiable | |
Circle of Confusion (CoC) of a given image format. | |
Example | |
======= | |
>>> from sympy.physics.optics import hyperfocal_distance | |
>>> round(hyperfocal_distance(f = 0.5, N = 8, c = 0.0033), 2) | |
9.47 | |
""" | |
f = sympify(f) | |
N = sympify(N) | |
c = sympify(c) | |
return (1/(N * c))*(f**2) | |
def transverse_magnification(si, so): | |
""" | |
Calculates the transverse magnification upon reflection in a mirror, | |
which is the ratio of the image size to the object size. | |
Parameters | |
========== | |
so: sympifiable | |
Lens-object distance. | |
si: sympifiable | |
Lens-image distance. | |
Example | |
======= | |
>>> from sympy.physics.optics import transverse_magnification | |
>>> transverse_magnification(30, 15) | |
-2 | |
""" | |
si = sympify(si) | |
so = sympify(so) | |
return (-(si/so)) | |