.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "examples\plot_light_ray_nonhomogenious_medium.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_examples_plot_light_ray_nonhomogenious_medium.py: Light Ray in Non-Homogeneous Medium =================================== Description. ----------- A light ray travels from A to B in a non-homogeneous 2D medium. As per Fermat's principle, the light ray will follow the path that minimizes the travel time. It is solved in two different ways: - Variational approach using Euler's equations, and using **solve_bvp** for the boundary value problem. - Let **opty** find the best way for the light ray. Here numerical trajectories are used to show their use in a simple problem. Notes ----- - Currently opty does not allow numerical trajectories to depend on more than one variable, see opty issue #494. So two separate numerical trajectories must be defined. This causes the constraint equation of the eoms to look a bit complicated. - For some reason, only ``backend='numpy'`` works, ``backend='cython'`` raises an error, see opty issue #558. As opty converges very fast with this simulation is not a problem. - Of course, this simulation would have worked without using numerical trajectories, they are used here to demonstrate how to use them. - The optimal trajectory 'reverses itself', so it cannot be described as a function y(x), but must be described parametrically as :math:`x(\tau), y(\tau)` in the variational formulation. (With opty it is described like this anyway) - This simple example shows, that both *opty* and the classical variational approach only find *local* stationary solutions (variational method) or *local* minimal solution (opty): changing ``way_points`` or initial guesses can lead to very different solutions. - Increasing ``fps`` in the animation will make the motion smoother, but at the expense of increase storage requirements. .. GENERATED FROM PYTHON SOURCE LINES 43-54 .. code-block:: Python import sympy as sm import sympy.physics.mechanics as me import numpy as np import matplotlib.pyplot as plt from opty import Problem from scipy.integrate import solve_bvp from scipy.interpolate import CubicSpline from matplotlib.animation import FuncAnimation .. GENERATED FROM PYTHON SOURCE LINES 55-56 Define the scalar speed field. .. GENERATED FROM PYTHON SOURCE LINES 56-83 .. code-block:: Python constant = 1.02 ox, oy = sm.symbols('ox oy') def scalar_x(ox): return sm.sin(sm.exp(0.1 * ox)) def scalar_y(oy): return sm.cos(oy) def scalar_speed_field(ox, oy): return scalar_x(ox) * scalar_y(oy) + constant speed_field_lam = sm.lambdify((ox, oy), scalar_speed_field(ox, oy)) scalar_x_lam = sm.lambdify(ox, scalar_x(ox)) scalar_y_lam = sm.lambdify(oy, scalar_y(oy)) speed_field_dx = scalar_x(ox).diff(ox) speed_field_dy = scalar_y(oy).diff(oy) speed_field_dx_lam = sm.lambdify((ox), speed_field_dx) speed_field_dy_lam = sm.lambdify((oy), speed_field_dy) .. GENERATED FROM PYTHON SOURCE LINES 84-172 **Variational Problem** A light ray travels from A to B in a non-homogenious medium i 2D. At each point P(x/y) it's speed is given by v(x, y). By Fermat's rule a path will be selected which minimizes - :math:`T = \displaystyle \int_{path} \dfrac{ds}{v(x, y)} = \int_{path} \dfrac{\sqrt{(x^{'})^2 + (y^{'})^2}}{v(x, y)} d\tau` where :math:`\tau` is some arbitrary parameter and :math:`{}^{'} = \dfrac{d}{d\tau}` As here the path may 'double back' it cannot be described as a function :math:`y(x)` but a description :math:`x(\tau), y(\tau)` is needed. **Euler's Equations** - :math:`L(x, y, x', y') = \dfrac{\sqrt{(x^{'})^2 + (y^{'})^2}}{v(x(\tau), y(\tau))}` so Euler's equations are: - :math:`\dfrac{d}{d\tau} \left(\dfrac{\partial{L}}{\partial{x'}}\right) - \dfrac{\partial{L}}{\partial{x}} = 0` - :math:`\dfrac{d}{d\tau} \left(\dfrac{\partial{L}}{\partial{y'}}\right) - \dfrac{\partial{L}}{\partial{y}} = 0` Set :math:`n := \dfrac{1}{v(x, y)}`, then - :math:`\dfrac{\partial{L}}{\partial{x'}} = \dfrac{n \cdot x'}{\sqrt{(x')^2 + (y')^2}}` - :math:`\dfrac{\partial{L}}{\partial{x}} = \dfrac{\frac{\partial{n}}{\partial{x}}}{\sqrt{(x')^2 + (y')^2}}` and same for y, y' Now on sets :math:`\tau = s`, the arclength, then :math:`dx(s) = \cos(\theta)ds, dy(s) = \sin(\theta)` where :math:`\theta`, the angle of the tangent a :math:`P(x(s) / y(s))` So :math:`(x')^2 + (y')^2 = 1`, and the Euler equations are: - :math:`\dfrac{d}{ds}\left(\dfrac{\cos(\theta)}{v(x, y)}\right) - \dfrac{\partial}{\partial{x}}\left(\dfrac{1}{v(x, y)}\right) = 0` and similar for the y equation. - :math:`\dfrac{d}{ds}\left(\dfrac{\sin(\theta)}{v(x, y)}\right) - \dfrac{\partial}{\partial{y}}\left(\dfrac{1}{v(x, y)}\right) = 0` Calculating this and some manipulations give: - :math:`\dfrac{d\theta(s)}{ds} = \dfrac{v_x \sin(\theta(s)) - v_y\cos(\theta(s))}{v(x, y)}`, :math:`v_x = \frac{\partial{v}}{\partial{x}}` , :math:`v_y = \frac{\partial{v}}{\partial{y}}` Combined: - :math:`\dfrac{d}{ds} \begin{pmatrix} x \\ y \\ \theta \end{pmatrix} = \begin{pmatrix} \cos(\theta) \\ \sin(\theta) \\\dfrac{v_x \sin(\theta(s)) - v_y\cos(\theta(s))}{v(x, y)} \end{pmatrix}`, :math:`0 \leq s \leq S` A problem is that S, the length of the path, is unknown, but *solve_bvp* needs fixed intervals. Set :math:`t = \frac{s}{S} \in [0, 1]`, so :math:`\frac{d}{dt} = S \frac{d}{ds}` to get: - :math:`\dfrac{d}{dt} \begin{pmatrix} x \\ y \\ \theta \end{pmatrix} = S \cdot \begin{pmatrix} \cos(\theta) \\ \sin(\theta) \\\dfrac{v_x \sin(\theta(s)) - v_y\cos(\theta(s))}{v(x, y)} \end{pmatrix}` S is an unknown parameter, put p = [S] for solve_ivp to find its value. **Boundary conditions** x(0) = 0, y(0) = 0, x(1) = c, y(1) = d exactly the right number for the three differential equations and the unknown parameter S. .. GENERATED FROM PYTHON SOURCE LINES 174-177 Set up for solve_bvp -------------------- .. GENERATED FROM PYTHON SOURCE LINES 177-181 .. code-block:: Python X_SYM, Y_SYM = sm.symbols("x y") v_sym = scalar_speed_field(X_SYM, Y_SYM) v_f = sm.lambdify((X_SYM, Y_SYM), v_sym) .. GENERATED FROM PYTHON SOURCE LINES 182-183 The derivatives of the speed field with respect to x and y. .. GENERATED FROM PYTHON SOURCE LINES 183-186 .. code-block:: Python vx_f = sm.lambdify((X_SYM, Y_SYM), v_sym.diff(X_SYM)) vy_f = sm.lambdify((X_SYM, Y_SYM), v_sym.diff(Y_SYM)) .. GENERATED FROM PYTHON SOURCE LINES 187-188 Starting point of the light ray. .. GENERATED FROM PYTHON SOURCE LINES 188-190 .. code-block:: Python A = [0.0, 0.0] .. GENERATED FROM PYTHON SOURCE LINES 191-193 Intermediate points to help solve_bvp to converge. Last entry must be the final destination of the light ray. .. GENERATED FROM PYTHON SOURCE LINES 193-195 .. code-block:: Python way_points = [(5.0, 1.0), (9.0, 7.0), (10.0, 10.0)] .. GENERATED FROM PYTHON SOURCE LINES 196-197 Tolerance for the BVP solver. .. GENERATED FROM PYTHON SOURCE LINES 197-199 .. code-block:: Python tol = 1e-6 .. GENERATED FROM PYTHON SOURCE LINES 200-201 Right-hand side for *solve_bvp*. .. GENERATED FROM PYTHON SOURCE LINES 201-214 .. code-block:: Python def rhs(t, u, p): X, Y, th = u S = p[0] V = v_f(X, Y) return np.vstack(( S * np.cos(th), S * np.sin(th), S * (np.sin(th) * vx_f(X, Y) - np.cos(th) * vy_f(X, Y)) / V, )) .. GENERATED FROM PYTHON SOURCE LINES 215-219 Boundary conditions for the BVP. It must be given as a function that returns an array of residuals for the boundary conditions. If they are zero, the boundary conditions are satisfied. :math:`u_a` and :math:`u_b` are supplied by solve_ivp. .. GENERATED FROM PYTHON SOURCE LINES 219-228 .. code-block:: Python def make_bc(end): def bc(ua, ub, p): return np.array([ua[0] - A[0], ua[1] - A[1], ub[0] - end[0], ub[1] - end[1]]) return bc .. GENERATED FROM PYTHON SOURCE LINES 229-230 Function to compute the travel time along a solution path. .. GENERATED FROM PYTHON SOURCE LINES 230-237 .. code-block:: Python def travel_time(sol, n=4000): t = np.linspace(0, 1, n) X, Y, _ = sol.sol(t) return sol.p[0] * np.trapezoid(1.0 / v_f(X, Y), t) .. GENERATED FROM PYTHON SOURCE LINES 238-240 Function to compute the travel time along a straight line from A to the given endpoint. .. GENERATED FROM PYTHON SOURCE LINES 240-250 .. code-block:: Python def straight_line_time(end, n=4000): t = np.linspace(0, 1, n) X = A[0] + t * (end[0] - A[0]) Y = A[1] + t * (end[1] - A[1]) length = np.hypot(end[0] - A[0], end[1] - A[1]) return length * np.trapezoid(1.0 / v_f(X, Y), t) .. GENERATED FROM PYTHON SOURCE LINES 251-253 Function to compute an initial solution near the starting point. This helps solve_bvp to converge. .. GENERATED FROM PYTHON SOURCE LINES 253-271 .. code-block:: Python def initial_solution(first_target, frac=0.05): end = (A[0] + frac * (first_target[0] - A[0]), A[1] + frac * (first_target[1] - A[1])) L = np.hypot(end[0] - A[0], end[1] - A[1]) th0 = np.arctan2(end[1] - A[1], end[0] - A[0]) t = np.linspace(0, 1, 500) guess = np.vstack((A[0] + t * (end[0] - A[0]), A[1] + t * (end[1] - A[1]), np.full_like(t, th0))) sol = solve_bvp(rhs, make_bc(end), t, guess, p=[L], tol=tol, max_nodes=1000) if sol.status != 0: raise RuntimeError(f"Initial problem failed: {sol.message}") return sol, end .. GENERATED FROM PYTHON SOURCE LINES 272-273 A loop over the waypoints. .. GENERATED FROM PYTHON SOURCE LINES 273-289 .. code-block:: Python def move_endpoint(sol, start, target): start = np.array(start, float) target = np.array(target, float) end = start trial_end = target trial = solve_bvp(rhs, make_bc(trial_end), sol.x, sol.y, p=sol.p, tol=tol, max_nodes=1000000) if trial.status != 0: raise RuntimeError(f"Failed to move to waypoint {target}: " f"{trial.message}") else: return trial, end .. GENERATED FROM PYTHON SOURCE LINES 290-291 Solve the BVP for each waypoint in sequence. .. GENERATED FROM PYTHON SOURCE LINES 291-301 .. code-block:: Python solutions = {} sol, end = initial_solution(way_points[0]) for wp in way_points: sol, end = move_endpoint(sol, end, wp) solutions[wp] = sol if sol.status != 0: raise RuntimeError(f"Failed to move to waypoint {wp}: {sol.message}") .. GENERATED FROM PYTHON SOURCE LINES 302-303 Plot the solutions of the intermediate waypoints and the final solution. .. GENERATED FROM PYTHON SOURCE LINES 303-352 .. code-block:: Python last_wp = list(solutions)[-1] xmax = max(wp[0] for wp in solutions) + 1 ymax = max(wp[1] for wp in solutions) + 1 gx, gy = np.meshgrid(np.linspace(-1, xmax, 400), np.linspace(-1, ymax, 400)) fig, ax = plt.subplots(figsize=(6, 6)) ax.set_aspect('equal') im = ax.pcolormesh(gx, gy, v_f(gx, gy), shading="auto", cmap="viridis") ax.contour(gx, gy, v_f(gx, gy), levels=15, colors="white", linewidths=0.4, alpha=0.5) fig.colorbar(im, ax=ax, label="speed v(x, y)", shrink=0.7) extrema = [] farben = ["blue", "green", "red", "cyan", "magenta", "yellow"] i = -1 for wp, s in solutions.items(): i += 1 X, Y, _ = s.sol(np.linspace(0, 1, 250)) ax.plot(X, Y, lw=2, label=f"ray to {wp}", color=farben[i]) ax.plot(*wp, "ko") ax.plot([A[0], wp[0]], [A[1], wp[1]], "w--", lw=0.75) ax.plot(*A, "ro") ax.set_xlabel("x") ax.set_ylabel("y") ax.set_title(f"Solutions for the intermediate waypoints \n " f"The black vertical line shows that the solution \n " "'doubles back' so it is not a function ") ax.legend(loc="upper left") # Get points where the ray doubles back eps = 1e-3 delta = 35 # Get the indices, where i < j n = len(X) i_idx, j_idx = np.triu_indices(n, k=1) mask = ( (np.abs(X[i_idx] - X[j_idx]) < eps) & (np.abs(i_idx - j_idx) > delta) ) i = i_idx[mask] j = j_idx[mask] for ii, jj in zip(i, j): ax.scatter(X[ii], Y[ii], color="white", s=20, marker="x") ax.axvline(X[ii], color="black", linestyle="-", lw=0.75) .. image-sg:: /examples/images/sphx_glr_plot_light_ray_nonhomogenious_medium_001.png :alt: Solutions for the intermediate waypoints The black vertical line shows that the solution 'doubles back' so it is not a function :srcset: /examples/images/sphx_glr_plot_light_ray_nonhomogenious_medium_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 353-357 Set Up for opty --------------- Set up the symbolic equations of motion. .. GENERATED FROM PYTHON SOURCE LINES 357-371 .. code-block:: Python N = me.ReferenceFrame('N') O, P = sm.symbols('O P', cls=me.Point) O.set_vel(N, 0) t = me.dynamicsymbols._t x, y, ux, uy = me.dynamicsymbols('x y ux uy') vx, vy = me.dynamicsymbols('vx vy') P.set_pos(O, x * N.x + y * N.y) P.set_vel(N, ux * N.x + uy * N.y) wx = sm.symbols('wx', cls=sm.Function)(x) wy = sm.symbols('wy', cls=sm.Function)(y) .. GENERATED FROM PYTHON SOURCE LINES 372-378 The third eom araises like this: speed field :math:`v(x, y) = wx(x) \cdot wy(y) + c` where :math:`c` is a constant. The constraint is :math:`ux^2 + uy^2 = v(x, y)^2 = wx(x)^2 \cdot wy(y)^2 + 2 \cdot c \cdot wx(x) \cdot wy(y) + c^2` .. GENERATED FROM PYTHON SOURCE LINES 378-384 .. code-block:: Python eom = sm.Matrix([ x.diff(t) - ux, y.diff(t) - uy, ux**2 + uy**2 - (wx**2 * wy**2 + 2*constant*wy*wx + constant**2) ]) .. GENERATED FROM PYTHON SOURCE LINES 385-387 ``h`` is the step size for the discretization of the trajectory, it will be minimized by opty. .. GENERATED FROM PYTHON SOURCE LINES 387-389 .. code-block:: Python h = sm.symbols('h') .. GENERATED FROM PYTHON SOURCE LINES 390-391 Define some parameters, objects needed. .. GENERATED FROM PYTHON SOURCE LINES 391-397 .. code-block:: Python num_nodes = 1000 t0, tf = 0.0, h * (num_nodes - 1) interval_value = h state_symbols = [x, y, ux, uy] .. GENERATED FROM PYTHON SOURCE LINES 398-400 ``h`` should be bounded to be positive to avoid meaningless negative step sizes. .. GENERATED FROM PYTHON SOURCE LINES 400-404 .. code-block:: Python bounds = { h: (0.0001, 0.5) } .. GENERATED FROM PYTHON SOURCE LINES 405-406 Set the initial and final position of the light ray. .. GENERATED FROM PYTHON SOURCE LINES 406-413 .. code-block:: Python instance_constraints = [ x.func(t0), y.func(t0), x.func(tf) - 10.0, y.func(tf) - 10.0, ] .. GENERATED FROM PYTHON SOURCE LINES 414-416 Define the objective function and its gradient: Travel time to be minimized, that is ``h`` to be minimized. .. GENERATED FROM PYTHON SOURCE LINES 416-429 .. code-block:: Python def obj(prob, free): h_vals = prob.extract_values(free, h) return h_vals def obj_grad(free): grad = np.zeros_like(free) prob.fill_free(grad, 1.0, h) return grad .. GENERATED FROM PYTHON SOURCE LINES 430-435 Define the numerical trajectories. In the 'real world' these would be measurements of the velocity field. Note that for each numerical trajectory traj(x), opty also needs :math:`\dfrac{\partial{\text{traj(x)}}}{\partial{x}}` .. GENERATED FROM PYTHON SOURCE LINES 435-471 .. code-block:: Python x_meas = np.linspace(-5, 15, 1000) y_meas = np.linspace(-5, 15, 1000) speed_meas_x = scalar_x_lam(x_meas) speed_meas_y = scalar_y_lam(y_meas) speed_dx_meas = speed_field_dx_lam(x_meas) speed_dy_meas = speed_field_dy_lam(y_meas) def wx_traj(free): x_vals = free[0: num_nodes] return np.interp(x_vals, x_meas, speed_meas_x) def wy_traj(free): y_vals = free[num_nodes: 2 * num_nodes] return np.interp(y_vals, y_meas, speed_meas_y) def wx_dx_traj(free): x_vals = free[0: num_nodes] return np.interp(x_vals, x_meas, speed_dx_meas) * 1 def wy_dy_traj(free): y_vals = free[num_nodes: 2 * num_nodes] return np.interp(y_vals, y_meas, speed_dy_meas) * 1 known_trajectory_map = { wx: wx_traj, wy: wy_traj, wx.diff(x): wx_dx_traj, wy.diff(y): wy_dy_traj } .. GENERATED FROM PYTHON SOURCE LINES 472-473 Set up Problem. .. GENERATED FROM PYTHON SOURCE LINES 473-489 .. code-block:: Python prob = Problem( obj, obj_grad, eom, state_symbols, num_nodes, interval_value, known_trajectory_map=known_trajectory_map, instance_constraints=instance_constraints, bounds=bounds, time_symbol=t, backend='numpy' ) print(prob.num_free) .. rst-class:: sphx-glr-script-out .. code-block:: none 4001 .. GENERATED FROM PYTHON SOURCE LINES 490-491 Solve the problem. .. GENERATED FROM PYTHON SOURCE LINES 491-503 .. code-block:: Python initial_guess = np.concatenate([ np.linspace(0, 10, num_nodes), # x initial guess np.linspace(0, 1, num_nodes), # y initial guess np.ones(num_nodes), # ux initial guess np.ones(num_nodes), # uy initial guess [0.0001], ]) prob.add_option('max_iter', 3000) solution, info = prob.solve(initial_guess) print(info['status_msg']) .. rst-class:: sphx-glr-script-out .. code-block:: none b'Algorithm terminated successfully at a locally optimal point, satisfying the convergence tolerances (can be specified by options).' .. GENERATED FROM PYTHON SOURCE LINES 504-505 Plot the objective value. .. GENERATED FROM PYTHON SOURCE LINES 505-507 .. code-block:: Python _ = prob.plot_objective_value() .. image-sg:: /examples/images/sphx_glr_plot_light_ray_nonhomogenious_medium_002.png :alt: Objective Value :srcset: /examples/images/sphx_glr_plot_light_ray_nonhomogenious_medium_002.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 508-509 Plot the violations of the constraints. .. GENERATED FROM PYTHON SOURCE LINES 509-511 .. code-block:: Python _ = prob.plot_constraint_violations(solution, subplots=True) .. image-sg:: /examples/images/sphx_glr_plot_light_ray_nonhomogenious_medium_003.png :alt: Constraint violations :srcset: /examples/images/sphx_glr_plot_light_ray_nonhomogenious_medium_003.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 512-513 Plot the trajectories of the solution. .. GENERATED FROM PYTHON SOURCE LINES 513-515 .. code-block:: Python _ = prob.plot_trajectories(solution) .. image-sg:: /examples/images/sphx_glr_plot_light_ray_nonhomogenious_medium_004.png :alt: State Trajectories, Input Trajectories :srcset: /examples/images/sphx_glr_plot_light_ray_nonhomogenious_medium_004.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 516-517 Animation. .. GENERATED FROM PYTHON SOURCE LINES 517-599 .. code-block:: Python fps = 5 def f(x, y): return speed_field_lam(x, y) resultat, *_, h_val = prob.parse_free(solution) x_min = np.min(resultat[0, :]) x_max = np.max(resultat[0, :]) y_min = np.min(resultat[1, :]) y_max = np.max(resultat[1, :]) x_max = max(x_max, y_max) + 1.0 x_min = min(x_min, y_min) - 1.0 t_arr = prob.time_vector(solution) state_sol = CubicSpline(t_arr, resultat.T) xx = np.linspace(x_min, x_max, 400) yy = np.linspace(x_min, x_max, 400) X, Y = np.meshgrid(xx, yy) Z = f(X, Y) fig, ax = plt.subplots(figsize=(8, 6), constrained_layout=True) im = ax.pcolormesh(X, Y, Z, shading="auto", cmap="viridis") fig.colorbar(im, ax=ax, label="speed [m/sec]") ax.contour(X, Y, Z, levels=15, colors="white", linewidths=0.4, alpha=0.5) ax.set_xlim(x_min, x_max) ax.set_ylim(x_min, x_max) ax.set_xlabel("x") ax.set_ylabel("y") ax.set_aspect("equal") ax.scatter([0], [0], color="yellow", s=50, edgecolor="black", zorder=6) ax.scatter([10], [10], color="green", s=50, zorder=6, edgecolor="black") point, = ax.plot([], [], "o", color="red", mec="black", ms=11, zorder=5) trail, = ax.plot([], [], "-", color="red", lw=2, zorder=4) wp = (10.0, 10.0) s = solutions[wp] X, Y, _ = s.sol(np.linspace(0, 1, 500)) ax.plot(X, Y, lw=2, label=f"ray to {wp}", color="yellow") ax.plot(*wp, "ko") ax.plot([A[0], wp[0]], [A[1], wp[1]], "w--", lw=0.8) def position(t): return state_sol(t)[:2] T_var = travel_time(s) delta_times = (h_val * (num_nodes - 1) - T_var) / T_var * 100 t_straight = straight_line_time([10.0, 10.0]) def update(t): px, py = position(t) point.set_data([px], [py]) ts = np.linspace(0, t, 50) path = np.array([position(s) for s in ts]) trail.set_data(path[:, 0], path[:, 1]) msg = (f"Running time = {t:.3f} sec \n Lighter colors indicated " "higher speed " f"\n The yellow line is the solution of the variational problem \n" f"Travel time opty solution = {h_val * (num_nodes - 1):.3f} sec \n " f"Travel time variational solution = {T_var:.3f} sec \n" f"Along the straight line, travel time = {t_straight:.1f} sec" ) ax.set_title(msg) return point, trail frames = np.concatenate((np.arange(0, h_val * (num_nodes - 1), 1/fps), [h_val * (num_nodes - 1)])) ani = FuncAnimation(fig, update, frames=frames, interval=1000/fps, blit=False, repeat=True) plt.show() .. container:: sphx-glr-animation .. raw:: html
.. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 41.580 seconds) .. _sphx_glr_download_examples_plot_light_ray_nonhomogenious_medium.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_light_ray_nonhomogenious_medium.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_light_ray_nonhomogenious_medium.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_light_ray_nonhomogenious_medium.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_