Skip to content
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

Feature/line integrator #1

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions biot_savart_integrators/generic/integrand.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""
from scipy.constants import mu_0, pi
from .utils import cross
import xarray as xr


_SI_FACTOR = mu_0 / (4*pi)
Expand All @@ -13,6 +14,16 @@
def biot_savart_integrand(r, r_j, j, spatial_dim):
R = r - r_j
numerator = cross(j, R, spatial_dim)
denominator = (R**2).sum(dim=spatial_dim)**(3/2.)
denominator = (R**2).sum(dim=spatial_dim) ** (3 / 2.)
kripnerl marked this conversation as resolved.
Show resolved Hide resolved
integrand = _SI_FACTOR * numerator / denominator
return integrand
return integrand


def biot_savart_potential_integrand(r: xr.DataArray, r_j: xr.DataArray,
kripnerl marked this conversation as resolved.
Show resolved Hide resolved
j: xr.DataArray, spatial_dim: str):
R = r - r_j
numerator = j
denominator = (R ** 2).sum(dim=spatial_dim) ** (1 / 2.)
kripnerl marked this conversation as resolved.
Show resolved Hide resolved
integrand = _SI_FACTOR * numerator / denominator
return integrand

36 changes: 34 additions & 2 deletions biot_savart_integrators/generic/line_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
"""
import xarray as xr
from .integrand import biot_savart_integrand as bsintegrand
from .integrand import biot_savart_potential_integrand as bs_pot_integrands


def biot_savart_integral(r: xr.DataArray, r_c: xr.DataArray,
kripnerl marked this conversation as resolved.
Show resolved Hide resolved
dl: xr.DataArray, j: xr.DataArray,
integration_dim: str, spatial_dim: str):
"""Numerical integration of B-S law for set of line elements.
integration_dim: str, spatial_dim: str) -> xr.DataArray:
"""Numerical integration of B-S law for the set of line elements.
kripnerl marked this conversation as resolved.
Show resolved Hide resolved

:param r: Positions where the magnetic field is evaluated.
:type r: xr.DataArray
Expand All @@ -34,3 +36,33 @@ def biot_savart_integral(r: xr.DataArray, r_c: xr.DataArray,
# integral = integrand.sum(integration_dim)
integral = integrand.integrate(integration_dim)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be resolved before the merge. See the todo note.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, a simple solution would be to add an optional parameter integral_method='trapz' in a similar fashion as you do in pleque and add a warning about this into the docstring.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose that method='sum' would be something like intergand.sum(integration_dim) * dl?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. integrate here only do "summation" of elements of the integrand, but with Trapez rule (see the example below). dl element is already included in j.

If you assign some coordinates (for example l = cumsum(dl)) the integration will brake.

In [3]: a = xr.DataArray(np.linspace(1, 20, 20))                                                                                     

In [4]: a                                                                                                                            
Out[4]: 
<xarray.DataArray (dim_0: 20)>
array([ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10., 11., 12., 13.,
       14., 15., 16., 17., 18., 19., 20.])
Dimensions without coordinates: dim_0

In [5]: a.sum()                                                                                                                      
Out[5]: 
<xarray.DataArray ()>
array(210.)

In [8]: a.integrate("dim_0")                                                                                                         
Out[8]: 
<xarray.DataArray ()>
array(199.5)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A possible solution would be something like this (untested; adjusted your function):

def biot_savart_integral(r, r_c, integration_dim, spatial_dim, I=1):
    # TODO make sure won't divide by coord - integration should cancel, but how accurately at edges?
    dl = r_c.differentiate(integration_dim)
    j = I*dl
    dl = (dl ** 2).sum(spatial_dim) ** 0.5 # normalize to be length element, not vector
    j = j / dl # remove length element

    integrand = bsintegrand(r, r_c, j, spatial_dim)
    integrand[integration_dim] = np.cumsum(dl)

    integral = integrand.integrate(integration_dim)
    return integral

or

def biot_savart_integral(r, r_c, integration_dim, spatial_dim, I=1):
    # TODO make sure won't divide by coord - integration should cancel, but how accurately at edges?
    dl = r_c.differentiate(integration_dim)
    j = I*dl
    integrand = bsintegrand(r, r_c, j, spatial_dim)
    
    integrand[integration_dim] = np.cumsum(dl)
    integral = integrand.integrate(integration_dim) / dl.sum()
    return integral

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean integrand.coords[integration_dim] = np.cumsum(dl) ?

I suppose the simplest solution would be just to use .sum() instead of .integrate(). Perhaps the possible inaccuracy of sum is not worth all these workarounds?

return integral


def biot_savart_potential_integral(r: xr.DataArray, r_c: xr.DataArray,
dl: xr.DataArray, j: xr.DataArray,
integration_dim: str, spatial_dim: str) -> xr.DataArray:
"""Numerical integration of B-S law for vector potential for the set of line elements.

