So Sage doesn't already use Gröbner bases when computing kernels of such maps? Okay, I'll try that.
Now that I've looked at the code a little bit, I see that `phi.is_injective()` just calls `phi.kernel()` and checks whether it's zero. I was hoping that there was something more clever: if I want to know whether something is injective, I only care whether the kernel is zero, not precisely what the kernel is.
By the way, this struck me as odd:
sage: phi
Ring morphism:
From: Multivariate Polynomial Ring in h20, h21, h30, h31, h40, h41 over Finite Field of size 2
To: Multivariate Polynomial Ring in h20, h21, h30, h31, xi1, xi2, xi3, xi4 over Finite Field of size 2
Defn: h20 |--> h20
h21 |--> h21
h30 |--> h20*xi1^4 + h21*xi1 + h30
h31 |--> h21*xi1^8 + h31
h40 |--> h21*xi1^9 + h30*xi1^8 + h20*xi2^4 + h31*xi1
h41 |--> h31*xi1^16 + h21*xi2^8
sage: %time phi.is_injective()
CPU times: user 7.32 s, sys: 58.7 ms, total: 7.38 s
Wall time: 7.44 s
True
Note that phi doesn't do anything very interesting with h20 and h21: it sends each of those to itself. If I remove them from the domain (I need to keep them in the codomain because they're involved in other terms), the computation is slower:
sage: phi
Ring morphism:
From: Multivariate Polynomial Ring in h30, h31, h40, h41 over Finite Field of size 2
To: Multivariate Polynomial Ring in h20, h21, h30, h31, xi1, xi2, xi3, xi4 over Finite Field of size 2
Defn: h30 |--> h20*xi1^4 + h21*xi1 + h30
h31 |--> h21*xi1^8 + h31
h40 |--> h21*xi1^9 + h30*xi1^8 + h20*xi2^4 + h31*xi1
h41 |--> h31*xi1^16 + h21*xi2^8
sage: %time phi.is_injective()
CPU times: user 15.7 s, sys: 101 ms, total: 15.8 s
Wall time: 15.9 s
True
I've seen this on two different machines: roughly double the time to do the second computation.