Skip to content

Math isclose - #4894

Closed
stinos wants to merge 1 commit into
micropython:masterfrom
stinos:math_isclose
Closed

stinos wants to merge 1 commit into
micropython:masterfrom
stinos:math_isclose

Conversation

@stinos

@stinos stinos commented Jul 4, 2019

Copy link
Copy Markdown
Contributor

From previous discussion:

Is it an original implementation (not copied from CPython)?

I honestly cannot tell, but I doubt it. What I did here is take code I already had for years in a personal project with several C++ utilities amongst which a couple of different ways of comparing floating point numbers, actual origin unknown, but comments say it's loosely based on Boost. This is one of those methods and I adapted it to work with MicroPython. I had a look at the current CPython implementation and it's pretty much exactly the same as this one. Which isn't a surprise because there's not that many ways to do this particular comparison [*], and the CPython code also mentions it looks like Boost's. However I didn't find a similar piece of code in (the current version of) Boost so hard to tell how it was extracted exactly.

[*] one alternative implementation here would be to first check if absolute value of a or b is the largest and then do the multiplication with rel_tol only once, instead of (possibly) twice now. Could be faster in general, didn't test that.

Also might want to use mp_arg_parse helpers instead of looking up kw's in dicts.

Was reluctant to do that because that lacks direct floating point support, but had a go at it now.

I did not make this feature conditional in the build as it just seems too important, but that might be me.

The implementation for complex numbers is going to look very similar I guess, but I'm not sure if we want to copy this one and adjust or make this one suitable for both with macros or function pointers or something similar.

Comment thread ports/windows/.appveyor.yml Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Until we fully switch to Python 3.5 as the reference, it'd be better to provide a .py.exp file for the test instead of this change.

Comment thread py/modmath.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think you can completely remove this check (at least the nan bits) and the logic below will catch these cases (I think).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point, works for the nan cases indeed but not for inf because of the <= comparison

Comment thread py/modmath.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To make it use less code these first two can be MP_QSTR_.

Comment thread py/modmath.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It is possible to make constant float objects and point to them here, but that'll probably make the code bigger.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

They won't be static though so allowed_args also won't be static so the end result might be less performant.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since the default value of abs_tol is zero it can be {.u_obj = MP_OBJ_NEW_SMALL_INT(0)}, and then always use mp_obj_get_float(...) to extract it below. This should save code size.

@dpgeorge

dpgeorge commented Jul 9, 2019

Copy link
Copy Markdown
Member

Which isn't a surprise because there's not that many ways to do this particular comparison

I agree. It's pretty straightforward and (like most math operations) only really one way to do it correctly.

I did not make this feature conditional in the build as it just seems too important,

I tend to agree, but I think this addition will be rather large (didn't test it yet).

@stinos

stinos commented Jul 9, 2019

Copy link
Copy Markdown
Contributor Author

I pushed the MP_QSTR_ change, removal of isnan and a .exp file.

Size of master (assuming the output after LINK micropython is what gets used to check code size?):

   text	   data	    bss	    dec	    hex	filename
   5149	   3814	      0	   8963	   2303	build/build/frozen_mpy.o
      2	      0	      0	      2	      2	build/build/frozen.o
 406221	   6008	   2488	 414717	  653fd	micropython

For this commit:

   text	   data	    bss	    dec	    hex	filename
   5149	   3814	      0	   8963	   2303	build/build/frozen_mpy.o
      2	      0	      0	      2	      2	build/build/frozen.o
 406869	   6008	   2488	 415365	  65685	micropython

When I apply the change where the default tolerance values are stored in constant objects the output is exactly the same.

@pfalcon pfalcon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As any other additional feature added to (otherwise relatively self-sufficient) MicroPython, this should be conditional, default off.

@jimmo

jimmo commented Jul 9, 2019

Copy link
Copy Markdown
Member

As any other additional feature added to (otherwise relatively self-sufficient) MicroPython, this should be conditional, default off.

@pfalcon

What's the threshold for a genuinely useful feature? Just because it came later somehow makes it less valuable/important than something that came earlier? Just because we wrote a lot of Python without a given feature doesn't make that feature not useful.

Conditional features are completely invisible to the vast majority of MicroPython users who aren't going to build their own firmware. It's the sort of thing that turns away new users. It makes the documentation confusing.

I'd be keen to investigate some sort of "feature levels" so that conditional flags could be grouped together to provide a manageable set of build targets, but in the meantime these sort of comments on every PR that adds a new feature aren't very helpful. Everyone already knows that features (usually) comes at a space cost, and nobody wants to waste bytes, but features should be considered on their merits rather than a blanket ban.

@pfalcon

pfalcon commented Jul 9, 2019

Copy link
Copy Markdown
Contributor

What's the threshold for a genuinely useful feature?

All various criteria were sounded many times. Here I'd suggest a following one: make a list of one thousand features you want to add to MicroPython. Of those, only one can be added. So, choose wisely. Don't make hasty choices. Collecting that list of 1000 features would alone take time, but still don't haste with that most important one. Wait half a year with it, hopefully it will wean off. If not, still wait a year to confirm it's really useful.

This particular PR shows a good example of a feature which is hardly worth the bloat it adds on the C level (which affects everyone, whether they use it or not). What 99% people would need out of it is simple Python expression abs(a - b) < epsilon. Can be made a function easily. And if you want to suggest that current math module should be umath instead, to allow to build such functions as isclose() in Python, in proper math module which can be made as much (or as little) compatible with CPython as needed - then you'd be very right, +1 from me.

Everyone already knows that features (usually) comes at a space cost, and nobody wants to waste bytes, but features should be considered on their merits rather than a blanket ban.

I'm sorry, but I disagree. A typical emotion people seem to feel about MicroPython (without even properly realizing it) is something like: "What a small little cutie, we like it so much! Now, let's turn it into UGLY FAT MONSTER, because heck, we like it so much." I'm sorry, but if you need more features right away, just use CPython. Otherwise, just develop (and share, maintain, etc.) standalone modules in Python, because even CPython doesn't implement "everything" and you need external modules (and majority of CPython stdlib is developed in Python of course).

@stinos

stinos commented Jul 9, 2019

Copy link
Copy Markdown
Contributor Author

Oops forgot to ask: is does this fall under 'special' functions i.e. should this be conditional with MICROPY_PY_MATH_SPECIAL_FUNCTIONS or a dedicated macro?

What 99% people would need out of it is simple Python expression abs(a - b) < epsilon

Surely not everyone needs it, but I'm not too sure of that number. Or of the amount of people who can correctly estimate that epsilon to do the right thing for their case.

@pfalcon

pfalcon commented Jul 9, 2019

Copy link
Copy Markdown
Contributor

Surely not everyone needs it, but I'm not too sure of that number.

But the talk started with pointing out that there's the established best practice that newly added stuff is made conditional. And if there's anything more to talk about it, it's a worrying fact that even long-time contributors ignore that best practice regularly. (Unless of course you think that math.isclose() is the crux of Python programming, in which case I'm happy to have countered that it's not.)

@stinos

stinos commented Jul 9, 2019

Copy link
Copy Markdown
Contributor Author

ignore that best practice regularly

Wasn't ignored here, rather intentionally done like that.. But it's a PR so if it's not to people's liking they can comment and it can still be added.

@dpgeorge

Copy link
Copy Markdown
Member

but if you need more features right away, just use CPython.

You can't use CPython on a microcontroller, which is the primary aim of MicroPython. And, following (C)Python, MicroPython's aim is to be a useful language. There are other languages (eg Lua) which are minimal languages. Python is not a minimal language. And via the PEP process it was decided that math.isclose() is a worthy addition to Python. So hence it's a worthy contender to put into MicroPython, perhaps enabled by default, perhaps not. Just because MicroPython started at the time CPython 3.4 was out (actually 3.3) doesn't mean that's the final version and it cannot go beyond that.

@dpgeorge

Copy link
Copy Markdown
Member

the established best practice that newly added stuff is made conditional

This is not a good principle to go by because it leads to overly conditional code, too many configuration options, complete fragmentation of new features moving forward, and confusion as to what port supports what feature. And then the inability to use any new feature because it may not be there. Sure there has to be a considered balance of selecting new features to be included by default, but it doesn't make sense to be stuck always at what exists now.

@dpgeorge

Copy link
Copy Markdown
Member

Oops forgot to ask: is does this fall under 'special' functions i.e. should this be conditional with MICROPY_PY_MATH_SPECIAL_FUNCTIONS or a dedicated macro?

It's not really a special function, more like an extra/misc/aux function. There is already MICROPY_PY_MATH_FACTORIAL for individual math.factorial(), so it could be MICROPY_PY_MATH_ISCLOSE. But it feels like math.isclose() is more useful/fundamental than functions like degrees() and radians() which are always there...

@stinos

stinos commented Jul 10, 2019

Copy link
Copy Markdown
Contributor Author

I have the same feeling but Paul doesn't so if we can decide what it's going to be I can adjust the code. How about MICROPY_PY_MATH_ISCLOSE which gets enabled for unix/windows/stm32 ports, for instance?

@robert-hh

robert-hh commented Jul 10, 2019 •

Copy link
Copy Markdown
Contributor

Looking at code which should work the same across various builds and platforms, having build options for parts of a module like math is painful. Build options for complete modules are fine, espcially when it comes to save code size.

Comment thread py/modmath.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since the default value of abs_tol is zero it can be {.u_obj = MP_OBJ_NEW_SMALL_INT(0)}, and then always use mp_obj_get_float(...) to extract it below. This should save code size.

Comment thread py/modmath.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If this goes before the isinf tests then it should be possible to test that either a or b are inf by a single call like isinf(difference), to save code size and make it more efficient for the more common case where inf is not passed in.

Comment thread py/modmath.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There should be tests for the equality part of this inequality. Eg: math.isclose(0, 1, rel_tol=1), better still test_combinations(0, 1, rel_tol=1).

@dpgeorge

Copy link
Copy Markdown
Member

For the record the PEP is https://www.python.org/dev/peps/pep-0485/

@dpgeorge

Copy link
Copy Markdown
Member

I computed the size increase with this patch:

   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:  +704 +0.141% [incl +64(data)]
unix nanbox:  +640 +0.144% [incl +32(data)]
      stm32:  +352 +0.095% PYBV10
     cc3200:    +0 +0.000% 
    esp8266:  +376 +0.057% 
      esp32:  +316 +0.028% [incl +96(data)]
        nrf:  +352 +0.241% pca10040
       samd:    +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS

That's quite a lot, so I suggest this new function be made optional via MICROPY_PY_MATH_ISCLOSE, enabled on those that have already got MATH_SPECIAL_FUNCTIONS: esp32, stm32, unix, windows.

Enabled for the ports which already have MATH_SPECIAL_FUNCTIONS.
@stinos

stinos commented Aug 17, 2019

Copy link
Copy Markdown
Contributor Author

Ok addressed all comments

@dpgeorge

Copy link
Copy Markdown
Member

Thanks! Merged in af5c998 with minor style edits, and additional tests for passing in negative numbers to abs_tol and rel_tol (to get 100% test coverage of the new function).

@dpgeorge dpgeorge closed this Aug 17, 2019
@stinos

stinos commented Aug 17, 2019

Copy link
Copy Markdown
Contributor Author

tests for passing in negative numbers

Good catch, thanks!

@stinos
stinos deleted the math_isclose branch August 17, 2019 13:31
@dlech dlech mentioned this pull request Mar 26, 2020
7 of 50 tasks
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