:param r: Positions where the magnetic field is evaluated.
:type r: xr.DataArray
:param r_c: Centers of line current elements.
:type r_c: xr.DataArray
:param dl: Lengt of the current element (scalar).
:type dl: xr.DataArray
:param j: Spatial current flowing through the element in Amps.
:type j: xr.DataArray
:param integration_dim: Dimension name over which index current
elements.
:type integration_dim: str
:param spatial_dim: Name of the spetial dimension.
:type spatial_dim: str
"""
j_int = j * dl
integrand = bs_pot_integrands(r, r_c, j_int, spatial_dim)

# TODO: Trapez rule used by integrate is more precise method then simple
# summation. However, this method may brake if the coordinates are assign
# to the integration_dim
# integral = integrand.sum(integration_dim)
integral = integrand.integrate(integration_dim)
return integral
26 changes: 24 additions & 2 deletions tests/test_inf_wire.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
@pytest.fixture
def setup():
I = 1.15766
a = 1.23
a = 1.0 # 1.23
# high phi resolution to fulfill tolerance

zs = xr.DataArray(np.linspace(-10000, 10000, int(1e6)), dims=['z'], name='z')
r0 = xr.DataArray([a, 0, 2.1], coords=[('s', list('xyz'))], name='r0')
#r0 = xr.DataArray([a, 0, 2.1], coords=[('s', list('xyz'))], name='r0')
r0 = xr.DataArray([a, 0, 0], coords=[('s', list('xyz'))], name='r0')
r_c = xr.concat([xr.zeros_like(zs), xr.zeros_like(zs), zs], r0.s)
return I, a, r0, r_c

Expand All @@ -35,3 +36,24 @@ def test_inf_wire_line_el(setup):

np.testing.assert_allclose(B_line_el.sel(s=['x', 'z']), [0, 0])
np.testing.assert_allclose(B_line_el.sel(s='y'), Btheta_analytic)


def test_inf_wire_line_el_vector_potential(setup):
I, a, r0, r_c = setup
dl = r_c.differentiate("z") # vector
j = dl * I # vector with length element
dl = (dl ** 2).sum("s") ** 0.5 # actual lengths
j = j / dl # normalization to line element length.

l = 10000
# Approximation for a << l
# https://phys.libretexts.org/Bookshelves/Electricity_and_Magnetism/Book%3A_Electricity_and_Magnetism_(Tatum)/09%3A_Magnetic_Potential/9.03%3A_Long%2C_Straight%2C_Current-carrying_Conductor

Az_analytical = (np.log(2*l) - np.log(a)) * mu_0 * I / (2 * np.pi)

A_calc = line_elements.biot_savart_potential_integral(r0, r_c, dl, j,
integration_dim="z",
spatial_dim="s")

np.testing.assert_allclose(A_calc.sel(s=['x', 'y']), [0, 0])
np.testing.assert_allclose(A_calc.sel(s='z'), Az_analytical)