505 lines
14 KiB
JavaScript
505 lines
14 KiB
JavaScript
const MAX_ALL_FACTORS = 100000;
|
|
const algorithm = 2;
|
|
let divisions = 0;
|
|
|
|
// Term class
|
|
class Term {
|
|
constructor(c, e) {
|
|
this.m_coefficient = c;
|
|
this.m_exponent = e;
|
|
}
|
|
|
|
coefficient() {
|
|
return this.m_coefficient;
|
|
}
|
|
|
|
degree() {
|
|
return this.m_exponent;
|
|
}
|
|
|
|
negated() {
|
|
return new Term(-this.m_coefficient, this.m_exponent);
|
|
}
|
|
|
|
multiply(rhs) {
|
|
return new Term(this.m_coefficient * rhs.m_coefficient, this.m_exponent + rhs.m_exponent);
|
|
}
|
|
|
|
add(rhs) {
|
|
if (this.m_exponent !== rhs.m_exponent) {
|
|
throw new Error("Exponents not equal");
|
|
}
|
|
return new Term(this.m_coefficient + rhs.m_coefficient, this.m_exponent);
|
|
}
|
|
|
|
toString() {
|
|
if (this.m_coefficient === 0) {
|
|
return '0';
|
|
}
|
|
if (this.m_exponent === 0) {
|
|
return this.m_coefficient.toString();
|
|
}
|
|
if (this.m_coefficient === 1) {
|
|
if (this.m_exponent === 1) {
|
|
return 'x';
|
|
}
|
|
return `x^${this.m_exponent}`;
|
|
}
|
|
if (this.m_coefficient === -1) {
|
|
if (this.m_exponent === 1) {
|
|
return "-x";
|
|
}
|
|
return `-x^${this.m_exponent}`;
|
|
}
|
|
if (this.m_exponent === 1) {
|
|
return `${this.m_coefficient}x`;
|
|
}
|
|
return `${this.m_coefficient}x^${this.m_exponent}`;
|
|
}
|
|
}
|
|
|
|
// Polynomial class
|
|
class Polynomial {
|
|
constructor(values) {
|
|
this.polynomialTerms = [];
|
|
|
|
if (values === undefined) {
|
|
this.polynomialTerms.push(new Term(0, 0));
|
|
} else if (Array.isArray(values) && values.length % 2 === 0) {
|
|
for (let i = 0; i < values.length; i += 2) {
|
|
this.polynomialTerms.push(new Term(values[i], values[i+1]));
|
|
}
|
|
this.polynomialTerms.sort((t, u) => u.degree() - t.degree());
|
|
} else if (Array.isArray(values)) {
|
|
if (values.length === 0) {
|
|
this.polynomialTerms.push(new Term(0, 0));
|
|
} else {
|
|
for (let t of values) {
|
|
if (t.coefficient() !== 0) {
|
|
this.polynomialTerms.push(t);
|
|
}
|
|
}
|
|
if (this.polynomialTerms.length === 0) {
|
|
this.polynomialTerms.push(new Term(0, 0));
|
|
}
|
|
this.polynomialTerms.sort((t, u) => u.degree() - t.degree());
|
|
}
|
|
}
|
|
}
|
|
|
|
leadingCoefficient() {
|
|
return this.polynomialTerms[0].coefficient();
|
|
}
|
|
|
|
degree() {
|
|
return this.polynomialTerms[0].degree();
|
|
}
|
|
|
|
hasCoefficientAbs(coeff) {
|
|
for (let term of this.polynomialTerms) {
|
|
if (Math.abs(term.coefficient()) === coeff) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
addTerm(term) {
|
|
const termList = [];
|
|
let added = false;
|
|
|
|
for (let index = 0; index < this.polynomialTerms.length; index++) {
|
|
const currentTerm = this.polynomialTerms[index];
|
|
if (currentTerm.degree() === term.degree()) {
|
|
added = true;
|
|
if (currentTerm.coefficient() + term.coefficient() !== 0) {
|
|
termList.push(currentTerm.add(term));
|
|
}
|
|
} else {
|
|
termList.push(currentTerm);
|
|
}
|
|
}
|
|
|
|
if (!added) {
|
|
termList.push(term);
|
|
}
|
|
|
|
return new Polynomial(termList);
|
|
}
|
|
|
|
multiplyByTerm(term) {
|
|
const termList = [];
|
|
|
|
for (let index = 0; index < this.polynomialTerms.length; index++) {
|
|
const currentTerm = this.polynomialTerms[index];
|
|
termList.push(currentTerm.multiply(term));
|
|
}
|
|
|
|
return new Polynomial(termList);
|
|
}
|
|
|
|
add(p) {
|
|
const termList = [];
|
|
let thisCount = this.polynomialTerms.length;
|
|
let polyCount = p.polynomialTerms.length;
|
|
|
|
while (thisCount > 0 || polyCount > 0) {
|
|
if (thisCount === 0) {
|
|
const polyTerm = p.polynomialTerms[polyCount - 1];
|
|
termList.push(polyTerm);
|
|
polyCount--;
|
|
} else if (polyCount === 0) {
|
|
const thisTerm = this.polynomialTerms[thisCount - 1];
|
|
termList.push(thisTerm);
|
|
thisCount--;
|
|
} else {
|
|
const polyTerm = p.polynomialTerms[polyCount - 1];
|
|
const thisTerm = this.polynomialTerms[thisCount - 1];
|
|
|
|
if (thisTerm.degree() === polyTerm.degree()) {
|
|
const t = thisTerm.add(polyTerm);
|
|
if (t.coefficient() !== 0) {
|
|
termList.push(t);
|
|
}
|
|
thisCount--;
|
|
polyCount--;
|
|
} else if (thisTerm.degree() < polyTerm.degree()) {
|
|
termList.push(thisTerm);
|
|
thisCount--;
|
|
} else {
|
|
termList.push(polyTerm);
|
|
polyCount--;
|
|
}
|
|
}
|
|
}
|
|
|
|
return new Polynomial(termList);
|
|
}
|
|
|
|
divide(v) {
|
|
divisions++;
|
|
|
|
let q = new Polynomial();
|
|
let r = new Polynomial(this.polynomialTerms);
|
|
const lcv = v.leadingCoefficient();
|
|
const dv = v.degree();
|
|
|
|
while (r.degree() >= v.degree()) {
|
|
const lcr = r.leadingCoefficient();
|
|
const s = Math.floor(lcr / lcv);
|
|
const term = new Term(s, r.degree() - dv);
|
|
q = q.addTerm(term);
|
|
r = r.add(v.multiplyByTerm(term.negated()));
|
|
}
|
|
|
|
return q;
|
|
}
|
|
|
|
toString() {
|
|
if (this.polynomialTerms.length === 0) {
|
|
return '0';
|
|
}
|
|
|
|
let result = this.polynomialTerms[0].toString();
|
|
|
|
for (let i = 1; i < this.polynomialTerms.length; i++) {
|
|
const term = this.polynomialTerms[i];
|
|
if (term.coefficient() > 0) {
|
|
result += ` + ${term.toString()}`;
|
|
} else {
|
|
result += ` - ${term.negated().toString()}`;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
function getDivisors(number) {
|
|
const divisors = [];
|
|
const root = Math.floor(Math.sqrt(number));
|
|
|
|
for (let i = 1; i <= root; i++) {
|
|
if (number % i === 0) {
|
|
divisors.push(i);
|
|
const div = Math.floor(number / i);
|
|
if (div !== i && div !== number) {
|
|
divisors.push(div);
|
|
}
|
|
}
|
|
}
|
|
|
|
return divisors;
|
|
}
|
|
|
|
const allFactors = new Map();
|
|
|
|
function getFactors(number) {
|
|
if (allFactors.has(number)) {
|
|
return allFactors.get(number);
|
|
}
|
|
|
|
const factors = new Map();
|
|
|
|
if (number % 2 === 0) {
|
|
const factorsDivTwo = getFactors(number / 2);
|
|
for (const [key, value] of factorsDivTwo) {
|
|
factors.set(key, value);
|
|
}
|
|
factors.set(2, (factors.get(2) || 0) + 1);
|
|
|
|
if (number < MAX_ALL_FACTORS) {
|
|
allFactors.set(number, factors);
|
|
}
|
|
|
|
return factors;
|
|
}
|
|
|
|
const root = Math.floor(Math.sqrt(number));
|
|
let i = 3;
|
|
|
|
while (i <= root) {
|
|
if (number % i === 0) {
|
|
const factorsDivI = getFactors(number / i);
|
|
for (const [key, value] of factorsDivI) {
|
|
factors.set(key, value);
|
|
}
|
|
factors.set(i, (factors.get(i) || 0) + 1);
|
|
|
|
if (number < MAX_ALL_FACTORS) {
|
|
allFactors.set(number, factors);
|
|
}
|
|
|
|
return factors;
|
|
}
|
|
i += 2;
|
|
}
|
|
|
|
factors.set(number, 1);
|
|
|
|
if (number < MAX_ALL_FACTORS) {
|
|
allFactors.set(number, factors);
|
|
}
|
|
|
|
return factors;
|
|
}
|
|
|
|
const COMPUTED = new Map();
|
|
|
|
function cyclotomicPolynomial(n) {
|
|
if (COMPUTED.has(n)) {
|
|
return COMPUTED.get(n);
|
|
}
|
|
|
|
if (n === 1) {
|
|
// Polynomial: x - 1
|
|
const p = new Polynomial([1, 1, -1, 0]);
|
|
COMPUTED.set(1, p);
|
|
return p;
|
|
}
|
|
|
|
const factors = getFactors(n);
|
|
|
|
if (factors.has(n)) {
|
|
// n prime
|
|
const termList = [];
|
|
for (let index = 0; index < n; index++) {
|
|
termList.push(new Term(1, index));
|
|
}
|
|
|
|
const cyclo = new Polynomial(termList);
|
|
COMPUTED.set(n, cyclo);
|
|
return cyclo;
|
|
} else if (factors.size === 2 && factors.has(2) && factors.get(2) === 1 && factors.has(n / 2) && factors.get(n / 2) === 1) {
|
|
// n = 2p
|
|
const prime = n / 2;
|
|
const termList = [];
|
|
let coeff = -1;
|
|
|
|
for (let index = 0; index < prime; index++) {
|
|
coeff *= -1;
|
|
termList.push(new Term(coeff, index));
|
|
}
|
|
|
|
const cyclo = new Polynomial(termList);
|
|
COMPUTED.set(n, cyclo);
|
|
return cyclo;
|
|
} else if (factors.size === 1 && factors.has(2)) {
|
|
// n = 2^h
|
|
const h = factors.get(2);
|
|
const termList = [];
|
|
termList.push(new Term(1, Math.pow(2, h - 1)));
|
|
termList.push(new Term(1, 0));
|
|
|
|
const cyclo = new Polynomial(termList);
|
|
COMPUTED.set(n, cyclo);
|
|
return cyclo;
|
|
} else if (factors.size === 1 && factors.has(n)) {
|
|
// n = p^k
|
|
let p = 0;
|
|
let k = 0;
|
|
|
|
for (const [key, value] of factors) {
|
|
p = key;
|
|
k = value;
|
|
}
|
|
|
|
const termList = [];
|
|
for (let index = 0; index < p; index++) {
|
|
termList.push(new Term(1, index * Math.pow(p, k - 1)));
|
|
}
|
|
|
|
const cyclo = new Polynomial(termList);
|
|
COMPUTED.set(n, cyclo);
|
|
return cyclo;
|
|
} else if (factors.size === 2 && factors.has(2)) {
|
|
// n = 2^h * p^k
|
|
let p = 0;
|
|
|
|
for (const [key, value] of factors) {
|
|
if (key !== 2) {
|
|
p = key;
|
|
}
|
|
}
|
|
|
|
const termList = [];
|
|
let coeff = -1;
|
|
const twoExp = Math.pow(2, factors.get(2) - 1);
|
|
const k = factors.get(p);
|
|
|
|
for (let index = 0; index < p; index++) {
|
|
coeff *= -1;
|
|
termList.push(new Term(coeff, index * twoExp * Math.pow(p, k - 1)));
|
|
}
|
|
|
|
const cyclo = new Polynomial(termList);
|
|
COMPUTED.set(n, cyclo);
|
|
return cyclo;
|
|
} else if (factors.has(2) && ((n / 2) % 2 === 1) && (n / 2) > 1) {
|
|
// CP(2m)[x] = CP(-m)[x], n odd integer > 1
|
|
const cycloDiv2 = cyclotomicPolynomial(n / 2);
|
|
const termList = [];
|
|
|
|
for (const term of cycloDiv2.polynomialTerms) {
|
|
if (term.degree() % 2 === 0) {
|
|
termList.push(term);
|
|
} else {
|
|
termList.push(term.negated());
|
|
}
|
|
}
|
|
|
|
const cyclo = new Polynomial(termList);
|
|
COMPUTED.set(n, cyclo);
|
|
return cyclo;
|
|
}
|
|
|
|
// General Case
|
|
if (algorithm === 0) {
|
|
// slow - uses basic definition
|
|
const divisors = getDivisors(n);
|
|
// Polynomial: (x^n - 1)
|
|
let cyclo = new Polynomial([1, n, -1, 0]);
|
|
|
|
for (const i of divisors) {
|
|
const p = cyclotomicPolynomial(i);
|
|
cyclo = cyclo.divide(p);
|
|
}
|
|
|
|
COMPUTED.set(n, cyclo);
|
|
return cyclo;
|
|
} else if (algorithm === 1) {
|
|
// Faster. Remove Max divisor (and all divisors of max divisor) - only one divide for all divisors of Max Divisor
|
|
const divisors = getDivisors(n);
|
|
let maxDivisor = Number.MIN_SAFE_INTEGER;
|
|
|
|
for (const div of divisors) {
|
|
maxDivisor = Math.max(maxDivisor, div);
|
|
}
|
|
|
|
const divisorExceptMax = [];
|
|
for (const div of divisors) {
|
|
if (maxDivisor % div !== 0) {
|
|
divisorExceptMax.push(div);
|
|
}
|
|
}
|
|
|
|
// Polynomial: ( x^n - 1 ) / ( x^m - 1 ), where m is the max divisor
|
|
let cyclo = new Polynomial([1, n, -1, 0]).divide(new Polynomial([1, maxDivisor, -1, 0]));
|
|
|
|
for (const i of divisorExceptMax) {
|
|
const p = cyclotomicPolynomial(i);
|
|
cyclo = cyclo.divide(p);
|
|
}
|
|
|
|
COMPUTED.set(n, cyclo);
|
|
return cyclo;
|
|
} else if (algorithm === 2) {
|
|
// Fastest
|
|
// Let p ; q be primes such that p does not divide n, and q q divides n.
|
|
// Then CP(np)[x] = CP(n)[x^p] / CP(n)[x]
|
|
let m = 1;
|
|
let cyclo = cyclotomicPolynomial(m);
|
|
const primes = Array.from(factors.keys()).sort((a, b) => a - b);
|
|
|
|
for (const prime of primes) {
|
|
// CP(m)[x]
|
|
const cycloM = cyclo;
|
|
// Compute CP(m)[x^p].
|
|
const termList = [];
|
|
|
|
for (const t of cycloM.polynomialTerms) {
|
|
termList.push(new Term(t.coefficient(), t.degree() * prime));
|
|
}
|
|
|
|
cyclo = new Polynomial(termList).divide(cycloM);
|
|
m = m * prime;
|
|
}
|
|
|
|
// Now, m is the largest square free divisor of n
|
|
const s = n / m;
|
|
// Compute CP(n)[x] = CP(m)[x^s]
|
|
const termList = [];
|
|
|
|
for (const t of cyclo.polynomialTerms) {
|
|
termList.push(new Term(t.coefficient(), t.degree() * s));
|
|
}
|
|
|
|
cyclo = new Polynomial(termList);
|
|
COMPUTED.set(n, cyclo);
|
|
return cyclo;
|
|
} else {
|
|
throw new Error("Invalid algorithm");
|
|
}
|
|
}
|
|
|
|
function main() {
|
|
// initialization
|
|
const factors = new Map();
|
|
factors.set(2, 1);
|
|
allFactors.set(2, factors);
|
|
|
|
// Task 1: cyclotomic polynomials for n <= 30
|
|
console.log("Task 1: cyclotomic polynomials for n <= 30:");
|
|
for (let i = 1; i <= 30; i++) {
|
|
const p = cyclotomicPolynomial(i);
|
|
console.log(`CP[${i}] = ${p.toString()}`);
|
|
}
|
|
|
|
// Task 2: Smallest cyclotomic polynomial with n or -n as a coefficient
|
|
console.log("Task 2: Smallest cyclotomic polynomial with n or -n as a coefficient:");
|
|
let n = 0;
|
|
for (let i = 1; i <= 10; i++) {
|
|
while (true) {
|
|
n++;
|
|
const cyclo = cyclotomicPolynomial(n);
|
|
if (cyclo.hasCoefficientAbs(i)) {
|
|
console.log(`CP[${n}] has coefficient with magnitude = ${i}`);
|
|
n--;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
main();
|