@cavelang/solver 0.35.0 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2155 -3
- package/dist/src/adapter.d.ts +6 -0
- package/dist/src/adapter.d.ts.map +1 -1
- package/dist/src/adapter.js +3 -0
- package/dist/src/adapter.js.map +1 -1
- package/dist/src/balanced-reduce.d.ts +3 -0
- package/dist/src/balanced-reduce.d.ts.map +1 -0
- package/dist/src/balanced-reduce.js +10 -0
- package/dist/src/balanced-reduce.js.map +1 -0
- package/dist/src/canonical-owned.d.ts +5 -0
- package/dist/src/canonical-owned.d.ts.map +1 -0
- package/dist/src/canonical-owned.js +206 -0
- package/dist/src/canonical-owned.js.map +1 -0
- package/dist/src/canonical.d.ts +3 -2
- package/dist/src/canonical.d.ts.map +1 -1
- package/dist/src/canonical.js +12 -85
- package/dist/src/canonical.js.map +1 -1
- package/dist/src/capability.d.ts.map +1 -1
- package/dist/src/capability.js +43 -33
- package/dist/src/capability.js.map +1 -1
- package/dist/src/clone.d.ts +3 -0
- package/dist/src/clone.d.ts.map +1 -0
- package/dist/src/clone.js +54 -0
- package/dist/src/clone.js.map +1 -0
- package/dist/src/constant-sign.d.ts +5 -0
- package/dist/src/constant-sign.d.ts.map +1 -0
- package/dist/src/constant-sign.js +54 -0
- package/dist/src/constant-sign.js.map +1 -0
- package/dist/src/context.d.ts +6 -0
- package/dist/src/context.d.ts.map +1 -0
- package/dist/src/context.js +151 -0
- package/dist/src/context.js.map +1 -0
- package/dist/src/diagnostic-text.d.ts +3 -0
- package/dist/src/diagnostic-text.d.ts.map +1 -0
- package/dist/src/diagnostic-text.js +5 -0
- package/dist/src/diagnostic-text.js.map +1 -0
- package/dist/src/evaluate-expression.d.ts +4 -0
- package/dist/src/evaluate-expression.d.ts.map +1 -0
- package/dist/src/evaluate-expression.js +24 -0
- package/dist/src/evaluate-expression.js.map +1 -0
- package/dist/src/exact.d.ts +2 -0
- package/dist/src/exact.d.ts.map +1 -1
- package/dist/src/exact.js +125 -28
- package/dist/src/exact.js.map +1 -1
- package/dist/src/explain.d.ts +4 -0
- package/dist/src/explain.d.ts.map +1 -1
- package/dist/src/explain.js +330 -91
- package/dist/src/explain.js.map +1 -1
- package/dist/src/explanation-budget.d.ts +14 -0
- package/dist/src/explanation-budget.d.ts.map +1 -0
- package/dist/src/explanation-budget.js +46 -0
- package/dist/src/explanation-budget.js.map +1 -0
- package/dist/src/expression-key.d.ts +8 -0
- package/dist/src/expression-key.d.ts.map +1 -0
- package/dist/src/expression-key.js +54 -0
- package/dist/src/expression-key.js.map +1 -0
- package/dist/src/fraction-product.d.ts +3 -0
- package/dist/src/fraction-product.d.ts.map +1 -0
- package/dist/src/fraction-product.js +13 -0
- package/dist/src/fraction-product.js.map +1 -0
- package/dist/src/fraction-sum.d.ts +3 -0
- package/dist/src/fraction-sum.d.ts.map +1 -0
- package/dist/src/fraction-sum.js +23 -0
- package/dist/src/fraction-sum.js.map +1 -0
- package/dist/src/integer-gcd.d.ts +3 -0
- package/dist/src/integer-gcd.d.ts.map +1 -0
- package/dist/src/integer-gcd.js +50 -0
- package/dist/src/integer-gcd.js.map +1 -0
- package/dist/src/linear.d.ts +2 -1
- package/dist/src/linear.d.ts.map +1 -1
- package/dist/src/linear.js +85 -17
- package/dist/src/linear.js.map +1 -1
- package/dist/src/numeric-size.d.ts +2 -0
- package/dist/src/numeric-size.d.ts.map +1 -0
- package/dist/src/numeric-size.js +33 -0
- package/dist/src/numeric-size.js.map +1 -0
- package/dist/src/options.d.ts +4 -0
- package/dist/src/options.d.ts.map +1 -0
- package/dist/src/options.js +18 -0
- package/dist/src/options.js.map +1 -0
- package/dist/src/result.d.ts +4 -0
- package/dist/src/result.d.ts.map +1 -0
- package/dist/src/result.js +29 -0
- package/dist/src/result.js.map +1 -0
- package/dist/src/solve.d.ts.map +1 -1
- package/dist/src/solve.js +34 -3
- package/dist/src/solve.js.map +1 -1
- package/dist/src/validate.d.ts +5 -0
- package/dist/src/validate.d.ts.map +1 -1
- package/dist/src/validate.js +323 -65
- package/dist/src/validate.js.map +1 -1
- package/dist/src/workflow.d.ts.map +1 -1
- package/dist/src/workflow.js +98 -26
- package/dist/src/workflow.js.map +1 -1
- package/package.json +2 -2
- package/src/adapter.ts +9 -0
- package/src/balanced-reduce.ts +9 -0
- package/src/canonical-owned.ts +186 -0
- package/src/canonical.ts +14 -90
- package/src/capability.ts +41 -30
- package/src/clone.ts +47 -0
- package/src/constant-sign.ts +59 -0
- package/src/context.ts +114 -0
- package/src/diagnostic-text.ts +4 -0
- package/src/evaluate-expression.ts +21 -0
- package/src/exact.ts +122 -27
- package/src/explain.ts +342 -94
- package/src/explanation-budget.ts +47 -0
- package/src/expression-key.ts +45 -0
- package/src/fraction-product.ts +10 -0
- package/src/fraction-sum.ts +20 -0
- package/src/integer-gcd.ts +47 -0
- package/src/linear.ts +82 -16
- package/src/numeric-size.ts +26 -0
- package/src/options.ts +18 -0
- package/src/result.ts +28 -0
- package/src/solve.ts +34 -3
- package/src/validate.ts +312 -66
- package/src/workflow.ts +92 -26
package/src/exact.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { diagnosticText } from './diagnostic-text.ts'
|
|
2
|
+
import { gcd } from './integer-gcd.ts'
|
|
1
3
|
import type { Integer, Rational } from './model.ts'
|
|
2
4
|
|
|
3
5
|
export type Normalized = {
|
|
@@ -8,15 +10,10 @@ export type Normalized = {
|
|
|
8
10
|
const integerPattern = /^[+-]?\d+$/
|
|
9
11
|
const decimalPattern = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/
|
|
10
12
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const remainder = a % b
|
|
16
|
-
a = b
|
|
17
|
-
b = remainder
|
|
18
|
-
}
|
|
19
|
-
return a
|
|
13
|
+
const assertIntegerText = (input: string): void => {
|
|
14
|
+
if (input.match(integerPattern)?.[0] !== input) {
|
|
15
|
+
throw new TypeError(`expected an integer string, received ${diagnosticText(input)}`)
|
|
16
|
+
}
|
|
20
17
|
}
|
|
21
18
|
|
|
22
19
|
export const integer = (input: Integer): bigint => {
|
|
@@ -26,40 +23,95 @@ export const integer = (input: Integer): bigint => {
|
|
|
26
23
|
}
|
|
27
24
|
return BigInt(input)
|
|
28
25
|
}
|
|
29
|
-
if (
|
|
30
|
-
throw new TypeError(
|
|
26
|
+
if (typeof input !== 'string') {
|
|
27
|
+
throw new TypeError('expected a safe integer number or an integer string')
|
|
31
28
|
}
|
|
29
|
+
assertIntegerText(input)
|
|
32
30
|
return BigInt(input)
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
37
|
-
throw new TypeError(`expected an exact decimal string, received ${
|
|
33
|
+
const decimalParts = (input: string): { coefficient: string, exponent: number } => {
|
|
34
|
+
if (input.match(decimalPattern)?.[0] !== input) {
|
|
35
|
+
throw new TypeError(`expected an exact decimal string, received ${diagnosticText(input)}`)
|
|
38
36
|
}
|
|
39
37
|
const [coefficient = '', exponentText] = input.toLowerCase().split('e')
|
|
40
38
|
const exponent = exponentText === undefined ? 0 : Number(exponentText)
|
|
41
39
|
if (!Number.isSafeInteger(exponent)) {
|
|
42
|
-
throw new TypeError(`decimal exponent is outside the supported range: ${
|
|
40
|
+
throw new TypeError(`decimal exponent is outside the supported range: ${diagnosticText(input)}`)
|
|
43
41
|
}
|
|
42
|
+
return { coefficient, exponent }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const decimal = (input: string): Normalized => {
|
|
46
|
+
const { coefficient, exponent } = decimalParts(input)
|
|
44
47
|
const negative = coefficient.startsWith('-')
|
|
45
48
|
const unsigned = coefficient.replace(/^[+-]/, '')
|
|
46
49
|
const [whole = '', fraction = ''] = unsigned.split('.')
|
|
47
50
|
const digits = `${whole === '' ? '0' : whole}${fraction}`
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
let scale = fraction.length - exponent
|
|
52
|
+
let magnitude = digits.replace(/^0+/, '')
|
|
53
|
+
if (magnitude === '') return { numerator: '0', denominator: '1' }
|
|
54
|
+
// Cancel powers of ten in text before allocating either bigint operand.
|
|
55
|
+
let cancelled = 0
|
|
56
|
+
while (cancelled < scale && magnitude[magnitude.length - cancelled - 1] === '0') cancelled++
|
|
57
|
+
if (cancelled > 0) {
|
|
58
|
+
magnitude = magnitude.slice(0, -cancelled)
|
|
59
|
+
scale -= cancelled
|
|
60
|
+
}
|
|
61
|
+
if (scale <= 0) {
|
|
62
|
+
return { numerator: `${negative ? '-' : ''}${magnitude}${'0'.repeat(-scale)}`, denominator: '1' }
|
|
63
|
+
}
|
|
64
|
+
return fromBigInts(BigInt(magnitude) * (negative ? -1n : 1n), 10n ** BigInt(scale))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const assertRationalObject = (input: unknown): void => {
|
|
68
|
+
if (input === null || typeof input !== 'object') {
|
|
69
|
+
throw new TypeError('expected an exact decimal string or a numerator/denominator object')
|
|
70
|
+
}
|
|
53
71
|
}
|
|
54
72
|
|
|
55
73
|
export const rational = (input: Rational): Normalized => {
|
|
56
|
-
let numerator
|
|
74
|
+
let numerator = 0n
|
|
57
75
|
let denominator: bigint
|
|
58
76
|
if (typeof input === 'string') {
|
|
59
|
-
|
|
77
|
+
return decimal(input)
|
|
60
78
|
} else {
|
|
61
|
-
|
|
62
|
-
|
|
79
|
+
assertRationalObject(input)
|
|
80
|
+
const suppliedNumerator = input.numerator
|
|
81
|
+
const numeratorText = typeof suppliedNumerator === 'string' ? suppliedNumerator : undefined
|
|
82
|
+
let zero: boolean
|
|
83
|
+
if (numeratorText !== undefined) {
|
|
84
|
+
assertIntegerText(numeratorText)
|
|
85
|
+
zero = !/[1-9]/.test(numeratorText)
|
|
86
|
+
} else {
|
|
87
|
+
numerator = integer(suppliedNumerator)
|
|
88
|
+
zero = numerator === 0n
|
|
89
|
+
}
|
|
90
|
+
const suppliedDenominator = input.denominator
|
|
91
|
+
if (zero && typeof suppliedDenominator === 'string') {
|
|
92
|
+
assertIntegerText(suppliedDenominator)
|
|
93
|
+
if (!/[1-9]/.test(suppliedDenominator)) {
|
|
94
|
+
throw new TypeError('rational denominator must not be zero')
|
|
95
|
+
}
|
|
96
|
+
return { numerator: '0', denominator: '1' }
|
|
97
|
+
}
|
|
98
|
+
denominator = integer(suppliedDenominator)
|
|
99
|
+
if (numeratorText !== undefined) {
|
|
100
|
+
if (denominator === 1n || denominator === -1n) {
|
|
101
|
+
const magnitude = numeratorText.replace(/^[+-]?0*/, '')
|
|
102
|
+
const negative = numeratorText.startsWith('-') !== (denominator === -1n)
|
|
103
|
+
return { numerator: magnitude === '' ? '0' : `${negative ? '-' : ''}${magnitude}`, denominator: '1' }
|
|
104
|
+
}
|
|
105
|
+
numerator = BigInt(numeratorText)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return fromBigInts(numerator, denominator)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Normalize integer intermediates without serializing and parsing them again. */
|
|
112
|
+
export const fromBigInts = (numerator: bigint, denominator: bigint): Normalized => {
|
|
113
|
+
if (typeof numerator !== 'bigint' || typeof denominator !== 'bigint') {
|
|
114
|
+
throw new TypeError('expected bigint numerator and denominator')
|
|
63
115
|
}
|
|
64
116
|
if (denominator === 0n) {
|
|
65
117
|
throw new TypeError('rational denominator must not be zero')
|
|
@@ -75,11 +127,54 @@ export const rational = (input: Rational): Normalized => {
|
|
|
75
127
|
}
|
|
76
128
|
}
|
|
77
129
|
|
|
130
|
+
/** Compare canonical integer text without allocating bigint magnitudes. */
|
|
131
|
+
const integerOrder = (left: string, right: string): -1 | 0 | 1 => {
|
|
132
|
+
if (left === right) return 0
|
|
133
|
+
const negative = left.startsWith('-')
|
|
134
|
+
if (negative !== right.startsWith('-')) return negative ? -1 : 1
|
|
135
|
+
const a = negative ? left.slice(1) : left
|
|
136
|
+
const b = negative ? right.slice(1) : right
|
|
137
|
+
const order = a.length === b.length ? (a < b ? -1 : 1) : a.length < b.length ? -1 : 1
|
|
138
|
+
return negative ? (order === -1 ? 1 : -1) : order
|
|
139
|
+
}
|
|
140
|
+
|
|
78
141
|
export const compare = (left: Rational, right: Rational): -1 | 0 | 1 => {
|
|
79
142
|
const a = rational(left)
|
|
80
143
|
const b = rational(right)
|
|
81
|
-
const
|
|
82
|
-
|
|
144
|
+
const numerators = integerOrder(a.numerator, b.numerator)
|
|
145
|
+
const negative = a.numerator.startsWith('-')
|
|
146
|
+
if (a.denominator === b.denominator || a.numerator === '0' || b.numerator === '0' ||
|
|
147
|
+
negative !== b.numerator.startsWith('-')) {
|
|
148
|
+
return numerators
|
|
149
|
+
}
|
|
150
|
+
const denominators = integerOrder(a.denominator, b.denominator)
|
|
151
|
+
if (numerators === 0) {
|
|
152
|
+
// Equal denominators were handled above. A positive common numerator
|
|
153
|
+
// reverses denominator order; a negative one preserves it.
|
|
154
|
+
return negative ? denominators : denominators === -1 ? 1 : -1
|
|
155
|
+
}
|
|
156
|
+
// With equal signs, a smaller magnitude numerator and larger denominator
|
|
157
|
+
// reinforce each other. Their ordering needs no cross-product allocation.
|
|
158
|
+
if ((numerators < 0) === (negative ? denominators < 0 : denominators > 0)) {
|
|
159
|
+
return numerators
|
|
160
|
+
}
|
|
161
|
+
const numeratorA = BigInt(a.numerator), numeratorB = BigInt(b.numerator)
|
|
162
|
+
const denominatorA = BigInt(a.denominator), denominatorB = BigInt(b.denominator)
|
|
163
|
+
const productA = numeratorA * denominatorB
|
|
164
|
+
const productB = numeratorB * denominatorA
|
|
165
|
+
return productA < productB ? -1 : productA > productB ? 1 : 0
|
|
83
166
|
}
|
|
84
167
|
|
|
85
|
-
|
|
168
|
+
const integerIsZero = (input: Integer): boolean => {
|
|
169
|
+
if (typeof input !== 'string') return integer(input) === 0n
|
|
170
|
+
assertIntegerText(input)
|
|
171
|
+
return !/[1-9]/.test(input)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export const isZero = (input: Rational): boolean => {
|
|
175
|
+
if (typeof input === 'string') return !/[1-9]/.test(decimalParts(input).coefficient)
|
|
176
|
+
assertRationalObject(input)
|
|
177
|
+
const zero = integerIsZero(input.numerator)
|
|
178
|
+
if (integerIsZero(input.denominator)) throw new TypeError('rational denominator must not be zero')
|
|
179
|
+
return zero
|
|
180
|
+
}
|