@numericelements/knot-sequence
Version:
A library for generating and manipulating knot sequences for b-spline curves and surfaces
746 lines (742 loc) • 39.7 kB
JavaScript
import { WarningLog } from './errorProcessing/ErrorLoging.js';
import { AbstractKnotSequence } from './AbstractKnotSequence.js';
import { Knot } from './Knot.js';
import { NO_KNOT_OPEN_CURVE, NO_KNOT_CLOSED_CURVE, UNIFORM_OPENKNOTSEQUENCE, UNIFORMLYSPREADINTERKNOTS_OPENKNOTSEQUENCE } from './KnotSequenceConstructorInterface.js';
import { UPPER_BOUND_NORMALIZED_BASIS_DEFAULT_ABSCISSA, NormalizedBasisAtSequenceExtremity, KNOT_COINCIDENCE_TOLERANCE, KNOT_SEQUENCE_ORIGIN } from './namedConstants/KnotSequences.js';
import { EM_KNOT_MULTIPLICITIES_AT_NORMALIZED_BASIS_BOUNDS_DIFFER, EM_ABSCISSA_AND_INDEX_ORIGIN_KNOT_SEQUENCE_INCONSISTENT, EM_ABSCISSA_TOO_CLOSE_TO_KNOT, EM_KNOT_INSERTION_OVER_UMAX, EM_KNOT_INSERTION_UNDER_SEQORIGIN, EM_MULTIPLICITY_ORDER_MODIFYING_NORMALIZED_BASIS, EM_MAXMULTIPLICITY_ORDER_ATKNOT, EM_CUMULATIVE_KNOTMULTIPLICITY_ATSTART, EM_CUMULATIVE_KNOTMULTIPLICITY_ATEND, EM_NORMALIZED_BASIS_INTERVAL_NOTSUFFICIENT } from './ErrorMessages/KnotSequences.js';
import { WM_ABSCISSA_NOT_FOUND_IN_SEQUENCE } from './WarningMessages/KnotSequences.js';
import { KnotIndexStrictlyIncreasingSequence } from './KnotIndexStrictlyIncreasingSequence.js';
import { KnotIndexIncreasingSequence } from './KnotIndexIncreasingSequence.js';
/**
* Abstract base class for open knot sequences used in B-spline curves or surfaces.
*
* @description
* Extends AbstractKnotSequence to provide functionality specific to open knot sequences,
* including normalized basis bounds and knot insertion/removal operations.
* This class is applicable to open B-spline curves and surfaces as well as closed ones that can be described not with periodic knot sequences but also with open ones.
*
* @abstract
* @extends AbstractKnotSequence
* @example
* // Example knot sequence: [0,0,0,1,2,3,3,3]
* class ConcreteOpenKnotSequence extends AbstractOpenKnotSequence {
* constructor() {
* super(3, {type: UNIFORMLYSPREADINTERKNOTS_OPENKNOTSEQUENCE});
* }
* }
*/
class AbstractOpenKnotSequence extends AbstractKnotSequence {
/**
* Creates a new open knot sequence with specified maximum multiplicity order.
*
* @param maxMultiplicityOrder - Maximum allowed multiplicity for any knot in the sequence
* @param knotParameters - Parameters defining the knot sequence. Their content depends on the AbstractOpenKnotSequence_type type
* that enables to specialize the constructor into a variety of categories defined by type property.
* @example
* // Create uniform open knot sequence
* const params = {
* type: UNIFORM_OPENKNOTSEQUENCE,
* BsplBasisSize: 5
* };
* new ConcreteOpenKnotSequence(3, params);
*/
constructor(maxMultiplicityOrder, knotParameters) {
super(maxMultiplicityOrder);
this.knotSequence = [];
this._uMax = UPPER_BOUND_NORMALIZED_BASIS_DEFAULT_ABSCISSA;
this._isKnotMultiplicityNonUniform = false;
if (knotParameters.type === NO_KNOT_OPEN_CURVE) {
this.computeKnotSequenceFromMaxMultiplicityOrderOCurve();
}
else if (knotParameters.type === NO_KNOT_CLOSED_CURVE) {
this.computeKnotSequenceFromMaxMultiplicityOrderCCurve();
}
else if (knotParameters.type === UNIFORM_OPENKNOTSEQUENCE) {
this.computeUniformKnotSequenceFromBsplBasisSize(knotParameters);
}
else if (knotParameters.type === UNIFORMLYSPREADINTERKNOTS_OPENKNOTSEQUENCE) {
this.computeNonUniformKnotSequenceFromBsplBasisSize(knotParameters);
}
}
/**
* Gets the knot abscissa of the right bound of the normalized basis of the knot sequence.
*/
get uMax() {
return this._uMax;
}
/**
* Indicates if knot multiplicity is non-uniform across the sequence
*
* @description
* When the knot multiplicity is maxMultiplicityOrder at both ends, the property is set to true.
*/
get isKnotMultiplicityNonUniform() {
return this._isKnotMultiplicityNonUniform;
}
/**
* Converts strictly increasing sequence index to an increasing sequence index.
*
* @param index - The knot index to convert as represented into the strictly increasing sequence used as reference.
* @returns The index into the corresponding increasing sequence.
* @throws {RangeError} if the index is out of range, i.e. either negative or greater than the strictly increasing knot sequence length.
*
* @description
* This index transformation is not unique. The convention followed here is the assignment of the first index
* of the increasing sequence where the abscissa at index (strictly increasing sequence) appears.
*
* @example
* // For sequence [0,0,0,1,2,3,3,3]
* // Convert index 2 (third unique knot) to index 4 (counting multiplicities)
* const strictIndex = new KnotIndexStrictlyIncreasingSequence(2);
* const incIndex = knotSequence.toKnotIndexIncreasingSequence(strictIndex);
*/
toKnotIndexIncreasingSequence(index) {
this.strictlyIncKnotIndexInputParamAssessment(index, "toKnotIndexIncreasingSequence");
let indexIncSeq = 0;
for (let i = 0; i < index.knotIndex; i++) {
indexIncSeq += this.knotSequence[i].multiplicity;
}
return new KnotIndexIncreasingSequence(indexIncSeq);
}
/**
* Gets the knot indices that bound the normalized basis of the knot sequence.
*
* @returns An object containing the start and end knot indices with their respective basis normalization states
* @property {start} Contains the knot index as KnotIndexStrictlyIncreasingSequence and basis state at sequence start
* @property {end} Contains the knot index as KnotIndexStrictlyIncreasingSequence and basis state at sequence end
*
* @description
* This method determines the boundary knots of the normalized basis interval by analyzing
* the cumulative multiplicities at both ends of the sequence with respect to maxMultiplicityOrder assigned
* to the knot sequence. For each boundary:
* - Returns the knot index as a KnotIndexStrictlyIncreasingSequence. The knot index is always valid, i.e. >= 0 and < knotSequence.length.
* - Indicates the basis normalization state (StrictlyNormalized, NotNormalized, or OverDefined)
* When returning the normaalization state, the knot index value is not relevant.
*
* @example
* // For sequence [0,0,0,1,2,3,3,3] with maxMultiplicityOrder = 3
* const bounds = knotSequence.getKnotIndicesBoundingNormalizedBasis();
* // Returns: {
* // start: {knot: KnotIndexStrictlyIncreasingSequence{knotIndex: 0}, basisAtSeqExt: StrictlyNormalized},
* // end: {knot: KnotIndexStrictlyIncreasingSequence{knotIndex: 3}, basisAtSeqExt: StrictlyNormalized}
* // }
*
* // For sequence [-2,-1,0,1,2,3,3,4,4] with maxMultiplicityOrder = 3
* const bounds = knotSequence.getKnotIndicesBoundingNormalizedBasis();
* // Returns: {
* // start: {knot: KnotIndexStrictlyIncreasingSequence{knotIndex: 2}, basisAtSeqExt: StrictlyNormalized},
* // end: {knot: KnotIndexStrictlyIncreasingSequence{knotIndex: 5}, basisAtSeqExt: OverDefined}
* // }
*
* // For sequence [-1,0,1] with maxMultiplicityOrder = 4
* const bounds = knotSequence.getKnotIndicesBoundingNormalizedBasis();
* // Returns: {
* // start: {knot: KnotIndexStrictlyIncreasingSequence{knotIndex: 2}, basisAtSeqExt: NotNormalized},
* // end: {knot: KnotIndexStrictlyIncreasingSequence{knotIndex: 0}, basisAtSeqExt: NotNormalized}
* // }
*/
getKnotIndicesBoundingNormalizedBasis() {
const normalizedBasisAtStart = this.getKnotIndexNormalizedBasisAtSequenceStart();
const normalizedBasisAtEnd = this.getKnotIndexNormalizedBasisAtSequenceEnd();
return { start: normalizedBasisAtStart, end: normalizedBasisAtEnd };
}
/**
* Gets the knot index and basis normalization state at the end of the normalized basis interval.
*
* @returns An object containing the knot index as KnotIndexStrictlyIncreasingSequence and basis state at sequence end
* @property {knot} Contains the knot index as KnotIndexStrictlyIncreasingSequence
* @property {basisAtSeqExt} Contains the basis normalization state (StrictlyNormalized, NotNormalized, or OverDefined)
*
* @description
* This method determines the knot index and basis normalization state at the end of the normalized basis interval
* by analyzing the cumulative multiplicities starting from the last knot and heading toward the start of the sequence.
* The cumulative multiplicities are constrained by the maxMultiplicityOrder assigned
* to the knot sequence. The knot index is always valid, i.e. >= 0 and < knotSequence.length.
*
* @example
* // For sequence [0,0,0,1,2,3,3,3] with maxMultiplicityOrder = 3
* const bounds = knotSequence.getKnotIndexNormalizedBasisAtSequenceEnd();
* // Returns: {knot: KnotIndexStrictlyIncreasingSequence{knotIndex: 3}, basisAtSeqExt: StrictlyNormalized}
*
* // For sequence [0,0,0,1,2,3,3,4,4] with maxMultiplicityOrder = 3
* const bounds = knotSequence.getKnotIndexNormalizedBasisAtSequenceEnd();
* // Returns: {knot: KnotIndexStrictlyIncreasingSequence{knotIndex: 3}, basisAtSeqExt: OverDefined}
*
* // For sequence [-1,0,1] with maxMultiplicityOrder = 4
* const bounds = knotSequence.getKnotIndexNormalizedBasisAtSequenceEnd();
* // Returns: {knot: KnotIndexStrictlyIncreasingSequence{knotIndex: 0}, basisAtSeqExt: NotNormalized}
*/
getKnotIndexNormalizedBasisAtSequenceEnd() {
let cumulativeMultiplicity = this.knotSequence[this.knotSequence.length - 1].multiplicity;
let index = this.knotSequence.length - 1;
let basisAtSeqEnd = NormalizedBasisAtSequenceExtremity.NotNormalized;
while (cumulativeMultiplicity < this._maxMultiplicityOrder && index > 0) {
index--;
cumulativeMultiplicity = cumulativeMultiplicity + this.knotSequence[index].multiplicity;
}
if (cumulativeMultiplicity > this._maxMultiplicityOrder) {
basisAtSeqEnd = NormalizedBasisAtSequenceExtremity.OverDefined;
}
else if (cumulativeMultiplicity === this._maxMultiplicityOrder) {
basisAtSeqEnd = NormalizedBasisAtSequenceExtremity.StrictlyNormalized;
}
return { knot: new KnotIndexStrictlyIncreasingSequence(index), basisAtSeqExt: basisAtSeqEnd };
}
/**
* Gets the knot index and basis normalization state at the start of the normalized basis interval.
*
* @returns An object containing the knot index as KnotIndexStrictlyIncreasingSequence and basis state at sequence start
* @property {knot} Contains the knot index as KnotIndexStrictlyIncreasingSequence
* @property {basisAtSeqExt} Contains the basis normalization state (StrictlyNormalized, NotNormalized, or OverDefined)
*
* @description
* This method determines the knot index and basis normalization state at the start of the normalized basis interval
* by analyzing the cumulative multiplicities starting from the first knot and heading toward the end of the sequence.
* The cumulative multiplicities are constrained by the maxMultiplicityOrder assigned
* to the knot sequence. The knot index is always valid, i.e. >= 0 and < knotSequence.length.
*
* @example
* // For sequence [0,0,0,1,2,3,3,3] with maxMultiplicityOrder = 3
* const bounds = knotSequence.getKnotIndexNormalizedBasisAtSequenceStart();
* // Returns: {knot: KnotIndexStrictlyIncreasingSequence{knotIndex: 0}, basisAtSeqExt: StrictlyNormalized}
*
* // For sequence [0,1,1,1,2,3,3,3] with maxMultiplicityOrder = 3
* const bounds = knotSequence.getKnotIndexNormalizedBasisAtSequenceStart();
* // Returns: {knot: KnotIndexStrictlyIncreasingSequence{knotIndex: 1}, basisAtSeqExt: OverDefined}
*
* // For sequence [-1,0,1] with maxMultiplicityOrder = 4
* const bounds = knotSequence.getKnotIndexNormalizedBasisAtSequenceStart();
* // Returns: {knot: KnotIndexStrictlyIncreasingSequence{knotIndex: 2}, basisAtSeqExt: NotNormalized}
*/
getKnotIndexNormalizedBasisAtSequenceStart() {
let cumulativeMultiplicity = this.knotSequence[0].multiplicity;
let index = 0;
let basisAtSeqStart = NormalizedBasisAtSequenceExtremity.NotNormalized;
while (cumulativeMultiplicity < this._maxMultiplicityOrder && index < (this.knotSequence.length - 1)) {
index++;
cumulativeMultiplicity = cumulativeMultiplicity + this.knotSequence[index].multiplicity;
}
if (cumulativeMultiplicity > this._maxMultiplicityOrder) {
basisAtSeqStart = NormalizedBasisAtSequenceExtremity.OverDefined;
}
else if (cumulativeMultiplicity === this._maxMultiplicityOrder) {
basisAtSeqStart = NormalizedBasisAtSequenceExtremity.StrictlyNormalized;
}
return { knot: new KnotIndexStrictlyIncreasingSequence(index), basisAtSeqExt: basisAtSeqStart };
}
/**
* Validates that knot multiplicities at normalized basis boundaries are equal.
*
* @description
* Compares the multiplicities of knots at the left and right boundaries of the
* normalized basis interval. This check ensures the B-spline basis maintains
* consistent behavior at both ends of the interval. This consition is particularly important for
* open knot sequences describing closed curves.
*
* The method:
* 1. Gets the indices of knots bounding the normalized basis
* 2. Compares their multiplicities
* 3. Throws an error if they differ
*
* @throws {RangeError} If the multiplicities at the normalized basis boundaries differ
*
* @example
* // Valid sequence [0,0,0,1,2,3,3,3] with maxMultiplicityOrder = 3
* knotSequence.checkKnotMultiplicitiesAtNormalizedBasisBoundaries();
* // Passes validation (both ends have multiplicity 3)
*
* @example
* // Invalid sequence [0,0,0,1,2,3,3] with maxMultiplicityOrder = 3
* knotSequence.checkKnotMultiplicitiesAtNormalizedBasisBoundaries();
* // Throws RangeError: multiplicities at normalized basis bounds differ (multiplicity at left bound: 3, multiplicity at right bound: 1)
*/
checkKnotMultiplicitiesAtNormalizedBasisBoundaries() {
const indexLeftBoundBasis = this.getKnotIndexNormalizedBasisAtSequenceStart().knot.knotIndex;
const indexRightBoundBasis = this.getKnotIndexNormalizedBasisAtSequenceEnd().knot.knotIndex;
if (this.knotSequence[indexLeftBoundBasis].multiplicity !== this.knotSequence[indexRightBoundBasis].multiplicity) {
this.throwRangeErrorMessage("checkKnotMultiplicitiesAtNormalizedBasisBoundaries", EM_KNOT_MULTIPLICITIES_AT_NORMALIZED_BASIS_BOUNDS_DIFFER);
}
}
checkNormalizedBasisOrigin(normalizedBasisAtStart) {
if (normalizedBasisAtStart.knot.knotIndex !== this._indexKnotOrigin.knotIndex) {
this.throwRangeErrorMessage("checkNormalizedBasisOrigin", EM_ABSCISSA_AND_INDEX_ORIGIN_KNOT_SEQUENCE_INCONSISTENT);
}
}
/**
* Computes a minimal knot sequence for an open curve.
*
* @description
* Creates a minimal knot sequence consisting of two knots:
* - First knot at 0 with maxMultiplicityOrder
* - Second knot at 1 with maxMultiplicityOrder.
* This method is associated with the constructor category NO_KNOT_OPEN_CURVE.
*
* This configuration represents the simplest possible open curve B-spline,
* where the basis functions are defined over [0,1].
*
* The method:
* 1. Validates maxMultiplicityOrder is at least 1
* 2. Creates knots at 0 and 1 with maxMultiplicityOrder
* 3. Sets uMax to the last knot abscissa (1)
*
* @throws {RangeError} If maxMultiplicityOrder is less than 1
*
* @example
* // For maxMultiplicityOrder = 3
* knotSequence.computeKnotSequenceFromMaxMultiplicityOrderOCurve();
* // Results in knot sequence [0,0,0,1,1,1]
*
* @example
* // For maxMultiplicityOrder = 2
* knotSequence.computeKnotSequenceFromMaxMultiplicityOrderOCurve();
* // Results in knot sequence [0,0,1,1]
*/
computeKnotSequenceFromMaxMultiplicityOrderOCurve() {
const minValueMaxMultiplicityOrder = 2;
this.constructorInputMultOrderAssessment(minValueMaxMultiplicityOrder);
this.knotSequence.push(new Knot(0, this._maxMultiplicityOrder));
this.knotSequence.push(new Knot(1, this._maxMultiplicityOrder));
this._uMax = this.knotSequence[this.knotSequence.length - 1].abscissa;
}
/**
* Computes a minimal open knot sequence for a closed curve.
*
* @description
* Creates a minimal uniform knot sequence consisting of:
* - maxMultiplicityOrder knots up to the KNOT_SEQUENCE_ORIGIN (0) where starts the normalized basis,
* - (maxMultiplicityOrder - 1) knots uniformly spaced that describe the normalized basis: uMax = maxMultiplicityOrder - 1,
* - (maxMultiplicityOrder - 1) knots up to the last knot abscissa.
* This method is associated with the constructor category NO_KNOT_CLOSED_CURVE.
*
* This configuration represents the simplest possible closed curve B-spline,
* where the basis functions are defined over [0,1] and the curve is closed.
*
* The method:
* 1. Validates maxMultiplicityOrder is at least 2
* 2. Creates a uniform knot sequence with normalized basis origin KNOT_SEQUENCE_ORIGIN (0) with maxMultiplicityOrder
* 3. Sets uMax to the knot abscissa (maxMultiplicityOrder - 1) except for maxMultiplicityOrder = 2 where uMax = 2 to
* produce enough control vertices for the curve to be closed minimaly.
*
* @throws {RangeError} If maxMultiplicityOrder is less than 2
*
* @example
* // For maxMultiplicityOrder = 3
* knotSequence.computeKnotSequenceFromMaxMultiplicityOrderCCurve();
* // Results in knot sequence [-2,-1,0,1,2,3,4]
* // where the normalized basis interval is [0,2]
*
* @example
* // For maxMultiplicityOrder = 2
* knotSequence.computeKnotSequenceFromMaxMultiplicityOrderCCurve();
* // Results in knot sequence [-1,0,1,2,3]
* // where the normalized basis interval is [0,2]
*/
computeKnotSequenceFromMaxMultiplicityOrderCCurve() {
const minValueMaxMultiplicityOrder = 2;
this.constructorInputMultOrderAssessment(minValueMaxMultiplicityOrder);
let upperBound = 2 * this._maxMultiplicityOrder - 1;
if (this._maxMultiplicityOrder === 2)
upperBound = 2 * this._maxMultiplicityOrder;
for (let i = -(this._maxMultiplicityOrder - 1); i < upperBound; i++) {
this.knotSequence.push(new Knot(i, 1));
}
this._uMax = this._maxMultiplicityOrder - 1;
if (this._maxMultiplicityOrder === 2)
this._uMax = 2;
}
/**
* Computes a uniform open knot sequence for a given B-spline basis size.
*
* @description
* Creates a uniform knot sequence with uniformly spaced knots where:
* - Knots start at -(maxMultiplicityOrder-1) to produce a normalized basis origin at KNOT_SEQUENCE_ORIGIN (0)
* - Each knot has multiplicity 1
* - Knots are spaced at unit intervals
* - Sequence extends to accommodate the specified basis size after uMax set to (BsplBasisSize - 1).
*
* The resulting sequence supports uniform B-spline basis functions
* with consistent spacing and behavior across the domain that can be used
* for open as well as closed curves.
*
* @param knotParameters - Parameters defining the knot sequence with constructor type UNIFORM_OPENKNOTSEQUENCE
* @param knotParameters.BsplBasisSize - Size of the B-spline basis
* @throws {RangeError} If maxMultiplicityOrder is less than 2
* @throws {RangeError} If BsplBasisSize is does not enable the generation of a consistent normalized basis
*
* @example
* // For maxMultiplicityOrder = 3 and BsplBasisSize = 5
* const params = {
* type: UNIFORM_OPENKNOTSEQUENCE,
* BsplBasisSize: 5
* };
* knotSequence.computeUniformKnotSequenceFromBsplBasisSize(params);
* // Results in [-2,-1,0,1,2,3,4,5,6]
*
* @example
* // For maxMultiplicityOrder = 2 and BsplBasisSize = 4
* const params = {
* type: UNIFORM_OPENKNOTSEQUENCE,
* BsplBasisSize: 4
* };
* knotSequence.computeUniformKnotSequenceFromBsplBasisSize(params);
* // Results in [-1,0,1,2,3,4]
*/
computeUniformKnotSequenceFromBsplBasisSize(knotParameters) {
const minValueMaxMultiplicityOrder = 2;
this.constructorInputMultOrderAssessment(minValueMaxMultiplicityOrder);
this.constructorInputBspBasisSizeAssessment(knotParameters);
for (let i = -(this._maxMultiplicityOrder - 1); i < (knotParameters.BsplBasisSize + this._maxMultiplicityOrder - 1); i++) {
this.knotSequence.push(new Knot(i, 1));
}
this._uMax = knotParameters.BsplBasisSize - 1;
}
/**
* Computes a non-uniform open knot sequence for a given B-spline basis size of an open curve.
*
* @description
* Creates a non-uniform knot sequence with knots at abscissas specified by the user.
* The resulting sequence supports non-uniform B-spline basis functions with multiplicity maxMultiplicityOrder at both extremities
* and a uniform spacing between of the intermediate knots.
* Defines the uMax of the basis as: BsplBasisSize - maxMultiplicityOrder + 1.
*
* @param knotParameters - Parameters defining the knot sequence with constructor type NON_UNIFORM_OPENKNOTSEQUENCE
* @param knotParameters.BsplBasisSize - Size of the B-spline basis
* @throws {RangeError} If maxMultiplicityOrder is less than 2
* @throws {RangeError} If BsplBasisSize is does not enable the generation of a consistent normalized basis
*
* @example
* // For maxMultiplicityOrder = 3 and BsplBasisSize = 5
* const params = {
* type: NON_UNIFORM_OPENKNOTSEQUENCE,
* BsplBasisSize: 5
* };
* knotSequence.computeNonUniformKnotSequenceFromBsplBasisSize(params);
* // Results in [0,0,0,1,2,3,3,3]
*
* @example
* // For maxMultiplicityOrder = 2 and BsplBasisSize = 4
* const params = {
* type: NON_UNIFORM_OPENKNOTSEQUENCE,
* BsplBasisSize: 4
* };
* knotSequence.computeNonUniformKnotSequenceFromBsplBasisSize(params);
* // Results in [0,0,1,2,3,3]
*/
computeNonUniformKnotSequenceFromBsplBasisSize(knotParameters) {
const minValueMaxMultiplicityOrder = 2;
this.constructorInputMultOrderAssessment(minValueMaxMultiplicityOrder);
this.constructorInputBspBasisSizeAssessment(knotParameters);
this.knotSequence.push(new Knot(0, this._maxMultiplicityOrder));
for (let i = 0; i < knotParameters.BsplBasisSize - this._maxMultiplicityOrder; i++) {
this.knotSequence.push(new Knot((i + 1), 1));
}
this.knotSequence.push(new Knot((knotParameters.BsplBasisSize - this._maxMultiplicityOrder + 1), this._maxMultiplicityOrder));
this._uMax = knotParameters.BsplBasisSize - this._maxMultiplicityOrder + 1;
}
/**
* Gets multiplicity of the knot located at specified abscissa.
*
* @param abscissa - abscissa of the knot to get multiplicity of
* @returns {number} Multiplicity of the knot at the specified abscissa.
* @throws warning message if abscissa is not found in the sequence and retruns a multiplicity order of 0.
*
* @description
* There is no error throw if the abscissa is below the abscissa of the first knot
* or greater than the abscissa of the last knot since the value returned is 0 in all cases.
*
* @example
* // For sequence [0,0,0,1,2,3,3,3]
* const mult = knotSequence.knotMultiplicityAtAbscissa(0); // Returns 3
*/
knotMultiplicityAtAbscissa(abscissa) {
let multiplicity = 0;
for (const knot of this.knotSequence) {
if (Math.abs(abscissa - knot.abscissa) < KNOT_COINCIDENCE_TOLERANCE) {
multiplicity = knot.multiplicity;
}
}
if (multiplicity === 0) {
const warning = new WarningLog(this.constructor.name, "knotMultiplicityAtAbscissa", WM_ABSCISSA_NOT_FOUND_IN_SEQUENCE);
warning.logMessage();
}
return multiplicity;
}
/**
* Inserts a new knot into the knot sequence.
*
* @param abscissa - Abscissa value for new knot. Must be within [KNOT_SEQUENCE_ORIGIN, uMax]
* @param multiplicity - Multiplicity of new knot. Must not exceed maxMultiplicityOrder. Defaults to 1
* @returns {boolean} True if insertion successful, false if abscissa coincides with existing knot
*
* @throws warning message if abscissa is too close to an existing knot.
* @throws {RangeError} if the abscissa is smaller than KNOT_SEQUENCE_ORIGIN
* @throws {RangeError} if the abscissa is greater than uMax
* @throws {RangeError} if the multiplicity is greater than maxMultiplicityOrder
*
* @description
* The multiplicity of the inserted knot defaults to 1 and is bounded by maxMultiplicityOrder.
* The new knot abscissa must be distinct from all other knots in the sequence. If not a warning message is issued and the method returns false.
* The knot insertion process incorporates the knot increasing or knot strictly increasing constraint depending on the knot sequence type.
* The uniformity of knot spacing, uniformity of knot multiplicity, and non uniformity of knot multiplicity at normalized basis boundaries are checked.
*
* @example
* // Insert knot with multiplicity 2 at x=1.5
* knotSequence.insertKnotMutSeq(1.5, 2);
*/
insertKnotMutSeq(abscissa, multiplicity = 1) {
if (this.isAbscissaCoincidingWithKnot(abscissa)) {
this.throwRangeErrorMessage("insertKnotMutSeq", EM_ABSCISSA_TOO_CLOSE_TO_KNOT);
}
else if (abscissa > this._uMax) {
this.throwRangeErrorMessage("insertKnotMutSeq", EM_KNOT_INSERTION_OVER_UMAX);
}
else if (abscissa < KNOT_SEQUENCE_ORIGIN) {
this.throwRangeErrorMessage("insertKnotMutSeq", EM_KNOT_INSERTION_UNDER_SEQORIGIN);
}
this.maxMultiplicityOrderInputParamAssessment(multiplicity, "insertKnotMutSeq");
const knot = new Knot(abscissa, multiplicity);
let i = 0;
while (i < (this.knotSequence.length - 1)) {
if (this.knotSequence[i].abscissa < abscissa && abscissa < this.knotSequence[i + 1].abscissa)
break;
i++;
}
// if(i === (this.knotSequence.length - 1)) {
// this.knotSequence.push(knot);
// } else {
this.knotSequence.splice((i + 1), 0, knot);
// }
this.checkUniformityOfKnotSpacing();
this.checkUniformityOfKnotMultiplicity();
this.checkNonUniformKnotMultiplicityOrder();
}
insertKnotAbscissaArrayMutSeq(abscissae, multiplicity = 1) {
for (const abscissa of abscissae) {
this.insertKnotMutSeq(abscissa, multiplicity);
}
}
/**
* Removes a knot from the knot sequence at the specified index.
*
* @param index - The index of the knot to remove, represented as a KnotIndexStrictlyIncreasingSequence object.
* @throws {RangeError} If the knot index is out of bounds.
*
* @description
* This method removes a knot from the knot sequence at the specified index, whatever its multiplicity order. It updates the internal strictly increasing knot sequence array by:
* 1. Retrieving the distinct abscissae and multiplicities of the current knot sequence.
* 2. Removing the knot at the specified index from the abscissae and multiplicities arrays.
* 3. Rebuilding the knot sequence array with the updated abscissae and multiplicities.
*/
removeKnot(index) {
this.strictlyIncKnotIndexInputParamAssessment(index, "removeKnot");
const abscissae = this.distinctAbscissae();
const multiplicities = this.multiplicities();
abscissae.splice(index.knotIndex, 1);
multiplicities.splice(index.knotIndex, 1);
this.knotSequence = [];
let i = 0;
for (const abscissa of abscissae) {
const knot = new Knot(abscissa, multiplicities[i]);
this.knotSequence.push(knot);
i++;
}
}
/**
* Raises the multiplicity of a knot at the specified index.
*
* @param index - The index of the knot to modify, represented as a KnotIndexStrictlyIncreasingSequence object.
* @param multiplicity - The amount to increase the knot's multiplicity. Defaults to 1.
* @param checkSequenceConsistency - Whether to perform additional consistency checks. Defaults to true.
*
* @throws {RangeError} If the knot index is out of bounds or if the new multiplicity violates sequence constraints.
*
* @description
* This method increases the multiplicity of a specified knot in the knot sequence.
*
* The method includes several checks to maintain the mathematical validity of the knot sequence:
* - Ensures the modified knot is not at the sequence boundaries
* - Verifies that the new multiplicity doesn't exceed allowed maximums
* - Checks for uniformity and allowed non-uniform orders in the sequence
*
* @example
* // Increase the multiplicity of the third knot (index 2) by 1
* const index = { knotIndex: 2 };
* this.raiseKnotMultiplicityMutSeq(index);
*
* // Increase the multiplicity of the fourth knot (index 3) by 2
* const index2 = { knotIndex: 3 };
* this.raiseKnotMultiplicityMutSeq(index2, 2);
*/
raiseKnotMultiplicityMutSeq(index, multiplicity = 1, checkSequenceConsistency = true) {
this.strictlyIncKnotIndexInputParamAssessment(index, "raiseKnotMultiplicityMutSeq");
this.knotSequence[index.knotIndex].multiplicity += multiplicity;
if (checkSequenceConsistency || (!checkSequenceConsistency && !this._isSequenceUpToC0Discontinuity)) {
const basisAtEnd = this.getKnotIndexNormalizedBasisAtSequenceEnd();
if (index.knotIndex <= this._indexKnotOrigin.knotIndex || index.knotIndex >= basisAtEnd.knot.knotIndex) {
this.throwRangeErrorMessage('raiseKnotMultiplicityMutSeq', EM_MULTIPLICITY_ORDER_MODIFYING_NORMALIZED_BASIS);
}
if (!this._isSequenceUpToC0Discontinuity) {
this.checkMaxKnotMultiplicityAtIntermediateKnots();
}
else if (this.knotSequence[index.knotIndex].multiplicity > this._maxMultiplicityOrder) {
this.throwRangeErrorMessage('raiseKnotMultiplicityMutSeq', EM_MAXMULTIPLICITY_ORDER_ATKNOT);
}
}
this.checkUniformityOfKnotMultiplicity();
this.checkNonUniformKnotMultiplicityOrder();
}
raiseKnotMultiplicityKnotArrayMutSeq(arrayIndices, multiplicity = 1, checkSequenceConsistency = true) {
for (const index of arrayIndices) {
this.raiseKnotMultiplicityMutSeq(index, multiplicity, checkSequenceConsistency);
}
}
/**
* Decrements the multiplicity of a knot at the specified index.
*
* @param index - The index of the knot to modify, represented as a KnotIndexStrictlyIncreasingSequence object.
* @param checkSequenceConsistency - Whether to perform additional consistency checks. Defaults to true.
*
* @throws {RangeError} If the knot index is out of bounds
* @throws {RangeError} if the new multiplicity violates sequence constraints regarding the interval of the normalized basis.
*
* @description
* This method decreases the multiplicity of a specified knot in the knot sequence.
* If checkSequenceConsistency is true, it performs additional checks to ensure the sequence remains consistent, particularly the normalized basis setting
*
* The method includes several checks to maintain the mathematical validity of the knot sequence:
* - Ensures the modified knot is not at the sequence boundaries
* - Removes the knot if its multiplicity becomes 1, except for the sequence origin
* - Updates the knot sequence properties (uniform knot spacing, uniform knot multiplicity, non uniform knot multiplicity) accordingly
*
* @example
* // For sequence [0,0,0,1,1,2,3,3,3]
* const index = new KnotIndexStrictlyIncreasingSequence(2);
* knotSequence.decrementKnotMultiplicityMutSeq(index); // Results in [0,0,0,1,2,3,3,3]
* // For sequence [0,0,0,1,2,3,3,3]
* const index = new KnotIndexStrictlyIncreasingSequence(2);
* knotSequence.decrementKnotMultiplicityMutSeq(index); // Results in [0,0,0,2,3,3,3]
*
* // Decrement without consistency check
* // For sequence [0,0,0,1,2,3,3,3]
* const index = new KnotIndexStrictlyIncreasingSequence(0);
* knotSequence.decrementKnotMultiplicityMutSeq(index, false); // Results in [0,0,1,2,3,3,3], the normalized basis interval is modified
*/
decrementKnotMultiplicityMutSeq(index, checkSequenceConsistency = true) {
this.strictlyIncKnotIndexInputParamAssessment(index, "decrementKnotMultiplicityMutSeq");
if (checkSequenceConsistency) {
const basisAtEnd = this.getKnotIndexNormalizedBasisAtSequenceEnd();
if (index.knotIndex <= this._indexKnotOrigin.knotIndex || index.knotIndex >= basisAtEnd.knot.knotIndex) {
this.throwRangeErrorMessage('decrementKnotMultiplicityMutSeq', EM_MULTIPLICITY_ORDER_MODIFYING_NORMALIZED_BASIS);
}
}
if (this.knotSequence[index.knotIndex].multiplicity === 1) {
this.removeKnot(index);
}
else {
this.knotSequence[index.knotIndex].decrementMultiplicity();
}
this.checkUniformityOfKnotSpacing();
this.checkUniformityOfKnotMultiplicity();
this.checkNonUniformKnotMultiplicityOrder();
}
decrementKnotMultiplicityKnotArrayMutSeq(arrayIndices, checkSequenceConsistency = true) {
for (const index of arrayIndices) {
this.decrementKnotMultiplicityMutSeq(index, checkSequenceConsistency);
}
}
/**
* Updates and validates knot sequence through normalized basis analysis.
*
* @throws {RangeError} If sequence start is not properly normalized
* @throws {RangeError} If sequence end is not properly normalized
* @throws {RangeError} If normalized basis interval is insufficient
*
* @description
*
* - Updates maxMultiplicityOrder if needed based on knot multiplicities
* - Validates normalization at sequence boundaries
* - Ensures proper sequence origin position
* - Sets correct uMax value
*
* @example
* // For sequence [0,0,0,1,2,3,3,3] with maxMultiplicityOrder = 3
* knotSequence.updateKnotSequenceThroughNormalizedBasisAnalysisMutSeq();
* // Validates normalization and updates sequence properties
*
* // For invalid sequence [0,0,1,2,3,3,3]
* knotSequence.updateKnotSequenceThroughNormalizedBasisAnalysisMutSeq();
* // Throws error: Not normalized at sequence start
*/
updateKnotSequenceThroughNormalizedBasisAnalysisMutSeq() {
for (let i = 0; i < this.knotSequence.length; i++) {
const knot = this.knotSequence[i];
if (knot.multiplicity > this._maxMultiplicityOrder) {
this._maxMultiplicityOrder = knot.multiplicity;
}
}
const indices = this.getKnotIndicesBoundingNormalizedBasis();
if (indices.start.basisAtSeqExt === NormalizedBasisAtSequenceExtremity.StrictlyNormalized) {
this._indexKnotOrigin = indices.start.knot;
if (this.knotSequence[this._indexKnotOrigin.knotIndex].abscissa !== KNOT_SEQUENCE_ORIGIN) {
this.resetKnotAbscissaeToOrigin();
}
this._uMax = this.knotSequence[indices.end.knot.knotIndex].abscissa;
}
else if (indices.start.basisAtSeqExt === NormalizedBasisAtSequenceExtremity.OverDefined) {
this.throwRangeErrorMessage("updateKnotSequenceThroughNormalizedBasisAnalysis", EM_CUMULATIVE_KNOTMULTIPLICITY_ATSTART);
}
// else if(indices.start.basisAtSeqExt === NormalizedBasisAtSequenceExtremity.NotNormalized) {
// this.throwRangeErrorMessage("updateKnotSequenceThroughNormalizedBasisAnalysis",EM_NOT_NORMALIZED_BASIS)
// }
if (indices.end.basisAtSeqExt === NormalizedBasisAtSequenceExtremity.StrictlyNormalized) {
this._uMax = this.knotSequence[indices.end.knot.knotIndex].abscissa;
// } else if(indices.end.basisAtSeqExt === NormalizedBasisAtSequenceExtremity.NotNormalized) {
// this.throwRangeErrorMessage("updateKnotSequenceThroughNormalizedBasisAnalysis", EM_NOT_NORMALIZED_BASIS);
}
else if (indices.end.basisAtSeqExt === NormalizedBasisAtSequenceExtremity.OverDefined) {
this.throwRangeErrorMessage("updateKnotSequenceThroughNormalizedBasisAnalysis", EM_CUMULATIVE_KNOTMULTIPLICITY_ATEND);
}
if (indices.end.knot.knotIndex <= indices.start.knot.knotIndex)
this.throwRangeErrorMessage("updateKnotSequenceThroughNormalizedBasisAnalysis", EM_NORMALIZED_BASIS_INTERVAL_NOTSUFFICIENT);
}
/**
* Resets all knot abscissae relative to the sequence origin.
*
* @description
* Translates all knot abscissae by subtracting the offset of the origin knot,
* effectively moving the sequence origin to KNOT_SEQUENCE_ORIGIN (0).
* Any resulting abscissa values that are within KNOT_COINCIDENCE_TOLERANCE of zero
* are set exactly to KNOT_SEQUENCE_ORIGIN.
*
* This method maintains the relative spacing between knots while ensuring
* the sequence starts at the standard origin position.
*
* @example
* // For sequence with knots at [2,2,2,3,4,5,5,5]
* knotSequence.resetKnotAbscissaeToOrigin();
* // Results in [0,0,0,1,2,3,3,3]
*
* @example
* // For sequence with knots at [1.001,1.001,1.001,2,3,4,4,4]
* // and KNOT_COINCIDENCE_TOLERANCE = 0.001
* knotSequence.resetKnotAbscissaeToOrigin();
* // Results in [0,0,0,1,2,3,3,3]
*/
resetKnotAbscissaeToOrigin() {
const offset = this.knotSequence[this._indexKnotOrigin.knotIndex].abscissa;
for (let i = 0; i < this.knotSequence.length; i++) {
let newAbscissa = this.knotSequence[i].abscissa - offset;
if (Math.abs(newAbscissa) < KNOT_COINCIDENCE_TOLERANCE) {
newAbscissa = KNOT_SEQUENCE_ORIGIN;
}
this.knotSequence[i].abscissa = newAbscissa;
}
}
revertKnotSpacing() {
super.revertKnotSpacing();
if (Math.abs(this.knotSequence[this._indexKnotOrigin.knotIndex].abscissa) > (KNOT_SEQUENCE_ORIGIN + KNOT_COINCIDENCE_TOLERANCE)) {
this.resetKnotAbscissaeToOrigin();
}
}
}
export { AbstractOpenKnotSequence };