rcas 0.2.0
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.
- checksums.yaml +7 -0
- data/CITATION.cff +17 -0
- data/DESIGN.md +783 -0
- data/LICENSE +21 -0
- data/MANUAL.md +6265 -0
- data/README.md +267 -0
- data/bin/rcas +9 -0
- data/bin/rcas-app +9 -0
- data/bin/rcas-chat +9 -0
- data/lib/rcas/algebraic.rb +481 -0
- data/lib/rcas/analysis.rb +966 -0
- data/lib/rcas/app/launcher.rb +203 -0
- data/lib/rcas/app/public/app.css +402 -0
- data/lib/rcas/app/public/app.js +449 -0
- data/lib/rcas/app/public/index.html +46 -0
- data/lib/rcas/app/server.rb +220 -0
- data/lib/rcas/app/window.rb +94 -0
- data/lib/rcas/app/worksheet.rb +290 -0
- data/lib/rcas/app.rb +168 -0
- data/lib/rcas/background.rb +758 -0
- data/lib/rcas/chat/assistant.rb +199 -0
- data/lib/rcas/chat/picker.rb +164 -0
- data/lib/rcas/chat/repl.rb +583 -0
- data/lib/rcas/chat/session.rb +137 -0
- data/lib/rcas/chat/settings.rb +71 -0
- data/lib/rcas/chat/style.rb +30 -0
- data/lib/rcas/chat/tool.rb +53 -0
- data/lib/rcas/chat/ui.rb +316 -0
- data/lib/rcas/chat/usage.rb +62 -0
- data/lib/rcas/chat/workspace.rb +132 -0
- data/lib/rcas/chat.rb +54 -0
- data/lib/rcas/coefficients.rb +170 -0
- data/lib/rcas/combinatorics.rb +274 -0
- data/lib/rcas/complex_parts.rb +160 -0
- data/lib/rcas/constants.rb +129 -0
- data/lib/rcas/core_ext.rb +35 -0
- data/lib/rcas/decide.rb +501 -0
- data/lib/rcas/decompositions.rb +241 -0
- data/lib/rcas/differentiate.rb +144 -0
- data/lib/rcas/discussion.rb +558 -0
- data/lib/rcas/distributions.rb +980 -0
- data/lib/rcas/dixon.rb +95 -0
- data/lib/rcas/docs.rb +321 -0
- data/lib/rcas/domains.rb +728 -0
- data/lib/rcas/expand.rb +174 -0
- data/lib/rcas/expression.rb +613 -0
- data/lib/rcas/factor.rb +605 -0
- data/lib/rcas/finite_field.rb +577 -0
- data/lib/rcas/fourier.rb +118 -0
- data/lib/rcas/fps.rb +678 -0
- data/lib/rcas/fraction.rb +126 -0
- data/lib/rcas/functions.rb +1136 -0
- data/lib/rcas/gcd.rb +112 -0
- data/lib/rcas/geometry.rb +266 -0
- data/lib/rcas/groebner.rb +162 -0
- data/lib/rcas/hold.rb +277 -0
- data/lib/rcas/hypothesis.rb +364 -0
- data/lib/rcas/inequalities.rb +689 -0
- data/lib/rcas/integral_functions.rb +260 -0
- data/lib/rcas/integrate.rb +1589 -0
- data/lib/rcas/integrate_substitutions.rb +434 -0
- data/lib/rcas/interpolate.rb +40 -0
- data/lib/rcas/irb.rb +146 -0
- data/lib/rcas/laplace.rb +159 -0
- data/lib/rcas/latex.rb +556 -0
- data/lib/rcas/lattice.rb +172 -0
- data/lib/rcas/linear_algebra.rb +117 -0
- data/lib/rcas/linear_program.rb +416 -0
- data/lib/rcas/lint.rb +79 -0
- data/lib/rcas/matrix.rb +531 -0
- data/lib/rcas/matrix_multiply.rb +202 -0
- data/lib/rcas/multimodular.rb +286 -0
- data/lib/rcas/named_polynomials.rb +274 -0
- data/lib/rcas/number_theory.rb +443 -0
- data/lib/rcas/numerics.rb +825 -0
- data/lib/rcas/ode.rb +488 -0
- data/lib/rcas/openmath/objects.rb +364 -0
- data/lib/rcas/openmath/phrasebook.rb +551 -0
- data/lib/rcas/openmath/popcorn.rb +518 -0
- data/lib/rcas/openmath/xml.rb +309 -0
- data/lib/rcas/openmath.rb +49 -0
- data/lib/rcas/petkovsek.rb +165 -0
- data/lib/rcas/piecewise.rb +488 -0
- data/lib/rcas/plot.rb +763 -0
- data/lib/rcas/plot3d.rb +419 -0
- data/lib/rcas/poly_matrix.rb +318 -0
- data/lib/rcas/poly_recurrence.rb +117 -0
- data/lib/rcas/polynomial.rb +466 -0
- data/lib/rcas/precision.rb +925 -0
- data/lib/rcas/printer.rb +150 -0
- data/lib/rcas/product.rb +155 -0
- data/lib/rcas/q_difference.rb +296 -0
- data/lib/rcas/q_functions.rb +158 -0
- data/lib/rcas/q_summation.rb +308 -0
- data/lib/rcas/q_zeilberger.rb +199 -0
- data/lib/rcas/random.rb +506 -0
- data/lib/rcas/rational_function.rb +186 -0
- data/lib/rcas/recurrence.rb +323 -0
- data/lib/rcas/render.rb +431 -0
- data/lib/rcas/results.rb +192 -0
- data/lib/rcas/scalar.rb +219 -0
- data/lib/rcas/series.rb +726 -0
- data/lib/rcas/simplify.rb +649 -0
- data/lib/rcas/solve.rb +2002 -0
- data/lib/rcas/special.rb +163 -0
- data/lib/rcas/statistics.rb +175 -0
- data/lib/rcas/steps.rb +835 -0
- data/lib/rcas/summation.rb +532 -0
- data/lib/rcas/trig.rb +264 -0
- data/lib/rcas/van_hoeij.rb +241 -0
- data/lib/rcas/vector.rb +175 -0
- data/lib/rcas/vector_calculus.rb +411 -0
- data/lib/rcas/version.rb +5 -0
- data/lib/rcas/zeilberger.rb +358 -0
- data/lib/rcas.rb +91 -0
- data/package.json +8 -0
- metadata +206 -0
data/lib/rcas/lattice.rb
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCAS
|
|
4
|
+
# Lattice basis reduction: the LLL algorithm, exactly.
|
|
5
|
+
#
|
|
6
|
+
# lll(matrix([[1, 1, 1], [-1, 0, 2], [3, 5, 6]])) # rows (0, 1, 0), (1, 0, 1), (-1, 0, 2)
|
|
7
|
+
# b, u = lll(m, transform: true) # u*m == b, u unimodular
|
|
8
|
+
# m.lll(delta: 99/100r)
|
|
9
|
+
#
|
|
10
|
+
# A lattice is every integer combination of some linearly independent
|
|
11
|
+
# vectors, its basis; many bases give the same lattice, and most of them
|
|
12
|
+
# are long and nearly parallel. A reduced basis is short and nearly
|
|
13
|
+
# orthogonal. Its first vector is at most 2**((n - 1)/2) times as long as
|
|
14
|
+
# the shortest non-zero vector of the lattice (for delta = 3/4), which is
|
|
15
|
+
# what makes the algorithm useful far beyond lattices: integer relations
|
|
16
|
+
# between real numbers, minimal polynomials from a decimal expansion,
|
|
17
|
+
# simultaneous approximation, knapsacks.
|
|
18
|
+
#
|
|
19
|
+
# The vectors are the rows, as in the literature. The arithmetic is exact
|
|
20
|
+
# (Rational), so the Gram-Schmidt coefficients are kept and updated rather
|
|
21
|
+
# than recomputed, as in Cohen's formulation: size reduction touches one
|
|
22
|
+
# row of mu, and a swap changes two rows and two columns of it. The price
|
|
23
|
+
# of exactness is the size of those rationals; the integral version
|
|
24
|
+
# [Coh93, Algorithm 2.6.7] keeps them as integers, and floating-point
|
|
25
|
+
# variants trade exactness for speed, neither of which rcas needs at the
|
|
26
|
+
# sizes a student reduces.
|
|
27
|
+
#
|
|
28
|
+
# Sources (keys: MANUAL.md, Sources): the algorithm and its bound
|
|
29
|
+
# [LLL82]; the formulation with the incremental Gram-Schmidt update
|
|
30
|
+
# [Coh93, §2.6, Algorithm 2.6.3]; [vzGG13, ch. 16] for the analysis.
|
|
31
|
+
module Lattice
|
|
32
|
+
DELTA = Rational(3, 4)
|
|
33
|
+
|
|
34
|
+
module_function
|
|
35
|
+
|
|
36
|
+
# The reduced basis in the shape it came in: a Matrix for a Matrix, an
|
|
37
|
+
# Array of vectors otherwise; with transform: true also the unimodular
|
|
38
|
+
# matrix U with U*B equal to the result.
|
|
39
|
+
def lll(basis, delta: DELTA, transform: false)
|
|
40
|
+
rows, shape = rows_of(basis)
|
|
41
|
+
delta = rational(delta) { "lll: delta must be a rational number, got #{delta.inspect}" }
|
|
42
|
+
raise ArgumentError, "lll: delta must lie in (1/4, 1], got #{delta}" unless delta > Rational(1, 4) && delta <= 1
|
|
43
|
+
reduced, u = reduce(rows, delta)
|
|
44
|
+
out = shape.call(reduced)
|
|
45
|
+
transform ? [out, RCAS.matrix(u)] : out
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Cohen's Algorithm 2.6.3. mu[i][j] (j < i) are the Gram-Schmidt
|
|
49
|
+
# coefficients and bb[i] the squared lengths of the orthogonalised
|
|
50
|
+
# vectors; k is the first vector not yet known to be reduced against
|
|
51
|
+
# the ones before it.
|
|
52
|
+
def reduce(rows, delta)
|
|
53
|
+
b = rows.map(&:dup)
|
|
54
|
+
n = b.size
|
|
55
|
+
u = Array.new(n) { |i| Array.new(n) { |j| i == j ? 1 : 0 } }
|
|
56
|
+
mu, bb = gram_schmidt(b)
|
|
57
|
+
if (i = bb.index(&:zero?))
|
|
58
|
+
raise ArgumentError, "lll: the vectors are linearly dependent (vector #{i + 1} lies in the span of the ones before it); LLL reduces a basis"
|
|
59
|
+
end
|
|
60
|
+
k = 1
|
|
61
|
+
while k < n
|
|
62
|
+
size_reduce(b, u, mu, k, k - 1)
|
|
63
|
+
# Lovasz's condition: the orthogonalised k-th vector is not much
|
|
64
|
+
# shorter than the one before it
|
|
65
|
+
if bb[k] >= (delta - mu[k][k - 1]**2) * bb[k - 1]
|
|
66
|
+
(k - 2).downto(0) { |l| size_reduce(b, u, mu, k, l) }
|
|
67
|
+
k += 1
|
|
68
|
+
else
|
|
69
|
+
swap(b, u, mu, bb, k)
|
|
70
|
+
k = [k - 1, 1].max
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
[b, u]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# b_k -= q*b_l with q the integer nearest mu[k][l], which leaves
|
|
77
|
+
# |mu[k][l]| <= 1/2; mu[k][j] for j < l changes with it.
|
|
78
|
+
def size_reduce(b, u, mu, k, l)
|
|
79
|
+
q = mu[k][l].round
|
|
80
|
+
return if q.zero?
|
|
81
|
+
b[k] = b[k].zip(b[l]).map { |x, y| x - q * y }
|
|
82
|
+
u[k] = u[k].zip(u[l]).map { |x, y| x - q * y }
|
|
83
|
+
mu[k][l] -= q
|
|
84
|
+
(0...l).each { |j| mu[k][j] -= q * mu[l][j] }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Exchange b_(k-1) and b_k and update the Gram-Schmidt data in place
|
|
88
|
+
# [Coh93, Algorithm 2.6.3, step 3]: only the two orthogonalised vectors
|
|
89
|
+
# involved change, and with them rows k-1, k and columns k-1, k of mu.
|
|
90
|
+
def swap(b, u, mu, bb, k)
|
|
91
|
+
b[k], b[k - 1] = b[k - 1], b[k]
|
|
92
|
+
u[k], u[k - 1] = u[k - 1], u[k]
|
|
93
|
+
(0...k - 1).each { |j| mu[k][j], mu[k - 1][j] = mu[k - 1][j], mu[k][j] }
|
|
94
|
+
m = mu[k][k - 1]
|
|
95
|
+
big = bb[k] + m * m * bb[k - 1]
|
|
96
|
+
mu[k][k - 1] = m * bb[k - 1] / big
|
|
97
|
+
bb[k] = bb[k - 1] * bb[k] / big
|
|
98
|
+
bb[k - 1] = big
|
|
99
|
+
(k + 1...b.size).each do |i|
|
|
100
|
+
t = mu[i][k]
|
|
101
|
+
mu[i][k] = mu[i][k - 1] - m * t
|
|
102
|
+
mu[i][k - 1] = t + mu[k][k - 1] * mu[i][k]
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# The Gram-Schmidt coefficients and squared lengths, exactly.
|
|
107
|
+
def gram_schmidt(b)
|
|
108
|
+
n = b.size
|
|
109
|
+
mu = Array.new(n) { Array.new(n, 0r) }
|
|
110
|
+
bb = Array.new(n, 0r)
|
|
111
|
+
star = []
|
|
112
|
+
b.each_with_index do |v, i|
|
|
113
|
+
w = v.dup
|
|
114
|
+
(0...i).each do |j|
|
|
115
|
+
next if bb[j].zero?
|
|
116
|
+
mu[i][j] = dot(v, star[j]) / bb[j]
|
|
117
|
+
w = w.zip(star[j]).map { |x, y| x - mu[i][j] * y }
|
|
118
|
+
end
|
|
119
|
+
star << w
|
|
120
|
+
bb[i] = dot(w, w)
|
|
121
|
+
end
|
|
122
|
+
[mu, bb]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Whether the rows are LLL-reduced: size-reduced (|mu| <= 1/2) and
|
|
126
|
+
# Lovasz's condition between neighbours. The tests ask this of every
|
|
127
|
+
# answer.
|
|
128
|
+
def reduced?(rows, delta: DELTA)
|
|
129
|
+
rows = rows_of(rows).first
|
|
130
|
+
mu, bb = gram_schmidt(rows)
|
|
131
|
+
size = (1...rows.size).all? { |i| (0...i).all? { |j| mu[i][j].abs <= Rational(1, 2) } }
|
|
132
|
+
size && (1...rows.size).all? { |k| bb[k] >= (Rational(delta) - mu[k][k - 1]**2) * bb[k - 1] }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def dot(v, w) = v.zip(w).sum { |x, y| x * y }
|
|
136
|
+
|
|
137
|
+
# [rows as Arrays of Rationals, a lambda giving the answer its shape]
|
|
138
|
+
def rows_of(basis)
|
|
139
|
+
rows, shape =
|
|
140
|
+
case basis
|
|
141
|
+
when Matrix then [basis.to_a, ->(r) { RCAS.matrix(numbers(r)) }]
|
|
142
|
+
when Array
|
|
143
|
+
vectors = basis.map { |v| v.is_a?(Vector) ? v.entries : v }
|
|
144
|
+
raise ArgumentError, "lll: expected a matrix or a list of vectors, got #{basis.inspect}" unless vectors.all?(Array)
|
|
145
|
+
[vectors, ->(r) { numbers(r).map { |row| RCAS.vector(*row) } }]
|
|
146
|
+
else raise ArgumentError, "lll: expected a matrix or a list of vectors, got #{basis.inspect}"
|
|
147
|
+
end
|
|
148
|
+
raise ArgumentError, "lll: no vectors to reduce" if rows.empty?
|
|
149
|
+
raise ArgumentError, "lll: the vectors have different lengths" unless rows.map(&:size).uniq.size == 1
|
|
150
|
+
entries = rows.map do |row|
|
|
151
|
+
row.map do |e|
|
|
152
|
+
rational(e) { "lll: the entries must be rational numbers, got #{e}; scale and round a real lattice first" }
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
[entries, shape]
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def rational(e)
|
|
159
|
+
e = e.value if e.is_a?(Num)
|
|
160
|
+
e = Expression.lift(e).simplify.then { |s| s.is_a?(Num) ? s.value : s } if e.is_a?(Expression)
|
|
161
|
+
return e.to_r if e.is_a?(Integer) || e.is_a?(Rational)
|
|
162
|
+
raise ArgumentError, yield
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def numbers(rows) = rows.map { |row| row.map { |x| Simplify.normalize_number(x) } }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
class Matrix
|
|
169
|
+
# The LLL-reduced basis of the lattice spanned by the rows; see Lattice.
|
|
170
|
+
def lll(delta: Lattice::DELTA, transform: false) = Lattice.lll(self, delta: delta, transform: transform)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RCAS
|
|
4
|
+
# The second course in linear algebra: orthogonality and least squares.
|
|
5
|
+
#
|
|
6
|
+
# gram_schmidt([vector(1, 1, 0), vector(1, 0, 1)]) # orthogonal basis
|
|
7
|
+
# gram_schmidt(vs, normalize: true) # orthonormal
|
|
8
|
+
# least_squares(matrix([[1, 1], [1, 2], [1, 3]]), vector(1, 2, 4))
|
|
9
|
+
# project(vector(1, 2), onto: vector(1, 0))
|
|
10
|
+
#
|
|
11
|
+
# Exact throughout, so an orthonormal basis keeps its square roots.
|
|
12
|
+
#
|
|
13
|
+
# Sources (keys: MANUAL.md, Sources): Gram-Schmidt and the normal equations
|
|
14
|
+
# as in [Str16, ch. 4]; the numerically stabler QR route is not used because
|
|
15
|
+
# the arithmetic here is exact.
|
|
16
|
+
module LinearAlgebra
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# The inner product, Hermitian where an entry carries an explicit
|
|
20
|
+
# complex number: (1, i).(1, i) is 1 + i*i = 0 bilinearly, and |(1, i)|
|
|
21
|
+
# is sqrt(2), not 0 (third review, L12). Symbolic entries are taken as
|
|
22
|
+
# they stand, so the real case is unchanged.
|
|
23
|
+
def dot(u, v)
|
|
24
|
+
same_length!(u, v, "dot")
|
|
25
|
+
u.entries.zip(v.entries).map { |a, b| Scalar.mul(a, conjugate(b)) }.reduce { |x, y| Scalar.add(x, y) }.simplify
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# zip drops the extra coordinates of the longer vector, which made
|
|
29
|
+
# project((1, 2), onto: (1, 0, 3)) a plausible (1/10, 0) (a review,
|
|
30
|
+
# 23 Sept 2026): vectors of two different spaces have no inner product.
|
|
31
|
+
def same_length!(u, v, name)
|
|
32
|
+
return if u.entries.size == v.entries.size
|
|
33
|
+
raise ArgumentError, "#{name}: the vectors have #{u.entries.size} and #{v.entries.size} entries; they must lie in the same space"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def conjugate(e)
|
|
37
|
+
e = Expression.lift(e)
|
|
38
|
+
return e unless e.each_node.any? { |n| n.is_a?(Num) && n.value.is_a?(Complex) }
|
|
39
|
+
ComplexParts.conj(e)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def norm(v) = RCAS.sqrt(dot(v, v)).simplify
|
|
43
|
+
|
|
44
|
+
def scale(v, factor) = v.space.unchecked(v.entries.map { |e| (e * factor).simplify })
|
|
45
|
+
|
|
46
|
+
def subtract(u, v)
|
|
47
|
+
same_length!(u, v, "subtract")
|
|
48
|
+
u.space.unchecked(u.entries.zip(v.entries).map { |a, b| (a - b).simplify })
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# The projection of v onto a vector or onto the span of several.
|
|
52
|
+
#
|
|
53
|
+
# The sum of the single projections is the projection onto the span only
|
|
54
|
+
# when the targets are pairwise orthogonal; a slanted pair has to be
|
|
55
|
+
# orthogonalised first. project(e1, onto: [e1, e1 + e2]) is e1 - those
|
|
56
|
+
# two span the plane, so the projection onto it is the identity - and
|
|
57
|
+
# was (3/2, 1/2) before (22 Sept 2026, from a review). Dependent
|
|
58
|
+
# generators drop out on the way, as they do in gram_schmidt.
|
|
59
|
+
def project(v, onto:)
|
|
60
|
+
targets = onto.is_a?(Array) ? onto : [onto]
|
|
61
|
+
targets.each do |u|
|
|
62
|
+
raise ArgumentError, "project: cannot project onto the zero vector" if Scalar.zero?(dot(u, u))
|
|
63
|
+
end
|
|
64
|
+
targets = gram_schmidt(targets) unless orthogonal_family?(targets)
|
|
65
|
+
onto_orthogonal(v, targets)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def orthogonal_family?(targets) = targets.combination(2).all? { |u, w| orthogonal?(u, w) }
|
|
69
|
+
|
|
70
|
+
# The sum of the single projections, which is the projection itself once
|
|
71
|
+
# the targets are known to be pairwise orthogonal. gram_schmidt builds
|
|
72
|
+
# such a family as it goes and calls this directly, so that project does
|
|
73
|
+
# not orthogonalise what it has just orthogonalised.
|
|
74
|
+
def onto_orthogonal(v, targets)
|
|
75
|
+
targets.reduce(v.space.unchecked(Array.new(v.entries.size, Num.new(0)))) do |sum, u|
|
|
76
|
+
add(sum, scale(u, (dot(v, u) / dot(u, u)).simplify))
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def add(u, v)
|
|
81
|
+
same_length!(u, v, "add")
|
|
82
|
+
u.space.unchecked(u.entries.zip(v.entries).map { |a, b| (a + b).simplify })
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# An orthogonal (or orthonormal) basis of the same span; dependent
|
|
86
|
+
# vectors drop out rather than appearing as zeros.
|
|
87
|
+
def gram_schmidt(vectors, normalize: false)
|
|
88
|
+
basis = []
|
|
89
|
+
Array(vectors).each do |v|
|
|
90
|
+
w = basis.empty? ? v : subtract(v, onto_orthogonal(v, basis))
|
|
91
|
+
next if w.entries.all? { |e| Scalar.zero?(e) }
|
|
92
|
+
basis << w
|
|
93
|
+
end
|
|
94
|
+
return basis unless normalize
|
|
95
|
+
basis.map { |w| scale(w, (1 / norm(w)).simplify) }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The x minimising |A x - b|, from the normal equations A* A x = A* b
|
|
99
|
+
# with A* the conjugate transpose. The plain transpose is right only for
|
|
100
|
+
# real entries: for A = (1, 2i)' it gave -1/3 where the minimiser of
|
|
101
|
+
# |z - 1|**2 + |2iz|**2 is 1/5, and (1, i)' had "dependent columns"
|
|
102
|
+
# (a review, 23 Sept 2026). `conjugate` leaves a symbolic entry alone,
|
|
103
|
+
# as `dot` does, so the real case is unchanged.
|
|
104
|
+
def least_squares(matrix, target)
|
|
105
|
+
unless target.entries.size == matrix.rows
|
|
106
|
+
raise ArgumentError, "least_squares: the matrix has #{matrix.rows} rows and the vector #{target.entries.size} entries"
|
|
107
|
+
end
|
|
108
|
+
adjoint = RCAS.matrix(matrix.transpose.to_a.map { |row| row.map { |e| conjugate(e) } })
|
|
109
|
+
normal = adjoint * matrix
|
|
110
|
+
right = adjoint * target
|
|
111
|
+
raise ArgumentError, "least_squares: the columns are dependent, so the solution is not unique" if Scalar.zero?(normal.det)
|
|
112
|
+
normal.solve(right.entries)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def orthogonal?(u, v) = Scalar.zero?(dot(u, v))
|
|
116
|
+
end
|
|
117
|
+
end
|