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

Math functions #645

Merged
merged 12 commits into from
Nov 8, 2024
Merged

Math functions #645

merged 12 commits into from
Nov 8, 2024

Conversation

rben01
Copy link
Contributor

@rben01 rben01 commented Nov 1, 2024

I made some choices of function names and functionality (e.g., binom taking non integer args). Let me know what you think.

The gamma function is pretty hairy — not defined for negative integers. Easier to just define these functions the “dumb” way.

Reimplemented `falling_factorial` without the gamma function
Reimplemented `binom` in terms of `falling_factorial` and `factorial` (so that it now perfectly matches the binomial series)
Updated descriptions accordingly

Added tests of new functions

Rebuilt book
Copy link
Owner

@sharkdp sharkdp 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 great. Thank you very much. Just a few minor comments.

use math::transcendental

@name("Factorial")
@description("The product of the integers 1 through n, also written n!")
Copy link
Owner

Choose a reason for hiding this comment

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

You can use $…$ to embed LaTeX formulas in @descriptions. This way, they render more nicely in the online documentation.

Copy link
Owner

Choose a reason for hiding this comment

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

Maybe this was your intent, but I think we could state more clearly that Numbat also supports ! as an operator for factorials.

Comment on lines 11 to 12
@description("Equal to n⋅(n-1)⋅…⋅(n-k+2)⋅(n-k+1) (k terms total). If n is an integer, this is the number
of k-element permutations from a set of size n. k must always be an integer.")
Copy link
Owner

Choose a reason for hiding this comment

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

Does this whitespace lead to problems in the rendered output? I think you can have multiple @description fields and they will be concatenated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Multiple @description fields are concatenated with newlines between them, which I'm not sure I want. But then again the literal newline is picked up in the string, so there may not be a way to get this stored as a single line while writing it across multiple lines.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Separately, this has me wondering whether there would be merit in having separate @descriptions for docs and for numbat CLI. I would like to write that product as $n\cdot(n-1)\cdot\ldots\cdot(n-k+2)\cdot(n-k+1)$ for the docs, but I wouldn't want to have to read that in the CLI. (Is there a LaTeX-to-unicode compiler?)

Copy link
Contributor

@Bzero Bzero Nov 6, 2024

Choose a reason for hiding this comment

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

We discussed this partially in #512 but didn't come to a satisfactory conclusion so far. I think the best to do as of now would be to use a math block but directly use the Unicode characters instead of their LaTeX commands (e.g $n⋅(n−1)⋅…⋅(n−k+2)⋅(n−k+1)$ in this case). MathJax handles this well as far as I can tell and it is also decently readable as plain text.

of k-element permutations from a set of size n. k must always be an integer.")
@url("https://en.wikipedia.org/wiki/Falling_and_rising_factorials")
fn falling_factorial(n: Scalar, k: Scalar) -> Scalar =
if k < 0 || fract(k) != 0 then
Copy link
Owner

Choose a reason for hiding this comment

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

We have is_non_negative. Maybe we should also add is_integer and use both functions here (and below)?

Comment on lines 28 to 31
@example("is_int(30 seconds)")
@example("is_int(0.5 minutes)")
fn is_int<D: Dim>(value: D) -> Bool
Copy link
Owner

Choose a reason for hiding this comment

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

I think this should be a function on Scalar. If we make it work on dimensionful quantities, it introduces inconsistencies, as you demonstrated in the example:

true = is_int(30 seconds) ?=? is_int(0.5 minutes) = false

We should avoid this if possible (I know that there are already ways to introduce inconsistencies like this via value_of; but those functions are at least marked "unsafe").

Minor: I'd like to rename it to is_integer

Also, can we implement it in Numbat, using fract?

fn is_integer(value: Scalar) -> Bool = is_zero(fract(value))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can do. What is the benefit of implementing functions in Numbat over Rust?

Copy link
Owner

Choose a reason for hiding this comment

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

That is a good question.

I think of the "FFI bridge" between Rust and Numbat as something that helps Numbat to get up on its feet. We will always need support from the "runtime". We will probably never implement our own PRNG to implement rand in Numbat. We will almost certainly never have a full networking stack to be able to look up currency exchange rates from within Numbat. And we will never implement print in Numbat.

But as Numbat grows into a more mature language of its own, I think it makes sense to keep the foreign-function interface as small as possible. Especially in areas which belong to the core functionality of Numbat like mathematical computations.

Having part of the "standard library" written in Numbat itself helps us to design APIs in a way that makes them easy to use … in Numbat itself. #646 is a good example. If all string functions were written in Rust and re-exported to Numbat, may we wouldn't have found out that they are not as ergonomic as they could be.

The standard library is the biggest Numbat "code base" that we have, and it's a good "benchmark" to have when we make changes to the language.

There are also practical advantages to having functions implemented in Numbat itself. Even if we reimplement something that would be readily available in Rust, it's typically way less code to maintain. The function name appears just once in the fn … definition instead of multiple times for a FFI function. It also allows us to change/develop functions without having to recompile the Numbat interpreter.

There's also downsides of course. Performance, for example. If we see that a heavily-used function is too slow, we can think about re-implementing it in Rust.

Side remark: Note that there are also some functions like map that can't even be implemented in Rust at the moment, as we don't have a way to call other Numbat functions from FFI functions.

Renamed `is_int` to `is_integer`
Implemented in Numbat instead of Rust, removed Rust implementation
Copy link
Owner

@sharkdp sharkdp 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!

@sharkdp sharkdp merged commit 2d6e1e2 into sharkdp:master Nov 8, 2024
15 checks passed
@rben01 rben01 deleted the math-functions branch November 11, 2024 20:26
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.

3 participants