Skip to content

Add 'ulab' as an extmod; enable on nrf and most samd51 boards #2583

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

Merged
merged 9 commits into from
Mar 4, 2020

Conversation

jepler
Copy link

@jepler jepler commented Feb 4, 2020

No description provided.

@jepler
Copy link
Author

jepler commented Feb 4, 2020

My test program prints the peak audio frequency detected:

import audiobusio
import board
import ulab
import time

audioin = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, bit_depth=16, sample_rate=16000)

b = array.array("H", [0]) * 512

def maxel(values):
    return max(enumerate(values), key=lambda x: x[1])

t0 = time.monotonic_ns()
while True:
    audioin.record(b, len(b))
    u = ulab.array(b)
    f = ulab.fft(u)[0] # Take real part
    f[0] = 0 # Remove DC component
    f = abs(f) # We care about the magnitude
    idx = ulab.argmax(f[:len(f)//2])[0]  # Only low frequencies, thank you
    freq = idx * audioin.sample_rate / len(f)
    value = f[idx]
    t1 = time.monotonic_ns()
    print("Peak at f={:.2f} in {:.1f}s".format(freq, (t1-t0) * 1e-9))
    t0 = t1

@ladyada
Copy link
Member

ladyada commented Feb 4, 2020

@caternuson ya might find this useful!

@caternuson
Copy link

oooooooooo FFT. nice!

@jepler jepler changed the title Add 'ulab' as an extmod; enable on clue Add 'ulab' as an extmod; enable on nrf and most samd51 boards Feb 4, 2020
@jepler
Copy link
Author

jepler commented Feb 4, 2020

As requested by @dhalbert this is now enabled for all nrf boards and most samd51 boards.

@jepler
Copy link
Author

jepler commented Feb 4, 2020

Here's the spectrograph demo adapted for ulab:
code.txt

@dhalbert dhalbert self-requested a review February 4, 2020 19:51
@tannewt
Copy link
Member

tannewt commented Feb 4, 2020

Nice job on this! I'd love to see the __init__.c moved to shared-bindings with the docs copied in. That way it'll appear here like other modules: https://circuitpython.readthedocs.io/en/latest/shared-bindings/index.html I don't expect the functions themselves to be split. Let's just document it in our standard way.

@ladyada
Copy link
Member

ladyada commented Feb 4, 2020

tested mic waterfall - works great! will be interesting to see what other projects we can do :)

@jepler
Copy link
Author

jepler commented Feb 5, 2020

updated faster non-scrolling version : code.txt

@jepler
Copy link
Author

jepler commented Feb 6, 2020

I think this is ready for review, including the "our style" docs even if they are mostly extremely short blurbs rather than full reference documentation.

There are some hard faults that I have encountered (wrong argument types not caught, I think) but we can pursue those by PR'ing upstream. The one I know offhand I verified exists in micropython, so it's not introduced by us.

I also have some ideas that might make the differences (vs micropython-ulab) we have to carry smaller, but I didn't put them into practice yet. (and they're just ideas, might not pan out)

Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

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

Thank you so much for adding these docs! Very helpful. One typo and a question about the extra bits on array. Otherwise it looks great!

@jepler
Copy link
Author

jepler commented Feb 7, 2020

I noticed that ulab float arrays are actually using doubles. This is probably not what we want, but I'm not sure where the knob is to turn. I know this because the rawsize method says elements are 8 bytes big...

@jepler
Copy link
Author

jepler commented Feb 7, 2020

oh I was mistaken -- arrays use doubles on the unix port, but floats on real devices, so that's no concern

@v923z
Copy link

v923z commented Feb 9, 2020

I noticed that ulab float arrays are actually using doubles. This is probably not what we want, but I'm not sure where the knob is to turn. I know this because the rawsize method says elements are 8 bytes big...

It depends on your platform and the implementation, whether you have floats or a doubles: https://github.com/v923z/micropython-ulab/blob/936bb3bae55dfdc81d1c8d9580905ae0ac760a44/code/ndarray.h#L22 , also in micropython#4380 (comment)

@v923z
Copy link

v923z commented Feb 9, 2020

My test program prints the peak audio frequency detected:
...
f = ulab.fft(u)[0] # Take real part
f[0] = 0 # Remove DC component
f = abs(f) # We care about the magnitude

It is probably nit-picking, but the real part of the FFT is not what you want. This is why I implemented spectrum. You need the absolute value of the complex transform, and not the absolute value of the real part. The maximum position of the real part might depend on the phase of the signal.

@jepler
Copy link
Author

jepler commented Feb 9, 2020

@v923z A philosophical difference between micropython and circuitpython is, how much like standard python and well known python packages should the product strive to be. Circuitpython has made a lot of decisions to be more like python. Our default is to try to make our reimplementations of standard Python features be subsets of the "standards". We think that this is helpful, because it removes friction when going between circuitpython and desktop python. Another adafruit software product is blinka, which lets ARM SBCs or even PCs with particular USB devices use packages implemented for CircuitPython to interface to hardware, another good reason to remove friction going between Python and CircuitPython. @tannewt could make a much more principled and impassioned explanation of this position than me. 😃

That means that, if we say "we are bringing in a numpy-like math library", we will probably make some changes to make it more like numpy. We might even name it numpy. But, whatever exactly these changes are, if they are things you don't want we will do our best to make sure it's circuitpython maintainers who bear the costs, not you.

I think there's still the possibility to say: we're bringing in ulab, which is numpy inspired but not a numpy subset. In that case, we'd want our software to behave as much like upstream ulab.

Personally, I think that trying to be a proper numpy subset is trouble. FFT is key functionality for us, but it becomes necessary to implement a proper complex type in ulab to match the numpy interface. As you outline, this has a serious negative effect on code size since aside from anything else, for binary operations we need new cases where complex can work with any of float, int8, uint18, int16, or uint16.

@dhalbert
Copy link
Collaborator

@jepler do you feel this is ready for a first merge? I think it just needs a make translate and push to build successfully.

Copy link
Collaborator

@dhalbert dhalbert left a comment

Choose a reason for hiding this comment

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

One trivial style thing, but I realize now there are still some things under discussion.

@v923z
Copy link

v923z commented Feb 10, 2020

@v923z A philosophical difference between micropython and circuitpython is, how much like standard python and well known python packages should the product strive to be. Circuitpython has made a lot of decisions to be more like python. Our default is to try to make our reimplementations of standard Python features be subsets of the "standards". We think that this is helpful, because it removes friction when going between circuitpython and desktop python. Another adafruit software product is blinka, which lets ARM SBCs or even PCs with particular USB devices use packages implemented for CircuitPython to interface to hardware, another good reason to remove friction going between Python and CircuitPython. @tannewt could make a much more principled and impassioned explanation of this position than me. smiley

That means that, if we say "we are bringing in a numpy-like math library", we will probably make some changes to make it more like numpy. We might even name it numpy. But, whatever exactly these changes are, if they are things you don't want we will do our best to make sure it's circuitpython maintainers who bear the costs, not you.

Well, it is easier said than done. And I believe these costs can be avoided. As far as I see from your and Scott's comments, the problem is that ulab contains some extra compared to numpy, and it breaks compatibility (upcasting notwithstanding). I think, there is an easy fix for that: we can factor out everything that is not numpy into a separate module with a pre-processor switch, and if you don't want it, you don't compile it. In fact, you can already do that in the present master, but I can make the separation of numpy-like features from numpy-unlike more obvious by moving everything into a separate module.

Do you want to implement sub-modules as is done in numpy (c.f. fft, linalg, etc.)?

Are special exceptions (e.g. in linalg) really necessary?

I can't stress enough that if you elect to maintain your own modifications in circuitpython, that will be the kiss of death. Fixes in ulab will take time to propagate to circuitpython, and fixes in circuitpython will take time to propagate to ulab. Documentation efforts will have to be doubled. If there is an issue, it won't be obvious to users, where that should be raised, etc. I should also selfishly point out that such a separation would also negatively affect ulab itself. I found the feedback from yourself, Dan, and Andrew extremely useful, and I would be sad to see these resources go.

I strongly believe that the best course of action is to maintain a single code base, and make it such that you can simply plug the code into either circuitpython, or micropython without having to do anything by hand. I would be happy to take the requirements of circuitpython into consideration, and implement the pre-processor macros.

Keeping these comments in mind, I wanted to ask, what you need to make the library compatible with circuitpython. I recall that Dan mentioned, that the call signature of make_new is different in circuitpython (v923z/micropython-ulab#21 (comment)), but I don't see that in your code. As I indicated in v923z/micropython-ulab#21 (comment), we can rectify that with a macro. Anything else? You have a compatibility header file, but that has to be done once, I suppose, and then we can forget about it.

I think there's still the possibility to say: we're bringing in ulab, which is numpy inspired but not a numpy subset. In that case, we'd want our software to behave as much like upstream ulab.

Personally, I think that trying to be a proper numpy subset is trouble. FFT is key functionality for us, but it becomes necessary to implement a proper complex type in ulab to match the numpy interface. As you outline, this has a serious negative effect on code size since aside from anything else, for binary operations we need new cases where complex can work with any of float, int8, uint18, int16, or uint16.

Bringing in complexes opens up a nasty can of worms: at that moment, you might want to be able call sin, or cos or whatever other function with complex arguments. That is expensive, but would probably be never used.

@tannewt
Copy link
Member

tannewt commented Feb 11, 2020

Hi @v923z, thanks for ulab and for jumping in! I'd encourage you to join our Discord chat so we can have quicker back and forth.

I like the idea of moving the non-numpy compatible stuff into a separate module. It also looks like you want to have different size tiers of the library. I wonder if having many non-numpy modules would be a better structure for ulab. That way compatibility is available at the module level and is checked at runtime on import rather than function use.

@v923z
Copy link

v923z commented Feb 11, 2020

Hi @v923z, thanks for ulab and for jumping in! I'd encourage you to join our Discord chat so we can have quicker back and forth.

Thanks for the invitation!

I like the idea of moving the non-numpy compatible stuff into a separate module. It also looks like you want to have different size tiers of the library.

I think it is a necessity, actually. Some platforms are rather small, while others have a generous amount of flash. I have been thinking of merging the 1dim, and master branches, and let the user decide at compile time, what they want. That should not be too much work, and we could then have a single code base.

I wonder if having many non-numpy modules would be a better structure for ulab. That way compatibility is available at the module level and is checked at runtime on import rather than function use.

I am not entirely sure I see what you mean...

@tannewt
Copy link
Member

tannewt commented Feb 12, 2020

I wonder if having many non-numpy modules would be a better structure for ulab. That way compatibility is available at the module level and is checked at runtime on import rather than function use.

I am not entirely sure I see what you mean...

I think we talked about this on Discord but I'll recap here. The idea is to split ulab into smaller modules than what numpy has so that functionality can be enabled at a more granular level. Enabling and disabling on module boundaries means that errors will be generated when an unsupported module is imported rather than when an unsupported function is called in the middle of code.

@v923z
Copy link

v923z commented Feb 12, 2020

I think we talked about this on Discord but I'll recap here. The idea is to split ulab into smaller modules than what numpy has so that functionality can be enabled at a more granular level. Enabling and disabling on module boundaries means that errors will be generated when an unsupported module is imported rather than when an unsupported function is called in the middle of code.

Yeah, the discussion on discord was clear, but it is great that you summarised it here, for the benefit of the others.

@dhalbert
Copy link
Collaborator

Needs merge from upstream due to translation changes.

@v923z
Copy link

v923z commented Feb 13, 2020

I think we talked about this on Discord but I'll recap here. The idea is to split ulab into smaller modules than what numpy has so that functionality can be enabled at a more granular level. Enabling and disabling on module boundaries means that errors will be generated when an unsupported module is imported rather than when an unsupported function is called in the middle of code.

@tannewt
Scott, I have managed to separate out the modules. Here is an example that works

import ulab
from ulab import fft
from ulab import poly

a = ulab.array([1, 2, 3, 4])
poly.polyval(ulab.array([1, 2, 3]), fft.spectrum(a))

I might be able to push the changes to github tonight, but it might be tomorrow.

@jepler jepler requested review from tannewt and dhalbert February 27, 2020 17:51
@dhalbert
Copy link
Collaborator

@jepler Does this need #2657 first or later?

@jepler
Copy link
Author

jepler commented Feb 27, 2020

@dhalbert It works with or without #2657 , but we can't use the preferred style of importing import ulab.fft as fft until #2657 has gone in. I don't mind if @tannewt prefers to hold this PR until that one is merged, though.

@jepler jepler added this to the 5.x.0 - Features milestone Mar 1, 2020
tannewt
tannewt previously approved these changes Mar 3, 2020
Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

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

This looks good to me! Thank you for the hard work!

@jepler jepler dismissed dhalbert’s stale review March 4, 2020 02:15

updated since review

@jepler jepler requested a review from tannewt March 4, 2020 02:15
@jepler
Copy link
Author

jepler commented Mar 4, 2020

@tannewt I updated the locales only in this last update

Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

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

Looks great to me! Thank you!

@tannewt tannewt merged commit b4e1955 into adafruit:master Mar 4, 2020
@ladyada
Copy link
Member

ladyada commented Mar 4, 2020

huzzah!

@jepler
Copy link
Author

jepler commented Mar 7, 2020

@tannewt do we need to merge this somewhere besides master to ensure it goes in 5.1.0?

@dhalbert
Copy link
Collaborator

dhalbert commented Mar 7, 2020

There's no 5.1.x branch yet, so no problem just merging to master.

@jepler jepler deleted the ulab branch November 3, 2021 21:10
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.

6 participants