-
Notifications
You must be signed in to change notification settings - Fork 221
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Gradient-index optics #69
Comments
This should be much more efficient, and a really interesting tool. Regarding how to estimate the direction of the ray based on n(x,y) - the path of the ray is governed by the Eikonal equation, which can be solved numerically, from what I read. |
@ricktu288 |
Great! I haven't worked on it. Please implement that. |
@ricktu288 For now, I only implemented one numerical solver (the simple Euler method). In addition, for now, the "draw" function in circlelens_grin.js works as if the grin lens has a constant refractive index, equal to 2. Before I implement additional features, such as surface merging, wavelength dependent refractive index, other glass forms (half-plane, free-shape, …) etc., I wanted to ask what do you think about the current solution/what changes should be made to it? By the way, about the implementation of the toolbar for this tool, I thought about adding an additional user text input to the circle glass toolbar, which will contain the refractive index function, and something like a checkbox, that will switch between the two modes – grin refractive index, and constant refractive index. Finally, there was an issue with ensuring (in an “if” statement) the condition that a point is inside/outside a circle, or on its boundary, due to numerical inaccuracies, which forced me to add the “eps” property, and it seems to solve it. Simply put, the condition for ensuring that a point ray.p1 is inside the circle “obj” (the case for outside the circle is very similar), is: And for ensuring that a point ray.p1 is on the boundary of the circle “obj”, the condition is: (graphs.length_squared(obj.p1, ray.p1) - R_squared - obj.eps < 0) && (graphs.length_squared(obj.p1, ray.p1) - R_squared + obj.eps > 0) It’s worth noting, that in practice, it seems that the tool works properly even when omitting/changing some of these conditions, however, I obviously can’t check every possible scenario, and I feel more confident with the current conditions in the code. And speaking of the "step_size" property, the length of the first ray segment which is refracted by the "refract" function (from the "refractor" object), is of length 1 (line 861 in "refractor.js"), so I guess that in general it will be better that a grin object (like "circlelens_grin") will have its own "refract" function, where the length of this refracted segment is "step_size" (specifically in the attached code, step_size = 1), |
I think the current solution inside the glass is OK, but the reflected rays on the surface are missing currently. One complication is that if the user enters a discontinuous function, in theory there should also be reflected rays from the discontinuity (and need surfaceMerging. But note that currently surfaceMerging is also not done properly for the custom equation glass anyway). I don't know how easy is to implement this properly. Using an additional option for the circlelens tool may be a good idea (I think that should be an "advanced option" which is hidden by default, as in the beam tool.) However, if you also add the rectangular GRIN slab tool, there is no direct constant-index counterpart in the toolbar currently. Combining the circular one with an existing tool but isolating the rectangular one seems inconsistent. One way to solve this is to add a rectangular glass which is constant-index by default. Also, I think the equation field should be consistent with the "custom equation" tool, that is, use MathQuill. The "length" of the "ray" object has no meaning, so it should not matter what size you set. Setting it to the step size is a good idea, but the rayIntersection must work even if the length is not the step size (for example, the ray may interact with another tool inside the |
If the refractive index is not restricted to having a particular symmetry, then maybe add the options to the custom shape/equation tool is also fine, but the coordinates may be more complicated. Also, I think the derivatives should be calculated automatically. Maybe math.js is a good solution for symbolic derivative. Note however that it appear to not integrated with MathQuill natively. One way to bridge these two libraries is to use tex-math-parser. |
In the Luneburg and Maxwell fisheye lenses examples, the reflected rays are missing, as they should be, because in these two specific examples, there is no discontinuity in the refractive index – the radius of these lenses according to the latex equation in the “p” property, is 100, but if you decrease the actual radius of the circle in the simulator, below 100, the reflected rays will appear due to a discontinuity in the refractive index, as in the picture below – You can also see reflected rays in the picture of the fourth example (where n=2). About what I wrote regarding the “step_size” property, I previously thought that out of all the ray segments which are drawn inside the circle, the first is of length 1 and the rest are of length step_size, which is not consistent, because if we set the numerical step accuracy to be step_size, the length of all the ray segments inside the circle, should be equal to step_size. I’ll keep working on it, and maybe the next thing that I’ll implement is another GRIN lens shape. |
The new refractor_grin tool, is a simple polygon GRIN lens. Note however, that the current implementation also has the circular arcs functionality (as the original refractor tool), which shouldn’t be used as part of this new tool – I guess I can take out the circular arcs functionality from the refractor_grin tool, but still one should know to use this new tool with simple polygons only (not self-intersecting and without holes), as the current GRIN implementation supports only that. Below is the GRIN slab example, using this new tool. I added the “origin” property to both of the current GRIN lenses (it represents the origin for the refractive index function n(x,y)), which the user should input into the toolbar (which I’ll implement in the future). I also made changes to the circlelens_grin.js structure – so that the main differences between different GRIN lenses (so far – circle and simple polygon GRIN lenses), are in the implementation of the functions – isInsideGlass, isOutsideGlass and isOnBoundary (the shot and rayIntersection functions in the new tool, are “inherited” from the circlelens_grin tool). The code is simpler now, but obviously the price is that it’s not as efficient. I also added auxiliary functions to circlelens_grin.js and refractor_grin.js, to make the code more readable. Now, I’ll try to implement surface merging for these two GRIN lenses. |
Unfortunately, I partially began working on the grin surface merging, just this week. |
I added the “body merging/overlapping” capability (overlapping grin objects function as expected now). Below is an example of seismic P wave(rays) shadow zones(for more information, you can see this). The next two examples also demonstrate the body merging function: Now, I’ll implement the toolbar for these grin lenses (including symbolic derivatives, etc.). PS – I thought about maybe adding an absorption feature for this tool, in the future, which I noticed someone requested today: |
That would be amazing if you could! Thanks for considering it 🙂. |
Update - |
I added the GRIN circle and free-shape objects to the toolbar. The integration of the tex to math parser was annoying - it works in a Node environment rather than a web environment, so certain changes had to be made to this parser. Since now the partial derivatives are done automatically, I added below another example of a GRIN lens, from this article. Tell me if you want me to change/add something before deploying these tools. In addition, I just now noticed a problem with the body merging functionality - the current implementation creates a bug when there're multiple GRIN objects with refractive index functions that are not defined everywhere (this problem doesn't exist without this functionality). |
I think one thing to add is to draw the glass according to the refractive index function, rather than a uniform gray color (note that for the original non-GRIN tools, even if you stack multiple glasses, the resulting gray color always corresponds to the effective refractive index). I don't know if that will affect the performance a lot. If it becomes too slow, a workaround may be to just draw the border or use a new kind of representation (so at least not conflicting with existing visual representation). The current representation is confusing because it makes people think the inside is uniform and the border is always discontinuous. As for body merging, i think it may not be as important as surface merging, so if it is too difficult to be fully implemented, you may just add a warning, etc., as you currently do for the requirement of simple polygon |
Sure, I'll try it, check the performance, and continue accordingly. About the body merging, I think it's important because it gives you flexibility to create different shapes, that you are unable to create otherwise (without adding new tools every time), like the concentric circles example of seismic rays (which would require an annulus tool without the body merging). |
Maybe a better way is to use a default n(x,y) which is defined on the entire plane so that a new user will not get confused. |
Yes, that might be better. |
Or maybe something like a periodic n(x,y) (is there a physically relevant example?) so that a new user can play with it non-trivially without need to enter an equation. |
One way to improve the performance (if the direct pixel-wise drawing is too slow) is to draw it on a virtual canvas and cache the image, and that every time draw() is called just draw that image on the canvas (also note the current "hack" of automatic red color for absolute n<1). Also note that the use of getImageData/putImageData may interfere with the hardware accelerated drawing on some hardware and may decrease performance and image quality (e.g. rasterization bias) in the entire simulator. I guess using getImageData/putImageData on a virtual canvas may be OK (without decreasing performance/quality), but I never tried that before. |
I estimated the performance of the point-wise calculation (for drawing the gray color) on an nxn grid. However, the performance starts to become poor for n>100. I think if we stick with drawing an n=100 grid then some cases (especially with singularity) the appearance on screen may become low-quality. Although caching the image will help, this does not prevent re-calculation when the user change the shape or possibly adjusting the parameters if #105 is going to be implemented. Possible solutions are using WebGL, workers (also very complicated), or the kind of setInterval drawing as what is currently done for ray tracing, but those are all quite completed and need to rewrite a lot of core code. Therefore, I think the best solution may be to just draw with another appearance without showing the index. I suggest filling with a constant dark sky-blue color. So I think what's left is to fix the body-merging bug. |
Adaptive sampling may help, but I don't know how complicated it is to implement that. Since both of us does not have much free time, I think the best way is to make a minimal implementation and merge the PR. Making the appearance intuitive may be left for future improvement. |
For the examples in the Gallery, I suggest putting two versions together, one is a rough approximation using a series of constant refractor where the distance between the surfaces is human-readable, and another version is using the GRIN refractor. If #105 is to be implemented, then in the future the rough approximation one may be replaced by an interactive, adjustable numbers or slices. |
Sure, I'll work on fixing the body-merging bug first and foremost. |
To fix the body merging bug, I currently added another implementation of the body merging functionality, based on symbolic computation, as opposed to the previous implementation which was mainly numerical. The grin objects have a toggle to switch between these two implementations – like I wrote in the note popover, the symbolic implementation is slower but doesn't have this bug(note that this bug arises in very specific scenarios, as written in the note popover). I considered other numerical implementations, as to fix this bug without the slower performance. However, every time I found a specific scenario in which this implementation fails (with regards to this bug), whereas the symbolic one remains robust (the slowness of the symbolic implementation stems, from what I checked, from the use of the symbolic derivative function – 'math.derivative' ). I changed the constant color of the GRIN objects – feel free to change it or to suggest a different color. The grin circlelens object is now initialized with a specific gaussian like function, and the grin refractor with a specific periodic function. Both of these functions are defined in the entire plane, and the numerical error associated with ray tracing them, can be handled within the prescribed limits of the step size (0.1 to 1), from what I checked. The periodic sine and cosine based refractive index functions, seem to be quite susceptible to numerical errors using the current Euler method. It would be interesting to add additional numerical algorithms in the future, such that the user could choose one from a defined list, that best suits his/her needs. |
Great! I will merge the PR in about 1~2 weeks. Thanks for your contribution! |
No problem :) |
Now I've merged the PR. For the examples, I think my previous suggestion of putting together a human-readable rough approximation alone with the GRIN glass one may not be good in some cases, especially those susceptible to numerical inaccuracy. So just put whatever you think is suitable. |
There have been several gradient-index optics examples made by @StasFainer:
They are made by and simulated with brute force stacking of a lot of single-index refractors, so the performance is very poor and a lot of artifacts are generated.
I think there should be a much better and efficient way to simulate them, such as having a tool that takes an arbitrary n(x,y) and simulate by the following:
If a beginning point
p1
of a ray is inside the relevant region, thenrayIntersection
will always return a pointp
such that the distance betweenp1
andp
is some small distanceeps
. And thenshot
will give the best estimation of the new direction of the ray.However, I am not very familiar with optics higher than the level of freshman physics (my research area is condensed matter theory). So I don't know the proper numerical algorithm to estimate the direction of the ray based on n(x,y) so that the numerical error is minimized. Also, I don't know how to deal with the reflected rays if the function is not smooth in general.
It would be great if someone familiar in this area can create such a tool, or at least provide a proper algorithm.
The text was updated successfully, but these errors were encountered: