Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

purepani
Copy link
Contributor

@purepani purepani commented May 6, 2025

This PR will track the status of array api support per #792.
TODO:

  • Fields
    • Dipole
    • Sphere
    • Cuboid
    • Circle
    • Cylinder
    • Cylinder Segment
      • Check docstrings for changes that were made unintentionally for find and replace np. with xp. and len(r) with r.shape[0], and maybe other things that I forgot about finding and replacing.
    • Polyline
    • Tetrahedron
    • Triangle
    • Triangular Mesh
  • Special Functions
    • Cel
    • El3
    • Elliptical E (from scipy)
    • Elliptical K (from scipy)
  • getB/getH/getM/getJ
  • Fix use of xp.linalg functions to be guarded under an if statement, or using a utility. Currently, the arrays are assumed to have the linalg extension, but this is not guaranteed by the api.
  • Implement a better conversion to float that isn't cast to xp.float64 for everything.
  • Implement a lazy_while for the elliptic integrals.

So far, I've implemented support for the dipole core function, and also modified the test to use array-api-strict instead of numpy.

I had to change the core function to use masks to avoid division by 0 instead of ignoring warnings, since otherwise there is no way to ignore the warnings in a general way. I haven't tested this yet with any API backends.

I'm planning on using this PR to collect all the commits for the core functions, but I am also able to split them into separate PR, since that will be easier to review(hence the empty dev commit). As of right now, the dipole could be merged on it's on after review, since it doesn't change the behavior of the library(though an argument is added to the dipole function for the array backend). I can create a separate PR for each function as they are implemented if wanted, but I'll keep this draft PR to track it's overall progress.

@purepani purepani force-pushed the push-yxwzsklvxltt branch 2 times, most recently from 2e5e20c to 6ce7346 Compare May 7, 2025 23:08
@purepani
Copy link
Contributor Author

purepani commented May 7, 2025

I just implemented the sphere. I implemented the inside/outside with masks. While, as noted in the comments, this might not be ideal for CPU(I'm not quite sure about this, but I assume you investigated that and found it to be true), It will make a good difference for the gpu. Ideally, these core functions should be implemented fully with array operations, so that backends like jax can compile them super efficiently.
I will add a checklist at the top for the different functions.

@purepani purepani force-pushed the push-yxwzsklvxltt branch from 6ce7346 to 6db8100 Compare May 7, 2025 23:13
@purepani
Copy link
Contributor Author

purepani commented May 7, 2025

Also note that each commit is individually reviewable, and I plan to keep it that way :)

@purepani
Copy link
Contributor Author

purepani commented May 7, 2025

Additionally, if someone is willing to work on #732, I would feel a lot more confident in my translations here.

@purepani purepani force-pushed the push-yxwzsklvxltt branch 7 times, most recently from a2d4c72 to 5a3e6f2 Compare May 8, 2025 04:54
@purepani
Copy link
Contributor Author

purepani commented May 8, 2025

Added cuboid, polyline, and triangle.
I'm fairly certain these aren't going the most efficient way to implement these, but that can be improved later.

@purepani purepani force-pushed the push-yxwzsklvxltt branch 2 times, most recently from b3cfa8a to f928a32 Compare May 8, 2025 06:47
@Alexboiboi
Copy link
Member

Hi @purepani,

Thank you very much for starting this !
I'm currently not able to spend much time reviewing this but I soon will.

Not sure yet if we should split this in multiple PRs but in the first step, modifications should only apply to the core functions ! Later when SciPy functions (Rotation) is implemented in an array-api compatible way we can start working the the general functional interface. And finally see if this can be applied all the way through the object oriented interface.

BR, Alex-

@purepani
Copy link
Contributor Author

purepani commented May 9, 2025

I have the commits organized so that it's super easy to create multiple PRs if needed(you can see the merge commit I have; they are already separate branches), so I can make them super easily if you'd prefer that! Or I can leave it all here! Just let me know if you want one or the other.

Also, right now, they change the core functions api out of some necessity. I will have to think about how to improve that.

@OrtnerMichael
Copy link
Member

Hey @purepani,
Its the first time someone outside of the core team (me and alex ;D) is attempting a major thing like this - so thanks a lot for starting this. I will take time to review and help where I can.

Concerning the FE checks #732: Not really relevant. Our implementations are all properly tested - the FE tests were simply thought as a "second guarantee" that things are ok. In any case FE is massively lacking the precision the analytical method offers.

I think @Alexboiboi is right that we should only start on the core functions for now, top level getB should have a parameter numerical_backend='numpy' or something like this that is by default set tom 'numpy' and allows one to select respective other packages.

Concerning special functions: in some packages some are well implemented and ready in compiled form - in which case we should make use of them. In other cases we should have our own (which will never be as fast). Not 100% sure how to handle this ?

br michael

@purepani
Copy link
Contributor Author

purepani commented May 9, 2025

I think @Alexboiboi is right that we should only start on the core functions for now, top level getB should have a parameter numerical_backend='numpy' or something like this that is by default set tom 'numpy' and allows one to select respective other packages.

Just to respond to this, the current way the array api works is that any array x from an array has a function xp = x.__array_namespace__(), such that xp acts like the array "module,"(i.e. in a similar way to np). So such a parameter for the numerical backend isn't necessary.

Some of the things that we should look to the current efforts in scipy are listed here: https://data-apis.org/array-api/latest/design_topics/index.html
I particularly want to figure out how to deal with lazy vs eager execution. Right now I'm optimizing for lazy execution(like in libraries like jax) but this will cause performance degradation in eager execution libraries(like numpy).

@purepani purepani force-pushed the push-yxwzsklvxltt branch 3 times, most recently from 70a1dcb to 51b6193 Compare May 9, 2025 17:09
Comment on lines 50 to 52
... observers=xp.array([(1,1,1), (2,2,2)]),
... dimensions=xp.array([(1,1,1), (1,2,3)]),
... polarizations=xp.array([(0,0,1), (.5,.5,0)]),

Choose a reason for hiding this comment

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

xp.array does not exist in the standard, you probably want xp.asarray

Copy link
Contributor Author

@purepani purepani May 9, 2025

Choose a reason for hiding this comment

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

Same here, I thought I corrected them

... polarizations=xp.array([(0,0,1), (.5,.5,0)]),
... )
>>> with np.printoptions(precision=3):
>>> with xp.printoptions(precision=3):

Choose a reason for hiding this comment

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

this is not in the standard

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Huh, I thought I removed these

Comment on lines 59 to 61
r = xp.sqrt(xp.vecdot(observers, observers))[
:, xp.newaxis
] # faster than np.linalg.norm

Choose a reason for hiding this comment

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

@lucascolley
Copy link

@Alexboiboi @OrtnerMichael @purepani of course feel free to proceed however you would like! But I would suggest the following integration strategy:

  • keep one branch with all changes that seem to be required (e.g. this one) so that we can see the total extent of changes
  • keep changes to separate modules in separate commits so they can be broken out into PRs at a later stage
  • if any changes make sense to do even for just NumPy (e.g. refactoring), merge them first in separate PRs to reduce the diffs
  • if it seems like you need to write any helpers which would be of general use rather than specific to magpylib, open an issue at array-api-extra. It may be that a solution already exists in SciPy/scikit-learn, or that something you write here can be upstreamed to array-api-extra
  • once everything starts to look relatively clean and feasible, have a think about how you want to test, and look into following what we have upstream
  • at that stage, think about the release strategy. Do you want to release a new major version with array-api-extra and array-api-compat as required dependencies? Or would you rather vendor them and gradually expand support in an experimental mode first?
  • once all of this is done, start breaking out PRs from the umbrella branch for individual review and merge

@lucascolley
Copy link

lucascolley commented May 10, 2025

I think @Alexboiboi is right that we should only start on the core functions for now, top level getB should have a parameter numerical_backend='numpy' or something like this that is by default set tom 'numpy' and allows one to select respective other packages.

Just to respond to this, the current way the array api works is that any array x from an array has a function xp = x.array_namespace(), such that xp acts like the array "module,"(i.e. in a similar way to np). So such a parameter for the numerical backend isn't necessary.

Yes, in a catchphrase the pattern this work enables is "array type in === array type out". I highly recommend reading scipy/scipy#18286 to understand the design we are implementing in SciPy.

@purepani purepani force-pushed the push-yxwzsklvxltt branch from 51b6193 to 39a63f7 Compare May 17, 2025 19:47
@purepani
Copy link
Contributor Author

I disabled the "Allow edits by maintainers" for now since I keep forgetting to run the precommit hook manually at the moment(I use jj which doesn't support git precommit hooks), and the automatic commit from the bot is a bit annoying. Obviously I'll try and format stuff when I remember to run ruff, but it's a bit annoying to deal with the bot adding commits every time I push.

@purepani purepani force-pushed the push-yxwzsklvxltt branch from f4c5131 to 0575e2c Compare May 19, 2025 20:42
@purepani
Copy link
Contributor Author

purepani commented May 19, 2025

For getting elliptic functions working, I used array_api_strict.lazy_apply in order to implicitly convert to numpy arrays for the scipy functions. This means that for backends that run on the gpu, this won't work without copy.

The reason I chose to do this right now instead of writing a python implementation for supporting gpus was because
A. It'll be way slower for CPU code
B. It wouldn't work with lazy backends like jax since the implementation uses a value dependent while loop(to test for tolerance).

The solution in jax would be to use jax.lax.while_loop, but obviously we can't directly use that here, so a utility function lazy_while that dispatches to jax.lax.while_loop for jax. Something similar will probably need to be done for dask, but I have never used it before, so I have no clue how dispatching a while loop to it would work.

Presumably that utility is something that would fit in array-api-extra, but I can just make it here for now and upstream it later.

@OrtnerMichael
Copy link
Member

hi @purepani

Sorry for the missing feedback from our side. So far I was not able to review all your PR's. I'm about to complete a publication and will then have more time to invest...probably starting next week.

As this touches the computational core I'll give it a very very thorough review. We should go through this function by function. which core function / PR do you suggest we start with ?

@purepani
Copy link
Contributor Author

purepani commented May 29, 2025

Hey @OrtnerMichael
No problems with the delays. We should start off with this, which just adds some tests for the core functions. This essentially just makes it slightly easier to develop against. I just ran the core functions with the examples given in the documentation, and used those values to make tests for them.

After that, I've been throwing all my code in #861. This is just to get an overview of the changes, as I started separating out the code into more PRs. Most of the individual functions are ready for review, but I need to split out the code in the last commit. I'll probably have that ready once the core tests PR is merged. I'll update here, as well as mark the other PRs as ready once I do so.
Most of the functions are ready after I split out the code correctly, aside from the cylinder segment, which may benefit a bit of vectorization before converting to the array API.

@lucascolley
Copy link

@OrtnerMichael I wrote a suggested review strategy here: #844 (comment)

@purepani
Copy link
Contributor Author

purepani commented May 31, 2025

Hey @lucascolley
Dask doesn't support the full linalg extension for the array api, but it also seems like they implement part of the linalg extension, so checking for the linalg extension with hasattr(xp, 'linalg') doesn't work. Per https://data-apis.org/array-api/latest/extensions/index.html, I believe the module should probably not exist under that name.
I just wanted to make sure that my understanding here is correct before making an issue in dask.

@lucascolley
Copy link

lucascolley commented May 31, 2025

@lithomas1 do you know if there is an issue or some other status somewhere for dask.linalg in array-api-compat?

@lucascolley
Copy link

lucascolley commented May 31, 2025

Per https://data-apis.org/array-api/latest/extensions/index.html, I believe the module should probably not exist under that name.

In an ideal world, yes. Unfortunately that guidance can't really hold for pre-existing array libraries. I guess we could look at some alternative for array-api-compat, but not sure what that would look like.

@purepani
Copy link
Contributor Author

purepani commented May 31, 2025

Maybe a function in array_api_compat which correctly gives the extension support? Or maybe such a function should be part of the spec?

@lucascolley
Copy link

There is https://github.com/data-apis/array-api-compat/blob/main/array_api_compat/dask/array/linalg.py, but IIUC it is a WIP and somewhat limited by what is implemented in Dask proper.

@purepani
Copy link
Contributor Author

purepani commented May 31, 2025

I realize my phrasing was slightly unclear.
I meant a function that simply tells you if xp has a valid linalg extension, so instead of

...
if hasattr(xp, 'linalg'):
    return xp.linalg.cross(x, y)

we can do

from array_api_compat import check_extensions, Extensions
if check_extensions(xp, Extensions.LINALG):
    return xp.linalg.cross(x, y)

(only using an enum in my example because I like type safety, but check_extensions(xp, "linalg"): is just as reasonable).

With it part of the spec

if xp.has_extension('linalg'):
    return xp.linalg.cross(x, y)

or maybe

if 'linalg' in xp.extensions:
    print(xp.extensions) # ['linalg', 'fft']
    return xp.linalg.cross(x, y)

@lucascolley
Copy link

Ah, got you. Feel free to open an issue on the array-api repo! I guess until now, we have made do with external knowledge of the limitations of pre-existing libraries. Real examples of hitting this for missing features which are not likely to be added soon sound like good motivation for this sort of addition, however.

@purepani purepani force-pushed the push-yxwzsklvxltt branch from 5b5a533 to 5a2bc13 Compare June 2, 2025 00:29
@purepani
Copy link
Contributor Author

purepani commented Jun 2, 2025

Ok @OrtnerMichael, I have some stuff that should be reviewable. I'll run the formatter later since my local branches are a bit of a mess and it's a bit tricky to resolve everything.
I also haven't completely separated everything so that each pull request is fully independent of each other yet(for example, the elliptic functions, and the tests for multiple backends) aren't all completely separated by use in the PRs. Just let me know if there's too much unnecessary stuff in one of the PRs and I'll try and make it more granular.

So first, this PR just adds core tests, as mentioned earlier. This should be reviewed first:

Then, here is a list of complete PRs, which work correctly with the all the backends that I added to the tests(dask, numpy, array_api_strict, and jax). I added tests for multiple backends since the lazy backends like jax are a bit more restrictive in what you can do with them.

Note that this is all the core functions except for the cylinder segment, since I think that core function will need to be refactored before a simple array api conversion can be done(I believe it's best to vectorize the application of all the different cases.

Additionally, this PR contains all the changes to the core functions, and some in-progress changes: #861

Feel free to review the core functions in any order. The dipole is obviously the simplest, so that will probably be best to start off with. I think I'll need to split off the modifications of the tests to support the array api before merging, but otherwise, they should be suitably reviewable.

@lithomas1
Copy link

@lithomas1 do you know if there is an issue or some other status somewhere for dask.linalg in array-api-compat?

I don't think so. There's not really anything actionable on the array-api-compat side (and there doesn't seem to be anyone working on implementing linalg stuff in dask for a while now)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants