flintlib / python-flint

Python bindings for Flint and Arb
MIT License
131 stars 25 forks source link

Add some mpoly context util functions #228

Closed Jake-Moss closed 1 week ago

Jake-Moss commented 2 weeks ago

This adds methods to

As well as some doc string updates and support for negative indecies in the variable_to_index method.

Missing ATM:

oscarbenjamin commented 2 weeks ago
  • coerce a mpoly from one context to another, either with an inferred mapping or a provided one.

We should check what generic rings does before adding any sort of coercion like this. I didn't add coercion in gh-218 but we could easily add some explicit coercion there.

oscarbenjamin commented 2 weeks ago

we could easily add some explicit coercion there.

There are two functions related to this gr_set_other and gr_ctx_cmp_coercion.

Jake-Moss commented 2 weeks ago

There are two functions related to this gr_set_other and gr_ctx_cmp_coercion.

I've taken a quick look at these and their mpoly variants.

The gr_ctx_cmp_coercion function says "Returns 1 if coercing elements into ctx1 is more meaningful, and returns -1 otherwise.", it seems to measure this by the ordering of the gr_which_structure enum. So if ctx1->which_ring is defined later than ctx2->which_ring then it is "more meaningful".

The coerce_to_context method in this PR only works between mpoly contexts of the same type. In that cause we'd fall all the way through in this function and return 1.

// flint/src/gr/cmp_coercion.c
int gr_ctx_cmp_coercion(gr_ctx_t ctx1, gr_ctx_t ctx2)
{
    if (ctx1->which_ring < ctx2->which_ring)
        return -1;
    if (ctx1->which_ring > ctx2->which_ring)
        return 1;

    if (ctx1->which_ring == GR_CTX_GR_POLY)
    {
        return gr_ctx_cmp_coercion(POLYNOMIAL_ELEM_CTX(ctx1), POLYNOMIAL_ELEM_CTX(ctx2));
    }

    if (ctx1->which_ring == GR_CTX_GR_MAT)
    {
        return gr_ctx_cmp_coercion(MATRIX_CTX(ctx1)->base_ring, MATRIX_CTX(ctx2)->base_ring);
    }

    return 1;
}

// flint/src/gr.h
typedef enum
{
    GR_CTX_FMPZ, GR_CTX_FMPQ, GR_CTX_FMPZI,
    GR_CTX_FMPZ_MOD, GR_CTX_NMOD, GR_CTX_NMOD8, GR_CTX_NMOD32, GR_CTX_MPN_MOD,
    GR_CTX_FQ, GR_CTX_FQ_NMOD, GR_CTX_FQ_ZECH,
    GR_CTX_NF,
    GR_CTX_REAL_ALGEBRAIC_QQBAR, GR_CTX_COMPLEX_ALGEBRAIC_QQBAR,
    GR_CTX_REAL_ALGEBRAIC_CA, GR_CTX_COMPLEX_ALGEBRAIC_CA,
    GR_CTX_RR_CA, GR_CTX_CC_CA,
    GR_CTX_COMPLEX_EXTENDED_CA,
    GR_CTX_RR_ARB, GR_CTX_CC_ACB,
    GR_CTX_REAL_FLOAT_ARF, GR_CTX_COMPLEX_FLOAT_ACF,
    GR_CTX_NFLOAT, GR_CTX_NFLOAT_COMPLEX,
    GR_CTX_FMPZ_POLY, GR_CTX_FMPQ_POLY, GR_CTX_GR_POLY,
    GR_CTX_FMPZ_MPOLY, GR_CTX_GR_MPOLY,
    GR_CTX_FMPZ_MPOLY_Q,
    GR_CTX_GR_SERIES, GR_CTX_SERIES_MOD_GR_POLY,
    GR_CTX_GR_MAT,
    GR_CTX_GR_VEC,
    GR_CTX_PSL2Z, GR_CTX_DIRICHLET_GROUP, GR_CTX_PERM,
    GR_CTX_FEXPR,
    GR_CTX_UNKNOWN_DOMAIN,
    GR_CTX_WHICH_STRUCTURE_TAB_SIZE
}
gr_which_structure;

Now looking at the gr_set_other method, the fmpz_mpoly gr defines this to be

// flint/src/gr/fmpz_mpoly.c
gr_method_tab_input _gr_fmpz_mpoly_methods_input[] =
{
    // ...
    {GR_METHOD_SET_OTHER,   (gr_funcptr) _gr_fmpz_mpoly_set_other},
    // ...
};

which directly sets the polynomial if the two contexts match in nvars and ordering


int
_gr_fmpz_mpoly_set_other(fmpz_mpoly_t res, gr_srcptr x, gr_ctx_t x_ctx, gr_ctx_t ctx)
{
    if (x_ctx->which_ring == GR_CTX_FMPZ_MPOLY)
    {
        /* fmpz_mpoly_set_fmpz_poly */
        if (MPOLYNOMIAL_MCTX(ctx)->minfo->nvars == MPOLYNOMIAL_MCTX(x_ctx)->minfo->nvars &&
            MPOLYNOMIAL_MCTX(ctx)->minfo->ord == MPOLYNOMIAL_MCTX(x_ctx)->minfo->ord)
        {
            fmpz_mpoly_set(res, x, MPOLYNOMIAL_MCTX(ctx));
            return GR_SUCCESS;
        }
    }

    return gr_generic_set_other(res, x, x_ctx, ctx);
}

In any other case it falls back to

// flint/src/gr_generic/generic.c
int gr_generic_set_other(gr_ptr res, gr_srcptr x, gr_ctx_t xctx, gr_ctx_t ctx)
{
    if (xctx == ctx)
    {
        return gr_set(res, x, ctx);
    }
    else if (xctx->which_ring == GR_CTX_FMPZ)
    {
        return gr_set_fmpz(res, x, ctx);
    }
    else if (xctx->which_ring == GR_CTX_FMPQ)
    {
        return gr_set_fmpq(res, x, ctx);
    }
    else if (xctx->which_ring == GR_CTX_FEXPR)
    {
        gr_vec_t vec;
        fexpr_vec_t fvec;
        int status;
        fexpr_vec_init(fvec, 0);
        gr_vec_init(vec, 0, ctx);
        status = gr_set_fexpr(res, fvec, vec, x, ctx);
        gr_vec_clear(vec, ctx);
        fexpr_vec_clear(fvec);
        return status;
    }
    else
    {
        return GR_UNABLE;
    }
}

which attempts some easy settings and what seems to be a conversion to a symbolic expression, otherwise it returns GR_UNABLE meaning the conversion is unimplemented.

GR_CTX_GR_MPOLY doesn't define an explicit GR_METHOD_SET_OTHER so I believe it falls back to the same generic method above.

Jake-Moss commented 2 weeks ago

It seems like the generic rings are only able to change mpoly context directly when the contexts match exactly. Maybe theres some method to go to a symbolic expression then back to another context, I'm not sure.

The coerce_to_context here is able to convert a mpoly between contexts of the same python-flint type, i.e. fmpz_mpoly_ctx to fmpz_mpoly_ctx via the compose generator functions. It's able to handle a change in nvars by mapping any absent ones to 0, and a change in ordering as the *_mpoly_compose_mat method used internally performs a sort, then combines like terms to normalise the polynomial before returning.

oscarbenjamin commented 1 week ago

Sorry, I missed that this was marked as ready for review.

Jake-Moss commented 1 week ago

Sorry, I missed that this was marked as ready for review.

No worries at all

oscarbenjamin commented 1 week ago

Okay, let's get this in.