-
Notifications
You must be signed in to change notification settings - Fork 41
Biot-Savart volume integral #996
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
base: master
Are you sure you want to change the base?
Conversation
Codecov Reportβ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #996 +/- ##
==========================================
+ Coverage 94.52% 94.56% +0.03%
==========================================
Files 102 105 +3
Lines 28712 29272 +560
==========================================
+ Hits 27141 27680 +539
- Misses 1571 1592 +21
π New features to boost your workflow:
|
| benchmark_name | dt(%) | dt(s) | t_new(s) | t_old(s) |
| -------------------------------------- | ---------------------- | ---------------------- | ---------------------- | ---------------------- |
test_build_transform_fft_lowres | +0.65 +/- 3.17 | +3.28e-03 +/- 1.60e-02 | 5.08e-01 +/- 1.0e-02 | 5.05e-01 +/- 1.3e-02 |
test_build_transform_fft_midres | +0.48 +/- 1.36 | +2.80e-03 +/- 8.02e-03 | 5.91e-01 +/- 4.7e-03 | 5.89e-01 +/- 6.5e-03 |
test_build_transform_fft_highres | +0.19 +/- 2.26 | +1.84e-03 +/- 2.21e-02 | 9.79e-01 +/- 1.0e-02 | 9.77e-01 +/- 2.0e-02 |
test_equilibrium_init_lowres | +0.51 +/- 0.61 | +1.86e-02 +/- 2.21e-02 | 3.63e+00 +/- 1.5e-02 | 3.61e+00 +/- 1.6e-02 |
test_equilibrium_init_medres | +0.10 +/- 1.63 | +4.19e-03 +/- 6.64e-02 | 4.09e+00 +/- 5.4e-02 | 4.09e+00 +/- 3.9e-02 |
test_equilibrium_init_highres | +0.32 +/- 0.52 | +1.76e-02 +/- 2.87e-02 | 5.50e+00 +/- 2.1e-02 | 5.48e+00 +/- 1.9e-02 |
test_objective_compile_dshape_current | +0.18 +/- 0.92 | +6.74e-03 +/- 3.45e-02 | 3.76e+00 +/- 2.5e-02 | 3.75e+00 +/- 2.4e-02 |
test_objective_compile_atf | +0.08 +/- 1.25 | +6.46e-03 +/- 1.02e-01 | 8.12e+00 +/- 7.9e-02 | 8.11e+00 +/- 6.4e-02 |
test_objective_compute_dshape_current | +1.45 +/- 5.20 | +1.82e-05 +/- 6.52e-05 | 1.27e-03 +/- 5.9e-05 | 1.26e-03 +/- 2.7e-05 |
test_objective_compute_atf | -0.32 +/- 4.53 | -1.38e-05 +/- 1.92e-04 | 4.23e-03 +/- 1.5e-04 | 4.25e-03 +/- 1.2e-04 |
test_objective_jac_dshape_current | -5.67 +/- 11.28 | -2.18e-03 +/- 4.33e-03 | 3.62e-02 +/- 3.1e-03 | 3.84e-02 +/- 3.0e-03 |
test_objective_jac_atf | +0.14 +/- 2.41 | +2.62e-03 +/- 4.53e-02 | 1.89e+00 +/- 3.8e-02 | 1.88e+00 +/- 2.4e-02 |
test_perturb_1 | +0.50 +/- 0.52 | +6.47e-02 +/- 6.73e-02 | 1.30e+01 +/- 4.8e-02 | 1.30e+01 +/- 4.7e-02 |
test_perturb_2 | +0.51 +/- 0.55 | +9.10e-02 +/- 9.81e-02 | 1.79e+01 +/- 8.3e-02 | 1.78e+01 +/- 5.3e-02 |
test_proximal_jac_atf | +0.16 +/- 0.96 | +1.18e-02 +/- 7.01e-02 | 7.31e+00 +/- 5.7e-02 | 7.30e+00 +/- 4.1e-02 |
test_proximal_freeb_compute | +1.08 +/- 0.70 | +1.90e-03 +/- 1.23e-03 | 1.78e-01 +/- 7.3e-04 | 1.76e-01 +/- 9.8e-04 |
test_proximal_freeb_jac | +0.39 +/- 0.99 | +2.83e-02 +/- 7.19e-02 | 7.31e+00 +/- 5.2e-02 | 7.28e+00 +/- 4.9e-02 |
test_solve_fixed_iter | +0.80 +/- 8.77 | +1.17e-01 +/- 1.29e+00 | 1.48e+01 +/- 9.3e-01 | 1.47e+01 +/- 8.9e-01 | |
|
use this fxn DESC/desc/magnetic_fields/_core.py Line 31 in b10f8c8
|
|
|
|
Several people at the scidac meeting today are using the BMW code: https://github.com/ORNL-Fusion/BMW/tree/master refs: https://scipub.euro-fusion.org/wp-content/uploads/eurofusion/WPS1CPR17_17542_submitted-4.pdf basically it does a biot-savart volume integral to compute A (not B), then takes the finite difference curl of A to get B. As far as I can tell from the code the source grid is hardcoded to ntheta=101, ns, nzeta taken from the vmec/mgrid file. Allegedly it works well and gives accurate results? Could try something similar (and maybe use spectral derivatives for going A->B) |
|
Try implementing Eq. 17-18 of Peraza-Rodriguez et al. Phys. Plasmas 2017 |
Memory benchmark result| Test Name | %Ξ | Master (MB) | PR (MB) | Ξ (MB) | Time PR (s) | Time Master (s) |
| -------------------------------------- | ------------ | ------------------ | ------------------ | ------------ | ------------------ | ------------------ |
test_objective_jac_w7x | 4.53 % | 3.796e+03 | 3.968e+03 | 171.93 | 39.19 | 36.44 |
test_proximal_jac_w7x_with_eq_update | 0.82 % | 6.440e+03 | 6.492e+03 | 52.57 | 161.59 | 161.18 |
test_proximal_freeb_jac | -0.29 % | 1.324e+04 | 1.320e+04 | -38.53 | 82.12 | 83.13 |
test_proximal_freeb_jac_blocked | -0.81 % | 7.549e+03 | 7.487e+03 | -61.39 | 72.69 | 72.61 |
test_proximal_freeb_jac_batched | 0.37 % | 7.465e+03 | 7.493e+03 | 27.90 | 72.59 | 71.79 |
test_proximal_jac_ripple | 1.04 % | 3.458e+03 | 3.494e+03 | 35.98 | 64.79 | 65.09 |
test_proximal_jac_ripple_bounce1d | -0.96 % | 3.517e+03 | 3.483e+03 | -33.85 | 76.15 | 76.24 |
test_eq_solve | 5.97 % | 2.016e+03 | 2.136e+03 | 120.25 | 93.58 | 93.37 |For the memory plots, go to the summary of |
β¦nto dd/B_plasma
|
Update: I fixed a silly bug and now this is working. Direct Biot-Savart volume integral to compute B and the similar volume integral to compute A both agree well with BMW results at moderate resolution. But more benchmarking is needed. |
|
Thank you! Could be really useful for my umbilic stuff! |
desc/integrals/singularities.py
Outdated
|
|
||
| # shape = (source_grid.num_nodes, eval_grid.num_nodes, 3) # noqa: E800 | ||
| dx = rpz2xyz(eval_grid)[None, :, :] - rpz2xyz(data["x"])[:, None, :] | ||
| B = np.sum( # sum over source_grid nodes |
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.
use biot_savart_general instead
|
|
Possibly redundant with #1436, should merge them once we decide on a public API. |
| self._set_up() | ||
|
|
||
|
|
||
| class DoubleChebyshevFourierBasis(_Basis): |
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.
Do we want to make things clearer that this is a real space basis (as opposed to flux coordinates) in the name/docstring?
| Maximum vertical resolution. | ||
| NFP : int | ||
| Number of field periods. | ||
| sym : {``'cos'``, ``'sin'``, ``False``} |
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.
there is not 't' in real space, what do these symmetries refer to now?
| ---------- | ||
| L : int | ||
| Maximum radial resolution. | ||
| M : int |
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 don't love this, almost everywhere in the code N is toroidal, yet here we change it so M is toroidal. I would prefer if here we don't use M, and keep N as toroidal and use something else for the vertical resolution (Or be explicit and write N_R N_phi N_Z as the resolutions
| Parameters | ||
| ---------- | ||
| grid : Grid or ndarray of float, size(num_nodes,3) | ||
| Node coordinates, in (rho,phi,Z). |
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.
rho or R?
| grid : Grid or ndarray of float, size(num_nodes,3) | ||
| Node coordinates, in (rho,phi,Z). | ||
| derivatives : ndarray of int, shape(num_derivatives,3) | ||
| Order of derivatives to compute in (rho,phi,Z). |
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.
rho or R?
| return np.array([]).reshape((grid.num_nodes, 0)) | ||
|
|
||
| # Get the nodes of the grid | ||
| r, p, z = grid.nodes.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.
nitpick but if r is really cylindrical R, use R, same for Z
| l, m, n = modes.T | ||
|
|
||
| # The coordinates in the grid are hardcoded to be called rho, theta, zeta, | ||
| # but these vars are just unique values for 1st, 2nd, and 3rd coordinates |
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 chebfit(y, axis): |
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.
we may aready have some utility functions that do this, see
Line 260 in 702ae49
| def compute_cheb(self, x): |
| return idct(y_c / f.reshape(f_shape), axis=axis, type=1, norm=None) | ||
|
|
||
|
|
||
| def fftfit(y, axis, n=None): |
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.
if these are to be public, some more safeguards should be added. If these are not meant to be public, add _ before the name
| """ | ||
| Real fast fourier transform along axis. | ||
|
|
||
| Designed to convert coefficients into the form expected by the |
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.
what is meant here? takes in a function evaluated at linearly spaced nodes, then fourier transforms it, then adjusts the coefficients to fit what DESC's fourier function requires?
| Parameters | ||
| ---------- | ||
| y : ndarray, shape(...,2N+1,...) | ||
| Function to decompose into basis of Chebyshev functions. |
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.
is this docstring accurate?
| transform from the spectral basis on which A is calculated to the | ||
| real grid on which the curl is to be evaluated. | ||
| the transform must have been built, with derivs>=1. | ||
| scales: jnp.ndarray, shape (3,) |
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.
shouldn't this be done in the transform itself, not here?
| Returns | ||
| ------- | ||
| curl_A : ndarray, shape(n,3) | ||
| The curl of the vector field, in cylindrical coordinates. |
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.
| The curl of the vector field, in cylindrical coordinates. | |
| The curl of the vector field, in cylindrical coordinates, at the points specified by `out_transform`. |
| out_R : ndarray, shape(n,) | ||
| radial location for each point at which to calculate | ||
| the curl. out_R should never be 0. | ||
| in_transform: Transform |
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.
specify these should be bases and grids of R, phi, Z
| for d in range(3): | ||
| if c == 1 and d == 0: | ||
| # partial (R*A_phi)/partial R instead of partial A_phi/partial R | ||
| term_cd = out_transform.transform(RA_phi_coeff, dr=1) |
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.
cant you just use chain rule and do d/dR (R Aphi) = Aphi + R d(Aphi)/dR? then you only need to do one fit above, and all of the partial derivatives are "consistent" in that they are all derivaitves of A, instead of derivatives of A and some derivatves of R*A (which may have a different spectral width than A i.e. same resolution basis may be a worse for for R*A then for A)
| A_coeff = in_transform.fit( | ||
| A * jnp.stack([in_R, jnp.ones_like(in_R), jnp.ones_like(in_R)], axis=-1) | ||
| ) | ||
| # Calculate matrix of terms for the curl |
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.
| # Calculate matrix of terms for the curl | |
| # Calculate matrix of terms for the div |
| @@ -0,0 +1,163 @@ | |||
| """ | |||
| Functions for taking the curl and divergence in cylindrical coordinates. | |||
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.
usually desc.compute has things for computing stuff in the compute dicts. This I could see as useful in the future when we have things in the compute dict using RpZ grids (#1805 ), for now let's call this like _nabla_utils.py maybe or _real_space_deriv_utils.py
| coords = jnp.atleast_2d(coords) | ||
|
|
||
| # Biot-Savart method | ||
| if method == methods[0]: |
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.
| if method == methods[0]: | |
| if method == "biot-savart": |
I prefer to be verbose in the code here
| B = xyz2rpz_vec(B, phi=coords[:, 1]) | ||
|
|
||
| # Virtual casing method | ||
| elif method == methods[1]: |
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.
| elif method == methods[1]: | |
| elif method == "virtual casing": |
| from desc.utils import errorif, xyz2rpz_vec | ||
|
|
||
|
|
||
| def integrate_surface(coords, source_data, source_grid, kernel, chunk_size=None): |
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.
maybe different name like integrate_surface_at_external_point
it does not need to be strictly outside per-say right? just needs to be off-surface, the kernels don't really care about outside or inside as far as the singularity goes
dpanici
left a comment
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.
partially reviewed but already some changes noted
| field = args[0] | ||
| r = rpz[:, 0] | ||
| br, bp, bz = ( | ||
| scale |
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.
[flake8] <821> reported by reviewdog πΆ
undefined name 'scale'
Computes B_plasma in a volume as:
πα΅₯(π«) = ΞΌβ/4Ο β« π(π«') Γ (π« β π«')/|π« β π«'|Β³ dΒ³π«'
This is useful for when you need B_total = B_plasma + B_external in the full computational domain, not just on the boundary surface.