Skip to content

Commit

Permalink
Add math/special:erf/2
Browse files Browse the repository at this point in the history
  • Loading branch information
dcnorris committed Jan 10, 2025
1 parent 958107f commit 157c35c
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 1 deletion.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ rust-version = "1.77"
crate-type = ["cdylib", "rlib"]

[features]
default = ["ffi", "repl", "hostname", "tls", "http", "crypto-full"]
default = ["ffi", "repl", "hostname", "tls", "http", "crypto-full", "special-math"]
ffi = ["dep:libffi"]
repl = ["dep:crossterm", "dep:ctrlc", "dep:rustyline"]
hostname = ["dep:hostname"]
tls = ["dep:native-tls"]
http = ["dep:warp", "dep:reqwest"]
crypto-full = []
special-math = ["dep:puruspe"]
"rust-version-1.80" = []

[build-dependencies]
Expand Down Expand Up @@ -62,6 +63,7 @@ scryer-modular-bitfield = "0.11.4"
num-order = { version = "1.2.0" }
ordered-float = "4.2.2"
phf = { version = "0.11", features = ["macros"] }
puruspe = { version = "0.3.0", optional = true }
rand = "0.8.5"
regex = "1.10.6"
ring = { version = "0.17.8", features = ["wasm32_unknown_unknown_js"] }
Expand Down
15 changes: 15 additions & 0 deletions build/instructions_template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,9 @@ enum SystemClauseType {
#[cfg(feature = "crypto-full")]
#[strum_discriminants(strum(props(Arity = "6", Name = "$crypto_data_decrypt")))]
CryptoDataDecrypt,
#[cfg(feature = "special-math")]
#[strum_discriminants(strum(props(Arity = "2", Name = "$erf")))]
Erf,
#[strum_discriminants(strum(props(Arity = "4", Name = "$ed25519_sign_raw")))]
Ed25519SignRaw,
#[strum_discriminants(strum(props(Arity = "4", Name = "$ed25519_verify_raw")))]
Expand Down Expand Up @@ -1930,6 +1933,12 @@ fn generate_instruction_preface() -> TokenStream {
functor!(atom!("call"), [atom(name), fixnum(arity)])
}
//
#[cfg(feature = "special-math")]
&Instruction::CallErf => {
let (name, arity) = self.to_name_and_arity();
functor!(atom!("call"), [atom(name), fixnum(arity)])
}
//
&Instruction::ExecuteAtomChars |
&Instruction::ExecuteAtomCodes |
&Instruction::ExecuteAtomLength |
Expand Down Expand Up @@ -2168,6 +2177,12 @@ fn generate_instruction_preface() -> TokenStream {
functor!(atom!("execute"), [atom(name), fixnum(arity)])
}
//
#[cfg(feature = "special-math")]
&Instruction::ExecuteErf => {
let (name, arity) = self.to_name_and_arity();
functor!(atom!("execute"), [atom(name), fixnum(arity)])
}
//
&Instruction::Deallocate => {
functor!(atom!("deallocate"))
}
Expand Down
80 changes: 80 additions & 0 deletions src/lib/math/special.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/** Special math functions in the Error, Gamma and Beta families
The underlying Rust implementations come from the puruspe crate.
*/

:- module(special, [

This comment has been minimized.

Copy link
@adri326

adri326 Jan 10, 2025

I think it would be nice after this to have something like

:- initialization(('$special_math_enabled'(Enabled), (
    Enabled = 1, !
    ; write("The rust feature flag 'special-math' must be enabled to use library(math/special)."), fail
))).

To warn users when the feature flag isn't enabled (akin to how tools like ripgrep warn you if you use PCRE2 regexes when the feature wasn't enabled).

This comment has been minimized.

Copy link
@dcnorris

dcnorris Jan 11, 2025

Author Owner

Is this already a supported idiom in Scryer? I don't find '$special_math_enabled'/1 to be defined automatically, as a result of the Rust feature declarations. Perhaps this would make a good Scryer Issue for general discussion?

This comment has been minimized.

Copy link
@dcnorris

dcnorris Jan 11, 2025

Author Owner

Submitted mthom#2766

This comment has been minimized.

Copy link
@triska

triska Jan 11, 2025

also, is this for example scientific?

This comment has been minimized.

Copy link
@dcnorris

dcnorris Jan 11, 2025

Author Owner

Do you mean, should we rename the directory 'scientific'? Or the module itself 'scientific.pl'? A lot of things could fall under the rubric 'scientific'; see e.g. https://docs.rs/scilib/latest/scilib/ which IMHO looks as if its ambitions might be overly-broad.

This comment has been minimized.

Copy link
@triska

triska Jan 11, 2025

The module name should ideally somehow indicate what the module provides. Is this maybe probability, pfunctions etc.? special seems not to be applicable, how are these predicates special?

This comment has been minimized.

Copy link
@dcnorris

dcnorris Jan 11, 2025

Author Owner

This is a traditional name for a class of functions:

julia> using SpecialFunctions

julia> names(SpecialFunctions)
77-element Vector{Symbol}:
 :SpecialFunctions
 :airy
 :airyai
 :airyaiprime
 :airyaiprimex
 :airyaix
 :airybi
 :airybiprime
 :airybiprimex
 :airybix
 :airyprime
 :airyx
 :besselh
 :besselhx
 :besseli
 :besselix
 
 :logabsgamma
 :logbeta
 :logerf
 :logerfc
 :logerfcx
 :logfactorial
 :loggamma
 :ncF
 :ncbeta
 :polygamma
 :sinint
 :sphericalbesselj
 :sphericalbessely
 :trigamma
 :zeta

This comment has been minimized.

Copy link
@dcnorris

dcnorris Jan 11, 2025

Author Owner

In fact, I have my eye on https://crates.io/crates/rmathlib for some statistics functions, once 'special' (or whatever we'll call it) is PR'able.

This comment has been minimized.

Copy link
@dcnorris

dcnorris Jan 11, 2025

Author Owner

Naming does remain an issue, though! What would you say to numerics/special_functions.pl? I was initially trying to keep the module name small, but maybe more explicit is better. Then special-purpose statistics routines would properly belong to the same subdirectory.

This comment has been minimized.

Copy link
@triska

triska Jan 11, 2025

Yes, that seems perfect, I think special_functions is indeed the ideal name for this module, and numerics seems a good subdirectory for it!

This comment has been minimized.

Copy link
@triska

triska Jan 11, 2025

Also, numerics/special may indeed be specific enough. The main concern is that just special by itself could have other rightful contenders for the name in the future. Within numerics, special may be indicative enough, but also there I think special_functions is even better.

erf/2
%,erfc/2
%,inverf/2
%,inverfc/2
%,gamma/2
%,gammq/3
%,invgammp/3
%,ln_gamma/2
%,beta/3
%,betai/4
%,invbetai/4
]).

