4 Contains the functions:
13 plus some internal utility funcions. If you make use of one of
14 them, consider asking on the mailing list: we can try to make a
15 more useful API and give it a sensible name (not prefixed by
16 "diag_"), that we won't change.
18 See the manual or the comments above the relevant function for more
19 information on how each works.
21 As with the rest of Maxima, this code is distributed under the GPL,
27 If EXPR is a matrix then return it unchanged. Otherwise, return a
28 1x1 matrix whose sole element is EXPR.
30 diag_matrixify (expr) :=
31 if matrixp (expr) then expr else matrix ([expr])$
34 Pad R with zeros before and after to make it WIDTH wide using INDENT
35 zeros on the left hand side.
37 diag_zeropad_row (r, indent, width) :=
38 block([right: width - (left + length(r))],
40 error(r, "does not fit in a row of length", width,
41 "with indent", indent),
42 append (makelist (0, indent), r, makelist (0, right)))$
45 Return the direct sum of the elements of LST, made into 1x1 matrices
46 if they weren't already matrices.
49 block ([lst: map (diag_matrixify, lst),
50 width, height, left: 0, rows: []],
51 width: lsum (length (first (A)), A, lst),
52 height: lsum (length (A), A, lst),
55 rows: cons (diag_zeropad_row (r, left, width), rows),
56 left: left + length (first (A))),
57 apply (matrix, reverse (rows)))$
60 Construct a Jordan block of size N x N and eigenvalue EIVAL.
63 if not (numberp (n)) then
65 elseif not (integerp (n)) then
66 error (n, "is not an integer, so not a valid Jordan block size")
67 elseif is (n <= 0) then
68 error ("Cannot construct matrix with negative size", n)
72 if is (i = j) then eival
73 elseif is (j = i + 1) then 1
78 Calculate the correct partition of multiplicity for the matrix A at
81 diag_calculate_mult_partition (A, multiplicity, eival) :=
82 if is (multiplicity = 1) then [1] else
83 block ([Abar: A - eival * ident (length (A)),
86 nullity: n - rank (Abar),
87 if is (nullity = 1) then [multiplicity]
88 elseif is (nullity = multiplicity) then makelist (1, nullity)
89 else block ([blocks_left: nullity, mults: [], Abarpow: Abar, dnull],
91 Abarpow: Abarpow . Abar,
92 dnull: n - rank (Abarpow) - nullity,
93 nullity: nullity + dnull,
94 if (dnull < blocks_left) then (
95 mults: append (makelist (k, blocks_left - dnull), mults),
96 multiplicity: multiplicity - k * (blocks_left - dnull),
98 if is (blocks_left * (k + 1) = multiplicity) then
99 return (append (makelist (k+1, blocks_left), mults)),
100 if is (blocks_left * (k + 1) > multiplicity) then
101 error ("Unexpected blocks left over!"))))$
104 Calculate the JCF of A. Returns a list of eigenvalues and their
108 if not (matrixp (A)) then
110 else block([eigenlist: eigenvectors (A)],
111 map (lambda ([eival, mult],
112 cons (eival, diag_calculate_mult_partition (A, mult, eival))),
113 eigenlist[1][1], eigenlist[1][2]))$
116 A simple sanity check that arguments are of the form returned by
119 diag_jordan_info_check (lst) :=
120 if not (listp (lst)) then false
121 else block([ret: true],
123 if not (listp (pair)) then (ret: false, return (false))
124 elseif is (length (pair) < 2) then
125 error ("Found a surprisingly short jordan_info list:",
130 Calculate the minimal polynomial, expressed in the symbol "x", of
131 any matrix with the given list of eigenvalues and their
134 NOTE: This assumes that the first multiplicity for an eigenvalue is
135 the largest and that the given eigenvalues are distinct. This
136 is the case for the output of jordan().
138 minimalPoly (jordan_info) :=
139 if not (diag_jordan_info_check (jordan_info)) then
140 'minimalPoly (jordan_info)
143 map (lambda ([eival_lst], ('x - first (eival_lst))^(second (eival_lst))),
147 Take a list of eigenvalues and their multiplicities and build the
148 corresponding Jordan matrix.
150 dispJordan (jordan_info) := block ([blocks: []],
151 if not (diag_jordan_info_check (jordan_info)) then
152 'dispJordan (jordan_info)
154 for eival_lst in jordan_info do
155 for size in rest (eival_lst) do
156 blocks: cons (JF (first (eival_lst), size), blocks),
157 diag (reverse (blocks)))$
160 Takes a "sorted list" and produces a histogram of elements and their
163 [3,3,2,2,2,1,1,1] => [[3,2],[2,3],[1,3]]
165 The "sorted" requirement is merely that any object only occurs in
166 one block. Thus, we will do the right thing on [1,1,0,0,2,2], but
167 not on [1,1,0,0,1,1].
169 diag_sorted_list_histogram (list) :=
170 if is (emptyp (list)) then [] else
171 block([pairs: [], current: first (list), count: 1],
172 for lst: rest (list) next rest (lst) while not (emptyp (lst))
174 if is (current = first (lst)) then
177 pairs: cons ([current, count], pairs),
178 current: first (lst),
180 reverse (cons ([current, count], pairs)))$
183 Find a formula for a general element of the kernel of A.
185 Returns the solution as a row vector of (linear) expressions in the
186 variables of %rnum_list.
188 diag_kernel_element (A) :=
189 block ([vars: makelist (gensym (), length (first (A))), eqns, soln],
190 if length (vars) = 1 then
191 eqns: [first(vars) * first (first (A))]
193 eqns: map (first, args (A . apply (matrix, map("[", vars)))),
194 soln: algsys (eqns, vars),
195 if emptyp (soln) then error ("Could not solve for the kernel of ", A),
196 map (rhs, first (soln)))$
199 Calculate the general form for a chain of generalized eigenvectors
200 for A, of the form [v1, v2, ..., vn] such that (A-eival*I).v1 = 0 and
201 (A-eival*I).vk = v(k-1) where n = degree.
203 If degree is not correct, throws an error.
205 Otherwise returns [chain, freevars] where chain is a list of the
206 eigenvectors, each of which is represented as a list, and freevars
207 is the list of free variables.
209 diag_general_jordan_chain (A, eival, degree) :=
210 block ([n: length (A), Abar, ret],
211 if (is (n < 1) or not (length (A[1]) = n)) then
212 error ("Invalid input matrix: ", A),
213 Abar: A - eival * ident (n),
214 ret: [diag_kernel_element (Abar^^degree)],
215 for i : 2 thru degree do
216 ret: cons (args (map (first, Abar . first(ret))), ret),
220 LI_ROWS should be a list of lists representing row vectors that are
221 linearly independent. Then CHAIN_EXPR is a list of row vectors in
222 VARIABLES representing a Jordan chain. The function tries to find a
223 choice of variables such that CHAIN_EXPR is linearly independent of
226 (Note: Because chain_expr represents a Jordan chain, it suffices to
227 check linear independence for the first and last elements of
230 diag_find_li_chain (li_rows, chain_expr, variables) :=
231 block ([dict: false, extra_rows],
232 for vals in ident (length (variables)) do (
233 dict: map ("=", variables, vals),
234 extra_rows: subst (dict,
235 if is (length (chain_expr) > 1) then
236 [first (chain_expr), last (chain_expr)]
237 else [first (chain_expr)]),
238 if is (rank (apply (matrix, append (li_rows, extra_rows))) =
239 length (li_rows) + length (extra_rows))
242 if is (dict = false) then
243 error ("Could not find a linearly independent vector"),
244 /* Rectform since complex eigenvalues result in horrible
245 expressions here otherwise */
246 rectform (subst (dict, chain_expr)))$
249 Calculates the mode matrix of A, which is a matrix T such that
250 T^^(-1) . A . T = JordanForm (A)
252 F should be a list of the eigenvalues of A together with their
253 multiplicities, as returned by jordan(A).
257 rest(ev_lst) is a list of chain lengths, which we convert to
258 multiplicities. To ensure we get the correct answer, when we hunt
259 for chains we need to start with those of maximal
260 length. Fortunately, jordan() returns lists where rest(ev_lst) is
261 (weakly) decreasing. As such, we can just work down the lengths in
262 the given order and we'll get the right answer.
264 For each chain length, we find a general form for a Jordan chain
265 that length then we evaluate the formula, varying the parameters to
266 make sure we end up with a linearly independent chain. Note that it
267 suffices to check that the first term of the chain list (the actual
268 eigenvector) is linearly independent from the rows we've got so
271 Since generalised eigenvectors for different eigenvalues are
272 linearly independent, we don't bother checking there.
274 diag_mode_matrix (a, F) :=
275 if not (diag_jordan_info_check (F)) or not (matrixp (a)) then
277 else block([msize: length(a), all_rows: []],
279 block ([eival: first (ev_pair),
280 multiplist: diag_sorted_list_histogram (rest (ev_pair)),
281 mat_rows: [], mat_rank: 0],
282 for degree_pair in multiplist do
283 block ([mindeg: first (degree_pair),
284 genev_pair, genevs, free_vars],
285 genev_pair: diag_general_jordan_chain (a, eival,
286 first (degree_pair)),
287 for k : 1 thru second (degree_pair) do
290 diag_find_li_chain (mat_rows,
292 second (genev_pair)))),
293 all_rows: append (all_rows, mat_rows)),
294 transpose (apply (matrix, all_rows)))$
297 Finds a matrix T such that T^^(-1) . A . T is the Jordan form of A.
299 ModeMatrix (A, [jordan_info]) :=
300 if is (length (jordan_info) > 1) then
301 error ("Too many arguments for ModeMatrix. (Expects 1 or 2)")
302 elseif not (matrixp (A)) then
303 if is (length (jordan_info) = 1) then
304 'ModeMatrix (A, first (jordan_info))
309 if is (length (jordan_info) = 1)
310 then first (jordan_info) else jordan (A))$
313 Return a list of taylor coefficients for the function f expanded
314 around some arbitrary point VAR where f(var) = EXPR. The first
315 element of the list is f(var) and the last is
316 diff(f(var),var,maxpow).
318 diag_taylor_coefficients (expr, var, maxpow) :=
319 block ([coeffs: [expr]],
320 for k:1 thru maxpow do
321 (expr: diff (expr, var), coeffs: cons (expr/k!, coeffs)),
325 Build a matrix of the form:
327 [ f(0) f'(0) f''(0) ]
331 except with coefficients taken from the COEFFS list, substituting
332 EIGENVALUE for VAR, where SIZE is the size of the matrix. This is
333 the result of Taylor expanding a f(A), where A is a Jordan block,
334 around its eigenvalue.
336 diag_taylor_expand_block (coeffs, var, eigenvalue, size) := (
337 coeffs: makelist (subst (eigenvalue, var, coeffs[i]), i, 1, size),
339 makelist (makelist (if is (i<k) then 0 else coeffs[i-k+1], i, 1, size),
343 Calculate the value of an analytic function on the matrix
344 represented by the Jordan list JORDAN. The function is given by EXPR
347 diag_mat_function_jordan (jordan, expr, var) :=
348 block ([coeffs, blocks: [],
349 max_degree: lmax (map (second, jordan)) - 1],
351 Expand f(var) about some arbitrary point as a Taylor series. The
352 Jordan matrix is diagonal plus a nilpotent matrix order one less
353 than the largest block. We need coefficients the same order as
356 coeffs: diag_taylor_coefficients (expr, var, max_degree),
358 We calculate the value of EXPR on each Jordan block using
359 DIAG_TAYLOR_EXPAND_BLOCK. The degrees in JORDAN_LST are known to
360 be decreasing, so we can be slightly clever about not computing
363 for jordan_lst in jordan do
364 block ([cached_block:
365 diag_taylor_expand_block (coeffs, var,
367 second (jordan_lst)),
368 cached_size: second(jordan_lst)],
369 for size in rest (jordan_lst) do
370 (if is (size # cached_size) then
375 makelist (cached_block[i,j], j, 1, size),
377 blocks: cons (cached_block, blocks))),
378 diag (reverse (blocks)))$
381 Take an analytic function, f, and a matrix, A, and calculate f(A) by
382 means of the associated Taylor series.
384 mat_function (f, A) :=
385 if not (matrixp (A)) then
388 block ([jj: jordan (A), var: gensym(), modemat],
389 modemat: ModeMatrix (A, jj),
390 modemat . diag_mat_function_jordan (jj, apply (f, [var]), var)