aphirst icon

SOS2DFTN

aphirst | PRO | 06/05/14 11:25:27 AM UTC | 0 ⭐ | 10165 👁️ | Never ⏰ | []
Fortran |

10.46 KB

|

None

|

0 👍

/

0 👎

! This file is part of SOS2DFTN.
! Copyright (C) 2014 Adam Hirst <[email protected]>
!
! SOS2DFTN is free software: you can redistribute it and/or modify
! it under the terms of the GNU General Public License as published by
! the Free Software Foundation, either version 3 of the License, or
! (at your option) any later version.
!
! SOS2DFTN is distributed in the hope that it will be useful,
! but WITHOUT ANY WARRANTY; without even the implied warranty of
! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
! GNU General Public License for more details.
!
! You should have received a copy of the GNU General Public License
! along with SOS2DFTN. If not, see <http://www.gnu.org/licenses/>.
 
module Constants
  implicit none
 
  real, parameter :: pi = 4 * atan(1.0)
 
end module Constants
 
module Types
  use Constants
  implicit none
 
  type Emitter
    ! The extended emitter lies on the x-axis
    ! edges of element are `-R` and `+R`, units in mm
    ! `-R` also referred to as `R'` in literature
    real :: R
    ! prescribed response at the z-axis
    real :: L_z
    ! prescribed response is defined from the z-axis down to some minimum angle
    real :: theta_min
  contains
    ! prescribed response in terms of the emitter's angular projected width
    procedure :: L => Response
  end type Emitter
 
  type SurfacePoint
    ! points exist in x-z plane, units in mm
    real :: P(2)
    ! we also store the normal unit-vector
    real :: N(2)
  end type SurfacePoint
 
  type Surface
    ! the surface points themselves
    type(SurfacePoint), allocatable :: A(:)
    ! total number of points comprising the surface
    integer :: J
  end type Surface
 
  type, extends(Surface) :: Lens
    ! Refractive index of the lens.
    real    :: n_1
    ! Number of oval-segments for the initial lens portion, where # oval points = # oval segments + 1
    integer :: N
  contains
    ! The surface is "seeded" with a Cartesian oval from the x-axis up to the emitter's minimum prescription angle.
    procedure :: Seed => SeedLens
    procedure :: Generate
  end type Lens
 
