Skip to content

Conversation

samparkky
Copy link

This draft pull request introduces a new way to represent a polynomial over commutative ring -- as a vector over the commutative ring -- and a proof that polynomials themselves make up a commutative ring.

@jamesmckinna
Copy link
Contributor

jamesmckinna commented Aug 30, 2025

Zeroth thoughts:

  • thanks for the contribution!
  • It's complicated, though, given that we already have at least one such representation (in the RingSolver architecture, based on an optimised Horner normal form, IIRC), and committing the namespace to have Algebra.Polynomial be the home for this presentation of the dense representation, while natural, seems ... premature without further discussion. I might tentatively suggest Algebra.Polynomial.Dense.* as a better root path for these additions?
  • if it is indeed 'draft', then it would be helpful to avoid any premature detailed review by marking it as such.

First thoughts:

  • there is no need to base this on CommutativeRing: the only place that I can see where *-comm is used is in the shift lemmas for multiplication, where one of the arguments is 0#, and 0# commutes with everything multiplicatively (as does 1#...); the use of Consequences to streamline proofs in the presence of commutativity is a short-cut, but not actually necessary, IIUC
  • so: suggest at least that the PR be refactored to base on Ring, with additional properties when multiplication is Commutative

Second thought:

  • no need for subtraction/unary negation either! So please consider doing this for Semiring instead? Indeed, the stdlib style might be to try to identify the minimal axiomatisation for which Polynomial makes sense, and build up from there. This PR makes a lot of commitments (esp. to naming) which might prove costly to refactor if we wanted to generalise later?

Third thought:

  • (but this could be added downstream) any version of Polynomial ought at least to have (some version of) its characteristic universal property, or even simply eval given an element of the underlying Carrier?

Fourth thoughts:

  • ergonomics: which version of Vec works best? the 'structural' account (Vec as you have it), or the 'functional' account (as in Data.Vec.Functional); or even a bounded version for normalising away leading zeroes? It's possible that each of these would require cast in order to state, and prove, basic properties, but should be investigated further...
  • some of your cast lemmas, as well as some of the actual suc/pred arithmetic, may already be present in the library, and it might be sensible to refactor to lift any missing others out as Data.Vec.Properties and Data.Nat.Properties?

Comment on lines +131 to +134
x≈ε⇒x⁻¹≈ε {x} x≈ε = begin
x ⁻¹ ≈⟨ ⁻¹-cong x≈ε ⟩
ε ⁻¹ ≈⟨ ε⁻¹≈ε ⟩
ε ∎
Copy link
Contributor

@jamesmckinna jamesmckinna Aug 30, 2025

Choose a reason for hiding this comment

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

I wasn't sure if

  • this lemma met the Fairbairn threshhold (moreover, it's used only once in this PR)
  • this proof was the most 'efficient', compared to
    x≈ε⇒x⁻¹≈ε = identityˡ-unique _ _ ∘ trans (inverseˡ _) ∘ sym

which uses only a Loop property beyond the basic IsGroup axiomatisation.

Comment on lines +137 to +141
x⁻¹≈ε⇒x≈ε {x} x⁻¹≈ε = begin
x ≈⟨ ⁻¹-involutive x ⟨
x ⁻¹ ⁻¹ ≈⟨ ⁻¹-cong x⁻¹≈ε ⟩
ε ⁻¹ ≈⟨ ε⁻¹≈ε ⟩
ε ∎
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto, only more so:

x⁻¹≈ε⇒x≈ε = identityʳ-unique _ _ ∘ trans (inverseˡ _) ∘ sym

Copy link
Contributor

Choose a reason for hiding this comment

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

Alternatively, you could bootstrap

Suggested change
x⁻¹≈ε⇒x≈ε {x} x⁻¹≈ε = begin
x ≈⟨ ⁻¹-involutive x ⟨
x ⁻¹ ⁻¹ ≈⟨ ⁻¹-cong x⁻¹≈ε ⟩
ε ⁻¹ ≈⟨ ε⁻¹≈ε ⟩
ε ∎
x⁻¹≈ε⇒x≈ε {x} x⁻¹≈ε = begin
x ≈⟨ ⁻¹-involutive x ⟨
x ⁻¹ ⁻¹ ≈⟨ x≈ε⇒x⁻¹≈ε x⁻¹≈ε ⟩
ε ∎

which is 'just'

x⁻¹≈ε⇒x≈ε = trans (sym (¹-involutive _)) ∘ x≈ε⇒x⁻¹≈ε

@Taneb
Copy link
Member

Taneb commented Aug 30, 2025

I've been working on a slightly different implementation of this. Let me find what I've done and compare - notably, I used lists rather than vectors, and didn't have a notion of degree

@Taneb
Copy link
Member

Taneb commented Aug 30, 2025

@jamesmckinna

there is no need to base this on CommutativeRing: the only place that I can see where *-comm is used is in the shift lemmas for multiplication, where one of the arguments is 0#, and 0# commutes with everything multiplicatively (as does 1#...); the use of Consequences to streamline proofs in the presence of commutativity is a short-cut, but not actually necessary, IIUC

I think you need commutativity of multiplication to show that polynomial evaluation preserves multiplication

Comment on lines +73 to +76
data _≈_ : (Poly m) (Poly n) Set (ℓ₁ L.⊔ ℓ₂) where
[]≈ : (q : Poly n) IsZero q [] ≈ q
≈[] : (p : Poly m) IsZero p p ≈ []
cons : (a ≈A b) (p ≈ q) (a ∷ p) ≈ (b ∷ q)
Copy link
Member

Choose a reason for hiding this comment

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

This particular definition has two separate proofs that [] is equal to []... not necessarily the end of the world, but it bothers me

@jamesmckinna
Copy link
Contributor

jamesmckinna commented Aug 31, 2025

@Taneb

there is no need to base this on CommutativeRing: the only place that I can see where *-comm is used is in the shift lemmas for multiplication, where one of the arguments is 0#, and 0# commutes with everything multiplicatively (as does 1#...); the use of Consequences to streamline proofs in the presence of commutativity is a short-cut, but not actually necessary, IIUC

I think you need commutativity of multiplication to show that polynomial evaluation preserves multiplication

Hmmm. https://math.stackexchange.com/questions/806119/do-polynomials-make-sense-over-non-commutative-rings

Otherwise could we even speak about polynomials over rings such as square matrices (for the Cayley-Hamilton theorem, for example, even if that's usually only considered for matrices over commutative rings, such matrices don't enjoy commutative multiplication... while for any polynomial and its values, it is the case that powers of an element do commute...)?

I think the real issue is making sure that multiplication is defined correctly... see also https://en.wikipedia.org/wiki/Monoid_ring

@JacquesCarette
Copy link
Contributor

Non-commutative polynomial rings certainly do make sense and are useful. See for examples the skew polynomials that arise when modeling various kinds of operators.

Axiom does allow polynomials over coefficients rings like square matrices.

@Taneb
Copy link
Member

Taneb commented Aug 31, 2025

For reference, here's my implementation of polynomials: https://gist.github.com/Taneb/3257f47436ec6b11de403d839d7c413b

@samparkky
Copy link
Author

samparkky commented Sep 1, 2025

Thank you for the detailed feedback and the comments and I apologize for the delayed response. Although it will take some time, I will try implement the changes using the semiring instead of the commutative ring and eventually prove the universal property of polynomial rings. Since this is only a draft which I should have made it clear, any further comments or suggestions are welcome.

@samparkky samparkky changed the title [new] Polynomial [draft] Polynomial Sep 1, 2025
@gallais gallais marked this pull request as draft September 1, 2025 14:24
Copy link
Contributor

@JacquesCarette JacquesCarette left a comment

Choose a reason for hiding this comment

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

Partial review of these additions.

open import Data.Vec.Base as Vec using ([]; _∷_)
import Level as L

module Algebra.Polynomial.Base1
Copy link
Contributor

Choose a reason for hiding this comment

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

Base1 is quite a rotten name. I'm sure we can help find a better name, once we know the intent?

-- Types

record Polynomial : Set ℓ₁ where
constructor _,_
Copy link
Contributor

Choose a reason for hiding this comment

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

I strongly object to overloading _,_ for this constructor!

record Polynomial : Set ℓ₁ where
constructor _,_
field
degree :
Copy link
Contributor

Choose a reason for hiding this comment

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

There should be a comment here that the "degree" is off-by-one from the usual notion of the degree of a polynomial. It is more like a length than a degree.

---------------------------------------------------
-- Operations

-- Multiply the polynomial by a factor of x
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd say "by x" rather than "by a factor of x", as I wondering "what factor?"

And why not implement `shift :: ℕ → Polynomial → Polynomial instread?

open import Data.Vec.Base as Vec using ([]; _∷_)
import Level as L

module Algebra.Polynomial.Base2
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment about the name

module PolynomialRingMorphism
{ℓ₃ ℓ₄}
(B : CommutativeRing ℓ₃ ℓ₄)
: CommutativeRing.Carrier B)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are Homomorphisms always scaled? This seems like an odd choice.

eval-equiv {ℕ.zero} [] = B.refl
eval-equiv {suc m} (a ∷ p) = begin
⟦ a ⟧ +B β *B eval-p p
≈⟨ B.+-congˡ (B.*-congˡ (eval-equiv p) ) ⟩
Copy link
Contributor

Choose a reason for hiding this comment

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

There's no point in using equational reasoning when the proof is exactly 1 inference.

eval-p (a ∷ p) = ⟦ a ⟧ +B (β *B eval-p p )


eval : Polynomial CommutativeRing.Carrier B
Copy link
Contributor

Choose a reason for hiding this comment

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

Bad name: it took me a while to figure out that this "evaluate at 1 scaled by β" or equivalently "evaluate at β". The evaluation point should be a (local) parameter, not a module parameter.

eval (m , p) +B eval (n , q)

eval-p-1#-homomorphic : eval-p (A1# ∷ []) ≈B 1#
Copy link
Contributor

Choose a reason for hiding this comment

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

Inlining the definition of a constant polynomial makes this definition harder to read. Plus this is true for any member of the ground ring.

where
step1 = begin
β *B -B eval-p p
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems like a special case of commutativity of scalars.

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.

4 participants