123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698 |
- """
- **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, 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))
|