-
Notifications
You must be signed in to change notification settings - Fork 242
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
Modular arithmetic #581
Comments
What are the current problems? And what would your suggestion be? |
My biggest suggestion would be implementing finite field operations for After that, it might be beneficial to be less dependent on the representation of |
Nope, you haven't missed them. If you'd be interested in coming up with an efficient implementation that'd be great!
Yup, I think @mechvel has suggested something similar in #65. It's quite a big project though as |
I tried to do this a little in the past. I found that infix 4 _≥_
data _≥_ (m : ℕ) : ℕ → Set where
m≥m : m ≥ m
m≥p : ∀ {n} → m ≥ suc n → m ≥ n
Mod : ℕ → Set
Mod = ∃ ∘ _≥_ (The Addition can be implemented with the same efficiency as on N: infixl 6 _+_
_+_ : ∀ {n} → Mod n → Mod n → Mod n
(_ , x) + (ys , y) = go x ys y
where
go : ∀ {n m} → n ≥ m → ∀ ys → n ≥ ys → Mod n
go m≥m ys y = ys , y
go (m≥p x) zero y = _ , x
go (m≥p x) (suc ys) y = go x ys (m≥p y) I also have linear implementations of to/from N. With Fin, the issue is that to check for "overflow" takes O(n) time, so + and so on implemented in the usual way are O(n^2). |
Interesting read, thanks!
Yup, I've also run into this problem!
I think you can implement it in O(n) time (with a big constant) by converting both to Nat, performing the addition, checking for overflow, subtracting if necessary, and then converting back again. But obviously it's incredibly ugly to work with and prove anything about... It looks like your definitions might be a good fit for using to reimplement |
Yeah, I think I might have an O(n) conversion from Fin -> Mod and back lying around somewhere, which would allow for O(n) |
this is a slightly fuller working through of the technique |
Fast modular arithmetic needs fast divMod, and fast divMod needs fast comparison <-cmp on natural nubmers. The Binary representation helps to exponentially reduce the cost order of these operations. There is a particular point about the performance of <-cmp on Bin in Binary-4. |
I've been working on "unnormalised" modulo arithmetic here: https://github.com/Taneb/antlion/blob/master/src/NumberTheory/ModularArithmetic.agda |
Currently standard library applies built-in arithmetic for Nat. If we take this, then there does not remain any performance question about modulo arithmetic. Because divMod works fast, "almost in constant time". |
Looks pretty good. We'd welcome a contribution to the library! My only comment is that it might be useful to expose a more obviously ternary version of the relation, i.e. |
I've been thinking about this some more, and I'm not sure exactly how I want to do it. I think something like module Data.R.ModularArithmetic where
module Mod (m : R) where
_≋_ : Rel R 0ℓ
isEquivalence : IsEquivalence _≋_
+-*-isRing : IsRing _≋_ _+_ _*_
-- some more properties where we stick to one modulus
_≋_%_ : ℕ → ℕ → ℕ → Set
x ≋ y % m = let open Mod m in x ≋ y
-- some properties where we use more than one modulus, like the Chinese remainder theorem is the way to go, where If data _≋_ : Rel ℕ 0ℓ where
k*m+a≡b : ∀ k {a b} → k * m + a ≡ b → a ≋ b
sk*m+b≡a : ∀ k {a b} → suc k * m + b ≡ a → a ≋ b Because this has two cases, some of the proofs get a lot messier than if there were only one case. If data _≋_ : Rel ℤ 0ℓ where
k*m+a≡b : ∀ k {a b} → k * m + a ≡ b → a ≋ b Or even using _≋_ : Rel ℤ 0ℓ
x ≋ y = ∃[ k ] k * m + x ≡ y This makes many proofs far simpler. For example, the proof of transitivity, when using naturals, needs 6 cases. When using integers, it only needs one. However, this comes at the cost of a much deeper dependency graph, for very little semantic gain.
Either way, many of the congruence proofs end up as a long series of rearranging formulas, before in a couple of steps applying some equalities provided as arguments. It winds up far simpler using one of the Ring solvers to get through the sludge of |
The tactic solvers make the dependency graph completely crazy. Simplicity for the developer does not always mean the same for the library as whole. Some tedious proofs are absolutely worth having a sane dependency graph! |
Hmm, we could very succinctly define it over the naturals using the difference operator and the divisibility relation: agda-stdlib/src/Data/Nat/Base.agda Line 158 in f6685da
data _≋_ : Rel ℕ 0ℓ where
m∣|a-b| : ∀ k {a b} → m ∣ | a - b | → a ≋ b and we already have a load of useful properties about both of them which should get us a long way to proving various results...
To be honest I think we should have modular arithmetic for both naturals and integers. I myself have a use case where I need to work over naturals/fin. The question is can we define modular arithmetic over integers in terms of naturals in order to save us duplicated effort?
Strongly agree with this. The fact that I can't import modulus and division operators over naturals without type-checking half the library and therefore gobbling up 2.5Gb of memory is a source of great frustration! I usually find that using some well chosen (and often independently useful) lemmas can reduce a lot of the boilerplate. The complete set of three element rearrangement lemmas in |
A reason against using |
Eugh, I hate the fact that this consideration leaks into all our design decisions. I really do feel it's a strong code-smell... I still think defining it terms of difference and divisibility really is the way to go in order to avoid duplicating a load of work. In order to fix the performance later on, then in version 2.0 we could then redefine min and max in terms of |
Actually, that might not be an issue at all, thinking about it. It'll be pretty rare we actually want to use the value of |
Great, looking forward to the PR 😄 |
I've partially implemented modular arithmetic in terms of As I was worried, it does indeed have performance issues. It takes significant time (more than a minute) to typecheck a term of type This isn't an issue for small values, so I'll continue with my implementation. I've got further with defining modular arithmetic with |
How/where do we situate PR #2073 in relation to this issue? |
My opinion is that that PR doesn't solve this issue, at least not fully. Doing modular arithmetic with |
I agree with @Taneb . I think the issue is exactly the same as with Peano Nats and blocks-of-binary-digits: one is great for proofs, one is great for efficient computation. You shouldn't choose between them, you should have both. |
I happen also to agree as well. The question in my mind is whether |
I wanted to come up with a better implementation for primality. Basic modular arithmetic should probably be tackled first but they might go hand in hand.
The text was updated successfully, but these errors were encountered: