Skip to content
Closed
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
17 changes: 2 additions & 15 deletions src/function/arithmetic/addScalar.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { factory } from '../../utils/factory.js'
import { addNumber } from '../../plain/number/index.js'
import { createUnitScalarHandler } from './unitArithmeticHelper.js'

const name = 'addScalar'
const dependencies = ['typed']
Expand Down Expand Up @@ -37,20 +38,6 @@ export const createAddScalar = /* #__PURE__ */ factory(name, dependencies, ({ ty
return x.add(y)
},

'Unit, Unit': typed.referToSelf(self => (x, y) => {
if (x.value === null || x.value === undefined) {
throw new Error('Parameter x contains a unit with undefined value')
}
if (y.value === null || y.value === undefined) {
throw new Error('Parameter y contains a unit with undefined value')
}
if (!x.equalBase(y)) throw new Error('Units do not match')

const res = x.clone()
res.value =
typed.find(self, [res.valueType(), y.valueType()])(res.value, y.value)
res.fixPrefix = false
return res
})
'Unit, Unit': typed.referToSelf(self => createUnitScalarHandler(self, typed))
})
})
57 changes: 6 additions & 51 deletions src/function/arithmetic/multiply.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { isMatrix } from '../../utils/is.js'
import { arraySize } from '../../utils/array.js'
import { createMatAlgo11xS0s } from '../../type/matrix/utils/matAlgo11xS0s.js'
import { createMatAlgo14xDs } from '../../type/matrix/utils/matAlgo14xDs.js'
import { validateMatrixMultiplicationDimensions } from './multiplyDimensionValidation.js'

const name = 'multiply'
const dependencies = [
Expand All @@ -18,55 +19,9 @@ export const createMultiply = /* #__PURE__ */ factory(name, dependencies, ({ typ
const matAlgo11xS0s = createMatAlgo11xS0s({ typed, equalScalar })
const matAlgo14xDs = createMatAlgo14xDs({ typed })

function _validateMatrixDimensions (size1, size2) {
// check left operand dimensions
switch (size1.length) {
case 1:
// check size2
switch (size2.length) {
case 1:
// Vector x Vector
if (size1[0] !== size2[0]) {
// throw error
throw new RangeError('Dimension mismatch in multiplication. Vectors must have the same length')
}
break
case 2:
// Vector x Matrix
if (size1[0] !== size2[0]) {
// throw error
throw new RangeError('Dimension mismatch in multiplication. Vector length (' + size1[0] + ') must match Matrix rows (' + size2[0] + ')')
}
break
default:
throw new Error('Can only multiply a 1 or 2 dimensional matrix (Matrix B has ' + size2.length + ' dimensions)')
}
break
case 2:
// check size2
switch (size2.length) {
case 1:
// Matrix x Vector
if (size1[1] !== size2[0]) {
// throw error
throw new RangeError('Dimension mismatch in multiplication. Matrix columns (' + size1[1] + ') must match Vector length (' + size2[0] + ')')
}
break
case 2:
// Matrix x Matrix
if (size1[1] !== size2[0]) {
// throw error
throw new RangeError('Dimension mismatch in multiplication. Matrix A columns (' + size1[1] + ') must match Matrix B rows (' + size2[0] + ')')
}
break
default:
throw new Error('Can only multiply a 1 or 2 dimensional matrix (Matrix B has ' + size2.length + ' dimensions)')
}
break
default:
throw new Error('Can only multiply a 1 or 2 dimensional matrix (Matrix A has ' + size1.length + ' dimensions)')
}
}
// Matrix dimension validation is now in multiplyDimensionValidation.js
// for better cohesion and single responsibility.
// Inline calls replaced with validateMatrixMultiplicationDimensions()

/**
* C = A * B
Expand Down Expand Up @@ -798,7 +753,7 @@ export const createMultiply = /* #__PURE__ */ factory(name, dependencies, ({ typ

'Array, Array': typed.referTo('Matrix, Matrix', selfMM => (x, y) => {
// check dimensions
_validateMatrixDimensions(arraySize(x), arraySize(y))
validateMatrixMultiplicationDimensions(arraySize(x), arraySize(y))

// use dense matrix implementation
const m = selfMM(matrix(x), matrix(y))
Expand All @@ -812,7 +767,7 @@ export const createMultiply = /* #__PURE__ */ factory(name, dependencies, ({ typ
const ysize = y.size()

// check dimensions
_validateMatrixDimensions(xsize, ysize)
validateMatrixMultiplicationDimensions(xsize, ysize)

// process dimensions
if (xsize.length === 1) {
Expand Down
94 changes: 94 additions & 0 deletions src/function/arithmetic/multiplyDimensionValidation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// multiplyDimensionValidation.js — Extracted matrix dimension validation for multiply
//
// Decomposed from multiply.js (882 lines) to improve cohesion and single responsibility.
// This module validates that two matrix operands have compatible dimensions
// for multiplication (dot product, matrix-vector, vector-matrix, or matrix-matrix).

/**
* Validate that two matrix operands have compatible dimensions for multiplication.
*
* Supported combinations:
* - Vector (N) × Vector (N) → requires same length
* - Vector (M) × Matrix (M×N) → vector length must match matrix rows
* - Matrix (M×N) × Vector (N) → matrix columns must match vector length
* - Matrix (M×K) × Matrix (K×N) → A columns must match B rows
*
* @param {number[]} size1 - Shape of the first operand (1D or 2D)
* @param {number[]} size2 - Shape of the second operand (1D or 2D)
* @throws {RangeError} If dimensions are incompatible
* @throws {Error} If either operand has more than 2 dimensions
*/
export function validateMatrixMultiplicationDimensions(size1, size2) {
switch (size1.length) {
case 1:
validateLeftVectorDimensions(size1, size2)
break
case 2:
validateLeftMatrixDimensions(size1, size2)
break
default:
throw new Error(
`Can only multiply a 1 or 2 dimensional matrix (Matrix A has ${size1.length} dimensions)`
)
}
}

/**
* Validate dimensions when the left operand is a vector (1D).
* @param {number[]} vectorSize - Shape of the vector [N]
* @param {number[]} rightSize - Shape of the right operand
*/
function validateLeftVectorDimensions(vectorSize, rightSize) {
switch (rightSize.length) {
case 1:
// Vector × Vector: lengths must match
if (vectorSize[0] !== rightSize[0]) {
throw new RangeError(
'Dimension mismatch in multiplication. Vectors must have the same length'
)
}
break
case 2:
// Vector × Matrix: vector length must match matrix rows
if (vectorSize[0] !== rightSize[0]) {
throw new RangeError(
`Dimension mismatch in multiplication. Vector length (${vectorSize[0]}) must match Matrix rows (${rightSize[0]})`
)
}
break
default:
throw new Error(
`Can only multiply a 1 or 2 dimensional matrix (Matrix B has ${rightSize.length} dimensions)`
)
}
}

/**
* Validate dimensions when the left operand is a matrix (2D).
* @param {number[]} matrixSize - Shape of the matrix [M, K]
* @param {number[]} rightSize - Shape of the right operand
*/
function validateLeftMatrixDimensions(matrixSize, rightSize) {
switch (rightSize.length) {
case 1:
// Matrix × Vector: matrix columns must match vector length
if (matrixSize[1] !== rightSize[0]) {
throw new RangeError(
`Dimension mismatch in multiplication. Matrix columns (${matrixSize[1]}) must match Vector length (${rightSize[0]})`
)
}
break
case 2:
// Matrix × Matrix: A columns must match B rows
if (matrixSize[1] !== rightSize[0]) {
throw new RangeError(
`Dimension mismatch in multiplication. Matrix A columns (${matrixSize[1]}) must match Matrix B rows (${rightSize[0]})`
)
}
break
default:
throw new Error(
`Can only multiply a 1 or 2 dimensional matrix (Matrix B has ${rightSize.length} dimensions)`
)
}
}
17 changes: 2 additions & 15 deletions src/function/arithmetic/subtractScalar.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { factory } from '../../utils/factory.js'
import { subtractNumber } from '../../plain/number/index.js'
import { createUnitScalarHandler } from './unitArithmeticHelper.js'

const name = 'subtractScalar'
const dependencies = ['typed']
Expand Down Expand Up @@ -37,20 +38,6 @@ export const createSubtractScalar = /* #__PURE__ */ factory(name, dependencies,
return x.sub(y)
},

'Unit, Unit': typed.referToSelf(self => (x, y) => {
if (x.value === null || x.value === undefined) {
throw new Error('Parameter x contains a unit with undefined value')
}
if (y.value === null || y.value === undefined) {
throw new Error('Parameter y contains a unit with undefined value')
}
if (!x.equalBase(y)) throw new Error('Units do not match')

const res = x.clone()
res.value =
typed.find(self, [res.valueType(), y.valueType()])(res.value, y.value)
res.fixPrefix = false
return res
})
'Unit, Unit': typed.referToSelf(self => createUnitScalarHandler(self, typed))
})
})
56 changes: 56 additions & 0 deletions src/function/arithmetic/unitArithmeticHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// unitArithmeticHelper.js — Shared logic for scalar arithmetic operations on Units
//
// Both addScalar and subtractScalar have identical Unit handling:
// - Validate both units have values
// - Check that units have the same base
// - Clone the first operand, compute new value via typed dispatch
// - Reset fixPrefix
//
// This module extracts that shared pattern to reduce duplication.
// Future scalar operations (e.g., modScalar) should reuse this helper.

/**
* Create a Unit handler for a scalar binary operation.
* Returns a typed.referToSelf callback that validates units and computes
* the result by delegating to the typed system for the value types.
*
* @param {function} self - The scalar function itself (via referToSelf)
* @param {function} typed - The typed function system
* @returns {function} Handler for 'Unit, Unit' signature
*/
export function createUnitScalarHandler(self, typed) {
return (x, y) => {
validateUnitOperand(x, 'x')
validateUnitOperand(y, 'y')
validateMatchingUnitBases(x, y)

const result = x.clone()
result.value = typed.find(self, [result.valueType(), y.valueType()])(result.value, y.value)
result.fixPrefix = false
return result
}
}

/**
* Validate that a Unit operand has a defined numeric value.
* @param {Unit} unit - The unit to validate
* @param {string} paramName - Name of the parameter for error messages ('x' or 'y')
* @throws {Error} If the unit's value is null or undefined
*/
function validateUnitOperand(unit, paramName) {
if (unit.value === null || unit.value === undefined) {
throw new Error(`Parameter ${paramName} contains a unit with undefined value`)
}
}

/**
* Validate that two Units have compatible bases for arithmetic.
* @param {Unit} x - First unit
* @param {Unit} y - Second unit
* @throws {Error} If the units have different bases
*/
function validateMatchingUnitBases(x, y) {
if (!x.equalBase(y)) {
throw new Error('Units do not match')
}
}