Skip to content

Feature Request: Add support for Gradient Index (GRIN) surfaces #337

@goldengrape

Description

@goldengrape

Checklist

  • I have searched the existing issues and discussions for a similar question or feature request.
  • I have read the documentation and tried to find an answer there.
  • I am using the latest version of Optiland.
  • I have included all necessary context.

Thanks for taking the time to go through this — it really helps us help you!

Feature Request

Of course. Here is the final draft of the issue, written in English and incorporating all the details you've provided. You can copy and paste this directly into the GitHub issue submission form.


Feature Request

Is your feature request related to a problem? Please describe.
Currently, Optiland lacks support for surfaces with a gradient refractive index (GRIN), such as the gradient3 surface type available in Zemax. This limits the software's capability to design and simulate optical systems that utilize GRIN lenses. These lenses are crucial for correcting aberrations and reducing the number of optical elements in a system.

Furthermore, GRIN lenses are of significant importance in the fields of ophthalmology and optometry for simulating realistic eye models. Many established models, such as the Atchison and Liou-Brennan model eyes, rely on gradient refractive indices to accurately represent the human eye's crystalline lens. The absence of this feature in Optiland is a considerable limitation for researchers and engineers working on ophthalmic and vision science applications.

Describe the solution you'd like
I would like to request the addition of a new surface type that supports a gradient refractive index, similar to Zemax's gradient3. This would involve implementing a ray tracing algorithm capable of handling propagation through a medium with a continuously varying refractive index.

Describe alternatives you've considered
I have considered using a series of shells with uniform refractive indices to approximate a gradient index. However, this method is computationally intensive and would significantly slow down simulations, making it an impractical alternative for complex or iterative design processes.

Additional context
The importance of GRIN lenses is particularly evident in the simulation of human eye models, where they are used to accurately model the crystalline lens. For example, the Atchison (doi:10.1016/j.visres.2006.01.004) and Liou-Brennan model eyes (doi: 10.1364/josaa.14.001684) both utilize gradient-index lenses, which is a key reason for their anatomical and optical accuracy. Adding this functionality would make Optiland a much more powerful tool for ophthalmic and vision science research.

To assist with implementation, I am providing pseudocode for a ray tracing algorithm within a gradient medium. This algorithm uses the Runge-Kutta (RK4) method to solve the ray equation in a medium with a refractive index profile defined by radial and axial coefficients.

Here is the pseudocode:

// -------------------------------------------------------------------------
// Function: F_derivative(z, v, params)
// Purpose: Calculate the state derivative vector F = dv/dz at a given point (z, v)
// Inputs:
//   z: current z-coordinate (float)
//   v: current state vector [x, y, Tx, Ty, OP] (5D array)
//   params: coefficients of the Gradient3 medium (struct or object)
// Output:
//   Derivative vector F (5D array), or an error flag (e.g., null) if total internal reflection occurs
// -------------------------------------------------------------------------
FUNCTION F_derivative(z, v, params):
    // 1. Unpack the state vector
    x = v[0], y = v[1], Tx = v[2], Ty = v[3]

    // 2. Efficiently calculate n and its partial derivatives
    r_sq = x*x + y*y
    r_sq_2 = r_sq * r_sq
    r_sq_3 = r_sq_2 * r_sq

    n = params.n0 + params.nr2*r_sq + params.nr4*r_sq_2 + params.nr6*r_sq_3 +
        params.nz1*z + params.nz2*z*z + params.nz3*z*z*z

    g_r = 2*params.nr2 + 4*params.nr4*r_sq + 6*params.nr6*r_sq_2
    dn_dx = x * g_r
    dn_dy = y * g_r

    // 3. Calculate the Hamiltonian H and check for validity
    n_sq = n*n
    T_sq_transverse = Tx*Tx + Ty*Ty

    radicand = n_sq - T_sq_transverse // Value inside the square root
    IF radicand <= 0 THEN
        // Total internal reflection or ray is perpendicular to the z-axis, cannot continue tracing with z as the step
        RETURN null // Return an error flag
    END IF

    H = -sqrt(radicand)

    // 4. Calculate and return the derivative vector F
    inv_H = 1.0 / H // Pre-calculate the inverse to avoid multiple divisions
    F = [
        -Tx * inv_H,
        -Ty * inv_H,
        -n * dn_dx * inv_H,
        -n * dn_dy * inv_H,
        -n_sq * inv_H
    ]
    RETURN F
END FUNCTION


// -------------------------------------------------------------------------
// Main procedure: RayTrace_Gradient3_RK4
// -------------------------------------------------------------------------
PROCEDURE RayTrace_Gradient3_RK4:
    // 1. Initialization
    // Define medium parameters
    gradient_params = {n0, nr2, nr4, nr6, nz1, nz2, nz3}

    // Define initial ray state
    z_start = 0.0
    z_end = 10.0
    step_size = 0.1

    initial_pos = [x0, y0]
    initial_dir = [dir_x0, dir_y0, dir_z0] // Unit direction vector

    // Calculate the initial state vector based on initial conditions
    n_start = calculate_n(z_start, initial_pos, gradient_params) // Requires a helper function
    Tx0 = n_start * initial_dir[0]
    Ty0 = n_start * initial_dir[1]

    z = z_start
    v = [initial_pos[0], initial_pos[1], Tx0, Ty0, 0.0] // [x, y, Tx, Ty, OP]

    // Store the trajectory
    ray_path = []
    ADD {z: z, state: v} TO ray_path

    // 2. RK4 main loop
    WHILE z < z_end:
        // Ensure the last step ends exactly at z_end
        current_step = min(step_size, z_end - z)

        // Calculate K1
        K1 = F_derivative(z, v, gradient_params)
        IF K1 IS null THEN BREAK // Error handling

        // Calculate K2
        v_temp2 = v + (current_step/2.0) * K1
        K2 = F_derivative(z + current_step/2.0, v_temp2, gradient_params)
        IF K2 IS null THEN BREAK // Error handling

        // Calculate K3
        v_temp3 = v + (current_step/2.0) * K2
        K3 = F_derivative(z + current_step/2.0, v_temp3, gradient_params)
        IF K3 IS null THEN BREAK // Error handling

        // Calculate K4
        v_temp4 = v + current_step * K3
        K4 = F_derivative(z + current_step, v_temp4, gradient_params)
        IF K4 IS null THEN BREAK // Error handling

        // 3. Update the state vector and z-coordinate
        v = v + (current_step / 6.0) * (K1 + 2*K2 + 2*K3 + K4)
        z = z + current_step

        ADD {z: z, state: v} TO ray_path

    END WHILE

    // 4. Output the results
    IF z >= z_end THEN
      PRINT "Ray tracing completed successfully."
      PRINT "Final state at z = ", z, ":"
      PRINT "  Position (x, y) = (", v[0], ", ", v[1], ")"
      PRINT "  Direction (Tx, Ty) = (", v[2], ", ", v[3], ")"
      PRINT "  Optical Path (OP) = ", v[4]
    ELSE
      PRINT "Ray tracing terminated early due to an error (e.g., total internal reflection) at z = ", z
    END IF

END PROCEDURE

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions