GAP 4.8.9 installation with standard packages -- copy to your CoCalc project to get it
#############################################################################
#
# This is straightforward implementation of Karatsuba multiplication for
# integers. It is used to demonstrate the algorithm using the GAP language,
# and not suitable for practical implementation, because it is slow by a
# number of reasons.
#
KaratsubaMultiplication:=function( x, y )
local n, b, x1, x2, y1, y2, u, v, w;
if x < 10 and y < 10 then
return x*y;
else
n:=Int( Length ( String ( Maximum( x, y ) ) ) / 2 );
b := 10^n;
x1 := Int( x / b );
x2 := x mod b;
y1 := Int( y / b );
y2 := y mod b;
u := KaratsubaMultiplication(x1,y1);
v := KaratsubaMultiplication(x2,y2);
w := KaratsubaMultiplication(x1+x2,y1+y2);
return u*(b^2) + (w-u-v)*b + v;
fi;
end;
#############################################################################
#
# This is straightforward implementation of Karatsuba multiplication for
# polynomials. It is used to demonstrate the algorithm using the GAP language,
# and not suitable for practical implementation, because it is slow by a
# number of reasons.
#
KaratsubaPolynomialMultiplicationSlow:=function( f, g )
local deg, n, x, b, f1, f0, g1, g0, u, v, w;
if IsZero(f) or IsZero(g) then
return Zero(f);
fi;
deg := Maximum( List( [f,g], DegreeOfLaurentPolynomial ) );
n:=1;
while n < deg do
n:=n*2;
od;
if n=1 then
return f*g;
else
x := IndeterminateOfUnivariateRationalFunction( f );
b := x^(n/2);
f1 := EuclideanQuotient( f, b );
f0 := EuclideanRemainder( f, b );
g1 := EuclideanQuotient( g, b );
g0 := EuclideanRemainder( g, b );
u := KaratsubaPolynomialMultiplicationSlow( f1, g1 );
v := KaratsubaPolynomialMultiplicationSlow( f0, g0 );
w := KaratsubaPolynomialMultiplicationSlow( f1+f0, g1+g0 );
return u*(b^2) + (w-u-v)*b + v;
fi;
end;
#############################################################################
#
# This implementation of Karatsuba multiplication for polynomials is faster
# than the previous, because it uses internal representation of polynomials
# for fast finding Euclidean quotient and remainder, and fast multiplying
# by x^(n/2) and x^n. Nevertheless, it is not fully efficient, since it
# uses explicit polynomials as arguments in recursive calls.
#
KaratsubaPolynomialMultiplicationBetter:=function( f, g )
local deg, n, halfn, x, b, nr, fam, f1, f0, g1, g0, u, v, w,
cf, cf1, cf0, cg, cg1, cg0, k, pos, val,
cu, ub2, cwuv, wuvb;
if IsZero(f) or IsZero(g) then
return Zero(f);
fi;
deg := Maximum( List( [f,g], DegreeOfLaurentPolynomial ) );
n:=1;
while n < deg do
n:=n*2;
od;
if n=1 then
return f*g;
else
halfn := n/2;
x := IndeterminateOfUnivariateRationalFunction( f );
b := x^(halfn);
nr := IndeterminateNumberOfLaurentPolynomial(f);
fam := FamilyObj( 1 );
if DegreeOfLaurentPolynomial(f) >= halfn then
cf := CoefficientsOfLaurentPolynomial( f );
k:=halfn-cf[2]+1;
if k<1 then
pos:=1;
val:=1-k;
else
pos:=k;
val:=0;
fi;
cf1 := cf[1]{[ pos .. Length(cf[1])]};
f1 := LaurentPolynomialByCoefficients( fam, cf1, val, nr ); # EuclideanQuotient( f, b )
cf0 := cf[1]{[ 1 .. halfn-cf[2] ]};
f0 := LaurentPolynomialByCoefficients( fam, cf0, cf[2], nr ); # EuclideanRemainder( f, b )
else
f1:=Zero(f);
f0:=f;
fi;
if DegreeOfLaurentPolynomial(g) >= halfn then
cg := CoefficientsOfLaurentPolynomial( g );
k:=halfn-cg[2]+1;
if k<1 then
pos:=1;
val:=1-k;
else
pos:=k;
val:=0;
fi;
cg1 := cg[1]{[ pos .. Length(cg[1])]};
g1 := LaurentPolynomialByCoefficients( fam, cg1, val, nr ); # EuclideanQuotient( g, b )
cg0 := cg[1]{[ 1 .. halfn-cg[2] ]};
g0 := LaurentPolynomialByCoefficients( fam, cg0, cg[2], nr ); # EuclideanRemainder( g, b )
else
g1:=Zero(g);
g0:=g;
fi;
u := KaratsubaPolynomialMultiplicationBetter(f1,g1);
v := KaratsubaPolynomialMultiplicationBetter(f0,g0);
w := KaratsubaPolynomialMultiplicationBetter(f1+f0,g1+g0);
cu := CoefficientsOfLaurentPolynomial( u );
ub2 := LaurentPolynomialByCoefficients( fam, cu[1], cu[2]+n, nr );
cwuv := CoefficientsOfLaurentPolynomial( w-u-v );
wuvb := LaurentPolynomialByCoefficients( fam, cwuv[1], cwuv[2]+halfn, nr );
return ub2 + wuvb + v;
fi;
end;
#############################################################################
#
# KARATSUBA MULTIPLICATION FOR POLYNOMIALS
#
#############################################################################
PlusLaurentPolynomialsExtRep := function( f, g )
local val, t, shift, i, j, ind, pos, pos1, k, zero;
if f[1]=[] then # f=0
return g;
elif g[1]=[] then # g=0
return f;
elif f[2]=g[2] then # f and g starts from monomials of the same degree
val := f[2];
t := f[1] + g[1];
elif f[2] < g [2] then # f starts earlier
zero := Zero(f[1][1]);
val := f[2];
t := ShallowCopy(f[1]);
shift := g[2]-f[2];
for j in [ Length(t) + 1 .. shift ] do
t[j]:=zero;
od;
for i in [ 1 .. Length(g[1])] do
ind := shift + i;
if IsBound(t[ind]) then
t[ind] := t[ind] + g[1][i];
else
t[ind] := g[1][i];
fi;
od;
else # g starts earlier
zero := Zero(f[1][1]);
val := g[2];
t := ShallowCopy(g[1]);
shift := f[2]-g[2];
for j in [ Length(t) + 1 .. shift ] do
t[j]:=zero;
od;
for i in [ 1 .. Length(f[1])] do
ind := shift + i;
if IsBound(t[ind]) then
t[ind] := t[ind] + f[1][i];
else
t[ind] := f[1][i];
fi;
od;
fi;
# Final analysis, removing trailing zeroes from both sides
if t=[] then
return [ [ ], 0 ];
else
pos := PositionNonZero( t );
if pos = Length(t)+1 then
return [ [ ], 0 ];
else
pos1 := First([1..Length(t)], k -> t[Length(t)-k+1]<>0 );
return [ t{[pos..Length(t)-pos1+1]}, val + pos -1 ];
fi;
fi;
end;
MinusLaurentPolynomialsExtRep := function( f, g )
local val, t, shift, i, j, ind, pos, pos1, k, zero;
if f[1]=[] then # f=0
return [ -g[1], g[2] ];
elif g[1]=[] then # g=0
return f;
elif f[2]=g[2] then # f and g starts from monomials of the same degree
val := f[2];
t := f[1] - g[1];
elif f[2] < g [2] then # f starts earlier
zero := Zero(f[1][1]);
val := f[2];
t := ShallowCopy(f[1]);
shift := g[2]-f[2];
for j in [ Length(t) + 1 .. shift ] do
t[j]:=zero;
od;
for i in [ 1 .. Length(g[1])] do
ind := shift + i;
if IsBound(t[ind]) then
t[ind] := t[ind] - g[1][i];
else
t[ind] := -g[1][i];
fi;
od;
else # g starts earlier
zero := Zero(f[1][1]);
val := g[2];
t := -ShallowCopy(g[1]);
shift := f[2]-g[2];
for j in [ Length(t) + 1 .. shift ] do
t[j]:=zero;
od;
for i in [ 1 .. Length(f[1])] do
ind := shift + i;
if IsBound(t[ind]) then
t[ind] := t[ind] + f[1][i];
else
t[ind] := f[1][i];
fi;
od;
fi;
# Final analysis, removing trailing zeroes from both sides
if t=[] then
return [ [ ], 0 ];
else
pos := PositionNonZero( t );
if pos = Length(t)+1 then
return [ [ ], 0 ];
else
pos1 := First([1..Length(t)], k -> t[Length(t)-k+1]<>0 );
return [ t{[pos..Length(t)-pos1+1]}, val + pos - 1 ];
fi;
fi;
end;
#############################################################################
#
# Must be >=3 for correct work. May depend on the coefficients field.
# For polynomials with integer coefficients, empirically determined
# optimal value is 48.
#
KARATSUBA_CUTOFF := 48;
#############################################################################
#
# Here the input is the presentation of polynomials as it is produced by
# the function CoefficientsOfLaurentPolynomial, that is the polynomial
# 2*x^4+3*x^3+x^2+x+1 will be represented as [ [ 1, 1, 1, 3, 2 ], 0 ],
# and 5*x^10-2*x^8+x^6 as [ [ 1, 0, -2, 0, 5 ], 6 ]
#
KaratsubaPolynomialMultiplicationExtRep:=function( f, g )
local degf, degg, deg, n, halfn, nr, f1, f0, g1, g0, u, v, w, wuv, k, pos, pos1, val;
# Zero polynomial will be represented as [ [ ], 0 ]
# We took care that other representations of zero, for example [ [ 0, 0 ], 0 ]
# or [ [ 0, 0 ], 1 ], or [ [ 0 ], 1 ], can not occure, because we reduce
# the presentation after adding polynomials in PlusLaurentPolynomialsExtRep
# and subtracting them in MinusLaurentPolynomialsExtRep. Note that degree
# determination is also based on this feature.
if f[1]=[] or g[1]=[] then
return [ [ ], 0 ];
fi;
if Length(f[1]) < KARATSUBA_CUTOFF and Length(g[1]) < KARATSUBA_CUTOFF then
return [ PRODUCT_COEFFS_GENERIC_LISTS( f[1], Length(f[1]), g[1], Length(g[1]) ), f[2]+g[2] ];
fi;
# We determine degree from valuation and length of the coefficients list
degf := f[2]+Length(f[1])-1;
degg := g[2]+Length(g[1])-1;
deg := Maximum( degf, degg );
n:=1;
while n < deg do
n:=n*2;
od;
# we can proceed immediately, since the case n=1 already caught by KARATSUBA_CUTOFF
halfn := n/2;
# developing the 1st polynomial
if degf >= halfn then
k:=halfn-f[2]+1;
if k<1 then
pos:=1;
val:=1-k;
else
pos:=k;
val:=0;
fi;
# we remove initial zeroes in the quotient, if such exist
pos1 := First([ pos .. Length(f[1])], k -> f[1][k] <> 0 );
f1 := [ f[1]{[ pos1 .. Length(f[1])]}, val+pos1-pos ]; # EuclideanQuotient( f, b )
# we remove trailing zeroes in the remainder, if such exist
pos1 := First( [ 1 .. halfn-f[2] ], k -> f[1][halfn-f[2]-k+1] <> 0 );
f0 := [ f[1]{[ 1 .. halfn-f[2]-pos1+1 ]}, f[2] ]; # EuclideanRemainder( f, b )
else
f1 := [ [ ], 0 ];
f0 := f;
fi;
# developing the 2nd polynomial
if degg >= halfn then
k:=halfn-g[2]+1;
if k<1 then
pos:=1;
val:=1-k;
else
pos:=k;
val:=0;
fi;
# we remove initial zeroes in the quotient, if such exist
pos1 := First([ pos .. Length(g[1])], k -> g[1][k] <> 0 );
g1 := [ g[1]{[ pos1 .. Length(g[1])]}, val+pos1-pos ]; # EuclideanQuotient( g, b )
# we remove trailing zeroes in the remainder, if such exist
pos1 := First( [ 1 .. halfn-g[2] ], k -> g[1][halfn-g[2]-k+1] <> 0 );
g0 := [ g[1]{[ 1 .. halfn-g[2]-pos1+1 ]}, g[2] ]; # EuclideanRemainder( g, b )
else
g1 := [ [ ], 0 ];
g0 := g;
fi;
# three recursive calls
u := KaratsubaPolynomialMultiplicationExtRep(f1,g1);
v := KaratsubaPolynomialMultiplicationExtRep(f0,g0);
w := KaratsubaPolynomialMultiplicationExtRep(
PlusLaurentPolynomialsExtRep(f1,f0),
PlusLaurentPolynomialsExtRep(g1,g0) );
# composing the result
wuv := MinusLaurentPolynomialsExtRep( MinusLaurentPolynomialsExtRep(w,u), v );
wuv[2] := wuv[2] + halfn;
u[2] := u[2] + n;
return PlusLaurentPolynomialsExtRep( PlusLaurentPolynomialsExtRep(u,wuv), v );
# return u*(b^2) + (w-u-v)*b + v;
end;
#############################################################################
#
# This is the top-level function that accepts polynomials in the natural
# presentation. The actual job is done by the recursively called function
# KaratsubaPolynomialMultiplicationExtRep. Nevertheless, we perform the
# first step in the top-level function instead of just passing the lists
# of coefficients to the KaratsubaPolynomialMultiplicationExtRep. This
# allows us to shorten the size of input, that maybe especially essential
# if KaratsubaPolynomialMultiplicationExtRep will be called as a web service.
#
KaratsubaPolynomialMultiplication:=function( f, g )
local deg, n, halfn, x, b, nr, fam, f1, f0, g1, g0, u, v, w, wuv,
cf, cg, k, pos, pos1, val, result;
if IsZero(f) or IsZero(g) then
return Zero(f);
fi;
deg := Maximum( List( [f,g], DegreeOfLaurentPolynomial ) );
n:=1;
while n < deg do
n:=n*2;
od;
if n=1 then
return f*g;
else
halfn := n/2;
x := IndeterminateOfUnivariateRationalFunction( f );
nr := IndeterminateNumberOfLaurentPolynomial(f);
fam := CoefficientsFamily( FamilyObj( f ) );
# developing the 1st polynomial
if DegreeOfLaurentPolynomial(f) >= halfn then
cf := CoefficientsOfLaurentPolynomial( f );
k:=halfn-cf[2]+1;
if k<1 then
pos:=1;
val:=1-k;
else
pos:=k;
val:=0;
fi;
# we remove initial zeroes in the quotient, if such exist
pos1 := First([ pos .. Length(cf[1])], k -> cf[1][k] <> 0 );
f1 := [ cf[1]{[ pos1 .. Length(cf[1])]}, val+pos1-pos ]; # EuclideanQuotient( f, b )
# we remove trailing zeroes in the remainder, if such exist
pos1 := First( [ 1 .. halfn-cf[2] ], k -> cf[1][halfn-cf[2]-k+1] <> 0 );
f0 := [ cf[1]{[ 1 .. halfn-cf[2]-pos1+1 ]}, cf[2] ]; # EuclideanRemainder( f, b )
else
f1 := [ [ ], 0 ];
f0 := CoefficientsOfLaurentPolynomial( f );
fi;
# developing the 2nd polynomial
if DegreeOfLaurentPolynomial(g) >= halfn then
cg := CoefficientsOfLaurentPolynomial( g );
k:=halfn-cg[2]+1;
if k<1 then
pos:=1;
val:=1-k;
else
pos:=k;
val:=0;
fi;
# we remove initial zeroes in the quotient, if such exist
pos1 := First([ pos .. Length(cg[1])], k -> cg[1][k] <> 0 );
g1 := [ cg[1]{[ pos1 .. Length(cg[1])]}, val+pos1-pos ]; # EuclideanQuotient( g, b )
# we remove trailing zeroes in the remainder, if such exist
pos1 := First( [ 1 .. halfn-cg[2] ], k -> cg[1][halfn-cg[2]-k+1] <> 0 );
g0 := [ cg[1]{[ 1 .. halfn-cg[2]-pos1+1 ]}, cg[2] ]; # EuclideanRemainder( g, b )
else
g1 := [ [ ], 0 ];
g0 := CoefficientsOfLaurentPolynomial( g );
fi;
# three recursive calls
u := KaratsubaPolynomialMultiplicationExtRep(f1,g1);
v := KaratsubaPolynomialMultiplicationExtRep(f0,g0);
w := KaratsubaPolynomialMultiplicationExtRep(
PlusLaurentPolynomialsExtRep(f1,f0),
PlusLaurentPolynomialsExtRep(g1,g0) );
# composing the result
wuv := MinusLaurentPolynomialsExtRep( MinusLaurentPolynomialsExtRep(w,u), v );
wuv[2] := wuv[2] + halfn;
u[2] := u[2] + n;
result := PlusLaurentPolynomialsExtRep( PlusLaurentPolynomialsExtRep(u,wuv), v );
return LaurentPolynomialByCoefficients( fam, result[1], result[2], nr );
# return u*(b^2) + (w-u-v)*b + v;
fi;
end;
#############################################################################
#
# This is the web-service using version of KaratsubaPolynomialMultiplication
#
KaratsubaPolynomialMultiplicationWS:=function( f, g )
local deg, n, halfn, x, b, nr, fam, f1, f0, g1, g0, u, v, w, wuv,
cf, cg, k, pos, pos1, val, wsresult, result;
if IsZero(f) or IsZero(g) then
return Zero(f);
fi;
deg := Maximum( List( [f,g], DegreeOfLaurentPolynomial ) );
n:=1;
while n < deg do
n:=n*2;
od;
if n=1 then
return f*g;
else
halfn := n/2;
x := IndeterminateOfUnivariateRationalFunction( f );
nr := IndeterminateNumberOfLaurentPolynomial(f);
fam := CoefficientsFamily( FamilyObj( f ) );
# developing the 1st polynomial
if DegreeOfLaurentPolynomial(f) >= halfn then
cf := CoefficientsOfLaurentPolynomial( f );
k:=halfn-cf[2]+1;
if k<1 then
pos:=1;
val:=1-k;
else
pos:=k;
val:=0;
fi;
# we remove initial zeroes in the quotient, if such exist
pos1 := First([ pos .. Length(cf[1])], k -> cf[1][k] <> 0 );
f1 := [ cf[1]{[ pos1 .. Length(cf[1])]}, val+pos1-pos ]; # EuclideanQuotient( f, b )
# we remove trailing zeroes in the remainder, if such exist
pos1 := First( [ 1 .. halfn-cf[2] ], k -> cf[1][halfn-cf[2]-k+1] <> 0 );
f0 := [ cf[1]{[ 1 .. halfn-cf[2]-pos1+1 ]}, cf[2] ]; # EuclideanRemainder( f, b )
else
f1 := [ [ ], 0 ];
f0 := CoefficientsOfLaurentPolynomial( f );
fi;
# developing the 2nd polynomial
if DegreeOfLaurentPolynomial(g) >= halfn then
cg := CoefficientsOfLaurentPolynomial( g );
k:=halfn-cg[2]+1;
if k<1 then
pos:=1;
val:=1-k;
else
pos:=k;
val:=0;
fi;
# we remove initial zeroes in the quotient, if such exist
pos1 := First([ pos .. Length(cg[1])], k -> cg[1][k] <> 0 );
g1 := [ cg[1]{[ pos1 .. Length(cg[1])]}, val+pos1-pos ]; # EuclideanQuotient( g, b )
# we remove trailing zeroes in the remainder, if such exist
pos1 := First( [ 1 .. halfn-cg[2] ], k -> cg[1][halfn-cg[2]-k+1] <> 0 );
g0 := [ cg[1]{[ 1 .. halfn-cg[2]-pos1+1 ]}, cg[2] ]; # EuclideanRemainder( g, b )
else
g1 := [ [ ], 0 ];
g0 := CoefficientsOfLaurentPolynomial( g );
fi;
# three recursive calls
# u := KaratsubaPolynomialMultiplicationExtRep(f1,g1);
# v := KaratsubaPolynomialMultiplicationExtRep(f0,g0);
u := NewProcess( "WS_Karatsuba",[ String(f1), String(g1) ],"localhost", 26133);
v := NewProcess( "WS_Karatsuba",[ String(f0), String(g0) ],"localhost", 26134);
w := KaratsubaPolynomialMultiplicationExtRep(
PlusLaurentPolynomialsExtRep(f1,f0),
PlusLaurentPolynomialsExtRep(g1,g0) );
wsresult:=SynchronizeProcesses2( u,v );
u := EvalString( wsresult[1].object );
v := EvalString( wsresult[2].object );
# composing the result
wuv := MinusLaurentPolynomialsExtRep( MinusLaurentPolynomialsExtRep(w,u), v );
wuv[2] := wuv[2] + halfn;
u[2] := u[2] + n;
result := PlusLaurentPolynomialsExtRep( PlusLaurentPolynomialsExtRep(u,wuv), v );
return LaurentPolynomialByCoefficients( fam, result[1], result[2], nr );
# return u*(b^2) + (w-u-v)*b + v;
fi;
end;