Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM public.ecr.aws/x8v8d7g8/mars-base:latest

WORKDIR /app

ENV NODE_ENV=development

COPY package*.json ./

RUN npm install
RUN npm install mocha

COPY . .

CMD ["/bin/bash"]
108 changes: 103 additions & 5 deletions src/function/arithmetic/mod.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ const dependencies = [
'equalScalar',
'zeros',
'DenseMatrix',
'concat'
'concat',
'Complex'
]

export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, round, matrix, equalScalar, zeros, DenseMatrix, concat }) => {
export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, round, matrix, equalScalar, zeros, DenseMatrix, concat, Complex }) => {
const floor = createFloor({ typed, config, round, matrix, equalScalar, zeros, DenseMatrix })
const matAlgo02xDS0 = createMatAlgo02xDS0({ typed, equalScalar })
const matAlgo03xDSf = createMatAlgo03xDSf({ typed })
Expand All @@ -37,6 +38,10 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, c
*
* x - y * floor(x / y)
*
* For complex numbers, the function implements Gaussian integer division.
* Given complex numbers w and z, it finds the Gaussian integer q (complex number
* with integer real and imaginary parts) such that the norm of (w - z*q) is minimized.
*
* See https://en.wikipedia.org/wiki/Modulo_operation.
*
* Syntax:
Expand All @@ -47,6 +52,7 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, c
*
* math.mod(8, 3) // returns 2
* math.mod(11, 2) // returns 1
* math.mod(math.complex(5, 3), math.complex(2, 1)) // returns complex remainder
*
* function isOdd(x) {
* return math.mod(x, 2) != 0
Expand All @@ -59,15 +65,19 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, c
*
* divide
*
* @param {number | BigNumber | bigint | Fraction | Array | Matrix} x Dividend
* @param {number | BigNumber | bigint | Fraction | Array | Matrix} y Divisor
* @return {number | BigNumber | bigint | Fraction | Array | Matrix} Returns the remainder of `x` divided by `y`.
* @param {number | BigNumber | bigint | Fraction | Complex | Array | Matrix} x Dividend
* @param {number | BigNumber | bigint | Fraction | Complex | Array | Matrix} y Divisor
* @return {number | BigNumber | bigint | Fraction | Complex | Array | Matrix} Returns the remainder of `x` divided by `y`.
*/
return typed(
name,
{
'number, number': _modNumber,

'Complex, Complex': function (x, y) {
return _modComplex(x, y)
},

'BigNumber, BigNumber': function (x, y) {
return y.isZero() ? x : x.sub(y.mul(floor(x.div(y))))
},
Expand Down Expand Up @@ -114,4 +124,92 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, c
// precision float approximation
return (y === 0) ? x : x - y * floor(x / y)
}

/**
* Calculate the modulus of two complex numbers using Gaussian integer division
* @param {Complex} x dividend
* @param {Complex} y divisor
* @returns {Complex} remainder
* @private
*/
function _modComplex (x, y) {
// Handle division by zero
if (y.re === 0 && y.im === 0) {
return x
}

// Calculate the exact quotient x/y
const quotient = x.div(y)

// Round to the nearest Gaussian integer
const roundedQuotient = _roundToNearestGaussianInteger(quotient)

// Calculate remainder: r = x - y * q
const remainder = x.sub(y.mul(roundedQuotient))

return remainder
}

/**
* Round a complex number to the nearest Gaussian integer (complex number with integer real and imaginary parts)
* If there are ties, choose the one that results in the quotient with smallest norm
* @param {Complex} z complex number to round
* @returns {Complex} nearest Gaussian integer
* @private
*/
function _roundToNearestGaussianInteger (z) {
const re = z.re
const im = z.im

// Get the four candidate Gaussian integers (floor and ceil for both parts)
const floorRe = Math.floor(re)
const ceilRe = Math.ceil(re)
const floorIm = Math.floor(im)
const ceilIm = Math.ceil(im)

const candidates = [
new Complex(floorRe, floorIm),
new Complex(floorRe, ceilIm),
new Complex(ceilRe, floorIm),
new Complex(ceilRe, ceilIm)
]

// Find the candidate with minimum distance to z
let bestCandidate = candidates[0]
let minDistance = _complexDistanceSquared(z, candidates[0])

for (let i = 1; i < candidates.length; i++) {
const distance = _complexDistanceSquared(z, candidates[i])
if (distance < minDistance ||
(distance === minDistance && _complexNormSquared(candidates[i]) < _complexNormSquared(bestCandidate))) {
minDistance = distance
bestCandidate = candidates[i]
}
}

return bestCandidate
}

/**
* Calculate the squared distance between two complex numbers
* @param {Complex} a first complex number
* @param {Complex} b second complex number
* @returns {number} squared distance
* @private
*/
function _complexDistanceSquared (a, b) {
const deltaRe = a.re - b.re
const deltaIm = a.im - b.im
return deltaRe * deltaRe + deltaIm * deltaIm
}

/**
* Calculate the squared norm (modulus squared) of a complex number
* @param {Complex} z complex number
* @returns {number} squared norm
* @private
*/
function _complexNormSquared (z) {
return z.re * z.re + z.im * z.im
}
})
47 changes: 43 additions & 4 deletions test/unit-tests/function/arithmetic/mod.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,49 @@ describe('mod', function () {
assert.deepStrictEqual(mod(false, bignumber(3)), bignumber(0))
})

it('should throw an error if used on complex numbers', function () {
assert.throws(function () { mod(math.complex(1, 2), 3) }, TypeError)
assert.throws(function () { mod(3, math.complex(1, 2)) }, TypeError)
assert.throws(function () { mod(bignumber(3), math.complex(1, 2)) }, TypeError)
it('should calculate the modulus of two complex numbers', function () {
// Test basic complex modulus operations using Gaussian integer division

// Simple case: mod(5+3i, 2+1i) should return -1+0i
approxDeepEqual(mod(math.complex(5, 3), math.complex(2, 1)), math.complex(-1, 0))

// Test with negative components
approxDeepEqual(mod(math.complex(-5, 3), math.complex(2, 1)), math.complex(-1, 0))

// Test pure imaginary case: mod(0+4i, 0+2i) should return 0+0i
approxDeepEqual(mod(math.complex(0, 4), math.complex(0, 2)), math.complex(0, 0))

// Test real divisor: mod(7+3i, 3+0i) should return 1+0i
approxDeepEqual(mod(math.complex(7, 3), math.complex(3, 0)), math.complex(1, 0))

// Test with division by zero (should return the dividend)
assert.deepStrictEqual(mod(math.complex(1, 2), math.complex(0, 0)), math.complex(1, 2))

// Test case where quotient is already a Gaussian integer
approxDeepEqual(mod(math.complex(4, 6), math.complex(2, 3)), math.complex(0, 0))

// Test with fractional results
const result1 = mod(math.complex(3, 4), math.complex(1, 1))
assert(result1.re !== undefined && result1.im !== undefined)

// Verify the fundamental property: w = z*q + r where norm(r) < norm(z)
const w = math.complex(7, 5)
const z = math.complex(3, 2)
const r = mod(w, z)

// The remainder should have smaller norm than the divisor
const normR = r.re * r.re + r.im * r.im
const normZ = z.re * z.re + z.im * z.im
assert(normR <= normZ, `Remainder norm ${normR} should be <= divisor norm ${normZ}`)
})

it('should handle mixed complex and real arguments', function () {
// Math.js should automatically convert types for mixed operations
const result1 = mod(math.complex(5, 3), 2)
assert(result1.isComplex === true, 'Result should be complex')

const result2 = mod(3, math.complex(2, 1))
assert(result2.isComplex === true, 'Result should be complex')
})

it('should convert string to number', function () {
Expand Down