74
74
75
75
# ###############################################################################
76
76
#
77
- # Matrix normal form over (euclidean) rings (hermite_form)
77
+ # Matrix normal form over (euclidean) domains (hermite_form)
78
78
#
79
79
# ###############################################################################
80
80
@@ -87,7 +87,7 @@ It is assumed that `base_ring(A)` is euclidean.
87
87
# Keyword arguments
88
88
* `reduced`: Whether the columns of $H$ which contain a pivot are reduced. The
89
89
default is `true`.
90
- * `shape`: Whether $R $ is an upper-right (`:upper`, default) or lower-left
90
+ * `shape`: Whether $H $ is an upper-right (`:upper`, default) or lower-left
91
91
(`:lower`) echelon form.
92
92
* `trim`: By default, $H$ will have as many rows as $A$ and potentially involve
93
93
rows only containing zeros. If `trim = true`, these rows are removed, so that
@@ -143,3 +143,251 @@ function hermite_form_with_transformation(A::MatElem{<:RingElement}; reduced::Bo
143
143
end
144
144
return H, U
145
145
end
146
+
147
+ # ###############################################################################
148
+ #
149
+ # Matrix normal form over principal ideal rings (howell_form)
150
+ #
151
+ # ###############################################################################
152
+
153
+ # Works in theory over any principal ideal ring; internally we require functions
154
+ # annihilator, gcdxx and _div_for_howell_form
155
+
156
+ # Swap rows so that there is a non-zero entry in A[start_row, col].
157
+ # Return 0 if this is not possible, 1 if no swapping was necessary and -1
158
+ # if rows were swapped.
159
+ function _pivot (A:: MatElem , start_row:: Int , col:: Int )
160
+ if ! is_zero_entry (A, start_row, col)
161
+ return 1
162
+ end
163
+
164
+ for j in start_row + 1 : nrows (A)
165
+ if ! is_zero_entry (A, j, col)
166
+ swap_rows! (A, j, start_row)
167
+ return - 1
168
+ end
169
+ end
170
+
171
+ return 0
172
+ end
173
+
174
+ function triangularize! (A:: MatElem{<:RingElement} )
175
+ n = nrows (A)
176
+ m = ncols (A)
177
+ d = one (base_ring (A))
178
+ row = 1
179
+ col = 1
180
+ while row <= nrows (A) && col <= ncols (A)
181
+ t = _pivot (A, row, col)
182
+ if iszero (t)
183
+ col = col + 1
184
+ continue
185
+ end
186
+ d = d* t
187
+ for i in (row + 1 ): nrows (A)
188
+ if is_zero_entry (A, i, col)
189
+ continue
190
+ end
191
+
192
+ b, q = divides (A[i, col], A[row, col])
193
+
194
+ if b
195
+ for k in col: m
196
+ A[i, k] = A[i, k] - q* A[row, k]
197
+ end
198
+ else
199
+ g, s, t, u, v = gcdxx (A[row, col], A[i, col])
200
+
201
+ for k in col: m
202
+ t1 = s* A[row, k] + t* A[i, k]
203
+ t2 = u* A[row, k] + v* A[i, k]
204
+ A[row, k] = t1
205
+ A[i, k] = t2
206
+ end
207
+ end
208
+ end
209
+ row = row + 1
210
+ col = col + 1
211
+ end
212
+ return d
213
+ end
214
+
215
+ # Naive version of inplace strong echelon form
216
+ # It is assumed that A has more rows then columns.
217
+ function strong_echelon_form_naive! (A:: MatElem{<:RingElement} )
218
+ n = nrows (A)
219
+ m = ncols (A)
220
+
221
+ @assert n >= m
222
+
223
+ triangularize! (A)
224
+
225
+ T = zero_matrix (base_ring (A), 1 , ncols (A))
226
+ for j in 1 : m
227
+ if ! is_zero_entry (A, j, j)
228
+ # Normalize/canonicalize the pivot
229
+ u = canonical_unit (A[j, j])
230
+ if ! is_one (u)
231
+ uinv = inv (u)
232
+ for i in j: ncols (A)
233
+ A[j, i] = uinv* A[j, i]
234
+ end
235
+ end
236
+
237
+ # This is the reduction
238
+ for i in 1 : j - 1
239
+ if is_zero_entry (A, i, j)
240
+ continue
241
+ end
242
+ q = _div_for_howell_form (A[i, j], A[j, j])
243
+ for l in i: m
244
+ A[i, l] = A[i, l] - q* A[j, l]
245
+ end
246
+ end
247
+
248
+ a = annihilator (A[j, j])
249
+
250
+ for k in 1 : m
251
+ T[1 , k] = a* A[j, k]
252
+ end
253
+ else
254
+ for k in 1 : m
255
+ T[1 , k] = A[j, k]
256
+ end
257
+ end
258
+
259
+ for i in j+ 1 : m
260
+
261
+ if is_zero_entry (T, 1 , i)
262
+ continue
263
+ end
264
+
265
+ if is_zero_entry (A, i, i)
266
+ for k in i: m
267
+ T[1 , k], A[i, k] = A[i, k], T[1 , k]
268
+ end
269
+ else
270
+ b, q = divides (T[1 , i], A[i, i])
271
+ if b
272
+ for k in i: m
273
+ T[1 , k] = T[1 , k] - q* A[i, k]
274
+ end
275
+ else
276
+ g, s, t, u, v = gcdxx (A[i, i], T[1 , i])
277
+ for k in i: m
278
+ t1 = s* A[i, k] + t* T[1 , k]
279
+ t2 = u* A[i, k] + v* T[1 , k]
280
+ A[i, k] = t1
281
+ T[1 , k] = t2
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end
287
+ return A
288
+ end
289
+
290
+ function howell_form! (A:: MatElem{<:RingElement} )
291
+ @assert nrows (A) >= ncols (A)
292
+
293
+ strong_echelon_form_naive! (A)
294
+
295
+ # Move the zero rows to the bottom
296
+ for i in 1 : nrows (A)
297
+ if ! is_zero_row (A, i)
298
+ continue
299
+ end
300
+
301
+ j = findfirst (l -> ! is_zero_row (A, l), i + 1 : nrows (A))
302
+ if isnothing (j)
303
+ break
304
+ end
305
+ swap_rows! (A, i, i + j)
306
+ end
307
+ return A
308
+ end
309
+
310
+ @doc raw """
311
+ howell_form(A::MatElem{<:RingElement}; reduced::Bool = true, shape::Symbol = :upper, trim::Bool = false)
312
+
313
+ Return a Howell form $H$ of $A$.
314
+ It is assumed that `base_ring(A)` is a principal ideal ring.
315
+
316
+ # Keyword arguments
317
+ * `reduced`: Whether the columns of $H$ which contain a pivot are reduced. The
318
+ default is `true`.
319
+ * `shape`: Whether $H$ is an upper-right (`:upper`, default) or lower-left
320
+ (`:lower`) echelon form.
321
+ * `trim`: By default, $H$ will have at least as many rows as $A$ and potentially
322
+ involve rows only containing zeros. If `trim = true`, these rows are removed.
323
+
324
+ See also [`howell_form_with_transformation`](@ref).
325
+ """
326
+ function howell_form (A:: MatElem{<:RingElement} ; reduced:: Bool = true , shape:: Symbol = :upper , trim:: Bool = false )
327
+ if shape != = :upper && shape != = :lower
328
+ throw (ArgumentError (" Unsupported argument :$shape for shape: Must be :upper or :lower." ))
329
+ end
330
+
331
+ H = deepcopy (A)
332
+ if shape === :lower
333
+ H = reverse_cols! (H)
334
+ end
335
+
336
+ if nrows (H) < ncols (H)
337
+ H = vcat (H, zero_matrix (base_ring (H), ncols (H) - nrows (H), ncols (H)))
338
+ end
339
+
340
+ howell_form! (H)
341
+
342
+ r = nrows (H)
343
+ # Compute the rank (if necessary)
344
+ if trim
345
+ while r > 0 && is_zero_row (H, r)
346
+ r -= 1
347
+ end
348
+ H = sub (H, 1 : r, 1 : ncols (H))
349
+ end
350
+
351
+ if shape === :lower
352
+ reverse_cols! (H)
353
+ reverse_rows! (H)
354
+ end
355
+ return H
356
+ end
357
+
358
+ @doc raw """
359
+ howell_form_with_transformation(A::MatElem{<:RingElement}; reduced::Bool = true, shape::Symbol = :upper)
360
+
361
+ Return a Howell form $H$ of $A$ and a matrix $U$ with $H = UA$.
362
+ Notice that $H$ may have more rows than $A$ and hence $U$ may not be invertible.
363
+ It is assumed that `base_ring(A)` is a principal ideal ring
364
+
365
+ See [`hermite_form`](@ref) for the keyword arguments.
366
+ """
367
+ function howell_form_with_transformation (A:: MatElem{<:RingElement} ; reduced:: Bool = true , shape:: Symbol = :upper )
368
+ if shape != = :upper && shape != = :lower
369
+ throw (ArgumentError (" Unsupported argument :$shape for shape: Must be :upper or :lower." ))
370
+ end
371
+
372
+ if shape === :lower
373
+ B = hcat (reverse_cols (A), identity_matrix (A, nrows (A)))
374
+ else
375
+ B = hcat (A, identity_matrix (A, nrows (A)))
376
+ end
377
+ if nrows (B) < ncols (B)
378
+ B = vcat (B, zero (A, ncols (B) - nrows (B), ncols (B)))
379
+ end
380
+
381
+ howell_form! (B)
382
+
383
+ m = max (nrows (A), ncols (A))
384
+ H = sub (B, 1 : m, 1 : ncols (A))
385
+ U = sub (B, 1 : m, ncols (A) + 1 : ncols (B))
386
+
387
+ if shape === :lower
388
+ reverse_cols! (H)
389
+ reverse_rows! (H)
390
+ reverse_rows! (U)
391
+ end
392
+ return H, U
393
+ end
0 commit comments