// Souffle - A Datalog Compiler
// Copyright (c) 2018, The Souffle Developers. All rights reserved
// Licensed under the Universal Permissive License v 1.0 as shown at:
// - https://opensource.org/licenses/UPL
// - <souffle root>/licenses/SOUFFLE-UPL.txt

// Perform the Euclidean Algorithm to calculate the Greatest Common Divisor of Rational Polynomials

// Integers, used in definition of Rationals
.type int <: number
// Indices of polynomials
.type index <: number

// Rational numbers, just hope the denominator isn't zero
.type rational = [
    numerator : int,
    denominator : int
]

// Polynomials with rational coefficients, written as `constant + higher * x'
.type poly = [
    constant : rational,
    higher : poly
]

// -- Calculating the greatest common divisor of two integers -- //

// Integers we should calculate the gcd of recursively
.decl ToGcdInt(first : int, second : int)
ToGcdInt(second,modulo) :-
    ToGcdInt(first,second),
    modulo = first % second,
    modulo != 0. // Modulo = 0 is a base case, do not recurse.

// Base case of integer gcd
.decl GcdBase(first : int)
GcdBase(second) :-
    ToGcdInt(first,second),
    0 = first % second.

// Result of calculating gcd of integers
.decl GcdInt(first : int, second : int, gcd : int)
GcdInt(first,0,first) :- GcdBase(first).
GcdInt(first,second,gcd) :-
    ToGcdInt(first,second),
    modulo = first % second,
    GcdInt(second,modulo,gcd).

// -- Simplifying coefficients and trimming tails of polynomials -- //

// Polynomials we wish to simplify
.decl ToSimplify(a : poly)
ToSimplify(tail) :- ToSimplify([_,tail]).

// Result of simplification
.decl Simplify(a : poly, b : poly)
Simplify(nil,nil) :- ToSimplify(nil).
Simplify([head,tail],nil) :- // Trim the tail of polynomials equivalent to zero
    ToSimplify([head,tail]),
    Simplify(tail,nil),
    head = [0,denominator].
Simplify([head,tail],[newHead,newTail]) :-
    ToSimplify([head,tail]),
    Simplify(tail,newTail),
    head = [numerator,denominator] ,
    (newTail != nil; numerator != 0), // make sure polynomial is non-zero
    GcdInt(numerator,denominator,gcd),
    newHead = [numerator/gcd,denominator/gcd]. // simplify coefficient

// Helper method for simplify
ToGcdInt(numerator,denominator) :-
    ToSimplify([head,tail]),
    head = [numerator,denominator] ,
    ( Simplify(tail, newTail),
      newTail != nil
    ; numerator != 0
    ).

// -- Add polynomials recursively -- //

// Polynomials that we need to add
.decl ToAdd(first : poly, second : poly)
ToAdd(firstTail, secondTail) :- ToAdd([_,firstTail],[_,secondTail]).

// Result of addition
.decl Add(first : poly, second : poly, added : poly)
Add(first,nil,first) :- ToAdd(first,nil).
Add(nil,second,second) :- ToAdd(nil,second).
Add([firstHead,firstTail],[secondHead,secondTail],[sumHead,sumTail]) :-
    ToAdd([firstHead,firstTail],[secondHead,secondTail]),
    Add(firstTail,secondTail,sumTail),
    firstHead = [firstNumerator,firstDenominator],
    secondHead = [secondNumerator,secondDenominator],
    sumHead =
        [firstNumerator*secondDenominator+firstDenominator*secondNumerator,
            firstDenominator*secondDenominator].

// -- Multiply polynomials by rational constants -- //

// Constant and polynomial to be multiplied
.decl ToMultiply(constant : rational, polynomial : poly)
ToMultiply(constant,tail) :- ToMultiply(constant,[_,tail]).

// Result of multiplication
.decl Multiply(constant : rational, polynomial : poly, product : poly)
Multiply(constant, nil, nil) :- ToMultiply(constant, nil).
Multiply(constant, [head,tail], [productHead, productTail]) :-
    ToMultiply(constant, [head,tail]),
    Multiply(constant, tail, productTail),
    constant = [constantNumerator, constantDenominator],
    head = [headNumerator, headDenominator],
    productHead =
        [constantNumerator*headNumerator, constantDenominator*headDenominator].

// -- Find the leading coefficient and index of a polynomial -- //
// We do not assume the polynomial is simplified
// The index of a polynomial equivalent to 0 will be 0
// Otherwise if the leading term is a*x^n (a /= 0) we return a and n+1

// Polynomials to find the leading term of
.decl ToLeading(a : poly)
ToLeading(tail) :- ToLeading([_,tail]).

// Resulting leading terms
.decl Leading(a : poly, coefficient : rational, i : index)
Leading(nil, [0,1], 0) :- ToLeading(nil).
Leading([head,tail], [0,1], 0) :- // Polynomial is equivalent to 0
    ToLeading([head,tail]),
    Leading(tail,[0,1],0),
    head = [0,denominator].
Leading([head,tail], head, 1) :- // Polynomial is equivalent to non-zero constant
    ToLeading([head,tail]),
    Leading(tail,[0,1],0),
    head = [numerator,denominator], numerator!=0.
Leading([head,tail], coefficient, i+1) :-
    ToLeading([head,tail]),
    Leading(tail,coefficient,i),
    i != 0.

// -- Multiply polynomials by powers of x -- //

// Input is of the form Raise(start,goal,0,start)
// Output is of the form Raise(start,goal,goal,result)
.decl Raise(start : poly, goal : index, current : index, result : poly)
Raise(start,goal,current+1,[[0,1],result]) :-
    Raise(start,goal,current,result),
    current < goal.

// -- Calculate one polynomial modulo another -- //

// Polynomials to calculate modulo of
.decl ToMod(first : poly, second : poly)
ToMod(next,second) :-
    ToMod(first,second),
    Leading(first,firstCoefficient,firstDegree),
    Leading(second,secondCoefficient,secondDegree),
    firstDegree >= secondDegree, // check we are not in a base case
    firstCoefficient = [firstNumerator,firstDenominator],
    secondCoefficient = [secondNumerator,secondDenominator],
    multiplicationConstant =
        [-firstNumerator*secondDenominator,firstDenominator*secondNumerator],
    Multiply(multiplicationConstant,second,multiplicationResult), // make the leading coefficients negatives of each other
    Raise(multiplicationResult,firstDegree-secondDegree,
        firstDegree-secondDegree,raiseResult), // make the leading indices the same
    Add(first,raiseResult,nextUnsimp), // this will cancel out the leading term
    Simplify(nextUnsimp,next). // avoid integer overflow and excessive computation

// Helper methods for ToMod
ToLeading(first) :- ToMod(first,_).
ToLeading(second) :- ToMod(_,second).
ToMultiply(multiplicationConstant,second) :-
    ToMod(first,second),
    Leading(first,firstCoefficient,firstDegree),
    Leading(second,secondCoefficient,secondDegree),
    firstDegree >= secondDegree,
    firstCoefficient = [firstNumerator,firstDenominator],
    secondCoefficient = [secondNumerator,secondDenominator],
    multiplicationConstant =
        [-firstNumerator*secondDenominator,firstDenominator*secondNumerator].
Raise(multiplicationResult,firstDegree-secondDegree,0,multiplicationResult) :-
    ToMod(first,second),
    Leading(first,firstCoefficient,firstDegree),
    Leading(second,secondCoefficient,secondDegree),
    firstDegree >= secondDegree,
    firstCoefficient = [firstNumerator,firstDenominator],
    secondCoefficient = [secondNumerator,secondDenominator],
    multiplicationConstant =
        [-firstNumerator*secondDenominator,firstDenominator*secondNumerator],
    Multiply(multiplicationConstant,second,multiplicationResult).
ToAdd(first,raiseResult) :-
    ToMod(first,second),
    Leading(first,firstCoefficient,firstDegree),
    Leading(second,secondCoefficient,secondDegree),
    firstDegree >= secondDegree,
    firstCoefficient = [firstNumerator,firstDenominator],
    secondCoefficient = [secondNumerator,secondDenominator],
    multiplicationConstant =
        [-firstNumerator*secondDenominator,firstDenominator*secondNumerator],
    Multiply(multiplicationConstant,second,multiplicationResult),
    Raise(multiplicationResult,firstDegree-secondDegree,
        firstDegree-secondDegree,raiseResult).
ToSimplify(nextUnsimp) :-
    ToMod(first,second),
    Leading(first,firstCoefficient,firstDegree),
    Leading(second,secondCoefficient,secondDegree),
    firstDegree >= secondDegree,
    firstCoefficient = [firstNumerator,firstDenominator],
    secondCoefficient = [secondNumerator,secondDenominator],
    multiplicationConstant =
        [-firstNumerator*secondDenominator,firstDenominator*secondNumerator],
    Multiply(multiplicationConstant,second,multiplicationResult),
    Raise(multiplicationResult,firstDegree-secondDegree,
        firstDegree-secondDegree,raiseResult),
    Add(first,raiseResult,nextUnsimp).

// The result of the modulo operation
.decl Mod(first : poly, second : poly, modulo : poly)
Mod(first,second,first) :-
    ToMod(first,second),
    Leading(first,_,firstDegree),
    Leading(second,_,secondDegree),
    firstDegree < secondDegree. // base case, first is smaller so return it
Mod(first,second,modulo) :-
    ToMod(first,second),
    Leading(first,firstCoefficient,firstDegree),
    Leading(second,secondCoefficient,secondDegree),
    firstDegree >= secondDegree,
    firstCoefficient = [firstNumerator,firstDenominator],
    secondCoefficient = [secondNumerator,secondDenominator],
    multiplicationConstant =
        [-firstNumerator*secondDenominator,firstDenominator*secondNumerator],
    Multiply(multiplicationConstant,second,multiplicationResult),
    Raise(multiplicationResult,firstDegree-secondDegree,
        firstDegree-secondDegree,raiseResult),
    Add(first,raiseResult,nextUnsimp),
    Simplify(nextUnsimp,next),
    Mod(next,second,modulo).

// -- Cakculate the greatest common divisor of two polynomials -- //

// Polynomials to calculate the gcd of
.decl ToGcd(first : poly, second : poly)
ToGcd(second,modulo) :-
    ToGcd(first,second),
    Leading(second,_,secondDegree),
    secondDegree > 0, // ensure this is not a base case
    Mod(first,second,modulo).

// Helper methods for ToGcd
ToMod(first,second) :-
    ToGcd(first,second),
    Leading(second,_,secondDegree),
    secondDegree > 0.
ToLeading(second) :- ToGcd(_,second).

// Calculate the resuling gcd
.decl Gcd(first : poly, second : poly, gcd : poly)
Gcd(first,second,gcd) :-
    ToGcd(first,second),
    Leading(second,_,0), // base case, one term is zero so return the other
    Leading(first,[firstNumerator,firstDenominator],_),
    Multiply([firstDenominator,firstNumerator],first,multiplicationResult), // make the gcd monic
    Simplify(multiplicationResult, gcd). // make output gcd look nicer
Gcd(first,second,gcd) :-
    ToGcd(first,second),
    Leading(second,_,secondDegree),
    secondDegree > 0,
    Mod(first,second,modulo),
    Gcd(second,modulo,gcd).

// Helper methods for Gcd base case
ToLeading(first) :- ToGcd(first,_).
ToMultiply([firstDenominator,firstNumerator],first) :-
    ToGcd(first,second),
    Leading(second,_,0),
    Leading(first,[firstNumerator,firstDenominator],_).
ToSimplify(multiplicationResult) :-
    ToGcd(first,second),
    Leading(second,_,0),
    Leading(first,[firstNumerator,firstDenominator],_),
    Multiply([firstDenominator,firstNumerator],first,multiplicationResult).

// -- Input -- //

// Which input polynomial (FirstTerm = 1, SecondTerm = 2)
.type inputIndex <: number

.decl FirstTerm(i : index, numerator : int, denominator : int)
.input FirstTerm

.decl SecondTerm(i : index, numerator : int, denominator : int)
.input SecondTerm

// Calculate the largest index in each polynomial
.decl Degree(n : inputIndex, i : index)
Degree(1,i) :- i = max x : FirstTerm(x,_,_).
Degree(2,i) :- i = max x : SecondTerm(x,_,_).

// Re-format input to be in one relation, with no missing indices
.decl Term(n : inputIndex, coefficient : rational, i : index)
Term(1,[numerator,denominator],i) :- FirstTerm(i,numerator,denominator).
Term(1,[0,1],i+1) :-
    Term(1,_,i),
    Degree(1,degree),
    degree > i,
    !FirstTerm(i+1,_,_). // Replace missing index with 0
Term(2,[numerator,denominator],i) :- SecondTerm(i,numerator,denominator).
Term(2,[0,1],i+1) :-
    Term(2,_,i),
    Degree(2,degree),
    degree > i,
    !SecondTerm(i+1,_,_). // Replace missing index with 0

// Build the link-list style polynomial objects
.decl InputPoly(n : inputIndex, i : index, polynomial : poly)
InputPoly(n,degree+1,nil) :- Degree(n,degree).
InputPoly(n,i-1,[head,tail]) :- InputPoly(n,i,tail), Term(n,head,i-1).

ToGcd(first,second) :-
    InputPoly(1,0,first),
    InputPoly(2,0,second).

// -- Output -- //

// Find the output polynomial and its degree
.decl OutputPoly(gcd : poly, degree : index)
OutputPoly(gcd, degree-1) :- // subtract one because Leading added one
    InputPoly(1,0,first),
    InputPoly(2,0,second),
    Gcd(first,second,gcd),
    Leading(gcd,_,degree).

ToLeading(gcd) :-
    InputPoly(1,0,first),
    InputPoly(2,0,second),
    Gcd(first,second,gcd).

// calculate the coefficient at each term, and keep the remaining terms on hand
.decl OutTerm(i : index, coefficient : rational, remaining : poly)
OutTerm(0, head, tail) :- OutputPoly([head,tail],_).
OutTerm(0, [0,1], nil) :- OutputPoly(_,0). // handle degenerate case
OutTerm(i+1, head, tail) :-
    OutTerm(i, _, [head,tail]),
    OutputPoly(_,degree),
    i < degree.

.decl Output(i : index, numerator : int, denominator : int)
Output(i,numerator,denominator) :- OutTerm(i,[numerator,denominator],_).
.output Output
