-
Notifications
You must be signed in to change notification settings - Fork 42
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
Integrating polarized field propagation? #91
Comments
As far as performance is concerned, I think it "doesn't matter." When fft2 is broadcast/"vectorized" over a subset of the total dimensions, it's just a loop over what is in this case a total of 4 things. Looping in python vs C over 4 things seems meh. We could certainly look at adding broadcast/vectorization support to MFT and CZT. In the former case, it is just two lines to make the matmuls broadcast nicely. For CZT there is mild complexity since right now a product of the form Something to think about is "how does this impact gradient back propagation?" As-is, the routines all presume in one way or another 2D input/output, and (even if only just) you never need more than the immediate past to have all your inputs in hand for the next backprop step. I may be wrong, but I think there will need to be a step where a It may instead be simpler/"better" to define def jones_adapter(J, prop_func, *prop_func_args, **prop_func_kwargs):
J00 = J.data[0,0,...] # can actually leave off the ellipsis if you want...
J01 = J.data[0,1,...]
J10 = J.data[1,0,...]
J11 = J.data[1,1,...]
for E in zip(J00, J01, J10, J11):
wf = Wavefront(E, J.dx, J.wavelength, J.space)
prop_func(wf, *prop_func_args, **prop_func_kwargs) e.g., # wf = "Jones wavefront," WF = from prysm.propagation import Wavefront as WF
wf = Wavefront(np.random.rand(2,2,1024,1024), dx=1, wavelength=.6328, space='pupil')
focused_wfs = jones_adapter(J, WF.focus, Q=2, efl=100) This may be super difficult to read properly (wf/WF) and is an unusual way of using python. And it would only work with the WF.xxx methods, not the free functions. That's probably OK. e: it may be possible to detect the difference between Another design might consider taking a page out of machine learning's book and make a context manager that does deep, insidious, evil things to generate a "tape" without actually doing the propagations, but storing the variables, etc, so that you could do
The code for this idea would be a fair amount harder to write, but is actually possible, I think. Better/worse, not sure. You would only "do one thing" to make many scalar into Jones props, instead of doing it to each one. But it's evil and magical, which is fairly antithetical to how prysm works. |
Thinking about this more, there is a small issue with the "jones adapter" approach: it turns a 2x2 input into a length 4 list return. It may be better to introduce a new type, which stores things as a length 4 tuple or list internally, but define This in some way breaks all of the operators you have in pol.py, so maybe this is not a very good solution either. Just copying the list elements into a new 2x2xMxN matrix fixes the interface problem, but is Not Super Great for performance. It might be possible to make a non-contiguous numpy array and avoid the copy. That would probably be best. |
consider myself @'d first thoughts are that in order to use polarization.py in propagation it needs to be able to do matrix multiplications with the incoming (generally) 2x2 wavefront. Keeping them as numpy arrays is nais because we can just
But 2x2 matrix multiplications aren't so special that we absolutely need I don't think I really get the non-contiguous numpy array, how does that solve the interface problem? |
Taking the sketch slightly further, def jones_adapter(J, prop_func, *prop_func_args, **prop_func_kwargs):
J00 = J.data[0,0]
J01 = J.data[0,1]
J10 = J.data[1,0]
J11 = J.data[1,1]
tmp = []
for E in zip(J00, J01, J10, J11):
wf = Wavefront(E, J.dx, J.wavelength, J.space)
ret = prop_func(wf, *prop_func_args, **prop_func_kwargs)
tmp.append(ret)
# one path, return list (no extra copies/allocs)
# return tmp
# different path, pack it back in (waste copies)
out = np.empty((2,2,*tmp[0].shape), tmp[0].dtype)
out[0,0] = tmp[0]
out[0,1] = tmp[1]
out[1,0] = tmp[2]
out[1,1] = tmp[3]
return out Another (ugly) approach is to make our own At 8192x8192, a few timings...
round numbers the copy is four times as expensive as an elementwise multiply, four times cheaper than expi. I do (strongly) prefer that we define some function, I don't know, |
The function would be easy to implement, I can throw something together and make a demo. I haven't used
As opposed to just keeping them as numpy arrays and calling
|
I prefer that the function is smart enough to understand the difference and act appropriately. It is mildly arcane, but you can sniff out whether something is a method or a free-function. The three cases would be
These can be sussed out as follows:
The free functions in propagation.py (focus, focus_fixed_sampling, ...) operate on arrays. To make chaining nicer, there is also the Wavefront object which has methods that wrap the free functions |
Working a bit on this, starting with the propagation functions and will move onto the methods of the Wavefront class Here's the adapter in my jones_adapter branch of my prysm fork And these are the tests for focus, unfocus, and angular_spectrum, all of which pass When writing the tests I did find it a bit weird to separate the
could instead be
What do you think? The only part I can think of where this is kind of a holdup might be for the propagation methods of |
I think it is probably nicer if the jones adapter is written as a decorator, that way the usage will be fully transparent, with the contents of propagation.py (eventually) written as,
In the interim, it would look like
or as a less clear one-liner,
This way the calling syntax is kept fully ordinary, not extraordinary, and the fact that it is a higher order function is "hidden" from the user |
Ah I see, I wasn't sure how committed we were to actually making it a decorator. I'm a little unfamiliar with how to structure the decorator so I'll play around with it a bit. |
This has been simmering on the backburner for long enough, best return to it before it's well-done @brandondube do you have any tests you'd recommend to check that I correctly wrote |
Could you help me understand the purpose of pasting most/all of propagation.py in polarization.py, thereby adding a bunch of immortal garbage to the git history? Could you not achieve the same thing, by
The edit-run-test cycle is terrible this way since autoreload won't work, but you can do the equivalent action in jupyter (etc) for live changes with autoreload For testing, I think it would be reasonable to pick one function (say, focus) and do ~=
It would probably be useful to add a utility function to polarization.py that takes an MxNx2x2 array and returns J00..J11, i.e., those four lines above, so they don't have to be pasted everywhere. In the broken out to fpm and back and backpropagation routines, I think there is a backwards co-mingling. All of the other free functions don't use any classes, but here something is stuffed into a class. I would prefer that not be done, and these refactoring changes be done directly in propagation.py. I also think the transfer function of free space ( |
@Jashcraf nudge |
Adding a sample code snippet here so that I don't have to dig through slack to find. I'll get on it this week after I'm done with JATIS proofs.
|
👋 can we close this out by the new year? |
made the push, ty for the bump Made the addition to my fork of prysm
Let me know what you think and I can make a PR |
A few comments
Thanks! |
ping |
I'll resume this and work on the remaining changes between meetings tomorrow |
👍 |
i am once again pinging ping |
Now that thesis is done I’m finally returning Is it best to refer to experimental modules like |
|
Sweet, I’ll make the PR |
merged in PR #111 |
I work a lot with Jones Pupils (see image below). Doing diffraction simulation with these involves propagating the 4 complex "wavefronts" that make up the Jones pupil function, and I was curious about what it would take to integrate the same utility to prysm?
Of course one could define some
propagator()
function that describes the common optical path and loop over it, but for performance I was curious about how the currentprysm.propagation
module could support wavefronts of shape [2 x 2 x Npix x Npix]np.fft.fft2
defaults to performing the computation on the last two axes of an array. A quick simulation with a 2x2x256x256 array shows thatnp.fft.fftshift
appears to work similarly, so propagation routines in prysm that call these functions are not immediately affected.I'm more concerned with
mft
andczt
because I don't really know how they work. Sincemft
uses matrix multiplication the arrays should be broadcasted just fine, but I don't really understand how a 4-D array would affect the configuration of the bases.Would supporting "broadcasted" propagation be meaningful to do?
The text was updated successfully, but these errors were encountered: