crowlogic / arb4j

arb4j is a Java API for the arbitrary precision ball arithmetic library found at http://arblib.org
Other
1 stars 0 forks source link

replace quasipolynomials with rationalfunctions #442

Closed crowlogic closed 3 months ago

crowlogic commented 3 months ago

To modify the code so that it generates a Fraction instead of a Real for expressions like "1/2", we need to make changes in a few key areas of the Expression compiler. Here's an approach to achieve this:

  1. In the LiteralConstant class:

    • Modify the parsing of numeric literals to recognize fractions.
    • Create Fraction objects instead of Real objects for recognized fractions.
  2. In the Expression class:

    • Update the evaluateNumber() method to handle fraction literals.
  3. In the Division class:

    • If both operands are integer literals, create a Fraction instead of performing Real division.

Here are the specific changes:

  1. In LiteralConstant.java:
public class LiteralConstant<D, R, F extends Function<? extends D, ? extends R>> extends Node<D, R, F> {
    // ...existing code...

    public LiteralConstant(Expression<D, R, F> expression, String value) {
        super(expression);
        this.value = value;
        if (value.contains("/")) {
            String[] parts = value.split("/");
            if (parts.length == 2) {
                try {
                    long numerator = Long.parseLong(parts[0]);
                    long denominator = Long.parseLong(parts[1]);
                    this.constant = new Fraction(numerator, denominator);
                    return;
                } catch (NumberFormatException e) {
                    // Not a valid fraction, continue with normal parsing
                }
            }
        }
        // Existing parsing logic for other types...
    }

    // ...rest of the class...
}
  1. In Expression.java:
public class Expression<D, R, F extends Function<? extends D, ? extends R>> {
    // ...existing code...

    public Node<D, R, F> evaluateNumber() {
        int startingPosition = position;
        boolean hasDivision = false;
        while (isNumeric(character) || character == '/') {
            if (character == '/') {
                hasDivision = true;
            }
            nextCharacter();
        }

        String numberStr = expression.substring(startingPosition, position);
        if (hasDivision) {
            return new LiteralConstant<>(this, numberStr);
        } else {
            // Existing logic for non-fraction numbers
            return new LiteralConstant<>(this, numberStr);
        }
    }

    // ...rest of the class...
}
  1. In Division.java:
public class Division<D, R, F extends Function<? extends D, ? extends R>> extends BinaryOperation<D, R, F> {
    // ...existing code...

    @Override
    public void generate(Class<?> type, MethodVisitor mv) {
        if (left instanceof LiteralConstant && right instanceof LiteralConstant) {
            LiteralConstant<D, R, F> leftConst = (LiteralConstant<D, R, F>) left;
            LiteralConstant<D, R, F> rightConst = (LiteralConstant<D, R, F>) right;
            if (leftConst.constant instanceof Integer && rightConst.constant instanceof Integer) {
                int numerator = (Integer) leftConst.constant;
                int denominator = (Integer) rightConst.constant;
                Fraction fraction = new Fraction(numerator, denominator);
                // Generate code to create this Fraction
                // You'll need to implement this part based on your Fraction class
                return;
            }
        }
        // Existing division logic for non-integer literals
        super.generate(type, mv);
    }

    // ...rest of the class...
}

These changes will:

  1. Recognize fraction literals like "1/2" and create Fraction objects for them.
  2. Handle division of integer literals by creating Fractions instead of performing Real division.

You'll need to ensure that your Fraction class is properly implemented and that the rest of your code can handle Fraction objects where it previously expected Real objects.

Also, you might need to adjust other parts of your code to handle Fractions in arithmetic operations, comparisons, and conversions to ensure consistency throughout your expression evaluation system.

crowlogic commented 3 months ago

The issue appears to be in how the expression "1/2-x/2" is being parsed and compiled into a RationalNullaryFunction. The current implementation is treating the entire expression as a Real number operation rather than a RationalFunction operation. Let's modify the code generation to correctly handle rational functions.

Here's how we can fix this:

  1. First, we need to ensure that the expression parser recognizes "x" as the indeterminate variable for a RationalFunction.

  2. We need to modify the code generation to use RationalFunction operations instead of Real operations.

  3. We should use Integer operations where possible to maintain exact rational arithmetic.

crowlogic commented 3 months ago

That's an insightful approach. Your idea of modifying the literal constant handling to emit a fraction by default for decimal inputs is indeed more elegant and flexible. This strategy would provide several advantages:

  1. Precision preservation: By representing decimals as fractions from the start, you maintain exact precision throughout your computations until explicitly converted.

  2. Flexibility: Different parts of your system can then decide how to interpret or convert this fraction based on their specific needs.

  3. Consistency: It provides a uniform representation for decimal inputs across your system.

  4. Performance: It potentially reduces the need for repeated conversions in different parts of your code.

Here's a conceptual outline of how this might work:

  1. When parsing a decimal literal, convert it to a fraction immediately.
  2. Store this fraction as the internal representation of the constant.
  3. Implement the node interface to return this fraction representation.
  4. When a specific type (like arb_t, fmpz_poly_q_t, etc.) is requested:
    • If the requesting type can use the fraction directly, return it.
    • If not, perform the necessary conversion at that point.

This approach aligns well with the principle of keeping data in its most precise form until a specific representation is required. It also allows for more efficient handling in parts of your system that can work directly with fractions.

For implementation, you might consider using a structure like this:

typedef struct {
    fmpz_t num;
    fmpz_t den;
} fraction_t;

typedef struct {
    enum { INT, FRACTION, ... } type;
    union {
        fmpz_t int_value;
        fraction_t frac_value;
        // other types...
    } value;
} constant_t;

Then, your parsing function would populate this structure appropriately, and your node interface would work with this constant_t type, converting as needed when a specific type is requested.

Does this align with what you had in mind? Would you like to discuss any specific aspects of implementing this approach?

crowlogic commented 3 months ago

Node: Default to RationalFunction instead of RationalPolynomial when the node is not the result node.

Node by generating IntegerPolynomials, if possible. Whenever we encounter a polynomial that's all integers, let's submit an IntegerPolynomial instead of a RealPolynomial.

And then specialize the division of IntegerPolynomials to the default output type of RationalFunction, just in the same way that divisions of pairs of integers specialize to the output of Fractions.

crowlogic commented 3 months ago

purge the complex quasi polynomial thing too

==== pFq({Minus2,3_5,1},{2,4},1Over2MinusxOver2).java ==== import arb.ComplexQuasiPolynomial; import arb.Fraction; import arb.Initializable; import arb.Integer; import arb.RationalFunction; import arb.RationalNullaryFunction; import arb.Real; import arb.Typesettable;

public class pFq({Minus2,3_5,1},{2,4},1Over2MinusxOver2) implements RationalNullaryFunction, Typesettable, AutoCloseable, Initializable { public boolean isInitialized; Integer cℤ1 = new Integer("2"); Integer cℤ4 = new Integer("4"); Integer cℤ3 = new Integer("1"); Real cℝ2 = new Real("3.5", 128); public Fraction vf1 = new Fraction(); public Fraction vf2 = new Fraction(); public Fraction f1 = new Fraction(); public Fraction f2 = new Fraction(); public Fraction f3 = new Fraction(); public Fraction f4 = new Fraction(); public Fraction f5 = new Fraction();

@Override public Class coDomainType() { return RationalFunction.class; }

@Override public RationalFunction evaluate(Object in, int order, int bits, RationalFunction result) { if (!this.isInitialized) { this.initialize(); }

  return new Object(
        this.vf1.set(new Fraction[]{this.cℤ1.neg(this.f1), this.f2.set(this.cℝ2), this.f3.set(this.cℤ3)}),
        this.vf2.set(new Fraction[]{this.f4.set(this.cℤ1), this.f5.set(this.cℤ4)}),
        ComplexQuasiPolynomial.parse("(1/2)-(x/2)")
     )
     .evaluate(null, 1, bits, result);

}

@Override public void initialize() { if (this.isInitialized) { throw new AssertionError("Already initialized"); } else { this.isInitialized = true; } }

@Override public void close() { this.cℤ1.close(); this.cℤ4.close(); this.cℤ3.close(); this.cℝ2.close(); this.vf1.close(); this.vf2.close(); this.f1.close(); this.f2.close(); this.f3.close(); this.f4.close(); this.f5.close(); }

@Override public String toString() { return "pFq([-2,3.5,1],[2,4],1/2-x/2)"; }

@Override public String typeset() { return "${_3F_2}\left(\left[-2,3.5,1\right], \left[2,4\right] ; \left(\frac{1}{2}-\frac{x}{2}\right)\right)"; } }