Skip to content

Make getB more batchable #883

Description

@purepani

Currently, it seems that magpy.getB is pretty restricted in how it can work with shapes. Based on the documentation, it can essentially only work with one extra dimension. It would be useful to be able to work with any number of extra dimensions. That way, you can easily calculate a ton of field values at once with broadcasting.
For example, the following is currently not possible, but should be:

import magpylib as magpy
import numpy as np

positions = np.arange((10, 20, 3))
B = magpy.getB(
     source='Cylinder',
     observers=positions,
     polarization=(0, 0, 1),
     dimension=(1, 1)
) # shape is (10, 20, 3)

positions = np.arange((10, 3))
polarization = np.arange((20, 3))
dimension = np.arange((30, 40, 2))
B = magpy.getB(
     source='Cylinder',
     observers=positions[:, None, None, None],
     polarization=polarization[None, :, None, None],
     dimension=dimension[None, None, :, :]
) # shape is (10, 20, 30, 40, 3)

I am guessing that the reason is due to the fact that the same limitation applies to scipy.spacial.transform.Rotation class, but this should be able to be worked around with reshaping before and after rotation.

Activity

  1. OrtnerMichael commented on Aug 17, 2025

    @OrtnerMichael
    Member

    HEy @purepani

    Thanks for pointing this out. We were too lazy - the original idea was that the users could do the reshaping - ofc. boadcasting i your example offers memory advantages. When moving to more array efficiency we should certainly consider this.

    But also I am currently quite unhappy with the structure of getB. I think it the functional part should be separated from the oo one completely for obvious reasons (terrible docstrings, overloding confuses, ...). Maybe something using classmethods

    magpy.magnet.Cuboid.getB(
        obs_array,
        pos_array,
        mag_array,
    )

    instead of
    getB("Cuboid", ....)

    what do you think ? I'm also discussing this with myself in #704 :)

  2. purepani commented on Aug 17, 2025

    @purepani
    ContributorAuthor

    You can maybe get away with the current interface by adding typing overloads
    Something like

    from typing import overload, Literal
    
    @overload
    def getB(magnet: Literal["Cuboid"], ...): ... # cuboid parameters.
    
    #other typing overloads for getB
    
    
    #actual implementation 
    def getB(magnet: Literal["Cuboid"] | Literal["Sphere"], ...):
    #actual implementation 

    This will definitely help with ide support. I have no clue how well docstrings work with this though.

    Otherwise I think the getB_cuboid is simple and easy to implement, and use.

  3. OrtnerMichael commented on Aug 17, 2025

    @OrtnerMichael
    Member

    What you are suggesting is an improvement from a developer point of view - makes sense.

    What do you mean by getB_cuboid ? You think the way its currently done is fine from a user perspective, or you think a function 'getB_cuboid()', getB_sphere() ... would be nice to have (top level?) functions for the functional interface ?

  4. purepani commented on Aug 17, 2025

    @purepani
    ContributorAuthor

    I think that getB_cuboid works sufficiently well for a top level interface. This feels like one of the situations where the simple answer is better.

    Alternatively, you could have something like getCuboid(field="B",...), or getCuboid(...).getB() but I think that's trying to be too clever here. I can't think of a usability reason that makes this any more usable on the user end compared to getB_Cuboid(...), and I don't think there's really an advantage in terms of simplicity either for trying to combine them in a unified interface.

    Maybe it would be useful to think of a Protocol that could declare what the interface for these getB functions look like, since they are pretty similar, but it seems a bit unnecessary to me. I can't think of any concrete use cases for it.

  5. purepani commented on Aug 17, 2025

    @purepani
    ContributorAuthor

    There's also maybe a way to do this with typing generics but again, I feel like it's maybe trying to be too smart.

  6. OrtnerMichael commented on Aug 18, 2025

    @OrtnerMichael
    Member

    Keeping it simple is the right way to go, but this would also mean that we would have a ton of top level functions ... getB_xxx(), getH_xxx(), getJ_xxx(), and getM_xxx() for xxx in all Magpylib classes

    This is why I thought to use classmethods to give the context of the respective class rather than adding about 50 top level functions.

  7. purepani commented on Aug 18, 2025

    @purepani
    ContributorAuthor

    I personally prefer flat structures particularly for functional programming(it's part of the zen of python as well).
    I'm also a bit worried about users getting confused with class methods, and maybe mixing them up with the magnet classes; and if you want to put them in the normal magnet classes, then there's no namespace separation between the 2.
    I think in an ideal world, the code I would want to write looks like this:

    getB["Cuboid"](...)

    With IDE support/auto complete when specifying the generic. Since the situation we currently have is quite similar to this, personally, I think we can try to fix the typing situation on the current getX functions and see if we can make the docgen and ide support better with that, rather than trying to change the interface.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions