-
Notifications
You must be signed in to change notification settings - Fork 33
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
SFWM Aeff calculation #131
base: main
Are you sure you want to change the base?
Conversation
Hey @Kunyuan-LI cool example! I am not sure it is necessary to remove the frozen state of the dataclass: since you only use Also, to make things more easy, you should update femwell to the last version :) |
@lucasgrjn thank you! |
Yeah you are right, it definitely make sense to cache it if you may need it on anther part of the code :) For the update, I checked your fork history and it looks like you had the last version. However you delete some pieces of the code such as:
|
It seems you're removing the newest stuff in waveguide.py, so you probably missed the latest commits on main 🤔 If we find out over time we use that normalization more often, we can put something in waveguide.py, but for now I don't think it makes sense to add the differently normalized fields there. When I see calculations based on only E, I'm also wondering if there's some approximation in there. The current normalization is based on the poynting vector which should be the most accurate way to normalize the field. Could you update the PR to have everything in the example? That'll make debugging easier :) |
Hmm, now you merged the most recent changes, but it seems you're still deleting stuff in waveguide.py 🤔 |
Sure. I made a standalone Aeff.py example. |
Ups, that's not what I meant! I meant you can use waveguide.py, but you shouldn't modify it. I.e. instead of defining mode.E0 you just calculate the integral needed for normalization and divide the values you get by it. So that you have something like: -Calculate normalization factor for each mode The idea is to have it built on waveguide.py, but decide afterwards if we want to move something there |
Hey @Kunyuan-LI , I think I found it! 🥳 Also it seemed to me that they use a different crosssection in the paper (?) I changed it to what the mention in the last paragraph With the changes we now get for the effective area Do they mention anywhere what exact wavelengths they use for that integrals? We should also think what exacly we want to use for the calculation - it's now a dot-product between the E-fields (whereas it's a bit random chosen which ones we dot product in Could you have a look if you find the reason for the small derivation? |
Great! 🥳 In my understanding, Regarding the "different crosssection in the paper (?)", I believe it should be that SFWM should be completed within the same crosssection because all the electric field distributions uv(x,y) are normalized. That is, only when using the same geometry, these normalized, physically meaningful 'electric field distributions' make sense in calculating the overlap integral(?). Otherwise, normalizing and then multiplying different crosssections seems make no sense here.🤔 After reviewing the literature at https://doi.org/10.1070/QEL16511, I believe in equations (3) and (4): Here the effective Hamiltonian (eq3, order equivalently modified) gives the content of the integration(p+p+s-i-), then describes the relationship between only exists when So we need again to reevaluate the overlap function 🤔. However, for me the result already makes sense. I used a different geometry and material, but for the comparison between Lumerical and theoretical results, in our case, we have ~0.4 in theoretical (Femwell?) versus 0.469 in Lumerical, and in the tutorial, there's 0.5 in theoretical calculation versus 0.59 in Lumerical. In fact, I'm also not clear on how this difference (significant enough) has emerged, nor whether it will have a notable impact on subsequent phase matching experiments. It can only be said that it remains to be assessed.🤔 They did not mention the Sellmeier equation in the calculation of Aeff, but we indeed used Sellmeier to obtain neff for TE and TM modes and plotted the contour of Δk=0 under the assumption of zero non-linear shifting, also realized by femwell😙 Regarding 'how the integrals are calculated', indeed, I also believe we need to reconsider how this overlap is calculated to accurately express it. And although for waveguides we generally only consider TE and TM modes, considering that in reality, there are TE-TM mode SFWM, anyway, we need to delve deeper into how the integrals are calculated.... Move to docs/photonics/examples will be completed instantaly. However, I feel like the holes we are digging are getting bigger and bigger(?)😂 |
Yes! both are equivalent. What confused me was that Yeah, all should be calculated with the same waveguide geometry. I didn't read the complete paper, I just looked in the end where they mentioned dimensions to calculate their effective area and took those 🤔 but I was also a bit wondering if they used other parameters before. Hmm, wouldn't that mean that we calculate the dot products between pump/signal and pump/idler? I mean signal and idler both have a dagger, which would mean that we would need to multiply them with something without a dagger 🤔 Your explanation to (4) makes totally sense to me! And yes, I agree, if we don't go from conjugate transposed to just conjugated, it should be fine! (I think they assume the modes to be either almost TE or TM and thus can use just one component) so we probably just need to exchange in the code the fields to have the right overlap integrals? Uijj, that graph looks amazing! Happy to see that the mode solver in use 😊 And makes sense! If one of the mode is not really guided, the probability to the conversion to that mode should also be pretty small! 👍 thanks for moving it! The next step would be to add a header (just copy from https://github.com/HelgeGehring/femwell/blob/main/docs/photonics/examples/waveguide_modes.py) and divide the example into cells using
That way we can generate a nice docs page! Digging deeper sounds great! I had during my master's also a look on the theory for SFWM and I'd be happy to give it a deeper drive :) What about keeping the main discussion in the issue #130 and merging pull requests as soon as we have a part of the problem solved? |
Sure, I'll delve deeper into this, but we can include this PR as part of the problem.😄 Now we have two problems:
|
Yes, sounds great :) (1) what exactly do you test there? Tests should have the option to "fail", i.e. they need to include something like assertAlmostEqual or similar. And to see the location of the modes: Just include them in this script!
to not show it in the first place (but allow to open the input/output) and keep the example not too long. But for double-checking and understanding it's nice to have these plots :) What do you think about this combined approach? About the negative numbers: Don't worry! We maybe could think of something like fixing the integral over (2) From eq (4) it seems (p s*) (p i*) should be the correct one. For this example I'd expect it to only negligibly change the result, as the mode is quite polarized. Even if we just multiply the x-components, it shouldn't really change. (But I'd guess it's best to go for the most general, so the dot products seem like the best solution :) ) |
Hi Helge, After rethinking the integration mechanism of
I believe this is the only way to accurately describe the nature of the integrand functions being multiplied internally. Any approach using the Therefore, this time I directly considered the contributions of |
Hmm, like this I don't think you get something depending on I read a bit more into it and I think I remember the solution to this - the taylor expansion of the optical susceptibility is a tensor! (see https://en.wikipedia.org/wiki/Electric_susceptibility) That means in the generalized case we'd need to multiply all combinations of the fields and then multiply it by the tensor element and sum it up. Thus, I think that's the reason why they just have a scalar? And therefore they also just multiply scalar fields in the paper? That would also mean that And as long as the absolute value of the In doi.org/10.1088/1612-2011/13/11/115204 is another example with an |
Sure, TOSPDC is also a very interesting non-linear optical phenomenon to study. The differences between SFWM and TOSPDC are discussed in detail in this literature: https://doi.org/10.48550/arXiv.1307.3266. However, the process of TOSPDC, where one pump photon splits into three photons, is indeed more counterintuitive compared to SFWM, which we can, to some extent, analogize with the collision of small balls (?).😂 For the Here is an example for 670x390nm. We selected three sets of p wavelengths and obtained the corresponding s and i wavelengths. Below is a comparison of the calculation results from femwell and ANSYS Lumerical using my previous overlap calculation method:
Considering the differences between our Sellmeier parameter fitting and Lumerical's fitting (which are observable), I believe this is completely reasonable. And this time, we have truly achieved it.🥳 As for TOSPDC, considering that the calculation method for overlap is p+s-i-r-, I believe the same implementation approach should also be completely feasible. However, I suggest creating a new PR and listing a new example because, firstly we need to reconsider the calculation of wavelengths. On the other hand, keywords such as SFWM and (TO)SPDC are also quite important.😂 |
docs/photonics/examples/SFWM_Aeff.py
Outdated
overlap_Ex = E_p_xy[0,:,0]*E_p_xy[0,:,0]*np.conj(E_s_xy[0,:,0])*np.conj(E_i_xy[0,:,0]) + \ | ||
E_p_xy[1,:,0]*E_p_xy[1,:,0]*np.conj(E_s_xy[1,:,0])*np.conj(E_i_xy[1,:,0]) | ||
overlap_Ey = E_p_xy[0,:,1]*E_p_xy[0,:,1]*np.conj(E_s_xy[0,:,1])*np.conj(E_i_xy[0,:,1]) + \ | ||
E_p_xy[1,:,1]*E_p_xy[1,:,1]*np.conj(E_s_xy[1,:,1])*np.conj(E_i_xy[1,:,1]) | ||
overlap_Ez = E_p_xy[0,:,2]*E_p_xy[0,:,2]*np.conj(E_s_xy[0,:,2])*np.conj(E_i_xy[0,:,2]) + \ | ||
E_p_xy[1,:,2]*E_p_xy[1,:,2]*np.conj(E_s_xy[1,:,2])*np.conj(E_i_xy[1,:,2]) | ||
|
||
return np.array([overlap_Ex, overlap_Ey, overlap_Ez]).T |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overlap_Ex = E_p_xy[0,:,0]*E_p_xy[0,:,0]*np.conj(E_s_xy[0,:,0])*np.conj(E_i_xy[0,:,0]) + \ | |
E_p_xy[1,:,0]*E_p_xy[1,:,0]*np.conj(E_s_xy[1,:,0])*np.conj(E_i_xy[1,:,0]) | |
overlap_Ey = E_p_xy[0,:,1]*E_p_xy[0,:,1]*np.conj(E_s_xy[0,:,1])*np.conj(E_i_xy[0,:,1]) + \ | |
E_p_xy[1,:,1]*E_p_xy[1,:,1]*np.conj(E_s_xy[1,:,1])*np.conj(E_i_xy[1,:,1]) | |
overlap_Ez = E_p_xy[0,:,2]*E_p_xy[0,:,2]*np.conj(E_s_xy[0,:,2])*np.conj(E_i_xy[0,:,2]) + \ | |
E_p_xy[1,:,2]*E_p_xy[1,:,2]*np.conj(E_s_xy[1,:,2])*np.conj(E_i_xy[1,:,2]) | |
return np.array([overlap_Ex, overlap_Ey, overlap_Ez]).T | |
return np.sum(E_p_xy * E_p_xy * np.conj(E_s_xy) * np.conj(E_i_xy), axis=0) |
Does exactly the same :)
The indices are (direction - x/y , cell_number , quadrature point).
You can easily see this if you change the integration order of the basis you use for setting epsilon. The last index depends on the integration order.
basis0 = Basis(mesh, ElementTriP0(), intorder=2)
will for example change the last index to 4.
The quadrature points is I think something we should leave to skfem, I don't see any situation where we would want to care about individual values on the quadrature points ourselves. (We just care about there being enough to be able to solve the system)
docs/photonics/examples/SFWM_Aeff.py
Outdated
def normalization_factor_mode(mode): | ||
@Functional | ||
def E2(w): | ||
return dot(w["E"][0], np.conj(w["E"][0])) | ||
|
||
E = mode.basis.interpolate(mode.E) # [0]=xy [1]=z | ||
E_squared_integral = E2.assemble(mode.basis, E=E) | ||
normalization_factor = 1 / np.sqrt(E_squared_integral) | ||
return normalization_factor # Return the normalization factor instead of modifying the mode | ||
|
||
# Apply normalization factors to the electric fields for the overlap calculation | ||
@Functional(dtype=np.complex64) | ||
def sfwm_overlap(w): | ||
|
||
E_p_xy = w["E_p"][0] # shape: (2, x, 3) | ||
E_s_xy = w["E_s"][0] # shape: (2, x, 3) | ||
E_i_xy = w["E_i"][0] # shape: (2, x, 3) | ||
|
||
overlap_Ex = E_p_xy[0,:,0]*E_p_xy[0,:,0]*np.conj(E_s_xy[0,:,0])*np.conj(E_i_xy[0,:,0]) + \ | ||
E_p_xy[1,:,0]*E_p_xy[1,:,0]*np.conj(E_s_xy[1,:,0])*np.conj(E_i_xy[1,:,0]) | ||
overlap_Ey = E_p_xy[0,:,1]*E_p_xy[0,:,1]*np.conj(E_s_xy[0,:,1])*np.conj(E_i_xy[0,:,1]) + \ | ||
E_p_xy[1,:,1]*E_p_xy[1,:,1]*np.conj(E_s_xy[1,:,1])*np.conj(E_i_xy[1,:,1]) | ||
overlap_Ez = E_p_xy[0,:,2]*E_p_xy[0,:,2]*np.conj(E_s_xy[0,:,2])*np.conj(E_i_xy[0,:,2]) + \ | ||
E_p_xy[1,:,2]*E_p_xy[1,:,2]*np.conj(E_s_xy[1,:,2])*np.conj(E_i_xy[1,:,2]) | ||
|
||
return np.array([overlap_Ex, overlap_Ey, overlap_Ez]).T | ||
|
||
#return dot(w["E_p"][0], w["E_p"][0]) * dot(np.conj(w["E_s"][0]), np.conj(w["E_i"][0]))#? | ||
#return dot(w["E_p"][0], np.conj(w["E_s"][0])) * dot(w["E_p"][0], np.conj(w["E_i"][0]))#?? | ||
|
||
overlap_result = sfwm_overlap.assemble( | ||
basis, | ||
E_p=mode_p.basis.interpolate(mode_p.E * normalization_factor_mode(mode_p)), | ||
E_s=mode_s.basis.interpolate(mode_s.E * normalization_factor_mode(mode_s)), | ||
E_i=mode_i.basis.interpolate(mode_i.E * normalization_factor_mode(mode_i)), | ||
) | ||
|
||
return 1 / overlap_result |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def normalization_factor_mode(mode): | |
@Functional | |
def E2(w): | |
return dot(w["E"][0], np.conj(w["E"][0])) | |
E = mode.basis.interpolate(mode.E) # [0]=xy [1]=z | |
E_squared_integral = E2.assemble(mode.basis, E=E) | |
normalization_factor = 1 / np.sqrt(E_squared_integral) | |
return normalization_factor # Return the normalization factor instead of modifying the mode | |
# Apply normalization factors to the electric fields for the overlap calculation | |
@Functional(dtype=np.complex64) | |
def sfwm_overlap(w): | |
E_p_xy = w["E_p"][0] # shape: (2, x, 3) | |
E_s_xy = w["E_s"][0] # shape: (2, x, 3) | |
E_i_xy = w["E_i"][0] # shape: (2, x, 3) | |
overlap_Ex = E_p_xy[0,:,0]*E_p_xy[0,:,0]*np.conj(E_s_xy[0,:,0])*np.conj(E_i_xy[0,:,0]) + \ | |
E_p_xy[1,:,0]*E_p_xy[1,:,0]*np.conj(E_s_xy[1,:,0])*np.conj(E_i_xy[1,:,0]) | |
overlap_Ey = E_p_xy[0,:,1]*E_p_xy[0,:,1]*np.conj(E_s_xy[0,:,1])*np.conj(E_i_xy[0,:,1]) + \ | |
E_p_xy[1,:,1]*E_p_xy[1,:,1]*np.conj(E_s_xy[1,:,1])*np.conj(E_i_xy[1,:,1]) | |
overlap_Ez = E_p_xy[0,:,2]*E_p_xy[0,:,2]*np.conj(E_s_xy[0,:,2])*np.conj(E_i_xy[0,:,2]) + \ | |
E_p_xy[1,:,2]*E_p_xy[1,:,2]*np.conj(E_s_xy[1,:,2])*np.conj(E_i_xy[1,:,2]) | |
return np.array([overlap_Ex, overlap_Ey, overlap_Ez]).T | |
#return dot(w["E_p"][0], w["E_p"][0]) * dot(np.conj(w["E_s"][0]), np.conj(w["E_i"][0]))#? | |
#return dot(w["E_p"][0], np.conj(w["E_s"][0])) * dot(w["E_p"][0], np.conj(w["E_i"][0]))#?? | |
overlap_result = sfwm_overlap.assemble( | |
basis, | |
E_p=mode_p.basis.interpolate(mode_p.E * normalization_factor_mode(mode_p)), | |
E_s=mode_s.basis.interpolate(mode_s.E * normalization_factor_mode(mode_s)), | |
E_i=mode_i.basis.interpolate(mode_i.E * normalization_factor_mode(mode_i)), | |
) | |
return 1 / overlap_result | |
# Apply normalization factors to the electric fields for the overlap calculation | |
def interpolate_and_normalize_xy(mode): | |
@Functional | |
def E2(w): | |
return dot(w.E, np.conj(w.E)) | |
E = mode.basis.interpolate(mode.E)[0] | |
return E / np.sqrt(E2.assemble(mode.basis, E=E)) | |
@Functional(dtype=np.complex64) | |
def sfwm_overlap(w): | |
return np.sum(w.E_p_xy * w.E_p_xy * np.conj(w.E_s_xy) * np.conj(w.E_i_xy), axis=0) | |
return 1 / sfwm_overlap.assemble( | |
basis, | |
E_p_xy=interpolate_and_normalize_xy(mode_p), | |
E_s_xy=interpolate_and_normalize_xy(mode_s), | |
E_i_xy=interpolate_and_normalize_xy(mode_i), | |
) |
This would make it even shorter and a bit more efficient as we don't have to interpolate twice :)
docs/photonics/examples/SFWM_Aeff.py
Outdated
def n_X(wavelength): | ||
x = wavelength | ||
return ( | ||
1 | ||
+ 2.19244563 / (1 - (0.20746607 / x) ** 2) | ||
+ 0.13435116 / (1 - (0.3985835 / x) ** 2) | ||
+ 2.20997784 / (1 - (0.20747044 / x) ** 2) | ||
) ** 0.5 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd guess we should use here
def n_X(wavelength): | |
x = wavelength | |
return ( | |
1 | |
+ 2.19244563 / (1 - (0.20746607 / x) ** 2) | |
+ 0.13435116 / (1 - (0.3985835 / x) ** 2) | |
+ 2.20997784 / (1 - (0.20747044 / x) ** 2) | |
) ** 0.5 | |
def n_X(wavelength): | |
x = wavelength * 1000 | |
return ( | |
1 | |
+ 3.0249 / (1 - (135.3406 / x) ** 2) | |
+ 40314 / (1 - (1239842 / x) ** 2) | |
) ** 0.5 | |
Then it matches way better the linked example, there they got 0.59, I get 0.6 :)
(And we should probably call it n_silicon_nitride)
YAY, that's just amazing! 🥳 🎉 I'm wondering if we should generalize the method for calculating the effective area a bit more to consider Thanks for the link, I'll have a look! And nice analogon with the small balls! :D The indices are basis0 = Basis(mesh, ElementTriP0(), intorder=4) Wow, that looks like perfect matching! 🥳 🥳 We should now care about turning the code into a real example by adding some explanations and maybe a graph to look at? How about showing first the graph for the phase-matching you posted? TOSPDC sounds also super interesting! Let's investigate that deeper in a new PR! |
I've just added some markdown and split the example in multiple cells to have it ready for the docs :) Have a look on the suggestion to simplify the code, didn't want to just change that :) but I'd guess that would make it easier to read and shorter :) |
Hi, I'm back. The material of the figure has a multi-layer structure based on TiO2 and various testing materials, so it's a little complicated. For now, we can treat it as an isotropic material to simplify the analysis. However, you're right that using silicon nitride directly for the example would be better. As for the TOSPDC, I'll create a 3D contour surface that describes the phase matching condition in x, y, z (signal, idler, rest) space. This will provide a clear visualization of the phase matching. |
Yay! Ah, okay, I thought we were matching the paper :) Sounds cool! Have a look on the suggestions above, those will make the code way easier to read :) |
Hello,
This is the PR for the issue #130.
Best,