%% erf(+X, -Erf)
%
% Erf is erf(X).
erf(X, Erf) :- '$erf'(X, Erf).

This comment has been minimized.

Copy link
@adri326

adri326 Jan 10, 2025

Don't forget to check that number(X) and throw/1 a user-friendly error otherwise :)


%% TODO: erfc(+X, -Erfc)
%
% Erfc is erfc(X).

%% TODO: inverf(+X, -InvErf)

This comment has been minimized.

Copy link
@adri326

adri326 Jan 10, 2025

Why not merge this with erf/2, so that it uses '$inverf'(Erf, X) when var(X), nonvar(Erf)? Same with some of the other inverse functions here.

This comment has been minimized.

Copy link
@dcnorris

dcnorris Jan 10, 2025

Author Owner

I'm very glad you mention this, as I had considered but [initially] rejected it. Certainly, this is a more 'Prologesque' treatment, but I don't know for a fact that we truly get a relation that works both ways ("integers good, floating-point bad"). So I thought it better to be honest that I'm really offering 2 distinct relations that work in one direction only. (Consider that the question of whether the 2 functions might not be perfect inverses is something you could test — and maybe falsify — with Prolog code that uses these distinct predicates.)

%
% InvErf is erf⁻¹(X). % TODO: Check this; puruspe docs have a mistake.

%% TODO: inverfc(+X, -InvErfc)
%
% InvErfc is erfc⁻¹(X). % TODO: Check this; puruspe docs have a mistake.

%% TODO: gamma(+X, -Gamma)
%
% Gamma is Γ(X).

%% TODO: gammp(+A, +X, -P)
%
% P is P(A,X), the regularized _lower_ incomplete gamma function.
% X ≥ 0 is the upper limit of integration
% A > 0 is the shape parameter

%% TODO: gammq(+A, +X, -Q)
%
% Q is Q(A,X) := Γ(A,X)/Γ(A), the regularized _upper_ incomplete gamma
% function.
% X ≥ 0 is the lower limit of integration
% A > 0 is the shape parameter

%% TODO: invgammp(+P, +A, -X)
%
% X is the unique solution of P = P(A,X), where P(-,-) is the
% regularized lower incomplete gamma function.
% P ∈ [0,1] is a probability
% A > 0 is the _shape parameter_

%% TODO: ln_gamma(+Z, -LnGamma)
%
% LnGamma is ln(Γ(Z)), the natural logarithm of Γ(Z).

%% TODO: beta(+Z, +W, -B)
%
% B is B(Z,W) := Γ(Z)*Γ(W)/Γ(Z+W)

%% TODO: betai(+A, +B, +X, -I)
%
% I is Iₓ(A,B) := B(X;A,B)/B(A,B), the regularized incomplete beta function
% A > 0 and B > 0 are the first and second shape parameters
% X ∈ [0,1] is the upper limit of integration.

%% TODO: invbetai(+P, +A, +B, -X)
%
% X is the unique solution of P = Iₓ(A,B) := B(X;A,B)/B(A,B)
% P ∈ [0,1] is a probability
% A > 0 and B > 0 are the first and second shape parameters
% X ∈ [0,1] is the upper limit of integration.
10 changes: 10 additions & 0 deletions src/machine/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4523,6 +4523,16 @@ impl Machine {
self.crypto_data_decrypt();
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
}
#[cfg(feature = "special-math")]
&Instruction::CallErf => {
self.erf();
step_or_fail!(self, self.machine_st.p += 1);
}
#[cfg(feature = "special-math")]
&Instruction::ExecuteErf => {
self.erf();
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
}
&Instruction::CallCryptoCurveScalarMult => {
self.crypto_curve_scalar_mult();
step_or_fail!(self, self.machine_st.p += 1);
Expand Down
20 changes: 20 additions & 0 deletions src/machine/system_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512};

use crrl::{ed25519, secp256k1, x25519};

#[cfg(feature = "special-math")]

This comment has been minimized.

Copy link
@adri326

adri326 Jan 10, 2025

I personally think that your code will be easier to maintain if it's moved within a module, like src/machine/system_calls/special_math.rs, which you can then gate behind the feature flag once.
If you put it in that path, then you can do the following in system_calls.rs:

#[cfg(feature = "special-math")]
pub(crate) mod special_math;

I know that the surrounding code isn't doing that (there are tls-specific stuff below this), but this file is 8k+ lines long when it really doesn't need to be :')

This comment has been minimized.

Copy link
@dcnorris

dcnorris Jan 10, 2025

Author Owner

I like this idea very much, and will give it a shot. All this duplicated code is hard to stomach.

use puruspe::error::erf;

#[cfg(feature = "tls")]
use native_tls::{Identity, TlsAcceptor, TlsConnector};

Expand Down Expand Up @@ -7748,6 +7751,23 @@ impl Machine {
);
}

#[cfg(feature = "special-math")]
#[inline(always)]
pub(crate) fn erf(&mut self) {
let x = self.deref_register(1);
let x = match Number::try_from(x) {
Ok(Number::Float(n)) => n.into_inner(),
Ok(Number::Fixnum(n)) => n.get_num() as f64,
Ok(Number::Integer(n)) => n.to_f64().value(),
_ => {
unreachable!()
}
};
let erf_x = float_alloc!(erf(x), self.machine_st.arena);
let return_value = self.deref_register(2);
self.machine_st.unify_f64(erf_x, return_value);
}

#[inline(always)]
pub(crate) fn crypto_curve_scalar_mult(&mut self) {
let stub_gen = || functor_stub(atom!("crypto_curve_scalar_mult"), 4);
Expand Down

4 comments on commit 157c35c

@adri326
Copy link

Choose a reason for hiding this comment

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

Darn, Github makes it not visually clear that I've left some comments. Hopefully you can find them above!

@dcnorris
Copy link
Owner Author

Choose a reason for hiding this comment

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

Wow, yes, this UI is hard to use!

@triska
Copy link

@triska triska commented on 157c35c Jan 11, 2025

Choose a reason for hiding this comment

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

This is a severe recent regression of github, it was previously obvious where a comment was placed, now it is almost invisible.

@adri326
Copy link

Choose a reason for hiding this comment

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

Yeah I've disabled the feature preview (can be found when you click your profile icon on the top right), I don't know why they felt the need to collapse comments by default with no option to un-collapse them all

Please sign in to comment.