contains
 
  pure function Reflect(i, n) result (rfx)
    ! Returns the unit vector of a ray after reflection, given unit vectors of the `i`ncident ray and the surface `n`ormal.
    ! "Introduction to Nonimaging Optics" (Chaves, 2008), Section 17.15
    real, intent(in) :: i(2), n(2)
    real             :: rfx(2)
 
    rfx = i - 2*dot_product(i,n)*n
  end function Reflect
 
  pure function Refract(i, n_S, n_1) result (rfr)
    ! Returns the unit-vector of a ray after refraction, given unit vectors of the `i`ncident ray and surface normal `n_S`, and the
    ! refractive index of the incident medium `n_1`.
    ! We assume that the second medium is air, i.e. that `n_2 = 1`.
    ! "Introduction to Nonimaging Optics" (Chaves, 2008), Section 17.15
    real, intent(in) :: i(2), n_S(2), n_1
    real             :: rfr(2), n(2), delta
 
    if (dot_product(i,n_S) >= 0) then
      n = n_S
    else
      n = -n_S
    end if
    delta = 1 - (n_1**2) * (1 - dot_product(i,n)**2)
    if (delta > 0) then
      rfr = i*n_1 + n*(sqrt(delta) - n_1*dot_product(i,n))
    else
      rfr = Reflect(i,n_S)
    end if
  end function Refract
 
  pure function IntersectLines(P, v, Q, u) result (isl)
    ! Returns the point of intersection of two lines, each defined as a point (`P` and `Q`) and a direction (`v` and `u`).
    ! "Introduction to Nonimaging Optics" (Chaves, 2008), Section 17.15
    real, intent(in) :: P(2), v(2), Q(2), u(2)
    real             :: isl(2), u_P(2)
 
    u_P = [-u(2), u(1)]
    isl = P + dot_product((Q-P),u_P)/dot_product(v,u_P) * v
  end function IntersectLines
 
  pure function RefractionNormal(i, r, n_1) result (rfrnrm)
    ! Returns the surface normal unit-vector given that an `i`ncident ray is `r`efracted from a medium of index `n_1` into air.
    ! "Introduction to Nonimaging Optics" (Chaves, 2008), Section 17.15
    real, intent(in) :: i(2), r(2), n_1
    real             :: rfrnrm(2)
 
    rfrnrm = n_1*i - r; rfrnrm = rfrnrm / norm2(rfrnrm)
  end function RefractionNormal
 
  pure function CartesianOval(F, n_1, P, alpha, phi) result (cop)
    ! Returns the surface point of the Cartesian oval which collimates rays from `F` in a medium of refractive index `n_1` into air
    ! at an angle `alpha`, which intersects the point `P`, in terms of `phi` (the angle about `F` above `alpha`).
    ! "Introduction to Nonimaging Optics" (Chaves, 2008), Section 17.15
    real, intent(in) :: F, n_1, P, alpha, phi
    ! Note that `F` and `P` are scalar, as are defined to lie on the x-axis.
    ! We additionally assume that `F` lies on `-x` and that P lies on `+x`.
    real             :: cop(2)
 
    cop = (P - F)*(n_1 - cos(alpha))*[cos(phi+alpha), sin(phi+alpha)] / (n_1 - cos(phi)) + [F,0.0]
  end function CartesianOval
 
  pure function CartesianOvalNormal(F, n_1, P, alpha, phi) result (copn)
    ! Returns the unit-vector of the normal to the surface of the above-described Cartesian oval at a given parameter `phi`.
    real, intent(in) :: F, n_1, P, alpha, phi
    real             :: copn(2)
 
    copn = (P - F)*(n_1 - cos(alpha))*[n_1*cos(phi+alpha) - cos(alpha), n_1*sin(phi+alpha) - sin(alpha)] / (n_1 - cos(phi))**2
    copn = copn / norm2(copn)
  end function CartesianOvalNormal
 
  function CartesianOvalProjection(F, n_1, P, alpha, L) result (phi)
    ! Returns the parameter `phi` corresponding to the end of the Cartesian oval with projected width `L` in the direction `alpha`.
    real, intent(in) :: F, n_1, P, alpha, L
    real             :: phi, C, phis(2), phi_C
 
    C = (P - F)*(n_1 - cos(alpha)) / ((P - F)*sin(alpha) - L)
    if (C**2 < n_1**2 - 1) then
      stop 'Projection and oval do not intersect.'
    end if
    ! `[1,-1]` indicates `\pm`, as lines intersect closed curves twice.
    phis(:) = 2 * atan( ([1, -1]*sqrt(C**2 - n_1**2 + 1.0) - C) / (n_1+1.0) )
    ! The intersecting line necessarily has positive slope (`alpha` is always in the positive x-z quadrant), so take the
    ! intersection closest to `phi = 0`.
    phi = phis( minloc(abs(phis),1) )
    ! `phi` must also correspond to a point on the oval within a valid range, namely that which permits refraction onto `F`.
    phi_C = pi/2 - asin(min(1.0,n_1)/max(1.0,n_1))
    if (abs(phi) > phi_C) then
      stop 'Projection does not intersect with refractive portion of oval.'
    else if (phi < -alpha) then
      stop 'Projection does not intersect above the x-axis.'
    end if
  end function CartesianOvalProjection
 
  pure real function Response(this, theta) result (L)
    ! Returns the prescribed angular response of the emitter at the given `theta`.
    ! "Nonimaging Optics" (Winston et al., 2005), Section 7.4.2
    class(Emitter), intent(in) :: this
    real,           intent(in) :: theta
 
    if (theta >= this%theta_min) then
      L = this%L_z * (1 + (2*theta - pi)/(2*this%theta_min - pi))
    else
      L = 0.0
    end if
  end function Response
 
  subroutine SeedLens(this, my_emitter, A_0)
    class(Lens),   intent(in out) :: this
    type(Emitter), intent(in)     :: my_emitter
    real,          intent(in)     :: A_0
    real                          :: phi_max
    integer :: i
 
    associate (R => my_emitter%R, n_1 => this%n_1, alpha => my_emitter%theta_min, N => this%N)
      phi_max = CartesianOvalProjection(-R, n_1, A_0, alpha, my_emitter%L(alpha))
      allocate(this%A(0:N))
      this%A(0)%P = [A_0, 0.0]
      do concurrent (i = 1:N)
        this%A(i)%P = CartesianOval(-R, n_1, A_0, alpha, i*(phi_max+alpha)/N - alpha)
      end do
      do concurrent (i = 0:N)
        this%A(i)%N = CartesianOvalNormal(-R, n_1, A_0, alpha, i*(phi_max+alpha)/N - alpha)
      end do
      this%J = N
    end associate
  end subroutine SeedLens
 
  pure subroutine Generate(this, my_emitter)
    class(Lens),         intent(in out)              :: this
    type(Emitter),       intent(in)                  :: my_emitter
    integer                                          :: i
    real                                             :: i_i(2), r_i(2), theta_i
    type(SurfacePoint)                               :: B
    type(SurfacePoint),                  allocatable :: A_new(:)
 
    ! for `i = 1` upwards
    i = 1
    do
      ! Refract from `R` through `A(i)` to get projection angle `theta_i` (and `r_i`, the unit-vector in that direction).
      ! `i_i` and `r_i` are the `i`th `i`ncident and `r`efracted ray directions respectively
      i_i = (this%A(i)%P - [my_emitter%R, 0.0]); i_i = i_i / norm2(i_i)
      r_i = Refract(i_i, this%A(i)%N, this%n_1)
      ! `r_i` is a unit vector, and unit vectors are equal to `[cos(theta), sin(theta)]`
      theta_i = acos(r_i(1))
      ! Intersect the line parallel to `r_i` perpendicularly separated from `A(i)` by `L(theta_i)` with the tangent to `A(N+i-1)` to
      ! get the next point in the surface, `A(N+i)`.
      associate (L => my_emitter%L(theta_i), P => this%A(i)%P, Q => this%A(this%N+i-1)%P, N => this%A(this%N+i-1)%N)
        ! TODO: Use a different offset for `Q` to avoid the need to compute `sin(theta_i)`
        B%P = IntersectLines(P - [L/sin(theta_i), 0.0], r_i, Q, [-N(2), N(1)])
      end associate
      ! Compute the normal to `A(N+i)`, from the fact that light from `R'` through `A(N+i)` must have resulting direction `theta_L`.
      i_i = B%P + [my_emitter%R, 0.0]; i_i = i_i / norm2(i_i)
      B%N = RefractionNormal(i_i, r_i, this%n_1)
      ! if the new point has not crossed the z-axis, simply store it
      if (B%P(1) >= 0.0) then
        ! TODO: implement chunked reallocation of `type(SurfacePoint)` array
        this%J = this%J + 1
        allocate(A_new(0:this%J))
        A_new(0:) = [this%A(0:), B]
        call move_alloc(A_new, this%A)
      else
        ! TODO: store the intersection of the new surface segment with the z-axis, and then terminate
        return
      end if
      ! keep increasing `i` until done
      i = i + 1
    end do
  end subroutine Generate
 
end module Types
 
program SOS2DFTN
  use Types
  implicit none
 
  type(Emitter) :: LED = Emitter(1.0, 6.0, pi/6)
  type(Lens)    :: glass
  integer       :: i
 
  ! TODO: %Seed should also store `N` and `n_1` in the `type(Lens)` object
  glass%N = 50; glass%n_1 = 1.49
  call glass%Seed(LED, 15.0)
  call glass%Generate(LED)
  do i = 0, glass%J
    print *, 'Point: ', glass%A(i)%P
    if (i == glass%N) print *, '##########'
  end do
 
end program SOS2DFTN

Comments

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