From 992dc2e07cf30342f97162c39ad722b6d5dccaac Mon Sep 17 00:00:00 2001 From: Feng Date: Sat, 3 Oct 2026 11:31:15 +0000 Subject: [PATCH 1/7] Bugfix and Optimization in a bulk --- ReadMe.md | 351 +- bench/bench.cc | 294 + bench/kept.txt | 8 + bench/results.md | 197 + docs/modern_cpp_optimization_plan.md | 192 + docs/opencode_sharded_review.md | 364 + examples/cases/0000_create.hpp | 48 +- examples/cases/0001_apply.hpp | 4 +- examples/cases/0002_access.hpp | 2 +- examples/cases/0003_clone.hpp | 6 +- examples/cases/0004_data.hpp | 4 +- examples/cases/0006_devide_equal.hpp | 2 +- examples/cases/0007_slicing.hpp | 6 +- examples/cases/0008_inverse.hpp | 2 +- examples/cases/0009_save_load.hpp | 26 +- examples/cases/0010_minus_equal.hpp | 8 +- examples/cases/0011_multiply_equal.hpp | 2 +- examples/cases/0012_plus_equal.hpp | 6 +- examples/cases/0013_prefix.hpp | 2 +- examples/cases/0014_sin.hpp | 4 +- examples/cases/0015_sinh.hpp | 4 +- examples/cases/0016_eye.hpp | 2 +- examples/cases/0017_make_view.hpp | 10 +- examples/cases/0018_conv.hpp | 23 +- examples/cases/0019_lu_decomposition.hpp | 12 +- .../cases/0020_gauss_jordan_elimination.hpp | 4 +- .../0021_singular_value_decomposition.hpp | 20 +- examples/cases/0022_save_with_colromap.hpp | 54 +- examples/cases/0023_magic.hpp | 2 +- examples/cases/0024_pooling.hpp | 16 +- examples/cases/0025_global_save_as_bmp.hpp | 2 +- examples/cases/0026_mandelbrot.hpp | 6 +- examples/cases/0027_julia_set.hpp | 6 +- examples/cases/0028_plot.hpp | 12 +- examples/cases/0029_meshgrid.hpp | 4 +- examples/cases/0030_arange.hpp | 2 +- examples/cases/0031_clip.hpp | 8 +- examples/cases/0032_empty.hpp | 2 +- examples/cases/0034_astype.hpp | 4 +- matrix.hpp | 7288 ++++++++++------- tests/api/access.cc | 93 + tests/api/allocator.cc | 65 + tests/api/arithmetic.cc | 93 + tests/api/construction.cc | 129 + tests/api/elementwise.cc | 152 + tests/api/image.cc | 57 + tests/api/io.cc | 75 + tests/api/linalg.cc | 206 + tests/api/link_a.cc | 33 + tests/api/link_b.cc | 20 + tests/api/reductions.cc | 110 + tests/api/shape.cc | 52 + tests/api/signal.cc | 66 + tests/api/views.cc | 58 + tests/cases/load_npy.hpp | 8 +- tests/cases/opencv.hpp | 2 +- tests/cases/s10_r3.hpp | 303 + tests/cases/s1_f16.hpp | 121 + tests/cases/s2_death.hpp | 124 + tests/cases/s2_r1.hpp | 30 + tests/cases/s2_r2.hpp | 387 + tests/cases/s2_r3.hpp | 143 + tests/cases/s2_r4.hpp | 318 + tests/cases/s3_alloc.hpp | 139 + tests/cases/s3_r1.hpp | 340 + tests/cases/s3_r2.hpp | 226 + tests/cases/s3_r3.hpp | 78 + tests/cases/s3_r4.hpp | 51 + tests/cases/s3_r5.hpp | 263 + tests/cases/s4_r1.hpp | 301 + tests/cases/s4_r2.hpp | 440 + tests/cases/s4_r3.hpp | 127 + tests/cases/s4_r4.hpp | 85 + tests/cases/s4_r5.hpp | 82 + tests/cases/s5_r1.hpp | 587 ++ tests/cases/s5_r2.hpp | 553 ++ tests/cases/s5_r3.hpp | 284 + tests/cases/s5_r4.hpp | 365 + tests/cases/s5_r5.hpp | 59 + tests/cases/s6_r1.hpp | 299 + tests/cases/s6_r3.hpp | 227 + tests/cases/s6_r4.hpp | 132 + tests/cases/s6_r5.hpp | 336 + tests/cases/s7_r1.hpp | 331 + tests/cases/s7_r2.hpp | 120 + tests/cases/s7_r3.hpp | 434 + tests/cases/s7_r4.hpp | 301 + tests/cases/s7_r5.hpp | 332 + tests/cases/s7_r6.hpp | 163 + tests/cases/s8_fixtures.hpp | 199 + tests/cases/s8_r1.hpp | 259 + tests/cases/s8_r2.hpp | 335 + tests/cases/s8_r3.hpp | 130 + tests/cases/s9_r1.hpp | 126 + tests/cases/s9_r4.hpp | 148 + tests/cases/s9_r5.hpp | 188 + tests/cases/s9_r6.hpp | 181 + tests/compile_fail/S3/bool.cc | 11 + tests/compile_fail/S3/string.cc | 13 + tests/compile_fail/S3/throwing_copy.cc | 20 + .../S4/make_mutable_view_const_rvalue.cc | 18 + .../S4/make_mutable_view_temporary.cc | 18 + .../compile_fail/S4/make_view_const_rvalue.cc | 18 + tests/compile_fail/S4/make_view_temporary.cc | 11 + .../S4/matrix_view_const_rvalue.cc | 18 + .../compile_fail/S4/matrix_view_temporary.cc | 18 + .../S4/mutable_matrix_view_const_owner.cc | 18 + .../S4/mutable_matrix_view_const_rvalue.cc | 18 + .../S4/mutable_matrix_view_temporary.cc | 18 + .../S4/mutable_view_const_owner.cc | 18 + tests/compile_fail/S7/det_int.cc | 12 + tests/compile_fail/S7/inverse_int.cc | 13 + tests/compile_fail/S7/lu_factor_int.cc | 12 + tests/compile_fail/S9/arg_real.cc | 12 + .../S9/cholesky_decomposition_int.cc | 13 + tests/compile_fail/S9/cholesky_int.cc | 12 + tests/compile_fail/S9/conj_real.cc | 12 + tests/compile_fail/S9/ctranspose_real.cc | 12 + tests/compile_fail/S9/det_int.cc | 12 + tests/compile_fail/S9/expm_int.cc | 12 + tests/compile_fail/S9/expm_status_int.cc | 13 + tests/compile_fail/S9/imag_real.cc | 12 + tests/compile_fail/S9/inverse_int.cc | 14 + tests/compile_fail/S9/inverse_status_int.cc | 13 + tests/compile_fail/S9/lu_decomposition_int.cc | 12 + tests/compile_fail/S9/lu_factor_int.cc | 12 + tests/compile_fail/S9/lu_solver_int.cc | 13 + .../S9/nodiscard_inverse_status.cc | 12 + tests/compile_fail/S9/nodiscard_load_npy.cc | 10 + tests/compile_fail/S9/nodiscard_lu_factor.cc | 10 + tests/compile_fail/S9/nodiscard_save.cc | 10 + tests/compile_fail/S9/nodiscard_transpose.cc | 12 + tests/compile_fail/S9/norm_real.cc | 12 + tests/compile_fail/S9/pinv_int.cc | 14 + tests/compile_fail/S9/pinverse_int.cc | 15 + tests/compile_fail/S9/polar_real.cc | 12 + tests/compile_fail/S9/proj_real.cc | 12 + tests/compile_fail/S9/real_real.cc | 12 + tests/compile_fail/S9/row_echelon_int.cc | 12 + tests/compile_fail/S9/rref_int.cc | 14 + tests/compile_fail/S9/solve_int.cc | 13 + tests/compile_fail/S9/svd_factor_int.cc | 12 + tests/compile_fail/S9/svd_int.cc | 14 + tests/config/tu_a.cc | 37 + tests/config/tu_b.cc | 14 + tests/fixtures/s5/bool.npy | Bin 0 -> 134 bytes tests/fixtures/s5/c16_be.npy | Bin 0 -> 192 bytes tests/fixtures/s5/c16_le.npy | Bin 0 -> 192 bytes tests/fixtures/s5/c8_be.npy | Bin 0 -> 160 bytes tests/fixtures/s5/c8_le.npy | Bin 0 -> 160 bytes tests/fixtures/s5/f2.npy | Bin 0 -> 140 bytes tests/fixtures/s5/f4_be.npy | Bin 0 -> 152 bytes tests/fixtures/s5/f4_le.npy | Bin 0 -> 152 bytes tests/fixtures/s5/f8_be.npy | Bin 0 -> 176 bytes tests/fixtures/s5/f8_le.npy | Bin 0 -> 176 bytes tests/fixtures/s5/f8_v2.npy | Bin 0 -> 176 bytes tests/fixtures/s5/f8_v3.npy | Bin 0 -> 176 bytes tests/fixtures/s5/fortran_2x3.npy | Bin 0 -> 176 bytes tests/fixtures/s5/i1.npy | Bin 0 -> 134 bytes tests/fixtures/s5/i2_be.npy | Bin 0 -> 140 bytes tests/fixtures/s5/i2_le.npy | Bin 0 -> 140 bytes tests/fixtures/s5/i4_be.npy | Bin 0 -> 152 bytes tests/fixtures/s5/i4_le.npy | Bin 0 -> 152 bytes tests/fixtures/s5/i8_be.npy | Bin 0 -> 176 bytes tests/fixtures/s5/i8_le.npy | Bin 0 -> 176 bytes tests/fixtures/s5/make_npy.py | 57 + tests/fixtures/s5/object.npy | Bin 0 -> 295 bytes tests/fixtures/s5/one_d.npy | Bin 0 -> 152 bytes tests/fixtures/s5/structured.npy | Bin 0 -> 200 bytes tests/fixtures/s5/three_d.npy | Bin 0 -> 192 bytes tests/fixtures/s5/u1.npy | Bin 0 -> 134 bytes tests/fixtures/s5/u2_be.npy | Bin 0 -> 140 bytes tests/fixtures/s5/u2_le.npy | Bin 0 -> 140 bytes tests/fixtures/s5/u4_be.npy | Bin 0 -> 152 bytes tests/fixtures/s5/u4_le.npy | Bin 0 -> 152 bytes tests/fixtures/s5/u8_be.npy | Bin 0 -> 176 bytes tests/fixtures/s5/u8_le.npy | Bin 0 -> 176 bytes tests/fixtures/s7/cholesky_c16_6_A.npy | Bin 0 -> 704 bytes tests/fixtures/s7/cholesky_c16_6_L.npy | Bin 0 -> 704 bytes tests/fixtures/s7/cholesky_f8_5_A.npy | Bin 0 -> 328 bytes tests/fixtures/s7/cholesky_f8_5_L.npy | Bin 0 -> 328 bytes tests/fixtures/s7/cholesky_f8_8_A.npy | Bin 0 -> 640 bytes tests/fixtures/s7/cholesky_f8_8_L.npy | Bin 0 -> 640 bytes tests/fixtures/s7/det_c16_6_A.npy | Bin 0 -> 704 bytes tests/fixtures/s7/det_c16_6_D.npy | Bin 0 -> 144 bytes tests/fixtures/s7/det_f8_3_singular_A.npy | Bin 0 -> 200 bytes tests/fixtures/s7/det_f8_3_singular_D.npy | Bin 0 -> 136 bytes tests/fixtures/s7/det_f8_4_A.npy | Bin 0 -> 256 bytes tests/fixtures/s7/det_f8_4_D.npy | Bin 0 -> 136 bytes tests/fixtures/s7/expm_c16_4_A.npy | Bin 0 -> 384 bytes tests/fixtures/s7/expm_c16_4_E.npy | Bin 0 -> 384 bytes tests/fixtures/s7/expm_f8_4_A.npy | Bin 0 -> 256 bytes tests/fixtures/s7/expm_f8_4_E.npy | Bin 0 -> 256 bytes tests/fixtures/s7/expm_f8_6_norm5_A.npy | Bin 0 -> 416 bytes tests/fixtures/s7/expm_f8_6_norm5_E.npy | Bin 0 -> 416 bytes tests/fixtures/s7/inv_c16_4_A.npy | Bin 0 -> 384 bytes tests/fixtures/s7/inv_c16_4_X.npy | Bin 0 -> 384 bytes tests/fixtures/s7/inv_f8_5_A.npy | Bin 0 -> 328 bytes tests/fixtures/s7/inv_f8_5_X.npy | Bin 0 -> 328 bytes tests/fixtures/s7/inv_f8_8_A.npy | Bin 0 -> 640 bytes tests/fixtures/s7/inv_f8_8_X.npy | Bin 0 -> 640 bytes tests/fixtures/s7/lu_solve_c16_5x2_A.npy | Bin 0 -> 528 bytes tests/fixtures/s7/lu_solve_c16_5x2_B.npy | Bin 0 -> 288 bytes tests/fixtures/s7/lu_solve_c16_5x2_X.npy | Bin 0 -> 288 bytes tests/fixtures/s7/lu_solve_f8_4x2_A.npy | Bin 0 -> 256 bytes tests/fixtures/s7/lu_solve_f8_4x2_B.npy | Bin 0 -> 192 bytes tests/fixtures/s7/lu_solve_f8_4x2_X.npy | Bin 0 -> 192 bytes tests/fixtures/s7/lu_solve_f8_8x3_A.npy | Bin 0 -> 640 bytes tests/fixtures/s7/lu_solve_f8_8x3_B.npy | Bin 0 -> 320 bytes tests/fixtures/s7/lu_solve_f8_8x3_X.npy | Bin 0 -> 320 bytes tests/fixtures/s7/make_fixtures.py | 277 + tests/fixtures/s7/manifest.txt | 33 + tests/fixtures/s7/pinv_c16_4x6_rank2_A.npy | Bin 0 -> 512 bytes tests/fixtures/s7/pinv_c16_4x6_rank2_X.npy | Bin 0 -> 512 bytes tests/fixtures/s7/pinv_c16_5x3_A.npy | Bin 0 -> 368 bytes tests/fixtures/s7/pinv_c16_5x3_X.npy | Bin 0 -> 368 bytes tests/fixtures/s7/pinv_f8_3x5_A.npy | Bin 0 -> 248 bytes tests/fixtures/s7/pinv_f8_3x5_X.npy | Bin 0 -> 248 bytes tests/fixtures/s7/pinv_f8_5x5_rank3_A.npy | Bin 0 -> 328 bytes tests/fixtures/s7/pinv_f8_5x5_rank3_X.npy | Bin 0 -> 328 bytes tests/fixtures/s7/pinv_f8_6x4_A.npy | Bin 0 -> 320 bytes tests/fixtures/s7/pinv_f8_6x4_X.npy | Bin 0 -> 320 bytes tests/fixtures/s7/rref_c16_3x4_rank2_A.npy | Bin 0 -> 320 bytes tests/fixtures/s7/rref_c16_3x4_rank2_R.npy | Bin 0 -> 320 bytes tests/fixtures/s7/rref_f8_3x3_A.npy | Bin 0 -> 200 bytes tests/fixtures/s7/rref_f8_3x3_R.npy | Bin 0 -> 200 bytes tests/fixtures/s7/rref_f8_3x5_A.npy | Bin 0 -> 248 bytes tests/fixtures/s7/rref_f8_3x5_R.npy | Bin 0 -> 248 bytes tests/fixtures/s7/rref_f8_4x4_rank2_A.npy | Bin 0 -> 256 bytes tests/fixtures/s7/rref_f8_4x4_rank2_R.npy | Bin 0 -> 256 bytes tests/fixtures/s7/rref_f8_5x3_rank2_A.npy | Bin 0 -> 248 bytes tests/fixtures/s7/rref_f8_5x3_rank2_R.npy | Bin 0 -> 248 bytes tests/fixtures/s7/svdvals_c16_3x6_A.npy | Bin 0 -> 416 bytes tests/fixtures/s7/svdvals_c16_3x6_S.npy | Bin 0 -> 152 bytes tests/fixtures/s7/svdvals_c16_7x4_A.npy | Bin 0 -> 576 bytes tests/fixtures/s7/svdvals_c16_7x4_S.npy | Bin 0 -> 160 bytes tests/fixtures/s7/svdvals_f8_4x7_A.npy | Bin 0 -> 352 bytes tests/fixtures/s7/svdvals_f8_4x7_S.npy | Bin 0 -> 160 bytes tests/fixtures/s7/svdvals_f8_6x6_rank3_A.npy | Bin 0 -> 416 bytes tests/fixtures/s7/svdvals_f8_6x6_rank3_S.npy | Bin 0 -> 176 bytes tests/fixtures/s7/svdvals_f8_8x5_A.npy | Bin 0 -> 448 bytes tests/fixtures/s7/svdvals_f8_8x5_S.npy | Bin 0 -> 168 bytes tests/fixtures/s8/conv_f8_1x6_1x3_A.npy | Bin 0 -> 176 bytes tests/fixtures/s8/conv_f8_1x6_1x3_B.npy | Bin 0 -> 152 bytes tests/fixtures/s8/conv_f8_5x1_2x1_A.npy | Bin 0 -> 168 bytes tests/fixtures/s8/conv_f8_5x1_2x1_B.npy | Bin 0 -> 144 bytes tests/fixtures/s8/conv_f8_7x9_10x11_A.npy | Bin 0 -> 632 bytes tests/fixtures/s8/conv_f8_7x9_10x11_B.npy | Bin 0 -> 1008 bytes tests/fixtures/s8/conv_f8_7x9_1x1_A.npy | Bin 0 -> 632 bytes tests/fixtures/s8/conv_f8_7x9_1x1_B.npy | Bin 0 -> 136 bytes tests/fixtures/s8/conv_f8_7x9_2x4_A.npy | Bin 0 -> 632 bytes tests/fixtures/s8/conv_f8_7x9_2x4_B.npy | Bin 0 -> 192 bytes tests/fixtures/s8/conv_f8_7x9_3x12_A.npy | Bin 0 -> 632 bytes tests/fixtures/s8/conv_f8_7x9_3x12_B.npy | Bin 0 -> 416 bytes tests/fixtures/s8/conv_f8_7x9_3x5_A.npy | Bin 0 -> 632 bytes tests/fixtures/s8/conv_f8_7x9_3x5_B.npy | Bin 0 -> 248 bytes tests/fixtures/s8/conv_f8_7x9_4x3_A.npy | Bin 0 -> 632 bytes tests/fixtures/s8/conv_f8_7x9_4x3_B.npy | Bin 0 -> 224 bytes tests/fixtures/s8/conv_full_f8_1x6_1x3_C.npy | Bin 0 -> 192 bytes tests/fixtures/s8/conv_full_f8_5x1_2x1_C.npy | Bin 0 -> 176 bytes .../fixtures/s8/conv_full_f8_7x9_10x11_C.npy | Bin 0 -> 2560 bytes tests/fixtures/s8/conv_full_f8_7x9_1x1_C.npy | Bin 0 -> 632 bytes tests/fixtures/s8/conv_full_f8_7x9_2x4_C.npy | Bin 0 -> 896 bytes tests/fixtures/s8/conv_full_f8_7x9_3x12_C.npy | Bin 0 -> 1568 bytes tests/fixtures/s8/conv_full_f8_7x9_3x5_C.npy | Bin 0 -> 1064 bytes tests/fixtures/s8/conv_full_f8_7x9_4x3_C.npy | Bin 0 -> 1008 bytes tests/fixtures/s8/conv_full_i4_6x8_1x1_C.npy | Bin 0 -> 320 bytes tests/fixtures/s8/conv_full_i4_6x8_2x4_C.npy | Bin 0 -> 436 bytes tests/fixtures/s8/conv_full_i4_6x8_3x5_C.npy | Bin 0 -> 512 bytes tests/fixtures/s8/conv_full_i4_6x8_9x10_C.npy | Bin 0 -> 1080 bytes tests/fixtures/s8/conv_i4_6x8_1x1_A.npy | Bin 0 -> 320 bytes tests/fixtures/s8/conv_i4_6x8_1x1_B.npy | Bin 0 -> 132 bytes tests/fixtures/s8/conv_i4_6x8_2x4_A.npy | Bin 0 -> 320 bytes tests/fixtures/s8/conv_i4_6x8_2x4_B.npy | Bin 0 -> 160 bytes tests/fixtures/s8/conv_i4_6x8_3x5_A.npy | Bin 0 -> 320 bytes tests/fixtures/s8/conv_i4_6x8_3x5_B.npy | Bin 0 -> 188 bytes tests/fixtures/s8/conv_i4_6x8_9x10_A.npy | Bin 0 -> 320 bytes tests/fixtures/s8/conv_i4_6x8_9x10_B.npy | Bin 0 -> 488 bytes tests/fixtures/s8/conv_same_f8_1x6_1x3_C.npy | Bin 0 -> 176 bytes tests/fixtures/s8/conv_same_f8_5x1_2x1_C.npy | Bin 0 -> 168 bytes .../fixtures/s8/conv_same_f8_7x9_10x11_C.npy | Bin 0 -> 632 bytes tests/fixtures/s8/conv_same_f8_7x9_1x1_C.npy | Bin 0 -> 632 bytes tests/fixtures/s8/conv_same_f8_7x9_2x4_C.npy | Bin 0 -> 632 bytes tests/fixtures/s8/conv_same_f8_7x9_3x12_C.npy | Bin 0 -> 632 bytes tests/fixtures/s8/conv_same_f8_7x9_3x5_C.npy | Bin 0 -> 632 bytes tests/fixtures/s8/conv_same_f8_7x9_4x3_C.npy | Bin 0 -> 632 bytes tests/fixtures/s8/conv_same_i4_6x8_1x1_C.npy | Bin 0 -> 320 bytes tests/fixtures/s8/conv_same_i4_6x8_2x4_C.npy | Bin 0 -> 320 bytes tests/fixtures/s8/conv_same_i4_6x8_3x5_C.npy | Bin 0 -> 320 bytes tests/fixtures/s8/conv_same_i4_6x8_9x10_C.npy | Bin 0 -> 320 bytes tests/fixtures/s8/conv_valid_f8_1x6_1x3_C.npy | Bin 0 -> 160 bytes tests/fixtures/s8/conv_valid_f8_5x1_2x1_C.npy | Bin 0 -> 160 bytes .../fixtures/s8/conv_valid_f8_7x9_10x11_C.npy | Bin 0 -> 224 bytes tests/fixtures/s8/conv_valid_f8_7x9_1x1_C.npy | Bin 0 -> 632 bytes tests/fixtures/s8/conv_valid_f8_7x9_2x4_C.npy | Bin 0 -> 416 bytes tests/fixtures/s8/conv_valid_f8_7x9_3x5_C.npy | Bin 0 -> 328 bytes tests/fixtures/s8/conv_valid_f8_7x9_4x3_C.npy | Bin 0 -> 352 bytes tests/fixtures/s8/conv_valid_i4_6x8_1x1_C.npy | Bin 0 -> 320 bytes tests/fixtures/s8/conv_valid_i4_6x8_2x4_C.npy | Bin 0 -> 228 bytes tests/fixtures/s8/conv_valid_i4_6x8_3x5_C.npy | Bin 0 -> 192 bytes .../fixtures/s8/conv_valid_i4_6x8_9x10_C.npy | Bin 0 -> 176 bytes tests/fixtures/s8/fft2_c16_1x1_C.npy | Bin 0 -> 144 bytes tests/fixtures/s8/fft2_c16_1x7_C.npy | Bin 0 -> 240 bytes tests/fixtures/s8/fft2_c16_31x17_C.npy | Bin 0 -> 8560 bytes tests/fixtures/s8/fft2_c16_3x5_C.npy | Bin 0 -> 368 bytes tests/fixtures/s8/fft2_c16_4x6_C.npy | Bin 0 -> 512 bytes tests/fixtures/s8/fft2_c16_7x11_C.npy | Bin 0 -> 1360 bytes tests/fixtures/s8/fft2_c16_8x8_C.npy | Bin 0 -> 1152 bytes tests/fixtures/s8/fft2_c8_3x5_C.npy | Bin 0 -> 248 bytes tests/fixtures/s8/fft2_c8_8x8_C.npy | Bin 0 -> 640 bytes tests/fixtures/s8/fft2_f4_13x1_C.npy | Bin 0 -> 232 bytes tests/fixtures/s8/fft2_f4_16x32_C.npy | Bin 0 -> 4224 bytes tests/fixtures/s8/fft2_f4_1x16_C.npy | Bin 0 -> 256 bytes tests/fixtures/s8/fft2_f4_1x2_C.npy | Bin 0 -> 144 bytes tests/fixtures/s8/fft2_f4_3x5_C.npy | Bin 0 -> 248 bytes tests/fixtures/s8/fft2_f4_7x11_C.npy | Bin 0 -> 744 bytes tests/fixtures/s8/fft2_f8_13x1_C.npy | Bin 0 -> 336 bytes tests/fixtures/s8/fft2_f8_16x32_C.npy | Bin 0 -> 8320 bytes tests/fixtures/s8/fft2_f8_1x16_C.npy | Bin 0 -> 384 bytes tests/fixtures/s8/fft2_f8_1x1_C.npy | Bin 0 -> 144 bytes tests/fixtures/s8/fft2_f8_1x2_C.npy | Bin 0 -> 160 bytes tests/fixtures/s8/fft2_f8_1x7_C.npy | Bin 0 -> 240 bytes tests/fixtures/s8/fft2_f8_31x17_C.npy | Bin 0 -> 8560 bytes tests/fixtures/s8/fft2_f8_3x5_C.npy | Bin 0 -> 368 bytes tests/fixtures/s8/fft2_f8_4x6_C.npy | Bin 0 -> 512 bytes tests/fixtures/s8/fft2_f8_64x64_C.npy | Bin 0 -> 65664 bytes tests/fixtures/s8/fft2_f8_7x11_C.npy | Bin 0 -> 1360 bytes tests/fixtures/s8/fft2_f8_8x8_C.npy | Bin 0 -> 1152 bytes tests/fixtures/s8/fft2_i4_1x7_C.npy | Bin 0 -> 240 bytes tests/fixtures/s8/fft2_i4_4x6_C.npy | Bin 0 -> 512 bytes tests/fixtures/s8/fft2_i4_7x11_C.npy | Bin 0 -> 1360 bytes tests/fixtures/s8/fft2_i4_8x8_C.npy | Bin 0 -> 1152 bytes tests/fixtures/s8/fftshift_c16_1x1_C.npy | Bin 0 -> 144 bytes tests/fixtures/s8/fftshift_c16_1x4_C.npy | Bin 0 -> 192 bytes tests/fixtures/s8/fftshift_c16_3x5_C.npy | Bin 0 -> 368 bytes tests/fixtures/s8/fftshift_c16_4x6_C.npy | Bin 0 -> 512 bytes tests/fixtures/s8/fftshift_c16_5x5_C.npy | Bin 0 -> 528 bytes tests/fixtures/s8/fftshift_c16_6x1_C.npy | Bin 0 -> 224 bytes tests/fixtures/s8/fftshift_f8_1x1_C.npy | Bin 0 -> 136 bytes tests/fixtures/s8/fftshift_f8_1x4_C.npy | Bin 0 -> 160 bytes tests/fixtures/s8/fftshift_f8_3x5_C.npy | Bin 0 -> 248 bytes tests/fixtures/s8/fftshift_f8_4x6_C.npy | Bin 0 -> 320 bytes tests/fixtures/s8/fftshift_f8_5x5_C.npy | Bin 0 -> 328 bytes tests/fixtures/s8/fftshift_f8_6x1_C.npy | Bin 0 -> 176 bytes tests/fixtures/s8/fftshift_i4_1x1_C.npy | Bin 0 -> 132 bytes tests/fixtures/s8/fftshift_i4_1x4_C.npy | Bin 0 -> 144 bytes tests/fixtures/s8/fftshift_i4_3x5_C.npy | Bin 0 -> 188 bytes tests/fixtures/s8/fftshift_i4_4x6_C.npy | Bin 0 -> 224 bytes tests/fixtures/s8/fftshift_i4_5x5_C.npy | Bin 0 -> 228 bytes tests/fixtures/s8/fftshift_i4_6x1_C.npy | Bin 0 -> 152 bytes tests/fixtures/s8/fourier_c16_1x1_A.npy | Bin 0 -> 144 bytes tests/fixtures/s8/fourier_c16_1x7_A.npy | Bin 0 -> 240 bytes tests/fixtures/s8/fourier_c16_31x17_A.npy | Bin 0 -> 8560 bytes tests/fixtures/s8/fourier_c16_3x5_A.npy | Bin 0 -> 368 bytes tests/fixtures/s8/fourier_c16_4x6_A.npy | Bin 0 -> 512 bytes tests/fixtures/s8/fourier_c16_7x11_A.npy | Bin 0 -> 1360 bytes tests/fixtures/s8/fourier_c16_8x8_A.npy | Bin 0 -> 1152 bytes tests/fixtures/s8/fourier_c8_3x5_A.npy | Bin 0 -> 248 bytes tests/fixtures/s8/fourier_c8_8x8_A.npy | Bin 0 -> 640 bytes tests/fixtures/s8/fourier_f4_13x1_A.npy | Bin 0 -> 180 bytes tests/fixtures/s8/fourier_f4_16x32_A.npy | Bin 0 -> 2176 bytes tests/fixtures/s8/fourier_f4_1x16_A.npy | Bin 0 -> 192 bytes tests/fixtures/s8/fourier_f4_1x2_A.npy | Bin 0 -> 136 bytes tests/fixtures/s8/fourier_f4_3x5_A.npy | Bin 0 -> 188 bytes tests/fixtures/s8/fourier_f4_7x11_A.npy | Bin 0 -> 436 bytes tests/fixtures/s8/fourier_f8_13x1_A.npy | Bin 0 -> 232 bytes tests/fixtures/s8/fourier_f8_16x32_A.npy | Bin 0 -> 4224 bytes tests/fixtures/s8/fourier_f8_1x16_A.npy | Bin 0 -> 256 bytes tests/fixtures/s8/fourier_f8_1x1_A.npy | Bin 0 -> 136 bytes tests/fixtures/s8/fourier_f8_1x2_A.npy | Bin 0 -> 144 bytes tests/fixtures/s8/fourier_f8_1x7_A.npy | Bin 0 -> 184 bytes tests/fixtures/s8/fourier_f8_31x17_A.npy | Bin 0 -> 4344 bytes tests/fixtures/s8/fourier_f8_3x5_A.npy | Bin 0 -> 248 bytes tests/fixtures/s8/fourier_f8_4x6_A.npy | Bin 0 -> 320 bytes tests/fixtures/s8/fourier_f8_64x64_A.npy | Bin 0 -> 32896 bytes tests/fixtures/s8/fourier_f8_7x11_A.npy | Bin 0 -> 744 bytes tests/fixtures/s8/fourier_f8_8x8_A.npy | Bin 0 -> 640 bytes tests/fixtures/s8/fourier_i4_1x7_A.npy | Bin 0 -> 156 bytes tests/fixtures/s8/fourier_i4_4x6_A.npy | Bin 0 -> 224 bytes tests/fixtures/s8/fourier_i4_7x11_A.npy | Bin 0 -> 436 bytes tests/fixtures/s8/fourier_i4_8x8_A.npy | Bin 0 -> 384 bytes tests/fixtures/s8/ifft2_c16_1x1_C.npy | Bin 0 -> 144 bytes tests/fixtures/s8/ifft2_c16_1x7_C.npy | Bin 0 -> 240 bytes tests/fixtures/s8/ifft2_c16_31x17_C.npy | Bin 0 -> 8560 bytes tests/fixtures/s8/ifft2_c16_3x5_C.npy | Bin 0 -> 368 bytes tests/fixtures/s8/ifft2_c16_4x6_C.npy | Bin 0 -> 512 bytes tests/fixtures/s8/ifft2_c16_7x11_C.npy | Bin 0 -> 1360 bytes tests/fixtures/s8/ifft2_c16_8x8_C.npy | Bin 0 -> 1152 bytes tests/fixtures/s8/ifft2_c8_3x5_C.npy | Bin 0 -> 248 bytes tests/fixtures/s8/ifft2_c8_8x8_C.npy | Bin 0 -> 640 bytes tests/fixtures/s8/ifft2_f4_13x1_C.npy | Bin 0 -> 232 bytes tests/fixtures/s8/ifft2_f4_16x32_C.npy | Bin 0 -> 4224 bytes tests/fixtures/s8/ifft2_f4_1x16_C.npy | Bin 0 -> 256 bytes tests/fixtures/s8/ifft2_f4_1x2_C.npy | Bin 0 -> 144 bytes tests/fixtures/s8/ifft2_f4_3x5_C.npy | Bin 0 -> 248 bytes tests/fixtures/s8/ifft2_f4_7x11_C.npy | Bin 0 -> 744 bytes tests/fixtures/s8/ifft2_f8_13x1_C.npy | Bin 0 -> 336 bytes tests/fixtures/s8/ifft2_f8_16x32_C.npy | Bin 0 -> 8320 bytes tests/fixtures/s8/ifft2_f8_1x16_C.npy | Bin 0 -> 384 bytes tests/fixtures/s8/ifft2_f8_1x1_C.npy | Bin 0 -> 144 bytes tests/fixtures/s8/ifft2_f8_1x2_C.npy | Bin 0 -> 160 bytes tests/fixtures/s8/ifft2_f8_1x7_C.npy | Bin 0 -> 240 bytes tests/fixtures/s8/ifft2_f8_31x17_C.npy | Bin 0 -> 8560 bytes tests/fixtures/s8/ifft2_f8_3x5_C.npy | Bin 0 -> 368 bytes tests/fixtures/s8/ifft2_f8_4x6_C.npy | Bin 0 -> 512 bytes tests/fixtures/s8/ifft2_f8_64x64_C.npy | Bin 0 -> 65664 bytes tests/fixtures/s8/ifft2_f8_7x11_C.npy | Bin 0 -> 1360 bytes tests/fixtures/s8/ifft2_f8_8x8_C.npy | Bin 0 -> 1152 bytes tests/fixtures/s8/ifft2_i4_1x7_C.npy | Bin 0 -> 240 bytes tests/fixtures/s8/ifft2_i4_4x6_C.npy | Bin 0 -> 512 bytes tests/fixtures/s8/ifft2_i4_7x11_C.npy | Bin 0 -> 1360 bytes tests/fixtures/s8/ifft2_i4_8x8_C.npy | Bin 0 -> 1152 bytes tests/fixtures/s8/ifftshift_c16_1x1_C.npy | Bin 0 -> 144 bytes tests/fixtures/s8/ifftshift_c16_1x4_C.npy | Bin 0 -> 192 bytes tests/fixtures/s8/ifftshift_c16_3x5_C.npy | Bin 0 -> 368 bytes tests/fixtures/s8/ifftshift_c16_4x6_C.npy | Bin 0 -> 512 bytes tests/fixtures/s8/ifftshift_c16_5x5_C.npy | Bin 0 -> 528 bytes tests/fixtures/s8/ifftshift_c16_6x1_C.npy | Bin 0 -> 224 bytes tests/fixtures/s8/ifftshift_f8_1x1_C.npy | Bin 0 -> 136 bytes tests/fixtures/s8/ifftshift_f8_1x4_C.npy | Bin 0 -> 160 bytes tests/fixtures/s8/ifftshift_f8_3x5_C.npy | Bin 0 -> 248 bytes tests/fixtures/s8/ifftshift_f8_4x6_C.npy | Bin 0 -> 320 bytes tests/fixtures/s8/ifftshift_f8_5x5_C.npy | Bin 0 -> 328 bytes tests/fixtures/s8/ifftshift_f8_6x1_C.npy | Bin 0 -> 176 bytes tests/fixtures/s8/ifftshift_i4_1x1_C.npy | Bin 0 -> 132 bytes tests/fixtures/s8/ifftshift_i4_1x4_C.npy | Bin 0 -> 144 bytes tests/fixtures/s8/ifftshift_i4_3x5_C.npy | Bin 0 -> 188 bytes tests/fixtures/s8/ifftshift_i4_4x6_C.npy | Bin 0 -> 224 bytes tests/fixtures/s8/ifftshift_i4_5x5_C.npy | Bin 0 -> 228 bytes tests/fixtures/s8/ifftshift_i4_6x1_C.npy | Bin 0 -> 152 bytes tests/fixtures/s8/make_fixtures.py | 190 + tests/fixtures/s8/manifest.txt | 136 + tests/fixtures/s8/shift_c16_1x1_A.npy | Bin 0 -> 144 bytes tests/fixtures/s8/shift_c16_1x4_A.npy | Bin 0 -> 192 bytes tests/fixtures/s8/shift_c16_3x5_A.npy | Bin 0 -> 368 bytes tests/fixtures/s8/shift_c16_4x6_A.npy | Bin 0 -> 512 bytes tests/fixtures/s8/shift_c16_5x5_A.npy | Bin 0 -> 528 bytes tests/fixtures/s8/shift_c16_6x1_A.npy | Bin 0 -> 224 bytes tests/fixtures/s8/shift_f8_1x1_A.npy | Bin 0 -> 136 bytes tests/fixtures/s8/shift_f8_1x4_A.npy | Bin 0 -> 160 bytes tests/fixtures/s8/shift_f8_3x5_A.npy | Bin 0 -> 248 bytes tests/fixtures/s8/shift_f8_4x6_A.npy | Bin 0 -> 320 bytes tests/fixtures/s8/shift_f8_5x5_A.npy | Bin 0 -> 328 bytes tests/fixtures/s8/shift_f8_6x1_A.npy | Bin 0 -> 176 bytes tests/fixtures/s8/shift_i4_1x1_A.npy | Bin 0 -> 132 bytes tests/fixtures/s8/shift_i4_1x4_A.npy | Bin 0 -> 144 bytes tests/fixtures/s8/shift_i4_3x5_A.npy | Bin 0 -> 188 bytes tests/fixtures/s8/shift_i4_4x6_A.npy | Bin 0 -> 224 bytes tests/fixtures/s8/shift_i4_5x5_A.npy | Bin 0 -> 228 bytes tests/fixtures/s8/shift_i4_6x1_A.npy | Bin 0 -> 152 bytes tests/fuzz/corpus/binary/double_2x3.bin | Bin 0 -> 64 bytes tests/fuzz/corpus/binary/double_2x3_short.bin | Bin 0 -> 56 bytes tests/fuzz/corpus/binary/empty_0x3.bin | Bin 0 -> 16 bytes tests/fuzz/corpus/binary/int32_2x2.bin | Bin 0 -> 32 bytes tests/fuzz/corpus/bmp/rgb24_bottom_up_3x2.bmp | Bin 0 -> 78 bytes tests/fuzz/corpus/bmp/rgb24_top_down_3x2.bmp | Bin 0 -> 78 bytes tests/fuzz/corpus/bmp/rgb24_trailing_1x1.bmp | Bin 0 -> 61 bytes tests/fuzz/corpus/bmp/rgb24_truncated.bmp | Bin 0 -> 74 bytes tests/fuzz/corpus/bmp/rgb32_2x2.bmp | Bin 0 -> 70 bytes tests/fuzz/corpus/npy/c16_le.npy | Bin 0 -> 192 bytes tests/fuzz/corpus/npy/f8_be.npy | Bin 0 -> 176 bytes tests/fuzz/corpus/npy/f8_le.npy | Bin 0 -> 176 bytes tests/fuzz/corpus/npy/f8_le_truncated.npy | Bin 0 -> 171 bytes tests/fuzz/corpus/npy/fortran_2x3.npy | Bin 0 -> 176 bytes tests/fuzz/corpus/npy/i4_be.npy | Bin 0 -> 152 bytes tests/fuzz/corpus/npy/one_d.npy | Bin 0 -> 152 bytes tests/fuzz/corpus/npy/structured.npy | Bin 0 -> 200 bytes tests/fuzz/corpus/npy/u1.npy | Bin 0 -> 134 bytes tests/fuzz/corpus/text/complex_2x2.txt | 2 + tests/fuzz/corpus/text/float_exp.txt | 2 + tests/fuzz/corpus/text/int_tabs_crlf.txt | 2 + tests/fuzz/corpus/text/ragged.txt | 2 + tests/fuzz/corpus/text/real_2x3.txt | 2 + tests/fuzz/corpus/text/unclosed.txt | 1 + tests/fuzz/fuzz_binary.cc | 10 + tests/fuzz/fuzz_bmp.cc | 10 + tests/fuzz/fuzz_checks.hpp | 136 + tests/fuzz/fuzz_npy.cc | 10 + tests/fuzz/fuzz_text.cc | 10 + tests/fuzz/make_corpus.py | 62 + tests/oracle/s7_eigen.cc | 211 + tests/test.cc | 41 +- 482 files changed, 18674 insertions(+), 3070 deletions(-) create mode 100644 bench/bench.cc create mode 100644 bench/kept.txt create mode 100644 bench/results.md create mode 100644 docs/modern_cpp_optimization_plan.md create mode 100644 docs/opencode_sharded_review.md create mode 100644 tests/api/access.cc create mode 100644 tests/api/allocator.cc create mode 100644 tests/api/arithmetic.cc create mode 100644 tests/api/construction.cc create mode 100644 tests/api/elementwise.cc create mode 100644 tests/api/image.cc create mode 100644 tests/api/io.cc create mode 100644 tests/api/linalg.cc create mode 100644 tests/api/link_a.cc create mode 100644 tests/api/link_b.cc create mode 100644 tests/api/reductions.cc create mode 100644 tests/api/shape.cc create mode 100644 tests/api/signal.cc create mode 100644 tests/api/views.cc create mode 100644 tests/cases/s10_r3.hpp create mode 100644 tests/cases/s1_f16.hpp create mode 100644 tests/cases/s2_death.hpp create mode 100644 tests/cases/s2_r1.hpp create mode 100644 tests/cases/s2_r2.hpp create mode 100644 tests/cases/s2_r3.hpp create mode 100644 tests/cases/s2_r4.hpp create mode 100644 tests/cases/s3_alloc.hpp create mode 100644 tests/cases/s3_r1.hpp create mode 100644 tests/cases/s3_r2.hpp create mode 100644 tests/cases/s3_r3.hpp create mode 100644 tests/cases/s3_r4.hpp create mode 100644 tests/cases/s3_r5.hpp create mode 100644 tests/cases/s4_r1.hpp create mode 100644 tests/cases/s4_r2.hpp create mode 100644 tests/cases/s4_r3.hpp create mode 100644 tests/cases/s4_r4.hpp create mode 100644 tests/cases/s4_r5.hpp create mode 100644 tests/cases/s5_r1.hpp create mode 100644 tests/cases/s5_r2.hpp create mode 100644 tests/cases/s5_r3.hpp create mode 100644 tests/cases/s5_r4.hpp create mode 100644 tests/cases/s5_r5.hpp create mode 100644 tests/cases/s6_r1.hpp create mode 100644 tests/cases/s6_r3.hpp create mode 100644 tests/cases/s6_r4.hpp create mode 100644 tests/cases/s6_r5.hpp create mode 100644 tests/cases/s7_r1.hpp create mode 100644 tests/cases/s7_r2.hpp create mode 100644 tests/cases/s7_r3.hpp create mode 100644 tests/cases/s7_r4.hpp create mode 100644 tests/cases/s7_r5.hpp create mode 100644 tests/cases/s7_r6.hpp create mode 100644 tests/cases/s8_fixtures.hpp create mode 100644 tests/cases/s8_r1.hpp create mode 100644 tests/cases/s8_r2.hpp create mode 100644 tests/cases/s8_r3.hpp create mode 100644 tests/cases/s9_r1.hpp create mode 100644 tests/cases/s9_r4.hpp create mode 100644 tests/cases/s9_r5.hpp create mode 100644 tests/cases/s9_r6.hpp create mode 100644 tests/compile_fail/S3/bool.cc create mode 100644 tests/compile_fail/S3/string.cc create mode 100644 tests/compile_fail/S3/throwing_copy.cc create mode 100644 tests/compile_fail/S4/make_mutable_view_const_rvalue.cc create mode 100644 tests/compile_fail/S4/make_mutable_view_temporary.cc create mode 100644 tests/compile_fail/S4/make_view_const_rvalue.cc create mode 100644 tests/compile_fail/S4/make_view_temporary.cc create mode 100644 tests/compile_fail/S4/matrix_view_const_rvalue.cc create mode 100644 tests/compile_fail/S4/matrix_view_temporary.cc create mode 100644 tests/compile_fail/S4/mutable_matrix_view_const_owner.cc create mode 100644 tests/compile_fail/S4/mutable_matrix_view_const_rvalue.cc create mode 100644 tests/compile_fail/S4/mutable_matrix_view_temporary.cc create mode 100644 tests/compile_fail/S4/mutable_view_const_owner.cc create mode 100644 tests/compile_fail/S7/det_int.cc create mode 100644 tests/compile_fail/S7/inverse_int.cc create mode 100644 tests/compile_fail/S7/lu_factor_int.cc create mode 100644 tests/compile_fail/S9/arg_real.cc create mode 100644 tests/compile_fail/S9/cholesky_decomposition_int.cc create mode 100644 tests/compile_fail/S9/cholesky_int.cc create mode 100644 tests/compile_fail/S9/conj_real.cc create mode 100644 tests/compile_fail/S9/ctranspose_real.cc create mode 100644 tests/compile_fail/S9/det_int.cc create mode 100644 tests/compile_fail/S9/expm_int.cc create mode 100644 tests/compile_fail/S9/expm_status_int.cc create mode 100644 tests/compile_fail/S9/imag_real.cc create mode 100644 tests/compile_fail/S9/inverse_int.cc create mode 100644 tests/compile_fail/S9/inverse_status_int.cc create mode 100644 tests/compile_fail/S9/lu_decomposition_int.cc create mode 100644 tests/compile_fail/S9/lu_factor_int.cc create mode 100644 tests/compile_fail/S9/lu_solver_int.cc create mode 100644 tests/compile_fail/S9/nodiscard_inverse_status.cc create mode 100644 tests/compile_fail/S9/nodiscard_load_npy.cc create mode 100644 tests/compile_fail/S9/nodiscard_lu_factor.cc create mode 100644 tests/compile_fail/S9/nodiscard_save.cc create mode 100644 tests/compile_fail/S9/nodiscard_transpose.cc create mode 100644 tests/compile_fail/S9/norm_real.cc create mode 100644 tests/compile_fail/S9/pinv_int.cc create mode 100644 tests/compile_fail/S9/pinverse_int.cc create mode 100644 tests/compile_fail/S9/polar_real.cc create mode 100644 tests/compile_fail/S9/proj_real.cc create mode 100644 tests/compile_fail/S9/real_real.cc create mode 100644 tests/compile_fail/S9/row_echelon_int.cc create mode 100644 tests/compile_fail/S9/rref_int.cc create mode 100644 tests/compile_fail/S9/solve_int.cc create mode 100644 tests/compile_fail/S9/svd_factor_int.cc create mode 100644 tests/compile_fail/S9/svd_int.cc create mode 100644 tests/config/tu_a.cc create mode 100644 tests/config/tu_b.cc create mode 100644 tests/fixtures/s5/bool.npy create mode 100644 tests/fixtures/s5/c16_be.npy create mode 100644 tests/fixtures/s5/c16_le.npy create mode 100644 tests/fixtures/s5/c8_be.npy create mode 100644 tests/fixtures/s5/c8_le.npy create mode 100644 tests/fixtures/s5/f2.npy create mode 100644 tests/fixtures/s5/f4_be.npy create mode 100644 tests/fixtures/s5/f4_le.npy create mode 100644 tests/fixtures/s5/f8_be.npy create mode 100644 tests/fixtures/s5/f8_le.npy create mode 100644 tests/fixtures/s5/f8_v2.npy create mode 100644 tests/fixtures/s5/f8_v3.npy create mode 100644 tests/fixtures/s5/fortran_2x3.npy create mode 100644 tests/fixtures/s5/i1.npy create mode 100644 tests/fixtures/s5/i2_be.npy create mode 100644 tests/fixtures/s5/i2_le.npy create mode 100644 tests/fixtures/s5/i4_be.npy create mode 100644 tests/fixtures/s5/i4_le.npy create mode 100644 tests/fixtures/s5/i8_be.npy create mode 100644 tests/fixtures/s5/i8_le.npy create mode 100644 tests/fixtures/s5/make_npy.py create mode 100644 tests/fixtures/s5/object.npy create mode 100644 tests/fixtures/s5/one_d.npy create mode 100644 tests/fixtures/s5/structured.npy create mode 100644 tests/fixtures/s5/three_d.npy create mode 100644 tests/fixtures/s5/u1.npy create mode 100644 tests/fixtures/s5/u2_be.npy create mode 100644 tests/fixtures/s5/u2_le.npy create mode 100644 tests/fixtures/s5/u4_be.npy create mode 100644 tests/fixtures/s5/u4_le.npy create mode 100644 tests/fixtures/s5/u8_be.npy create mode 100644 tests/fixtures/s5/u8_le.npy create mode 100644 tests/fixtures/s7/cholesky_c16_6_A.npy create mode 100644 tests/fixtures/s7/cholesky_c16_6_L.npy create mode 100644 tests/fixtures/s7/cholesky_f8_5_A.npy create mode 100644 tests/fixtures/s7/cholesky_f8_5_L.npy create mode 100644 tests/fixtures/s7/cholesky_f8_8_A.npy create mode 100644 tests/fixtures/s7/cholesky_f8_8_L.npy create mode 100644 tests/fixtures/s7/det_c16_6_A.npy create mode 100644 tests/fixtures/s7/det_c16_6_D.npy create mode 100644 tests/fixtures/s7/det_f8_3_singular_A.npy create mode 100644 tests/fixtures/s7/det_f8_3_singular_D.npy create mode 100644 tests/fixtures/s7/det_f8_4_A.npy create mode 100644 tests/fixtures/s7/det_f8_4_D.npy create mode 100644 tests/fixtures/s7/expm_c16_4_A.npy create mode 100644 tests/fixtures/s7/expm_c16_4_E.npy create mode 100644 tests/fixtures/s7/expm_f8_4_A.npy create mode 100644 tests/fixtures/s7/expm_f8_4_E.npy create mode 100644 tests/fixtures/s7/expm_f8_6_norm5_A.npy create mode 100644 tests/fixtures/s7/expm_f8_6_norm5_E.npy create mode 100644 tests/fixtures/s7/inv_c16_4_A.npy create mode 100644 tests/fixtures/s7/inv_c16_4_X.npy create mode 100644 tests/fixtures/s7/inv_f8_5_A.npy create mode 100644 tests/fixtures/s7/inv_f8_5_X.npy create mode 100644 tests/fixtures/s7/inv_f8_8_A.npy create mode 100644 tests/fixtures/s7/inv_f8_8_X.npy create mode 100644 tests/fixtures/s7/lu_solve_c16_5x2_A.npy create mode 100644 tests/fixtures/s7/lu_solve_c16_5x2_B.npy create mode 100644 tests/fixtures/s7/lu_solve_c16_5x2_X.npy create mode 100644 tests/fixtures/s7/lu_solve_f8_4x2_A.npy create mode 100644 tests/fixtures/s7/lu_solve_f8_4x2_B.npy create mode 100644 tests/fixtures/s7/lu_solve_f8_4x2_X.npy create mode 100644 tests/fixtures/s7/lu_solve_f8_8x3_A.npy create mode 100644 tests/fixtures/s7/lu_solve_f8_8x3_B.npy create mode 100644 tests/fixtures/s7/lu_solve_f8_8x3_X.npy create mode 100644 tests/fixtures/s7/make_fixtures.py create mode 100644 tests/fixtures/s7/manifest.txt create mode 100644 tests/fixtures/s7/pinv_c16_4x6_rank2_A.npy create mode 100644 tests/fixtures/s7/pinv_c16_4x6_rank2_X.npy create mode 100644 tests/fixtures/s7/pinv_c16_5x3_A.npy create mode 100644 tests/fixtures/s7/pinv_c16_5x3_X.npy create mode 100644 tests/fixtures/s7/pinv_f8_3x5_A.npy create mode 100644 tests/fixtures/s7/pinv_f8_3x5_X.npy create mode 100644 tests/fixtures/s7/pinv_f8_5x5_rank3_A.npy create mode 100644 tests/fixtures/s7/pinv_f8_5x5_rank3_X.npy create mode 100644 tests/fixtures/s7/pinv_f8_6x4_A.npy create mode 100644 tests/fixtures/s7/pinv_f8_6x4_X.npy create mode 100644 tests/fixtures/s7/rref_c16_3x4_rank2_A.npy create mode 100644 tests/fixtures/s7/rref_c16_3x4_rank2_R.npy create mode 100644 tests/fixtures/s7/rref_f8_3x3_A.npy create mode 100644 tests/fixtures/s7/rref_f8_3x3_R.npy create mode 100644 tests/fixtures/s7/rref_f8_3x5_A.npy create mode 100644 tests/fixtures/s7/rref_f8_3x5_R.npy create mode 100644 tests/fixtures/s7/rref_f8_4x4_rank2_A.npy create mode 100644 tests/fixtures/s7/rref_f8_4x4_rank2_R.npy create mode 100644 tests/fixtures/s7/rref_f8_5x3_rank2_A.npy create mode 100644 tests/fixtures/s7/rref_f8_5x3_rank2_R.npy create mode 100644 tests/fixtures/s7/svdvals_c16_3x6_A.npy create mode 100644 tests/fixtures/s7/svdvals_c16_3x6_S.npy create mode 100644 tests/fixtures/s7/svdvals_c16_7x4_A.npy create mode 100644 tests/fixtures/s7/svdvals_c16_7x4_S.npy create mode 100644 tests/fixtures/s7/svdvals_f8_4x7_A.npy create mode 100644 tests/fixtures/s7/svdvals_f8_4x7_S.npy create mode 100644 tests/fixtures/s7/svdvals_f8_6x6_rank3_A.npy create mode 100644 tests/fixtures/s7/svdvals_f8_6x6_rank3_S.npy create mode 100644 tests/fixtures/s7/svdvals_f8_8x5_A.npy create mode 100644 tests/fixtures/s7/svdvals_f8_8x5_S.npy create mode 100644 tests/fixtures/s8/conv_f8_1x6_1x3_A.npy create mode 100644 tests/fixtures/s8/conv_f8_1x6_1x3_B.npy create mode 100644 tests/fixtures/s8/conv_f8_5x1_2x1_A.npy create mode 100644 tests/fixtures/s8/conv_f8_5x1_2x1_B.npy create mode 100644 tests/fixtures/s8/conv_f8_7x9_10x11_A.npy create mode 100644 tests/fixtures/s8/conv_f8_7x9_10x11_B.npy create mode 100644 tests/fixtures/s8/conv_f8_7x9_1x1_A.npy create mode 100644 tests/fixtures/s8/conv_f8_7x9_1x1_B.npy create mode 100644 tests/fixtures/s8/conv_f8_7x9_2x4_A.npy create mode 100644 tests/fixtures/s8/conv_f8_7x9_2x4_B.npy create mode 100644 tests/fixtures/s8/conv_f8_7x9_3x12_A.npy create mode 100644 tests/fixtures/s8/conv_f8_7x9_3x12_B.npy create mode 100644 tests/fixtures/s8/conv_f8_7x9_3x5_A.npy create mode 100644 tests/fixtures/s8/conv_f8_7x9_3x5_B.npy create mode 100644 tests/fixtures/s8/conv_f8_7x9_4x3_A.npy create mode 100644 tests/fixtures/s8/conv_f8_7x9_4x3_B.npy create mode 100644 tests/fixtures/s8/conv_full_f8_1x6_1x3_C.npy create mode 100644 tests/fixtures/s8/conv_full_f8_5x1_2x1_C.npy create mode 100644 tests/fixtures/s8/conv_full_f8_7x9_10x11_C.npy create mode 100644 tests/fixtures/s8/conv_full_f8_7x9_1x1_C.npy create mode 100644 tests/fixtures/s8/conv_full_f8_7x9_2x4_C.npy create mode 100644 tests/fixtures/s8/conv_full_f8_7x9_3x12_C.npy create mode 100644 tests/fixtures/s8/conv_full_f8_7x9_3x5_C.npy create mode 100644 tests/fixtures/s8/conv_full_f8_7x9_4x3_C.npy create mode 100644 tests/fixtures/s8/conv_full_i4_6x8_1x1_C.npy create mode 100644 tests/fixtures/s8/conv_full_i4_6x8_2x4_C.npy create mode 100644 tests/fixtures/s8/conv_full_i4_6x8_3x5_C.npy create mode 100644 tests/fixtures/s8/conv_full_i4_6x8_9x10_C.npy create mode 100644 tests/fixtures/s8/conv_i4_6x8_1x1_A.npy create mode 100644 tests/fixtures/s8/conv_i4_6x8_1x1_B.npy create mode 100644 tests/fixtures/s8/conv_i4_6x8_2x4_A.npy create mode 100644 tests/fixtures/s8/conv_i4_6x8_2x4_B.npy create mode 100644 tests/fixtures/s8/conv_i4_6x8_3x5_A.npy create mode 100644 tests/fixtures/s8/conv_i4_6x8_3x5_B.npy create mode 100644 tests/fixtures/s8/conv_i4_6x8_9x10_A.npy create mode 100644 tests/fixtures/s8/conv_i4_6x8_9x10_B.npy create mode 100644 tests/fixtures/s8/conv_same_f8_1x6_1x3_C.npy create mode 100644 tests/fixtures/s8/conv_same_f8_5x1_2x1_C.npy create mode 100644 tests/fixtures/s8/conv_same_f8_7x9_10x11_C.npy create mode 100644 tests/fixtures/s8/conv_same_f8_7x9_1x1_C.npy create mode 100644 tests/fixtures/s8/conv_same_f8_7x9_2x4_C.npy create mode 100644 tests/fixtures/s8/conv_same_f8_7x9_3x12_C.npy create mode 100644 tests/fixtures/s8/conv_same_f8_7x9_3x5_C.npy create mode 100644 tests/fixtures/s8/conv_same_f8_7x9_4x3_C.npy create mode 100644 tests/fixtures/s8/conv_same_i4_6x8_1x1_C.npy create mode 100644 tests/fixtures/s8/conv_same_i4_6x8_2x4_C.npy create mode 100644 tests/fixtures/s8/conv_same_i4_6x8_3x5_C.npy create mode 100644 tests/fixtures/s8/conv_same_i4_6x8_9x10_C.npy create mode 100644 tests/fixtures/s8/conv_valid_f8_1x6_1x3_C.npy create mode 100644 tests/fixtures/s8/conv_valid_f8_5x1_2x1_C.npy create mode 100644 tests/fixtures/s8/conv_valid_f8_7x9_10x11_C.npy create mode 100644 tests/fixtures/s8/conv_valid_f8_7x9_1x1_C.npy create mode 100644 tests/fixtures/s8/conv_valid_f8_7x9_2x4_C.npy create mode 100644 tests/fixtures/s8/conv_valid_f8_7x9_3x5_C.npy create mode 100644 tests/fixtures/s8/conv_valid_f8_7x9_4x3_C.npy create mode 100644 tests/fixtures/s8/conv_valid_i4_6x8_1x1_C.npy create mode 100644 tests/fixtures/s8/conv_valid_i4_6x8_2x4_C.npy create mode 100644 tests/fixtures/s8/conv_valid_i4_6x8_3x5_C.npy create mode 100644 tests/fixtures/s8/conv_valid_i4_6x8_9x10_C.npy create mode 100644 tests/fixtures/s8/fft2_c16_1x1_C.npy create mode 100644 tests/fixtures/s8/fft2_c16_1x7_C.npy create mode 100644 tests/fixtures/s8/fft2_c16_31x17_C.npy create mode 100644 tests/fixtures/s8/fft2_c16_3x5_C.npy create mode 100644 tests/fixtures/s8/fft2_c16_4x6_C.npy create mode 100644 tests/fixtures/s8/fft2_c16_7x11_C.npy create mode 100644 tests/fixtures/s8/fft2_c16_8x8_C.npy create mode 100644 tests/fixtures/s8/fft2_c8_3x5_C.npy create mode 100644 tests/fixtures/s8/fft2_c8_8x8_C.npy create mode 100644 tests/fixtures/s8/fft2_f4_13x1_C.npy create mode 100644 tests/fixtures/s8/fft2_f4_16x32_C.npy create mode 100644 tests/fixtures/s8/fft2_f4_1x16_C.npy create mode 100644 tests/fixtures/s8/fft2_f4_1x2_C.npy create mode 100644 tests/fixtures/s8/fft2_f4_3x5_C.npy create mode 100644 tests/fixtures/s8/fft2_f4_7x11_C.npy create mode 100644 tests/fixtures/s8/fft2_f8_13x1_C.npy create mode 100644 tests/fixtures/s8/fft2_f8_16x32_C.npy create mode 100644 tests/fixtures/s8/fft2_f8_1x16_C.npy create mode 100644 tests/fixtures/s8/fft2_f8_1x1_C.npy create mode 100644 tests/fixtures/s8/fft2_f8_1x2_C.npy create mode 100644 tests/fixtures/s8/fft2_f8_1x7_C.npy create mode 100644 tests/fixtures/s8/fft2_f8_31x17_C.npy create mode 100644 tests/fixtures/s8/fft2_f8_3x5_C.npy create mode 100644 tests/fixtures/s8/fft2_f8_4x6_C.npy create mode 100644 tests/fixtures/s8/fft2_f8_64x64_C.npy create mode 100644 tests/fixtures/s8/fft2_f8_7x11_C.npy create mode 100644 tests/fixtures/s8/fft2_f8_8x8_C.npy create mode 100644 tests/fixtures/s8/fft2_i4_1x7_C.npy create mode 100644 tests/fixtures/s8/fft2_i4_4x6_C.npy create mode 100644 tests/fixtures/s8/fft2_i4_7x11_C.npy create mode 100644 tests/fixtures/s8/fft2_i4_8x8_C.npy create mode 100644 tests/fixtures/s8/fftshift_c16_1x1_C.npy create mode 100644 tests/fixtures/s8/fftshift_c16_1x4_C.npy create mode 100644 tests/fixtures/s8/fftshift_c16_3x5_C.npy create mode 100644 tests/fixtures/s8/fftshift_c16_4x6_C.npy create mode 100644 tests/fixtures/s8/fftshift_c16_5x5_C.npy create mode 100644 tests/fixtures/s8/fftshift_c16_6x1_C.npy create mode 100644 tests/fixtures/s8/fftshift_f8_1x1_C.npy create mode 100644 tests/fixtures/s8/fftshift_f8_1x4_C.npy create mode 100644 tests/fixtures/s8/fftshift_f8_3x5_C.npy create mode 100644 tests/fixtures/s8/fftshift_f8_4x6_C.npy create mode 100644 tests/fixtures/s8/fftshift_f8_5x5_C.npy create mode 100644 tests/fixtures/s8/fftshift_f8_6x1_C.npy create mode 100644 tests/fixtures/s8/fftshift_i4_1x1_C.npy create mode 100644 tests/fixtures/s8/fftshift_i4_1x4_C.npy create mode 100644 tests/fixtures/s8/fftshift_i4_3x5_C.npy create mode 100644 tests/fixtures/s8/fftshift_i4_4x6_C.npy create mode 100644 tests/fixtures/s8/fftshift_i4_5x5_C.npy create mode 100644 tests/fixtures/s8/fftshift_i4_6x1_C.npy create mode 100644 tests/fixtures/s8/fourier_c16_1x1_A.npy create mode 100644 tests/fixtures/s8/fourier_c16_1x7_A.npy create mode 100644 tests/fixtures/s8/fourier_c16_31x17_A.npy create mode 100644 tests/fixtures/s8/fourier_c16_3x5_A.npy create mode 100644 tests/fixtures/s8/fourier_c16_4x6_A.npy create mode 100644 tests/fixtures/s8/fourier_c16_7x11_A.npy create mode 100644 tests/fixtures/s8/fourier_c16_8x8_A.npy create mode 100644 tests/fixtures/s8/fourier_c8_3x5_A.npy create mode 100644 tests/fixtures/s8/fourier_c8_8x8_A.npy create mode 100644 tests/fixtures/s8/fourier_f4_13x1_A.npy create mode 100644 tests/fixtures/s8/fourier_f4_16x32_A.npy create mode 100644 tests/fixtures/s8/fourier_f4_1x16_A.npy create mode 100644 tests/fixtures/s8/fourier_f4_1x2_A.npy create mode 100644 tests/fixtures/s8/fourier_f4_3x5_A.npy create mode 100644 tests/fixtures/s8/fourier_f4_7x11_A.npy create mode 100644 tests/fixtures/s8/fourier_f8_13x1_A.npy create mode 100644 tests/fixtures/s8/fourier_f8_16x32_A.npy create mode 100644 tests/fixtures/s8/fourier_f8_1x16_A.npy create mode 100644 tests/fixtures/s8/fourier_f8_1x1_A.npy create mode 100644 tests/fixtures/s8/fourier_f8_1x2_A.npy create mode 100644 tests/fixtures/s8/fourier_f8_1x7_A.npy create mode 100644 tests/fixtures/s8/fourier_f8_31x17_A.npy create mode 100644 tests/fixtures/s8/fourier_f8_3x5_A.npy create mode 100644 tests/fixtures/s8/fourier_f8_4x6_A.npy create mode 100644 tests/fixtures/s8/fourier_f8_64x64_A.npy create mode 100644 tests/fixtures/s8/fourier_f8_7x11_A.npy create mode 100644 tests/fixtures/s8/fourier_f8_8x8_A.npy create mode 100644 tests/fixtures/s8/fourier_i4_1x7_A.npy create mode 100644 tests/fixtures/s8/fourier_i4_4x6_A.npy create mode 100644 tests/fixtures/s8/fourier_i4_7x11_A.npy create mode 100644 tests/fixtures/s8/fourier_i4_8x8_A.npy create mode 100644 tests/fixtures/s8/ifft2_c16_1x1_C.npy create mode 100644 tests/fixtures/s8/ifft2_c16_1x7_C.npy create mode 100644 tests/fixtures/s8/ifft2_c16_31x17_C.npy create mode 100644 tests/fixtures/s8/ifft2_c16_3x5_C.npy create mode 100644 tests/fixtures/s8/ifft2_c16_4x6_C.npy create mode 100644 tests/fixtures/s8/ifft2_c16_7x11_C.npy create mode 100644 tests/fixtures/s8/ifft2_c16_8x8_C.npy create mode 100644 tests/fixtures/s8/ifft2_c8_3x5_C.npy create mode 100644 tests/fixtures/s8/ifft2_c8_8x8_C.npy create mode 100644 tests/fixtures/s8/ifft2_f4_13x1_C.npy create mode 100644 tests/fixtures/s8/ifft2_f4_16x32_C.npy create mode 100644 tests/fixtures/s8/ifft2_f4_1x16_C.npy create mode 100644 tests/fixtures/s8/ifft2_f4_1x2_C.npy create mode 100644 tests/fixtures/s8/ifft2_f4_3x5_C.npy create mode 100644 tests/fixtures/s8/ifft2_f4_7x11_C.npy create mode 100644 tests/fixtures/s8/ifft2_f8_13x1_C.npy create mode 100644 tests/fixtures/s8/ifft2_f8_16x32_C.npy create mode 100644 tests/fixtures/s8/ifft2_f8_1x16_C.npy create mode 100644 tests/fixtures/s8/ifft2_f8_1x1_C.npy create mode 100644 tests/fixtures/s8/ifft2_f8_1x2_C.npy create mode 100644 tests/fixtures/s8/ifft2_f8_1x7_C.npy create mode 100644 tests/fixtures/s8/ifft2_f8_31x17_C.npy create mode 100644 tests/fixtures/s8/ifft2_f8_3x5_C.npy create mode 100644 tests/fixtures/s8/ifft2_f8_4x6_C.npy create mode 100644 tests/fixtures/s8/ifft2_f8_64x64_C.npy create mode 100644 tests/fixtures/s8/ifft2_f8_7x11_C.npy create mode 100644 tests/fixtures/s8/ifft2_f8_8x8_C.npy create mode 100644 tests/fixtures/s8/ifft2_i4_1x7_C.npy create mode 100644 tests/fixtures/s8/ifft2_i4_4x6_C.npy create mode 100644 tests/fixtures/s8/ifft2_i4_7x11_C.npy create mode 100644 tests/fixtures/s8/ifft2_i4_8x8_C.npy create mode 100644 tests/fixtures/s8/ifftshift_c16_1x1_C.npy create mode 100644 tests/fixtures/s8/ifftshift_c16_1x4_C.npy create mode 100644 tests/fixtures/s8/ifftshift_c16_3x5_C.npy create mode 100644 tests/fixtures/s8/ifftshift_c16_4x6_C.npy create mode 100644 tests/fixtures/s8/ifftshift_c16_5x5_C.npy create mode 100644 tests/fixtures/s8/ifftshift_c16_6x1_C.npy create mode 100644 tests/fixtures/s8/ifftshift_f8_1x1_C.npy create mode 100644 tests/fixtures/s8/ifftshift_f8_1x4_C.npy create mode 100644 tests/fixtures/s8/ifftshift_f8_3x5_C.npy create mode 100644 tests/fixtures/s8/ifftshift_f8_4x6_C.npy create mode 100644 tests/fixtures/s8/ifftshift_f8_5x5_C.npy create mode 100644 tests/fixtures/s8/ifftshift_f8_6x1_C.npy create mode 100644 tests/fixtures/s8/ifftshift_i4_1x1_C.npy create mode 100644 tests/fixtures/s8/ifftshift_i4_1x4_C.npy create mode 100644 tests/fixtures/s8/ifftshift_i4_3x5_C.npy create mode 100644 tests/fixtures/s8/ifftshift_i4_4x6_C.npy create mode 100644 tests/fixtures/s8/ifftshift_i4_5x5_C.npy create mode 100644 tests/fixtures/s8/ifftshift_i4_6x1_C.npy create mode 100644 tests/fixtures/s8/make_fixtures.py create mode 100644 tests/fixtures/s8/manifest.txt create mode 100644 tests/fixtures/s8/shift_c16_1x1_A.npy create mode 100644 tests/fixtures/s8/shift_c16_1x4_A.npy create mode 100644 tests/fixtures/s8/shift_c16_3x5_A.npy create mode 100644 tests/fixtures/s8/shift_c16_4x6_A.npy create mode 100644 tests/fixtures/s8/shift_c16_5x5_A.npy create mode 100644 tests/fixtures/s8/shift_c16_6x1_A.npy create mode 100644 tests/fixtures/s8/shift_f8_1x1_A.npy create mode 100644 tests/fixtures/s8/shift_f8_1x4_A.npy create mode 100644 tests/fixtures/s8/shift_f8_3x5_A.npy create mode 100644 tests/fixtures/s8/shift_f8_4x6_A.npy create mode 100644 tests/fixtures/s8/shift_f8_5x5_A.npy create mode 100644 tests/fixtures/s8/shift_f8_6x1_A.npy create mode 100644 tests/fixtures/s8/shift_i4_1x1_A.npy create mode 100644 tests/fixtures/s8/shift_i4_1x4_A.npy create mode 100644 tests/fixtures/s8/shift_i4_3x5_A.npy create mode 100644 tests/fixtures/s8/shift_i4_4x6_A.npy create mode 100644 tests/fixtures/s8/shift_i4_5x5_A.npy create mode 100644 tests/fixtures/s8/shift_i4_6x1_A.npy create mode 100644 tests/fuzz/corpus/binary/double_2x3.bin create mode 100644 tests/fuzz/corpus/binary/double_2x3_short.bin create mode 100644 tests/fuzz/corpus/binary/empty_0x3.bin create mode 100644 tests/fuzz/corpus/binary/int32_2x2.bin create mode 100644 tests/fuzz/corpus/bmp/rgb24_bottom_up_3x2.bmp create mode 100644 tests/fuzz/corpus/bmp/rgb24_top_down_3x2.bmp create mode 100644 tests/fuzz/corpus/bmp/rgb24_trailing_1x1.bmp create mode 100644 tests/fuzz/corpus/bmp/rgb24_truncated.bmp create mode 100644 tests/fuzz/corpus/bmp/rgb32_2x2.bmp create mode 100644 tests/fuzz/corpus/npy/c16_le.npy create mode 100644 tests/fuzz/corpus/npy/f8_be.npy create mode 100644 tests/fuzz/corpus/npy/f8_le.npy create mode 100644 tests/fuzz/corpus/npy/f8_le_truncated.npy create mode 100644 tests/fuzz/corpus/npy/fortran_2x3.npy create mode 100644 tests/fuzz/corpus/npy/i4_be.npy create mode 100644 tests/fuzz/corpus/npy/one_d.npy create mode 100644 tests/fuzz/corpus/npy/structured.npy create mode 100644 tests/fuzz/corpus/npy/u1.npy create mode 100644 tests/fuzz/corpus/text/complex_2x2.txt create mode 100644 tests/fuzz/corpus/text/float_exp.txt create mode 100644 tests/fuzz/corpus/text/int_tabs_crlf.txt create mode 100644 tests/fuzz/corpus/text/ragged.txt create mode 100644 tests/fuzz/corpus/text/real_2x3.txt create mode 100644 tests/fuzz/corpus/text/unclosed.txt create mode 100644 tests/fuzz/fuzz_binary.cc create mode 100644 tests/fuzz/fuzz_bmp.cc create mode 100644 tests/fuzz/fuzz_checks.hpp create mode 100644 tests/fuzz/fuzz_npy.cc create mode 100644 tests/fuzz/fuzz_text.cc create mode 100644 tests/fuzz/make_corpus.py create mode 100644 tests/oracle/s7_eigen.cc diff --git a/ReadMe.md b/ReadMe.md index 809e40c..8a85549 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -22,6 +22,7 @@ A modern, C++20-native, single-file header-only dense 2D matrix library. - [det -- matrix determinant](#det----matrix-determinant) - [operator `/=`](#operator-divide-equal) - [inverse](#matrix-inverse) + - [linear algebra: lu_factor, svd_factor, linalg_status](#linear-algebra) - [save matrix to images with colormap](#save-matrix-to-images-with-colormap) - [save/load bmp](#save-load-bmp) - [save png](#save-png) @@ -42,6 +43,7 @@ A modern, C++20-native, single-file header-only dense 2D matrix library. - [linspace](#linspace) - [magic](#magic-function) - [matrix convolution](#matrix-convolution) + - [fft](#fft) - [make_view](#make-view-function) - [lu_decomposition](#lu-decomposition) - [guass_jordan_elimination](#gauss-jordan-elimination) @@ -98,11 +100,24 @@ A modern, C++20-native, single-file header-only dense 2D matrix library. g++ -o your_exe_file your_source_code.cpp -std=c++20 -O2 -pthread -lstdc++fs ``` -Please note [`std::thread`](https://en.cppreference.com/w/cpp/header/thread) is not enabled by default. If you prefer multi-thread mode, pass `-DPARALLEL` option to compiler, and add necessary link options. +This is the portable flag set the Makefile uses by default (`make test example`, outputs in `build/`); `-Ofast` and `-march=native` are not needed, and `make FAST=1 ...` opts back into them. See [Building tests and examples](#building-tests-and-examples). + +Please note [`std::thread`](https://en.cppreference.com/w/cpp/header/thread) is not enabled by default. If you prefer multi-thread mode, pass `-DFENG_MATRIX_PARALLEL` to the compiler (the old `-DPARALLEL` is deprecated but still accepted), and add necessary link options. [`std::filesystem`](https://en.cppreference.com/w/cpp/filesystem/path) is used, make sure corresponding library option is passed during link time (`-lstdc++fs` for g++). Variadic macro `__VA_OPT__` is used. It is officially supported since c++20([link1](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1042r1.html), [link2](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0306r4.html)), so the compiler must be compatible with c++20. +#### Configuration macros + +- `FENG_MATRIX_PARALLEL` enables the `std::jthread` helpers (the old name `PARALLEL` is deprecated but still accepted and maps to it); each call starts its own threads and joins them before returning, on min( hardware_concurrency, work / grain ) workers, so small calls run on the calling thread (see [operator multiply equal](#operator-multiply-equal) below); +- `FENG_MATRIX_OPENCV` enables the `cv::Mat` interface (the old name `OPENCV` is deprecated but still accepted); +- `FENG_MATRIX_CHECKED_ITERATORS` adds bounds checks to the stride and view iterators (see [Views and iterator invalidation](#views-and-iterator-invalidation)); +- `FENG_MATRIX_NO_CONFIG_CHECK` turns off the mixed-configuration link check below. + +All translation units of a program must agree on the first three. On ELF targets every translation unit that includes `matrix.hpp` defines the symbol `feng_matrix_configuration_mismatch_between_translation_units` for its configuration, so linking two translation units with different settings fails with a multiple-definition error naming that symbol, while matching ones link. The check does not exist on non-ELF targets and does not span separate shared libraries. + +Results that should not be ignored are `[[nodiscard]]`: the `bool` of the loaders (`load_txt`, `load_binary`, `load_npy`) and writers (`save_as_txt`, `save_as_binary`, `save_as_npy`, `save_as_png`, `save_as_bmp`, `save_as_pgm`), the `linalg_status` overloads such as `inverse( A, out )`, and the side-effect-free functions that return a new matrix. Discarding one warns (`-Wunused-result`, an error under `-Werror`); write `(void)m.save_as_bmp( ... );` to discard on purpose. Value-returning functions no longer return top-level `const`, so `m = f()` moves. See the `## S9` section of [docs/migration.md](docs/migration.md). + ### basic #### creating matrices @@ -114,7 +129,7 @@ Variadic macro `__VA_OPT__` is used. It is officially supported since c++20([lin ```cpp feng::matrix m{ 12, 34 }; ``` - - creating a random matrix of size `12 X 34`, in range `[0.0, 1.0]`: + - creating a random matrix of size `12 X 34`, in the open interval `(0, 1)` (see [Random numbers](#random-numbers-statistics-and-arithmetic-types)): ```cpp auto rand = feng::rand(12, 34); @@ -185,12 +200,12 @@ Variadic macro `__VA_OPT__` is used. It is officially supported since c++20([lin ``` However, to compile and link the code above, make sure to - 1. define the opencv guard by passing `-DOPENCV` option to the compiler (g++), + 1. define the opencv guard by passing `-DFENG_MATRIX_OPENCV` to the compiler (g++; the old `-DOPENCV` is deprecated but still accepted), 2. tell the compile where to find the opencv header files, for example, passing `pkg-config --cflags opencv4` to the compiler (g++), and 3. tell the linker which libraries to link against, for example, passing `pkg-config --libs opencv4` to the linker (g++). Or 4. compile and link in a single command, such as ```bash - g++ -o ./test_test -std=c++20 -Wall -Wextra -fmax-errors=1 -Ofast -flto=auto -funroll-all-loops -pipe -march=native -DPARALLEL -DOPENCV `pkg-config --cflags opencv4` -Wno-deprecated-enum-enum-conversion `pkg-config --libs opencv4` -pthread -lstdc++fs -Wl,--gc-sections -flto tests/test.cc + g++ -o ./build/test_test -std=c++20 -O2 -Wall -Wextra -DFENG_MATRIX_PARALLEL -isystem tests -DFENG_MATRIX_OPENCV `pkg-config --cflags opencv4` -Wno-deprecated-enum-enum-conversion tests/test.cc `pkg-config --libs opencv4` -pthread ``` @@ -250,6 +265,61 @@ m.save_as_bmp( "./images/0019_create.bmp" ); ![create](./images/0019_create.bmp) +#### Contract checks + +The library is exception-free. A violated precondition writes one line `feng::matrix: contract violation: (:) ` to stderr and calls `std::abort()`. The checks are always on: debug and `-DNDEBUG` builds behave the same, and there is no handler to install. Checked preconditions include: + +- sizes: a negative signed dimension, rows×cols or its byte count overflowing, a byte count above `PTRDIFF_MAX`, or rows×cols above the allocator's `max_size`, checked before anything is allocated; +- `reshape(r, c)` when r×c overflows or differs from `size()`, before the matrix changes; +- `m.at(r, c)` (const and non-const on a matrix, returning a reference; const only on a view from `make_view`) and `m(r, c)` when `r >= m.row()` or `c >= m.col()`, and `m[r]` when `r >= m.row()`; on a 0×0, 0×N or N×0 matrix every index aborts; +- `clone(other, r0, r1, c0, c1)`, its brace-list forms and the slicing constructors when `r1 > other.row()`, `c1 > other.col()`, `r0 >= r1` or `c0 >= c1` (an empty range aborts), or a brace list does not hold two values; +- `shrink_to_size(r, c)` with a zero extent, and `flipdim(m, dim)` with a `dim` other than 1 or 2; +- operand shapes: a `std::valarray` or `std::vector` × matrix product whose length differs from the matrix rows, a matrix × `std::valarray` or `std::vector` product whose length differs from the matrix columns, `+`, `-`, `+=`, `-=` and the matrix–matrix element-wise functions (`fma`, `ldexp`, `scalbn`, `scalbln`, `pow`, `hypot`, `fmod`, `remainder`, `copysign`, `nextafter`, `fdim`, `fmax`, `fmin`, `atan2`, and `feng::matrix_details::map` with two or three matrices) on different shapes, `*` and `*=` when the left columns differ from the right rows, and `/` and `/=` (matrix division) unless the divisor is square with as many rows as the dividend has columns, all checked before any element is read; +- `pooling(m, …, action)` with an action other than `mean`, `average`, `max` or `min`. + +The row pointer returned by `m[r]` is not checked further, so `m[r][c]` stays an unchecked escape hatch; use `at(r, c)` for a checked access. File open, read and write failures also abort for now, until a later stage replaces them with status returns. + +Self-assignment (`a = a`, `a.copy(a)`) keeps the contents, `a *= a` gives the product of the old `a`, and `m.copy(src, {r0, r1}, {c0, c1})` whose source overlaps the destination block (for example a `make_view` of `m` itself) gives the same result as copying a snapshot of the source; `m.copy(v)` and `feng::matrix n{ v }` accept either view type, and `m.copy(v)` with `v` a view of `m` itself copies a snapshot of `v`. The curried helpers `feng::matrix_details::map(func)` and `feng::matrix_details::reduce(func, init)` return lambdas that hold copies of `func` and `init`, so they may be stored (`auto f = feng::matrix_details::reduce(func, init); f(m);`) and called after the arguments are gone; `reduce(func, init)(m)` folds `init` in exactly once and equals `std::accumulate(m.begin(), m.end(), init, func)` for an associative `func`. See the `## S2` and `## S4` sections of [docs/migration.md](docs/migration.md) for the old and new behaviour. + +`shrink_to_size(r, c)` keeps the overlapping top-left block exactly and fills the rest with zeros. The flips follow the MATLAB/NumPy convention: `fliplr(m)` flips columns (same as `flipdim(m, 2)`, column j becomes column cols−1−j) and `flipud(m)` flips rows (same as `flipdim(m, 1)`, row i becomes row rows−1−i); before S2 the two names had their axes swapped. + +#### Element types, storage and callbacks + +`feng::matrix` and `feng::matrix_view` require `T` to satisfy the concept `feng::matrix_element`: a non-const, non-volatile object type other than `bool` whose default, copy and move constructors, copy and move assignments and destructor are all `noexcept` (`std::complex` is accepted even though libstdc++ does not mark its default constructor `noexcept`). `matrix`, `matrix>` and `matrix` are fine; `matrix` and `matrix` do not compile. Use `matrix` for a 0/1 mask: `is_inf`, `isinf`, `is_nan` and `isnan` return one. + +The elements live in a private `std::vector` and the extents are private too; use `m.row()`, `m.col()`, `m.size()`, `m.data()` and `m.get_allocator()`. Copy assignment, move assignment and `swap` follow the allocator's propagation traits; `m.copy(rhs)` keeps `m`'s allocator. The allocator's `value_type` must be `T` (`matrix>` fails a `static_assert`; rebind the allocator instead), and matrix products are built with the left operand's allocator; `real`, `imag`, `abs`, `arg` and `norm` of a complex matrix rebind the operand's allocator (`m.get_allocator()`), so a stateful allocator carries over. Every allocation checks rows×cols against overflow and the allocator's `max_size()` first and aborts with a `matrix size: …` message if it does not fit. A moved-from matrix is 0×0, and `a = std::move(a)` keeps the contents. `matrix(view)` copies exactly the rectangle the view shows. + +Callbacks passed to the library (`apply`, `for_each`, the element-wise maps and the parallel helpers) must not throw. The library is exception-free and gives no exception guarantee; every public operation of `matrix`, `matrix_view` and the free functions in `feng` is `noexcept`, so an allocation failure or an exception escaping a callback calls `std::terminate`. See the `## S3` section of [docs/migration.md](docs/migration.md). + +#### Random numbers, statistics and arithmetic types + +Random generation takes a caller-owned engine: any `g` satisfying `std::uniform_random_bit_generator` (for example `std::mt19937_64 g{ 42 };`) can be passed to `feng::random( r, c, g )`, `random( n, g )`, `rand( r, c, g )`, `rand( n, g )`, `rand_like( m, g )` and `random_like( m, g )`. Elements are drawn in row-major order from `g` alone, so equally seeded engines give identical matrices and each thread can own its engine. `float`, `double` and `long double` elements lie in the open interval (0, 1) (draws of exactly 0 or 1 are redrawn); a `std::complex` element has its real and imaginary parts each in (0, 1); integer elements come from `std::uniform_int_distribution` over the full range of the type, `numeric_limits::min()` to `max()`. The engine-less overloads (`rand( r, c, seed = 0 )`, `rand( n )`, `random( r, c )`, `random( n )`, `rand_like( m )`, `random_like( m )`, `randn_like( m )`) build a local `std::mt19937_64` from the nonzero seed, or from the clock for seed 0, so `rand( r, c, 7 )` equals `random( r, c, g )` with `std::mt19937_64 g{ 7 }`; there is no global random state. + +`feng::mean( m )` returns `double` for integer `T`, `T` for floating-point `T` and `std::complex` for complex `T` (the mean of `int` {1, 2} is 1.5). `feng::variance( m, ddof = 0 )` returns the sum of |x − mean|² divided by n − `ddof`, as the mean's real type, and `feng::standard_deviation( m, ddof = 0 )` is its square root; both share the `ddof` (delta degrees of freedom) parameter, so pass `ddof = 1` for the sample estimate as in NumPy. `max`, `min`, `minmax`, `mean`, `variance` and `standard_deviation` of an empty matrix, and `variance` or `standard_deviation` with n ≤ `ddof`, abort with a message naming the function. + +Mixed-type arithmetic follows one promotion policy, with the kinds ordered integral < floating < complex: + +- `m + n`, `m - n`, `m * n` and `m / n` for `matrix` and `matrix` give `matrix< feng::matrix_details::common_element_t >`: `std::common_type_t` for two real types, `std::complex< std::common_type_t >` when either is complex (X and Y the real parts), so `matrix + matrix` is `matrix` and `matrix + matrix>` is `matrix>`; +- `m ⊕ s` and `s ⊕ m` (⊕ one of `+ - * /`) with a scalar `s` (an arithmetic type or `std::complex`) keep `T` when the scalar's kind is not higher than `T`'s, converting the scalar to `T` (or to `T`'s value type for complex `T`) first, and otherwise give `common_element_t` (the trait `scalar_result_t`): `matrix * 2.0` stays `matrix`, `image + 1` on a `matrix` stays `std::uint8_t`, `matrix * 0.5` is `matrix` and `matrix * 2` compiles; `s / m` is `s` times `m.inverse()`; +- compound assignment (`m += s`, `m *= n`, ...) keeps `T`, converting the operand to `T` as by `static_cast` first (`m *= 0.5` on a `matrix` multiplies by 0); +- the result's allocator is the left matrix's allocator rebound to the result element type. + +Signed integer overflow in element arithmetic is undefined behaviour and not checked: avoiding overflow is a precondition of the caller, as is avoiding integer division by zero; unsigned arithmetic wraps. See the `## S6` section of [docs/migration.md](docs/migration.md). + +#### Linear algebra + +The linear-algebra routines take floating-point or `std::complex` elements; `inverse`, `inv`, `try_inverse`, `det`, `inverse( A, out )`, `lu_factor`, `svd_factor`, `cholesky_factor`, `row_echelon`, `solve`, `pinverse`, `expm`, their factorization classes and the legacy wrappers (`lu_decomposition`, `lu_solver`, `singular_value_decomposition`, `svd`, `pinv`, `svd_inverse`, `cholesky_decomposition`, `rref`, `gauss_jordan_elimination`) are constrained by the concept `feng::linalg_element< T >` (`std::floating_point< T >` or `std::complex`), so an integer matrix fails that constraint at compile time and the diagnostic names `linalg_element`; convert first (`m.astype().det()`). `ctranspose` and `conj` are constrained by `ComplexMatrix`. Numeric failures are reported, never hidden in NaN: `feng::linalg_status` is one of `ok`, `singular`, `not_positive_definite`, `not_converged` and `nonfinite`, and `feng::linalg_result` holds a `value` and a `status` (`r.ok()`, or test `r` in a boolean context). Non-square inputs and mismatched shapes are contract violations and abort with a message. + +- `feng::lu_factor( A )` returns an `lu_factorization` from one partial-pivoting LU: `status()`, `rank()` (|u_kk| > n·ε·max|U|), `pivots()` (row i of P·A is row `pivots()[i]` of A), `l()`, `u()`, `p()`, `det()` and `solve( B )` / `inverse()` (each a `linalg_result`). `feng::solve( A, B )` and `feng::try_inverse( A )` forward to it. `det()` is exactly 0 for a rank-deficient A and 1 for a 0×0 A; `inverse()`, `inverse( A )` and `inv( A )` return an empty 0×0 matrix for a singular or nonfinite A, while `inverse( A, out )` returns the `linalg_status` and leaves `out` unchanged unless it is `ok`. +- `feng::svd_factor( A, max_sweeps = 64 )` returns an `svd_factorization` (one-sided Jacobi, real or complex, any shape) with thin `u()` (m×k), `s()` (k singular values, descending), `v()` (n×k), k = min(m, n), `status()` (`ok` or `not_converged`) and `sweeps()`. `pinverse( A, rtol )`, `pinv( A, rtol )` and `svd_inverse( A )` are one pseudoinverse that drops s_i ≤ rtol·s_1 (default rtol = max(m, n)·ε, as in NumPy 2) and return 0×0 on failure; `pinverse( A, out, rtol )` returns the status. +- `feng::cholesky_factor( A )` returns a `cholesky_factorization` with lower-triangular `l()` (A = L·Lᴴ) or status `not_positive_definite`; the legacy `cholesky_decomposition( m, a )` returns 0 on success and 1 (with `a` unchanged) otherwise. +- `feng::row_echelon( A )` returns an `rref_result{ r, pivot_columns, rank, status }` for any m×n A (pivots below max(m, n)·ε·‖A‖∞ count as zero, as in MATLAB's `rref`); `rref( A )` and `gauss_jordan_elimination( A )` return the same `r`, and `std::nullopt` only for a NaN or inf input. +- `expm( A )` gives an all-NaN matrix for a nonfinite input and `expm( A, out )` returns `nonfinite` instead; `cgs` and `bicgstab` treat `eps` as relative to ‖b‖ and return 1 when they run out of iterations. + +Experimental: `eigen_jacobi`, `cyclic_eigen_jacobi`, `eigen_real_symmetric`, `eigen_hermitian`, `eigen_power_iteration` and `householder` are experimental: S7 did not qualify them, so their results and loop bounds are not tested (D-029). See the `## S7` section of [docs/migration.md](docs/migration.md). + +Under `__cpp_lib_expected` (C++23 and later), `feng::to_expected( r )` turns a `linalg_result`, a factorization or an `rref_result` into a `std::expected< …, linalg_status >`, and `feng::load_expected< T >( path, feng::io_format::npy )` (or `txt`, `binary`) returns a `std::expected< matrix< T >, feng::io_status >`. In C++20 they are absent; `linalg_result`, the factorization objects and the `bool` loaders stay as they are. See the `## S9` section of [docs/migration.md](docs/migration.md). + ---------- @@ -341,6 +411,26 @@ p.save_as_bmp( "./images/0002_slicing.bmp" ); ![matrix slicing](./images/0002_slicing.bmp) +#### Views and iterator invalidation + +`feng::make_view(m, {r0, r1}, {c0, c1})` returns a `matrix_view` (read-only) and `feng::make_mutable_view(m, {r0, r1}, {c0, c1})` returns a `mutable_matrix_view` that writes the owner's elements; a mutable view converts to a const view. Both borrow from an lvalue owner only: a view of a temporary owner, such as `make_view(feng::matrix{3, 3}, {0, 1}, {0, 1})`, does not compile, and `make_mutable_view` of a const owner does not compile either. The ranges must satisfy `r0 <= r1 <= m.row()` and `c0 <= c1 <= m.col()`, with exactly two values per brace list, or the call aborts (in every build); an empty range gives an empty view, and nothing is clamped or normalized. A view carries the parent's row stride (`v.row_stride() == m.col()`), so `v[r]`, `v.row_begin(r)`, `v.col_begin(c)` and `v.begin()` walk the parent's memory directly; `feng::matrix n{ v };` copies the viewed rectangle. + +```cpp +feng::matrix m{ 4, 5 }; +auto w = feng::make_mutable_view( m, {1, 3}, {2, 5} ); // 2x3 block of m +std::fill( w.begin(), w.end(), 1.0 ); // writes m[1..2][2..4] +feng::matrix_view> const v = w; // read-only view of the same block +``` + +A view is not a lifetime guarantee: it holds a pointer to the owner and to its elements. Views and the column, diagonal and anti-diagonal iterators (and the raw row pointers) are invalidated by + +- the owner's destruction; +- reallocation of the owner: `resize`, `reshape`, `clear`, `clone`, a copy into it, a move from it, and `swap`; +- assignment to the owner (copy or move); +- any change of the owner's shape. + +Using an invalidated view or iterator is undefined behaviour. Column and diagonal iterators compare by their logical position, so `col_end(c)` never forms an address past the owner's storage, and an out-of-range column or diagonal index aborts. Building with `-DFENG_MATRIX_CHECKED_ITERATORS` adds the owner's origin and extent to the stride and view iterators and aborts on any dereference or position outside the range (the sanitizer and tagged lanes of `tools/check.sh` build this way); it changes the iterator layout, so all translation units of a program must agree on it. See the `## S4` section of [docs/migration.md](docs/migration.md). + #### meshgrid meshgrid returns 2-D grid coordinates based on the coordinates contained in interger x and y. @@ -759,6 +849,8 @@ after modification ![data](./images/0001_data.bmp) +Span and mdspan adapters: `feng::as_span( m )` is a `std::span` over the contiguous row-major storage and `feng::row_span( m, r )` one over row `r` (an out-of-range `r` aborts), in every language mode. Under `__cpp_lib_mdspan` (C++23 and later) `feng::to_mdspan( m )` is a layout_right `std::mdspan` of shape `m.row()`×`m.col()`, and under `__cpp_lib_submdspan` (C++26) `feng::submdspan( m, {r0, r1}, {c0, c1} )` is a validated block of it; in C++20 use `row_span` or the views of [Views and iterator invalidation](#views-and-iterator-invalidation). The adapters borrow the matrix, so a temporary owner is rejected, and they are invalidated like views. See the `## S9` section of [docs/migration.md](docs/migration.md). + -------------------- @@ -776,6 +868,8 @@ generated output is 1069.00941294551 : 1069.0094129455 ``` +`det()` (and `feng::det( m )`) is sign(P)·∏u_kk from the partial-pivoting LU of [`lu_factor`](#linear-algebra): exactly 0 when the matrix is rank deficient, 1 for a 0×0 matrix, and NaN for a NaN or inf input. An integer matrix must be converted first, e.g. `m.astype().det()`. + ------------- #### operator divide-equal @@ -803,6 +897,8 @@ identity.save_as_bmp( "./images/0000_inverse.bmp" ); ![matrix inverse](./images/0000_inverse.bmp) +`inverse()`, `feng::inverse( m )` and `feng::inv( m )` return an empty 0×0 matrix when `m` is singular or holds a NaN or inf; to see the reason use `feng::try_inverse( m )` (a `linalg_result`) or `feng::linalg_status feng::inverse( m, out )`, which leaves `out` unchanged unless the status is `ok`. + ---------------------------------------- @@ -1061,6 +1157,8 @@ feng::matrix mat; mat.load_npy( "./images/64.npy"); ``` +The loaders validate their input and are transactional: `load_npy`, `load_txt` and `load_binary` return `false` on a missing, malformed, truncated or oversized file and leave the matrix unchanged, `feng::load_bmp` returns an empty optional, and `operator>>` sets `failbit` and keeps the matrix; none of them aborts, and each failing file loader prints one line to `std::cerr`. `load_npy` checks the magic, version, header dict, shape and payload size; the dtype must match the element type exactly (D-021: ``, ``, with `i1`–`i8`, `u1`–`u8`, `c8` and `c16` likewise, no conversion), a foreign byte order is swapped, `fortran_order: True` loads the logical matrix, and a 1-D array loads as 1×n; bool, `f2`, object, structured, rank 0 and rank 3+ arrays are rejected. A dtype size or shape dimension with a leading zero (`>` parse every token completely with `std::from_chars`: ragged rows, trailing characters, an out-of-range value (including a floating-point underflow such as `1e-400` into `double`) and `-1` into an unsigned type are rejected, and `inf` and `nan` load. Every writer (`save_as_txt`, `save_as_binary`, `save_as_npy`, `save_as_bmp`, `save_as_png`, `save_as_pgm`) returns `false` on a directory, open, write or close failure. `m.save_as_npy( "a.npy" )` writes a v1.0 C-order file with the element type's dtype that `numpy.load` and `load_npy` read back. See the `## S5` section of [docs/migration.md](docs/migration.md). + #### save load bmp @@ -1248,6 +1346,8 @@ m.save_as_bmp("images/0001_multiply_equal.bmp"); ![image multiply equal](images/0001_multiply_equal.bmp) +Matrix products (`operator*`, `operator*=` and `direct_multiply`) run one cache-blocked row-major kernel in serial and parallel builds; each entry is the sum over k in ascending order, so serial and parallel builds give bit-identical products. Serial builds no longer switch to Strassen recursion for dimensions of 17 and up (`strassen_multiply` stays callable). In `FENG_MATRIX_PARALLEL` builds the default worker count is work-based: an elementwise call, `for_each`, map or reduction over n elements uses min( hardware_concurrency, n / 65536 ) workers and a product M×K by K×N min( hardware_concurrency, M·K·N / 262144, M ), at least one; starting a thread costs about 19 µs, so a 4×4 add no longer starts 24 threads. Elementwise results do not depend on the worker count; parallel-build reductions of more than 131071 elements fold in that many chunks, so their floating-point rounding depends on the size and the core count (an explicit worker count fixes it). `tools/check.sh bench compare [--smoke]` builds `bench/bench.cc` against the stage start header and the working tree with `-std=c++20 -O2 -pthread`, interleaves pinned runs and judges each optimization in `bench/kept.txt` (at least 10% faster, and no stable workload more than 5% slower); the numbers are in [bench/results.md](bench/results.md). See the `## S10` section of [docs/migration.md](docs/migration.md). + #### operator plus equal @@ -1367,6 +1467,11 @@ matrix linspace( T start, T stop, const std::uint_least64_t num = 50ULL, bool #### magic function +```cpp +template < typename T = std::uint_least64_t, typename A = std::allocator< T > > matrix< T, A > magic( n ) +``` + +`magic( n )` is unchanged and `magic< double >( n )` builds the same square with `double` elements. Since `magic` is a template, `&feng::magic` and passing `feng::magic` as a callable no longer compile; use `&feng::magic<>` or a lambda. See the `## S9` section of [docs/migration.md](docs/migration.md). Calling `magic` method is quite straightforward: @@ -1449,7 +1554,7 @@ m.save_as_bmp( "./images/0000_conv.bmp", "gray" ); ![convolution 1](./images/0000_conv.bmp) -Full 2D matrix convolution with zero-paddings is given by `conv` or `conv2` : +`conv( A, B )` and `conv( A, B, mode )` (`conv2` is the same function) follow `scipy.signal.convolve2d` (D-004, D-030): the kernel B is reversed, out[i][j] = Σ A[p][q]·B[i−p][j−q], so `conv` of [1, 2] with [3, 4] is [3, 10, 8]. The value type is `Mat::value_type`. The default mode is `full`, the (ra+rb−1)×(ca+cb−1) result with zero-paddings: ```cpp @@ -1463,9 +1568,9 @@ edge.save_as_bmp( "./images/0001_conv.bmp", "gray" ); ![convolution 2](./images/0001_conv.bmp) -The convolution has three modes: `valid`, `same`, and `full` +The convolution has three modes, `"full"`, `"same"` and `"valid"`; any other mode string aborts with a message. -The valid mode gives out convolution result without zero-paddings: +The `valid` mode keeps only the entries computed without zero-paddings, (ra−rb+1)×(ca−cb+1) when A contains B in both dimensions; when B contains A the operands are swapped (as scipy does), and when neither contains the other the call aborts: ```cpp auto const& edge_valid = feng::conv( m, filter, "valid" ); @@ -1474,7 +1579,7 @@ edge_valid.save_as_bmp( "./images/0001_conv_valid.bmp", "gray" ); ![convolution valid](./images/0001_conv_valid.bmp) -The `same` mode returns the central part of the convolution result with zero-paddings, of the same size as the larger matrix passed to function `conv` +The `same` mode returns A's shape, cropped from the full result at row (rb−1)/2 and column (cb−1)/2 (integer division), for every kernel shape, including even and 1×1 kernels: ```cpp auto const& edge_same = feng::conv( m, filter, "same" ); @@ -1494,6 +1599,35 @@ edge_full.save_as_bmp( "./images/0001_conv_full.bmp", "gray" ); ![convolution full](./images/0001_conv_full.bmp) +Because the kernel is reversed, an asymmetric kernel gives the true convolution, not a correlation; with this Sobel kernel the result is the horizontal derivative (left minus right neighbours): + +```cpp +feng::matrix sobel{3, 3, {-1.0, 0.0, 1.0, + -2.0, 0.0, 2.0, + -1.0, 0.0, 1.0}}; +auto const& edge_sobel = feng::conv( m, sobel, "same" ); +edge_sobel.save_as_bmp( "./images/0001_conv_sobel.bmp", "gray" ); +``` + +![convolution sobel](./images/0001_conv_sobel.bmp) + +An operand with a zero dimension gives an empty 0×0 matrix in `full` and `valid` mode and zeros of A's shape in `same` mode. See the `## S8` section of [docs/migration.md](docs/migration.md). + + +#### fft + +`feng::fft( x )` and `feng::ifft( x )` follow `numpy.fft.fft2` and `numpy.fft.ifft2`: X[k][l] = Σ x[m][n]·exp(−2πi(km/R + ln/C)), and `ifft` uses exp(+2πi(km/R + ln/C)) divided by R·C, so `ifft( fft( x ) )` ≈ x. Both run in O(N log N) for every size (radix-2 for power-of-two lengths, Bluestein otherwise, no external dependency, D-007). The result is a `feng::matrix` of complex elements: `float`, `double` and `long double` give `std::complex` of the same real type, `std::complex` stays, and integer types give `std::complex` (D-031); an empty input gives an empty result of the same shape. + +`feng::fftshift( x )` and `feng::ifftshift( x )` are pure permutations, no transform: they roll x by (⌊R/2⌋, ⌊C/2⌋) and by (−⌊R/2⌋, −⌊C/2⌋) like `numpy.fft.fftshift` and `ifftshift`, return x's own matrix type, and `ifftshift( fftshift( x ) )` equals x for odd and even sizes. To centre a spectrum write `feng::fftshift( feng::fft( x ) )`. + +```cpp +feng::matrix x( 4, 6 ); +// ... fill x ... +auto const X = feng::fft( x ); // feng::matrix> +auto const centred = feng::fftshift( X ); // zero frequency at (2, 3) +auto const back = feng::ifft( feng::ifftshift( centred ) ); // ≈ x +``` + @@ -1577,7 +1711,9 @@ The noisy image now looks like ![svd_2](./images/0001_singular_value_decomposition.bmp) -We execute Singular Value Decomposition by calling function `std::optional> singular_value_decomposition( matrix const& )`, or `svd` +We execute Singular Value Decomposition by calling function `std::optional> singular_value_decomposition( matrix const& )`, or `svd`. +For an m×n matrix the tuple is `(u, w, v)` with thin shapes: `u` is m×k, `w` is the k×k diagonal matrix of singular values (descending) and `v` is n×k, k = min(m, n), so that `m == u * w * vᴴ` (use `feng::ctranspose( v )` for complex elements). +It is `std::nullopt` when the one-sided Jacobi iteration does not converge within 64 sweeps; `feng::svd_factor( m )` returns the factors with a status instead (see [Linear algebra](#linear-algebra)). ```cpp @@ -1592,10 +1728,10 @@ If the svd is successfully, we can verify the accuricy by reconstructing the noi // check svd result if (svd) // case successful { - // extracted svd result matrices, u, v w - auto const& [u, v, w] = (*svd); - // try to reconstruct matrix using u * v * w' - auto const& m_ = u * v * (w.transpose()); + // extracted svd result matrices: u (r x k), w (k x k, diagonal), v (c x k), k = min(r, c) + auto const& [u, w, v] = (*svd); + // try to reconstruct matrix using u * w * v' + auto const& m_ = u * w * (v.transpose()); // record reconstructed matrix m_.save_as_bmp( "./images/0002_singular_value_decomposition.bmp", "gray" ); ``` @@ -1617,10 +1753,10 @@ The code below demonstrates how: auto new_dm = dm / factor; feng::matrix const new_u{ u, std::make_pair(0UL, r), std::make_pair(0UL, new_dm) }; - feng::matrix const new_v{ v, std::make_pair(0UL, new_dm), std::make_pair(0UL, new_dm) }; - feng::matrix const new_w{ w, std::make_pair(0UL, c), std::make_pair(0UL, new_dm) }; + feng::matrix const new_w{ w, std::make_pair(0UL, new_dm), std::make_pair(0UL, new_dm) }; + feng::matrix const new_v{ v, std::make_pair(0UL, c), std::make_pair(0UL, new_dm) }; - auto const& new_m = new_u * new_v * new_w.transpose(); + auto const& new_m = new_u * new_w * new_v.transpose(); new_m.save_as_bmp( "./images/0003_singular_value_decomposition_"+std::to_string(new_dm)+".bmp", "gray" ); @@ -1763,6 +1899,8 @@ else std::cout << "Failed to execute Gauss-Jordan Elimination for matrix m.\n"; ``` +`gauss_jordan_elimination` and `rref` accept a matrix of any shape (square, tall, wide, rank deficient) and return its reduced row echelon form; a column without a pivot above max(m, n)·ε·‖m‖∞ is skipped, as in MATLAB's `rref`. They return `std::nullopt` only when the matrix holds a NaN or inf; `feng::row_echelon( m )` also gives the pivot columns and the rank. + after applying Gauss Jordan elimination, the matrix is reduced to a form of @@ -1817,9 +1955,11 @@ we can do LU decomposition simply with `std::optional ``` the result of the LU decompositon is a Maybe monad of a tuple of two matrices, i.e., `std::optional, feng::matrix>>`, -therefor, we need to check its value before using it. +therefor, we need to check its value before using it. It is `std::nullopt` when the matrix is singular (rank below n) or holds a NaN or inf. +The factorization uses partial pivoting, and, like MATLAB's two-output `[L, U] = lu(A)`, `L` is the row-permuted lower triangular matrix Pᵀ·L₀, so `A == L * U`; +`feng::lu_factor( m )` gives the unit lower triangular factor, the pivots and the status separately (see [Linear algebra](#linear-algebra)). -Then we can draw the lower matrix `L` +Then we can draw the (permuted) lower matrix `L` ![lu_2](./images/0002_lu_decomposition.bmp) @@ -1856,7 +1996,7 @@ We can multiply `L` and `U` back to see if the decomposition is correct or not. A typical use of LU Decomposition is to solve an equation in the form of `Ax=b`, this is done by calling `auto const& x = feng::lu_solver(A,b)`, and, again, the returned value is a Maybe monad, `std::optional lu_solver( matrix const&, matrix const& )`, -therefore we need to check its value before using it. +therefore we need to check its value before using it (it is `std::nullopt` for a singular `A`). `feng::solve( A, B )` solves for several right-hand sides at once and returns a `linalg_result`. ```cpp @@ -1914,7 +2054,9 @@ This is a single-file header-only library. Put `matrix.hpp` directly into the pr ## Building tests and examples -Simple execute `make` or `make test` or `make example` at the root folder. +Run `make test example` (or `make`) at the root folder. It builds with portable `-std=c++20 -O2 -Wall -Wextra -DFENG_MATRIX_PARALLEL -isystem tests -pthread` into `build/test_test` and `build/test_example` (`BUILD_DIR=...` picks another directory). `make FAST=1 test example` restores the old `-Ofast -flto=auto -funroll-all-loops -march=native` flags. Run both binaries from the root folder: the tests read `./images/*.npy` and the example rewrites `./images/*`. + +The checks run through `tools/check.sh [args] [--smoke]`, which builds only under `build/` and ends with `LANE PASS` or `LANE FAIL: `. Lanes: `gcc` and `clang` (C++20/23/26, debug and NDEBUG, serial and FENG_MATRIX_PARALLEL), `sanitize` (ASan+UBSan), `warnings` (`-Wall -Wextra -Werror`), `api` (one translation unit per API family, with and without `-fno-exceptions`), `examples` (runs the example in a scratch copy of `images/`), `make`, `ci`, `docs `, `tagged ''` and `all` (every regression lane; `tools/check.sh all --smoke` for a quick run). ## Notes and references @@ -2110,10 +2252,10 @@ namespace xxx void plot( string const& file_name_, string const& builtin_color_scheme_name_ ) const; //unary operators - matrix const operator+() const; - matrix const operator-() const; - matrix const operator~() const; - matrix const operator~() const; //bool only + matrix operator+() const; + matrix operator-() const; + matrix operator~() const; + matrix operator~() const; //bool only //computed assignment //TODO: return optional? @@ -2141,123 +2283,124 @@ namespace xxx }; //building functions - template< typename T, typename A > matrix const make_eye( size_type row_, size_type col_, A const& alloc_ ); - template< typename T > matrix> const make_eye( size_type row_, size_type col_ ); - template< typename T, typename A > matrix const make_zeros( size_type row_, size_type col_, A const& alloc_ ); - template< typename T > matrix> const make_zeros( size_type row_, size_type col_ ); - template< typename T, typename A > matrix const make_ones( size_type row_, size_type col_, A const& alloc_ ); - template< typename T > matrix> const make_ones( size_type row_, size_type col_ ); - template< typename T, typename A > matrix const make_diag( matrix const ); - template< typename T, typename A > matrix const make_triu( matrix const ); - template< typename T, typename A > matrix const make_tril( matrix const ); - template< typename T, typename A > matrix const make_rand( size_type row_, size_type col_, A const& alloc_ ); - template< typename T > matrix> const make_rand( size_type row_, size_type col_ ); - template< typename T, typename A > matrix const make_hilb( size_type n_, A const& alloc_ ); - template< typename T > matrix> const make_hilb( size_type n_ ); - template< typename T, typename A > matrix const make_magic( size_type n_, A const& alloc_ ); - template< typename T > matrix> const make_magic( size_type n_ ); - template< typename T, typename A, typename Input_Itor_1, typename Input_Iterator_2 > matrix const make_toeplitz( Input_Iterator_1 begin_, Input_Iterator_1 end_, Input_Iterator_2 begin_2_, A const alloc_ ); - template< typename T, typename A, typename Input_Itor > matrix const make_toeplitz( Input_Iterator begin_, Input_Iterator end_, A const alloc_ ); - template< typename T, typename Input_Itor_1, typename Input_Iterator_2 > const matrix > make_toeplitz( Input_Iterator_1 begin_, Input_Iterator_1 end_, Input_Iterator_2 begin_2_ ): - template< typename T, typename Input_Itor > matrix> const make_toeplitz( Input_Iterator begin_, Input_Iterator end_ ); - template< typename T, typename A > matrix const make_horizontal_cons( matrix const&, matrix const& ); - template< typename T, typename A > matrix const make_vertical_cons( matrix const&, matrix const& ); + template< typename T, typename A > matrix make_eye( size_type row_, size_type col_, A const& alloc_ ); + template< typename T > matrix> make_eye( size_type row_, size_type col_ ); + template< typename T, typename A > matrix make_zeros( size_type row_, size_type col_, A const& alloc_ ); + template< typename T > matrix> make_zeros( size_type row_, size_type col_ ); + template< typename T, typename A > matrix make_ones( size_type row_, size_type col_, A const& alloc_ ); + template< typename T > matrix> make_ones( size_type row_, size_type col_ ); + template< typename T, typename A > matrix make_diag( matrix const ); + template< typename T, typename A > matrix make_triu( matrix const ); + template< typename T, typename A > matrix make_tril( matrix const ); + template< typename T, typename A > matrix make_rand( size_type row_, size_type col_, A const& alloc_ ); + template< typename T > matrix> make_rand( size_type row_, size_type col_ ); + template< typename T, typename A > matrix make_hilb( size_type n_, A const& alloc_ ); + template< typename T > matrix> make_hilb( size_type n_ ); + template< typename T, typename A > matrix make_magic( size_type n_, A const& alloc_ ); + template< typename T > matrix> make_magic( size_type n_ ); + template< typename T, typename A, typename Input_Itor_1, typename Input_Iterator_2 > matrix make_toeplitz( Input_Iterator_1 begin_, Input_Iterator_1 end_, Input_Iterator_2 begin_2_, A const alloc_ ); + template< typename T, typename A, typename Input_Itor > matrix make_toeplitz( Input_Iterator begin_, Input_Iterator end_, A const alloc_ ); + template< typename T, typename Input_Itor_1, typename Input_Iterator_2 > matrix > make_toeplitz( Input_Iterator_1 begin_, Input_Iterator_1 end_, Input_Iterator_2 begin_2_ ): + template< typename T, typename Input_Itor > matrix> make_toeplitz( Input_Iterator begin_, Input_Iterator end_ ); + template< typename T, typename A > matrix make_horizontal_cons( matrix const&, matrix const& ); + template< typename T, typename A > matrix make_vertical_cons( matrix const&, matrix const& ); //binary operation //TODO: return optional? - template< typename T, typename A > matrix const operator * ( matrix const&, matrix const& ); - template< typename T, typename A > matrix const operator * ( matrix const&, T const& ); - template< typename T, typename A > matrix const operator * ( T const&, matrix const& ); + template< typename T, typename A > matrix operator * ( matrix const&, matrix const& ); + template< typename T, typename A > matrix operator * ( matrix const&, T const& ); + template< typename T, typename A > matrix operator * ( T const&, matrix const& ); - template< typename T, typename A > matrix const operator / ( matrix const&, matrix const& ); - template< typename T, typename A > matrix const operator / ( matrix const&, T const& ); - template< typename T, typename A > matrix const operator / ( T const&, matrix const& ); + template< typename T, typename A > matrix operator / ( matrix const&, matrix const& ); + template< typename T, typename A > matrix operator / ( matrix const&, T const& ); + template< typename T, typename A > matrix operator / ( T const&, matrix const& ); - template< typename T, typename A > matrix const operator + ( matrix const&, matrix const& ); - template< typename T, typename A > matrix const operator + ( matrix const&, T const& ); - template< typename T, typename A > matrix const operator + ( T const&, matrix const& ); + template< typename T, typename A > matrix operator + ( matrix const&, matrix const& ); + template< typename T, typename A > matrix operator + ( matrix const&, T const& ); + template< typename T, typename A > matrix operator + ( T const&, matrix const& ); - template< typename T, typename A > matrix const operator - ( matrix const&, matrix const& ); - template< typename T, typename A > matrix const operator - ( matrix const&, T const& ); - template< typename T, typename A > matrix const operator - ( T const&, matrix const& ); + template< typename T, typename A > matrix operator - ( matrix const&, matrix const& ); + template< typename T, typename A > matrix operator - ( matrix const&, T const& ); + template< typename T, typename A > matrix operator - ( T const&, matrix const& ); - template< typename T, typename A > matrix const operator % ( matrix const&, T const& ); - template< typename T, typename A > matrix const operator ^ ( matrix const&, T const& ); - template< typename T, typename A > matrix const operator & ( matrix const&, T const& ); - template< typename T, typename A > matrix const operator | ( matrix const&, T const& ); - template< typename T, typename A > matrix const operator << ( matrix const&, T const& ); - template< typename T, typename A > matrix const operator >> ( matrix const&, T const& ); + template< typename T, typename A > matrix operator % ( matrix const&, T const& ); + template< typename T, typename A > matrix operator ^ ( matrix const&, T const& ); + template< typename T, typename A > matrix operator & ( matrix const&, T const& ); + template< typename T, typename A > matrix operator | ( matrix const&, T const& ); + template< typename T, typename A > matrix operator << ( matrix const&, T const& ); + template< typename T, typename A > matrix operator >> ( matrix const&, T const& ); template< typename T, typename A > std::ostream& operator << ( std::ostream&, matrix const& ); template< typename T, typename A > std::istream& operator << ( std::istream&, matrix const& ); - //logical - template< typename T, typename A > matrix::rebind_alloc > const operator == ( matrix const&, matrix const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator == ( matrix const&, T const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator == ( T const&, matrix const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator != ( matrix const&, matrix const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator != ( matrix const&, T const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator != ( T const&, matrix const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator >= ( matrix const&, matrix const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator >= ( matrix const&, T const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator >= ( T const&, matrix const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator <= ( matrix const&, matrix const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator <= ( matrix const&, T const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator <= ( T const&, matrix const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator > ( matrix const&, matrix const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator > ( matrix const&, T const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator > ( T const&, matrix const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator < ( matrix const&, matrix const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator < ( matrix const&, T const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator < ( T const&, matrix const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator && ( matrix const&, matrix const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator && ( matrix const&, T const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator && ( T const&, matrix const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator || ( matrix const&, matrix const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator || ( matrix const&, T const& ); - template< typename T, typename A > matrix::rebind_alloc > const operator || ( T const&, matrix const& ); + //logical (design only, not implemented: since S3 `matrix` is ill-formed, see `matrix_element`; the + //implemented matrix-matrix ==, <, >, <= and >= return a single bool) + template< typename T, typename A > matrix::rebind_alloc > operator == ( matrix const&, matrix const& ); + template< typename T, typename A > matrix::rebind_alloc > operator == ( matrix const&, T const& ); + template< typename T, typename A > matrix::rebind_alloc > operator == ( T const&, matrix const& ); + template< typename T, typename A > matrix::rebind_alloc > operator != ( matrix const&, matrix const& ); + template< typename T, typename A > matrix::rebind_alloc > operator != ( matrix const&, T const& ); + template< typename T, typename A > matrix::rebind_alloc > operator != ( T const&, matrix const& ); + template< typename T, typename A > matrix::rebind_alloc > operator >= ( matrix const&, matrix const& ); + template< typename T, typename A > matrix::rebind_alloc > operator >= ( matrix const&, T const& ); + template< typename T, typename A > matrix::rebind_alloc > operator >= ( T const&, matrix const& ); + template< typename T, typename A > matrix::rebind_alloc > operator <= ( matrix const&, matrix const& ); + template< typename T, typename A > matrix::rebind_alloc > operator <= ( matrix const&, T const& ); + template< typename T, typename A > matrix::rebind_alloc > operator <= ( T const&, matrix const& ); + template< typename T, typename A > matrix::rebind_alloc > operator > ( matrix const&, matrix const& ); + template< typename T, typename A > matrix::rebind_alloc > operator > ( matrix const&, T const& ); + template< typename T, typename A > matrix::rebind_alloc > operator > ( T const&, matrix const& ); + template< typename T, typename A > matrix::rebind_alloc > operator < ( matrix const&, matrix const& ); + template< typename T, typename A > matrix::rebind_alloc > operator < ( matrix const&, T const& ); + template< typename T, typename A > matrix::rebind_alloc > operator < ( T const&, matrix const& ); + template< typename T, typename A > matrix::rebind_alloc > operator && ( matrix const&, matrix const& ); + template< typename T, typename A > matrix::rebind_alloc > operator && ( matrix const&, T const& ); + template< typename T, typename A > matrix::rebind_alloc > operator && ( T const&, matrix const& ); + template< typename T, typename A > matrix::rebind_alloc > operator || ( matrix const&, matrix const& ); + template< typename T, typename A > matrix::rebind_alloc > operator || ( matrix const&, T const& ); + template< typename T, typename A > matrix::rebind_alloc > operator || ( T const&, matrix const& ); //numeric functions - template< typename T, typename A, typename F > matrix const element_wise_apply ( matrix const&, F const& f_ ); + template< typename T, typename A, typename F > matrix element_wise_apply ( matrix const&, F const& f_ ); //Linear Equations //mldivide Solve systems of linear equations Ax = B for x - template< typename T, typename A, typename F > matrix const mldivide ( matrix const&, matrix const& ); + template< typename T, typename A, typename F > matrix mldivide ( matrix const&, matrix const& ); //mrdivide Solve systems of linear equations xA = B for x - template< typename T, typename A, typename F > matrix const mrdivide ( matrix const&, matrix const& ); + template< typename T, typename A, typename F > matrix mrdivide ( matrix const&, matrix const& ); //linsolve Solve linear system of equations - template< typename T, typename A, typename F > matrix const mrdivide ( matrix const&, matrix const& ); + template< typename T, typename A, typename F > matrix mrdivide ( matrix const&, matrix const& ); //inv Matrix inverse - template< typename T, typename A, typename F > matrix const inv ( matrix const& ); + template< typename T, typename A, typename F > matrix inv ( matrix const& ); //pinv Moore-Penrose pseudoinverse of matrix - template< typename T, typename A, typename F > matrix const pinv ( matrix const& ); + template< typename T, typename A, typename F > matrix pinv ( matrix const& ); //lscov Least-squares solution in presence of known covariance - template< typename T, typename A, typename F > matrix const lscov ( matrix const&, matrix const& ); - template< typename T, typename A, typename F > matrix const lscov ( matrix const&, matrix const&, matrix const& ); + template< typename T, typename A, typename F > matrix lscov ( matrix const&, matrix const& ); + template< typename T, typename A, typename F > matrix lscov ( matrix const&, matrix const&, matrix const& ); //lsqnonneg Solve nonnegative linear least-squares problem - template< typename T, typename A, typename F > matrix const lsqnonneg ( matrix const&, matrix const& ); - template< typename T, typename A, typename F > matrix const lsqnonneg ( matrix const&, matrix const&, matrix const& ); + template< typename T, typename A, typename F > matrix lsqnonneg ( matrix const&, matrix const& ); + template< typename T, typename A, typename F > matrix lsqnonneg ( matrix const&, matrix const&, matrix const& ); //sylvester Solve Sylvester equation AX + XB = C for X - template< typename T, typename A, typename F > matrix const sylvester ( matrix const&, matrix const&, matrix const& ); + template< typename T, typename A, typename F > matrix sylvester ( matrix const&, matrix const&, matrix const& ); //Eigenvalues ans Singular Values //eig Eigenvalues and eigenvectors - template< typename T, typename A, typename F > std::tuple,matrix> const eig( matrix const& ); + template< typename T, typename A, typename F > std::tuple,matrix> eig( matrix const& ); //eigs Subset of eigenvalues and eigenvectors -- TODO //balance Diagonal scaling to improve eigenvalue accuracy //svd Singular value decomposition - template< typename T, typename A, typename F > std::tuple,matrix,matrix> const svd( matrix const& ); + template< typename T, typename A, typename F > std::tuple,matrix,matrix> svd( matrix const& ); //svds Subset of singular values and vectors -- TODO //gsvd Generalized singular value decomposition - template< typename T, typename A, typename F > std::tuple,matrix,matrix,matrix> const gsvd( matrix const&, matrix const& ); + template< typename T, typename A, typename F > std::tuple,matrix,matrix,matrix> gsvd( matrix const&, matrix const& ); //ordeig Eigenvalues of quasitriangular matrices - template< typename T, typename A, typename F > matrix const ordeig( matrix const& ); - template< typename T, typename A, typename F > matrix const ordeig( matrix const&, matrix const& ); + template< typename T, typename A, typename F > matrix ordeig( matrix const& ); + template< typename T, typename A, typename F > matrix ordeig( matrix const&, matrix const& ); diff --git a/bench/bench.cc b/bench/bench.cc new file mode 100644 index 0000000..43489b6 --- /dev/null +++ b/bench/bench.cc @@ -0,0 +1,294 @@ +// bench/bench.cc: the S10-R1/S10-R2 (D-008) benchmark harness, built twice by `tools/check.sh bench compare` (once on +// the stage start commit's matrix.hpp, once on the working tree) with the same flags; it uses only API present at the +// baseline. One translation unit: matrix.hpp plus the standard library. +// +// Each workload builds its inputs from std::mt19937_64 seeded with a constant plus a hash of its name (without a par/ +// prefix), then: +// warmup: runs a batch of `reps` operations, doubling reps (from 1, capped) until one batch lasts >= 5 ms (1 ms with +// --smoke); an operation slower than that keeps reps = 1; +// timing: n samples of one batch each on steady_clock, printed as `SAMPLES reps ... ` in seconds +// per operation. +// Every result feeds a checksum sink, printed as `CHECKSUM `, so nothing is optimized away. The run starts with +// `INFO cpu `, `INFO cores `, `INFO compiler <__VERSION__>` and `INFO flags `. +// CLI: --list (names, one per line; with --smoke the smoke subset, one small size per family), --only (repeat +// to run several), --samples (default 10; 3 with --smoke), --smoke. +// Parallel variant (S10-T6): built with -DFENG_MATRIX_PARALLEL the harness offers only the par/ set (in_par_set: +// elementwise and statistics at every size, gemm 8/16/17/18/32/64/256/1024/tall/wide, gemv 8/32/64/1024), each +// name prefixed `par/` (e.g. par/add/4x4); --list, --smoke and --only work on those prefixed names. +#include "matrix.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef BENCH_FLAGS +#define BENCH_FLAGS "unknown" +#endif + +namespace +{ + using dmat = feng::matrix< double >; + using cmat = feng::matrix< std::complex< double > >; + + double sink = 0.0; + + void touch( double x ) { sink += x; } + void touch( std::complex< double > x ) { sink += x.real() + x.imag(); } + template < typename M > + void touch_matrix( M const& m ) + { + if ( m.size() != 0 ) touch( m[m.row() / 2][m.col() / 2] ); + } + + std::uint64_t name_hash( std::string const& s ) + { + std::uint64_t h = 1469598103934665603ULL; + for ( unsigned char c : s ) { h ^= c; h *= 1099511628211ULL; } + return h; + } + + dmat random_dmat( std::mt19937_64& gen, std::size_t r, std::size_t c, double lo, double hi ) + { + std::uniform_real_distribution< double > dist{ lo, hi }; + dmat m( r, c ); + for ( auto& v : m ) v = dist( gen ); + return m; + } + + cmat random_cmat( std::mt19937_64& gen, std::size_t r, std::size_t c ) + { + std::uniform_real_distribution< double > dist{ -1.0, 1.0 }; + cmat m( r, c ); + for ( auto& v : m ) v = std::complex< double >{ dist( gen ), dist( gen ) }; + return m; + } + + using op_type = std::function< void() >; + + struct workload + { + std::string name; + bool smoke; + std::function< op_type( std::mt19937_64& ) > setup; // builds the inputs, returns one operation + }; + + // The workloads of the parallel build (see the header comment); names without the par/ prefix. + bool in_par_set( std::string const& name ) + { + static char const* const fixed[] = { "gemm/8", "gemm/16", "gemm/17", "gemm/18", "gemm/32", "gemm/64", + "gemm/256", "gemm/1024", "gemm/tall", "gemm/wide", + "gemv/8", "gemv/32", "gemv/64", "gemv/1024" }; + for ( char const* f : fixed ) + if ( name == f ) return true; + static char const* const families[] = { "add/", "scale/", "map/", "sum/", "mean/", "variance/", "max/" }; + for ( char const* f : families ) + if ( name.rfind( f, 0 ) == 0 ) return true; + return false; + } + + std::vector< workload > make_workloads_all(); + + std::vector< workload > make_workloads() + { +#ifdef FENG_MATRIX_PARALLEL + std::vector< workload > par; + for ( auto& w : make_workloads_all() ) + if ( in_par_set( w.name ) ) par.push_back( { "par/" + w.name, w.smoke, std::move( w.setup ) } ); + return par; +#else + return make_workloads_all(); +#endif + } + + std::vector< workload > make_workloads_all() + { + std::vector< workload > w; + auto gemm = []( std::size_t m, std::size_t k, std::size_t n ) + { + return [m, k, n]( std::mt19937_64& gen ) -> op_type + { + auto a = std::make_shared< dmat >( random_dmat( gen, m, k, -1.0, 1.0 ) ); + auto b = std::make_shared< dmat >( random_dmat( gen, k, n, -1.0, 1.0 ) ); + return [a, b] { touch_matrix( ( *a ) * ( *b ) ); }; + }; + }; + for ( std::size_t n : { 8, 16, 17, 18, 32, 64, 128, 256, 512, 1024 } ) + w.push_back( { "gemm/" + std::to_string( n ), n == 32, gemm( n, n, n ) } ); + w.push_back( { "gemm/tall", false, gemm( 4096, 64, 64 ) } ); + w.push_back( { "gemm/wide", false, gemm( 64, 64, 4096 ) } ); + for ( std::size_t n : { 8, 16, 32, 64, 128, 256, 512, 1024 } ) + w.push_back( { "gemv/" + std::to_string( n ), n == 32, gemm( n, n, 1 ) } ); + + auto shaped = []( std::size_t n ) { return std::to_string( n ) + "x" + std::to_string( n ); }; + for ( std::size_t n : { 4, 32, 100, 1000 } ) + { + bool const sm = n == 32; + auto const sz = shaped( n ); + w.push_back( { "add/" + sz, sm, [n]( std::mt19937_64& gen ) -> op_type { + auto a = std::make_shared< dmat >( random_dmat( gen, n, n, 0.5, 1.5 ) ); + auto b = std::make_shared< dmat >( random_dmat( gen, n, n, 0.5, 1.5 ) ); + return [a, b] { touch_matrix( ( *a ) + ( *b ) ); }; } } ); + w.push_back( { "scale/" + sz, sm, [n]( std::mt19937_64& gen ) -> op_type { + auto a = std::make_shared< dmat >( random_dmat( gen, n, n, 0.5, 1.5 ) ); + return [a] { touch_matrix( ( *a ) * 1.0001 ); }; } } ); + w.push_back( { "map/" + sz, sm, [n]( std::mt19937_64& gen ) -> op_type { + auto a = std::make_shared< dmat >( random_dmat( gen, n, n, 0.5, 1.5 ) ); + return [a] { touch_matrix( feng::sqrt( *a ) ); }; } } ); + w.push_back( { "sum/" + sz, sm, [n]( std::mt19937_64& gen ) -> op_type { + auto a = std::make_shared< dmat >( random_dmat( gen, n, n, 0.5, 1.5 ) ); + return [a] { touch( feng::sum( *a ) ); }; } } ); + w.push_back( { "mean/" + sz, sm, [n]( std::mt19937_64& gen ) -> op_type { + auto a = std::make_shared< dmat >( random_dmat( gen, n, n, 0.5, 1.5 ) ); + return [a] { touch( feng::mean( *a ) ); }; } } ); + w.push_back( { "variance/" + sz, sm, [n]( std::mt19937_64& gen ) -> op_type { + auto a = std::make_shared< dmat >( random_dmat( gen, n, n, 0.5, 1.5 ) ); + return [a] { touch( feng::variance( *a ) ); }; } } ); + w.push_back( { "max/" + sz, sm, [n]( std::mt19937_64& gen ) -> op_type { + auto a = std::make_shared< dmat >( random_dmat( gen, n, n, 0.5, 1.5 ) ); + return [a] { touch( feng::max( *a ) ); }; } } ); + } + + for ( std::size_t n : { 64, 1024 } ) + { + bool const sm = n == 64; + auto const sz = std::to_string( n ); + w.push_back( { "transpose/" + sz, sm, [n]( std::mt19937_64& gen ) -> op_type { + auto a = std::make_shared< dmat >( random_dmat( gen, n, n, -1.0, 1.0 ) ); + return [a] { touch_matrix( a->transpose() ); }; } } ); + // a view of the n x n block at (n/4, n/3) of a 2n x 2n owner + w.push_back( { "view_copy/" + sz, sm, [n]( std::mt19937_64& gen ) -> op_type { + auto a = std::make_shared< dmat >( random_dmat( gen, 2 * n, 2 * n, -1.0, 1.0 ) ); + return [a, n] { + dmat const c{ feng::make_view( *a, { n / 4, n / 4 + n }, { n / 3, n / 3 + n } ) }; + touch_matrix( c ); }; } } ); + w.push_back( { "view_sum/" + sz, sm, [n]( std::mt19937_64& gen ) -> op_type { + auto a = std::make_shared< dmat >( random_dmat( gen, 2 * n, 2 * n, -1.0, 1.0 ) ); + return [a, n] { + auto const v = feng::make_view( *a, { n / 4, n / 4 + n }, { n / 3, n / 3 + n } ); + touch( std::accumulate( v.begin(), v.end(), 0.0 ) ); }; } } ); + } + + for ( std::size_t n : { 128, 256, 125, 250 } ) + w.push_back( { "fft/" + std::to_string( n ), n == 128 || n == 125, [n]( std::mt19937_64& gen ) -> op_type { + auto x = std::make_shared< cmat >( random_cmat( gen, n, n ) ); + return [x] { touch_matrix( feng::fft( *x ) ); }; } } ); + + auto conv = []( std::size_t n, std::size_t k ) + { + return [n, k]( std::mt19937_64& gen ) -> op_type + { + auto a = std::make_shared< dmat >( random_dmat( gen, n, n, -1.0, 1.0 ) ); + auto b = std::make_shared< dmat >( random_dmat( gen, k, k, -1.0, 1.0 ) ); + return [a, b] { touch_matrix( feng::conv( *a, *b ) ); }; + }; + }; + w.push_back( { "conv/64k5", true, conv( 64, 5 ) } ); + w.push_back( { "conv/256k7", false, conv( 256, 7 ) } ); + return w; + } + + std::string cpu_model() + { + std::ifstream in{ "/proc/cpuinfo" }; + std::string line; + while ( std::getline( in, line ) ) + if ( line.rfind( "model name", 0 ) == 0 ) + { + auto const p = line.find( ':' ); + if ( p != std::string::npos ) + { + auto s = line.substr( p + 1 ); + s.erase( 0, s.find_first_not_of( " \t" ) ); + return s; + } + } + return "unknown"; + } + + void run_workload( workload const& w, int samples, double target ) + { + // par/ workloads hash the unprefixed name, so they get the same inputs as their serial twins + std::string const base_name = w.name.rfind( "par/", 0 ) == 0 ? w.name.substr( 4 ) : w.name; + std::mt19937_64 gen{ 20261001ULL + name_hash( base_name ) }; + op_type const op = w.setup( gen ); + auto const batch = [&]( long reps ) + { + auto const t0 = std::chrono::steady_clock::now(); + for ( long r = 0; r != reps; ++r ) op(); + auto const t1 = std::chrono::steady_clock::now(); + return std::chrono::duration< double >( t1 - t0 ).count(); + }; + long reps = 1; + long const reps_cap = 1L << 24; + while ( batch( reps ) < target && reps < reps_cap ) reps *= 2; + std::printf( "SAMPLES %s reps %ld", w.name.c_str(), reps ); + for ( int s = 0; s != samples; ++s ) std::printf( " %.6e", batch( reps ) / static_cast< double >( reps ) ); + std::printf( "\n" ); + std::fflush( stdout ); + } +} + +int main( int argc, char** argv ) +{ + bool list = false, smoke = false; + int samples = -1; + std::vector< std::string > only; + for ( int i = 1; i < argc; ++i ) + { + if ( std::strcmp( argv[i], "--list" ) == 0 ) list = true; + else if ( std::strcmp( argv[i], "--smoke" ) == 0 ) smoke = true; + else if ( std::strcmp( argv[i], "--only" ) == 0 && i + 1 < argc ) only.emplace_back( argv[++i] ); + else if ( std::strcmp( argv[i], "--samples" ) == 0 && i + 1 < argc ) + { + samples = std::atoi( argv[++i] ); + if ( samples < 1 ) { std::fprintf( stderr, "bench: --samples needs a positive count\n" ); return 2; } + } + else { std::fprintf( stderr, "bench: unknown argument '%s'\nusage: bench [--list] [--only ]... [--samples ] [--smoke]\n", argv[i] ); return 2; } + } + if ( samples < 0 ) samples = smoke ? 3 : 10; + + auto const all = make_workloads(); + if ( list ) + { + for ( auto const& w : all ) + if ( !smoke || w.smoke ) std::printf( "%s\n", w.name.c_str() ); + return 0; + } + + std::vector< workload const* > chosen; + if ( only.empty() ) + { + for ( auto const& w : all ) + if ( !smoke || w.smoke ) chosen.push_back( &w ); + } + else + for ( auto const& name : only ) + { + workload const* found = nullptr; + for ( auto const& w : all ) + if ( w.name == name ) found = &w; + if ( !found ) { std::fprintf( stderr, "bench: unknown workload '%s'\n", name.c_str() ); return 2; } + chosen.push_back( found ); + } + + std::printf( "INFO cpu %s\n", cpu_model().c_str() ); + std::printf( "INFO cores %u\n", std::thread::hardware_concurrency() ); + std::printf( "INFO compiler %s\n", __VERSION__ ); + std::printf( "INFO flags %s\n", BENCH_FLAGS ); + std::fflush( stdout ); + double const target = smoke ? 0.001 : 0.005; + for ( auto const* w : chosen ) run_workload( *w, samples, target ); + std::printf( "CHECKSUM %.17g\n", sink ); + return 0; +} diff --git a/bench/kept.txt b/bench/kept.txt new file mode 100644 index 0000000..68cc7f1 --- /dev/null +++ b/bench/kept.txt @@ -0,0 +1,8 @@ +# bench/kept.txt: the kept S10 optimizations judged by `tools/check.sh bench compare` (S10-R3, D-008). +# One line per optimization: ` ...`, workload names as printed by `bench --list`. Each listed +# workload must be at least 10% faster (median) than the baseline. `#` starts a comment; blank lines are ignored. +gemm-blocked gemm/8 gemm/16 gemm/17 gemm/18 gemm/32 gemm/64 gemm/128 gemm/256 gemm/512 gemm/1024 gemm/tall gemm/wide gemv/8 gemv/16 gemv/32 gemv/64 gemv/128 gemv/256 gemv/512 gemv/1024 +# par-thresholds (S10-T7): work-based default worker counts in FENG_MATRIX_PARALLEL builds (matrix_details::work_workers). +par-thresholds par/add/4x4 par/add/32x32 par/add/100x100 par/add/1000x1000 par/map/100x100 par/map/1000x1000 par/sum/32x32 par/sum/100x100 par/sum/1000x1000 par/gemm/8 par/gemm/16 par/gemm/17 par/gemm/18 par/gemm/32 par/gemm/64 par/gemv/8 par/gemv/32 par/gemv/64 par/gemv/1024 +# fft-plans (S10-T8): one plan per length per fft2 call, reused across rows and columns (matrix_details::fft_plan). +fft-plans fft/128 fft/256 fft/125 fft/250 diff --git a/bench/results.md b/bench/results.md new file mode 100644 index 0000000..4b42000 --- /dev/null +++ b/bench/results.md @@ -0,0 +1,197 @@ +# S10 benchmark results + +The measured optimizations of stage S10 (S10-R3, D-008). Each kept optimization is listed in `bench/kept.txt` +under the same id and is judged by the compare lane on every full run. + +## How to run + +- `tools/check.sh bench compare`: full run, every workload of `bench --list`; judges kept optimizations and + regressions; ends with `LANE bench PASS: …` or `LANE bench FAIL: …`. About 145 s on the host below with the + parallel variant (95 s serial only). +- `tools/check.sh bench compare --smoke`: one small size per family plus the kept workloads; kept optimizations + are judged, regressions are printed but not judged. +- `BENCH_CPU=` sets the CPU the runs are pinned to (default 2); `BENCH_ONLY=` restricts the workloads, + for experiments only (a run that leaves out a kept workload cannot pass). +- Logs go to `build/logs/bench/`; binaries to `build/bench/compare/{base,head,base-par,head-par}/bench`. + +## Method + +- Baseline: `matrix.hpp` at the parent of the oldest commit whose subject is exactly `S10: spec and tasks`, + extracted with `git show`; head: the working tree. Both builds compile the same `bench/bench.cc` with the + first GCC and exactly `-std=c++20 -O2 -pthread` (no -O3, -march=native or fast-math, D-006). +- Inputs come from `std::mt19937_64` seeded with a constant plus a hash of the workload name. +- Each process warms up first: doubling a batch of operations until one batch lasts at least 5 ms (1 ms smoke). + Each sample is then one batch, in seconds per operation. +- Interleaving: per workload the lane runs 8 processes per build in the order ABBA BAAB ABBA BAAB (A base, + B head), 5 samples each, pinned with `taskset -c $BENCH_CPU`; the 40 samples of each build are pooled + (smoke: ABBA, 3 samples each, 6 pooled). +- Per build: median and rel. dispersion = median absolute deviation / median. Change = head median / base + median − 1. A workload is stable when its rel. dispersion is at most 5% in both builds (D-035). +- Judgement (D-008): every workload of a kept optimization has change ≤ −10%; no stable workload has change + > +5%; unstable workloads are printed with their numbers and not judged. + +## Host + +From the INFO lines of the full run below (head at commit c8b3206, baseline 78dc886, the parent of 3cbad44): + +- CPU: AMD Ryzen 9 7900X3D 12-Core Processor, 24 cores; pinned to cpu 2 (`taskset -c 2`); par/ workloads + unpinned +- Compiler: g++ 16.2.1 20260810 (all four builds); flags `-std=c++20 -O2 -pthread` (base, head) and + `-std=c++20 -O2 -pthread -DFENG_MATRIX_PARALLEL` (base-par, head-par) +- Mode: full, order ABBA BAAB ABBA BAAB, 8 runs x 5 samples = 40 pooled per build +- Result: `LANE bench PASS: 3 kept, 102 workloads, 6 unstable` (the six: conv/64k5, par/gemm/256, + par/gemm/1024, par/gemm/wide and the kept par/gemm/64 and par/gemv/1024); largest stable change outside the + kept workloads: par/map/4x4 +3.42% + +The tables below come from this run. Medians in seconds per operation; rel. dispersion base / head in %; label +`stable` when both are at most 5% (D-035). + +## Kept optimizations + +`gemm-blocked` (D-036): `operator*=`, `operator*` and `direct_multiply` use one cache-blocked row-major i-k-j +kernel (`matrix_details::gemm_blocked`) in serial and parallel builds, in place of the strided +`std::inner_product` per entry and, in serial builds, Strassen recursion for max dimension ≥ 17. Each entry is +the k-ascending sum from zero, bit-identical to the old kernel kept as `matrix_details::gemm_reference` +(`[S10][S10-R3]` tests). + +| id | workload | baseline median | head median | change % | dispersion % | label | +|---|---|---|---|---|---|---| +| gemm-blocked | gemm/8 | 2.5209e-07 | 1.5264e-07 | -39.45 | 1.34 / 2.07 | stable | +| gemm-blocked | gemm/16 | 1.5914e-06 | 9.7036e-07 | -39.02 | 0.38 / 0.40 | stable | +| gemm-blocked | gemm/17 | 3.4108e-06 | 1.1400e-06 | -66.58 | 0.81 / 0.39 | stable | +| gemm-blocked | gemm/18 | 6.5067e-06 | 2.2080e-06 | -66.07 | 0.67 / 0.55 | stable | +| gemm-blocked | gemm/32 | 1.4983e-05 | 7.2298e-06 | -51.75 | 0.24 / 0.42 | stable | +| gemm-blocked | gemm/64 | 1.7852e-04 | 5.7231e-05 | -67.94 | 0.92 / 0.19 | stable | +| gemm-blocked | gemm/128 | 1.2685e-03 | 4.5228e-04 | -64.34 | 0.57 / 0.18 | stable | +| gemm-blocked | gemm/256 | 8.4660e-03 | 3.9624e-03 | -53.20 | 0.26 / 0.48 | stable | +| gemm-blocked | gemm/512 | 5.7637e-02 | 3.0558e-02 | -46.98 | 0.51 / 0.36 | stable | +| gemm-blocked | gemm/1024 | 3.9512e-01 | 2.3764e-01 | -39.86 | 0.61 / 0.75 | stable | +| gemm-blocked | gemm/tall | 7.5342e-02 | 4.9836e-03 | -93.39 | 1.64 / 1.75 | stable | +| gemm-blocked | gemm/wide | 6.2296e-02 | 4.0113e-03 | -93.56 | 1.10 / 2.94 | stable | +| gemm-blocked | gemv/8 | 5.5061e-08 | 3.8638e-08 | -29.83 | 1.79 / 0.96 | stable | +| gemm-blocked | gemv/16 | 1.4597e-07 | 8.6724e-08 | -40.59 | 1.85 / 1.93 | stable | +| gemm-blocked | gemv/32 | 4.7085e-07 | 2.6251e-07 | -44.25 | 0.94 / 1.56 | stable | +| gemm-blocked | gemv/64 | 2.1021e-06 | 1.1171e-06 | -46.86 | 0.20 / 0.10 | stable | +| gemm-blocked | gemv/128 | 9.3135e-06 | 4.6953e-06 | -49.59 | 0.21 / 0.22 | stable | +| gemm-blocked | gemv/256 | 4.3272e-05 | 1.9614e-05 | -54.67 | 0.29 / 0.37 | stable | +| gemm-blocked | gemv/512 | 1.8907e-04 | 8.6784e-05 | -54.10 | 0.41 / 1.09 | stable | +| gemm-blocked | gemv/1024 | 7.8252e-04 | 3.4892e-04 | -55.41 | 0.47 / 1.40 | stable | + +### par-thresholds + +`par-thresholds` (S10-T7): in `FENG_MATRIX_PARALLEL` builds the default worker count is work-based, +`matrix_details::work_workers( work, grain )` = min( hardware_concurrency, work / grain ), at least 1, in place of +`hardware_concurrency` workers for every elementwise call (threshold 0), for `for_each` above 1024 elements and for +reductions above 32 elements. Grains: 2^16 elements for elementwise calls (`+=`, `-=`, `+`, `-`, unary minus, +`copy`, `clone`, `meshgrid`, `save_as_bmp` rows), callbacks (`for_each`, the maps, `apply`, colormaps, `pooling`) +and reductions (`sum`, `min`, `max`, `reduce`); 2^18 multiply-adds (M·K·N) for products, whose rows are split over +at most M workers. Explicit worker counts and serial builds are unchanged; the 1-worker run is the fallback, +and `[S10][S10-R3]` "thresholded parallel helpers equal the serial path" shows elementwise results bit-identical +to it, reductions equal to `reduce_range` with the same worker count and products bit-identical to +`gemm_reference`. + +How the grains were chosen: a scratch program timed `parallel_workers`, `reduce_range` and `gemm_blocked` at +1, 2, 3, 4, 6, 8, 12, 16 and 24 workers (median of 15 batches each, warm inputs, -O2). Starting and joining one +`std::jthread` costs about 19 µs, so at 24 workers any call costs about 0.45 ms. One worker is fastest up to +about 2^15 elements for sqrt via `for_each` (54 µs vs 58 µs on 2), 2^16 for sum (38 µs vs 49 µs) and 2^18 +multiply-adds for GEMM (64^3: 51 µs vs 58 µs on 3). At 2^17 elements sqrt is 211 µs on 1 worker and 111 µs on 4; +96^3 GEMM is 171 µs on 1 and 105 µs on 4. The elementwise grain comes from the workload itself: par/add/1000x1000 +(a + b into a fresh result), three interleaved runs each, gave 0.75–0.89 ms at grain 2^16 (15 workers), +0.84–0.90 ms at 2^17 (7), 0.98–1.06 ms at 2^18 (3) and 0.98–1.06 ms at the baseline's 24. For par/map/1000x1000 a +callback grain of 2^16 (15 workers, 0.92–0.95 ms) beat 2^13–2^15 (24 workers, 1.00–1.09 ms). + +par/gemm/256, par/gemm/1024, tall and wide are not listed: their worker count is 24 in both builds, so their gains +come from `gemm-blocked`. par/map/1000x1000 is closest to the limit (−14.00 %). par/gemm/64 and par/gemv/1024 are +unstable in this run (baseline dispersion 5.25 %); both are far beyond the 10 % bar. + +| id | workload | baseline median | head median | change % | dispersion % | label | +|---|---|---|---|---|---|---| +| par-thresholds | par/add/4x4 | 3.0267e-04 | 1.6460e-08 | -99.99 | 4.08 / 1.24 | stable | +| par-thresholds | par/add/32x32 | 4.7099e-04 | 2.7403e-07 | -99.94 | 2.50 / 1.25 | stable | +| par-thresholds | par/add/100x100 | 4.7990e-04 | 3.2316e-06 | -99.33 | 2.96 / 1.79 | stable | +| par-thresholds | par/add/1000x1000 | 8.4921e-04 | 6.7131e-04 | -20.95 | 2.16 / 2.08 | stable | +| par-thresholds | par/map/100x100 | 4.7906e-04 | 1.6933e-05 | -96.47 | 2.07 / 0.71 | stable | +| par-thresholds | par/map/1000x1000 | 9.6448e-04 | 8.2944e-04 | -14.00 | 1.82 / 1.20 | stable | +| par-thresholds | par/sum/32x32 | 4.6266e-04 | 5.5930e-07 | -99.88 | 3.38 / 0.70 | stable | +| par-thresholds | par/sum/100x100 | 4.5574e-04 | 5.8643e-06 | -98.71 | 2.82 / 1.35 | stable | +| par-thresholds | par/sum/1000x1000 | 4.7748e-04 | 3.0659e-04 | -35.79 | 1.81 / 1.94 | stable | +| par-thresholds | par/gemm/8 | 1.3027e-04 | 1.3194e-07 | -99.90 | 1.76 / 1.02 | stable | +| par-thresholds | par/gemm/16 | 2.8894e-04 | 8.9055e-07 | -99.69 | 0.98 / 0.81 | stable | +| par-thresholds | par/gemm/17 | 3.0637e-04 | 1.0283e-06 | -99.66 | 1.93 / 1.13 | stable | +| par-thresholds | par/gemm/18 | 3.3770e-04 | 1.1884e-06 | -99.65 | 2.70 / 1.35 | stable | +| par-thresholds | par/gemm/32 | 4.8804e-04 | 6.6837e-06 | -98.63 | 4.43 / 1.13 | stable | +| par-thresholds | par/gemm/64 | 5.0838e-04 | 5.2863e-05 | -89.60 | 5.25 / 1.06 | unstable | +| par-thresholds | par/gemv/8 | 1.3267e-04 | 3.3619e-08 | -99.97 | 1.72 / 2.87 | stable | +| par-thresholds | par/gemv/32 | 4.8237e-04 | 2.5033e-07 | -99.95 | 4.78 / 1.64 | stable | +| par-thresholds | par/gemv/64 | 4.8741e-04 | 1.0649e-06 | -99.78 | 4.60 / 1.66 | stable | +| par-thresholds | par/gemv/1024 | 9.5979e-04 | 5.7155e-04 | -40.45 | 5.25 / 3.93 | unstable | + +Rows of this run outside the kept workloads with change ≥ +2 %, all stable and in code paths the kept changes +leave serial or untouched: par/map/4x4 +3.42 % (16 elements, one worker in both builds), map/1000x1000 +2.31 %, +scale/4x4 +2.08 %. In the run before `par-thresholds` par/map/4x4 was +1.54 %; these rows move by a few percent +between runs (D-035). + +### fft-plans + +`fft-plans` (S10-T8, B-042): `matrix_details::fft2` builds one `fft_plan` per length per call and reuses it for +every row (length C) and every column (length R; the row plan when R == C). A radix-2 plan holds the twiddle +table for the sign used; a Bluestein plan for length n holds the chirp w (n), the forward radix-2 transform of b +(length M) and the length-M twiddle tables for -1 and +1, plus the length-M scratch. The per-row path that +rebuilt all of these for each row and column (three table builds and the chirp transform per Bluestein call) is +kept as `matrix_details::fft2_reference`; every plan value comes from the same `std::polar` expressions in the +same order, and `[S10][S10-R3]` "fft with reused plans equals the per-row transform" shows fft and ifft +bit-identical to it (int, float, double, complex; 0x0 to 250x3). + +| id | workload | baseline median | head median | change % | dispersion % | label | +|---|---|---|---|---|---|---| +| fft-plans | fft/128 | 3.4088e-04 | 2.0700e-04 | -39.28 | 1.67 / 2.14 | stable | +| fft-plans | fft/256 | 1.6610e-03 | 1.1714e-03 | -29.48 | 1.20 / 0.44 | stable | +| fft-plans | fft/125 | 2.4250e-03 | 8.7432e-04 | -63.95 | 0.87 / 3.62 | stable | +| fft-plans | fft/250 | 1.0221e-02 | 3.8516e-03 | -62.32 | 0.80 / 3.08 | stable | + +## Parallel variant + +The lane also builds both headers with `-std=c++20 -O2 -pthread -DFENG_MATRIX_PARALLEL` (`base-par`, `head-par`). +Their workloads are named `par/`: elementwise (add, scale, map) and statistics (sum, mean, variance, +max) at all four sizes, gemm 8/16/17/18/32/64/256/1024/tall/wide and gemv 8/32/64/1024. They use the same +inputs, interleave and rules as the serial rows but run unpinned (they need many cores). Starting point for the +threshold work (before `par-thresholds`), from one full run (head 9f75ba6 plus this lane, baseline 78dc886; `LANE bench PASS: 1 kept, 102 +workloads, 5 unstable`). Medians in seconds per operation; rel. dispersion base / head in %. + +| workload | baseline median | head median | change % | dispersion % | +|---|---|---|---|---| +| par/add/4x4 | 3.1543e-04 | 3.1329e-04 | -0.68 | 5.40 / 6.26 | +| par/scale/4x4 | 1.4137e-08 | 1.3950e-08 | -1.32 | 3.31 / 1.05 | +| par/map/4x4 | 2.7156e-08 | 2.7574e-08 | +1.54 | 2.10 / 1.34 | +| par/sum/4x4 | 4.4818e-09 | 4.2869e-09 | -4.35 | 1.81 / 1.39 | +| par/add/32x32 | 4.6794e-04 | 4.6743e-04 | -0.11 | 3.19 / 3.16 | +| par/sum/32x32 | 4.5935e-04 | 4.5610e-04 | -0.71 | 2.61 / 1.77 | +| par/add/100x100 | 4.7078e-04 | 4.6572e-04 | -1.07 | 1.91 / 2.21 | +| par/map/100x100 | 4.7225e-04 | 4.7453e-04 | +0.48 | 2.55 / 1.56 | +| par/add/1000x1000 | 9.4195e-04 | 9.7350e-04 | +3.35 | 7.26 / 6.49 | +| par/gemm/8 | 1.3303e-04 | 1.3789e-04 | +3.65 | 3.42 / 6.01 | +| par/gemm/64 | 4.9291e-04 | 4.7936e-04 | -2.75 | 3.51 / 2.76 | +| par/gemm/256 | 1.5610e-03 | 7.3545e-04 | -52.89 | 3.05 / 1.09 | +| par/gemm/1024 | 2.1967e-01 | 2.4593e-02 | -88.80 | 4.87 / 7.57 | +| par/gemv/8 | 1.3303e-04 | 1.3271e-04 | -0.24 | 3.09 / 3.54 | + +What the numbers show: a call that reaches the parallel loop costs 0.13–0.5 ms however small it is (add at every +size, map from 100x100, sum from 32x32, gemm and gemv at every size up to 64; par/add/4x4 is 3.1e-04 s, serial +add/32x32 3.2e-07 s against par/add/32x32 4.7e-04 s); scale, mean, variance, max and the 4x4 statistics stay at +serial cost. In the parallel build the head GEMM gains 53% at 256 and 89% at 1024. The full table is in the lane +output (`build/logs/bench/compare.log`). These are the numbers before `par-thresholds` (above), which is now +listed in `bench/kept.txt`. + +## Rejected candidates + +None yet. + +## Unstable workloads + +From the full run above, not judged (rel. dispersion base / head): + +- conv/64k5: baseline 4.2579e-05 s, head 4.0150e-05 s, change −5.70 %, 10.07 % / 5.13 % +- par/gemm/256: baseline 1.9032e-03 s, head 7.7015e-04 s, change −59.53 %, 14.46 % / 3.20 % +- par/gemm/1024: baseline 2.3209e-01 s, head 2.7134e-02 s, change −88.31 %, 7.17 % / 10.15 % +- par/gemm/wide: baseline 9.7876e-03 s, head 8.5125e-04 s, change −91.30 %, 5.52 % / 2.59 % +- par/gemm/64 and par/gemv/1024 (kept, `par-thresholds` table above) diff --git a/docs/modern_cpp_optimization_plan.md b/docs/modern_cpp_optimization_plan.md new file mode 100644 index 0000000..d13534f --- /dev/null +++ b/docs/modern_cpp_optimization_plan.md @@ -0,0 +1,192 @@ +# Matrix library review and executable modernization plan + +**Recommendation:** repair storage, bounds, parsing, and numerical contracts before optimizing kernels or raising the language requirement. Keep a C++20 compatibility path during the repair release, introduce C++23 vocabulary types through adapters, and qualify C++26 features individually. The existing test suite is useful but does not justify a production-safety claim. + +Prepared **2026-10-01** against commit **`8f5489966f94c3d9356697312248ec4d86d663e8`**. The reviewed [matrix.hpp](../matrix.hpp) has 7,688 lines and SHA-256 `4840a67d1484d184f7cfba99f7b9555fbc1ae3560a5638503e56b649ea8976fa`. Line references below belong to that snapshot. Inputs include the [July review](opencode_sharded_review.md), [ReadMe](../ReadMe.md), [tests](../tests/test.cc), [Makefile](../Makefile), and GitHub workflows. Only this report is delivered; no library, test, example, or build source was changed or added. + +“Bug-free” should be an engineering objective with a release gate: **zero unresolved known correctness or memory-safety defects in the supported API, no findings in the specified analysis/test campaigns, and passing downstream acceptance tests**. Neither this review, sanitizers, nor adopting C++26 can prove that arbitrary C++ programs are free of bugs. Unsupported types, formats, and operations must fail explicitly instead of remaining accidental template instantiations. + +**Evidence and scope.** This is a source review plus builds and execution of existing tests. No new C++ probes or regression tests were written, respecting the research-only request. Findings marked **S** follow directly from source; **E** means observed in this review; **H** means empirical evidence recorded in the earlier review, not reproduced here. Proposed regression inputs are specifications for the implementation phase, not claims of execution. Numerical algorithms, optional integrations, and custom allocators are not exhaustively verified. + +| Check performed | Result and limits | +|---|---| +| GCC 16.2.1, existing suite, C++20, `-O2 -DPARALLEL -pthread` | **E: passed**, 49,216,592 assertions in 57 test cases. Uses ordinary floating-point optimization, rather than the Makefile's `-Ofast`. | +| GCC 16.2.1, existing suite, C++20, `-O1 -DNDEBUG -DPARALLEL -fsanitize=address,undefined -fno-omit-frame-pointer -pthread` | **E: passed**, 49,216,592 assertions in 57 cases; no sanitizer diagnostic in the captured run, with leak detection enabled. This covers only existing test paths. | +| Clang 22.1.8, existing suite, C++20, `-fsyntax-only -DPARALLEL -pthread` | **E: failed**, six incomplete-template errors at lines 3366–3368 and 3406–3408, plus two warnings. | +| Clang 22.1.8, ASan/UBSan build, C++20 release-check configuration | **E: compilation failed** at the same six locations; no Clang sanitizer executable ran. | +| GCC 16.2.1, existing suite, `-std=c++26 -fsyntax-only -DPARALLEL -pthread` | **E: passed syntax check**, with deprecated literal-operator spelling warnings at line 362. Does not establish C++26 library-feature availability or runtime correctness. | +| New defect probes, fuzzing, TSan, MSan, MSVC, optional OpenCV build, downstream projects, benchmarks | **Not run.** These require future harnesses, platforms, dependencies, or consumer access. No performance gain is claimed. | + +The exact commands and decisive output are recorded at the end. The large assertion count mainly reflects loops over existing happy paths; it is not an API-coverage measure. + +**The July findings still require action, but several recommendations need correction.** The current source retains the important defect patterns. The earlier report has some inconsistent line numbers and finding labels, so use the symbols and the fresh references here when implementing. + +| Earlier finding | Current assessment and implementation consequence | +|---|---| +| C1, C2: crop/pad and flip memory corruption | Confirmed in source at 3518–3535 and 4451–4486; historical ASan evidence remains relevant. Fix before structural refactoring. | +| C3: flipped aliases | Confirmed at 4491–4499. Correct axis semantics with migration notes. | +| C4, R1: pseudoinverse and SVD naming | `pinverse` still multiplies by singular values. `svd_inverse` compensates for its swapped output-variable names in subsequent expressions, so the naming alone is not a separate numerical bug. It still ignores SVD failure and leaves small nonzero singular values uninverted rather than setting them to zero. Do **not** use it as an already-qualified replacement. | +| C5, P2: determinant and decomposition | Confirmed; the same block-inversion assumption also breaks `inverse` for some invertible matrices. Unpivoted LU is not a safe replacement. A singular matrix has determinant zero, not automatically an error or NaN. | +| C6: matrix power | The bad expression is inside an ordinary runtime `if`. The entire function specialization must compile, including that branch, for **any exponent**, not only odd exponents. Test 0, 1, 2, 3, and larger powers. | +| C7 / S1 / S2: assertions, NPY, PNG | Confirmed and expanded below. With `NDEBUG`, `better_assert` still evaluates its expression but the failure reporter does nothing. Column indexing through `m[r][c]` is unchecked even in debug builds. | +| C8, C9, C10: statistics, convolution, RREF | More than local assertion fixes are needed. Statistics have type and denominator inconsistencies; full convolution also has an orientation bug; RREF needs independent pivot-row and pivot-column tracking for rank-deficient or tall inputs. Merely relaxing `row < col` can expose out-of-bounds access. | +| C11: global random state | Global reseeding is confirmed. A data race is not proven on every implementation; thread-safety of the C random facility is implementation-dependent. Use caller-owned engines for isolation and reproducibility. | +| C12: zero hardware concurrency | The iterator-based reduction at 1146–1177 has the zero-divisor path. The separate reduction at 4036–4040 already guards values ≤1; the earlier report's proposed second zero guard was misplaced. That second helper has other defects. | +| C13, P1: Fourier transforms | Correct total work is Θ(R²C²), or Θ(N⁴) for an N×N image, not Θ(R⁴C⁴). At 256×256 this is about 4.29 billion innermost iterations, not trillions. Both transforms also use the wrong input indices. Shift functions perform a transform as well as a shift and mishandle odd sizes. | +| T1, T2 | Coverage gaps remain. Add API-instantiation and adversarial tests, not simply more iterations of existing tests. | +| A1, A2, A3, R2, R3 | Simplify CRTP and boilerplate later. `matrix_view` does reuse some mixins, so “exactly one concrete class” overstates the case. Forwarding aliases and ADL-friendly math names are not inherently defects; preserve compatible aliases and constrain overloads. Top-level `const` value returns can inhibit moves; they are not merely cosmetic. | +| Earlier “verified non-issues” | Do not carry forward a blanket clearance for BMP, full convolution, or pooling. BMP validation is incomplete; full convolution is incorrect for asymmetric inputs; pooling mishandles negative maxima and invalid mode names. Large/nonfinite `expm` inputs also need explicit domain and shift-range tests rather than an “unreachable” assumption. | + +**Release-blocking source findings.** Severity describes potential impact; priority reflects the order of work. P0 covers memory corruption, invalid object state, and unsafe external-input boundaries. P1 covers numerical correctness, portability, and failure handling. Source-derived examples below must become regression tests before their fixes are accepted. + +| ID | Priority / evidence | Location in matrix.hpp | Finding, trigger, and required outcome | +|---|---|---|---| +| F01 | P0 / S,H | `shrink_to_size`, 3518–3535; `flipdim`, 4451–4486 | Crop/pad copies the row count as the number of columns. A 5×5→5×3 resize can write past storage. Dimension-2 flip swaps a column with a row; non-square matrices can corrupt memory. Empty flips also subtract one from a zero unsigned extent. Use correct extents and define empty behavior. | +| F02 | P0 / S | constructors, 3790–3843; `resize`/`reshape`, 2834–2868; public fields, 3780–3783 | Extent multiplication is unchecked. Signed initializer-list dimensions are cast to `unsigned long`; other dimensions convert to an unsigned size type. On this 64-bit target, extents 2⁶³×2 wrap the element count to zero while retaining huge extents. Release `reshape` can claim more elements than allocated. Public `row_`, `col_`, `dat_`, and `allocator_` let callers bypass all invariants. Validate signed values, element/byte counts, allocator limits, and pointer-difference limits before mutation. | +| F03 | P0 / S | `matrix(matrix_view const&)`, 3899–3903 | This constructor does not initialize the scalar storage members before `resize` reads them. It also copies from the first source row to an end in a later row, ignores column offsets, and can overrun the destination. Repair initialization and copy precisely the selected rectangle. | +| F04 | P0 / S | constructors, 3810–3843; `clear`, 1853–1863; `copy`, 1992–1998; `swap`, 3589–3596 | Raw allocation is followed by assignment, without general element construction or destruction. This is not valid support for arbitrary nontrivial `T`; implicit-lifetime rules for suitable scalar types do not justify the generic API. Copy assignment installs the source allocator before releasing destination storage, so unequal stateful allocators can free memory through the wrong resource. Allocator propagation traits and exception guarantees are not honored consistently. | +| F05 | P0 / S | `stride_iterator`, 1192–1310; column ends, 1928–1954; diagonal/anti-diagonal ranges, 1531–1760, 2087–2275 | A column end for column c>0 becomes `data + rows*cols + c`, beyond one-past the array. Similar diagonal ends exceed the allocation. Anti-diagonals of a one-column matrix have zero stride, including division by zero in iterator distance. Represent end positions logically and form pointers only for dereferenceable positions. | +| F06 | P0 / S | `make_view`, 4094–4112; `matrix_view`, 3709–3719; `clone`, 1871–1912 | Views borrow a matrix through `const&` and accept temporary owners. Stored views can therefore dangle. Resizing or destroying the owner invalidates extents/lifetime assumptions. Range normalization silently changes invalid requests; cloning lacks source upper-bound validation. Reject temporary-owner borrowing and validate slices in release builds. | +| F07 | P0 / S,H | `load_npy`, 2508–2564 | Reads the version before checking buffer length, trusts header offsets, never validates magic or dtype, and copies typed values from raw bytes without establishing alignment/representation preconditions. Foreign endianness is ignored; Fortran order changes shape instead of preserving the logical matrix. Malformed numeric fields can throw through `noexcept`. Parse, validate and convert into a temporary before committing. | +| F08 | P0 / S | text/binary loading, 2421–2495; BMP loading, 6643–6689; PNG writer, 3096–3348 | Empty text dereferences `rbegin`; ragged text can be flattened into a different rectangular matrix. Binary loading allocates and changes the destination before validating the payload, and accepts unchecked native object representations. BMP assumes a 54-byte, 24-bit layout without validating signature, offset, compression or signed height; arithmetic can overflow. PNG uses an unchecked `FILE*` and does not report write/close failure. | +| F09 | P0 / S | access, 1796–1821; multi-input maps, 3954–3980; vector product, 4281–4290; pooling, 6738–6742 | Public shape checks disappear as enforcement in release builds. Left-vector multiplication checks vector length against columns instead of rows: a length-3 vector times a 2×3 matrix passes the debug check and reads a third column element beyond storage. Multi-input internal maps trust the first input's size. Unknown pooling actions dereference `map::end()` after the ineffective release assertion. Validate before traversal. | +| F10 | P1 / E,S | image mixins, 3366–3368, 3406–3408; declaration 137; definition 3723 onward | Clang rejects nondependent local `matrix` objects while the class is incomplete. GCC acceptance does not make the header portable. Move these method definitions after the complete type or separate the image layer. | +| F11 | P1 / S | `noexcept` constructors and operations throughout; `parallel`, 267–326 | Allocation, I/O, parsing, user callbacks and thread creation can throw through unconditional `noexcept`. An escaping worker exception terminates the process; failure partway through creating a vector of joinable threads is also unsafe. Specify exception guarantees, join workers on every exit, and propagate errors after joining. | +| F12 | P1 / S | `parallel`, 317–322; reductions, 1146–1177 and 4024–4062; `map`, 4013–4018 | The final parallel chunk omits `dim_first`, so nonzero-origin ranges can overlap or execute outside the requested range. The iterator reduction divides by a possibly zero core count. Curried `reduce` captures its by-value `init` parameter by reference in the returned lambda, which dangles immediately; curried `map` can retain a temporary callable by reference. The second reduction repeats `init` per worker and can form invalid pointers when workers exceed elements. These internal helpers are not all used by the current public reductions. | +| F13 | P1 / S,H | determinant, 2055–2082; inverse, 2370–2407; LU, 6499–6530; SVD, 4921–5236 | Block determinant/inverse assumes invertible subblocks. The invertible 4×4 block-exchange matrix with zero 2×2 diagonal blocks is a counterexample. LU lacks pivoting and a final-pivot singularity check. `pinverse` omits reciprocation; both inverse wrappers ignore convergence failure. Require reliable factorization, singular/rank policy, and residual checks. | +| F14 | P1 / S | `fft`/`ifft`, 6313–6337, 6446–6476; shifts, 6349–6366, 6480–6496 | Inner sums use `x[r][c]`, not `x[r_][c_]`; an off-origin impulse therefore does not transform correctly. The inverse has no normalization. `fftshift`/`ifftshift` unexpectedly compute transforms and use incompatible odd-size swaps. Specify transform sign, scaling, axes, and pure permutation semantics before optimizing. | +| F15 | P1 / S | `conv`, 6573–6634; RREF, 6393–6429; statistics, 7634–7662; maxima, 3635–3675, 6720–6726 | Convolution does not reverse the kernel. Source tracing for 1×2 inputs [1,2] and [3,4] gives [6,11,4], whereas convolution is [3,10,8]. Maxima initialize with floating `min()` (smallest positive normal), so all-negative maxima are wrong. Integer mean truncates, and signed sums can convert to unsigned during division. Variance uses N while standard deviation uses N−1; integral instantiations also need return-type compilation checks. | +| F16 | P1 / S,H | matrix power, 5555–5570; valarray product, 4257–4265; clone overload, 1871–1876; OpenCV, 1432 onward | Public templates contain latent compile errors: power precedence, nonexistent `valarray::row()`, and indexing `initializer_list` with `[]`. The OpenCV branch calls `isContiguous()`; the documented `cv::Mat` API is `isContinuous()`. The OpenCV branch was not built here. Add explicit API instantiation coverage and adapter tests. | +| F17 | P1 / S | random, 5242–5253; scalar and mixed arithmetic throughout | Random generation reseeds global state, uses a floating formula for unconstrained types, and does not establish its claimed open interval across rounding/types. Mixed operations often keep the left element type, risking narrowing; signed element arithmetic can overflow independently of shape safety. Define supported scalar domains and promotion/overflow behavior. | +| F18 | P1 / S | Makefile; `.github/workflows/ci.yml`; header configuration, 4–66 | CI selects GCC 12 and `PARALLEL` only. Makefile uses `-Ofast` and `-march=native`; the former can invalidate NaN/Inf-based diagnostics, and the latter is inappropriate for portable release qualification. Macro-dependent definitions risk ODR violations if translation units disagree. The C++20 version check uses the draft value 201709L rather than the final 202002L, and the version constant remains 20240314. | + +F05 is a language-level issue even without dereferencing the end pointer: pointer arithmetic is limited to an array and its one-past position. Sanitizers do not necessarily report every invalid pointer formation. [C++ working draft, pointer addition](https://eel.is/c++draft/expr.add). + +For F07, dtype, byte order, shape, and Fortran layout are actual format metadata, not optional hints; rank-2 support can be deliberately narrower than NumPy if rejected cases are documented. [NumPy NPY specification](https://numpy.org/doc/stable/reference/generated/numpy.lib.format.html). F14/F15 use the conventional NumPy/SciPy contracts as the proposed future behavior, with a migration path for existing consumers. [Inverse FFT](https://numpy.org/doc/stable/reference/generated/numpy.fft.ifft2.html), [frequency shift](https://numpy.org/doc/stable/reference/generated/numpy.fft.fftshift.html), [2D convolution](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.convolve2d.html). F16's OpenCV comparison is against the official [cv::Mat reference](https://docs.opencv.org/4.x/d3/d63/classcv_1_1Mat.html). + +**Target design and contracts.** Keep the recognizable `feng::matrix` facade while shrinking the trusted implementation. Separate ownership, borrowing, algorithms, external-data parsing, and optional acceleration. The relevant Core Guidelines principles are RAII (R.1), avoiding explicit allocation management where possible (R.11), expressing invariants in the type (C.2), and using `noexcept` only where failure cannot escape (E.12). These principles guide the following project-specific decisions. [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines). + +| Area | Recommended decision | Compatibility and safety requirement | +|---|---|---| +| Ownership | Private contiguous storage, preferably `std::vector`, plus validated extents. Use a bespoke allocator-aware buffer only if a measured requirement rules out vector. | Preserve row-major layout and allocator template parameter. Correct construction/destruction and allocator propagation are required. Exclude `bool` from a contiguous `T*` model or introduce a separately specified mask type; `vector` is not suitable storage. | +| Object invariants | Storage size equals checked rows×columns; extents and offsets fit the index/difference domain. Allow 0×N and N×0 shapes, with no accessible elements. | A moved-from object must remain consistent. Defaulting all moves after adding a vector can leave nonzero extents with moved-away storage; explicitly preserve the invariant. Assignment must commit metadata and storage together. | +| Checked access | Provide always-checked `at(r,c)` and checked slicing; make the safe facade's two-index access validate both coordinates. Validate shapes once before trusted internal loops. | Retain raw `data()` and legacy row-pointer access as documented escape hatches during migration. Safe access cannot be promised through arbitrary pointer arithmetic. Changing `m[r]` to a checked proxy is a versioned API change. | +| Views | Explicit mutable and const rank-2 views with extents and physical row stride. Add span for contiguous rows/storage and mdspan adapters. | Borrow only from lvalues; reject temporary owners. Document invalidation by destruction, reallocation, assignment and shape changes. Do not mark the owning matrix as a borrowed range. A view is not a lifetime guarantee. | +| Strided iteration | Store base, stride, logical index and count; derive addresses only for valid dereferences. | End/sentinel values must not require an out-of-array pointer. For subviews, physical row stride is the parent's width, not the subview's width. Test reverse iteration and standard iterator concepts. | +| Errors | Use exceptions for invalid construction/access and allocation failure; use structured recoverable results for parsing and numerical failure. | Suggested categories: invalid shape, overflow, unsupported dtype/format, truncation, I/O failure, singularity, nonconvergence. Preserve old bool/optional entry points as adapters. Remove false `noexcept`; `expected` does not itself make allocation nonthrowing. | +| Numeric types | Support integer storage/basic arithmetic and real/complex floating algorithms explicitly. Introduce operation-specific concepts and a documented promotion policy. | Do not constrain all algorithms to `std::floating_point`, which excludes complex. Inverse/decomposition on integers must be rejected or explicitly promoted. Checked integer operations are needed wherever the API promises overflow detection; otherwise document the representable-input precondition. | +| Statistics | Promote integral mean/variance to a floating result; use a stable accumulation algorithm and a shared degrees-of-freedom parameter. | Make population/sample selection explicit; empty mean/min/max and insufficient sample count return a defined error. Decide NaN handling and complex variance as separate contracts. | +| Numerics | Factorization objects carry pivots, rank/status and reusable factors; solve systems from factors. Implement determinant from pivot parity and U's diagonal. | For complex algorithms use conjugate transpose where required. Use relative, scale-aware tolerances, not one universal 1e−10 cutoff. Distinguish singularity from nonconvergence and nonfinite input. | +| Execution | Serial is the default for callbacks; parallel numeric operations opt in through a policy with bounded workers. | Guarantee every scheduled index is visited exactly once, join on exceptions, avoid nested backend oversubscription, document reduction ordering. `jthread` helps lifetime management but does not transport exceptions automatically. | +| Public API and packaging | Keep compatibility aliases forwarding to one implementation. Split maintained headers by responsibility and generate a single-header distribution. | Preserve simple inclusion. Name configuration macros with a project prefix and require one consistent configuration per program. Test generated and maintained headers against the same suite. | + +Adopt value returns without top-level `const`, `[[nodiscard]]` on results that must be checked, explicit conversions, and constrained overloads. Replace repetitive elementwise bodies with a small audited transform layer, preserving operation-specific result types. Prefer standard algorithms/ranges when they simplify traversal; do not introduce lazy expressions whose lifetimes are harder to prove. Directly include the standard headers actually used, including facilities currently obtained transitively. These changes follow the source defects and should not become a broad style rewrite in the safety release. + +**C++20, C++23 and C++26 adoption.** The core safety work does not need C++26. Freeze the minimum supported compiler/library pairs after the consumer inventory in WP01; the working recommendation is a C++20 maintenance line and an opt-in modern interface, followed by a C++23 baseline only when consumers can build it. Compiler language mode and standard-library support are separate capabilities. The official GCC page still describes experimental aspects of its C++26 support; vendor support tables are moving evidence, not a deployment guarantee. [GCC language support](https://gcc.gnu.org/projects/cxx-status.html), [libstdc++ library status](https://gcc.gnu.org/onlinedocs/libstdc++/manual/status.html), [libc++ C++26 status](https://libcxx.llvm.org/Status/Cxx26.html). + +| Facility | Adoption decision | Qualification gate | +|---|---|---| +| C++20 concepts, ranges, span, jthread, numbers | Use immediately where they remove bespoke machinery or clarify requirements. The project already uses concepts/ranges. | Compile the maintained C++20 path; retain explicit shape validation and worker-exception handling. | +| C++23 `std::expected` | Use for new parser/factorization APIs when available; expose an explicit adapter on C++20 rather than changing one public function's return type invisibly with language mode. | Check `__cpp_lib_expected`, compile the exact operations used, and test error propagation. [WG21 expected design](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p0323r12.html). | +| C++23 `std::mdspan` | Use as the interoperability view for contiguous and strided matrices. It replaces neither ownership nor runtime input validation. | Check `__cpp_lib_mdspan`, test constness/layout/strides, and verify the backing storage remains alive. If needed, use an explicitly packaged backport behind the adapter. [WG21 mdspan](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p0009r18.html), [Kokkos reference implementation](https://github.com/kokkos/mdspan). | +| C++26 `submdspan` and padded layouts | Prefer for slicing and padded backend buffers once the exact implementation is available. | Check `__cpp_lib_submdspan` and required layout support separately; verify rectangular offsets and required storage span. Keep the tested fallback. [WG21 submdspan](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2630r4.html), [padded layouts](https://open-std.org/JTC1/SC22/WG21/docs/papers/2023/p2642r5.html). | +| C++26 `` | Optional backend for BLAS-like operations on mdspan. It is not a replacement for the owning matrix, LU/SVD implementations, or FFT. | Check `__cpp_lib_linalg` and compile/link/run every selected operation; verify backend dispatch and benchmark it. [WG21 BLAS-based interface](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p1673r13.html). | +| C++26 `` | Evaluate for contiguous elementwise kernels and reductions after scalar correctness and benchmarks exist. | Check `__cpp_lib_simd`, the exact supported revision/API, alignment and tail handling. Do not assume historical proposal spellings match the deployed API. Keep ABI-neutral dispatch and a scalar fallback. [WG21 SIMD proposal](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p1928r15.pdf), [current draft SIMD interface](https://eel.is/c++draft/simd). | +| C++26 contracts | Optional expression of developer preconditions and invariants. Keep ordinary validation for files, dimensions and recoverable numerical errors. | Check `__cpp_contracts` and the selected evaluation semantics. Assertions that may be ignored or merely observed cannot be the sole barrier before unsafe access. Test with enforcement disabled as well. [GCC contracts support entry](https://gcc.gnu.org/projects/cxx-status.html). | +| C++26 library hardening | Enable a supported vendor hardening mode for qualification and production where appropriate. | Verify that the mode is active and what it checks. It does not repair dangling views, raw pointer writes, malformed formats, or incorrect algorithms. C++26 mode alone does not promise these protections. [WG21 hardening](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3471r4.html), [libc++ hardening modes](https://libcxx.llvm.org/Hardening.html). | +| Reflection, modules, newer execution frameworks | Defer unless a measured consumer need appears. | Reflection does not justify complicating forty wrappers; modules need a demonstrated build-time benefit and packaging support. A safety-profile or borrow-checking proposal is not a portable memory-safety guarantee supplied by C++26. | + +Use both feature-test macros and actual compile/link/runtime probes. Do not select capabilities solely from `__cplusplus` or a compiler version. Record the compiler, standard-library vendor/version, feature macro values, backend version, flags, and architecture in CI artifacts. The draft links above are research references; pin the applicable standard/proposal revision when implementing because the live working draft can move beyond C++26. + +**Dependency choices.** Preserve a standard-library-only storage and basic arithmetic core. Use mature libraries where they reduce the numerical maintenance burden, with adapters and pinned qualification versions. + +| Component | Recommended first choice | Decision boundary | +|---|---|---| +| Advanced decompositions | Evaluate Eigen as an optional header-only adapter and independent test oracle; use BLAS/LAPACK for consumers that already accept linked numerical dependencies. | Select after checking consumer packaging, license, supported scalar types and required sizes. Retain an audited pivoted-LU fallback if dependency-free solving is required. Prefer disabling an unqualified advanced operation to silently using a known-wrong fallback. [Eigen decomposition catalogue](https://libeigen.gitlab.io/eigen/docs-nightly/group__TopicLinearAlgebraDecompositions.html), [LAPACK factorization/solve model](https://www.netlib.org/lapack/lug/node38.html). | +| FFT | Evaluate pocketfft through a narrow optional adapter. | Validate normalization, arbitrary sizes, strides, complex types and thread policy. Keep a tiny correct direct DFT only as a test oracle or explicitly named small-input fallback. Confirm the chosen version's license and packaging. [Pocketfft project](https://github.com/mreineck/pocketfft). | +| Image I/O | Isolate existing codecs immediately; then evaluate a maintained codec dependency based on actual consumer formats. | Do not make image support a mandatory dependency of all matrix users. Replacing a parser does not remove the need for input limits and malformed-file tests. | +| Testing and packaging | Update the vendored Catch v2.0.1 through a separate test-only change; introduce CMake interface targets/CTest while retaining a Makefile convenience entry point. | Pin test dependencies and release artifacts. A build-system migration must preserve simple single-header consumers and must not inject `-Ofast` or `-march=native` into their targets. | + +**Executable work queue.** Open one issue per work package and split it into reviewable pull requests at the listed boundaries. Roles are suggested responsibilities, not assigned people: **L** library maintainer, **Q** test/build engineer, **N** numerical reviewer, **C** downstream project owner. Estimates are engineering days including review/tests, assume one primary implementer with specialist review, and are planning ranges rather than promises. No implementation is authorized by this report itself. + +| Package | Priority / owner / effort | Depends on | Concrete deliverable and acceptance criteria | +|---|---|---|---| +| **WP01 — consumer and contract inventory** | P0 / L+C / 2–3d | None | Record all consuming repositories, compiler/stdlib pairs, scalar types, allocator use, direct field access, exception policy, file formats, and numerical APIs used. Classify every public family as supported, experimental, deprecated, or removed. Freeze the error/empty/shape/aliasing contracts above and create migration decisions for behavior changes. Acceptance: each production consumer has an owner and a build/test command; unavailable consumers remain an explicit release risk. | +| **WP02 — build and evidence baseline** | P0 / Q+L / 2–4d | None | Fix F10's definition-order errors without changing arithmetic. Add GCC and Clang builds, debug/release, serial/PARALLEL, strict floating-point flags, ASan+UBSan, and public API compile tests. Start consumer-required MSVC coverage. Correct the standard check and direct includes. Acceptance: existing suite builds on selected compilers; all new reproductions fail for the intended reason before their fix. Track unresolved cases individually; no blanket expected-failure suite. | +| **WP03 — immediate bounds repairs** | P0 / L+Q / 3–5d | WP01 contract slice, WP02 | Fix F01/F02/F09: crop/pad counts, both flip axes and aliases, empty cases, extent/byte overflow, reshape invariant, clone source bounds, input shape checks, vector multiplication and pooling mode validation. Acceptance: R01–R03 and relevant R07 cases below pass with and without NDEBUG under ASan/UBSan; validation occurs before allocation or traversal. This package can land as small safety patches before storage replacement. | +| **WP04 — owning storage and exceptions** | P0 / L+Q / 4–7d | WP01, WP02, checked dimensions from WP03 | Replace raw ownership or implement complete allocator-aware lifetime handling; make fields private in the migration release; repair F03/F04. Audit allocation/callback/I/O `noexcept` specifications across callers. Acceptance: R04 passes for supported nontrivial and throwing elements and unequal stateful allocators; failure leaves the documented state, and moved-from extents/storage agree. No wrong-resource deallocation, leaks or unbalanced lifetimes. | +| **WP05 — views and iterators** | P0 / L+Q / 4–6d | WP03, WP04 | Repair logical stride ends, parent-stride view traversal and view-to-owner copying. Reject temporary-owner views; define range validation and invalidation. Repair or remove the unused dangling curried helpers. Acceptance: R05 passes, including compile-negative lifetime cases, nonzero slice offsets, one-column anti-diagonals and reverse iteration. Source review proves end construction stays within pointer rules; sanitizer silence alone is insufficient. | +| **WP06 — external data boundaries** | P0 / L+Q / 5–8d | WP03, WP04 | Make NPY, text, native binary and BMP parsing transactional, bounded and typed; report PNG/stream failures. Keep legacy binary explicitly native-format and restricted to supported trivially copyable scalar representations, or add a versioned portable format. Acceptance: R06 plus parser fuzzing pass; malformed input cannot alter the destination, trigger unbounded allocation, or terminate through `noexcept`. | +| **WP07 — execution and randomness** | P1 / L+Q / 3–5d | WP02, WP04 | Repair range partitioning, zero-core fallback, reduction identity rules and thread cleanup. Propagate worker failures after joining. Introduce caller-owned random engines with explicit real/integer distributions. Acceptance: R07 passes for injected worker counts and failures; TSan has no project findings in the supported concurrency scenarios; existing aliases share the implementation. | +| **WP08 — arithmetic and statistics contracts** | P1 / L+N / 3–5d | WP03–WP05, WP07 | Repair power, valarray/clone instantiation defects, floating maxima, mean promotion and shared variance/stddev degrees of freedom. Specify mixed-type results and integer-overflow expectations. Acceptance: R08 passes for signed/unsigned, float/double and applicable complex operations; unsupported instantiations fail with useful constraints. Negative-only tests must detect the old maxima defect. | +| **WP09 — reliable linear algebra** | P1 / N+L / 6–10d | WP01, WP04, WP05, WP08 | Select and qualify backend/fallback; implement pivoted factorization, solve, determinant and inverse from factors. Qualify SVD/pseudoinverse, Cholesky and RREF separately. Propagate singularity/nonconvergence and remove invalid fixed-subblock assumptions. Acceptance: R09 identities/residuals pass across square/tall/wide/rank-deficient inputs in the supported domains; all retained iterative/eigen routines receive convergence and breakdown tests or are marked experimental. | +| **WP10 — convolution and Fourier semantics** | P1 / N+L / 4–7d | WP03, WP05, WP08 | Correct asymmetric-kernel convolution, same/valid sizing, transform indexing and normalization, and pure shifts. Then integrate the selected FFT adapter. Acceptance: R10 passes against independent fixtures, including prime and odd dimensions, impulses and non-symmetric kernels; complexity/performance evidence accompanies the FFT replacement. | +| **WP11 — modern API and maintainable layout** | P2 / L / 4–6d | WP04–WP10 correctness gates | Consolidate transform helpers, constrain concepts, remove top-level const value returns, add nodiscard results, namespaced configuration, structured errors and mdspan/span adapters. Split maintained headers and generate the umbrella distribution. Acceptance: compatible consumers build; generated header passes the same suite and a multi-translation-unit link test; declared feature combinations are tested. Avoid unrelated renaming. | +| **WP12 — optional adapters and packaging** | P1 for used adapters, otherwise P2 / L+Q / 2–4d | WP02, WP05, WP06, WP11 | Repair OpenCV continuity/layout handling and integer dimension conversion. Add enabled/disabled adapter tests, interface build targets/install/export checks, and release automation that publishes only tested artifacts. Acceptance: non-square multi-channel ROI roundtrips pass; a consumer can build from both installed headers and the generated header. | +| **WP13 — measured optimization** | P2 / L+N / 4–8d | WP07–WP12 | Establish pinned benchmark workloads; optimize allocation counts, matrix multiplication locality, thread thresholds and selected SIMD kernels. Qualify optional BLAS/linalg dispatch. Acceptance: performance policy below passes with numerical and sanitizer gates intact; each enabled optimization has measured benefit and a fallback. | +| **WP14 — downstream migration and release** | P0 release gate / L+Q+C / 3–5d | All applicable packages | Run consumer suites, migration tests, fuzz campaigns, release-mode checks and artifact verification. Publish contracts, deprecations, corrected numerical behavior and residual limitations. Acceptance: all release gates below pass, every production consumer signs off, and rollback to the previous pinned version is documented. | + +The listed packages total approximately **49–83 engineering days** before contingency. Plan 20–30% additional capacity for downstream migration and numerical surprises. The critical dependency chain is WP01/WP02 → WP03/WP04 → WP05/WP06 → numerical qualification → WP14. WP07 and independent test/fixture preparation can overlap when staffing permits. Do not defer P0 repairs until a large modernization branch is ready. WP12 must be brought forward if an affected integration is used in production. + +**Regression specifications for those packages.** Use deterministic hand-checkable cases first, then properties and independent reference fixtures. Test expectations must describe the contract rather than repeat the implementation. Compile-only cases are essential because template bodies absent from the current test suite can remain broken indefinitely. + +| Suite | Inputs and properties | Required result | +|---|---|---| +| **R01: shapes and overflow** | 0×0, 0×5, 5×0, 1×1; negative signed dimensions; near-size-limit extents; element/byte overflow; allocator max-size limits; incompatible reshape; out-of-range row and column. | Empty operations obey documented identities/errors; invalid values fail before allocation/traversal and preserve the destination. Run in release and debug. | +| **R02: crop/pad/flip** | 5×5→5×3, 3×10→5×2, 2×3→4×5; 3×5 and 5×3 labeled grids; square and empty flips; both axis aliases. | Exact expected content/zero padding; applying each flip twice restores input. Bounds repairs do not alter unrelated elements. | +| **R03: multi-input and overlap** | Left vector of length 2 times a 2×3 matrix; invalid length 3; wrong-shaped binary/ternary inputs; self-assignment; overlapping slice copy; self-multiplication. | Valid output shape/content; deterministic rejection of mismatches; an explicit overlap policy, using a temporary where necessary. | +| **R04: ownership/failure injection** | Tracked nontrivial element lifetimes; throwing copies; fail-on-N allocation; allocators with different resource IDs and propagation traits; copy/move/swap/self-move; supported over-aligned types. | Balanced construction/destruction; each resource frees its allocations; no leaks; stated strong/basic guarantees; consistent moved-from objects. | +| **R05: borrowed storage** | Copy an offset 2×3 slice out of a larger matrix; stride-aware rows/columns; every valid diagonal of small rectangular shapes; one-column anti-diagonals; forward/reverse traversal; temporary-owner construction. | Exact logical ranges with valid pointer formation. Temporary-owner borrowing fails to compile. Invalidation rules are documented; diagnostic lifetime tests run under sanitizers where appropriate. | +| **R06: file formats** | Empty/truncated files at each header/payload boundary; huge dimensions and header lengths; bad magic/version/dtype; endian variations; NPY C/Fortran order and unsupported object/structured dtypes; ragged text; BMP unsupported compression/offset/top-down height; write/open/close failure. | Either correct values/shape or a specific error, bounded resource use, and unchanged destination on failure. Reject unsupported cases intentionally. Do not deserialize Python objects. | +| **R07: scheduling/randomness** | Inject worker counts 0,1,2 and greater than element count; nonzero-start ranges; throw in a worker and during thread creation; reductions with nonzero init; stored curried callables; invalid pooling action; simultaneous independent seeded generators. | Every index executes exactly once; no joinable threads escape; errors propagate; init appears exactly once; stored callables remain valid; deterministic engine behavior with documented distribution portability limits. | +| **R08: arithmetic/statistics/API** | Matrix powers 0,1,2,3,13; left/right vector and valarray products; every clone overload; max of −3 and −1; mean of 1 and 2, and −1 and −2; population/sample variance; empty inputs; nonfinite values. | Correct values and result types. Integer and complex support is explicit. Variance and stddev agree for the same degrees of freedom. Each retained overload compiles or is intentionally constrained out. | +| **R09: linear algebra** | Identity, diagonal, row-swap, block-exchange, singular/rank-deficient, nearly singular, tall/wide matrices; float/double and supported complex; non-SPD Cholesky; forced nonconvergence. | Pivoted factorization reconstructs input; solve/inverse residuals meet scale-aware limits; determinant has correct sign and is zero for singular input. Pseudoinverse passes all four Moore–Penrose identities, including conjugate-transpose identities for complex. | +| **R10: signal operations** | Asymmetric convolution [1,2] with [3,4]; 1×1 kernel; all modes; empty/oversized kernel policy; off-origin impulse and sinusoid FFT; 1×N, 3×5, 4×6 and prime-size inputs; odd/even shift roundtrips. | Convolution [3,10,8] for the example; documented output alignment; inverse(transform(x))≈x under selected normalization; shifts only permute values and invert one another. | +| **R11: portability/integration** | One translation unit per public API family; multiple translation units using identical configuration; maintained/generated headers; GCC/Clang/MSVC as supported; C++20/23/26 capability lanes; OpenCV ROI/non-square/channel cases. | No latent template errors, unintended mandatory dependencies or ODR defects; feature-disabled fallbacks pass equivalent tests. Detect/document mixed-configuration misuse rather than relying on linker luck. | + +For well-conditioned floating-point fixtures, start with residual tolerances scaled by dimension, machine epsilon, and operand norms, then justify constants per algorithm. For example, assess a solve using ‖AX−B‖/(‖A‖‖X‖+‖B‖), handling a zero denominator separately. Near-singular tests should check failure/rank behavior and backward error; they should not demand small forward error regardless of condition number. Use independent Eigen/LAPACK/NumPy/SciPy fixtures where suitable, recording versions and conventions. A backend cannot be its own sole oracle. + +WP09 also owns qualification of retained matrix-exponential and eigen/iterative APIs: include zero and diagonal matrix exponentials, extreme finite and nonfinite inputs, scaling-shift limits, eigenpair residuals, iterative-solver breakdown, and forced iteration exhaustion. Any family not qualified by these tests stays explicitly experimental. This closes the earlier review's unsupported clearance of extreme `expm` inputs without asserting a new runtime reproduction. + +**Verification and release gates.** Add the following lanes incrementally in WP02 and make the relevant lanes blocking before WP14: + +1. **Ordinary builds:** every supported compiler/stdlib pair, strict C++ mode, debug and release, serial and parallel paths. Retain a C++20 lane until explicitly retired; C++26-only facilities require their own enabled/disabled tests. Test the Strassen path as well as direct multiplication: the current `PARALLEL` configuration always selects direct multiplication. +2. **Memory/undefined behavior:** ASan+UBSan with debug and NDEBUG, leak checking where supported, and optimized builds without fast-math. Run TSan separately on parallel/lifetime scenarios; use MSan only with a correctly instrumented dependency environment. Sanitizers complement source reasoning and compile tests. [AddressSanitizer documentation](https://clang.llvm.org/docs/AddressSanitizer.html). +3. **Static checks:** compiler warnings including conversions/shadowing on project headers; clang-tidy lifetime, bounds and owning-memory checks where applicable; review all suppressions. Scope warning enforcement to project code rather than treating the old vendored test framework as the library. +4. **Fuzzing:** byte-buffer parsers for NPY/BMP/native binary/text and a bounded shape/slice operation-sequence harness. Fix seeds, timeouts and input/allocation caps. On a fixed runner, budget at least one CPU-hour per target nightly and 24 CPU-hours per target before release; retain minimized failures as permanent regressions. Record coverage and executed corpus, not only elapsed time. +5. **Coverage:** every supported public family must have at least one instantiation and behavioral test; every documented error category needs an exercised negative path. Target at least 90% branch coverage of new validation/parser/ownership code, but never use coverage percentage as an exception to a missing known-defect test. Each F01–F18 item needs closure evidence or an explicit supported-scope decision. +6. **Consumers and artifacts:** build and run all inventoried production consumers against the release candidate; test installed and amalgamated headers; publish only after those checks succeed. The existing auto-release workflow does not depend on the test workflow and must be changed before trusting releases. + +Release acceptance requires zero unresolved P0/P1 findings in the supported surface, all specified regression suites green, no unsuppressed project sanitizer/static-analysis findings, completed fuzz budgets without open crashes, and accepted consumer migration results. Suspected defects are not closed by renaming the API or disabling an assertion. Unsupported experimental features must be labeled, excluded from the production guarantee, and unavailable through an apparently safe fallback. + +**Performance work starts from a correct baseline.** Benchmark serial kernels before selecting thread counts or ISA-specific paths. The current code creates threads repeatedly and switches multiplication algorithms based on the global `PARALLEL` macro; it also materializes many matrices in Strassen and block inversion. These are optimization hypotheses, not measured bottleneck rankings. + +| Workload | Sizes/data | Measurements and candidate change | +|---|---|---| +| GEMM and matrix-vector | Square 8,16,32,64,128,256,512,1024; tall/skinny and wide cases; dimensions 16/17/18 around the current threshold | Latency, throughput, allocations, temporary bytes, thread count, numerical error. Compare cache-blocked traversal and optional BLAS/linalg against current direct/Strassen paths. | +| Elementwise/statistics | Tiny matrices through 10⁶ elements; aligned/misaligned and tail lengths; mixed signs | Per-call overhead, effective bandwidth, rounding behavior. Reduce allocations and temporary matrices before adding SIMD. | +| Views/transposes | Whole matrix and offset narrow slices, rectangular shapes | Copy count, stride costs and bytes touched. Use views internally where lifetime/aliasing are explicit. | +| FFT/convolution | Power-of-two, prime and rectangular sizes; small/large kernels | Correctness first, then scaling, throughput, plan/setup cost and temporary memory. Compare a qualified FFT backend against the correct small-input reference. | +| Build footprint | Minimal matrix include, common consumer translation unit, image-enabled unit | Clean compile time, object/executable size and startup allocations. Separate optional I/O and per-translation-unit color-map initialization if measurements justify it. | + +Use pinned compiler/flags/backend versions, fixed seeds, warmups, at least 20 timed samples and medians plus dispersion. Record CPU, available cores, affinity policy and backend threads. Compare like-for-like scalar types, checking, numerical tolerances and result consumption. Portable release builds use normal `-O2`/`-O3`; native ISA and relaxed floating-point builds are separate opt-in measurements. GCC documents that `-Ofast` enables optimizations that do not preserve all standard guarantees. [GCC optimization options](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html). + +Proposed performance acceptance: no unexplained median regression greater than 5% on stable representative workloads; enable a new optional path only with a repeatable benefit (initial target ≥10% on its intended workload) and unchanged numerical/error guarantees. Review noisy microbenchmarks separately. A necessary safety check can exceed the performance budget only with an explicit documented decision; removing validation is not an optimization strategy. No universal speedup target should be promised before WP13 establishes the baseline. + +**Migration and rollback.** Ship bounded safety repairs early with release notes; reserve storage privacy, checked row proxies, changed return types and changed numerical conventions for a clearly versioned migration. Keep `pinv`/`pinverse`, `rand`/`random`, and member/free convenience names as forwarding aliases when behavior can be shared safely. Introduce corrected pure shift and convolution contracts explicitly so consumers can update golden results; do not preserve memory-unsafe behavior behind a compatibility switch. + +Inventory direct access to storage fields before making them private. Provide accessors and an upgrade guide, then rebuild all consumers: header-only libraries can still cross binary boundaries when instantiated objects are passed between separately built components. Keep one backend/configuration contract per program. Pin each consumer to the previous release until its acceptance tests pass, then upgrade progressively. Backend dispatch must be switchable back to the qualified scalar path without changing the mathematical API. Keep corrected fixture provenance and any changed numerical outputs in release notes. + +**Reproduction record.** Commands below were executed from the repository root through the required `rtk` proxy. They compile only existing source and place outputs outside the repository. `/tmp/matrix-review-20261001.gcCOxS` was the temporary evidence directory for this review; it is not a durable artifact dependency of this report. + +| Purpose | Command | Decisive output / exit | +|---|---|---| +| Baseline build | `rtk proxy g++ -std=c++20 -O2 -DPARALLEL -pthread tests/test.cc -o /tmp/matrix-review-20261001.gcCOxS/test-gcc` | Exit 0; build log empty. | +| Baseline run | `rtk proxy /tmp/matrix-review-20261001.gcCOxS/test-gcc` | `All tests passed (49216592 assertions in 57 test cases)`; exit 0. | +| Sanitizer build | `rtk proxy g++ -std=c++20 -O1 -DNDEBUG -DPARALLEL -pthread -fsanitize=address,undefined -fno-omit-frame-pointer tests/test.cc -o /tmp/matrix-review-20261001.gcCOxS/test-gcc-asan` | Build succeeded. | +| Sanitizer run | `rtk proxy env ASAN_OPTIONS=detect_leaks=1 /tmp/matrix-review-20261001.gcCOxS/test-gcc-asan` | `All tests passed (49216592 assertions in 57 test cases)`; no sanitizer diagnostic. | +| Clang portability | `rtk proxy clang++ -std=c++20 -fsyntax-only -DPARALLEL -pthread tests/test.cc` | Exit 1; `implicit instantiation of undefined template 'feng::matrix>'`; six errors, two warnings. | +| Clang sanitizer attempt | `rtk proxy clang++ -std=c++20 -O1 -DNDEBUG -DPARALLEL -pthread -fsanitize=address,undefined -fno-omit-frame-pointer tests/test.cc -o /tmp/matrix-review-20261001.gcCOxS/test-clang-asan` | Exit 1; same six incomplete-template errors. | +| C++26 syntax | `rtk proxy g++ -std=c++26 -fsyntax-only -DPARALLEL -pthread tests/test.cc` | Exit 0; deprecated whitespace before `_u8` literal suffix at line 362, emitted twice. | + +Compiler version strings were `g++ (GCC) 16.2.1 20260810` and `clang version 22.1.8`, targeting x86_64 Linux. Captured commands used shell redirection for logs and ran build/run pairs sequentially; the table separates the equivalent invocations for readability. All online references were consulted on 2026-10-01. Estimates, priority assignments, target architecture, test budgets, and performance thresholds are recommendations from this review, not guarantees made by the cited standards or libraries. diff --git a/docs/opencode_sharded_review.md b/docs/opencode_sharded_review.md new file mode 100644 index 0000000..79d072a --- /dev/null +++ b/docs/opencode_sharded_review.md @@ -0,0 +1,364 @@ +# Sharded Code Review — `matrix.hpp` + +- **Date:** 2026-07-13 +- **Scope:** `matrix.hpp` (7,689 lines, single-header C++20 matrix library, `namespace feng`), plus `tests/` and `ReadMe.md` for contract evidence. +- **Method:** Manual review along six axes (correctness, readability, security/safety, tests, architecture, performance) followed by empirical verification with GCC 16.2 (C++20, `-DPARALLEL`): + - Full test suite built via `make test` and executed: **All tests passed (49,216,592 assertions in 57 test cases)**. + - AddressSanitizer probes compiled with `-DNDEBUG -DPARALLEL -fsanitize=address` (so `better_assert` is a silent no-op and the *actual* out-of-bounds behavior is observable rather than aborted on a precondition). +- **Line numbers** refer to `matrix.hpp` at review time. + +## Findings summary + +| # | Severity | Axis | Finding | Evidence verified | +|---|----------|------|---------|-------------------| +| C1 | **Critical** | Correctness | `shrink_to_size` copies the wrong column count → heap OOB write + silent corruption | ASan-confirmed | +| C2 | **Critical** | Correctness | `flipdim(m, 2)` swaps a *column* with a *row* → heap OOB (non-square) / silent corruption (square) | ASan-confirmed | +| C3 | High | Correctness | `fliplr`/`flipud` aliases are swapped vs. conventional semantics | Code-verified | +| C4 | High | Correctness | `pinverse`/`pinv` never inverts the singular values | Probe-confirmed (returns 2.0 where 0.5 expected) | +| C5 | High | Correctness | `det()` Schur complement uses `P.inverse()` with no singularity handling → silent `NaN` | Probe-confirmed | +| C6 | High | Correctness | `operator^` does not compile for any odd exponent ≥ 3 (precedence bug) | Compile probe-confirmed | +| S1 | High | Security/Correctness | `load_npy` performs no buffer-size validation → OOB read on truncated files; `stoul` can throw from `noexcept` | ASan-confirmed | +| P1 | High | Performance | `fft`/`ifft` are naive O(N⁴) direct DFTs despite the FFT name | Code-verified | +| S2 | Medium | Security/Safety | `save_png` dereferences unchecked `fopen` result (null `FILE*`) | Code-verified | +| C7 | Medium | Correctness | `better_assert` silently no-ops under `NDEBUG`, turning all boundary checks into UB paths in release builds | Code-verified | +| C8 | Medium | Correctness | `mean`/`variance`/`standard_deviation` truncate for integer matrices | Probe-confirmed | +| C9 | Medium | Correctness | `conv` "same" mode: second assert checks `rb` instead of `cb`; both reject valid 1×1 kernel | Code-verified | +| C10 | Medium | Correctness | `rref`/`gauss_jordan_elimination` precondition `row < col` rejects square systems the algorithm handles | Code-verified | +| C11 | Medium | Correctness | `rand` uses global `srand`/`rand`: re-seeds every call, not thread-safe, low quality | Code-verified | +| T1 | Medium | Tests | Test suite is green but the five most buggy code paths (shrink_to_size, flipdim, pinverse, det, `^`) have zero test coverage | Verified by listing `tests/cases/` | +| A1 | Medium | Architecture | ~30 CRTP mixins each re-derive identical typedefs via `type_proxy_type`; high indirection for a single concrete class | Code-verified | +| R1 | Medium | Readability | `svd_inverse` calls `singular_value_decomposition(a, u, v, w)` with swapped argument order vs. the signature `(a, u, w, v)` | Code-verified | +| R2 | Low | Readability | ~40 nearly identical 6-line elementwise templates (unary/binary/complex math, ~1,200 lines of boilerplate) | Code-verified | +| R3 | Low | Readability | Stray double semicolon in `save_png`; typo in `det` precondition message ("the row and matrix are supposed to be same") | Code-verified | +| C12 | Low | Correctness | `matrix_details::reduce` divides by `hardware_concurrency()` which may be 0 → SIGFPE | Code-verified (unreachable on typical hosts) | +| A2 | Low | Architecture | API duplication: `random`↔`rand`, `random_like`↔`rand_like`, `pinv`↔`pinverse`; free `det(m)` + member `m.det()` | Code-verified | +| P2 | Low | Performance | `lu_decomposition` has no partial pivoting (stability), and `cholesky` has no positive-definiteness guard | Code-verified | +| C13 | Low | Correctness | `fftshift`/`ifftshift` are wrong for odd dimensions (pair-swap, not circular rotation) | Derived (not executed) | + +--- + +## Correctness + +### C1 — `shrink_to_size` copies the wrong column count (Critical) + +- **Severity:** Critical (memory corruption) +- **Evidence:** `matrix.hpp:3528-3532` + ```cpp + size_type const the_rows_to_copy = std::min( zen.row(), new_row ); + size_type const the_cols_to_copy = std::min( zen.col(), new_col ); + + for ( size_type r = 0; r != the_rows_to_copy; ++r ) + std::copy( zen.row_begin( r ), zen.row_begin( r ) + the_rows_to_copy, other.row_begin( r ) ); + ``` + The loop copies `the_rows_to_copy` **columns per row** instead of `the_cols_to_copy`. +- **Violated contract:** the documented behavior ("if new row or col are larger than the original, padding with zero; otherwise, drop these elements", comment at `matrix.hpp:3515-3517`). +- **Impact (empirically verified):** + - `matrix{5,5,1.0}.shrink_to_size(5,3)` → AddressSanitizer: `heap-buffer-overflow` at `matrix.hpp:3532`. + - `matrix{3,10}.shrink_to_size(5,2)` → no crash but **silent corruption**: last row becomes `(21, 22, 23)` instead of the documented zero padding. +- **Smallest safe fix:** `std::copy( zen.row_begin( r ), zen.row_begin( r ) + the_cols_to_copy, other.row_begin( r ) );` +- **Confidence:** 100% (reproduced). + +### C2 — `flipdim(m, 2)` swaps a column with a row (Critical) + +- **Severity:** Critical (memory corruption) +- **Evidence:** `matrix.hpp:4476-4481` + ```cpp + std::swap_ranges( ans.col_begin( index_left ), ans.col_end( index_left ), ans.row_begin( index_right ) ); + ``` + The third argument of `swap_ranges` must be the start of the *second column*, i.e. `ans.col_begin( index_right )`. As written, it swaps a column (length `row()`) against a *row* (length `col()`). +- **Violated contract:** `flipdim` must flip along dimension 2 (left/right flip), per the parallel structure of the `dim == 1` branch and the public `fliplr`/`flipud` API. +- **Impact (empirically verified):** + - Square 4×4: result **does not equal** a left-right flip (silent data corruption). + - Non-square 3×5: AddressSanitizer `heap-buffer-overflow` at `matrix.hpp:4479`. +- **Smallest safe fix:** use `ans.col_begin( index_right )` as the third argument. +- **Confidence:** 100% (reproduced). + +### C3 — `fliplr` / `flipud` aliases are swapped (High) + +- **Severity:** High (wrong semantics; compounds C2) +- **Evidence:** `matrix.hpp:4491-4499` + ```cpp + matrix const fliplr( matrix const& m ) { return flipdim( m, 1 ); } // dim 1 flips up/down + matrix const flipud( matrix const& m ) { return flipdim( m, 2 ); } // dim 2 flips left/right + ``` +- **Violated contract:** MATLAB/NumPy convention, which this library follows elsewhere (`meshgrid`, `conv`, pooling): `fliplr` = left-right (column) flip, `flipud` = up-down (row) flip. +- **Impact:** users get the transpose-axis flip they didn't ask for; silent, no error. +- **Smallest safe fix:** `fliplr → flipdim(m, 2)`, `flipud → flipdim(m, 1)`. +- **Confidence:** High (semantics by convention; the flipdim body itself is broken anyway). + +### C4 — `pinverse` / `pinv` never inverts the singular values (High) + +- **Severity:** High (silently wrong numerical results) +- **Evidence:** `matrix.hpp:5226-5230` + ```cpp + Matrix const pinverse( const Matrix& m ) + { + Matrix u, w, v; + singular_value_decomposition( m, u, w, v ); + return v * w * u.transpose(); // W is the diagonal of singular values, NOT inverted + } + ``` + The pseudoinverse is `V · Σ⁺ · Uᵀ`; this returns `V · Σ · Uᵀ`. Compare `svd_inverse` (`matrix.hpp:5216-5224`), which *does* invert the diagonal with a 1e-10 threshold and produces correct results. +- **Violated contract:** a function named `pinverse` must compute the Moore–Penrose pseudoinverse. +- **Impact (empirically verified):** `pinverse(diag(1,2))` returns `diag(1, 2)`; expected `diag(1, 0.5)`. `svd_inverse(diag(1,2))` correctly returns `diag(1, 0.5)`. +- **Smallest safe fix:** `return svd_inverse( m );` (delete the body), or apply the same diagonal-inversion loop as `svd_inverse`. +- **Confidence:** 100% (reproduced). + +### C5 — `det()` uses `P.inverse()` without handling a singular P (High) + +- **Severity:** High (silent `NaN`/wrong results) +- **Evidence:** `matrix.hpp:2063-2067` + ```cpp + zen_type const& tmp = S - ( R * ( P.inverse() ) * Q ); + return P.det() * tmp.det(); + ``` + The Schur-complement identity `det = det(P)·det(S − R·P⁻¹·Q)` requires `P` nonsingular. There is no check; `inverse()` on a singular block yields `inf`/`NaN`, which propagates silently. +- **Violated contract:** `det` must return the determinant for any square matrix (ReadMe §"det -- matrix determinant"); for singular input the answer is `0`, not `NaN`. +- **Impact (empirically verified):** + ```cpp + // P block [[1,2],[2,4]] is singular; true determinant is 0 + det(m) == -nan + ``` +- **Additional evidence:** the precondition message at `matrix.hpp:2056` has a typo ("the row and matrix are supposed to be same"). +- **Smallest safe fix:** compute the determinant via the existing `lu_decomposition` (`matrix.hpp:~6700`): `det = ±∏U_ii` with a singularity check, and return `NaN`/`std::optional` on pivot zero. This also removes the O(n³)-per-level `inverse()` (see P2). +- **Confidence:** 100% (reproduced). + +### C6 — `operator^` does not compile for odd exponents ≥ 3 (High) + +- **Severity:** High (public API member unusable) +- **Evidence:** `matrix.hpp:5567` + ```cpp + if ( n & 1 ) + return lhs ^ ( n - 1 ) * lhs; // parses as lhs ^ ((n-1) * lhs) — `*` binds tighter than `^` + ``` +- **Violated contract:** `m ^ n` (integer power) is a documented public operation. +- **Impact (empirically verified):** + ``` + matrix.hpp:5567:36: error: no match for ‘operator*’ + (operand types are ‘uint_least64_t’ and ‘const feng::matrix’) + return lhs ^ ( n - 1 ) * lhs; + ``` + `m ^ 3` fails to instantiate; only `n == 0, 1` and even powers compile. +- **Smallest safe fix:** + ```cpp + auto const& half = lhs ^ ( n >> 1 ); + return half * half * lhs; + ``` +- **Confidence:** 100% (reproduced). + +### C7 — `mean`/`variance`/`standard_deviation` truncate for integer matrices (Medium) + +- **Severity:** Medium (wrong numerical results for integer types) +- **Evidence:** `matrix.hpp:7640-7641` + ```cpp + auto mean( Mat const& m ) { return sum( m ) / m.size(); } + ``` + For `matrix` this is integer division. +- **Violated contract:** "mean" is the arithmetic mean; ReadMe documents `mean` for numeric matrices generally. +- **Impact (empirically verified):** `mean(matrix{1,2, {1,2}}) == 1` (expected 1.5). Also `variance` of `{1,2}` is `0.25 → 0`, so `standard_deviation` of a 2-element int matrix is `0`. +- **Smallest safe fix:** promote the divisor/accumulator to `double` (or the matrix's floating-point promotion type) in the reduce helpers, or document integer truncation explicitly in the ReadMe. +- **Confidence:** 100% (reproduced); severity is a contract judgment. + +### C9 — `conv` "same" mode asserts are wrong (Medium) + +- **Severity:** Medium +- **Evidence:** `matrix.hpp:6620-6621` + ```cpp + better_assert( rb > 1, " ... the row of the second matrix is at least 1, but now has ", rb ); + better_assert( rb > 1, " ... the column of the second matrix is at least 1, but now has ", cb ); + ``` + Two problems: (1) the second assert re-checks `rb` instead of `cb`; (2) the message says "at least 1" but the condition `> 1` rejects a valid 1×1 kernel (for which "same" mode is well-defined and the code below handles it: `(rb-1)>>1 == 0`). +- **Violated contract:** the documented "same" mode (matches NumPy/Matlab `conv(...,'same')`, ReadMe §pooling/conv region). +- **Impact:** in debug builds a valid 1×1-kernel "same" convolution aborts; in release builds the column bound is never enforced. +- **Smallest safe fix:** `better_assert( rb >= 1 && cb >= 1, ... )` (or drop, since the slicing below already requires positive dims), and fix the copy-pasted condition. +- **Confidence:** High. + +### C10 — `rref`/`gauss_jordan_elimination` precondition `row < col` (Medium) + +- **Severity:** Medium (overly restrictive documented precondition) +- **Evidence:** `matrix.hpp:6396` + ```cpp + better_assert( row < col && "matrix row must be less than colum to execut a Gauss-Jordan Elimination" ); + ``` + The algorithm (partial-pivoting Gauss–Jordan, `matrix.hpp:6398-6420`) is fully defined for square and even over-determined systems; only the assert is restrictive. In debug builds `rref(square)` aborts; in release the same call succeeds — inconsistent behavior across build modes. +- **Violated contract:** `rref` (Matlab alias, comment at `matrix.hpp:6427`) is expected to work on square systems. +- **Smallest safe fix:** relax to `row > 0 && col > 0`; keep the pivot-magnitude early exit (`1.0e-10`) as the singularity signal. +- **Confidence:** High (code-level; not executed in debug mode to avoid the intended abort). + +### C11 — `rand` uses the global `srand`/`rand` (Medium) + +- **Severity:** Medium +- **Evidence:** `matrix.hpp:5244-5250` + ```cpp + if ( 0 == seed ) + std::srand( static_cast< unsigned int >( ... std::time(nullptr) + reinterpret_cast<...>( &ans ) ) ); + else + std::srand( seed ); + auto const& generator = []() noexcept + { return ( static_cast( std::rand() ) + 1 ) / ( static_cast( RAND_MAX ) + 2 ); }; + ``` +- **Violated contract / invariant:** `rand` is a public API of a library whose own algorithms run on multiple threads; C++11+ `rand()`/`srand()` are not required to be thread-safe (concurrent `rand()` calls are a data race → UB), and re-seeding the single global generator from a time+address value on every call makes repeated calls within the same second highly correlated. +- **Impact:** low-quality, potentially correlated randomness; UB if users fill matrices concurrently (e.g., inside a `std::async`/thread pool). +- **Smallest safe fix:** use a local `std::mt19937` (seeded as today) and `std::uniform_real_distribution(0.0, 1.0)`; drop `noexcept` if the allocation can throw. +- **Confidence:** High (code-level; concurrency impact is latent). + +### C12 — `reduce` divides by `hardware_concurrency()` which may be 0 (Low) + +- **Severity:** Low +- **Evidence:** `matrix.hpp:1152-1161` — `cache.resize( total_cores ); auto block_size = total_elements / total_cores;` with `total_cores = std::thread::hardware_concurrency()`, which is permitted to return `0` ("cannot determine"). +- **Impact:** integer division by zero (SIGFPE) on hosts where it returns 0. The `parallel` helper at `matrix.hpp:276` guards with `total_cores <= 1`; this `reduce` path does not. +- **Smallest safe fix:** `if ( total_cores < 1 ) total_cores = 1;` (also applies to `matrix.hpp:4036`). +- **Confidence:** High. + +### C13 — `fftshift`/`ifftshift` wrong for odd dimensions (Low) + +- **Severity:** Low +- **Evidence:** `matrix.hpp:6340-6355` (and mirror at 6470-6485). For odd `R`, `row_starter = R/2 + 1` and the loop swaps rows `i` with `R/2+1+i` only, leaving the middle row fixed — a pair-swap, not the circular rotation by `floor(R/2)` that `fftshift` is defined as. E.g. `R=3`: produces `[2,1,0]` instead of `[1,2,0]`. +- **Smallest safe fix:** implement as a two-block move (`std::rotate` of row indices), or `row r → (r + (R-1)>>1) % R`. +- **Confidence:** Medium (derived by hand; not executed because the DFT around it makes a probe slow). + +--- + +## Security / Safety + +### S1 — `load_npy` performs no size validation on untrusted file input (High) + +- **Severity:** High (out-of-bounds reads on malformed external input) +- **Evidence:** `matrix.hpp:2508-2560`. After `std::ifstream` succeeds the code dereferences fixed offsets with no length checks: + - `buffer.data()+6` (version), `buffer.data()+8..11` (header length), `buffer.data()+10/12 + header_length` (header string), and finally `std::copy_n( buffer.data()+data_offset, row*col, ... )` where `row*col` comes from *parsed file contents*. +- **Violated contract / invariant:** "data from external sources is treated as untrusted; external data flows are validated at system boundaries before use." `load_npy` is a file-input boundary. +- **Impact (empirically verified):** a 3-byte file → AddressSanitizer `heap-buffer-overflow` read at `matrix.hpp:2520`. Additional issues: + - `std::stoul` on a malformed header **throws** from a `noexcept` member → `std::terminate`. + - No `dtype` check: a `float32`/complex `.npy` loaded into `matrix` silently copies misinterpreted bytes. + - In `NDEBUG` builds the only guard (`better_assert( ifs, ... )`) is a no-op, so even open failures fall through into the OOB path. +- **Smallest safe fix:** validate before any dereference: + ```cpp + if ( buffer.size() < 12 ) return false; + // after parsing header_length: + if ( buffer.size() < data_offset + header_length + std::size_t{row} * col * sizeof( value_type ) ) + return false; + // after parsing dtype: + if ( header.find( expected_dtype_string ) == std::string::npos ) return false; + ``` + and either drop `noexcept` or catch `stoul` exceptions. +- **Confidence:** 100% (OOB reproduced); dtype issue verified by reading the code. + +### S2 — `save_png` dereferences unchecked `fopen` result (Medium) + +- **Severity:** Medium +- **Evidence:** `matrix.hpp:3100-3105` + ```cpp + FILE* fp = fopen( file_name, "wb" ); + for ( i = 0; i < 8; i++ ) + fputc( ( "\x89PNG\r\n\32\n" )[i], fp );; // also a stray double semicolon + ``` + No `if ( !fp )` check before the first `fputc` (null-pointer UB on open failure, e.g. bad path/permissions); the function is `noexcept`. +- **Smallest safe fix:** `if ( !fp ) return;` immediately after `fopen`; remove the stray `;`. Contrast with `save_as_bmp` (`matrix.hpp:~6830`), which correctly checks the stream and reports via `better_assert`. +- **Confidence:** High. + +--- + +## Readability / Simplicity + +### R1 — `svd_inverse` swaps argument order against the function signature (Medium) + +- **Severity:** Medium (comprehensibility trap; one of the direct causes of C4) +- **Evidence:** `matrix.hpp:5216-5224` calls `singular_value_decomposition( a, u, v, w )` while the signature is `( A, u, w, v )`. Local variable names then match the *call site*, not the function's parameters, so reading the body (`for_each( v.begin(), ... ) 1.0/val`) requires knowing the swap. +- **Smallest safe fix:** keep names consistent with the signature: `matrix u, w, v; singular_value_decomposition( a, u, w, v ); ... invert w ...; return v * w.transpose()*... ` (i.e., stop transposing the V matrix into the "w" slot), or better, delete `svd_inverse` and fix `pinverse` (C4) to be the single correct implementation. +- **Confidence:** High. + +### R2 — ~40 near-identical elementwise templates (Low–Medium) + +- **Severity:** Low (no correctness impact; maintainability cost) +- **Evidence:** the "unary functions" block (`matrix.hpp:6495-6930`) and "binary functions" block (`matrix.hpp:6940-7545`) contain ~40 functions that are the same 6-line shape: `zeros_like` + `matrix_details::for_each` + `std::`. E.g. `exp`, `exp2`, `expm1`, `log`, `log10`, `log1p`, `log2`, `sqrt`, … `abs`, `exp`, `imag` each differ only in the standard function and (for complex) the result type. +- **Contract clause:** "Could this be done in fewer lines?" — this is ~1,200 lines of copy-paste. +- **Smallest safe fix:** one macro or a small `apply_unary(m)`/`apply_binary(a,b)` helper; or keep the explicit list but generate it via a single macro that lists the function names. Low urgency. +- **Confidence:** High. + +### R3 — Dead/broken artifacts (Low) + +- Stray `;;` at `matrix.hpp:3105` (save_png). +- `better_assert` typo in `det` message at `matrix.hpp:2056`: "the row and matrix are supposed to be same". +- `matrix const` return type (top-level `const` on returned prvalues) is used across the free-function API (e.g. `magic`, `flipdim`, `rand`); harmless but non-idiomatic and signals confusion with `const&` returns. +- **Confidence:** High. + +--- + +## Tests + +### T1 — Test suite is green, but coverage avoids the buggy paths (Medium) + +- **Severity:** Medium +- **Evidence:** `tests/cases/` contains 59 small files (1,158 lines total), dominated by elementwise unary-math cases (`sin.hpp`, `cos.hpp`, …). There is **no test** for: `shrink_to_size`, `flipdim`/`fliplr`/`flipud`, `pinverse`/`svd_inverse`, `det` (member or free), `operator^`/`pow` on matrices, `operator*(valarray, matrix)`, `conv` modes, `fft`, or file save/load except a single happy-path `load_npy`. Examples (`examples/cases/0005_det.hpp`, `0018_conv.hpp`, `0021_singular_value_decomposition.hpp`, …) exercise some of these, but the maintained Catch2 suite does not. +- **Violated contract clause:** "Are all error paths covered? Do the tests actually assert the right things?" — every Critical/High finding above (C1, C2, C4, C5, C6, S1) is in a path with no test, which is why a fully green suite (57 cases / 49.2M assertions) coexists with heap corruption. +- **Smallest safe fix:** add one regression case each: `shrink_to_size(5,5→5,3)` content+shape check; `flipdim` on 3×5 vs. expected; `pinverse(diag(1,2))` ≈ `diag(1,0.5)`; `det` of the singular-P matrix ≈ 0; `m ^ 3` vs. `m*m*m`; `load_npy` on a truncated file expecting `false` (needs S1 fix first). +- **Confidence:** High (file listing + `make test` run). + +### T2 — Happy-path-only assertions; error paths untested (Medium) + +- **Severity:** Medium +- **Evidence:** e.g. `tests/cases/ones.hpp` (shown above) only checks well-formed shapes; `load_npy.hpp` loads a valid file; no test expects `{}` from `lu_solver` on a singular matrix or `nullopt` from `gauss_jordan_elimination`. +- **Smallest safe fix:** after fixing S1/C5/C10, add negative-path cases (singular det, singular LU, truncated npy, `rref` on a square matrix). +- **Confidence:** High. + +--- + +## Architecture + +### A1 — CRTP mixin sprawl for a single concrete class (Medium) + +- **Severity:** Medium (design debt; no behavior bug) +- **Evidence:** `matrix.hpp:3760` — `matrix` inherits ~30 `crtp_*` structs (`crtp_typedef`, `crtp_inverse`, `crtp_det`, `crtp_clone`, `crtp_shrink_to_size`, `crtp_load_npy`, …). Every mixin re-derives the same typedefs through `crtp_typedef`/`type_proxy_type` and casts back with `static_cast(*this)`. +- **Contract clause:** "Are abstractions earning their complexity?" — CRTP pays a real comprehension cost (a reader must jump mixin → typedef → cast to see what a method does) but buys no reuse: there is exactly one class template, and no second derived type exists. Regular member functions (grouped in sections) would delete the `zen`/`zen_type` indirection layer entirely. +- **Caveat:** this is a *refactor* recommendation, not a fix; do it after the correctness fixes land and are covered by tests (T1). +- **Confidence:** High (structural observation). + +### A2 — Duplicated public API (Low) + +- **Evidence:** `random`→`rand` (`matrix.hpp:5260-5268`), `random_like`→`rand_like` (`5276-5280`), `pinv`→`pinverse` (`5232-5236`), free `det(m)`→`m.det()` (`4319-4321`). Each alias is one line, but doubling the surface means every fix must be applied/verified twice (C4 shows the two SVD-inversion paths already diverged). +- **Smallest safe fix:** keep one canonical name per operation; delete or `static_assert` the duplicates. +- **Confidence:** High. + +### A3 — Hostile-to-ADL name collisions (Low) + +- **Evidence:** `namespace feng` defines free `abs`, `exp`, `sqrt`, `log`, `pow`, `norm`, `real`, `imag`, `conj`, `det`, `diag`, `fft`, `meshgrid` (e.g. `matrix.hpp:6505`, `7560-7630`). With `using namespace feng;` in a translation unit that also uses `std::` or third-party code, overload sets merge and unqualified calls can change meaning (e.g. `abs(x)` for a scalar now also sees `feng::abs(Mat)` — usually SFINAE'd away, but `norm` has *both* a complex-matrix version and the commented-out scalar version at `6160-6190`, which shows the drift risk). +- **Smallest safe fix:** namespace the elementwise layer (e.g. `feng::elem::`) or rename the colliding few (`norm` → `cmplx_norm`). +- **Confidence:** Medium. + +--- + +## Performance + +### P1 — `fft` / `ifft` are naive O(N⁴) direct DFTs (High) + +- **Severity:** High (misleading complexity; unusable for real image sizes) +- **Evidence:** `matrix.hpp:6313-6335` (and `ifft` at `6446-6468`): quadruple-nested loops with the definition `X[r][c] = Σ_r' Σ_c' x[r'][c'] · ω…`, i.e. O(R²C²) per output element → O(R⁴C⁴)-ish per matrix, plus two `cos`/`sin` evaluations (`make_omege`) per multiply. +- **Violated contract / invariant:** the name (`fft`, and `fftshift` matching the FFT convention) implies O(N log N) behavior; a 256×256 input costs trillions of operations here. +- **Smallest safe fix:** (a) rename to `dft` and document the complexity, or (b) implement a real radix-2 FFT row-wise + column-wise (the standard separable 2-D FFT) and keep trig precomputation per row. +- **Confidence:** High (algorithm is plainly the direct sum). + +### P2 — `det`/`inverse`-level routines avoid the library's own LU (Low–Medium) + +- **Severity:** Low–Medium +- **Evidence:** `det` recurses through Schur complements built with `P.inverse()` (`matrix.hpp:2063-2067`), i.e. O(n³) work per recursion level instead of O(n³/3) once via the existing `lu_decomposition` (`matrix.hpp:~6700`). `lu_decomposition` itself performs no partial pivoting, so stability depends on the input; `forward_substitution` masks failure with an `isinf`/`isnan` check (`matrix.hpp:6379-6383`), and `cholesky_decomposition` has no positive-definiteness guard (sqrt of a negative silently yields `NaN`). +- **Smallest safe fix:** implement `det` via LU (folds into C5); add pivot selection to `lu_decomposition`; return `std::optional` from `cholesky` on `sum < 0`. +- **Confidence:** High. + +--- + +## Verified non-issues (checked and found acceptable) + +- `load_bmp` validates header/size consistency before parsing (`matrix.hpp:6760-6770`) — good boundary handling; the model S1 should follow. +- `save_as_bmp` checks stream construction and shape equality of the three channels. +- `expm` scaling matches the standard `A/s2` reduction (the `s == 0` case reduces to the identity scaling); the only edge is `1 << s` at `s ≥ 64`, unreachable in practice for double inputs. +- `conv` padding and `mode == "full"` path are correct; only the `"same"` asserts are wrong (C9). +- `pooling` correctly ignores leftover rows/cols (`row/dim_r` truncation) and validates the action name. +- Full test suite passes as-is (`make test`, 57 cases, 49,216,592 assertions); `examples/` builds the remaining 2 cases gated behind missing optional data. + +## Suggested fix order + +1. C1, C2 (memory corruption, one-line fixes each) + T1 regression tests. +2. S1 (`load_npy` validation) — unblocks negative-path tests. +3. C4 (make `pinverse` = `svd_inverse`), C5 (`det` via LU), C6 (parenthesize `operator^`), C3 (swap aliases). +4. S2, C7 (document or convert the `NDEBUG` policy), C8–C11. +5. P1 (FFT rename or real implementation), then A1/A2/R2 refactors behind the new tests. diff --git a/examples/cases/0000_create.hpp b/examples/cases/0000_create.hpp index c13df04..69a7bcd 100644 --- a/examples/cases/0000_create.hpp +++ b/examples/cases/0000_create.hpp @@ -2,7 +2,7 @@ void _0000_create() { feng::matrix m{ 64, 256 }; std::generate( m.begin(), m.end(), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0000_create.bmp" ); + (void)m.save_as_bmp( "./images/0000_create.bmp" ); } void _0001_create() @@ -10,13 +10,13 @@ void _0001_create() feng::matrix m{ 64, 256 }; std::generate( m.row_begin(17), m.row_end(17), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0001_create.bmp" ); + (void)m.save_as_bmp( "./images/0001_create.bmp" ); } void _0002_create() { feng::matrix m{ 64, 256 }; - m.save_as_bmp( "./images/0002_create.bmp" ); + (void)m.save_as_bmp( "./images/0002_create.bmp" ); assert( m.row() == 64 ); assert( m.col() == 256 ); assert( m.size() == m.row() * m.col() ); @@ -33,7 +33,7 @@ void _0003_create() feng::matrix m{ 64, 256 }; std::generate( m.row_rbegin(17), m.row_rend(17), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0003_create.bmp" ); + (void)m.save_as_bmp( "./images/0003_create.bmp" ); } void _0004_create() @@ -41,7 +41,7 @@ void _0004_create() feng::matrix m{ 64, 256 }; std::generate( m.col_begin(17), m.col_end(17), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0004_create.bmp" ); + (void)m.save_as_bmp( "./images/0004_create.bmp" ); } void _0005_create() @@ -49,14 +49,14 @@ void _0005_create() feng::matrix m{ 64, 256 }; std::generate( m.col_rbegin(17), m.col_rend(17), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0005_create.bmp" ); + (void)m.save_as_bmp( "./images/0005_create.bmp" ); } void _0006_create() { feng::matrix m{ 64, 256 }; std::generate( m.rbegin(), m.rend(), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0006_create.bmp" ); + (void)m.save_as_bmp( "./images/0006_create.bmp" ); } void _0007_create() @@ -64,7 +64,7 @@ void _0007_create() feng::matrix m{ 64, 256 }; std::generate( m.upper_diag_begin(17), m.upper_diag_end(17), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0007_create.bmp" ); + (void)m.save_as_bmp( "./images/0007_create.bmp" ); } void _0008_create() @@ -72,7 +72,7 @@ void _0008_create() feng::matrix m{ 64, 256 }; std::generate( m.upper_diag_rbegin(17), m.upper_diag_rend(17), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0008_create.bmp" ); + (void)m.save_as_bmp( "./images/0008_create.bmp" ); } void _0009_create() @@ -80,7 +80,7 @@ void _0009_create() feng::matrix m{ 64, 256 }; std::generate( m.lower_diag_begin(17), m.lower_diag_end(17), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0009_create.bmp" ); + (void)m.save_as_bmp( "./images/0009_create.bmp" ); } void _0010_create() @@ -88,7 +88,7 @@ void _0010_create() feng::matrix m{ 64, 256 }; std::generate( m.lower_diag_rbegin(17), m.lower_diag_rend(17), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0010_create.bmp" ); + (void)m.save_as_bmp( "./images/0010_create.bmp" ); } void _0011_create() @@ -96,7 +96,7 @@ void _0011_create() feng::matrix m{ 64, 256 }; std::generate( m.diag_begin(), m.diag_end(), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0011_create.bmp" ); + (void)m.save_as_bmp( "./images/0011_create.bmp" ); } void _0012_create() @@ -104,7 +104,7 @@ void _0012_create() feng::matrix m{ 64, 256 }; std::generate( m.diag_rbegin(), m.diag_rend(), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0012_create.bmp" ); + (void)m.save_as_bmp( "./images/0012_create.bmp" ); } void _0013_create() @@ -112,7 +112,7 @@ void _0013_create() feng::matrix m{ 64, 256 }; std::generate( m.upper_anti_diag_begin(17), m.upper_anti_diag_end(17), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0013_create.bmp" ); + (void)m.save_as_bmp( "./images/0013_create.bmp" ); } void _0014_create() @@ -120,7 +120,7 @@ void _0014_create() feng::matrix m{ 64, 256 }; std::generate( m.upper_anti_diag_rbegin(17), m.upper_anti_diag_rend(17), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0014_create.bmp" ); + (void)m.save_as_bmp( "./images/0014_create.bmp" ); } void _0015_create() @@ -128,7 +128,7 @@ void _0015_create() feng::matrix m{ 64, 256 }; std::generate( m.lower_anti_diag_begin(17), m.lower_anti_diag_end(17), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0015_create.bmp" ); + (void)m.save_as_bmp( "./images/0015_create.bmp" ); } void _0016_create() @@ -136,7 +136,7 @@ void _0016_create() feng::matrix m{ 64, 256 }; std::generate( m.lower_anti_diag_rbegin(17), m.lower_anti_diag_rend(17), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0016_create.bmp" ); + (void)m.save_as_bmp( "./images/0016_create.bmp" ); } void _0017_create() @@ -144,7 +144,7 @@ void _0017_create() feng::matrix m{ 64, 256 }; std::generate( m.anti_diag_begin(), m.anti_diag_end(), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0017_create.bmp" ); + (void)m.save_as_bmp( "./images/0017_create.bmp" ); } void _0018_create() @@ -152,7 +152,7 @@ void _0018_create() feng::matrix m{ 64, 256 }; std::generate( m.anti_diag_rbegin(), m.anti_diag_rend(), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0018_create.bmp" ); + (void)m.save_as_bmp( "./images/0018_create.bmp" ); } void _0019_create() @@ -167,7 +167,7 @@ void _0019_create() for ( auto c = 123; c != 234; ++c ) m(r, c) = -1.0; - m.save_as_bmp( "./images/0019_create.bmp" ); + (void)m.save_as_bmp( "./images/0019_create.bmp" ); } void _0020_create() @@ -176,16 +176,16 @@ void _0020_create() for ( auto r = 12; r != 34; ++r ) for ( auto c = 12; c != 34; ++c ) m[r][c] = 1.0; - m.save_as_bmp( "./images/0020_create.bmp" ); + (void)m.save_as_bmp( "./images/0020_create.bmp" ); feng::matrix n = m; //copying - n.save_as_bmp( "./images/0021_create.bmp" ); + (void)n.save_as_bmp( "./images/0021_create.bmp" ); n.resize( 63, 244 ); - n.save_as_bmp( "./images/0022_create.bmp" ); + (void)n.save_as_bmp( "./images/0022_create.bmp" ); m.reshape( m.col(), m.row() ); - m.save_as_bmp( "./images/0023_create.bmp" ); + (void)m.save_as_bmp( "./images/0023_create.bmp" ); } diff --git a/examples/cases/0001_apply.hpp b/examples/cases/0001_apply.hpp index 1aaf0ea..1e94c4b 100644 --- a/examples/cases/0001_apply.hpp +++ b/examples/cases/0001_apply.hpp @@ -2,8 +2,8 @@ void _0000_apply() { feng::matrix m{ 64, 256 }; std::generate( m.begin(), m.end(), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0000_apply.bmp" ); + (void)m.save_as_bmp( "./images/0000_apply.bmp" ); m.apply( [](auto& x) { x = std::sin(x); } ); - m.save_as_bmp( "./images/0001_apply.bmp" ); + (void)m.save_as_bmp( "./images/0001_apply.bmp" ); } diff --git a/examples/cases/0002_access.hpp b/examples/cases/0002_access.hpp index fcaa35f..63c9014 100644 --- a/examples/cases/0002_access.hpp +++ b/examples/cases/0002_access.hpp @@ -11,5 +11,5 @@ void _0000_access() int val = starter++ & 0x7; x = keys[val]; } - m.save_as_bmp( "./images/0000_access.bmp" ); + (void)m.save_as_bmp( "./images/0000_access.bmp" ); } diff --git a/examples/cases/0003_clone.hpp b/examples/cases/0003_clone.hpp index 4917b5a..99477b1 100644 --- a/examples/cases/0003_clone.hpp +++ b/examples/cases/0003_clone.hpp @@ -3,11 +3,11 @@ void _0000_clone() feng::matrix m{ 64, 256 }; //std::generate( m.begin(), m.end(), [](){ double x = 0.0; return [x]()mutable{ x+=0.1; return std::sin(x);}; }() ); std::fill( m.diag_begin(), m.diag_end(), 1.1 ); - m.save_as_bmp( "./images/0000_clone.bmp" ); + (void)m.save_as_bmp( "./images/0000_clone.bmp" ); auto n = m.clone( 0, 32, 0, 64 ); - n.save_as_bmp( "./images/0001_clone.bmp" ); + (void)n.save_as_bmp( "./images/0001_clone.bmp" ); n.clone( m, 32, 64, 0, 64 ); - n.save_as_bmp( "./images/0002_clone.bmp" ); + (void)n.save_as_bmp( "./images/0002_clone.bmp" ); } diff --git a/examples/cases/0004_data.hpp b/examples/cases/0004_data.hpp index 3c0a917..049d6aa 100644 --- a/examples/cases/0004_data.hpp +++ b/examples/cases/0004_data.hpp @@ -1,11 +1,11 @@ void _0000_data() { feng::matrix m{ 64, 256 }; - m.save_as_bmp( "./images/0000_data.bmp" ); + (void)m.save_as_bmp( "./images/0000_data.bmp" ); auto ptr = m.data(); for ( auto idx = 0UL; idx != m.size(); ++idx ) ptr[idx] = std::sin( idx*idx*0.1 ); - m.save_as_bmp( "./images/0001_data.bmp" ); + (void)m.save_as_bmp( "./images/0001_data.bmp" ); } diff --git a/examples/cases/0006_devide_equal.hpp b/examples/cases/0006_devide_equal.hpp index e617cff..3970469 100644 --- a/examples/cases/0006_devide_equal.hpp +++ b/examples/cases/0006_devide_equal.hpp @@ -5,6 +5,6 @@ void _0000_divide_equal() n /= 2.0; m /= n; - m.save_as_bmp( "images/0000_divide_equal.bmp" ); + (void)m.save_as_bmp( "images/0000_divide_equal.bmp" ); } diff --git a/examples/cases/0007_slicing.hpp b/examples/cases/0007_slicing.hpp index 124898d..9827628 100644 --- a/examples/cases/0007_slicing.hpp +++ b/examples/cases/0007_slicing.hpp @@ -4,12 +4,12 @@ void _0000_slicing() std::fill( m.upper_diag_begin(1), m.upper_diag_end(1), 1.0 ); std::fill( m.diag_begin(), m.diag_end(), 1.0 ); std::fill( m.lower_diag_begin(1), m.lower_diag_end(1), 1.0 ); - m.save_as_bmp( "./images/0000_slicing.bmp" ); + (void)m.save_as_bmp( "./images/0000_slicing.bmp" ); feng::matrix n{ m, 0, 32, 0, 64 }; - n.save_as_bmp( "./images/0001_slicing.bmp" ); + (void)n.save_as_bmp( "./images/0001_slicing.bmp" ); feng::matrix p{ m, {16, 48}, {0, 64} }; - p.save_as_bmp( "./images/0002_slicing.bmp" ); + (void)p.save_as_bmp( "./images/0002_slicing.bmp" ); } diff --git a/examples/cases/0008_inverse.hpp b/examples/cases/0008_inverse.hpp index 92270e2..60ac871 100644 --- a/examples/cases/0008_inverse.hpp +++ b/examples/cases/0008_inverse.hpp @@ -3,6 +3,6 @@ void _0000_inverse() auto const& m = feng::rand( 128, 128 ); auto const& n = m.inverse(); auto const& identity = m * n; - identity.save_as_bmp( "./images/0000_inverse.bmp" ); + (void)identity.save_as_bmp( "./images/0000_inverse.bmp" ); } diff --git a/examples/cases/0009_save_load.hpp b/examples/cases/0009_save_load.hpp index 883c533..40b2350 100644 --- a/examples/cases/0009_save_load.hpp +++ b/examples/cases/0009_save_load.hpp @@ -2,31 +2,31 @@ void _0000_save_load() { //auto const& m = feng::rand( 128, 128 ); feng::matrix m; - m.load_txt( "./images/Lenna.txt" ); - m.save_as_txt( "./images/0000_save_load.txt" ); - m.save_as_binary( "./images/0000_save_load.bin" ); - m.save_as_bmp( "./images/0000_save_load.bmp" ); + (void)m.load_txt( "./images/Lenna.txt" ); + (void)m.save_as_txt( "./images/0000_save_load.txt" ); + (void)m.save_as_binary( "./images/0000_save_load.bin" ); + (void)m.save_as_bmp( "./images/0000_save_load.bmp" ); feng::matrix n; - n.load_txt( "./images/0000_save_load.txt" ); - n.save_as_bmp( "./images/0001_save_load.bmp" ); - n.load_binary( "./images/0000_save_load.bin" ); - n.save_as_pgm( "./images/0002_save_load.pgm" ); + (void)n.load_txt( "./images/0000_save_load.txt" ); + (void)n.save_as_bmp( "./images/0001_save_load.bmp" ); + (void)n.load_binary( "./images/0000_save_load.bin" ); + (void)n.save_as_pgm( "./images/0002_save_load.pgm" ); } void _0001_save_load() { { feng::matrix m; - m.load_txt( "./images/Lenna.txt" ); - m.save_as_bmp( "./images/Lenna.bmp", "gray" ); + (void)m.load_txt( "./images/Lenna.txt" ); + (void)m.save_as_bmp( "./images/Lenna.bmp", "gray" ); } auto const& mat_3 = feng::load_bmp( "./images/Lenna.bmp" ); if ( mat_3 ) { - (*mat_3)[0].save_as_bmp( "./images/0001_save_load_julia_red.bmp", "gray" ); - (*mat_3)[1].save_as_bmp( "./images/0001_save_load_julia_green.bmp", "gray" ); - (*mat_3)[2].save_as_bmp( "./images/0001_save_load_julia_blue.bmp", "gray" ); + (void)(*mat_3)[0].save_as_bmp( "./images/0001_save_load_julia_red.bmp", "gray" ); + (void)(*mat_3)[1].save_as_bmp( "./images/0001_save_load_julia_green.bmp", "gray" ); + (void)(*mat_3)[2].save_as_bmp( "./images/0001_save_load_julia_blue.bmp", "gray" ); } else { diff --git a/examples/cases/0010_minus_equal.hpp b/examples/cases/0010_minus_equal.hpp index 4b6d228..057e02b 100644 --- a/examples/cases/0010_minus_equal.hpp +++ b/examples/cases/0010_minus_equal.hpp @@ -1,16 +1,16 @@ void _0000_minus_equal() { feng::matrix image; - image.load_txt( "images/Lenna.txt" ); - image.save_as_bmp("images/0000_minus_equal.bmp", "gray"); + (void)image.load_txt( "images/Lenna.txt" ); + (void)image.save_as_bmp("images/0000_minus_equal.bmp", "gray"); double const min = *std::min_element( image.begin(), image.end() ); image -= min; - image.save_as_bmp("images/0001_minus_equal.bmp", "jet"); + (void)image.save_as_bmp("images/0001_minus_equal.bmp", "jet"); image -= image; - image.save_as_bmp("images/0002_minus_equal.bmp"); + (void)image.save_as_bmp("images/0002_minus_equal.bmp"); } diff --git a/examples/cases/0011_multiply_equal.hpp b/examples/cases/0011_multiply_equal.hpp index ea87f3a..cb2f1f7 100644 --- a/examples/cases/0011_multiply_equal.hpp +++ b/examples/cases/0011_multiply_equal.hpp @@ -3,6 +3,6 @@ void _0000_multiply_equal() auto m = feng::rand( 127, 127 ); m *= m.inverse(); - m.save_as_bmp("images/0001_multiply_equal.bmp"); + (void)m.save_as_bmp("images/0001_multiply_equal.bmp"); } diff --git a/examples/cases/0012_plus_equal.hpp b/examples/cases/0012_plus_equal.hpp index b086b54..6c9399a 100644 --- a/examples/cases/0012_plus_equal.hpp +++ b/examples/cases/0012_plus_equal.hpp @@ -1,8 +1,8 @@ void _0000_plus_equal() { feng::matrix image; - image.load_txt( "images/Lenna.txt" ); - image.save_as_bmp("images/0000_plus_equal.bmp", "gray"); + (void)image.load_txt( "images/Lenna.txt" ); + (void)image.save_as_bmp("images/0000_plus_equal.bmp", "gray"); double const mn = *std::min_element( image.begin(), image.end() ); double const mx = *std::max_element( image.begin(), image.end() ); @@ -10,6 +10,6 @@ void _0000_plus_equal() auto const& noise = feng::rand( image.row(), image.col(), 1 ); //setting random seed to 1 image += 0.1*noise; - image.save_as_bmp("images/0001_plus_equal.bmp", "gray"); + (void)image.save_as_bmp("images/0001_plus_equal.bmp", "gray"); } diff --git a/examples/cases/0013_prefix.hpp b/examples/cases/0013_prefix.hpp index 98e3dcb..2ceb26a 100644 --- a/examples/cases/0013_prefix.hpp +++ b/examples/cases/0013_prefix.hpp @@ -4,6 +4,6 @@ void _0000_prefix() auto const& pp = +m; auto const& pm = -m; auto const& shoule_be_zero = pp + pm; - shoule_be_zero.save_as_bmp("images/0000_prefix.bmp"); + (void)shoule_be_zero.save_as_bmp("images/0000_prefix.bmp"); } diff --git a/examples/cases/0014_sin.hpp b/examples/cases/0014_sin.hpp index f748eb0..dfb9135 100644 --- a/examples/cases/0014_sin.hpp +++ b/examples/cases/0014_sin.hpp @@ -2,8 +2,8 @@ void _0000_sin() { feng::matrix m{ 64, 256 }; std::generate( m.begin(), m.end(), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0000_sin.bmp" ); + (void)m.save_as_bmp( "./images/0000_sin.bmp" ); m = feng::sin(m); - m.save_as_bmp( "./images/0001_sin.bmp" ); + (void)m.save_as_bmp( "./images/0001_sin.bmp" ); } diff --git a/examples/cases/0015_sinh.hpp b/examples/cases/0015_sinh.hpp index 934b1b4..460ea43 100644 --- a/examples/cases/0015_sinh.hpp +++ b/examples/cases/0015_sinh.hpp @@ -2,8 +2,8 @@ void _0000_sinh() { feng::matrix m{ 64, 256 }; std::generate( m.begin(), m.end(), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init/500.0; }; }() ); - m.save_as_bmp( "./images/0000_sinh.bmp" ); + (void)m.save_as_bmp( "./images/0000_sinh.bmp" ); m = feng::sinh(m); - m.save_as_bmp( "./images/0001_sinh.bmp" ); + (void)m.save_as_bmp( "./images/0001_sinh.bmp" ); } diff --git a/examples/cases/0016_eye.hpp b/examples/cases/0016_eye.hpp index 571e6f2..d9595a0 100644 --- a/examples/cases/0016_eye.hpp +++ b/examples/cases/0016_eye.hpp @@ -1,5 +1,5 @@ void _0000_eye() { auto const& m = feng::eye( 128, 128 ); - m.save_as_bmp( "./images/0000_eye.bmp" ); + (void)m.save_as_bmp( "./images/0000_eye.bmp" ); } diff --git a/examples/cases/0017_make_view.hpp b/examples/cases/0017_make_view.hpp index 6d17498..a9301f0 100644 --- a/examples/cases/0017_make_view.hpp +++ b/examples/cases/0017_make_view.hpp @@ -1,21 +1,21 @@ void _0000_make_view() { feng::matrix m; - m.load_txt( "./images/Lenna.txt" ); - m.save_as_bmp( "./images/0000_make_view.bmp" ); + (void)m.load_txt( "./images/Lenna.txt" ); + (void)m.save_as_bmp( "./images/0000_make_view.bmp" ); auto const[r,c] = m.shape(); auto const& v = feng::make_view( m, {r>>2, (r>>2)*3}, {c>>2, (c>>2)*3} ); - v.save_as_bmp( "./images/0001_make_view.bmp" ); + (void)v.save_as_bmp( "./images/0001_make_view.bmp" ); auto new_matrix{v}; - new_matrix.save_as_bmp( "./images/0002_make_view.bmp" ); + (void)new_matrix.save_as_bmp( "./images/0002_make_view.bmp" ); feng::matrix n{ v.row(), v.col() }; // row() and col() of a matrix view for ( auto r = 0UL; r != n.row(); ++r ) for ( auto c = 0UL; c != n.col(); ++c ) n[r][c] = v[r][c]; // accessing matrix elements using operator [], read-only - n.save_as_bmp( "./images/0003_make_view.bmp", "gray" ); + (void)n.save_as_bmp( "./images/0003_make_view.bmp", "gray" ); } diff --git a/examples/cases/0018_conv.hpp b/examples/cases/0018_conv.hpp index c1ddc25..79da6fc 100644 --- a/examples/cases/0018_conv.hpp +++ b/examples/cases/0018_conv.hpp @@ -1,24 +1,31 @@ void _0000_conv() { feng::matrix m; - m.load_txt( "./images/Lenna.txt" ); - m.save_as_bmp( "./images/0000_conv.bmp", "gray" ); + (void)m.load_txt( "./images/Lenna.txt" ); + (void)m.save_as_bmp( "./images/0000_conv.bmp", "gray" ); feng::matrix filter{3, 3, {0.0, 1.0, 0.0, 1.0,-4.0, 1.0, 0.0, 1.0, 0.0}}; auto const& edge = feng::conv( m, filter ); - edge.save_as_bmp( "./images/0001_conv.bmp", "gray" ); - edge.save_as_txt( "./images/0001_conv.txt" ); - edge.save_as_pgm( "./images/0001_conv.pgm" ); + (void)edge.save_as_bmp( "./images/0001_conv.bmp", "gray" ); + (void)edge.save_as_txt( "./images/0001_conv.txt" ); + (void)edge.save_as_pgm( "./images/0001_conv.pgm" ); auto const& edge_valid = feng::conv( m, filter, "valid" ); - edge_valid.save_as_bmp( "./images/0001_conv_valid.bmp", "gray" ); + (void)edge_valid.save_as_bmp( "./images/0001_conv_valid.bmp", "gray" ); auto const& edge_same = feng::conv( m, filter, "same" ); - edge_same.save_as_bmp( "./images/0001_conv_same.bmp", "gray" ); + (void)edge_same.save_as_bmp( "./images/0001_conv_same.bmp", "gray" ); auto const& edge_full = feng::conv( m, filter, "full" ); - edge_full.save_as_bmp( "./images/0001_conv_full.bmp", "gray" ); + (void)edge_full.save_as_bmp( "./images/0001_conv_full.bmp", "gray" ); + + // asymmetric kernel: conv reverses it (scipy.signal.convolve2d), so this is the horizontal Sobel derivative + feng::matrix sobel{3, 3, {-1.0, 0.0, 1.0, + -2.0, 0.0, 2.0, + -1.0, 0.0, 1.0}}; + auto const& edge_sobel = feng::conv( m, sobel, "same" ); + (void)edge_sobel.save_as_bmp( "./images/0001_conv_sobel.bmp", "gray" ); } diff --git a/examples/cases/0019_lu_decomposition.hpp b/examples/cases/0019_lu_decomposition.hpp index eaaa5a3..d06e727 100644 --- a/examples/cases/0019_lu_decomposition.hpp +++ b/examples/cases/0019_lu_decomposition.hpp @@ -2,8 +2,8 @@ void _0000_lu_decomposition() { // initial matrix feng::matrix m; - m.load_txt( "./images/Lenna.txt" ); - m.save_as_bmp( "./images/0000_lu_decomposition.bmp", "gray" ); + (void)m.load_txt( "./images/Lenna.txt" ); + (void)m.save_as_bmp( "./images/0000_lu_decomposition.bmp", "gray" ); // adding noise double mn = *std::min_element( m.begin(), m.end() ); @@ -11,18 +11,18 @@ void _0000_lu_decomposition() m = (m-mn) / (mx - mn + 1.0e-10); auto const& [row, col] = m.shape(); m += feng::rand( row, col, 1 ); // set random seed to 1 - m.save_as_bmp( "./images/0001_lu_decomposition.bmp", "gray" ); + (void)m.save_as_bmp( "./images/0001_lu_decomposition.bmp", "gray" ); // lu decomposition auto const& lu = feng::lu_decomposition( m ); if (lu) { auto const& [l, u] = lu.value(); - l.save_as_bmp( "./images/0002_lu_decomposition.bmp", "jet" ); - u.save_as_bmp( "./images/0003_lu_decomposition.bmp", "jet" ); + (void)l.save_as_bmp( "./images/0002_lu_decomposition.bmp", "jet" ); + (void)u.save_as_bmp( "./images/0003_lu_decomposition.bmp", "jet" ); auto const& reconstructed = l * u; - reconstructed.save_as_bmp( "./images/0004_lu_decomposition.bmp", "gray" ); + (void)reconstructed.save_as_bmp( "./images/0004_lu_decomposition.bmp", "gray" ); } else { diff --git a/examples/cases/0020_gauss_jordan_elimination.hpp b/examples/cases/0020_gauss_jordan_elimination.hpp index 9597b03..73b68ea 100644 --- a/examples/cases/0020_gauss_jordan_elimination.hpp +++ b/examples/cases/0020_gauss_jordan_elimination.hpp @@ -1,12 +1,12 @@ void _0000_gauss_jordan_elimination() { auto const& m = feng::rand( 64, 128, 1 ); // setting random seed to 1 - m.save_as_bmp( "./images/0000_gauss_jordan_elimination.bmp", "gray" ); + (void)m.save_as_bmp( "./images/0000_gauss_jordan_elimination.bmp", "gray" ); auto const& n = feng::gauss_jordan_elimination( m ); //<- also `feng::rref(m);`, alias name from Matlab if (n) - (*n).save_as_bmp( "./images/0001_gauss_jordan_elimination.bmp", "gray" ); + (void)(*n).save_as_bmp( "./images/0001_gauss_jordan_elimination.bmp", "gray" ); else std::cout << "Failed to execute Gauss-Jordan Elimination for matrix m.\n"; } diff --git a/examples/cases/0021_singular_value_decomposition.hpp b/examples/cases/0021_singular_value_decomposition.hpp index 4ac21c4..646d59e 100644 --- a/examples/cases/0021_singular_value_decomposition.hpp +++ b/examples/cases/0021_singular_value_decomposition.hpp @@ -2,18 +2,18 @@ void _0000_singular_value_decomposition() { // load feng::matrix m; - m.load_txt( "./images/Teacher.txt" ); + (void)m.load_txt( "./images/Teacher.txt" ); // normalize auto const mx = *std::max_element( m.begin(), m.end() ); auto const mn = *std::min_element( m.begin(), m.end() ); m = ( m - mn ) / ( mx - mn + 1.0e-10 ); // take a snapshot - m.save_as_bmp( "./images/0000_singular_value_decomposition.bmp", "gray" ); + (void)m.save_as_bmp( "./images/0000_singular_value_decomposition.bmp", "gray" ); // adding noise auto const[r, c] = m.shape(); m += feng::rand( r, c, 2 ); // record noisy matrix - m.save_as_bmp( "./images/0001_singular_value_decomposition.bmp", "gray" ); + (void)m.save_as_bmp( "./images/0001_singular_value_decomposition.bmp", "gray" ); // execute svd auto const& svd = feng::singular_value_decomposition( m ); // check svd result @@ -24,7 +24,7 @@ void _0000_singular_value_decomposition() // try to reconstruct matrix using u * v * w' auto const& m_ = u * v * (w.transpose()); // record reconstructed matrix - m_.save_as_bmp( "./images/0002_singular_value_decomposition.bmp", "gray" ); + (void)m_.save_as_bmp( "./images/0002_singular_value_decomposition.bmp", "gray" ); auto dm = std::min( r, c ); auto factor = 2UL; @@ -38,7 +38,7 @@ void _0000_singular_value_decomposition() auto const& new_m = new_u * new_v * new_w.transpose(); - new_m.save_as_bmp( "./images/0003_singular_value_decomposition_"+std::to_string(new_dm)+".bmp", "gray" ); + (void)new_m.save_as_bmp( "./images/0003_singular_value_decomposition_"+std::to_string(new_dm)+".bmp", "gray" ); factor *= 2UL; } @@ -53,18 +53,18 @@ void _0001_singular_value_decomposition() { // load feng::matrix m; - m.load_txt( "./images/frame_1.txt" ); + (void)m.load_txt( "./images/frame_1.txt" ); // normalize auto const mx = *std::max_element( m.begin(), m.end() ); auto const mn = *std::min_element( m.begin(), m.end() ); m = ( m - mn ) / ( mx - mn + 1.0e-10 ); // take a snapshot - m.save_as_bmp( "./images/1_0000_singular_value_decomposition.bmp", "gray" ); + (void)m.save_as_bmp( "./images/1_0000_singular_value_decomposition.bmp", "gray" ); // adding noise auto const[r, c] = m.shape(); m += feng::rand( r, c, 2 ); // record noisy matrix - m.save_as_bmp( "./images/1_0001_singular_value_decomposition.bmp", "gray" ); + (void)m.save_as_bmp( "./images/1_0001_singular_value_decomposition.bmp", "gray" ); // execute svd auto const& svd = feng::singular_value_decomposition( m ); // check svd result @@ -75,7 +75,7 @@ void _0001_singular_value_decomposition() // try to reconstruct matrix using u * v * w' auto const& m_ = u * v * (w.transpose()); // record reconstructed matrix - m_.save_as_bmp( "./images/1_0002_singular_value_decomposition.bmp", "gray" ); + (void)m_.save_as_bmp( "./images/1_0002_singular_value_decomposition.bmp", "gray" ); auto dm = std::min( r, c ); auto factor = 2UL; @@ -89,7 +89,7 @@ void _0001_singular_value_decomposition() auto const& new_m = new_u * new_v * new_w.transpose(); - new_m.save_as_bmp( "./images/1_0003_singular_value_decomposition_"+std::to_string(new_dm)+".bmp", "gray" ); + (void)new_m.save_as_bmp( "./images/1_0003_singular_value_decomposition_"+std::to_string(new_dm)+".bmp", "gray" ); factor *= 2UL; } diff --git a/examples/cases/0022_save_with_colromap.hpp b/examples/cases/0022_save_with_colromap.hpp index 45d82b5..db58214 100644 --- a/examples/cases/0022_save_with_colromap.hpp +++ b/examples/cases/0022_save_with_colromap.hpp @@ -1,38 +1,38 @@ void _0000_save_with_colormap() { feng::matrix m; - m.load_txt( "./images/Lenna.txt" ); - m.save_as_bmp( "./images/0000_save_with_colormap_default.bmp" ); - m.save_as_bmp( "./images/0000_save_with_colormap_parula.bmp", "parula" ); - m.save_as_bmp( "./images/0000_save_with_colormap_hotblue.bmp", "hotblue" ); - m.save_as_bmp( "./images/0000_save_with_colormap_bluehot.bmp", "bluehot" ); - m.save_as_bmp( "./images/0000_save_with_colormap_jet.bmp", "jet" ); - m.save_as_bmp( "./images/0000_save_with_colormap_obscure.bmp", "obscure" ); - m.save_as_bmp( "./images/0000_save_with_colormap_gray.bmp", "gray" ); + (void)m.load_txt( "./images/Lenna.txt" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_default.bmp" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_parula.bmp", "parula" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_hotblue.bmp", "hotblue" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_bluehot.bmp", "bluehot" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_jet.bmp", "jet" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_obscure.bmp", "obscure" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_gray.bmp", "gray" ); - m.save_as_bmp( "./images/0000_save_with_colormap_hsv.bmp", "hsv" ); - m.save_as_bmp( "./images/0000_save_with_colormap_spring.bmp", "spring" ); - m.save_as_bmp( "./images/0000_save_with_colormap_summer.bmp", "summer" ); - m.save_as_bmp( "./images/0000_save_with_colormap_autumn.bmp", "autumn" ); - m.save_as_bmp( "./images/0000_save_with_colormap_winter.bmp", "winter" ); - m.save_as_bmp( "./images/0000_save_with_colormap_pink.bmp", "pink" ); - m.save_as_bmp( "./images/0000_save_with_colormap_hot.bmp", "hot" ); - m.save_as_bmp( "./images/0000_save_with_colormap_cool.bmp", "cool" ); - m.save_as_bmp( "./images/0000_save_with_colormap_bone.bmp", "bone" ); - m.save_as_bmp( "./images/0000_save_with_colormap_copper.bmp", "copper" ); - m.save_as_bmp( "./images/0000_save_with_colormap_lines.bmp", "lines" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_hsv.bmp", "hsv" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_spring.bmp", "spring" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_summer.bmp", "summer" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_autumn.bmp", "autumn" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_winter.bmp", "winter" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_pink.bmp", "pink" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_hot.bmp", "hot" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_cool.bmp", "cool" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_bone.bmp", "bone" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_copper.bmp", "copper" ); + (void)m.save_as_bmp( "./images/0000_save_with_colormap_lines.bmp", "lines" ); - m.save_as_png( "./images/0000_save_with_colormap_default.png" ); - m.save_as_png( "./images/0000_save_with_colormap_parula.png", "parula" ); + (void)m.save_as_png( "./images/0000_save_with_colormap_default.png" ); + (void)m.save_as_png( "./images/0000_save_with_colormap_parula.png", "parula" ); } void _0001_save_with_colormap() { feng::matrix m; - m.load_txt( "./images/star.txt" ); - m.save_as_bmp( "./images/0001_star_hotblue.bmp", "hotblue" ); - m.save_as_bmp( "./images/0001_star_bluehot.bmp", "bluehot" ); - m.save_as_bmp( "./images/0001_star_hotgreen.bmp", "hotgreen" ); - m.save_as_bmp( "./images/0001_star_greenhot.bmp", "greenhot" ); - m.save_as_bmp( "./images/0001_star_tealhot.bmp", "tealhot" ); + (void)m.load_txt( "./images/star.txt" ); + (void)m.save_as_bmp( "./images/0001_star_hotblue.bmp", "hotblue" ); + (void)m.save_as_bmp( "./images/0001_star_bluehot.bmp", "bluehot" ); + (void)m.save_as_bmp( "./images/0001_star_hotgreen.bmp", "hotgreen" ); + (void)m.save_as_bmp( "./images/0001_star_greenhot.bmp", "greenhot" ); + (void)m.save_as_bmp( "./images/0001_star_tealhot.bmp", "tealhot" ); } diff --git a/examples/cases/0023_magic.hpp b/examples/cases/0023_magic.hpp index 0296090..7515b90 100644 --- a/examples/cases/0023_magic.hpp +++ b/examples/cases/0023_magic.hpp @@ -23,6 +23,6 @@ void _0001_magic() for ( auto cc = 0UL; cc != pixs; ++cc ) v_mat[r*pixs+rr][c*pixs+cc] = mat[r][c]; - v_mat.save_as_bmp("./images/0001_magic.bmp"); + (void)v_mat.save_as_bmp("./images/0001_magic.bmp"); } diff --git a/examples/cases/0024_pooling.hpp b/examples/cases/0024_pooling.hpp index 9716a4a..ca490c5 100644 --- a/examples/cases/0024_pooling.hpp +++ b/examples/cases/0024_pooling.hpp @@ -1,22 +1,22 @@ void _0000_pooling() { feng::matrix m; - m.load_txt( "./images/Lenna.txt" ); - m.save_as_bmp( "./images/0000_pooling.bmp", "gray" ); + (void)m.load_txt( "./images/Lenna.txt" ); + (void)m.save_as_bmp( "./images/0000_pooling.bmp", "gray" ); auto const& pooling_2 = feng::pooling( m, 2 ); - pooling_2.save_as_bmp( "./images/0000_pooling_2.bmp", "gray" ); + (void)pooling_2.save_as_bmp( "./images/0000_pooling_2.bmp", "gray" ); auto const& pooling_4 = feng::pooling( m, 4 ); - pooling_4.save_as_bmp( "./images/0000_pooling_4.bmp", "gray" ); + (void)pooling_4.save_as_bmp( "./images/0000_pooling_4.bmp", "gray" ); auto const& pooling_2_4 = feng::pooling( m, 2, 4 ); - pooling_2_4.save_as_bmp( "./images/0000_pooling_2_4.bmp", "gray" ); + (void)pooling_2_4.save_as_bmp( "./images/0000_pooling_2_4.bmp", "gray" ); auto const& pooling_4_2 = feng::pooling( m, 4, 2 ); - pooling_4_2.save_as_bmp( "./images/0000_pooling_4_2.bmp", "gray" ); + (void)pooling_4_2.save_as_bmp( "./images/0000_pooling_4_2.bmp", "gray" ); auto const& pooling_min = feng::pooling( m, 2, "min" ); - pooling_min.save_as_bmp( "./images/0000_pooling_2_min.bmp", "gray" ); + (void)pooling_min.save_as_bmp( "./images/0000_pooling_2_min.bmp", "gray" ); auto const& pooling_max = feng::pooling( m, 2, "max" ); - pooling_max.save_as_bmp( "./images/0000_pooling_2_max.bmp", "gray" ); + (void)pooling_max.save_as_bmp( "./images/0000_pooling_2_max.bmp", "gray" ); } diff --git a/examples/cases/0025_global_save_as_bmp.hpp b/examples/cases/0025_global_save_as_bmp.hpp index a9b67bf..cf96595 100644 --- a/examples/cases/0025_global_save_as_bmp.hpp +++ b/examples/cases/0025_global_save_as_bmp.hpp @@ -36,6 +36,6 @@ void _0000_global_save_as_bmp() blue[r][c] = BL( r, c ); } - feng::save_as_bmp( "./images/0000_global_save_as_bmp.bmp", red, green, blue ); + (void)feng::save_as_bmp( "./images/0000_global_save_as_bmp.bmp", red, green, blue ); } diff --git a/examples/cases/0026_mandelbrot.hpp b/examples/cases/0026_mandelbrot.hpp index 0210256..c44ab01 100644 --- a/examples/cases/0026_mandelbrot.hpp +++ b/examples/cases/0026_mandelbrot.hpp @@ -27,7 +27,7 @@ auto make_mandelbrot( std::complex const& lower_left, std::complex{-2.25, -1.5}, std::complex{0.75, 1.5} ); - mat.save_as_bmp( "./images/0000_mandelbrot.bmp", "gray" ); + (void)mat.save_as_bmp( "./images/0000_mandelbrot.bmp", "gray" ); } void _0001_mandelbrot() @@ -35,7 +35,7 @@ void _0001_mandelbrot() unsigned long const dims = 1024; unsigned long const iterations = 1024; auto&& mat = make_mandelbrot( std::complex{-2.0, -1.25}, std::complex{0.5, 1.25}, dims, iterations ); - mat.save_as_bmp( "./images/0001_mandelbrot.bmp", "bluehot" ); + (void)mat.save_as_bmp( "./images/0001_mandelbrot.bmp", "bluehot" ); mat /= static_cast( iterations ); //std::cout << "Var(mandelbrot) = " << feng::variance( mat ); } @@ -68,7 +68,7 @@ void _0002_mandelbrot() std::string const file_name = std::string{ "./images/mandelbrot_5/0002_mandel_brot_" } + std::to_string(r) + std::string{"-"} + std::to_string(c) + std::string{".bmp"}; //mat.save_as_bmp( file_name, "bluehot" ); //mat.save_as_bmp( file_name, "gray" ); - mat.save_as_bmp( file_name, "tealhot" ); + (void)mat.save_as_bmp( file_name, "tealhot" ); } } diff --git a/examples/cases/0027_julia_set.hpp b/examples/cases/0027_julia_set.hpp index 0f6cd2e..a57b050 100644 --- a/examples/cases/0027_julia_set.hpp +++ b/examples/cases/0027_julia_set.hpp @@ -59,7 +59,7 @@ void _0000_julia_set() { auto&& mat = make_julia_set( std::complex{-1.5, -1.0}, std::complex{1.5, 1.0}, std::complex{-0.4, 0.6} ); mat.apply( [](auto& x){ x = std::log(1.0+x); } ); - mat.save_as_bmp( "./images/0000_julia_set.bmp", "tealhot" ); + (void)mat.save_as_bmp( "./images/0000_julia_set.bmp", "tealhot" ); } void _0001_julia_set() @@ -76,7 +76,7 @@ void _0001_julia_set() //std::string file_name = std::string{"./images/julia_set/0001_julia_set_"} + std::to_string(r) + std::string{"-"} + std::to_string(c) + std::string{".bmp"}; //std::string file_name = std::string{"./images/julia_set_2/0001_julia_set_"} + std::to_string(r) + std::string{"-"} + std::to_string(c) + std::string{".bmp"}; std::string file_name = std::string{"./images/julia_set_3/0001_julia_set_"} + std::to_string(r) + std::string{"-"} + std::to_string(c) + std::string{".bmp"}; - mat.save_as_bmp( file_name, "bluehot" ); + (void)mat.save_as_bmp( file_name, "bluehot" ); } } @@ -91,7 +91,7 @@ void _0002_julia_set() std::complex zc{ double(r)/n*1.8-0.9, double(c)/n*1.8-0.9 }; auto&& mat = make_julia_set( std::complex{-1.5, -1.0}, std::complex{1.5, 1.0}, zc, 1024, 1024, 4 ); std::string file_name = std::string{"./images/julia_set_4/0001_julia_set_"} + std::to_string(r) + std::string{"-"} + std::to_string(c) + std::string{".bmp"}; - mat.save_as_bmp( file_name, "tealhot" ); + (void)mat.save_as_bmp( file_name, "tealhot" ); } } diff --git a/examples/cases/0028_plot.hpp b/examples/cases/0028_plot.hpp index 6f4b0fe..1800074 100644 --- a/examples/cases/0028_plot.hpp +++ b/examples/cases/0028_plot.hpp @@ -1,15 +1,15 @@ void _0000_plot() { feng::matrix m; - m.load_txt( "./images/Lenna.txt" ); - m.plot( "./images/0000_plot_default.bmp" ); - m.plot( "./images/0000_plot_jet.bmp", "jet" ); + (void)m.load_txt( "./images/Lenna.txt" ); + (void)m.plot( "./images/0000_plot_default.bmp" ); + (void)m.plot( "./images/0000_plot_jet.bmp", "jet" ); } void _0001_plot() { feng::matrix m; - m.load_txt( "./images/star.txt" ); - m.plot( "./images/0001_plot_star_hotblue.bmp", "hotblue" ); - m.plot( "./images/0001_plot_star_bluehot.bmp", "bluehot" ); + (void)m.load_txt( "./images/star.txt" ); + (void)m.plot( "./images/0001_plot_star_hotblue.bmp", "hotblue" ); + (void)m.plot( "./images/0001_plot_star_bluehot.bmp", "bluehot" ); } diff --git a/examples/cases/0029_meshgrid.hpp b/examples/cases/0029_meshgrid.hpp index 2d448a8..e232c0d 100644 --- a/examples/cases/0029_meshgrid.hpp +++ b/examples/cases/0029_meshgrid.hpp @@ -1,8 +1,8 @@ void _0000_meshgrid() { auto const& [X, Y] = feng::meshgrid( 384, 512 ); - X.save_as_bmp( "./images/0000_meshgrid_x.bmp", "grey" ); - Y.save_as_bmp( "./images/0000_meshgrid_y.bmp", "grey" ); + (void)X.save_as_bmp( "./images/0000_meshgrid_x.bmp", "grey" ); + (void)Y.save_as_bmp( "./images/0000_meshgrid_y.bmp", "grey" ); { auto const& [X, Y] = feng::meshgrid( 3, 5 ); diff --git a/examples/cases/0030_arange.hpp b/examples/cases/0030_arange.hpp index a1aad09..975e77d 100644 --- a/examples/cases/0030_arange.hpp +++ b/examples/cases/0030_arange.hpp @@ -2,6 +2,6 @@ void _0000_arange() { auto m = feng::arange( 256*256 ); m.reshape( 256, 256 ); - m.save_as_bmp( "./images/0000_arange.bmp" ); + (void)m.save_as_bmp( "./images/0000_arange.bmp" ); } diff --git a/examples/cases/0031_clip.hpp b/examples/cases/0031_clip.hpp index 4301d20..450763d 100644 --- a/examples/cases/0031_clip.hpp +++ b/examples/cases/0031_clip.hpp @@ -2,14 +2,14 @@ void _0000_clip() { feng::matrix m{ 64, 256 }; std::generate( m.begin(), m.end(), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return init; }; }() ); - m.save_as_bmp( "./images/0000_clip.bmp" ); + (void)m.save_as_bmp( "./images/0000_clip.bmp" ); m = feng::sin(m); - m.save_as_bmp( "./images/0001_clip.bmp" ); + (void)m.save_as_bmp( "./images/0001_clip.bmp" ); auto const& cm0 = feng::clip( 0.1, 0.9 )( m ); - cm0.save_as_bmp( "./images/0002_clip.bmp" ); + (void)cm0.save_as_bmp( "./images/0002_clip.bmp" ); auto const& cm1 = feng::clip( 0.4, 0.6 )( m ); - cm1.save_as_bmp( "./images/0003_clip.bmp" ); + (void)cm1.save_as_bmp( "./images/0003_clip.bmp" ); } diff --git a/examples/cases/0032_empty.hpp b/examples/cases/0032_empty.hpp index 819da96..9cbe8a1 100644 --- a/examples/cases/0032_empty.hpp +++ b/examples/cases/0032_empty.hpp @@ -1,5 +1,5 @@ void _0000_empty() { auto const& m = feng::empty( 128, 128 ); - m.save_as_bmp( "./images/0000_empty.bmp" ); + (void)m.save_as_bmp( "./images/0000_empty.bmp" ); } diff --git a/examples/cases/0034_astype.hpp b/examples/cases/0034_astype.hpp index 77d0889..eb8690a 100644 --- a/examples/cases/0034_astype.hpp +++ b/examples/cases/0034_astype.hpp @@ -3,8 +3,8 @@ void _0000_astype() feng::matrix m{ 64, 256 }; std::generate( m.begin(), m.end(), [](){ double init = 0.0; return [init]() mutable { init += 0.1; return std::sin(init); }; }() ); m = m * 2.0; - m.save_as_bmp( "./images/0000_astype.bmp" ); + (void)m.save_as_bmp( "./images/0000_astype.bmp" ); auto const& mm = m.astype(); - mm.save_as_bmp( "./images/0001_astype.bmp" ); + (void)mm.save_as_bmp( "./images/0001_astype.bmp" ); } diff --git a/matrix.hpp b/matrix.hpp index 603a33c..534bf2b 100644 --- a/matrix.hpp +++ b/matrix.hpp @@ -1,16 +1,22 @@ #ifndef FENG_MATRIX_HPP_INCLUDED_ #define FENG_MATRIX_HPP_INCLUDED_ -static_assert( __cplusplus >= 201709L, "C++20 is a must for this library, please update your compiler, or enable corresponding option such as -std=c++2a" ); +static_assert( __cplusplus >= 202002L, "C++20 is a must for this library, please update your compiler, or enable corresponding option such as -std=c++2a" ); #include #include +#include +#include +#include +#include #include +#include #include #include #include #include #include +#include #include #include #include @@ -32,23 +38,43 @@ static_assert( __cplusplus >= 201709L, "C++20 is a must for this library, please #include #include #include +#include #include #include #include #include +#include #include #include +#include +#if defined( __cpp_lib_mdspan ) +#include +#endif +#if defined( __cpp_lib_expected ) +#include +#endif + +// S9-R3 (D-033): configuration macros. FENG_MATRIX_PARALLEL runs elementwise loops in parallel, FENG_MATRIX_OPENCV +// enables the cv::Mat interface, FENG_MATRIX_CHECKED_ITERATORS (D-020) bounds-checks view iterators. The old names +// PARALLEL and OPENCV are deprecated and still accepted: each maps to its FENG_MATRIX_ name here, and the library +// below tests only the new names. +#if defined( PARALLEL ) && !defined( FENG_MATRIX_PARALLEL ) +#define FENG_MATRIX_PARALLEL +#endif +#if defined( OPENCV ) && !defined( FENG_MATRIX_OPENCV ) +#define FENG_MATRIX_OPENCV +#endif -#ifdef OPENCV // Interfacing cv::Mat. Enable this feature by passing `-DOPENCV` option to g++. +#ifdef FENG_MATRIX_OPENCV // Interfacing cv::Mat. Enable this feature by passing `-DFENG_MATRIX_OPENCV` to the compiler. #include #include -#endif//OPENCV +#endif//FENG_MATRIX_OPENCV namespace feng { constexpr std::uint_least64_t matrix_version = 20240314ULL; - #ifdef PARALLEL + #ifdef FENG_MATRIX_PARALLEL constexpr std::uint_least64_t parallel_mode = 1; #else constexpr std::uint_least64_t parallel_mode = 0; @@ -60,39 +86,176 @@ namespace feng constexpr std::uint_least64_t debug_mode = 1; #endif - #ifdef OPENCV + #ifdef FENG_MATRIX_OPENCV constexpr std::uint_least64_t enable_cv_mat = 1; #else constexpr std::uint_least64_t enable_cv_mat = 0; #endif + // S9-R3 (D-033): the configuration (parallel, checked iterators, OpenCV) names a namespace holding one inline + // variable, kept even in a TU that uses nothing from the library. On ELF, unless FENG_MATRIX_NO_CONFIG_CHECK, an + // extern "C" alias to that variable sits in the variable's COMDAT group: TUs with the same configuration fold + // into one group, TUs with different ones each keep theirs and the link fails with a multiple definition of + // feng_matrix_configuration_mismatch_between_translation_units. NDEBUG is not part of the configuration. + #ifdef FENG_MATRIX_PARALLEL + #define FENG_MATRIX_CONFIG_P_ 1 + #else + #define FENG_MATRIX_CONFIG_P_ 0 + #endif + #ifdef FENG_MATRIX_CHECKED_ITERATORS + #define FENG_MATRIX_CONFIG_C_ 1 + #else + #define FENG_MATRIX_CONFIG_C_ 0 + #endif + #ifdef FENG_MATRIX_OPENCV + #define FENG_MATRIX_CONFIG_O_ 1 + #else + #define FENG_MATRIX_CONFIG_O_ 0 + #endif + // matrix_config_p

_c_o has 22 characters, hence the Itanium name _ZN4feng22matrix_config_p

_c_o3tagE. + #define FENG_MATRIX_CONFIG_NS2_( P, C, O ) matrix_config_p ## P ## _c ## C ## _o ## O + #define FENG_MATRIX_CONFIG_NS_( P, C, O ) FENG_MATRIX_CONFIG_NS2_( P, C, O ) + #define FENG_MATRIX_CONFIG_MANGLED2_( P, C, O ) _ZN4feng22matrix_config_p ## P ## _c ## C ## _o ## O ## 3tagE + #define FENG_MATRIX_CONFIG_MANGLED_( P, C, O ) FENG_MATRIX_CONFIG_MANGLED2_( P, C, O ) + #define FENG_MATRIX_CONFIG_STR2_( X ) #X + #define FENG_MATRIX_CONFIG_STR_( X ) FENG_MATRIX_CONFIG_STR2_( X ) + + namespace FENG_MATRIX_CONFIG_NS_( FENG_MATRIX_CONFIG_P_, FENG_MATRIX_CONFIG_C_, FENG_MATRIX_CONFIG_O_ ) + { + [[gnu::used]] inline char tag = 0; + } +}//namespace feng + +#if defined( __ELF__ ) && !defined( FENG_MATRIX_NO_CONFIG_CHECK ) +extern "C" char feng_matrix_configuration_mismatch_between_translation_units + __attribute__(( alias( FENG_MATRIX_CONFIG_STR_( FENG_MATRIX_CONFIG_MANGLED_( FENG_MATRIX_CONFIG_P_, FENG_MATRIX_CONFIG_C_, FENG_MATRIX_CONFIG_O_ ) ) ) )); +#endif + +#undef FENG_MATRIX_CONFIG_STR_ +#undef FENG_MATRIX_CONFIG_STR2_ +#undef FENG_MATRIX_CONFIG_MANGLED_ +#undef FENG_MATRIX_CONFIG_MANGLED2_ +#undef FENG_MATRIX_CONFIG_NS_ +#undef FENG_MATRIX_CONFIG_NS2_ +#undef FENG_MATRIX_CONFIG_O_ +#undef FENG_MATRIX_CONFIG_C_ +#undef FENG_MATRIX_CONFIG_P_ + +namespace feng +{ + namespace matrix_private - { // for macro `better_assert` + { + // S2-R1 (D-003, D-011): the one contract-violation path. Formats a single line, writes it to stderr with + // one fwrite, flushes and aborts; used in every build mode, never throws. template< typename... Args > - void print_assertion(std::ostream& out, Args&&... args) + [[noreturn]] void contract_violation( char const* expr, char const* file, int line, Args const&... detail ) noexcept { - if constexpr( debug_mode ) + std::ostringstream os; + os.precision( 20 ); + os << "feng::matrix: contract violation: " << expr << " (" << file << ":" << line << ")"; + if constexpr( sizeof...( Args ) > 0 ) { - out.precision( 20 ); - (out << ... << args) << std::endl; - abort(); + os << ' '; + ( os << ... << detail ); } + os << '\n'; + std::string const msg = os.str(); + std::fwrite( msg.data(), 1, msg.size(), stderr ); + std::fflush( stderr ); + std::abort(); } } + #ifdef FENG_MATRIX_EXPECTS + #undef FENG_MATRIX_EXPECTS + #endif + // FENG_MATRIX_EXPECTS( expr, detail... ): when `expr` is false, report it and abort, in debug and NDEBUG builds. + #define FENG_MATRIX_EXPECTS(EXPRESSION, ... ) ((EXPRESSION) ? (void)0 : ::feng::matrix_private::contract_violation( #EXPRESSION, __FILE__, __LINE__ __VA_OPT__(,) __VA_ARGS__ )) + #ifdef better_assert #undef better_assert #endif // - // enhancing 'assert' macro, usage: + // legacy name (D-002), forwards to FENG_MATRIX_EXPECTS, usage: // - // int a; - // ... - // better_assert( a > 0 ); //same as 'assert' - // better_assert( a > 0, "a is expected larger than 0, but now a = " a ); //with more info dumped to std::cerr + // better_assert( a > 0 ); + // better_assert( a > 0, "a is expected larger than 0, but now a = ", a ); // detail appended to the message // - #define better_assert(EXPRESSION, ... ) ((EXPRESSION) ? (void)0 : matrix_private::print_assertion(std::cerr, "[Assertion Failure]: '", #EXPRESSION, "' in File: ", __FILE__, " in Line: ", __LINE__ __VA_OPT__(,) __VA_ARGS__)) + #define better_assert(EXPRESSION, ... ) FENG_MATRIX_EXPECTS( EXPRESSION __VA_OPT__(,) __VA_ARGS__ ) + + namespace matrix_private + { + // S2-R2 (D-012): element count for a rows x cols allocation; aborts on rows*cols overflow, on a byte count + // that overflows or exceeds PTRDIFF_MAX (F02), or above allocator_traits::max_size, before anything + // is allocated. + template< typename Alloc > + std::size_t checked_count( Alloc const& alloc, std::size_t r, std::size_t c ) noexcept + { + typedef typename std::allocator_traits< Alloc >::value_type value_type; + constexpr std::size_t size_max = std::numeric_limits< std::size_t >::max(); + FENG_MATRIX_EXPECTS( r == 0 || c <= size_max / r, "matrix size: rows*cols overflows, rows = ", r, ", cols = ", c ); + std::size_t const n = r * c; + FENG_MATRIX_EXPECTS( n <= size_max / sizeof( value_type ), "matrix size: byte count overflows, rows = ", r, ", cols = ", c ); + FENG_MATRIX_EXPECTS( n * sizeof( value_type ) <= static_cast< std::size_t >( PTRDIFF_MAX ), "matrix size: byte count above PTRDIFF_MAX, rows = ", r, ", cols = ", c ); + FENG_MATRIX_EXPECTS( n <= static_cast< std::size_t >( std::allocator_traits< Alloc >::max_size( alloc ) ), "matrix size: rows*cols above the allocator's max_size, rows = ", r, ", cols = ", c ); + return n; + } + + // S3-R5: checks a rows x cols allocation against alloc, then returns alloc for the owning container. + template< typename Alloc > + Alloc checked_allocator( Alloc alloc, std::size_t r, std::size_t c ) noexcept + { + checked_count( alloc, r, c ); + return alloc; + } + + // S3-R1: the one access point to matrix's private storage and extents, used by the CRTP bases. + struct storage_access + { + template< typename M > + static auto& storage( M& m ) noexcept { return m.storage_; } + template< typename M > + static auto& rows( M& m ) noexcept { return m.row_; } + template< typename M > + static auto& cols( M& m ) noexcept { return m.col_; } + }; + + // S2-R2: a signed or unsigned dimension is usable when it is non-negative and fits std::size_t. + template< std::integral I > + constexpr bool dimension_fits( I v ) noexcept + { + if constexpr( std::is_signed_v< I > ) + if ( v < 0 ) return false; + return static_cast< std::uintmax_t >( v ) <= std::numeric_limits< std::size_t >::max(); + } + + // S2-R2: clone bounds, checked before the source is read or anything is allocated. + inline void check_clone_lists( std::size_t rows_listed, std::size_t cols_listed ) noexcept + { + FENG_MATRIX_EXPECTS( rows_listed == 2, "matrix clone: the row range needs exactly two values, got ", rows_listed ); + FENG_MATRIX_EXPECTS( cols_listed == 2, "matrix clone: the column range needs exactly two values, got ", cols_listed ); + } + inline void check_clone_range( std::size_t r0, std::size_t r1, std::size_t c0, std::size_t c1, std::size_t rows, std::size_t cols ) noexcept + { + FENG_MATRIX_EXPECTS( r0 < r1 && r1 <= rows, "matrix clone: row range [", r0, ", ", r1, ") outside a source with ", rows, " rows" ); + FENG_MATRIX_EXPECTS( c0 < c1 && c1 <= cols, "matrix clone: column range [", c0, ", ", c1, ") outside a source with ", cols, " columns" ); + } + + // S2-R2: the one two-index check behind at( r, c ) and m( r, c ); aborts with `index` when out of range. + inline void check_index( std::size_t r, std::size_t c, std::size_t rows, std::size_t cols ) noexcept + { + FENG_MATRIX_EXPECTS( r < rows && "Row index out of boundary!", "matrix index: (", r, ", ", c, ") on a ", rows, "x", cols, " matrix" ); + FENG_MATRIX_EXPECTS( c < cols && "Column index out of boundary!", "matrix index: (", r, ", ", c, ") on a ", rows, "x", cols, " matrix" ); + } + + // S3-R3 (D-018): true for std::complex specializations. + template< typename T > + inline constexpr bool is_std_complex_v = false; + template< typename T > + inline constexpr bool is_std_complex_v< std::complex< T > > = true; + } // // begin of concept allocators @@ -133,9 +296,26 @@ namespace feng // - template < typename Type, Allocator Alloc > + // S3-R3 (PR-5, D-012): element types of matrix and matrix_view; bool is excluded (no contiguous T* storage). + template < typename T > + concept matrix_element = std::is_object_v< T > && !std::is_const_v< T > && !std::is_volatile_v< T > && + !std::is_same_v< T, bool > && + // D-018: the size constructors default-construct elements. std::complex is carved out + // because libstdc++ declares complex() without noexcept, though it only value-initializes two arithmetic members. + ( std::is_nothrow_default_constructible_v< T > || matrix_private::is_std_complex_v< T > ) && + std::is_nothrow_copy_constructible_v< T > && std::is_nothrow_move_constructible_v< T > && + std::is_nothrow_copy_assignable_v< T > && std::is_nothrow_move_assignable_v< T > && + std::is_nothrow_destructible_v< T >; + + template < matrix_element Type, Allocator Alloc > struct matrix; + template < matrix_element Type, Allocator Alloc > + struct matrix_view; + + template < matrix_element Type, Allocator Alloc > + struct mutable_matrix_view; + namespace matrix_details { @@ -147,7 +327,7 @@ namespace feng typedef Integer_Type value_type; typedef value_type& reference; typedef value_type* pointer; - typedef const reference const_reference; + typedef reference const_reference; // same type as before: const on a reference is ignored typedef std::random_access_iterator_tag iterator_category; typedef std::ptrdiff_t difference_type; typedef integer_iterator self_type; @@ -188,7 +368,7 @@ namespace feng return *this; } - self_type const operator++(int) noexcept + self_type operator++(int) noexcept { self_type ans{*this}; ++(*this); @@ -200,12 +380,12 @@ namespace feng return lhs.value_ - rhs.value_; } - friend self_type const operator + ( self_type const& lhs, difference_type const& rhs ) noexcept + friend self_type operator + ( self_type const& lhs, difference_type const& rhs ) noexcept { return self_type{ lhs.value_ + rhs }; } - friend self_type const operator + ( difference_type const& lhs, self_type const& rhs ) noexcept + friend self_type operator + ( difference_type const& lhs, self_type const& rhs ) noexcept { return rhs + lhs; } @@ -243,93 +423,162 @@ namespace feng template< std::weakly_incrementable W > - constexpr auto range( W val_begin, W val_end ) + constexpr auto range( W val_begin, W val_end ) noexcept { return std::ranges::iota_view( val_begin, val_end ); } template< std::weakly_incrementable W > - constexpr auto range( W val_end ) + constexpr auto range( W val_end ) noexcept { return range( W{0}, val_end ); } template - void repeat( Function function, Integer_Type n ) + void repeat( Function function, Integer_Type n ) noexcept { while ( n-- ) function(); } + // S6-R1 (F11, F12): the one partition behind parallel and both reductions. For n = last - first > 0 the + // effective worker count is w = clamp( workers, 1, n ) (0 means serial); an empty or reversed range has + // w = 0. Chunk k holds n / w + ( k < n % w ) indices starting at first + k * ( n / w ) + min( k, n % w ), + // so the w chunks are contiguous, disjoint, non-empty and cover [first, last) in order. + template< std::integral Integer_Type > + constexpr std::size_t effective_workers( Integer_Type first, Integer_Type last, std::size_t workers ) noexcept + { + if ( !( first < last ) ) return 0; + std::size_t const n = static_cast( last - first ); + return std::clamp( workers, std::size_t{1}, n ); + } + + // [begin, end) of chunk k; an empty pair for an empty range or k >= effective_workers( first, last, workers ). + template< std::integral Integer_Type > + constexpr std::pair chunk_bounds( Integer_Type first, Integer_Type last, std::size_t workers, std::size_t k ) noexcept + { + std::size_t const w = effective_workers( first, last, workers ); + if ( k >= w ) return { first < last ? last : first, first < last ? last : first }; + std::size_t const n = static_cast( last - first ); + std::size_t const q = n / w; + std::size_t const r = n % w; + std::size_t const offset = k * q + std::min( k, r ); + Integer_Type const b = static_cast( first + static_cast( offset ) ); + Integer_Type const e = static_cast( b + static_cast( q + ( k < r ? 1 : 0 ) ) ); + return { b, e }; + } + + // Runs func( i ) once for every i in [first, last): chunk 0 on the calling thread, chunks 1..w-1 on + // std::jthreads that are all joined before return. The injected count is honoured in every build. A + // thread-creation failure terminates (noexcept, D-012); func must not throw. template< typename Function, std::integral Integer_Type > - void parallel( Function const& func, Integer_Type dim_first, Integer_Type dim_last, unsigned long threshold = 1024 ) // 1d parallel + void parallel_workers( Function const& func, Integer_Type first, Integer_Type last, std::size_t workers ) noexcept { - if constexpr( parallel_mode == 0 ) + std::size_t const w = effective_workers( first, last, workers ); + if ( w == 0 ) return; + auto const run_chunk = [&func, first, last, w]( std::size_t k ) noexcept + { + auto const [b, e] = chunk_bounds( first, last, w, k ); + for ( Integer_Type i = b; i != e; ++i ) + func( i ); + }; + if ( w == 1 ) { - for ( auto a : range( dim_first, dim_last ) ) - func( a ); + run_chunk( 0 ); return; } - else // <- this is constexpr-if, `else` is a must - { - unsigned int const total_cores = std::thread::hardware_concurrency(); - - // case of non-parallel or small jobs - if ( (total_cores <= 1) || ((dim_last - dim_first) <= threshold) ) - { - for ( auto a : range( dim_first, dim_last ) ) - func( a ); - return; - } - - // case of small job numbers - std::vector threads; - if ( dim_last - dim_first <= total_cores ) - { - for ( auto index = dim_first; index != dim_last; ++index ) - threads.emplace_back( std::thread{[&func, index](){ func( index ); }} ); - for ( auto& th : threads ) - th.join(); - return; - } + std::vector threads; + threads.reserve( w - 1 ); + for ( std::size_t k = 1; k != w; ++k ) + threads.emplace_back( run_chunk, k ); + run_chunk( 0 ); + for ( auto& th : threads ) + th.join(); + } - // case of more jobs than CPU cores - auto const& job_slice = [&func]( Integer_Type a, Integer_Type b ) - { - if ( a >= b ) return; - while ( a != b ) - func(a++); - }; + // 1 in serial builds or for n <= threshold, else hardware_concurrency (1 if it reports 0). + inline std::size_t default_workers( std::size_t n, std::size_t threshold ) noexcept + { + if constexpr( parallel_mode == 0 ) + { + (void)n; (void)threshold; + return 1; + } + else + { + if ( n <= threshold ) return 1; + unsigned int const cores = std::thread::hardware_concurrency(); + return cores == 0 ? std::size_t{1} : static_cast( cores ); + } + } - threads.reserve( total_cores-1 ); - std::uint_least64_t tasks_per_thread = ( dim_last - dim_first + total_cores - 1 ) / total_cores; + // S10-R3 (D-008, kept optimization par-thresholds): work-based default worker counts. In parallel builds a + // call doing `work` units runs on min( hardware_concurrency, work / grain ) workers, at least 1, so a call + // with less than two grains of work stays on the calling thread; serial builds always return 1. Explicit + // worker counts (parallel_workers, reduce_range, the trailing reduce argument) are not affected. + // Grains, measured on the S10 host (Ryzen 9 7900X3D, 24 threads, g++ -O2 -pthread; lane numbers in + // bench/results.md "par-thresholds"): starting and joining one std::jthread costs about 19 us, so a worker + // pays off only once it takes over more than that much work. Per-call medians, 1 worker against the best + // count, warm inputs: sqrt via for_each 2^15 doubles 54 us (1) vs 58 us (2), 2^17 211 us vs 111 us (4); sum + // 2^16 38 us (1) vs 49 us (2), 2^18 151 us vs 90 us (3); GEMM 64^3 (2^18 multiply-adds) 51 us (1) vs 58 us + // (3), 96^3 171 us vs 105 us (4), 1024x1024 by 1024x1 171 us vs 105 us (4). The elementwise grain comes from + // the bench workload par/add/1000x1000 (a + b into a fresh result): grain 2^16 (15 workers) 0.75-0.89 ms, + // 2^17 (7) 0.84-0.90 ms, 2^18 (3) 0.98-1.06 ms, 24 workers (pre-S10) 0.98-1.06 ms; for map/1000x1000 a + // callback grain of 2^16 (15 workers) beat 2^13-2^15 (24). + inline constexpr std::size_t elementwise_grain = std::size_t{ 1 } << 16; // elements: add, minus, negate, copy, clone + inline constexpr std::size_t callback_grain = std::size_t{ 1 } << 16; // elements: for_each, apply, colormap, pooling + inline constexpr std::size_t reduce_grain = std::size_t{ 1 } << 16; // elements folded: reduce, sum, min, max + inline constexpr std::size_t gemm_grain = std::size_t{ 1 } << 18; // multiply-adds M*K*N; workers <= M rows + + inline std::size_t work_workers( std::size_t work, std::size_t grain ) noexcept + { + if constexpr( parallel_mode == 0 ) + { + (void)work; (void)grain; + return 1; + } + else + { + std::size_t const want = work / std::max( grain, std::size_t{ 1 } ); + if ( want <= 1 ) return 1; + unsigned int const cores = std::thread::hardware_concurrency(); + std::size_t const cap = cores == 0 ? std::size_t{ 1 } : static_cast( cores ); + return std::min( want, cap ); + } + } - for ( auto index : range( total_cores-1 ) ) - { - Integer_Type first = tasks_per_thread * index + dim_first; - first = std::min( first, dim_last ); - Integer_Type last = first + tasks_per_thread; - last = std::min( last, dim_last ); - threads.emplace_back( std::thread{ job_slice, first, last } ); - } + // a * b, saturated at SIZE_MAX (work amounts only). + constexpr std::size_t saturating_work( std::size_t a, std::size_t b ) noexcept + { + if ( a != 0 && b > std::numeric_limits::max() / a ) return std::numeric_limits::max(); + return a * b; + } - job_slice( tasks_per_thread*(total_cores-1), dim_last ); + // Runs func( i ) for i in [first, last) on work_workers( ( last - first ) * work_per_index, grain ) workers. + template< typename Function, std::integral Integer_Type > + void parallel_work( Function const& func, Integer_Type first, Integer_Type last, std::size_t work_per_index, std::size_t grain ) noexcept + { + std::size_t const n = first < last ? static_cast( last - first ) : 0; + parallel_workers( func, first, last, work_workers( saturating_work( n, work_per_index ), grain ) ); + } - for ( auto& th : threads ) - th.join(); - } + template< typename Function, std::integral Integer_Type > + void parallel( Function const& func, Integer_Type dim_first, Integer_Type dim_last, unsigned long threshold = 1024 ) noexcept // 1d parallel + { + std::size_t const n = dim_first < dim_last ? static_cast( dim_last - dim_first ) : 0; + parallel_workers( func, dim_first, dim_last, default_workers( n, threshold ) ); } template< typename Function, typename Integer_Type > - void parallel( Function const& func, Integer_Type dim_last ) + void parallel( Function const& func, Integer_Type dim_last ) noexcept { parallel( func, Integer_Type{0}, dim_last ); } namespace bmp_details { - inline std::vector const generate_bmp_header( std::uint_least64_t const the_row, std::uint_least64_t const the_col ) + inline std::vector generate_bmp_header( std::uint_least64_t const the_row, std::uint_least64_t const the_col ) noexcept { auto const& ul_to_byte = []( std::uint_least64_t val ) { return static_cast< std::uint8_t >( val & 0xffUL ); }; std::uint8_t file[14] = { 0x42, 0x4D, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0 }; @@ -359,19 +608,19 @@ namespace feng return header; } - inline std::uint8_t operator "" _u8(unsigned long long value) + inline std::uint8_t operator""_u8(unsigned long long value) noexcept { return static_cast(value); } static std::function(double)> make_transformation_function( std::tuple const& color_1, double value_1, - std::tuple const& color_2, double value_2 ) + std::tuple const& color_2, double value_2 ) noexcept { auto const [r1, g1, b1] = color_1; auto const [r2, g2, b2] = color_2; - return [r1=r1, g1=g1, b1=b1, r2=r2, g2=g2, b2=b2, value_1, value_2]( double x ) + return [r1=r1, g1=g1, b1=b1, r2=r2, g2=g2, b2=b2, value_1, value_2]( double x ) noexcept { double dr = static_cast(r1) - static_cast(r2); double dg = static_cast(g1) - static_cast(g2); @@ -392,13 +641,13 @@ namespace feng //static std::tuple static std::function(double)> - make_color_map( std::vector const& values, std::vector> const& colors ) + make_color_map( std::vector const& values, std::vector> const& colors ) noexcept { better_assert( (values.size() == colors.size()), "make_color_map::length of values and colors not match!" ); better_assert( std::abs(*(values.begin())) < 1.0e-10 && "make_color_map::value should start from 0!" ); better_assert( std::abs(*(values.rbegin())-1.0) < 1.0e-10 && "make_color_map::value should end at 1!" ); - return [=]( double x ) + return [=]( double x ) noexcept { for ( auto index : matrix_details::range( values.size() - 1 ) ) if ( x <= values[index+1] ) @@ -408,7 +657,7 @@ namespace feng }; } static std::function(double)> - make_color_map( std::initializer_list const& values, std::initializer_list> const& colors ) + make_color_map( std::initializer_list const& values, std::initializer_list> const& colors ) noexcept { return make_color_map( std::vector{values}, std::vector>{colors} ); } @@ -1065,7 +1314,7 @@ namespace feng *start_pos++ = channel_r[row_index][c]; } }; - matrix_details::parallel( fill_row, 0UL, the_row, 0UL ); + matrix_details::parallel_work( fill_row, 0UL, the_row, saturating_work( the_col, 3 ), elementwise_grain ); return {encoding}; } @@ -1100,15 +1349,15 @@ namespace feng }; template < typename Function, typename InputIterator1, typename... InputIteratorn > - Function _for_each_n( Function f, std::uint_least64_t n, InputIterator1 begin1, InputIteratorn... beginn ) + Function _for_each_n( Function f, std::uint_least64_t n, InputIterator1 begin1, InputIteratorn... beginn ) noexcept { auto const& func = [&]( std::uint_least64_t idx ) { f( *(begin1+idx), *(beginn+idx)... ); }; - parallel( func, 0UL, n ); + parallel_work( func, std::uint_least64_t{ 0 }, n, 1, callback_grain ); return f; } template < typename Function, typename InputIterator1, typename... InputIteratorn > - Function _for_each( Function f, InputIterator1 begin1, InputIterator1 end1, InputIteratorn... beginn ) + Function _for_each( Function f, InputIterator1 begin1, InputIterator1 end1, InputIteratorn... beginn ) noexcept { return _for_each_n( f, std::distance( begin1, end1 ), begin1, beginn... ); } @@ -1121,13 +1370,13 @@ namespace feng typedef typename extract_type_backward< 1, Types_N... >::result_type return_type; template < typename Predict, typename... Types > - Predict impl( Predict p, dummy, Types... types ) const + Predict impl( Predict p, dummy, Types... types ) const noexcept { return _for_each( p, types... ); } template < typename S, typename... Types > - return_type impl( S s, Types... types ) const + return_type impl( S s, Types... types ) const noexcept { return impl( types..., s ); } @@ -1136,180 +1385,977 @@ namespace feng template < typename... Types > typename for_each_impl_private::extract_type_backward< 1, Types... >::result_type - for_each( Types... types ) + for_each( Types... types ) noexcept { static_assert( sizeof...( types ) > 2, "f::for_each requires at least 3 arguments" ); return for_each_impl_private::for_each_impl_with_dummy< Types... >().impl( types..., for_each_impl_private::dummy() ); } + // S6-R1 (F11, F12): the one reduction. at( i ) reads element i of [0, n). Each chunk of the shared partition + // folds from its own first element; the result is init folded with the chunk results in order, so init + // appears exactly once and any associative func gives std::accumulate's result. Only at( i ) for i < n is + // read, and no count is ever divided by. + template< typename Result, typename At, typename Function > + Result reduce_range( At const& at, std::size_t n, Result init, Function const& func, std::size_t workers ) noexcept + { + std::size_t const w = effective_workers( std::size_t{0}, n, workers ); + Result ans = std::move( init ); + if ( w <= 1 ) + { + for ( std::size_t i = 0; i != n; ++i ) + ans = func( ans, at( i ) ); + return ans; + } + std::vector cache( w ); + auto const chunk_func = [&]( std::size_t k ) noexcept + { + auto const [b, e] = chunk_bounds( std::size_t{0}, n, w, k ); + Result acc = at( b ); + for ( std::size_t i = b + 1; i != e; ++i ) + acc = func( acc, at( i ) ); + cache[k] = std::move( acc ); + }; + parallel_workers( chunk_func, std::size_t{0}, w, w ); + for ( std::size_t k = 0; k != w; ++k ) + ans = func( ans, cache[k] ); + return ans; + } + + template< typename Iterator > + std::size_t iterator_range_size( Iterator begin, Iterator end ) noexcept + { + auto const d = std::distance( begin, end ); + return d > 0 ? static_cast( d ) : std::size_t{0}; + } + + // Iterator reduce over [begin, end) with an injected worker count; the overload below defaults it to + // work_workers( n, reduce_grain ) (S10-R3; before S10 default_workers( n, 32 )). template typename std::invoke_result::value_type, typename std::iterator_traits::value_type>::type - reduce( Iterator begin, Iterator end, typename std::iterator_traits::value_type init, Function const& func ) + reduce( Iterator begin, Iterator end, typename std::iterator_traits::value_type init, Function const& func, std::size_t workers ) noexcept { typedef typename std::iterator_traits::value_type value_type; typedef typename std::invoke_result::type result_type; + std::size_t const n = iterator_range_size( begin, end ); + auto const at = [begin]( std::size_t i ) noexcept -> decltype( auto ) { return *std::next( begin, static_cast::difference_type>( i ) ); }; + return reduce_range( at, n, static_cast( std::move( init ) ), func, workers ); + } - unsigned int const total_cores = std::thread::hardware_concurrency(); - unsigned long const total_elements = std::distance( begin, end ); - - // case of small size, reduce inplace - if (total_elements <= total_cores) - return std::accumulate( begin, end, init, func ); - - // parallel reduce to cache - //std::vector cache{ static_cast(total_cores), static_cast(init) };// of size `total_cores`, of same value `init` - std::vector cache; - cache.resize( total_cores ); - auto const block_size = total_elements / total_cores; - auto const& thread_reducer = [&]( unsigned long idx ) - { - auto const block_begin = idx * block_size; - auto const block_end = idx + 1 != total_cores ? block_begin + block_size : total_elements; - result_type temp = *(begin+block_begin); - for ( auto id : range( block_begin + 1, block_end ) ) - temp = func( temp, *(begin+id) ); - cache[idx] = temp; - }; - parallel( thread_reducer, static_cast(0), total_cores, 0UL ); - - // inplace reduce from cache - return std::accumulate( cache.begin(), cache.end(), init, func ); + template + typename std::invoke_result::value_type, typename std::iterator_traits::value_type>::type + reduce( Iterator begin, Iterator end, typename std::iterator_traits::value_type init, Function const& func ) noexcept + { + std::size_t const n = matrix_details::iterator_range_size( begin, end ); + return reduce( begin, end, std::move( init ), func, work_workers( n, reduce_grain ) ); } - static bool create_directory_if_not_present( std::string const& file_name ) + inline bool create_directory_if_not_present( std::string const& file_name ) noexcept { + // S5-R4 (D-011, D-012): error_code overloads only; false on failure, never throws + std::error_code ec; std::filesystem::path file_path{ file_name }; auto directory = file_path.parent_path(); if ( directory.empty() ) return true; - if ( ! std::filesystem::exists(directory) ) return std::filesystem::create_directories(directory); - return true; + if ( std::filesystem::is_directory( directory, ec ) ) return true; + ec.clear(); + if ( std::filesystem::exists( directory, ec ) || ec ) return false; // a non-directory, or not checkable + std::filesystem::create_directories( directory, ec ); + return !ec && std::filesystem::is_directory( directory, ec ); } - }//namespace matrix_details - - // for column, diagonal and anti-diagonal iteration - template < typename Iterator_Type > - struct stride_iterator - { - typedef stride_iterator self_type; - typedef typename std::iterator_traits< Iterator_Type >::value_type value_type; - typedef typename std::iterator_traits< Iterator_Type >::reference reference; - typedef typename std::iterator_traits< Iterator_Type >::difference_type difference_type; - typedef typename std::iterator_traits< Iterator_Type >::pointer pointer; - typedef typename std::iterator_traits< Iterator_Type >::iterator_category iterator_category; - - Iterator_Type iterator_; - difference_type step_; - - stride_iterator( const Iterator_Type& it, const difference_type& dt ) noexcept : iterator_( it ) , step_( dt ) { } - - stride_iterator() noexcept : iterator_( 0 ) , step_( 1 ) { } - stride_iterator( const self_type& ) noexcept = default; - stride_iterator( self_type&& ) noexcept = default; - self_type& operator=( const self_type& ) noexcept = default; - self_type& operator=( self_type&& ) noexcept = default; - - self_type& operator++() noexcept + // S5-R4 (D-011): prints the one stderr line of a failed writer, " -- : ", and returns false. + inline bool write_failed( char const* who, char const* reason, std::string const& path ) noexcept { - iterator_ += step_; - return *this; + std::cerr << who << " -- " << reason << ": " << path << "\n"; + return false; } - const self_type operator++( int ) noexcept + + // S5-R4: flushes and closes a written stream; true only if every write, the flush and the close succeeded. + inline bool checked_close( std::ofstream& ofs ) noexcept { - self_type ans( *this ); - operator++(); - return ans; + ofs.flush(); + ofs.close(); + return !ofs.fail(); } - self_type& operator+=( const difference_type dt ) noexcept + + // S5-R4: the common tail of a stream writer: a directory, open, write or close failure prints one line naming + // `who` and `path` and returns false. `body` writes into the open stream. + template < typename Body > + bool write_stream( char const* who, std::string const& path, std::ios_base::openmode mode, Body&& body ) noexcept { - iterator_ += dt * step_; - return *this; + if ( !create_directory_if_not_present( path ) ) return write_failed( who, "failed to create the parent directory", path ); + std::ofstream ofs( path, mode ); + if ( !ofs ) return write_failed( who, "failed to open file", path ); + body( ofs ); + if ( !ofs ) return write_failed( who, "failed to write file", path ); + if ( !checked_close( ofs ) ) return write_failed( who, "failed to write or close file", path ); + return true; } - friend const self_type operator+( const self_type& lhs, const difference_type rhs ) noexcept + + // S5-R1/S5-R2: overflow-checked size arithmetic for every external-data size computation; true when the + // result fits in `out` (D-012: no throwing, no wrap-around). + template < std::integral T > + constexpr bool checked_mul( T a, T b, T& out ) noexcept { - self_type ans( lhs ); - ans += rhs; - return ans; + return !__builtin_mul_overflow( a, b, &out ); } - friend const self_type operator+( const difference_type lhs, const self_type& rhs ) noexcept + + template < std::integral T > + constexpr bool checked_add( T a, T b, T& out ) noexcept { - return rhs + lhs; + return !__builtin_add_overflow( a, b, &out ); } - self_type& operator--() noexcept + + // S5-R4 (D-011): reads the whole file into `bytes`; on a directory, an open or a read failure prints one stderr + // line " -- : " and returns false, leaving `bytes` unchanged. Never aborts. + inline bool read_file( char const* path, std::vector< std::uint8_t >& bytes, char const* who = "read_file" ) noexcept { - iterator_ -= step_; - return *this; + if ( path == nullptr ) + { + std::cerr << who << " -- no file name given\n"; + return false; + } + std::error_code ec; + if ( std::filesystem::is_directory( path, ec ) ) + { + std::cerr << who << " -- is a directory: " << path << "\n"; + return false; + } + std::ifstream ifs( path, std::ios::binary ); + if ( !ifs ) + { + std::cerr << who << " -- failed to open file: " << path << "\n"; + return false; + } + std::vector< std::uint8_t > buffer; + std::array< char, 65536 > chunk; + while ( ifs ) + { + ifs.read( chunk.data(), static_cast< std::streamsize >( chunk.size() ) ); + std::streamsize const got = ifs.gcount(); + if ( got > 0 ) + buffer.insert( buffer.end(), reinterpret_cast< std::uint8_t const* >( chunk.data() ), + reinterpret_cast< std::uint8_t const* >( chunk.data() ) + got ); + } + if ( ifs.bad() || !ifs.eof() ) + { + std::cerr << who << " -- failed to read file: " << path << "\n"; + return false; + } + bytes.swap( buffer ); + return true; } - const self_type operator--( int ) noexcept + + // S5-R1 (D-021): the NPY dtype code of an element type; kind 0 means the type has no NPY dtype. + template < typename T > + struct npy_dtype + { + static constexpr char kind = std::is_same_v< T, float > && sizeof( T ) == 4 ? 'f' + : std::is_same_v< T, double > && sizeof( T ) == 8 ? 'f' + : std::is_same_v< T, std::complex< float > > && sizeof( T ) == 8 ? 'c' + : std::is_same_v< T, std::complex< double > > && sizeof( T ) == 16 ? 'c' + : std::is_integral_v< T > && !std::is_same_v< T, bool > && std::is_signed_v< T > + && ( sizeof( T ) == 1 || sizeof( T ) == 2 || sizeof( T ) == 4 || sizeof( T ) == 8 ) ? 'i' + : std::is_integral_v< T > && !std::is_same_v< T, bool > && std::is_unsigned_v< T > + && ( sizeof( T ) == 1 || sizeof( T ) == 2 || sizeof( T ) == 4 || sizeof( T ) == 8 ) ? 'u' + : '\0'; + static constexpr std::size_t component = kind == 'c' ? sizeof( T ) / 2 : sizeof( T ); // bytes swapped as one unit + }; + + // S5-R1: a cursor over the NPY header text; every read checks the bound first. + struct npy_header_cursor { - self_type ans( *this ); - operator--(); - return ans; - } - self_type& operator-=( const difference_type dt ) noexcept + char const* p; + char const* end; + + bool at_end() const noexcept { return p == end; } + char peek() const noexcept { return p == end ? '\0' : *p; } + void skip_ws() noexcept + { + while ( p != end && ( *p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' ) ) ++p; + } + bool eat( char c ) noexcept + { + if ( p == end || *p != c ) return false; + ++p; + return true; + } + // A Python string literal in ' or " quotes without escapes; [b, e) receives its contents. + bool string_literal( char const*& b, char const*& e ) noexcept + { + if ( p == end || ( *p != '\'' && *p != '"' ) ) return false; + char const quote = *p++; + b = p; + while ( p != end && *p != quote ) + { + if ( *p == '\\' || *p == '\n' ) return false; + ++p; + } + if ( p == end ) return false; + e = p++; + return true; + } + bool word( char const* w ) noexcept + { + std::size_t const n = std::strlen( w ); + if ( static_cast< std::size_t >( end - p ) < n || std::memcmp( p, w, n ) != 0 ) return false; + char const* const after = p + n; + if ( after != end && ( std::isalnum( static_cast< unsigned char >( *after ) ) || *after == '_' ) ) return false; + p = after; + return true; + } + }; + + // S5-R1 (F07, D-021): parses a complete NPY v1.0/2.0/3.0 file image of `size` bytes into `out`, a matrix< T, A >. + // Validates the magic, version, header length, the header dict, the dtype (exact match to T), the order and + // the shape before any allocation; the element count times sizeof(T) must equal the payload length exactly. + // Builds the result in a temporary with out's allocator and moves it into `out` only on success; on failure + // returns false, leaves `out` unchanged and, when `why` is given, stores a static reason string there. + template < typename T, typename Out > + bool parse_npy( std::uint8_t const* bytes, std::size_t size, Out& out, char const** why = nullptr ) noexcept { - iterator_ -= dt * step_; - return *this; + static_assert( std::is_same_v< typename Out::value_type, T >, "parse_npy: Out must be a matrix of T" ); + auto fail = [why]( char const* reason ) noexcept + { + if ( why ) *why = reason; + return false; + }; + constexpr char kind = npy_dtype< T >::kind; + if constexpr ( kind == '\0' ) + { + (void)bytes; (void)size; (void)out; + return fail( "the element type has no NPY dtype" ); + } + else + { + static_assert( std::is_trivially_copyable_v< T > ); + if ( bytes == nullptr || size < 10 ) return fail( "file too short for the NPY preamble" ); + if ( std::memcmp( bytes, "\x93NUMPY", 6 ) != 0 ) return fail( "bad NPY magic" ); + std::uint8_t const major = bytes[6]; + std::uint8_t const minor = bytes[7]; + if ( ( major != 1 && major != 2 && major != 3 ) || minor != 0 ) return fail( "unsupported NPY version" ); + std::size_t header_begin = 10; + std::size_t header_length = static_cast< std::size_t >( bytes[8] ) | ( static_cast< std::size_t >( bytes[9] ) << 8 ); + if ( major != 1 ) + { + if ( size < 12 ) return fail( "file too short for the NPY header length" ); + header_begin = 12; + header_length |= ( static_cast< std::size_t >( bytes[10] ) << 16 ) | ( static_cast< std::size_t >( bytes[11] ) << 24 ); + } + std::size_t data_offset = 0; + if ( !checked_add( header_begin, header_length, data_offset ) || data_offset > size ) + return fail( "NPY header extends past the end of the file" ); + + // The header is a Python dict literal with exactly the keys descr, fortran_order and shape. + npy_header_cursor cur{ reinterpret_cast< char const* >( bytes ) + header_begin, reinterpret_cast< char const* >( bytes ) + data_offset }; + bool have_descr = false, have_order = false, have_shape = false; + bool fortran = false, foreign = false; + std::size_t dims[2] = { 0, 0 }; + std::size_t rank = 0; + cur.skip_ws(); + if ( !cur.eat( '{' ) ) return fail( "NPY header is not a dict literal" ); + cur.skip_ws(); + while ( !cur.eat( '}' ) ) + { + char const* kb = nullptr; + char const* ke = nullptr; + if ( !cur.string_literal( kb, ke ) ) return fail( "NPY header key is not a string" ); + std::string_view const key{ kb, static_cast< std::size_t >( ke - kb ) }; + cur.skip_ws(); + if ( !cur.eat( ':' ) ) return fail( "NPY header is missing ':'" ); + cur.skip_ws(); + if ( key == "descr" ) + { + if ( have_descr ) return fail( "NPY header repeats 'descr'" ); + have_descr = true; + if ( cur.peek() == '[' ) return fail( "structured NPY dtype is not supported" ); + char const* vb = nullptr; + char const* ve = nullptr; + if ( !cur.string_literal( vb, ve ) ) return fail( "NPY 'descr' is not a plain string" ); + if ( ve - vb < 3 ) return fail( "unsupported NPY dtype" ); + char const order = vb[0]; + if ( order != '<' && order != '>' && order != '|' && order != '=' ) return fail( "bad NPY byte order" ); + if ( vb[1] != kind ) return fail( "NPY dtype kind does not match the element type" ); + if ( vb[2] < '0' || vb[2] > '9' ) return fail( "bad NPY dtype size" ); + if ( vb[2] == '0' ) return fail( "bad NPY dtype size" ); // no zero size, no leading zeros (' 1 ) + { + if ( order == '<' ) foreign = std::endian::native != std::endian::little; + if ( order == '>' ) foreign = std::endian::native != std::endian::big; + } + } + else if ( key == "fortran_order" ) + { + if ( have_order ) return fail( "NPY header repeats 'fortran_order'" ); + have_order = true; + if ( cur.word( "True" ) ) fortran = true; + else if ( cur.word( "False" ) ) fortran = false; + else return fail( "NPY 'fortran_order' is not True or False" ); + } + else if ( key == "shape" ) + { + if ( have_shape ) return fail( "NPY header repeats 'shape'" ); + have_shape = true; + if ( !cur.eat( '(' ) ) return fail( "NPY 'shape' is not a tuple" ); + cur.skip_ws(); + bool comma = false; + while ( !cur.eat( ')' ) ) + { + char const c0 = cur.peek(); + if ( c0 < '0' || c0 > '9' ) return fail( "NPY dimension is not a non-negative integer" ); + std::size_t d = 0; + auto const [ptr, ec] = std::from_chars( cur.p, cur.end, d ); + if ( ec != std::errc{} ) return fail( "NPY dimension is out of range" ); + if ( c0 == '0' && ptr - cur.p > 1 ) return fail( "NPY dimension has a leading zero" ); // '(01, 2)' + cur.p = ptr; + if ( rank == 2 ) return fail( "NPY rank above 2 is not supported" ); + dims[rank++] = d; + cur.skip_ws(); + comma = cur.eat( ',' ); + cur.skip_ws(); + if ( !comma && cur.peek() != ')' ) return fail( "malformed NPY 'shape'" ); + } + if ( rank == 0 ) return fail( "NPY rank 0 is not supported" ); + if ( rank == 1 && !comma ) return fail( "malformed NPY 'shape'" ); + } + else + return fail( "unknown key in the NPY header" ); + cur.skip_ws(); + if ( cur.eat( ',' ) ) + { + cur.skip_ws(); + continue; + } + if ( cur.peek() != '}' ) return fail( "malformed NPY header dict" ); + } + cur.skip_ws(); + if ( !cur.at_end() ) return fail( "trailing characters after the NPY header dict" ); + if ( !have_descr || !have_order || !have_shape ) return fail( "NPY header is missing a key" ); + + std::size_t const r = rank == 1 ? 1 : dims[0]; + std::size_t const c = rank == 1 ? dims[0] : dims[1]; + std::size_t count = 0; + std::size_t payload = 0; + if ( !checked_mul( r, c, count ) || !checked_mul( count, sizeof( T ), payload ) ) + return fail( "NPY shape is too large" ); + if ( payload != size - data_offset ) return fail( "NPY payload length does not match the shape" ); + using size_type = typename Out::size_type; + if ( r > std::numeric_limits< size_type >::max() || c > std::numeric_limits< size_type >::max() ) + return fail( "NPY shape is too large" ); + + Out tmp{ out.get_allocator(), static_cast< size_type >( r ), static_cast< size_type >( c ) }; + std::uint8_t const* const src = bytes + data_offset; + T* const dst = tmp.data(); + constexpr std::size_t unit = npy_dtype< T >::component; + for ( std::size_t k = 0; k != count; ++k ) + { + // Row-major element k is (i, j); a Fortran payload stores it at j*r + i. + std::size_t const from = fortran ? ( k % c ) * r + ( k / c ) : k; + std::uint8_t element[sizeof( T )]; + std::memcpy( element, src + from * sizeof( T ), sizeof( T ) ); + if ( foreign ) + for ( std::size_t u = 0; u != sizeof( T ); u += unit ) + std::reverse( element + u, element + u + unit ); + std::memcpy( dst + k, element, sizeof( T ) ); + } + out = std::move( tmp ); + return true; + } } - friend const self_type operator-( const self_type& lhs, const difference_type rhs ) noexcept + + // S5-R2: the element types the native binary format (save_as_binary / load_binary) supports. + template < typename T > + inline constexpr bool is_binary_element_v = std::is_arithmetic_v< T > || std::is_same_v< T, std::complex< float > > + || std::is_same_v< T, std::complex< double > > || std::is_same_v< T, std::complex< long double > >; + + template < typename T > + struct text_complex : std::false_type {}; + template < typename F > + struct text_complex< std::complex< F > > : std::true_type { using component = F; }; + + // S5-R2: the text token separators; '\n' ends a row. + constexpr bool is_text_separator( char c ) noexcept { - self_type ans( lhs ); - ans -= rhs; - return ans; + return c == ',' || c == ';' || c == ' ' || c == '\t' || c == '\r'; } - reference operator[]( const difference_type dt ) noexcept + + // S5-R2: calls on_token( b, e ) for each token of the line [b, e); a token starting with '(' runs to the + // next ')' so the ',' of a complex pair is not a separator. False on an unclosed '(' or when on_token fails. + template < typename F > + bool for_each_text_token( char const* b, char const* e, F&& on_token ) noexcept { - return iterator_[dt * step_]; + for ( ;; ) + { + while ( b != e && is_text_separator( *b ) ) ++b; + if ( b == e ) return true; + char const* const t = b; + if ( *b == '(' ) + { + while ( b != e && *b != ')' ) ++b; + if ( b == e ) return false; + ++b; + } + while ( b != e && !is_text_separator( *b ) ) ++b; + if ( !on_token( t, b ) ) return false; + } } - const reference operator[]( const difference_type dt ) const noexcept + + enum class text_value_status { ok, syntax, range }; + + // S5-R2: one real number or integer in [b, e), parsed completely by std::from_chars (one leading '+' allowed). + template < typename T > + text_value_status parse_text_real( char const* b, char const* e, T& v ) noexcept { - return iterator_[dt * step_]; + if ( b != e && *b == '+' ) + { + ++b; + if ( b != e && ( *b == '+' || *b == '-' ) ) return text_value_status::syntax; + } + if ( b == e ) return text_value_status::syntax; + std::from_chars_result res{}; + if constexpr ( std::is_integral_v< T > ) + res = std::from_chars( b, e, v, 10 ); + else + res = std::from_chars( b, e, v, std::chars_format::general ); + if ( res.ec == std::errc::result_out_of_range ) return text_value_status::range; + if ( res.ec != std::errc{} || res.ptr != e ) return text_value_status::syntax; + return text_value_status::ok; } - reference operator*() noexcept + + template < typename T > + concept text_real = ( std::is_integral_v< T > && !std::is_same_v< T, bool > ) + || ( std::is_floating_point_v< T > && requires( char const* p, T& x ) { std::from_chars( p, p, x, std::chars_format::general ); } ); + + template < typename T > + concept text_element = text_real< T > || ( text_complex< T >::value && text_real< typename text_complex< T >::component > ); + + // S5-R2: one element token: a real or integer of type T, or for std::complex a bare real or "(re,im)". + template < typename T > + text_value_status parse_text_value( char const* b, char const* e, T& v ) noexcept { - return *iterator_; + if constexpr ( text_complex< T >::value ) + { + using F = typename text_complex< T >::component; + F re{}, im{}; + if ( b == e || *b != '(' ) + { + auto const s = parse_text_real( b, e, re ); + if ( s == text_value_status::ok ) v = T{ re, F{} }; + return s; + } + if ( e - b < 2 || *( e - 1 ) != ')' ) return text_value_status::syntax; + char const* const inner_b = b + 1; + char const* const inner_e = e - 1; + char const* const comma = std::find( inner_b, inner_e, ',' ); + if ( comma == inner_e ) return text_value_status::syntax; + auto trim = []( char const*& x, char const*& y ) noexcept + { + while ( x != y && ( *x == ' ' || *x == '\t' ) ) ++x; + while ( y != x && ( *( y - 1 ) == ' ' || *( y - 1 ) == '\t' ) ) --y; + }; + char const* rb = inner_b; char const* re_ = comma; + char const* ib = comma + 1; char const* ie = inner_e; + trim( rb, re_ ); + trim( ib, ie ); + auto const s1 = parse_text_real( rb, re_, re ); + if ( s1 != text_value_status::ok ) return s1; + auto const s2 = parse_text_real( ib, ie, im ); + if ( s2 != text_value_status::ok ) return s2; + v = T{ re, im }; + return text_value_status::ok; + } + else + return parse_text_real( b, e, v ); } - const reference operator*() const noexcept + + // S5-R2 (F08): parses text of `size` chars into `out`, a matrix< T, A >: each line holding a token is one row, + // all rows hold the same token count >= 1, every token parses completely as T. A first pass validates the + // structure, so the temporary (built with out's allocator) holds at most one element per input char; `out` + // is assigned only on success. On failure returns false, leaves `out` unchanged and sets *why when given. + template < typename T, typename Out > + bool parse_text( char const* chars, std::size_t size, Out& out, char const** why = nullptr ) noexcept + { + static_assert( std::is_same_v< typename Out::value_type, T >, "parse_text: Out must be a matrix of T" ); + auto fail = [why]( char const* reason ) noexcept + { + if ( why ) *why = reason; + return false; + }; + if constexpr ( !text_element< T > ) + { + (void)chars; (void)size; (void)out; + return fail( "the element type cannot be read from text" ); + } + else + { + if ( chars == nullptr || size == 0 ) return fail( "empty text" ); + char const* const end = chars + size; + std::size_t rows = 0, cols = 0; + for ( char const* line = chars; line != end; ) + { + char const* const eol = std::find( line, end, '\n' ); + std::size_t n = 0; + if ( !for_each_text_token( line, eol, [&n]( char const*, char const* ) noexcept { ++n; return true; } ) ) + return fail( "unclosed '(' in a row" ); + if ( n != 0 ) + { + if ( rows == 0 ) cols = n; + else if ( n != cols ) return fail( "rows hold different numbers of values" ); + ++rows; + } + line = eol == end ? end : eol + 1; + } + if ( rows == 0 ) return fail( "no values in the text" ); + using size_type = typename Out::size_type; + std::size_t count = 0; + if ( !checked_mul( rows, cols, count ) || rows > std::numeric_limits< size_type >::max() || cols > std::numeric_limits< size_type >::max() ) + return fail( "text matrix is too large" ); + + Out tmp{ out.get_allocator(), static_cast< size_type >( rows ), static_cast< size_type >( cols ) }; + T* dst = tmp.data(); + text_value_status status = text_value_status::ok; + for ( char const* line = chars; line != end; ) + { + char const* const eol = std::find( line, end, '\n' ); + bool const ok = for_each_text_token( line, eol, [&dst, &status]( char const* b, char const* e ) noexcept + { + status = parse_text_value( b, e, *dst ); + ++dst; + return status == text_value_status::ok; + } ); + if ( !ok ) + return fail( status == text_value_status::range ? "a value is out of range for the element type" + : "a token does not parse completely as the element type" ); + line = eol == end ? end : eol + 1; + } + out = std::move( tmp ); + return true; + } + } + + // S5-R2: writes m as text that parse_text reads back: tab-separated rows, one-byte integers as numbers, + // floating-point values with max_digits10 significant digits. + template < typename M > + void write_text( std::ostream& os, M const& m ) noexcept { - return *iterator_; + using T = typename M::value_type; + if constexpr ( std::is_floating_point_v< T > ) + os.precision( std::numeric_limits< T >::max_digits10 ); + else if constexpr ( text_complex< T >::value ) + os.precision( std::numeric_limits< typename text_complex< T >::component >::max_digits10 ); + else + os.precision( 18 ); + for ( typename M::size_type r = 0; r != m.row(); ++r ) + { + for ( auto it = m.row_begin( r ); it != m.row_end( r ); ++it ) + { + if constexpr ( std::is_integral_v< T > && sizeof( T ) == 1 ) + os << static_cast< int >( *it ) << '\t'; + else + os << *it << '\t'; + } + os << '\n'; + } } - friend bool operator==( const self_type& lhs, const self_type& rhs ) noexcept + + // S5-R2 (F08): parses a native binary image (two size_type counts, then the raw elements) into `out`, a + // matrix< T, A >. The count product and byte size are overflow-checked and the payload length must equal + // the declared size exactly; `out` is assigned only on success, otherwise *why is set when given. + template < typename T, typename Out > + bool parse_binary( std::uint8_t const* bytes, std::size_t size, Out& out, char const** why = nullptr ) noexcept + { + static_assert( std::is_same_v< typename Out::value_type, T >, "parse_binary: Out must be a matrix of T" ); + static_assert( is_binary_element_v< T >, "the native binary format supports arithmetic and std::complex elements only" ); + auto fail = [why]( char const* reason ) noexcept + { + if ( why ) *why = reason; + return false; + }; + using size_type = typename Out::size_type; + constexpr std::size_t head = 2 * sizeof( size_type ); + if ( bytes == nullptr || size < head ) return fail( "file shorter than the two counts" ); + size_type r = 0, c = 0; + std::memcpy( &r, bytes, sizeof( r ) ); + std::memcpy( &c, bytes + sizeof( r ), sizeof( c ) ); + size_type count = 0, payload = 0; + if ( !checked_mul( r, c, count ) || !checked_mul( count, static_cast< size_type >( sizeof( T ) ), payload ) ) + return fail( "the element count or byte size overflows" ); + if ( payload != size - head ) return fail( "payload length does not match the counts" ); + Out tmp{ out.get_allocator(), r, c }; + if ( payload != 0 ) std::memcpy( static_cast< void* >( tmp.data() ), bytes + head, static_cast< std::size_t >( payload ) ); + out = std::move( tmp ); + return true; + } + + // S6-R5 (D-023): a scalar operand is an arithmetic type or a std::complex specialization. + template< typename S > + concept matrix_scalar = std::is_arithmetic_v< S > || matrix_private::is_std_complex_v< S >; + + // The scalar as an operation on R elements sees it: R itself, or R's value type for complex R and a real + // scalar, converted as by static_cast. + template< typename R, typename S > + constexpr auto scalar_as( S const& s ) noexcept + { + if constexpr ( std::same_as< R, S > ) + return s; + else if constexpr ( matrix_private::is_std_complex_v< R > && !matrix_private::is_std_complex_v< S > ) + return static_cast< typename R::value_type >( s ); + else + return static_cast< R >( s ); + } + + // A scalar of another type that a compound operator on T elements converts first (D-023: T is kept). + template< typename S, typename T > + concept compound_scalar = matrix_scalar< S > && matrix_scalar< T > && !std::same_as< S, T >; + + // A scalar or matrix element of type S converts to T: not complex into real. + template< typename S, typename T > + inline constexpr bool converts_to_element_v = !( matrix_private::is_std_complex_v< S > && !matrix_private::is_std_complex_v< T > ); + + }//namespace matrix_details + + // S4-R1 (F05): column, diagonal and anti-diagonal iteration. Holds a base pointer, a stride, a logical index + // and a count; a pointer base_ + i*stride_ is formed only in operator*, operator[] and operator->, for a + // dereferenceable position. Arithmetic, equality, ordering and distance work on the index alone, so one past + // the end is index == count and a stride of 0 is legal. Under FENG_MATRIX_CHECKED_ITERATORS (D-020) the + // iterator also keeps its owner's [origin, origin+extent] and aborts on any position or address outside it. + template < typename P > + struct stride_iterator + { + typedef stride_iterator self_type; + typedef typename std::iterator_traits< P >::value_type value_type; + typedef typename std::iterator_traits< P >::reference reference; + typedef typename std::iterator_traits< P >::difference_type difference_type; + typedef P pointer; + typedef std::size_t size_type; + typedef std::random_access_iterator_tag iterator_category; + typedef std::random_access_iterator_tag iterator_concept; + + private: + template < typename Q > friend struct stride_iterator; + + P base_ = nullptr; + difference_type stride_ = 1; + difference_type index_ = 0; + difference_type count_ = 0; + #ifdef FENG_MATRIX_CHECKED_ITERATORS + P origin_ = nullptr; + difference_type extent_ = 0; + #endif + + void check_position() const noexcept + { + #ifdef FENG_MATRIX_CHECKED_ITERATORS + FENG_MATRIX_EXPECTS( 0 <= index_ && index_ <= count_, "matrix iterator: position ", index_, " outside [0, ", count_, "]" ); + #endif + } + + P address( difference_type i ) const noexcept + { + #ifdef FENG_MATRIX_CHECKED_ITERATORS + FENG_MATRIX_EXPECTS( 0 <= i && i < count_, "matrix iterator: dereference at position ", i, " outside [0, ", count_, ")" ); + #endif + return base_ + i * stride_; + } + + public: + stride_iterator() noexcept = default; + + // base: the element at index 0; count: the number of elements; index: the starting position in [0, count]; + // origin, extent: the owner's data() and size(), checked only under FENG_MATRIX_CHECKED_ITERATORS. + stride_iterator( P base, difference_type stride, difference_type count, difference_type index, [[maybe_unused]] P origin, [[maybe_unused]] size_type extent ) noexcept + : base_( base ), stride_( stride ), index_( index ), count_( count ) + #ifdef FENG_MATRIX_CHECKED_ITERATORS + , origin_( origin ), extent_( static_cast< difference_type >( extent ) ) + #endif + { + #ifdef FENG_MATRIX_CHECKED_ITERATORS + FENG_MATRIX_EXPECTS( 0 <= count_, "matrix iterator: negative count ", count_ ); + FENG_MATRIX_EXPECTS( extent <= static_cast< size_type >( PTRDIFF_MAX ), "matrix iterator: owner extent ", extent, " above PTRDIFF_MAX" ); + // integer offsets from the owner's origin, computed before any other pointer is formed + std::uintptr_t const b = reinterpret_cast< std::uintptr_t >( base ); + std::uintptr_t const o = reinterpret_cast< std::uintptr_t >( origin ); + std::uintptr_t const bytes = b - o; + std::uintptr_t const span = static_cast< std::uintptr_t >( extent_ ) * sizeof( value_type ); + FENG_MATRIX_EXPECTS( b >= o && bytes <= span && bytes % sizeof( value_type ) == 0, "matrix iterator: first address outside the owner's [data(), data()+size()]" ); + difference_type const first = static_cast< difference_type >( bytes / sizeof( value_type ) ); + if ( count_ > 0 ) + { + FENG_MATRIX_EXPECTS( first < extent_, "matrix iterator: first element at offset ", first, " outside an owner of size ", extent_ ); + FENG_MATRIX_EXPECTS( stride_ == 0 || ( count_ - 1 ) <= ( stride_ > 0 ? ( extent_ - 1 - first ) / stride_ : first / -stride_ ), + "matrix iterator: last element of ", count_, " with stride ", stride_, " from offset ", first, " outside an owner of size ", extent_ ); + } + #endif + check_position(); + } + + stride_iterator( const self_type& ) noexcept = default; + stride_iterator( self_type&& ) noexcept = default; + self_type& operator=( const self_type& ) noexcept = default; + self_type& operator=( self_type&& ) noexcept = default; + + // stride_iterator converts to stride_iterator + template < typename Q > + requires ( !std::is_same_v< Q, P > && std::is_convertible_v< Q, P > ) + stride_iterator( stride_iterator< Q > const& other ) noexcept + : base_( other.base_ ), stride_( other.stride_ ), index_( other.index_ ), count_( other.count_ ) + #ifdef FENG_MATRIX_CHECKED_ITERATORS + , origin_( other.origin_ ), extent_( other.extent_ ) + #endif + { } + + self_type& operator++() noexcept + { + ++index_; + check_position(); + return *this; + } + self_type operator++( int ) noexcept + { + self_type ans( *this ); + operator++(); + return ans; + } + self_type& operator--() noexcept + { + --index_; + check_position(); + return *this; + } + self_type operator--( int ) noexcept { - if ( lhs.step_ != rhs.step_ ) return false; - return lhs.iterator_ == rhs.iterator_; + self_type ans( *this ); + operator--(); + return ans; } - friend bool operator!=( const self_type& lhs, const self_type& rhs ) noexcept + self_type& operator+=( const difference_type dt ) noexcept { - if ( lhs.step_ != rhs.step_ ) return true; - return lhs.iterator_ != rhs.iterator_; + index_ += dt; + check_position(); + return *this; } - friend bool operator<( const self_type& lhs, const self_type& rhs ) noexcept + self_type& operator-=( const difference_type dt ) noexcept { - if ( lhs.step_ != rhs.step_ ) return false; - return lhs.iterator_ < rhs.iterator_; + index_ -= dt; + check_position(); + return *this; } - friend bool operator<=( const self_type& lhs, const self_type& rhs ) noexcept + friend self_type operator+( const self_type& lhs, const difference_type rhs ) noexcept { - if ( lhs.step_ != rhs.step_ ) return false; - return lhs.iterator_ <= rhs.iterator_; + self_type ans( lhs ); + ans += rhs; + return ans; } - friend bool operator>( const self_type& lhs, const self_type& rhs ) noexcept + friend self_type operator+( const difference_type lhs, const self_type& rhs ) noexcept { - if ( lhs.step_ != rhs.step_ ) return false; - return lhs.iterator_ > rhs.iterator_; + return rhs + lhs; } - friend bool operator>=( const self_type& lhs, const self_type& rhs ) noexcept + friend self_type operator-( const self_type& lhs, const difference_type rhs ) noexcept { - if ( lhs.step_ != rhs.step_ ) return false; - return lhs.iterator_ >= rhs.iterator_; + self_type ans( lhs ); + ans -= rhs; + return ans; } friend difference_type operator-( const self_type& lhs, const self_type& rhs ) noexcept { - better_assert( lhs.step_ == rhs.step_ && "stride iterators of different steps" ); - return ( lhs.iterator_ - rhs.iterator_ ) / lhs.step_; + return lhs.index_ - rhs.index_; + } + + reference operator*() const noexcept + { + return *address( index_ ); + } + reference operator[]( const difference_type dt ) const noexcept + { + return *address( index_ + dt ); + } + pointer operator->() const noexcept + { + return address( index_ ); + } + + friend bool operator==( const self_type& lhs, const self_type& rhs ) noexcept + { + return lhs.index_ == rhs.index_; + } + friend std::strong_ordering operator<=>( const self_type& lhs, const self_type& rhs ) noexcept + { + return lhs.index_ <=> rhs.index_; + } + }; + + // S4-R2 (F06, F05): row-major iteration over a rank-2 view. Holds the first viewed element, the parent's row + // stride, the view's column count, a logical index and a count; the address + // base_ + (i/cols_)*row_stride_ + i%cols_ is formed only in operator*, operator[] and operator->, for a + // dereferenceable position. Arithmetic, equality, ordering and distance work on the index alone. Under + // FENG_MATRIX_CHECKED_ITERATORS (D-020) it keeps its owner's [origin, origin+extent] and aborts on any position + // or address outside it, as stride_iterator does. + template < typename P > + struct view_iterator + { + typedef view_iterator self_type; + typedef typename std::iterator_traits< P >::value_type value_type; + typedef typename std::iterator_traits< P >::reference reference; + typedef typename std::iterator_traits< P >::difference_type difference_type; + typedef P pointer; + typedef std::size_t size_type; + typedef std::random_access_iterator_tag iterator_category; + typedef std::random_access_iterator_tag iterator_concept; + + private: + template < typename Q > friend struct view_iterator; + + P base_ = nullptr; + difference_type row_stride_ = 0; + difference_type cols_ = 1; + difference_type index_ = 0; + difference_type count_ = 0; + #ifdef FENG_MATRIX_CHECKED_ITERATORS + P origin_ = nullptr; + difference_type extent_ = 0; + #endif + + void check_position() const noexcept + { + #ifdef FENG_MATRIX_CHECKED_ITERATORS + FENG_MATRIX_EXPECTS( 0 <= index_ && index_ <= count_, "matrix iterator: position ", index_, " outside [0, ", count_, "]" ); + #endif + } + + P address( difference_type i ) const noexcept + { + #ifdef FENG_MATRIX_CHECKED_ITERATORS + FENG_MATRIX_EXPECTS( 0 <= i && i < count_, "matrix iterator: dereference at position ", i, " outside [0, ", count_, ")" ); + #endif + return base_ + ( ( i / cols_ ) * row_stride_ + i % cols_ ); + } + + public: + view_iterator() noexcept = default; + + // base: the element at index 0; row_stride: the parent's columns; cols: the view's columns (> 0 when + // count > 0); count: the number of elements; index: the starting position in [0, count]; origin, extent: + // the owner's data() and size(), checked only under FENG_MATRIX_CHECKED_ITERATORS. + view_iterator( P base, difference_type row_stride, difference_type cols, difference_type count, difference_type index, [[maybe_unused]] P origin, [[maybe_unused]] size_type extent ) noexcept + : base_( base ), row_stride_( row_stride ), cols_( cols > 0 ? cols : 1 ), index_( index ), count_( count ) + #ifdef FENG_MATRIX_CHECKED_ITERATORS + , origin_( origin ), extent_( static_cast< difference_type >( extent ) ) + #endif + { + #ifdef FENG_MATRIX_CHECKED_ITERATORS + FENG_MATRIX_EXPECTS( 0 <= count_ && 0 <= row_stride_, "matrix iterator: negative count ", count_, " or row stride ", row_stride_ ); + FENG_MATRIX_EXPECTS( extent <= static_cast< size_type >( PTRDIFF_MAX ), "matrix iterator: owner extent ", extent, " above PTRDIFF_MAX" ); + // integer offsets from the owner's origin, computed before any other pointer is formed + std::uintptr_t const b = reinterpret_cast< std::uintptr_t >( base ); + std::uintptr_t const o = reinterpret_cast< std::uintptr_t >( origin ); + std::uintptr_t const bytes = b - o; + std::uintptr_t const span = static_cast< std::uintptr_t >( extent_ ) * sizeof( value_type ); + FENG_MATRIX_EXPECTS( b >= o && bytes <= span && bytes % sizeof( value_type ) == 0, "matrix iterator: first address outside the owner's [data(), data()+size()]" ); + difference_type const first = static_cast< difference_type >( bytes / sizeof( value_type ) ); + if ( count_ > 0 ) + { + difference_type const last_row = ( count_ - 1 ) / cols_; + difference_type const last_col = ( count_ - 1 ) % cols_; + FENG_MATRIX_EXPECTS( first < extent_ && ( row_stride_ == 0 || last_row <= ( extent_ - 1 - first ) / row_stride_ ) && + last_row * row_stride_ + last_col <= extent_ - 1 - first, + "matrix iterator: last element of ", count_, " in rows of ", cols_, " with row stride ", row_stride_, " from offset ", first, " outside an owner of size ", extent_ ); + } + #endif + check_position(); } + + view_iterator( const self_type& ) noexcept = default; + view_iterator( self_type&& ) noexcept = default; + self_type& operator=( const self_type& ) noexcept = default; + self_type& operator=( self_type&& ) noexcept = default; + + // view_iterator converts to view_iterator + template < typename Q > + requires ( !std::is_same_v< Q, P > && std::is_convertible_v< Q, P > ) + view_iterator( view_iterator< Q > const& other ) noexcept + : base_( other.base_ ), row_stride_( other.row_stride_ ), cols_( other.cols_ ), index_( other.index_ ), count_( other.count_ ) + #ifdef FENG_MATRIX_CHECKED_ITERATORS + , origin_( other.origin_ ), extent_( other.extent_ ) + #endif + { } + + self_type& operator++() noexcept { ++index_; check_position(); return *this; } + self_type operator++( int ) noexcept { self_type ans( *this ); operator++(); return ans; } + self_type& operator--() noexcept { --index_; check_position(); return *this; } + self_type operator--( int ) noexcept { self_type ans( *this ); operator--(); return ans; } + self_type& operator+=( const difference_type dt ) noexcept { index_ += dt; check_position(); return *this; } + self_type& operator-=( const difference_type dt ) noexcept { index_ -= dt; check_position(); return *this; } + friend self_type operator+( const self_type& lhs, const difference_type rhs ) noexcept { self_type ans( lhs ); ans += rhs; return ans; } + friend self_type operator+( const difference_type lhs, const self_type& rhs ) noexcept { return rhs + lhs; } + friend self_type operator-( const self_type& lhs, const difference_type rhs ) noexcept { self_type ans( lhs ); ans -= rhs; return ans; } + friend difference_type operator-( const self_type& lhs, const self_type& rhs ) noexcept { return lhs.index_ - rhs.index_; } + + reference operator*() const noexcept { return *address( index_ ); } + reference operator[]( const difference_type dt ) const noexcept { return *address( index_ + dt ); } + pointer operator->() const noexcept { return address( index_ ); } + + friend bool operator==( const self_type& lhs, const self_type& rhs ) noexcept { return lhs.index_ == rhs.index_; } + friend std::strong_ordering operator<=>( const self_type& lhs, const self_type& rhs ) noexcept { return lhs.index_ <=> rhs.index_; } }; + namespace matrix_private + { + // S4-R3 (F06): a view of an owner with `rows` rows and `cols` columns needs r0 <= r1 <= rows and + // c0 <= c1 <= cols; an empty range is allowed, nothing is normalized. + inline void check_view_range( std::size_t r0, std::size_t r1, std::size_t c0, std::size_t c1, std::size_t rows, std::size_t cols ) noexcept + { + FENG_MATRIX_EXPECTS( r0 <= r1 && r1 <= rows, "matrix view: row range [", r0, ", ", r1, ") outside an owner with ", rows, " rows" ); + FENG_MATRIX_EXPECTS( c0 <= c1 && c1 <= cols, "matrix view: column range [", c0, ", ", c1, ") outside an owner with ", cols, " columns" ); + } + + // S4-R3: a factory's range list must hold exactly two values. + template < typename Integer_Type > + std::pair< std::size_t, std::size_t > view_extent( std::initializer_list< Integer_Type > list, char const* what ) noexcept + { + FENG_MATRIX_EXPECTS( list.size() == 2, "matrix view: the ", what, " range needs exactly two values, got ", list.size() ); + return { static_cast< std::size_t >( *list.begin() ), static_cast< std::size_t >( *( list.begin() + 1 ) ) }; + } + + // S4-R1: a column index must name a column. + inline void check_column( std::size_t c, std::size_t cols ) noexcept + { + FENG_MATRIX_EXPECTS( c < cols, "matrix iterator: column ", c, " outside a matrix with ", cols, " columns" ); + } + + // S4-R1: diagonal (and anti-diagonal) k exists for -rows < k < cols; diagonal 0 always exists (empty on + // an empty matrix). + inline void check_diagonal( std::ptrdiff_t k, std::size_t rows, std::size_t cols ) noexcept + { + bool const valid = k == 0 || ( k > 0 ? static_cast< std::size_t >( k ) < cols : static_cast< std::size_t >( -( k + 1 ) ) + 1 < rows ); + FENG_MATRIX_EXPECTS( valid, "matrix iterator: diagonal ", k, " outside a ", rows, "x", cols, " matrix" ); + } + + // the signed diagonal of an unsigned upper (k >= 0) or lower (k <= 0) index, saturated so that it stays invalid + inline std::ptrdiff_t upper_diagonal( std::size_t index ) noexcept + { + return static_cast< std::ptrdiff_t >( std::min< std::size_t >( index, PTRDIFF_MAX ) ); + } + inline std::ptrdiff_t lower_diagonal( std::size_t index ) noexcept + { + return -static_cast< std::ptrdiff_t >( std::min< std::size_t >( index, PTRDIFF_MAX ) ); + } + + // S4-R1: the [begin, end) pair of a strided range of `length` elements starting at integer offset `start` + // of an owner [data, data+size); an empty range starts at offset 0, so no pointer past data+size is formed. + template < typename It, typename Ptr > + It stride_range( Ptr data, std::size_t size, std::size_t start, std::ptrdiff_t stride, std::size_t length, bool at_end ) noexcept + { + if ( length == 0 ) start = 0; + std::ptrdiff_t const n = static_cast< std::ptrdiff_t >( length ); + return It( data + start, stride, n, at_end ? n : 0, data, size ); + } + } + template < typename Type, Allocator Alloc > struct crtp_typedef { @@ -1356,9 +2402,9 @@ namespace feng typedef Matrix zen_type; typedef Type value_type; - #ifdef OPENCV + #ifdef FENG_MATRIX_OPENCV - cv::Mat const to_opencv( unsigned long channels = 1 ) const + cv::Mat to_opencv( unsigned long channels = 1 ) const noexcept { better_assert( ((channels>=1) && (channels<=4)), "Expecting 1-4 channels." ); @@ -1434,7 +2480,7 @@ namespace feng return ans; } - auto& from_opencv( cv::Mat image ) + auto& from_opencv( cv::Mat image ) noexcept { if ( !image.isContiguous() ) // data stored in Mat is not always continuous in memory image = image.clone(); @@ -1523,7 +2569,7 @@ namespace feng auto get_allocator() const noexcept { auto const& zen = static_cast(*this); - return zen.allocator_; + return matrix_private::storage_access::storage( zen ).get_allocator(); } }; @@ -1540,25 +2586,19 @@ namespace feng typedef typename type_proxy_type::const_reverse_anti_diag_type const_reverse_anti_diag_type; anti_diag_type upper_anti_diag_begin( const size_type index = 0 ) noexcept { - zen_type& zen = static_cast< zen_type& >( *this ); - return anti_diag_type( zen.begin() + zen.col() - index - 1, zen.col() - 1 ); + return upper_anti_diag_range< anti_diag_type >( static_cast< zen_type& >( *this ), index, false ); } anti_diag_type upper_anti_diag_end( const size_type index = 0 ) noexcept { - zen_type& zen = static_cast< zen_type& >( *this ); - size_type const depth = std::min( zen.col()-index, zen.row() ); - return upper_anti_diag_begin( index ) + depth; + return upper_anti_diag_range< anti_diag_type >( static_cast< zen_type& >( *this ), index, true ); } const_anti_diag_type upper_anti_diag_begin( const size_type index = 0 ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - return const_anti_diag_type( zen.begin() + zen.col() - index - 1, zen.col() - 1 ); + return upper_anti_diag_range< const_anti_diag_type >( static_cast< zen_type const& >( *this ), index, false ); } const_anti_diag_type upper_anti_diag_end( const size_type index = 0 ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - size_type const depth = std::min( zen.col()-index, zen.row() ); - return upper_anti_diag_begin( index ) + depth; + return upper_anti_diag_range< const_anti_diag_type >( static_cast< zen_type const& >( *this ), index, true ); } const_anti_diag_type upper_anti_diag_cbegin( const size_type index = 0 ) const noexcept { @@ -1594,27 +2634,19 @@ namespace feng } anti_diag_type lower_anti_diag_begin( const size_type index = 0 ) noexcept { - zen_type& zen = static_cast< zen_type& >( *this ); - return anti_diag_type( zen.begin() + ( zen.col() * ( index + 1 ) ) - 1, zen.col() - 1 ); + return lower_anti_diag_range< anti_diag_type >( static_cast< zen_type& >( *this ), index, false ); } anti_diag_type lower_anti_diag_end( const size_type index = 0 ) noexcept { - zen_type& zen = static_cast< zen_type& >( *this ); - - size_type const depth = std::min( zen.row()-index, zen.col() ); - return lower_anti_diag_begin( index ) + depth; + return lower_anti_diag_range< anti_diag_type >( static_cast< zen_type& >( *this ), index, true ); } const_anti_diag_type lower_anti_diag_begin( const size_type index = 0 ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - return const_anti_diag_type( zen.begin() + ( zen.col() * ( index + 1 ) ) - 1, zen.col() - 1 ); + return lower_anti_diag_range< const_anti_diag_type >( static_cast< zen_type const& >( *this ), index, false ); } const_anti_diag_type lower_anti_diag_end( const size_type index = 0 ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - - size_type const depth = std::min( zen.row()-index, zen.col() ); - return lower_anti_diag_begin( index ) + depth; + return lower_anti_diag_range< const_anti_diag_type >( static_cast< zen_type const& >( *this ), index, true ); } const_anti_diag_type lower_anti_diag_cbegin( const size_type index = 0 ) const noexcept { @@ -1650,111 +2682,108 @@ namespace feng } anti_diag_type anti_diag_begin( const difference_type index = 0 ) noexcept { - if ( index > 0 ) - { - return upper_anti_diag_begin( index ); - } - - return lower_anti_diag_begin( -index ); + zen_type& zen = static_cast< zen_type& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_anti_diag_begin( static_cast< size_type >( index ) ); + return lower_anti_diag_begin( static_cast< size_type >( -index ) ); } anti_diag_type anti_diag_end( const difference_type index = 0 ) noexcept { - if ( index > 0 ) - { - return upper_anti_diag_end( index ); - } - - return lower_anti_diag_end( -index ); + zen_type& zen = static_cast< zen_type& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_anti_diag_end( static_cast< size_type >( index ) ); + return lower_anti_diag_end( static_cast< size_type >( -index ) ); } const_anti_diag_type anti_diag_begin( const difference_type index = 0 ) const noexcept { - if ( index > 0 ) - { - return upper_anti_diag_begin( index ); - } - - return lower_anti_diag_begin( -index ); + zen_type const& zen = static_cast< zen_type const& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_anti_diag_begin( static_cast< size_type >( index ) ); + return lower_anti_diag_begin( static_cast< size_type >( -index ) ); } const_anti_diag_type anti_diag_end( const difference_type index = 0 ) const noexcept { - if ( index > 0 ) - { - return upper_anti_diag_end( index ); - } - - return lower_anti_diag_end( -index ); + zen_type const& zen = static_cast< zen_type const& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_anti_diag_end( static_cast< size_type >( index ) ); + return lower_anti_diag_end( static_cast< size_type >( -index ) ); } const_anti_diag_type anti_diag_cbegin( const difference_type index = 0 ) const noexcept { - if ( index > 0 ) - { - return upper_anti_diag_cbegin( index ); - } - - return lower_anti_diag_cbegin( -index ); + zen_type const& zen = static_cast< zen_type const& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_anti_diag_cbegin( static_cast< size_type >( index ) ); + return lower_anti_diag_cbegin( static_cast< size_type >( -index ) ); } const_anti_diag_type anti_diag_cend( const difference_type index = 0 ) const noexcept { - if ( index > 0 ) - { - return upper_anti_diag_cend( index ); - } - - return lower_anti_diag_cend( -index ); + zen_type const& zen = static_cast< zen_type const& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_anti_diag_cend( static_cast< size_type >( index ) ); + return lower_anti_diag_cend( static_cast< size_type >( -index ) ); } reverse_anti_diag_type anti_diag_rbegin( const difference_type index = 0 ) noexcept { - if ( index > 0 ) - { - return upper_anti_diag_rbegin( index ); - } - - return lower_anti_diag_rbegin( -index ); + zen_type& zen = static_cast< zen_type& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_anti_diag_rbegin( static_cast< size_type >( index ) ); + return lower_anti_diag_rbegin( static_cast< size_type >( -index ) ); } reverse_anti_diag_type anti_diag_rend( const difference_type index = 0 ) noexcept { - if ( index > 0 ) - { - return upper_anti_diag_rend( index ); - } - - return lower_anti_diag_rend( -index ); + zen_type& zen = static_cast< zen_type& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_anti_diag_rend( static_cast< size_type >( index ) ); + return lower_anti_diag_rend( static_cast< size_type >( -index ) ); } const_reverse_anti_diag_type anti_diag_rbegin( const difference_type index = 0 ) const noexcept { - if ( index > 0 ) - { - return upper_anti_diag_rbegin( index ); - } - - return lower_anti_diag_rbegin( -index ); + zen_type const& zen = static_cast< zen_type const& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_anti_diag_rbegin( static_cast< size_type >( index ) ); + return lower_anti_diag_rbegin( static_cast< size_type >( -index ) ); } const_reverse_anti_diag_type anti_diag_rend( const difference_type index = 0 ) const noexcept { - if ( index > 0 ) - { - return upper_anti_diag_rend( index ); - } - - return lower_anti_diag_rend( -index ); + zen_type const& zen = static_cast< zen_type const& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_anti_diag_rend( static_cast< size_type >( index ) ); + return lower_anti_diag_rend( static_cast< size_type >( -index ) ); } const_reverse_anti_diag_type anti_diag_crbegin( const difference_type index = 0 ) const noexcept { - if ( index > 0 ) - { - return upper_anti_diag_crbegin( index ); - } - - return lower_anti_diag_crbegin( -index ); + zen_type const& zen = static_cast< zen_type const& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_anti_diag_crbegin( static_cast< size_type >( index ) ); + return lower_anti_diag_crbegin( static_cast< size_type >( -index ) ); } const_reverse_anti_diag_type anti_diag_crend( const difference_type index = 0 ) const noexcept { - if ( index > 0 ) - { - return upper_anti_diag_crend( index ); - } - - return lower_anti_diag_crend( -index ); + zen_type const& zen = static_cast< zen_type const& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_anti_diag_crend( static_cast< size_type >( index ) ); + return lower_anti_diag_crend( static_cast< size_type >( -index ) ); + } + private: + // S4-R1: upper anti-diagonal k starts at (0, col()-1-k), lower anti-diagonal k at (k, col()-1); each step + // moves one row down and one column left (stride col()-1, 0 for one column). + template < typename It, typename Z > + static It upper_anti_diag_range( Z& zen, size_type index, bool at_end ) noexcept + { + size_type const rows = zen.row(), cols = zen.col(); + matrix_private::check_diagonal( matrix_private::upper_diagonal( index ), rows, cols ); + size_type const length = index < cols ? std::min( cols - index, rows ) : 0; + size_type const start = length ? cols - 1 - index : 0; + return matrix_private::stride_range< It >( std::to_address( zen.data() ), zen.size(), start, static_cast< difference_type >( cols ) - 1, length, at_end ); + } + template < typename It, typename Z > + static It lower_anti_diag_range( Z& zen, size_type index, bool at_end ) noexcept + { + size_type const rows = zen.row(), cols = zen.col(); + matrix_private::check_diagonal( matrix_private::lower_diagonal( index ), rows, cols ); + size_type const length = index < rows ? std::min( rows - index, cols ) : 0; + size_type const start = length ? index * cols + cols - 1 : 0; + return matrix_private::stride_range< It >( std::to_address( zen.data() ), zen.size(), start, static_cast< difference_type >( cols ) - 1, length, at_end ); } }; template < typename Matrix, typename Type, Allocator Alloc > @@ -1769,7 +2798,7 @@ namespace feng zen_type& zen = static_cast< zen_type& >( *this ); value_type* x = zen.data(); auto && parallel_function = [x, &func]( std::uint_least64_t offset ) { func( x[offset] ); }; - matrix_details::parallel( parallel_function, 0UL, zen.size(), 0UL ); + matrix_details::parallel_work( parallel_function, 0UL, zen.size(), 1, matrix_details::callback_grain ); } template < typename Function > @@ -1796,52 +2825,35 @@ namespace feng row_type operator[]( const size_type index ) noexcept { zen_type& zen = static_cast< zen_type& >( *this ); - better_assert( index < zen.row() && "Row index outof boundary!", " with index ", index, " and zen.row() ", zen.row() ); + FENG_MATRIX_EXPECTS( index < zen.row() && "Row index out of boundary!", "matrix index: row ", index, " with row() ", zen.row() ); return zen.row_begin( index ); } const_row_type operator[]( const size_type index ) const noexcept { zen_type const& zen = static_cast< zen_type const& >( *this ); - better_assert( index < zen.row() && "Row index outof boundary!", " with index ", index, " and zen.row() ", zen.row() ); + FENG_MATRIX_EXPECTS( index < zen.row() && "Row index out of boundary!", "matrix index: row ", index, " with row() ", zen.row() ); return zen.row_cbegin( index ); } - value_type operator()( size_type r, size_type c ) const noexcept + // S2-R2: at( r, c ) and m( r, c ) share matrix_private::check_index and abort with `index` out of range. + value_type const& at( size_type r, size_type c ) const noexcept { zen_type const& zen = static_cast< zen_type const& >( *this ); - better_assert( r < zen.row() && "Row index out of boundary!" ); - better_assert( c < zen.col() && "Column index out of boundary!" ); - return zen[r][c]; + matrix_private::check_index( r, c, zen.row(), zen.col() ); + return zen.row_cbegin( r )[c]; } - value_type& operator()( size_type r, size_type c ) noexcept + value_type& at( size_type r, size_type c ) noexcept { zen_type& zen = static_cast< zen_type& >( *this ); - better_assert( r < zen.row() && "Row index out of boundary!" ); - better_assert( c < zen.col() && "Column index out of boundary!" ); - return zen[r][c]; + matrix_private::check_index( r, c, zen.row(), zen.col() ); + return zen.row_begin( r )[c]; } - }; - - template < typename Matrix, typename Type, Allocator Alloc > - struct crtp_bracket_operator_view - { - typedef Matrix zen_type; - typedef crtp_typedef< Type, Alloc > type_proxy_type; - typedef typename type_proxy_type::value_type value_type; - typedef typename type_proxy_type::size_type size_type; - typedef typename type_proxy_type::row_type row_type; - typedef typename type_proxy_type::const_row_type const_row_type; - const_row_type operator[]( const size_type index ) const noexcept + value_type operator()( size_type r, size_type c ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - better_assert( index < zen.row() && "Row index outof boundary!" ); - return zen.row_cbegin( index ); + return (*this).at( r, c ); } - value_type operator()( size_type r, size_type c ) const noexcept + value_type& operator()( size_type r, size_type c ) noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - better_assert( r < zen.row() && "Row index out of boundary!" ); - better_assert( c < zen.col() && "Column index out of boundary!" ); - return zen[r][c]; + return (*this).at( r, c ); } }; @@ -1853,13 +2865,11 @@ namespace feng void clear() noexcept { zen_type& zen = static_cast< zen_type& >( *this ); - - if ( zen.data() != nullptr ) - zen.get_allocator().deallocate( zen.data(), zen.size() ); - - zen.dat_ = nullptr; - zen.row_ = 0; - zen.col_ = 0; + auto& storage = matrix_private::storage_access::storage( zen ); + std::remove_reference_t< decltype( storage ) > empty{ storage.get_allocator() }; + storage.swap( empty ); // equal allocators: releases the old buffer + matrix_private::storage_access::rows( zen ) = 0; + matrix_private::storage_access::cols( zen ) = 0; } }; template < typename Matrix, typename Type, Allocator Alloc > @@ -1871,15 +2881,13 @@ namespace feng template < typename Other_Matrix > zen_type& clone( const Other_Matrix& other, std::initializer_list r, std::initializer_list c ) noexcept { - better_assert( r.size() == 2 && "row size should be 2!" ); - better_assert( c.size() == 2 && "col size should be 2!" ); - return clone( other, r[0], r[1], c[0], c[1] ); + matrix_private::check_clone_lists( r.size(), c.size() ); + return clone( other, *r.begin(), *(r.begin()+1), *c.begin(), *(c.begin()+1) ); } template < typename Other_Matrix > zen_type& clone( const Other_Matrix& other, size_type const r0, size_type const r1, size_type const c0, size_type const c1 ) noexcept { - better_assert( r1 > r0 && "row range error!" ); - better_assert( c1 > c0 && "col range error!" ); + matrix_private::check_clone_range( r0, r1, c0, c1, other.row(), other.col() ); zen_type& zen = static_cast< zen_type& >( *this ); zen_type tmp{ zen.get_allocator(), r1-r0, c1-c0 }; @@ -1889,22 +2897,21 @@ namespace feng std::copy_n( other.row_begin(r+r0)+c0, tmp.col(), tmp.row_begin(r) ); }; - matrix_details::parallel( parallel_function, 0UL, tmp.row(), 0UL ); + matrix_details::parallel_work( parallel_function, 0UL, tmp.row(), tmp.col(), matrix_details::elementwise_grain ); zen.swap( tmp ); return zen; } - zen_type const clone( std::initializer_list r, std::initializer_list c ) const noexcept + [[nodiscard]] zen_type clone( std::initializer_list r, std::initializer_list c ) const noexcept { - better_assert( r.size() == 2 && "row size should be 2!" ); - better_assert( c.size() == 2 && "col size should be 2!" ); + matrix_private::check_clone_lists( r.size(), c.size() ); auto const [r0, r1] = std::tuple{ *r.begin(), *(r.begin()+1) }; auto const [c0, c1] = std::tuple{ *c.begin(), *(c.begin()+1) }; return clone( r0, r1, c0, c1 ); } - zen_type const clone( size_type const r0, size_type const r1, size_type const c0, size_type const c1 ) const noexcept + [[nodiscard]] zen_type clone( size_type const r0, size_type const r1, size_type const c0, size_type const c1 ) const noexcept { zen_type const& zen = static_cast< zen_type const& >( *this ); zen_type ans{ zen.get_allocator() }; @@ -1918,39 +2925,34 @@ namespace feng typedef Matrix zen_type; typedef crtp_typedef< Type, Alloc > type_proxy_type; typedef typename type_proxy_type::size_type size_type; + typedef typename type_proxy_type::difference_type difference_type; typedef typename type_proxy_type::col_type col_type; typedef typename type_proxy_type::const_col_type const_col_type; typedef typename type_proxy_type::reverse_col_type reverse_col_type; typedef typename type_proxy_type::const_reverse_col_type const_reverse_col_type; col_type col_begin( const size_type index ) noexcept { - zen_type& zen = static_cast< zen_type& >( *this ); - return col_type( zen.row_begin(0) + index, zen.col() ); + return col_range< col_type >( static_cast< zen_type& >( *this ), index, false ); } col_type col_end( const size_type index ) noexcept { - zen_type& zen = static_cast< zen_type& >( *this ); - return col_begin( index ) + zen.row(); + return col_range< col_type >( static_cast< zen_type& >( *this ), index, true ); } const_col_type col_begin( const size_type index ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - return const_col_type( zen.row_begin(0) + index, zen.col() ); + return col_range< const_col_type >( static_cast< zen_type const& >( *this ), index, false ); } const_col_type col_end( const size_type index ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - return zen.col_begin( index ) + zen.row(); + return col_range< const_col_type >( static_cast< zen_type const& >( *this ), index, true ); } const_col_type col_cbegin( const size_type index ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - return const_col_type( zen.begin() + index, zen.col() ); + return col_begin( index ); } const_col_type col_cend( const size_type index ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - return zen.col_begin( index ) + zen.row(); + return col_end( index ); } reverse_col_type col_rbegin( const size_type index = 0 ) noexcept { @@ -1970,17 +2972,22 @@ namespace feng } const_reverse_col_type col_crbegin( const size_type index = 0 ) const noexcept { - return const_reverse_col_type( col_end( index ) ); + return col_rbegin( index ); } const_reverse_col_type col_crend( const size_type index = 0 ) const noexcept { - return const_reverse_col_type( col_begin( index ) ); + return col_rend( index ); + } + private: + // S4-R1: column c starts at offset c with stride col() and row() elements. + template < typename It, typename Z > + static It col_range( Z& zen, size_type index, bool at_end ) noexcept + { + matrix_private::check_column( index, zen.col() ); + return matrix_private::stride_range< It >( std::to_address( zen.data() ), zen.size(), index, static_cast< difference_type >( zen.col() ), zen.row(), at_end ); } }; - template < typename Matrix, typename Type, Allocator Alloc > - using crtp_col_iterator_view = crtp_col_iterator; - template < typename Matrix, typename Type, Allocator Alloc > struct crtp_copy { @@ -1991,7 +2998,26 @@ namespace feng void copy( const Other_Matrix& rhs ) noexcept { zen_type& zen = static_cast< zen_type& >( *this ); - zen.allocator_ = rhs.allocator_; + // S2-R4: copying a matrix onto itself is a no-op that keeps the contents. + if ( static_cast< void const* >( std::addressof( rhs ) ) == static_cast< void const* >( std::addressof( zen ) ) ) return; + // S3 (F04): the destination keeps its own allocator. + // S4-R2 (B-009): a view of the destination is copied through a snapshot taken before resizing. + if constexpr ( requires { rhs.row_stride(); } && std::is_constructible_v< zen_type, Other_Matrix const& > ) + { + if ( rhs.size() != 0 ) + { + void const* const first = static_cast< void const* >( std::addressof( rhs.at( 0, 0 ) ) ); + void const* const lo = static_cast< void const* >( zen.data() ); + void const* const hi = static_cast< void const* >( zen.data() + zen.size() ); + std::less<> const before{}; + if ( !before( first, lo ) && before( first, hi ) ) + { + zen_type const snapshot{ rhs }; + zen.copy( snapshot ); + return; + } + } + } zen.resize( rhs.row(), rhs.col() ); std::copy( rhs.begin(), rhs.end(), zen.begin() );//<- should be overloaded when with cuda_allocator // TODO: copy-and-swap @@ -2018,11 +3044,30 @@ namespace feng better_assert( r1 - r0 == other.row(), " row dim does not match, expected ", other.row(), " rows, but passed parameters are ", r0, " and ", r1 ); better_assert( c1 - c0 == other.col(), " col dim does not match, expected ", other.col(), " cols, but passed parameters are ", c0, " and ", c1 ); + if ( other.row() == 0 || other.col() == 0 ) return; + + // S2-R4: when the source's element range overlaps the destination block, copy a snapshot of the source. + { + auto const* const src_first = static_cast< void const* >( std::addressof( *other.row_begin( 0 ) ) ); + auto const* const src_last = static_cast< void const* >( std::addressof( *( other.row_begin( other.row() - 1 ) + ( other.col() - 1 ) ) ) + 1 ); + auto const* const dst_first = static_cast< void const* >( std::addressof( *( zen.row_begin( r0 ) + c0 ) ) ); + auto const* const dst_last = static_cast< void const* >( std::addressof( *( zen.row_begin( r1 - 1 ) + ( c1 - 1 ) ) ) + 1 ); + std::less<> const before{}; + if ( before( src_first, dst_last ) && before( dst_first, src_last ) ) + { + zen_type snapshot( other.row(), other.col() ); + for ( size_type row_index = 0; row_index != other.row(); ++row_index ) + std::copy( other.row_begin( row_index ), other.row_end( row_index ), snapshot.row_begin( row_index ) ); + zen.copy( snapshot, { r0, r1 }, { c0, c1 } ); + return; + } + } + auto const& copy_function = [&, r0=r0, c0=c0]( size_type const row_index ) { std::copy( other.row_begin(row_index), other.row_end(row_index), zen.row_begin(r0+row_index)+c0 ); }; - matrix_details::parallel( copy_function, 0UL, other.row(), 0UL ); + matrix_details::parallel_work( copy_function, 0UL, other.row(), other.col(), matrix_details::elementwise_grain ); } }; template < typename Matrix, typename Type, Allocator Alloc > @@ -2035,52 +3080,186 @@ namespace feng pointer data() noexcept { zen_type& zen = static_cast< zen_type& >( *this ); - return zen.dat_; + return matrix_private::storage_access::storage( zen ).data(); } const_pointer data() const noexcept { zen_type const& zen = static_cast< zen_type const& >( *this ); - return zen.dat_; + return matrix_private::storage_access::storage( zen ).data(); } }; - template < typename Matrix, typename Type, Allocator Alloc > - struct crtp_det + // ---- linear algebra: status, result and the one LU kernel (S7-R1, S7-R2, D-026, D-027) ---- + // Declared ahead of the crtp members so det() and inverse() can use them. + enum class linalg_status { ok, singular, not_positive_definite, not_converged, nonfinite }; + + template < typename V > + struct linalg_result { - typedef Matrix zen_type; - typedef crtp_typedef< Type, Alloc > type_proxy_type; - typedef typename type_proxy_type::size_type size_type; - typedef typename type_proxy_type::value_type value_type; - typedef typename type_proxy_type::range_type range_type; - value_type det() const noexcept - { - zen_type const& zen = static_cast< zen_type const& >( *this ); - better_assert( zen.row()==zen.col(), " matrix::det(), the row and matrix are supposed to be same, but now row is ", zen.row(), " and col is ", zen.col() ); + V value; + linalg_status status; + constexpr bool ok() const noexcept { return status == linalg_status::ok; } + constexpr explicit operator bool() const noexcept { return ok(); } + }; - if ( 0 == zen.size() ) - { - return value_type{}; - } + // S9-R2 (D-034): the element types linear algebra accepts: floating-point or std::complex. An integral matrix + // fails this constraint; convert it with astype() first. + template < typename T > + concept linalg_element = std::floating_point< T > || matrix_private::is_std_complex_v< T >; - if ( 1 == zen.size() ) - { - return *( zen.begin() ); + namespace matrix_details + { + template < typename T > + struct linalg_real { using type = T; }; + template < typename T > + struct linalg_real< std::complex< T > > { using type = T; }; + template < typename T > + using linalg_real_t = typename linalg_real< T >::type; + + template < typename T > + bool linalg_isfinite( T const& x ) noexcept + { + if constexpr ( matrix_private::is_std_complex_v< T > ) + return std::isfinite( x.real() ) && std::isfinite( x.imag() ); + else + return std::isfinite( x ); + } + + struct lu_info + { + linalg_status status; + std::size_t rank; + int sign; // sign of the row permutation + }; + + // In-place partial-pivoting LU of the row-major n×n array `a`: on return the strict lower triangle holds L + // (unit diagonal implied) and the upper triangle U, with P·A = L·U. `piv` (size n) gets row i of P·A = + // row piv[i] of A. The pivot of column k is the first entry of largest |·| in rows k..n-1; a column that + // is zero there is left as is (no elimination). rank counts |u_kk| > n·ε·max|U| (D-027). + template < typename T > + lu_info lu_in_place( T* a, std::size_t n, std::size_t* piv ) noexcept + { + using R = linalg_real_t< T >; + for ( std::size_t i = 0; i != n; ++i ) piv[i] = i; + for ( std::size_t i = 0; i != n * n; ++i ) + if ( !linalg_isfinite( a[i] ) ) + return lu_info{ linalg_status::nonfinite, 0, 1 }; + int sign = 1; + for ( std::size_t k = 0; k != n; ++k ) + { + std::size_t p = k; + R best = std::abs( a[k * n + k] ); + for ( std::size_t i = k + 1; i != n; ++i ) + if ( R const v = std::abs( a[i * n + k] ); v > best ) { best = v; p = i; } + if ( p != k ) + { + std::swap_ranges( a + k * n, a + k * n + n, a + p * n ); + std::swap( piv[k], piv[p] ); + sign = -sign; + } + if ( best == R{ 0 } ) continue; // zero column: nothing to eliminate + T const pivot = a[k * n + k]; + for ( std::size_t i = k + 1; i != n; ++i ) + { + T* const ri = a + i * n; + T const l = ri[k] / pivot; + ri[k] = l; + if ( l == T{ 0 } ) continue; + T const* const rk = a + k * n; + for ( std::size_t j = k + 1; j != n; ++j ) ri[j] -= l * rk[j]; + } } + for ( std::size_t i = 0; i != n * n; ++i ) // overflow during elimination + if ( !linalg_isfinite( a[i] ) ) return lu_info{ linalg_status::nonfinite, 0, sign }; + R max_u{ 0 }; + for ( std::size_t i = 0; i != n; ++i ) + for ( std::size_t j = i; j != n; ++j ) + max_u = std::max( max_u, R( std::abs( a[i * n + j] ) ) ); + R const tol = static_cast< R >( n ) * std::numeric_limits< R >::epsilon() * max_u; + std::size_t rank = 0; + for ( std::size_t k = 0; k != n; ++k ) + if ( std::abs( a[k * n + k] ) > tol ) ++rank; + return lu_info{ rank < n ? linalg_status::singular : linalg_status::ok, rank, sign }; + } + + // det = sign(P)·∏u_kk; exactly 0 when rank < n, 1 for n = 0, NaN for a nonfinite input or factor + template < typename T > + T lu_det( T const* lu, std::size_t n, lu_info const& info ) noexcept + { + if ( info.status == linalg_status::nonfinite ) return T( std::numeric_limits< linalg_real_t< T > >::quiet_NaN() ); + if ( info.status != linalg_status::ok ) return T{ 0 }; + T d = T( info.sign ); + for ( std::size_t k = 0; k != n; ++k ) d *= lu[k * n + k]; + return d; + } - if ( 4 == zen.size() ) - { - return zen[0][0] * zen[1][1] - zen[1][0] * zen[0][1]; + // Solves A·X = B from the factors of lu_in_place: b is the row-major n×k right-hand side, x (n×k) gets X. + template < typename T > + void lu_solve( T const* lu, std::size_t n, std::size_t const* piv, T const* b, std::size_t k, T* x ) noexcept + { + for ( std::size_t i = 0; i != n; ++i ) + std::copy( b + piv[i] * k, b + piv[i] * k + k, x + i * k ); + for ( std::size_t i = 0; i != n; ++i ) // L·Y = P·B, unit diagonal + for ( std::size_t j = 0; j != i; ++j ) + if ( T const l = lu[i * n + j]; l != T{ 0 } ) + for ( std::size_t c = 0; c != k; ++c ) x[i * k + c] -= l * x[j * k + c]; + for ( std::size_t r = n; r-- != 0; ) // U·X = Y + { + for ( std::size_t j = r + 1; j != n; ++j ) + if ( T const u = lu[r * n + j]; u != T{ 0 } ) + for ( std::size_t c = 0; c != k; ++c ) x[r * k + c] -= u * x[j * k + c]; + T const d = lu[r * n + r]; + for ( std::size_t c = 0; c != k; ++c ) x[r * k + c] /= d; } + } - size_type const n = zen.row(); - size_type const m = n >> 1; - zen_type const P( zen, range_type( 0, m ), range_type( 0, m ) ); - zen_type const Q( zen, range_type( 0, m ), range_type( m, n ) ); - zen_type const R( zen, range_type( m, n ), range_type( 0, m ) ); - zen_type const S( zen, range_type( m, n ), range_type( m, n ) ); - zen_type const& tmp = S - ( R * ( P.inverse() ) * Q ); + template < typename T > + bool all_finite( T const* x, std::size_t n ) noexcept + { + for ( std::size_t i = 0; i != n; ++i ) + if ( !linalg_isfinite( x[i] ) ) return false; + return true; + } - return P.det() * tmp.det(); + // inverse of the square matrix `m` into `out` (resized to n×n only on success); returns the status + template < typename M > + linalg_status lu_inverse( M const& m, M& out ) noexcept + { + using T = typename M::value_type; + std::size_t const n = m.row(); + M lu( m ); + std::vector< std::size_t > piv( n ); + lu_info const info = lu_in_place( lu.data(), n, piv.data() ); + if ( info.status != linalg_status::ok ) return info.status; + M id( n, n ); + std::fill( id.begin(), id.end(), T{ 0 } ); + for ( std::size_t i = 0; i != n; ++i ) id[i][i] = T{ 1 }; + M x( n, n ); + lu_solve( lu.data(), n, piv.data(), id.data(), n, x.data() ); + if ( !all_finite( x.data(), n * n ) ) return linalg_status::nonfinite; + out = std::move( x ); + return linalg_status::ok; + } + }//namespace matrix_details + + template < typename Matrix, typename Type, Allocator Alloc > + struct crtp_det + { + typedef Matrix zen_type; + typedef crtp_typedef< Type, Alloc > type_proxy_type; + typedef typename type_proxy_type::size_type size_type; + typedef typename type_proxy_type::value_type value_type; + typedef typename type_proxy_type::range_type range_type; + // S7-R2: sign(P)·∏u_kk from the one LU kernel; exactly 0 when rank < n, 1 for 0×0 (D-027) + [[nodiscard]] value_type det() const noexcept requires linalg_element< value_type > + { + zen_type const& zen = static_cast< zen_type const& >( *this ); + better_assert( zen.row()==zen.col(), "matrix::det: expecting a square matrix, but got ", zen.row(), "x", zen.col() ); + size_type const n = zen.row(); + zen_type lu( zen ); + std::vector< std::size_t > piv( n ); + auto const info = matrix_details::lu_in_place( lu.data(), n, piv.data() ); + return matrix_details::lu_det( lu.data(), n, info ); } }; template < typename Matrix, typename Type, Allocator Alloc > @@ -2100,36 +3279,27 @@ namespace feng typedef typename type_proxy_type::const_reverse_diag_type const_reverse_diag_type; diag_type upper_diag_begin( const size_type index ) noexcept { - zen_type& zen = static_cast< zen_type& >( *this ); - return diag_type( zen.begin() + index, zen.col() + 1 ); + return upper_diag_range< diag_type >( static_cast< zen_type& >( *this ), index, false ); } diag_type upper_diag_end( const size_type index ) noexcept { - zen_type& zen = static_cast< zen_type& >( *this ); - size_type depth = std::min( zen.col()-index, zen.row() ); - return diag_type( upper_diag_begin( index ) + depth ); + return upper_diag_range< diag_type >( static_cast< zen_type& >( *this ), index, true ); } const_diag_type upper_diag_begin( const size_type index ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - return const_diag_type( zen.begin() + index, zen.col() + 1 ); + return upper_diag_range< const_diag_type >( static_cast< zen_type const& >( *this ), index, false ); } const_diag_type upper_diag_end( const size_type index ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - size_type depth = std::min( zen.col()-index, zen.row() ); - return upper_diag_begin( index ) + depth; + return upper_diag_range< const_diag_type >( static_cast< zen_type const& >( *this ), index, true ); } const_diag_type upper_diag_cbegin( const size_type index ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - return const_diag_type( zen.cbegin() + index, zen.col() + 1 ); + return upper_diag_begin( index ); } const_diag_type upper_diag_cend( const size_type index ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - size_type depth = std::min( zen.col()-index, zen.row() ); - return upper_diag_cbegin( index ) + depth; + return upper_diag_end( index ); } reverse_upper_diag_type upper_diag_rbegin( const size_type index = 0 ) noexcept { @@ -2149,44 +3319,35 @@ namespace feng } const_reverse_upper_diag_type upper_diag_crbegin( const size_type index = 0 ) const noexcept { - return const_reverse_upper_diag_type( upper_diag_end( index ) ); + return upper_diag_rbegin( index ); } const_reverse_upper_diag_type upper_diag_crend( const size_type index = 0 ) const noexcept { - return const_reverse_upper_diag_type( upper_diag_begin( index ) ); + return upper_diag_rend( index ); } diag_type lower_diag_begin( const size_type index ) noexcept { - zen_type& zen = static_cast< zen_type& >( *this ); - return diag_type( zen.begin() + index * zen.col(), zen.col() + 1 ); + return lower_diag_range< diag_type >( static_cast< zen_type& >( *this ), index, false ); } diag_type lower_diag_end( const size_type index ) noexcept { - zen_type& zen = static_cast< zen_type& >( *this ); - size_type depth = std::min( zen.row()-index, zen.col() ); - return lower_diag_begin( index ) + depth; + return lower_diag_range< diag_type >( static_cast< zen_type& >( *this ), index, true ); } const_diag_type lower_diag_begin( const size_type index ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - return const_diag_type( zen.begin() + index * zen.col(), zen.col() + 1 ); + return lower_diag_range< const_diag_type >( static_cast< zen_type const& >( *this ), index, false ); } const_diag_type lower_diag_end( const size_type index ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - size_type depth = std::min( zen.row()-index, zen.col() ); - return lower_diag_begin( index ) + depth; + return lower_diag_range< const_diag_type >( static_cast< zen_type const& >( *this ), index, true ); } const_diag_type lower_diag_cbegin( const size_type index ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - return const_diag_type( zen.begin() + index * zen.col(), zen.col() + 1 ); + return lower_diag_begin( index ); } const_diag_type lower_diag_cend( const size_type index ) const noexcept { - zen_type const& zen = static_cast< zen_type const& >( *this ); - size_type depth = std::min( zen.row()-index, zen.col() ); - return lower_diag_begin( index ) + depth; + return lower_diag_end( index ); } reverse_lower_diag_type lower_diag_rbegin( const size_type index = 0 ) noexcept { @@ -2206,71 +3367,114 @@ namespace feng } const_reverse_lower_diag_type lower_diag_crbegin( const size_type index = 0 ) const noexcept { - return const_reverse_lower_diag_type( lower_diag_end( index ) ); + return lower_diag_rbegin( index ); } const_reverse_lower_diag_type lower_diag_crend( const size_type index = 0 ) const noexcept { - return const_reverse_lower_diag_type( lower_diag_begin( index ) ); + return lower_diag_rend( index ); } diag_type diag_begin( const difference_type index = 0 ) noexcept { - if ( index > 0 ) return upper_diag_begin( index ); - return lower_diag_begin( -index ); + zen_type& zen = static_cast< zen_type& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_diag_begin( static_cast< size_type >( index ) ); + return lower_diag_begin( static_cast< size_type >( -index ) ); } diag_type diag_end( const difference_type index = 0 ) noexcept { - if ( index > 0 ) return upper_diag_end( index ); - return lower_diag_end( -index ); + zen_type& zen = static_cast< zen_type& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_diag_end( static_cast< size_type >( index ) ); + return lower_diag_end( static_cast< size_type >( -index ) ); } const_diag_type diag_begin( const difference_type index = 0 ) const noexcept { - if ( index > 0 ) return upper_diag_begin( index ); - return lower_diag_begin( -index ); + zen_type const& zen = static_cast< zen_type const& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_diag_begin( static_cast< size_type >( index ) ); + return lower_diag_begin( static_cast< size_type >( -index ) ); } const_diag_type diag_end( const difference_type index = 0 ) const noexcept { - if ( index > 0 ) return upper_diag_end( index ); - return lower_diag_end( -index ); + zen_type const& zen = static_cast< zen_type const& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_diag_end( static_cast< size_type >( index ) ); + return lower_diag_end( static_cast< size_type >( -index ) ); } const_diag_type diag_cbegin( const difference_type index = 0 ) const noexcept { - if ( index > 0 ) return upper_diag_cbegin( index ); - return lower_diag_cbegin( -index ); + zen_type const& zen = static_cast< zen_type const& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_diag_cbegin( static_cast< size_type >( index ) ); + return lower_diag_cbegin( static_cast< size_type >( -index ) ); } const_diag_type diag_cend( const difference_type index = 0 ) const noexcept { - if ( index > 0 ) return upper_diag_cend( index ); - return lower_diag_cend( -index ); + zen_type const& zen = static_cast< zen_type const& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_diag_cend( static_cast< size_type >( index ) ); + return lower_diag_cend( static_cast< size_type >( -index ) ); } reverse_diag_type diag_rbegin( const difference_type index = 0 ) noexcept { - if ( index > 0 ) return upper_diag_rbegin( index ); - return lower_diag_rbegin( -index ); + zen_type& zen = static_cast< zen_type& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_diag_rbegin( static_cast< size_type >( index ) ); + return lower_diag_rbegin( static_cast< size_type >( -index ) ); } reverse_diag_type diag_rend( const difference_type index = 0 ) noexcept { - if ( index > 0 ) return upper_diag_rend( index ); - return lower_diag_rend( -index ); + zen_type& zen = static_cast< zen_type& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_diag_rend( static_cast< size_type >( index ) ); + return lower_diag_rend( static_cast< size_type >( -index ) ); } const_reverse_diag_type diag_rbegin( const difference_type index = 0 ) const noexcept { - if ( index > 0 ) return upper_diag_rbegin( index ); - return lower_diag_rbegin( -index ); + zen_type const& zen = static_cast< zen_type const& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_diag_rbegin( static_cast< size_type >( index ) ); + return lower_diag_rbegin( static_cast< size_type >( -index ) ); } const_reverse_diag_type diag_rend( const difference_type index = 0 ) const noexcept { - if ( index > 0 ) return upper_diag_rend( index ); - return lower_diag_rend( -index ); + zen_type const& zen = static_cast< zen_type const& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_diag_rend( static_cast< size_type >( index ) ); + return lower_diag_rend( static_cast< size_type >( -index ) ); } const_reverse_diag_type diag_crbegin( const difference_type index = 0 ) const noexcept { - if ( index > 0 ) return upper_diag_crbegin( index ); - return lower_diag_crbegin( -index ); + zen_type const& zen = static_cast< zen_type const& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_diag_crbegin( static_cast< size_type >( index ) ); + return lower_diag_crbegin( static_cast< size_type >( -index ) ); } const_reverse_diag_type diag_crend( const difference_type index = 0 ) const noexcept { - if ( index > 0 ) return upper_diag_crend( index ); - return lower_diag_crend( -index ); + zen_type const& zen = static_cast< zen_type const& >( *this ); + matrix_private::check_diagonal( index, zen.row(), zen.col() ); + if ( index > 0 ) return upper_diag_crend( static_cast< size_type >( index ) ); + return lower_diag_crend( static_cast< size_type >( -index ) ); + } + private: + // S4-R1: upper diagonal k starts at (0, k), lower diagonal k at (k, 0); stride col()+1. + template < typename It, typename Z > + static It upper_diag_range( Z& zen, size_type index, bool at_end ) noexcept + { + size_type const rows = zen.row(), cols = zen.col(); + matrix_private::check_diagonal( matrix_private::upper_diagonal( index ), rows, cols ); + size_type const length = index < cols ? std::min( cols - index, rows ) : 0; + return matrix_private::stride_range< It >( std::to_address( zen.data() ), zen.size(), index, static_cast< difference_type >( cols ) + 1, length, at_end ); + } + template < typename It, typename Z > + static It lower_diag_range( Z& zen, size_type index, bool at_end ) noexcept + { + size_type const rows = zen.row(), cols = zen.col(); + matrix_private::check_diagonal( matrix_private::lower_diagonal( index ), rows, cols ); + size_type const length = index < rows ? std::min( rows - index, cols ) : 0; + size_type const start = length ? index * cols : 0; + return matrix_private::stride_range< It >( std::to_address( zen.data() ), zen.size(), start, static_cast< difference_type >( cols ) + 1, length, at_end ); } }; template < typename Matrix, typename Type, Allocator Alloc > @@ -2344,6 +3548,26 @@ namespace feng typedef Matrix zen_type; typedef crtp_typedef< Type, Alloc > type_proxy_type; typedef typename type_proxy_type::value_type value_type; + // S6-R5 (D-023): a scalar or matrix of another element type keeps T; the operand is converted as by + // static_cast first (to T's value type for complex T and a real scalar); complex into real is rejected. + template< typename S > requires matrix_details::compound_scalar< S, value_type > + zen_type& operator/=( const S& rhs ) noexcept + { + static_assert( matrix_details::converts_to_element_v< S, value_type >, "feng::matrix operator/=: a complex operand does not convert to a real element type" ); + zen_type& zen = static_cast< zen_type& >( *this ); + auto const v = matrix_details::scalar_as< value_type >( rhs ); + zen.elementwise_apply( [&v]( value_type& x ) { x /= v; } ); + return zen; + } + template< typename U, Allocator B > requires ( !std::same_as< matrix< U, B >, zen_type > ) + zen_type& operator/=( const matrix< U, B >& rhs ) noexcept + { + static_assert( matrix_details::converts_to_element_v< U, value_type >, "feng::matrix operator/=: a complex operand does not convert to a real element type" ); + zen_type& zen = static_cast< zen_type& >( *this ); + zen_type converted{ zen.get_allocator(), rhs.row(), rhs.col() }; + std::transform( rhs.begin(), rhs.end(), converted.begin(), []( U const& x ) noexcept { return static_cast< value_type >( x ); } ); + return zen /= converted; + } zen_type& operator/=( const value_type& rhs ) noexcept { zen_type& zen = static_cast< zen_type& >( *this ); @@ -2353,6 +3577,7 @@ namespace feng zen_type& operator/=( const zen_type& rhs ) noexcept { zen_type& zen = static_cast< zen_type& >( *this ); + FENG_MATRIX_EXPECTS( rhs.row() == rhs.col() && zen.col() == rhs.row(), "operator /=: operand shape mismatch, ", zen.row(), "x", zen.col(), " / ", rhs.row(), "x", rhs.col(), " (the divisor must be square with as many rows as the dividend has columns)" ); zen *= rhs.inverse(); return zen; } @@ -2365,45 +3590,14 @@ namespace feng typedef typename type_proxy_type::size_type size_type; typedef typename type_proxy_type::value_type value_type; typedef typename type_proxy_type::range_type range_type; - // TODO: block-wise inverse here - const zen_type inverse() const noexcept + // S7-R2: the inverse from the LU factors; an empty 0×0 matrix for a singular or nonfinite input (D-026) + [[nodiscard]] zen_type inverse() const noexcept requires linalg_element< value_type > { zen_type const& zen = static_cast< zen_type const& >( *this ); - better_assert( zen.row() == zen.col(), " Expecting square matrix, but now with row = ", zen.row(), " and col = ", zen.col() ); - - // case of empty matrix - if ( zen.size() == 0 ) + better_assert( zen.row() == zen.col(), "matrix::inverse: expecting a square matrix, but got ", zen.row(), "x", zen.col() ); + zen_type ans; + if ( matrix_details::lu_inverse( zen, ans ) != linalg_status::ok ) return zen_type{}; - - if ( zen.size() == 1 ) - return zen_type{ 1, 1, {value_type{1} / zen[0][0]} }; - - if ( zen.size() == 4 ) - { - auto const [a, b, c, d] = std::make_tuple( zen[0][0], zen[0][1], zen[1][0], zen[1][1] ); - value_type const factor = a*d - b*c; - return zen_type{ 2, 2, { d / factor, -b / factor, -c / factor, a / factor } }; - } - - size_type const N = zen.row(); - size_type const n = N >> 1; - zen_type const& A = zen.clone({0, n}, {0, n}); zen_type const& B = zen.clone({0, n}, {n, N}); - zen_type const& C = zen.clone({n, N}, {0, n}); zen_type const& D = zen.clone({n, N}, {n, N}); - - auto const& D_ = D.inverse(); - auto const& D_C = D_ * C; - auto const& BD_C = B * D_C; - auto const& A__BD_C = A - BD_C; - auto const& A__BD_C_ = A__BD_C.inverse(); - auto const& BD_ = B * D_; - auto const& A__BD_C_BD_ = A__BD_C_ * BD_; - - auto const& E = A__BD_C_; auto const& F = -A__BD_C_BD_; - auto const& G = -D_C * A__BD_C_; auto const& H = D_ + D_C * A__BD_C_BD_; - - zen_type ans{ N, N }; - ans.copy( E, {0, n}, {0, n} ); ans.copy( F, {0, n}, {n, N} ); - ans.copy( G, {n, N}, {0, n} ); ans.copy( H, {n, N}, {n, N} ); return ans; } }; @@ -2413,46 +3607,27 @@ namespace feng typedef Matrix zen_type; typedef typename crtp_typedef< Type, Alloc >::value_type value_type; typedef typename crtp_typedef< Type, Alloc >::size_type size_type; - bool load_txt( std::string const& file_name ) noexcept + [[nodiscard]] bool load_txt( std::string const& file_name ) noexcept { return load_txt(file_name.c_str()); } - bool load_txt( const char* const file_name ) noexcept + // S5-R2/S5-R4 (F08, D-011): reads the whole file, parses it into a temporary with this matrix's allocator + // and commits only on success; on failure returns false, leaves the matrix unchanged and prints one stderr + // line naming load_txt and the reason. Never aborts. + [[nodiscard]] bool load_txt( const char* const file_name ) noexcept { zen_type& zen = static_cast< zen_type& >( *this ); - std::ifstream ifs( file_name ); - better_assert( ifs && "matrix::load_txt -- failed to open file" ); - if (!ifs.good()) - { - std::cerr << "matrix::load_txt -- Failed to open file " << file_name << "\n"; + std::vector< std::uint8_t > bytes; + if ( !matrix_details::read_file( file_name, bytes, "matrix::load_txt" ) ) return false; - } - std::stringstream iss; - std::copy( std::istreambuf_iterator< char >( ifs ), std::istreambuf_iterator< char >(), std::ostreambuf_iterator< char >( iss ) ); //TODO: parallel here? - std::string cache = iss.str(); - //for_each( cache.begin(), cache.end(), []( auto & ch ) { if ( ch == ',' || ch == ';' ) ch = ' '; } ); - auto && replace_delimiter_func = [&cache]( size_type idx ){ auto& ch = cache[idx]; ch = (ch==','||ch==';') ? ' ' : ch; }; - matrix_details::parallel( replace_delimiter_func, 0UL, cache.size(), 0UL ); - - iss.str( cache ); - std::vector< value_type > buff; - std::copy( std::istream_iterator< value_type >( iss ), std::istream_iterator< value_type >(), std::back_inserter( buff ) ); // TODO: parallel here? - size_type const total_elements = buff.size(); - const std::string& stream_buff = iss.str(); - size_type const r_ = std::count( stream_buff.begin(), stream_buff.end(), '\n' ); - size_type const r = *( stream_buff.rbegin() ) == '\n' ? r_ : r_ + 1; - size_type const c = total_elements / r; - - if ( r * c != total_elements ) - { - std::cerr << "Error: Failed to match matrix size.\n \tthe size of matrix stored in file \"" << file_name << "\" is " << buff.size() << ".\n"; - std::cerr << " \tthe size of the destination matrix is " << r << " by " << c << " elements.\n"; + zen_type tmp{ zen.get_allocator() }; + char const* why = "invalid text"; + if ( !matrix_details::parse_text< value_type >( reinterpret_cast< char const* >( bytes.data() ), bytes.size(), tmp, &why ) ) + { + std::cerr << "matrix::load_txt -- " << why << ": " << file_name << "\n"; return false; } - - zen.resize( r, c ); - std::copy( buff.begin(), buff.end(), zen.begin() ); - ifs.close(); + zen = std::move( tmp ); return true; } }; @@ -2462,36 +3637,29 @@ namespace feng typedef Matrix zen_type; typedef typename crtp_typedef< Type, Alloc >::value_type value_type; typedef typename crtp_typedef< Type, Alloc >::size_type size_type; - bool load_binary( std::string const& file_name ) noexcept + [[nodiscard]] bool load_binary( std::string const& file_name ) noexcept { return load_binary( file_name.c_str() ); } - bool load_binary( char const* const file_name ) noexcept + // S5-R2/S5-R4 (F08, D-011): reads the whole file, validates the counts against the payload length with + // checked arithmetic and commits only on success; on failure returns false, leaves the matrix unchanged and + // prints one stderr line naming load_binary and the reason. Never aborts. + [[nodiscard]] bool load_binary( char const* const file_name ) noexcept { + static_assert( matrix_details::is_binary_element_v< value_type >, + "load_binary: the native binary format supports arithmetic and std::complex elements only" ); zen_type& zen = static_cast< zen_type& >( *this ); - std::ifstream ifs( file_name, std::ios::binary ); - better_assert( ifs && "matrix::load_binary -- failed to open file" ); - - if ( !ifs ) - return false; - - std::vector< char > buffer{ ( std::istreambuf_iterator< char >( ifs ) ), ( std::istreambuf_iterator< char >() ) }; - better_assert( buffer.size() >= sizeof( size_type ) + sizeof( size_type ) && "matrix::load_library -- file too small, maybe be damaged" ); - - if ( buffer.size() <= sizeof( size_type ) + sizeof( size_type ) ) + std::vector< std::uint8_t > bytes; + if ( !matrix_details::read_file( file_name, bytes, "matrix::load_binary" ) ) return false; - - size_type r; - std::copy( buffer.begin(), buffer.begin() + sizeof( r ), reinterpret_cast< std::int8_t* >( std::addressof( r ) ) ); - size_type c; - std::copy( buffer.begin() + sizeof( r ), buffer.begin() + sizeof( r ) + sizeof( c ), reinterpret_cast< std::int8_t* >( std::addressof( c ) ) ); - zen.resize( r, c ); - better_assert( buffer.size() == sizeof( r ) + sizeof( c ) + sizeof( Type ) * zen.size() && "matrix::load_binary -- data does not match, maybe damaged" ); - - if ( buffer.size() != sizeof( r ) + sizeof( c ) + sizeof( Type ) * zen.size() ) + zen_type tmp{ zen.get_allocator() }; + char const* why = "invalid binary file"; + if ( !matrix_details::parse_binary< value_type >( bytes.data(), bytes.size(), tmp, &why ) ) + { + std::cerr << "matrix::load_binary -- " << why << ": " << file_name << "\n"; return false; - - std::copy( buffer.begin() + sizeof( r ) + sizeof( c ), buffer.end(), reinterpret_cast< std::int8_t* >( zen.data() ) ); + } + zen = std::move( tmp ); return true; } }; @@ -2501,67 +3669,27 @@ namespace feng typedef Matrix zen_type; typedef typename crtp_typedef< Type, Alloc >::value_type value_type; typedef typename crtp_typedef< Type, Alloc >::size_type size_type; - bool load_npy( std::string const& file_name ) noexcept + [[nodiscard]] bool load_npy( std::string const& file_name ) noexcept { return load_npy( file_name.c_str() ); } - bool load_npy( char const* const file_name ) noexcept + // S5-R1/S5-R4 (F07, D-011): reads the whole file, validates and parses it into a temporary with this matrix's + // allocator and commits by move assignment only on success. On failure returns false, leaves the matrix + // unchanged and prints one stderr line naming load_npy and the reason; never aborts. + [[nodiscard]] bool load_npy( char const* const file_name ) noexcept { zen_type& zen = static_cast< zen_type& >( *this ); - std::ifstream ifs( file_name, std::ios::binary ); - better_assert( ifs, "matrix::load_npy -- failed to open file ", file_name ); - - if ( !ifs ) + std::vector< std::uint8_t > bytes; + if ( !matrix_details::read_file( file_name, bytes, "matrix::load_npy" ) ) return false; - - std::vector< char > buffer{ ( std::istreambuf_iterator< char >( ifs ) ), ( std::istreambuf_iterator< char >() ) }; - - //get version - std::size_t const version = *(reinterpret_cast< std::uint8_t* >( buffer.data()+6 )); - - //get header length and header - std::uint32_t header_length; - std::string header; - if ( version == 1 ) //version 1 using 2 bytes - { - std::uint32_t const _l0 = *(reinterpret_cast( buffer.data() + 8 )); - std::uint32_t const _l1 = *(reinterpret_cast( buffer.data() + 9 )); - header_length = (_l1 << 8) + _l0; - header = std::string{ buffer.data() + 10, buffer.data() + 10 + header_length }; - } - else //version 2/3 using 4 bytes + zen_type tmp{ zen.get_allocator() }; + char const* why = "invalid NPY file"; + if ( !matrix_details::parse_npy< value_type >( bytes.data(), bytes.size(), tmp, &why ) ) { - std::uint32_t const _l0 = *(reinterpret_cast( buffer.data() + 8 )); - std::uint32_t const _l1 = *(reinterpret_cast( buffer.data() + 9 )); - std::uint32_t const _l2 = *(reinterpret_cast( buffer.data() + 10 )); - std::uint32_t const _l3 = *(reinterpret_cast( buffer.data() + 11 )); - header_length = (_l3 << 24) + (_l2 << 16) + (_l1 << 8) + _l0; - header = std::string{ buffer.data() + 12, buffer.data() + 12 + header_length }; + std::cerr << "matrix::load_npy -- " << why << ": " << file_name << "\n"; + return false; } - - // fortran format or not - bool const row_major = ( header.find("T") != std::string::npos ) ? false : true; - - //extract row and column - std::size_t const shape_pos = header.find("'shape': ("); - std::size_t const row_pos = shape_pos + 10; //start of row - std::size_t const row_pos_end = header.find( ",", row_pos ); //end of row - std::string const row_string = header.substr( row_pos, row_pos_end - row_pos ); - std::size_t const row = std::stoul( row_string ); - std::size_t const col_pos = row_pos_end + 1; //start of col - std::size_t const col_pos_end = header.find( ")", col_pos ); //end of col - std::string const col_string = header.substr( col_pos, col_pos_end - col_pos ); - std::size_t const col = std::stoul( col_string ); - - //resize matrix - zen.resize( row, col ); - if (!row_major) - zen.reshape( col, row ); - - //copy binary value - std::size_t const data_offset = (version==1) ? (10 + header_length) : (12 +header_length); - std::copy_n( reinterpret_cast(buffer.data()+data_offset), row*col, zen.data() ); - + zen = std::move( tmp ); return true; } };//struct crtp_load_npy @@ -2573,6 +3701,26 @@ namespace feng typedef typename type_proxy_type::value_type value_type; typedef typename type_proxy_type::size_type size_type; + // S6-R5 (D-023): a scalar or matrix of another element type keeps T; the operand is converted as by + // static_cast first (to T's value type for complex T and a real scalar); complex into real is rejected. + template< typename S > requires matrix_details::compound_scalar< S, value_type > + zen_type& operator-=( const S& rhs ) noexcept + { + static_assert( matrix_details::converts_to_element_v< S, value_type >, "feng::matrix operator-=: a complex operand does not convert to a real element type" ); + zen_type& zen = static_cast< zen_type& >( *this ); + auto const v = matrix_details::scalar_as< value_type >( rhs ); + zen.elementwise_apply( [&v]( value_type& x ) { x -= v; } ); + return zen; + } + template< typename U, Allocator B > requires ( !std::same_as< matrix< U, B >, zen_type > ) + zen_type& operator-=( const matrix< U, B >& rhs ) noexcept + { + static_assert( matrix_details::converts_to_element_v< U, value_type >, "feng::matrix operator-=: a complex operand does not convert to a real element type" ); + zen_type& zen = static_cast< zen_type& >( *this ); + zen_type converted{ zen.get_allocator(), rhs.row(), rhs.col() }; + std::transform( rhs.begin(), rhs.end(), converted.begin(), []( U const& x ) noexcept { return static_cast< value_type >( x ); } ); + return zen -= converted; + } zen_type& operator-=( const value_type& rhs ) noexcept { zen_type& zen = static_cast< zen_type& >( *this ); @@ -2583,16 +3731,152 @@ namespace feng zen_type& operator-=( const zen_type& rhs ) noexcept { zen_type& zen = static_cast< zen_type& >( *this ); + FENG_MATRIX_EXPECTS( zen.row() == rhs.row() && zen.col() == rhs.col(), "operator -=: operand shape mismatch, ", zen.row(), "x", zen.col(), " -= ", rhs.row(), "x", rhs.col() ); auto v = zen.data(); auto x = rhs.data(); auto const& elementwise_minus = [v, x]( size_type offset ) { v[offset] -= x[offset]; }; - matrix_details::parallel( elementwise_minus, 0UL, zen.size(), 0UL ); + matrix_details::parallel_work( elementwise_minus, 0UL, zen.size(), 1, matrix_details::elementwise_grain ); return zen; } }; + + namespace matrix_details + { + // S10-R3 (D-008): the GEMM kernel behind operator*= and direct_multiply. C = A * B on row-major contiguous + // arrays (A is m x k, B is k x n, C is m x n). Every C entry is computed as ( ( 0 + a0*b0 ) + a1*b1 ) + ... + // with k ascending, the order std::inner_product used before S10, so results are bit-identical to + // gemm_reference for every element type (under the portable flags, which do not contract a*b+c into FMA). + inline constexpr std::size_t gemm_k_block = 128; + inline constexpr std::size_t gemm_n_block = 256; + + // Rows [i0, i1) of C. B rows are read contiguously (i-k-j order), four C rows share each B load, and the + // k and j loops are blocked so a B panel stays in cache; narrow B (n < 4) uses a row-dot path instead. + template < typename T > + void gemm_rows( T const* __restrict a, T const* __restrict b, T* __restrict c, std::size_t i0, std::size_t i1, std::size_t K, std::size_t N ) noexcept + { + if ( !( i0 < i1 ) || N == 0 ) return; + std::fill( c + i0 * N, c + i1 * N, T( 0 ) ); + if ( K == 0 ) return; + if ( N < 4 ) + { + std::size_t i = i0; + for ( ; i + 4 <= i1; i += 4 ) + { + T const* __restrict a0 = a + i * K; + T const* __restrict a1 = a0 + K; + T const* __restrict a2 = a1 + K; + T const* __restrict a3 = a2 + K; + for ( std::size_t j = 0; j != N; ++j ) + { + T s0 = c[i * N + j], s1 = c[( i + 1 ) * N + j], s2 = c[( i + 2 ) * N + j], s3 = c[( i + 3 ) * N + j]; + for ( std::size_t k = 0; k != K; ++k ) + { + T const bk = b[k * N + j]; + s0 = s0 + a0[k] * bk; + s1 = s1 + a1[k] * bk; + s2 = s2 + a2[k] * bk; + s3 = s3 + a3[k] * bk; + } + c[i * N + j] = s0; + c[( i + 1 ) * N + j] = s1; + c[( i + 2 ) * N + j] = s2; + c[( i + 3 ) * N + j] = s3; + } + } + for ( ; i != i1; ++i ) + { + T const* __restrict a0 = a + i * K; + for ( std::size_t j = 0; j != N; ++j ) + { + T s0 = c[i * N + j]; + for ( std::size_t k = 0; k != K; ++k ) + s0 = s0 + a0[k] * b[k * N + j]; + c[i * N + j] = s0; + } + } + return; + } + for ( std::size_t jb = 0; jb < N; jb += gemm_n_block ) + { + std::size_t const je = std::min( N, jb + gemm_n_block ); + for ( std::size_t kb = 0; kb < K; kb += gemm_k_block ) + { + std::size_t const ke = std::min( K, kb + gemm_k_block ); + std::size_t i = i0; + for ( ; i + 4 <= i1; i += 4 ) + { + T* __restrict c0 = c + i * N; + T* __restrict c1 = c0 + N; + T* __restrict c2 = c1 + N; + T* __restrict c3 = c2 + N; + T const* __restrict ar = a + i * K; + for ( std::size_t k = kb; k != ke; ++k ) + { + T const x0 = ar[k], x1 = ar[K + k], x2 = ar[2 * K + k], x3 = ar[3 * K + k]; + T const* __restrict bk = b + k * N; + for ( std::size_t j = jb; j != je; ++j ) + { + T const y = bk[j]; + c0[j] = c0[j] + x0 * y; + c1[j] = c1[j] + x1 * y; + c2[j] = c2[j] + x2 * y; + c3[j] = c3[j] + x3 * y; + } + } + } + for ( ; i != i1; ++i ) + { + T* __restrict c0 = c + i * N; + T const* __restrict ar = a + i * K; + for ( std::size_t k = kb; k != ke; ++k ) + { + T const x0 = ar[k]; + T const* __restrict bk = b + k * N; + for ( std::size_t j = jb; j != je; ++j ) + c0[j] = c0[j] + x0 * bk[j]; + } + } + } + } + } + + // C = A * B with the rows of C split into contiguous chunks over `workers` threads (parallel_workers; 0 or + // 1 runs serially on the caller). Each entry is computed the same way whatever the split. + template < typename T > + void gemm_blocked( T const* a, T const* b, T* c, std::size_t M, std::size_t K, std::size_t N, std::size_t workers ) noexcept + { + std::size_t const w = effective_workers( std::size_t{ 0 }, M, workers ); + if ( w <= 1 ) + { + gemm_rows( a, b, c, 0, M, K, N ); + return; + } + auto const chunk = [&]( std::size_t k ) noexcept + { + auto const [first, last] = chunk_bounds( std::size_t{ 0 }, M, w, k ); + gemm_rows( a, b, c, first, last, K, N ); + }; + parallel_workers( chunk, std::size_t{ 0 }, w, w ); + } + + // The pre-S10 kernel, kept as the reference and fallback: each C entry is a strided std::inner_product of + // an A row and a B column from value_type( 0 ). The result keeps a's allocator. + template < typename Matrix > + Matrix gemm_reference( Matrix const& a, Matrix const& b ) noexcept + { + better_assert( a.col() == b.row() && "gemm_reference: dimesion not match!", "gemm_reference: operand shape mismatch, ", a.row(), "x", a.col(), " * ", b.row(), "x", b.col() ); + using value_type = typename Matrix::value_type; + Matrix tmp( a.get_allocator(), a.row(), b.col() ); + for ( std::size_t i = 0; i != tmp.row(); ++i ) + for ( std::size_t j = 0; j != tmp.col(); ++j ) + tmp[i][j] = std::inner_product( a.row_begin( i ), a.row_end( i ), b.col_begin( j ), value_type( 0 ) ); + return tmp; + } + }//namespace matrix_details + template < typename Matrix, typename Type, Allocator Alloc > struct crtp_multiply_equal_operator { @@ -2601,6 +3885,26 @@ namespace feng typedef typename type_proxy_type::value_type value_type; typedef typename type_proxy_type::size_type size_type; typedef typename type_proxy_type::range_type range_type; + // S6-R5 (D-023): a scalar or matrix of another element type keeps T; the operand is converted as by + // static_cast first (to T's value type for complex T and a real scalar); complex into real is rejected. + template< typename S > requires matrix_details::compound_scalar< S, value_type > + zen_type& operator*=( const S& rhs ) noexcept + { + static_assert( matrix_details::converts_to_element_v< S, value_type >, "feng::matrix operator*=: a complex operand does not convert to a real element type" ); + zen_type& zen = static_cast< zen_type& >( *this ); + auto const v = matrix_details::scalar_as< value_type >( rhs ); + zen.elementwise_apply( [&v]( value_type& x ) { x *= v; } ); + return zen; + } + template< typename U, Allocator B > requires ( !std::same_as< matrix< U, B >, zen_type > ) + zen_type& operator*=( const matrix< U, B >& rhs ) noexcept + { + static_assert( matrix_details::converts_to_element_v< U, value_type >, "feng::matrix operator*=: a complex operand does not convert to a real element type" ); + zen_type& zen = static_cast< zen_type& >( *this ); + zen_type converted{ zen.get_allocator(), rhs.row(), rhs.col() }; + std::transform( rhs.begin(), rhs.end(), converted.begin(), []( U const& x ) noexcept { return static_cast< value_type >( x ); } ); + return zen *= converted; + } zen_type& operator*=( const value_type& rhs ) noexcept { zen_type& zen = static_cast< zen_type& >( *this ); @@ -2612,15 +3916,12 @@ namespace feng zen_type& direct_multiply( const zen_type& other ) noexcept { zen_type& zen = static_cast< zen_type& >( *this ); - better_assert( zen.col() == other.row() && "direct_multiply: dimesion not match!" ); - zen_type tmp( zen.row(), other.col() ); - - auto const& func = [&]( size_type i ) - { - for ( size_type j = 0; j != tmp.col(); ++j ) - tmp[i][j] = std::inner_product( zen.row_begin( i ), zen.row_end( i ), other.col_begin( j ), value_type( 0 ) ); - }; - matrix_details::parallel( func, 0UL, tmp.row(), 0UL ); + better_assert( zen.col() == other.row() && "direct_multiply: dimesion not match!", "direct_multiply: operand shape mismatch, ", zen.row(), "x", zen.col(), " * ", other.row(), "x", other.col() ); + zen_type tmp( zen.get_allocator(), zen.row(), other.col() ); // S3-R1: the product stays on this matrix's resource + // S10-R3: the blocked kernel, rows split over work_workers( M*K*N, gemm_grain ) workers (gemm_blocked caps + // them at M); the strided inner_product kernel it replaced is matrix_details::gemm_reference. + std::size_t const work = matrix_details::saturating_work( matrix_details::saturating_work( zen.row(), zen.col() ), other.col() ); + matrix_details::gemm_blocked( zen.data(), other.data(), tmp.data(), zen.row(), zen.col(), other.col(), matrix_details::work_workers( work, matrix_details::gemm_grain ) ); zen.swap( tmp ); return zen; } @@ -2717,55 +4018,10 @@ namespace feng zen_type& operator*=( const zen_type& other ) noexcept { zen_type& zen = static_cast< zen_type& >( *this ); - better_assert( zen.col() == other.row() && "operator *= :: the matrix dims not match!" ); - - if constexpr( parallel_mode ) - return direct_multiply( other ); - - static const size_type threshold = 17; - const size_type max_dims = std::max( std::max( zen.row(), zen.col() ), other.col() ); - const size_type min_dims = std::min( std::min( zen.row(), zen.col() ), other.col() ); - - if ( ( max_dims < threshold ) || ( min_dims == 1 ) ) - { - return direct_multiply( other ); - } - - const size_type R = zen.row(); - const size_type C = zen.col(); - const size_type OC = other.col(); - - if ( R & 1 ) - { - if ( R & 2 ) - { - return rr1( other ); - } - - return rr2( other ); - } - - if ( C & 1 ) - { - if ( C & 2 ) - { - return cc1( other ); - } - - return cc2( other ); - } - - if ( OC & 1 ) - { - if ( OC & 2 ) - { - return oc1( other ); - } - - return oc2( other ); - } - - return strassen_multiply( other ); + better_assert( zen.col() == other.row() && "operator *= :: the matrix dims not match!", "operator *=: operand shape mismatch, ", zen.row(), "x", zen.col(), " * ", other.row(), "x", other.col() ); + // S10-R3: every shape goes through the blocked kernel in serial and parallel builds; Strassen + // (strassen_multiply, rr1 ... oc2) is no longer dispatched but stays callable. + return direct_multiply( other ); } }; template < typename Matrix, typename Type, Allocator Alloc > @@ -2775,6 +4031,26 @@ namespace feng typedef crtp_typedef< Type, Alloc > type_proxy_type; typedef typename type_proxy_type::value_type value_type; typedef typename type_proxy_type::size_type size_type; + // S6-R5 (D-023): a scalar or matrix of another element type keeps T; the operand is converted as by + // static_cast first (to T's value type for complex T and a real scalar); complex into real is rejected. + template< typename S > requires matrix_details::compound_scalar< S, value_type > + zen_type& operator+=( const S& rhs ) noexcept + { + static_assert( matrix_details::converts_to_element_v< S, value_type >, "feng::matrix operator+=: a complex operand does not convert to a real element type" ); + zen_type& zen = static_cast< zen_type& >( *this ); + auto const v = matrix_details::scalar_as< value_type >( rhs ); + zen.elementwise_apply( [&v]( value_type& x ) { x += v; } ); + return zen; + } + template< typename U, Allocator B > requires ( !std::same_as< matrix< U, B >, zen_type > ) + zen_type& operator+=( const matrix< U, B >& rhs ) noexcept + { + static_assert( matrix_details::converts_to_element_v< U, value_type >, "feng::matrix operator+=: a complex operand does not convert to a real element type" ); + zen_type& zen = static_cast< zen_type& >( *this ); + zen_type converted{ zen.get_allocator(), rhs.row(), rhs.col() }; + std::transform( rhs.begin(), rhs.end(), converted.begin(), []( U const& x ) noexcept { return static_cast< value_type >( x ); } ); + return zen += converted; + } zen_type& operator+=( const value_type& rhs ) noexcept { zen_type& zen = static_cast< zen_type& >( *this ); @@ -2786,6 +4062,7 @@ namespace feng zen_type& operator+=( const zen_type& rhs ) noexcept { zen_type& zen = static_cast< zen_type& >( *this ); + FENG_MATRIX_EXPECTS( zen.row() == rhs.row() && zen.col() == rhs.col(), "operator +=: operand shape mismatch, ", zen.row(), "x", zen.col(), " += ", rhs.row(), "x", rhs.col() ); auto x = zen.data(); auto y = rhs.data(); @@ -2793,7 +4070,7 @@ namespace feng { x[offset] += y[offset]; }; - matrix_details::parallel( elementwise_add, 0UL, zen.size(), 0UL ); + matrix_details::parallel_work( elementwise_add, 0UL, zen.size(), 1, matrix_details::elementwise_grain ); return zen; } }; @@ -2804,7 +4081,7 @@ namespace feng typedef crtp_typedef< Type, Alloc > type_proxy_type; typedef typename type_proxy_type::size_type size_type; typedef typename type_proxy_type::value_type value_type; - const zen_type operator-() const noexcept + zen_type operator-() const noexcept { zen_type const& zen = static_cast< zen_type const& >( *this ); zen_type ans{ zen }; @@ -2814,7 +4091,7 @@ namespace feng { x[offset] = -x[offset]; }; - matrix_details::parallel( minus_function, 0UL, ans.size(), 0UL ); + matrix_details::parallel_work( minus_function, 0UL, ans.size(), 1, matrix_details::elementwise_grain ); return ans; } }; @@ -2825,7 +4102,7 @@ namespace feng typedef crtp_typedef< Type, Alloc > type_proxy_type; typedef typename type_proxy_type::value_type value_type; typedef typename type_proxy_type::size_type size_type; - const zen_type operator+() const noexcept + zen_type operator+() const noexcept { return static_cast< zen_type const& >( *this ); } @@ -2839,9 +4116,11 @@ namespace feng zen_type& reshape( const size_type new_row, const size_type new_col ) noexcept { zen_type& zen = static_cast< zen_type& >( *this ); - better_assert( new_row * new_col == zen.row() * zen.col() && "error: size before and after reshape does not agree, use resize() instead!" ); - zen.row_ = new_row; - zen.col_ = new_col; + constexpr size_type size_max = std::numeric_limits< size_type >::max(); + FENG_MATRIX_EXPECTS( new_row == 0 || new_col <= size_max / new_row, "matrix reshape: rows*cols overflows, rows = ", new_row, ", cols = ", new_col ); + FENG_MATRIX_EXPECTS( new_row * new_col == zen.size(), "matrix reshape: size before and after reshape does not agree, use resize() instead; size() = ", zen.size(), ", rows = ", new_row, ", cols = ", new_col ); + matrix_private::storage_access::rows( zen ) = new_row; + matrix_private::storage_access::cols( zen ) = new_col; return zen; } }; @@ -2856,7 +4135,7 @@ namespace feng { zen_type& zen = static_cast< zen_type& >( *this ); - if ( zen.size() == new_row * new_col ) + if ( zen.size() == matrix_private::checked_count( zen.get_allocator(), new_row, new_col ) ) { zen.reshape( new_row, new_col ); return zen; @@ -2877,39 +4156,17 @@ namespace feng size_type row() const noexcept { zen_type const& zen = static_cast< zen_type const& >( *this ); - return zen.row_; - } - size_type col() const noexcept - { - zen_type const& zen = static_cast< zen_type const& >( *this ); - return zen.col_; - } - size_type size() const noexcept - { - return row()*col(); - } - }; - - template < typename Matrix, typename Type, Allocator Alloc > - struct crtp_row_col_size_view - { - typedef Matrix zen_type; - typedef crtp_typedef< Type, Alloc > type_proxy_type; - typedef typename type_proxy_type::size_type size_type; - size_type row() const noexcept - { - zen_type const& zen = static_cast< zen_type const& >( *this ); - return zen.row_dim_.second - zen.row_dim_.first; + return matrix_private::storage_access::rows( zen ); } size_type col() const noexcept { zen_type const& zen = static_cast< zen_type const& >( *this ); - return zen.col_dim_.second - zen.col_dim_.first; + return matrix_private::storage_access::cols( zen ); } size_type size() const noexcept { zen_type const& zen = static_cast< zen_type const& >( *this ); - return zen.row()*zen.col(); + return matrix_private::storage_access::storage( zen ).size(); } }; @@ -2981,101 +4238,94 @@ namespace feng }; template < typename Matrix, typename Type, Allocator Alloc > - struct crtp_row_iterator_view : crtp_row_iterator + struct crtp_save_as_txt { typedef Matrix zen_type; - typedef crtp_typedef< Type, Alloc > type_proxy_type; - typedef typename type_proxy_type::size_type size_type; - typedef typename type_proxy_type::row_type row_type; - typedef typename type_proxy_type::const_row_type const_row_type; - typedef typename type_proxy_type::reverse_row_type reverse_row_type; - typedef typename type_proxy_type::const_reverse_row_type const_reverse_row_type; -/* - auto row_begin( const size_type index = 0 ) noexcept + typedef Type value_type; + [[nodiscard]] bool save_as_txt( std::string const& file_name ) const noexcept { - zen_type& zen = static_cast< zen_type& >( *this ); - size_type const row_offset = zen.row_dim_.first; - size_type const col_offset = zen.col_dim_.first; - return zen.matrix_.row_begin(row_offset+index) + col_offset; + return save_as_txt( file_name.c_str() ); } - */ - auto row_begin( const size_type index = 0 ) const noexcept + // S5-R4 (D-011): false with one stderr line on a directory, open, write or close failure; never aborts. + [[nodiscard]] bool save_as_txt( char const * const file_name ) const noexcept { zen_type const& zen = static_cast< zen_type const& >( *this ); - size_type const row_offset = zen.row_dim_.first; - size_type const col_offset = zen.col_dim_.first; - return zen.matrix_.row_cbegin(row_offset+index) + col_offset; + if ( file_name == nullptr ) return matrix_details::write_failed( "matrix::save_as_txt", "no file name given", "" ); + return matrix_details::write_stream( "matrix::save_as_txt", std::string{ file_name }, std::ios_base::out, + [&zen]( std::ofstream& ofs ) { matrix_details::write_text( ofs, zen ); } ); // S5-R2: loads back through load_txt } - - auto row_cbegin( const size_type index = 0 ) const noexcept + }; + template < typename Matrix, typename Type, Allocator Alloc > + struct crtp_save_as_binary + { + typedef Matrix zen_type; + typedef Type value_type; + [[nodiscard]] bool save_as_binary( std::string const& file_name ) const noexcept + { + return save_as_binary( file_name.c_str() ); + } + [[nodiscard]] bool save_as_binary( char const* const file_name ) const noexcept { + static_assert( matrix_details::is_binary_element_v< Type >, + "save_as_binary: the native binary format supports arithmetic and std::complex elements only" ); zen_type const& zen = static_cast< zen_type const& >( *this ); - size_type const row_offset = zen.row_dim_.first; - size_type const col_offset = zen.col_dim_.first; - return zen.matrix_.row_cbegin(row_offset+index) + col_offset; + if ( file_name == nullptr ) return matrix_details::write_failed( "matrix::save_as_binary", "no file name given", "" ); + // S5-R4 (D-011): false with one stderr line on a directory, open, write or close failure; never aborts. + return matrix_details::write_stream( "matrix::save_as_binary", std::string{ file_name }, std::ios_base::out | std::ios_base::binary, + [&zen]( std::ofstream& ofs ) + { + auto const r = zen.row(); + ofs.write( reinterpret_cast< char const* >( std::addressof( r ) ), sizeof( r ) ); + auto const c = zen.col(); + ofs.write( reinterpret_cast< char const* >( std::addressof( c ) ), sizeof( c ) ); + ofs.write( reinterpret_cast< char const* >( zen.data() ), static_cast< std::streamsize >( sizeof( Type ) * zen.size() ) ); + } ); } - }; template < typename Matrix, typename Type, Allocator Alloc > - struct crtp_save_as_txt + struct crtp_save_as_npy { typedef Matrix zen_type; typedef Type value_type; - bool save_as_txt( std::string const& file_name ) const noexcept + [[nodiscard]] bool save_as_npy( std::string const& file_name ) const noexcept { - return save_as_txt( file_name.c_str() ); + return save_as_npy( file_name.c_str() ); } - bool save_as_txt( char const * const file_name ) const noexcept + // D-022: a v1.0 C-order NPY file with the D-021 dtype of Type and the native-order payload; S5-R4 (D-011): + // false with one stderr line on a directory, open, write or close failure; never aborts. + [[nodiscard]] bool save_as_npy( char const* const file_name ) const noexcept { + constexpr char kind = matrix_details::npy_dtype< Type >::kind; + static_assert( kind != '\0', "save_as_npy: the element type has no NPY dtype (D-021)" ); zen_type const& zen = static_cast< zen_type const& >( *this ); - - if ( !matrix_details::create_directory_if_not_present( file_name ) ) - better_assert( !"save_as_txt", " failed to create parent directory: ", " with the target file name ", file_name ); - - std::ofstream ofs( file_name ); - better_assert( ofs, " with the target file name is ", file_name ); - - if ( !ofs ) return false; - - ofs.precision( 16 ); - ofs << zen; - ofs.close(); - return true; - } - }; - template < typename Matrix, typename Type, Allocator Alloc > - struct crtp_save_as_binary - { - typedef Matrix zen_type; - typedef Type value_type; - bool save_as_binary( std::string const& file_name ) const - { - return save_as_binary( file_name.c_str() ); - } - bool save_as_binary( char const* const file_name ) const - { - zen_type const& zen = static_cast< zen_type const& >( *this ); - - if ( !matrix_details::create_directory_if_not_present( file_name ) ) - better_assert( !"save_as_binary", " failed to create directory: ", " with the target file name ", file_name ); - - std::ofstream ofs( file_name, std::ios::out | std::ios::binary ); - better_assert( ofs, " save_as_binary failed to create file: ", " with the target file name ", file_name ); - - if ( !ofs ) return false; - - auto const r = zen.row(); - ofs.write( reinterpret_cast< char const* >( std::addressof( r ) ), sizeof( r ) ); - auto const c = zen.col(); - ofs.write( reinterpret_cast< char const* >( std::addressof( c ) ), sizeof( c ) ); - ofs.write( reinterpret_cast< char const* >( zen.data() ), sizeof( Type ) * zen.size() ); - - better_assert( ofs.good(), " save_as_binary failed to write: ", " with the target file name ", file_name ); - if ( !ofs.good() ) return false; - - ofs.close(); - return true; + if ( file_name == nullptr ) return matrix_details::write_failed( "matrix::save_as_npy", "no file name given", "" ); + + char const order = sizeof( Type ) == 1 ? '|' : ( std::endian::native == std::endian::little ? '<' : '>' ); + std::string header{ "{'descr': '" }; + header += order; + header += kind; + header += std::to_string( sizeof( Type ) ); + header += "', 'fortran_order': False, 'shape': ("; + header += std::to_string( zen.row() ); + header += ", "; + header += std::to_string( zen.col() ); + header += "), }"; + std::size_t const unpadded = 10 + header.size() + 1; // magic, version, length, header, '\n' + header.append( ( 64 - unpadded % 64 ) % 64, ' ' ); + header += '\n'; + std::size_t const header_len = header.size(); // at most a few hundred bytes, fits the v1.0 u16 length + + return matrix_details::write_stream( "matrix::save_as_npy", std::string{ file_name }, std::ios_base::out | std::ios_base::binary, + [&]( std::ofstream& ofs ) + { + char const preamble[10] = { '\x93', 'N', 'U', 'M', 'P', 'Y', '\x01', '\x00', + static_cast< char >( header_len & 0xff ), + static_cast< char >( ( header_len >> 8 ) & 0xff ) }; + ofs.write( preamble, 10 ); + ofs.write( header.data(), static_cast< std::streamsize >( header_len ) ); + ofs.write( reinterpret_cast< char const* >( zen.data() ), static_cast< std::streamsize >( sizeof( Type ) * zen.size() ) ); + } ); } }; @@ -3083,7 +4333,7 @@ namespace feng struct crtp_plot { typedef Matrix zen_type; - bool plot( std::string const& file_name, std::string const& color_map=std::string{"parula"} ) const noexcept + [[nodiscard]] bool plot( std::string const& file_name, std::string const& color_map=std::string{"parula"} ) const noexcept { zen_type const& zen = static_cast< zen_type const& >( *this ); return zen.save_as_bmp( file_name, color_map ); @@ -3093,11 +4343,26 @@ namespace feng namespace { // adapted from https://github.com/miloyip/svpng/blob/master/svpng.inc - inline static void save_png( std::uint8_t* img, unsigned w, unsigned h, int alpha, char const* const file_name ) noexcept - { + // S5-R4 (D-011): returns false, with the reason in `*why`, when the size does not fit the format (width or + // height above 2^31-1, a row or the IDAT chunk length overflowing 32 bits; checked before opening), or when + // fopen, a write or fclose fails. Never aborts. + inline static bool save_png( std::uint8_t const* img, std::uint64_t width, std::uint64_t height, int alpha, char const* const file_name, char const** why = nullptr ) noexcept + { + auto const fail = [why]( char const* reason ) noexcept { if ( why ) *why = reason; return false; }; + constexpr std::uint64_t png_max = 0x7fffffffULL; + if ( width > png_max || height > png_max ) return fail( "image too large for PNG" ); + std::uint64_t row_bytes = 0, idat = 0; + if ( !matrix_details::checked_mul( width, std::uint64_t{ alpha ? 4U : 3U }, row_bytes ) || ++row_bytes > std::numeric_limits< unsigned >::max() ) + return fail( "PNG row size overflows" ); + if ( !matrix_details::checked_mul( height, row_bytes + 5, idat ) || !matrix_details::checked_add( idat, std::uint64_t{ 6 }, idat ) || idat > png_max ) + return fail( "PNG image data size overflows" ); + if ( file_name == nullptr ) return fail( "no file name given" ); + constexpr unsigned t[] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; - unsigned a = 1, b = 0, c, p = w * ( alpha ? 4 : 3 ) + 1, x, y, i; + unsigned const w = static_cast< unsigned >( width ), h = static_cast< unsigned >( height ); + unsigned a = 1, b = 0, c, p = static_cast< unsigned >( row_bytes ), x, y, i; FILE* fp = fopen( file_name, "wb" ); + if ( fp == nullptr ) return fail( "failed to open file" ); for ( i = 0; i < 8; i++ ) fputc( ( "\x89PNG\r\n\32\n" )[i], fp );; @@ -3344,7 +4609,10 @@ namespace feng fputc( ( ~c ) & 255, fp ); } - fclose( fp ); + bool const written = ferror( fp ) == 0; + bool const closed = fclose( fp ) == 0; + if ( !written || !closed ) return fail( "failed to write or close file" ); + return true; }//save_png } @@ -3352,7 +4620,7 @@ namespace feng struct crtp_save_as_png { typedef Matrix zen_type; - bool save_as_png( const std::string& file_name, std::string const& color_map = std::string{ "parula" } ) const + [[nodiscard]] bool save_as_png( const std::string& file_name, std::string const& color_map = std::string{ "parula" } ) const noexcept { zen_type const& zen = static_cast< zen_type const& >( *this ); better_assert( zen.row() && "save_as_png: matrix row cannot be zero" ); @@ -3363,9 +4631,11 @@ namespace feng auto&& selected_map = ( *( color_maps.find( map_name ) ) ).second; auto const [the_row, the_col] = zen.shape(); - matrix> channel_r{ the_row, the_col }; - matrix> channel_g{ the_row, the_col }; - matrix> channel_b{ the_row, the_col }; + // dependent on Type so the lookup waits until matrix is complete (F10); same type as before + using byte_matrix = matrix, std::allocator, void>>; + byte_matrix channel_r{ the_row, the_col }; + byte_matrix channel_g{ the_row, the_col }; + byte_matrix channel_b{ the_row, the_col }; auto const& [mn, mx] = zen.minmax(); std::vector cache; @@ -3381,8 +4651,10 @@ namespace feng cache.push_back( b_ ); } - save_png( cache.data(), the_col, the_row, 0, file_name.c_str() ); - + // S5-R4 (D-011): an oversized image, an open, write or close failure returns false with one stderr line + char const* why = ""; + if ( !save_png( cache.data(), the_col, the_row, 0, file_name.c_str(), &why ) ) + return matrix_details::write_failed( "matrix::save_as_png", why, file_name ); return true; } };//struct crtp_save_as_png @@ -3392,7 +4664,7 @@ namespace feng struct crtp_save_as_bmp { typedef Matrix zen_type; - bool save_as_bmp( const std::string& file_name, std::string const& color_map = std::string{ "parula" } ) const + [[nodiscard]] bool save_as_bmp( const std::string& file_name, std::string const& color_map = std::string{ "parula" } ) const noexcept { zen_type const& zen = static_cast< zen_type const& >( *this ); better_assert( zen.row() && "save_as_bmp: matrix row cannot be zero" ); @@ -3403,9 +4675,11 @@ namespace feng auto&& selected_map = ( *( color_maps.find( map_name ) ) ).second; auto const [the_row, the_col] = zen.shape(); - matrix> channel_r{ the_row, the_col }; - matrix> channel_g{ the_row, the_col }; - matrix> channel_b{ the_row, the_col }; + // dependent on Type so the lookup waits until matrix is complete (F10); same type as before + using byte_matrix = matrix, std::allocator, void>>; + byte_matrix channel_r{ the_row, the_col }; + byte_matrix channel_g{ the_row, the_col }; + byte_matrix channel_b{ the_row, the_col }; //auto const& [mx, mn] = std::make_tuple( zen.max(), zen.min() ); auto const& [mn, mx] = zen.minmax(); @@ -3420,32 +4694,26 @@ namespace feng channel_b[row_index][c] = b_; } }; - matrix_details::parallel( make_colormap, 0UL, the_row, 0UL ); + matrix_details::parallel_work( make_colormap, 0UL, the_row, the_col, matrix_details::callback_grain ); auto const& encoding = matrix_details::encode_bmp_stream( channel_r, channel_g, channel_b ); - if ( encoding ) - { - std::string new_file_name{ file_name }; - std::string const extension{ ".bmp" }; - if ( ( new_file_name.size() < 4 ) || ( std::string{ new_file_name.begin() + new_file_name.size() - 4, new_file_name.end() } != extension ) ) - new_file_name += extension; - - if ( !matrix_details::create_directory_if_not_present( new_file_name ) ) - better_assert( !"save_as_bmp", " failed to create directory with the target file name is ", new_file_name ); + if ( !encoding ) return matrix_details::write_failed( "matrix::save_as_bmp", "failed to encode the BMP stream", file_name ); - std::ofstream stream( new_file_name.c_str(), std::ios_base::out | std::ios_base::binary ); - better_assert( stream, " failed to open file with the target file name is ", new_file_name ); - if ( !stream ) return false; + std::string new_file_name{ file_name }; + std::string const extension{ ".bmp" }; + if ( ( new_file_name.size() < 4 ) || ( std::string{ new_file_name.begin() + new_file_name.size() - 4, new_file_name.end() } != extension ) ) + new_file_name += extension; - stream.write( reinterpret_cast((*encoding).data()), (*encoding).size() ); - return true; - } - return false; + // S5-R4 (D-011): false with one stderr line on a directory, open, write or close failure; never aborts. + return matrix_details::write_stream( "matrix::save_as_bmp", new_file_name, std::ios_base::out | std::ios_base::binary, + [&encoding]( std::ofstream& stream ) + { stream.write( reinterpret_cast((*encoding).data()), static_cast( (*encoding).size() ) ); } ); } - bool save_as_bmp( char const* const file_name ) const + [[nodiscard]] bool save_as_bmp( char const* const file_name ) const noexcept { + if ( file_name == nullptr ) return matrix_details::write_failed( "matrix::save_as_bmp", "no file name given", "" ); return save_as_bmp( std::string{ file_name } ); } }; //crtp_save_as_bmp @@ -3457,7 +4725,7 @@ namespace feng struct crtp_save_as_pgm { typedef Matrix zen_type; - bool save_as_pgm( const std::string& file_name ) const noexcept + [[nodiscard]] bool save_as_pgm( const std::string& file_name ) const noexcept { zen_type const& zen = static_cast< zen_type const& >( *this ); std::string new_file_name{ file_name }; @@ -3466,41 +4734,32 @@ namespace feng if ( ( new_file_name.size() < 4 ) || ( std::string{ new_file_name.begin() + new_file_name.size() - 4, new_file_name.end() } != extension ) ) new_file_name += extension; - if ( !matrix_details::create_directory_if_not_present( new_file_name ) ) - better_assert( !"save_as_pgm", " failed to create directory with the target file name is ", new_file_name ); - - std::ofstream stream( new_file_name.c_str() ); - better_assert( stream, " save_as_pgm failed to open file with the target file name is ", new_file_name ); - if ( !stream ) return false; - + // S5-R4 (D-011): false with one stderr line on a directory, open, write or close failure; never aborts. + return matrix_details::write_stream( "matrix::save_as_pgm", new_file_name, std::ios_base::out, [&]( std::ofstream& stream ) { stream << "P2\n"; stream << zen.col() << " " << zen.row() << "\n"; stream << "255\n"; stream << "# Generated Portable GrayMap image for path [" << file_name << "]\n"; - } - double const max_val = static_cast< double >( *std::max_element( zen.begin(), zen.end() ) ); - double const min_val = static_cast< double >( *std::min_element( zen.begin(), zen.end() ) ); - double const divider = max_val - min_val + 1.0e-10; + double const max_val = static_cast< double >( *std::max_element( zen.begin(), zen.end() ) ); + double const min_val = static_cast< double >( *std::min_element( zen.begin(), zen.end() ) ); + double const divider = max_val - min_val + 1.0e-10; - //for ( std::uint_least64_t r = 0; r < zen.row(); r++ ) - for ( auto const r : matrix_details::range( zen.row() ) ) - { - //for ( std::uint_least64_t c = 0; c < zen.col(); c++ ) - for ( auto const c : matrix_details::range( zen.col() ) ) + for ( auto const r : matrix_details::range( zen.row() ) ) { - unsigned long const rgb = static_cast( 256.0 * ( zen[r][c] - min_val ) / divider ); - stream << std::min( rgb, 255UL ) << " "; + for ( auto const c : matrix_details::range( zen.col() ) ) + { + unsigned long const rgb = static_cast( 256.0 * ( zen[r][c] - min_val ) / divider ); + stream << std::min( rgb, 255UL ) << " "; + } + stream << "\n"; } - stream << "\n"; - } - - stream.close(); - return true; + } ); } - bool save_as_pgm( char const* const file_name ) const + [[nodiscard]] bool save_as_pgm( char const* const file_name ) const noexcept { + if ( file_name == nullptr ) return matrix_details::write_failed( "matrix::save_as_pgm", "no file name given", "" ); return save_as_pgm( std::string{ file_name } ); } }; @@ -3517,7 +4776,7 @@ namespace feng // otherwise, drop these elements zen_type& shrink_to_size( const size_type new_row, const size_type new_col ) noexcept { - better_assert( new_row && new_col ); + FENG_MATRIX_EXPECTS( new_row && new_col, "matrix shrink_to_size: zero extent, new_row = ", new_row, ", new_col = ", new_col ); zen_type& zen = static_cast< zen_type& >( *this ); if ( new_row == zen.row() && new_col == zen.col() ) @@ -3529,7 +4788,7 @@ namespace feng size_type const the_cols_to_copy = std::min( zen.col(), new_col ); for ( size_type r = 0; r != the_rows_to_copy; ++r ) - std::copy( zen.row_begin( r ), zen.row_begin( r ) + the_rows_to_copy, other.row_begin( r ) ); + std::copy( zen.row_begin( r ), zen.row_begin( r ) + the_cols_to_copy, other.row_begin( r ) ); zen.swap( other ); return zen; @@ -3542,7 +4801,7 @@ namespace feng typedef crtp_typedef< Type, Alloc > type_proxy_type; typedef typename type_proxy_type::size_type size_type; typedef typename type_proxy_type::value_type value_type; - friend std::ostream& operator<<( std::ostream& lhs, zen_type const& rhs ) + friend std::ostream& operator<<( std::ostream& lhs, zen_type const& rhs ) noexcept { lhs.precision( 18 ); @@ -3554,31 +4813,36 @@ namespace feng return lhs; } - friend std::istream& operator>>( std::istream& is, zen_type& rhs ) + // S5-R2/S5-R4: reads the rest of the stream as load_txt text; on failure sets failbit and leaves rhs unchanged. + friend std::istream& operator>>( std::istream& is, zen_type& rhs ) noexcept { - std::vector< std::string > row_element; - std::string string_line; - - while ( std::getline( is, string_line, '\n' ) ) - row_element.push_back( string_line ); - - size_type const row = row_element.size(); - size_type const col = std::count_if( row_element[0].begin(), row_element[0].end(), []( char ch ) { return '\t' == ch; } ); - - if ( row == 0 || col == 0 ) + std::istream::sentry const ok( is, true ); + if ( !ok ) return is; + std::string text; +#if defined( __cpp_exceptions ) + // libstdc++'s filebuf throws std::ios_base::failure from underflow on a read error (e.g. EISDIR) whatever the + // exception mask, so that one is turned into badbit|failbit. Only it is caught: std::bad_alloc and anything + // else escapes this noexcept function and terminates, as a real allocation failure must (D-012). + try { - is.setstate( std::ios::failbit ); + text.assign( std::istreambuf_iterator< char >( is ), std::istreambuf_iterator< char >() ); + } + catch ( std::ios_base::failure const& ) + { + is.setstate( std::ios::badbit | std::ios::failbit ); return is; } - - rhs.resize( row, col ); - - for ( size_type r = 0; r != row; ++r ) +#else + text.assign( std::istreambuf_iterator< char >( is ), std::istreambuf_iterator< char >() ); +#endif + is.setstate( std::ios::eofbit ); + zen_type tmp{ rhs.get_allocator() }; + if ( !matrix_details::parse_text< value_type >( text.data(), text.size(), tmp ) ) { - std::istringstream the_row( row_element[r] ); - std::copy( std::istream_iterator< value_type >( the_row ), std::istream_iterator< value_type >(), rhs.row_begin( r ) ); + is.setstate( std::ios::failbit ); + return is; } - + rhs = std::move( tmp ); return is; } }; @@ -3589,10 +4853,24 @@ namespace feng void swap( zen_type& other ) noexcept { zen_type& zen = static_cast< zen_type& >( *this ); - std::swap( zen.row_, other.row_ ); - std::swap( zen.col_, other.col_ ); - std::swap( zen.dat_, other.dat_ ); - std::swap( zen.allocator_, other.allocator_ ); + if ( std::addressof( zen ) == std::addressof( other ) ) return; + typedef typename zen_type::allocator_type allocator_type; + typedef matrix_private::storage_access access; + if ( std::allocator_traits< allocator_type >::propagate_on_container_swap::value || zen.get_allocator() == other.get_allocator() ) + { + access::storage( zen ).swap( access::storage( other ) ); + std::swap( access::rows( zen ), access::rows( other ) ); + std::swap( access::cols( zen ), access::cols( other ) ); + return; + } + // S3: unequal allocators and no propagation on swap: exchange the contents element-wise, each side + // keeping its own allocator; the moves below are between equal allocators, so they steal. + zen_type to_zen{ zen.get_allocator(), other.row(), other.col() }; + std::copy( other.begin(), other.end(), to_zen.begin() ); + zen_type to_other{ other.get_allocator(), zen.row(), zen.col() }; + std::copy( zen.begin(), zen.end(), to_other.begin() ); + zen = std::move( to_zen ); + other = std::move( to_other ); } }; template < typename Matrix, typename Type, Allocator Alloc > @@ -3601,7 +4879,7 @@ namespace feng typedef Matrix zen_type; typedef crtp_typedef< Type, Alloc > type_proxy_type; typedef typename type_proxy_type::value_type value_type; - const value_type tr() const noexcept + [[nodiscard]] value_type tr() const noexcept { zen_type const& zen = static_cast< zen_type const& >( *this ); return std::accumulate( zen.diag_begin(), zen.diag_end(), value_type() ); @@ -3613,7 +4891,7 @@ namespace feng typedef Matrix zen_type; typedef crtp_typedef< Type, Alloc > type_proxy_type; typedef typename type_proxy_type::size_type size_type; - const zen_type transpose() const noexcept + [[nodiscard]] zen_type transpose() const noexcept { const zen_type& zen = static_cast< zen_type const& >( *this ); zen_type ans( zen.get_allocator(), zen.col(), zen.row() ); @@ -3632,12 +4910,14 @@ namespace feng typedef Matrix zen_type; template< typename LessThanCompare > - auto minmax( LessThanCompare comp ) const noexcept + [[nodiscard]] auto minmax( LessThanCompare comp ) const noexcept { + // S6-R4 (F15): start from element 0, so all-negative and non-arithmetic inputs give their extremes auto const& zen = static_cast( *this ); - value_type min_val = std::numeric_limits::max(); - value_type max_val = std::numeric_limits::min(); auto const [the_row, the_col] = zen.shape(); + FENG_MATRIX_EXPECTS( the_row != 0 && the_col != 0, "matrix minmax: empty matrix, shape ", the_row, "x", the_col ); + value_type min_val = zen[0][0]; + value_type max_val = zen[0][0]; for ( auto r : matrix_details::range(the_row) ) for ( auto c : matrix_details::range(the_col) ) { @@ -3648,7 +4928,7 @@ namespace feng return std::make_tuple( min_val, max_val ); } - auto minmax() const noexcept + [[nodiscard]] auto minmax() const noexcept { auto const& zen = static_cast( *this ); return zen.minmax( []( value_type const& x, value_type const& y ) noexcept { return x < y; } ); @@ -3662,26 +4942,28 @@ namespace feng typedef Matrix zen_type; template< typename LessThanCompare > - value_type min( LessThanCompare comp ) const noexcept + [[nodiscard]] value_type min( LessThanCompare comp ) const noexcept { auto const& zen = static_cast( *this ); - return matrix_details::reduce( zen.begin(), zen.end(), std::numeric_limits::max(), [&comp]( value_type const& a, value_type const& b ){ return comp(a, b) ? a : b; } ); + FENG_MATRIX_EXPECTS( zen.size() != 0, "matrix min: empty matrix, shape ", zen.row(), "x", zen.col() ); + return matrix_details::reduce( zen.begin() + 1, zen.end(), *zen.begin(), [&comp]( value_type const& a, value_type const& b ){ return comp(a, b) ? a : b; } ); } template< typename LessThanCompare > - value_type max( LessThanCompare comp ) const noexcept + [[nodiscard]] value_type max( LessThanCompare comp ) const noexcept { auto const& zen = static_cast( *this ); - return matrix_details::reduce( zen.begin(), zen.end(), std::numeric_limits::min(), [&comp]( value_type const& a, value_type const& b ){ return comp(a, b) ? b : a; } ); + FENG_MATRIX_EXPECTS( zen.size() != 0, "matrix max: empty matrix, shape ", zen.row(), "x", zen.col() ); + return matrix_details::reduce( zen.begin() + 1, zen.end(), *zen.begin(), [&comp]( value_type const& a, value_type const& b ){ return comp(a, b) ? b : a; } ); } - value_type min() const noexcept + [[nodiscard]] value_type min() const noexcept { auto const& zen = static_cast( *this ); return zen.min( []( value_type const& x, value_type const& y ) noexcept { return x < y; } ); } - value_type max() const noexcept + [[nodiscard]] value_type max() const noexcept { auto const& zen = static_cast( *this ); return zen.max( []( value_type const& x, value_type const& y ) noexcept { return x < y; } ); @@ -3692,34 +4974,196 @@ namespace feng template < typename Matrix, typename Type, Allocator Alloc > using crtp_minmax_view = crtp_minmax; - template < typename Type, Allocator Alloc > + // S4-R2 (F06, F05; D-019): rank-2 views. Each keeps its parent (allocator, checked-mode origin and extent), a + // pointer to the first viewed element (the parent's data() for an empty view), its extents and the parent's + // physical row stride. A view does not own or extend the parent's lifetime; it is invalidated by the + // parent's destruction, reallocation, assignment or shape change. Shared members of both view types. + template < typename View, typename P, typename Matrix > + struct view_members + { + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef std::remove_const_t< std::remove_pointer_t< P > > value_type; + typedef value_type const* const_pointer; + typedef P pointer; + typedef pointer row_type; + typedef const_pointer const_row_type; + typedef stride_iterator< pointer > col_type; + typedef stride_iterator< const_pointer > const_col_type; + typedef std::reverse_iterator< col_type > reverse_col_type; + typedef std::reverse_iterator< const_col_type > const_reverse_col_type; + typedef view_iterator< pointer > iterator; + typedef view_iterator< const_pointer > const_iterator; + typedef std::reverse_iterator< iterator > reverse_iterator; + typedef std::reverse_iterator< const_iterator > const_reverse_iterator; + typedef std::iter_reference_t< pointer > reference; + typedef value_type const& const_reference; + + protected: + Matrix const* parent_ = nullptr; + pointer data_ = nullptr; + size_type rows_ = 0; + size_type cols_ = 0; + size_type row_stride_ = 0; + + view_members() noexcept = default; + view_members( Matrix const* parent, pointer data, size_type rows, size_type cols, size_type row_stride ) noexcept + : parent_( parent ), data_( data ), rows_( rows ), cols_( cols ), row_stride_( row_stride ) { } + + // the first viewed element of rows [r0, r1), columns [c0, c1) of `mat`, after check_view_range + template < typename Q > + static Q first_element( Q origin, std::size_t r0, std::size_t r1, std::size_t c0, std::size_t c1, std::size_t parent_cols ) noexcept + { + return ( r0 == r1 || c0 == c1 ) ? origin : origin + ( r0 * parent_cols + c0 ); + } + + void check_row( size_type r ) const noexcept + { + FENG_MATRIX_EXPECTS( r < rows_ && "Row index out of boundary!", "matrix index: row ", r, " with row() ", rows_ ); + } + + // the parent's data() as a Q; a mutable view was made from a non-const parent, so dropping const is sound + template < typename Q > + Q origin() const noexcept + { + return Q( const_cast< value_type* >( parent_->data() ) ); + } + + template < typename Q > + stride_iterator< Q > column( size_type c, difference_type index ) const noexcept + { + matrix_private::check_column( c, cols_ ); + Q const base = rows_ ? Q( data_ + c ) : Q( data_ ); + return stride_iterator< Q >( base, static_cast< difference_type >( row_stride_ ), static_cast< difference_type >( rows_ ), index, origin< Q >(), parent_->size() ); + } + + template < typename Q > + view_iterator< Q > element( difference_type index ) const noexcept + { + return view_iterator< Q >( Q( data_ ), static_cast< difference_type >( row_stride_ ), static_cast< difference_type >( cols_ ), + static_cast< difference_type >( rows_ * cols_ ), index, origin< Q >(), parent_->size() ); + } + + public: + size_type row() const noexcept { return rows_; } + size_type col() const noexcept { return cols_; } + size_type size() const noexcept { return rows_ * cols_; } + bool empty() const noexcept { return size() == 0; } + size_type row_stride() const noexcept { return row_stride_; } + auto get_allocator() const noexcept { return parent_->get_allocator(); } + + row_type operator[]( size_type r ) const noexcept { check_row( r ); return data_ + r * row_stride_; } + reference at( size_type r, size_type c ) const noexcept + { + matrix_private::check_index( r, c, rows_, cols_ ); + return data_[ r * row_stride_ + c ]; + } + + row_type row_begin( size_type r ) const noexcept { check_row( r ); return data_ + r * row_stride_; } + row_type row_end( size_type r ) const noexcept { return row_begin( r ) + cols_; } + const_row_type row_cbegin( size_type r ) const noexcept { return row_begin( r ); } + const_row_type row_cend( size_type r ) const noexcept { return row_end( r ); } + + col_type col_begin( size_type c ) const noexcept { return column< pointer >( c, 0 ); } + col_type col_end( size_type c ) const noexcept { return column< pointer >( c, static_cast< difference_type >( rows_ ) ); } + const_col_type col_cbegin( size_type c ) const noexcept { return column< const_pointer >( c, 0 ); } + const_col_type col_cend( size_type c ) const noexcept { return column< const_pointer >( c, static_cast< difference_type >( rows_ ) ); } + reverse_col_type col_rbegin( size_type c ) const noexcept { return reverse_col_type( col_end( c ) ); } + reverse_col_type col_rend( size_type c ) const noexcept { return reverse_col_type( col_begin( c ) ); } + const_reverse_col_type col_crbegin( size_type c ) const noexcept { return const_reverse_col_type( col_cend( c ) ); } + const_reverse_col_type col_crend( size_type c ) const noexcept { return const_reverse_col_type( col_cbegin( c ) ); } + + iterator begin() const noexcept { return element< pointer >( 0 ); } + iterator end() const noexcept { return element< pointer >( static_cast< difference_type >( size() ) ); } + const_iterator cbegin() const noexcept { return element< const_pointer >( 0 ); } + const_iterator cend() const noexcept { return element< const_pointer >( static_cast< difference_type >( size() ) ); } + reverse_iterator rbegin() const noexcept { return reverse_iterator( end() ); } + reverse_iterator rend() const noexcept { return reverse_iterator( begin() ); } + const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator( cend() ); } + const_reverse_iterator crend() const noexcept { return const_reverse_iterator( cbegin() ); } + }; + + // the const view; make_view( m, {r0, r1}, {c0, c1} ) of an lvalue owner + template < matrix_element Type, Allocator Alloc > struct matrix_view : + view_members< matrix_view, Type const*, matrix >, crtp_save_as_bmp_view, Type, Alloc>, crtp_shape_view, Type, Alloc>, - crtp_bracket_operator_view, Type, Alloc>, - crtp_minmax_view, Type, Alloc>, - crtp_row_col_size_view, Type, Alloc>, - crtp_row_iterator_view, Type, Alloc>, - crtp_col_iterator_view, Type, Alloc> - { - typedef crtp_typedef< Type, Alloc > type_proxy_type; - typedef typename type_proxy_type::size_type size_type; - //matrix_view( matrix const& mat, std::pair const& row_dim, std::pair const& col_dim ) noexcept; - //template < typename Type, class Alloc > - matrix_view( matrix const& mat, std::pair const& row_dim, std::pair const& col_dim ) noexcept: matrix_{ mat } - { - row_dim_.first = (row_dim.first >= matrix_.row() || row_dim.first >= row_dim.second) ? 0 : row_dim.first; - col_dim_.first = (col_dim.first >= matrix_.col() || col_dim.first >= col_dim.second) ? 0 : col_dim.first; - row_dim_.second = (row_dim.second <= matrix_.row()) ? row_dim.second : matrix_.row(); - col_dim_.second = (col_dim.second <= matrix_.col()) ? col_dim.second : matrix_.col(); - } - - matrix const& matrix_; - std::pair row_dim_; - std::pair col_dim_; + crtp_minmax_view, Type, Alloc> + { + private: + typedef view_members< matrix_view, Type const*, matrix > base_type; + public: + typedef typename base_type::size_type size_type; + typedef typename base_type::value_type value_type; + typedef Alloc allocator_type; + typedef matrix matrix_type; + typedef std::pair range_type; + + matrix_view( matrix_type const& mat, range_type const& row_dim, range_type const& col_dim ) noexcept + { + matrix_private::check_view_range( row_dim.first, row_dim.second, col_dim.first, col_dim.second, mat.row(), mat.col() ); + (*this).parent_ = std::addressof( mat ); + (*this).data_ = base_type::first_element( mat.data(), row_dim.first, row_dim.second, col_dim.first, col_dim.second, mat.col() ); + (*this).rows_ = row_dim.second - row_dim.first; + (*this).cols_ = col_dim.second - col_dim.first; + (*this).row_stride_ = mat.col(); + } + // S4-R3: a view of a temporary owner would dangle + matrix_view( matrix_type&&, range_type const&, range_type const& ) = delete; + matrix_view( matrix_type const&&, range_type const&, range_type const& ) = delete; + + // a mutable view converts to the const view of the same elements + matrix_view( mutable_matrix_view const& other ) noexcept + : base_type( other.parent_, other.data_, other.rows_, other.cols_, other.row_stride_ ) { } + + matrix_view( matrix_view const& ) noexcept = default; + matrix_view& operator=( matrix_view const& ) noexcept = default; + + // read-only access, as for a const matrix + value_type operator()( size_type r, size_type c ) const noexcept { return (*this).at( r, c ); } };//struct matrix_view - template < typename Type, Allocator Alloc = std::allocator > + // the mutable view; make_mutable_view( m, {r0, r1}, {c0, c1} ) of a non-const lvalue owner. Constness is + // shallow, as for std::span: a const mutable view still writes the parent's elements. + template < matrix_element Type, Allocator Alloc > + struct mutable_matrix_view : + view_members< mutable_matrix_view, Type*, matrix >, + crtp_save_as_bmp_view, Type, Alloc>, + crtp_shape_view, Type, Alloc>, + crtp_minmax_view, Type, Alloc> + { + private: + typedef view_members< mutable_matrix_view, Type*, matrix > base_type; + friend struct matrix_view; + public: + typedef typename base_type::size_type size_type; + typedef typename base_type::value_type value_type; + typedef Alloc allocator_type; + typedef matrix matrix_type; + typedef std::pair range_type; + + mutable_matrix_view( matrix_type& mat, range_type const& row_dim, range_type const& col_dim ) noexcept + { + matrix_private::check_view_range( row_dim.first, row_dim.second, col_dim.first, col_dim.second, mat.row(), mat.col() ); + (*this).parent_ = std::addressof( mat ); + (*this).data_ = base_type::first_element( mat.data(), row_dim.first, row_dim.second, col_dim.first, col_dim.second, mat.col() ); + (*this).rows_ = row_dim.second - row_dim.first; + (*this).cols_ = col_dim.second - col_dim.first; + (*this).row_stride_ = mat.col(); + } + // S4-R3: a view of a temporary owner would dangle; a mutable view of a const owner would write through it + mutable_matrix_view( matrix_type const&, range_type const&, range_type const& ) = delete; + mutable_matrix_view( matrix_type&&, range_type const&, range_type const& ) = delete; + mutable_matrix_view( matrix_type const&&, range_type const&, range_type const& ) = delete; + + mutable_matrix_view( mutable_matrix_view const& ) noexcept = default; + mutable_matrix_view& operator=( mutable_matrix_view const& ) noexcept = default; + + typename base_type::reference operator()( size_type r, size_type c ) const noexcept { return (*this).at( r, c ); } + };//struct mutable_matrix_view + + template < matrix_element Type, Allocator Alloc = std::allocator > struct matrix : crtp_anti_diag_iterator< matrix< Type, Alloc >, Type, Alloc > , crtp_apply< matrix< Type, Alloc >, Type, Alloc > , crtp_bracket_operator< matrix< Type, Alloc >, Type, Alloc > @@ -3752,6 +5196,7 @@ namespace feng , crtp_row_col_size< matrix< Type, Alloc >, Type, Alloc > , crtp_row_iterator< matrix< Type, Alloc >, Type, Alloc > , crtp_save_as_binary< matrix< Type, Alloc >, Type, Alloc > + , crtp_save_as_npy< matrix< Type, Alloc >, Type, Alloc > , crtp_save_as_bmp< matrix< Type, Alloc >, Type, Alloc > , crtp_save_as_pgm< matrix< Type, Alloc >, Type, Alloc > , crtp_save_as_png< matrix< Type, Alloc >, Type, Alloc > @@ -3760,6 +5205,7 @@ namespace feng , crtp_shrink_to_size< matrix< Type, Alloc >, Type, Alloc > , crtp_stream_operator< matrix< Type, Alloc >, Type, Alloc > , crtp_swap< matrix< Type, Alloc >, Type, Alloc > + , crtp_tr< matrix< Type, Alloc >, Type, Alloc > , crtp_transpose< matrix< Type, Alloc >, Type, Alloc > { typedef matrix self_type; @@ -3771,86 +5217,92 @@ namespace feng typedef typename type_proxy_type::allocator_type allocator_type; typedef typename type_proxy_type::range_type range_type; - size_type row_; - size_type col_; - pointer dat_; - allocator_type allocator_; + static_assert( std::is_same_v< typename std::allocator_traits< Alloc >::value_type, Type >, + "feng::matrix: the allocator's value_type must be Type; rebind the allocator to the element type" ); - ~matrix() - { - (*this).clear(); - } + private: + friend struct matrix_private::storage_access; + typedef std::allocator_traits< allocator_type > allocator_traits_type; + // S3-R1: private RAII storage; storage_.size() == row_ * col_ after every operation. + size_type row_ = 0; + size_type col_ = 0; + std::vector< value_type, allocator_type > storage_; + + public: + ~matrix() noexcept = default; + + // S3-R2: the free swap follows the member swap (POCS, or element-wise for unequal allocators), not std::swap's + // move-based fallback, which would follow POCMA instead. + friend void swap( self_type& lhs, self_type& rhs ) noexcept { lhs.swap( rhs ); } matrix( std::integral auto row, std::integral auto col, std::initializer_list const& value_list ) noexcept : matrix{} { - (*this).resize( static_cast(row), static_cast(col) ); + FENG_MATRIX_EXPECTS( matrix_private::dimension_fits( row ), "matrix size: negative or unrepresentable rows = ", row ); + FENG_MATRIX_EXPECTS( matrix_private::dimension_fits( col ), "matrix size: negative or unrepresentable cols = ", col ); + matrix_private::checked_count( (*this).get_allocator(), static_cast(row), static_cast(col) ); + (*this).resize( static_cast(row), static_cast(col) ); size_type elements_to_copy = std::min( (*this).size(), value_list.size() ); std::copy( value_list.begin(), value_list.begin()+elements_to_copy, (*this).begin() ); } - matrix( self_type&& other ) noexcept : matrix{} + // S3-R1: steals the storage; the source is left 0x0 with size 0. + matrix( self_type&& other ) noexcept : row_{ other.row_ }, col_{ other.col_ }, storage_( std::move( other.storage_ ) ) { - (*this).swap( other ); + other.storage_.clear(); + other.row_ = 0; + other.col_ = 0; } - self_type& operator = ( self_type&& other ) + // S3-R1: self-move keeps the contents; otherwise the vector honours POCMA and the source is left 0x0. + self_type& operator = ( self_type&& other ) noexcept { - (*this).clear(); - (*this).swap( other ); + if ( this == std::addressof( other ) ) return *this; + // S3-R2: unequal allocators that do not propagate make the vector allocate on this side; check first. + if constexpr ( !allocator_traits_type::propagate_on_container_move_assignment::value && !allocator_traits_type::is_always_equal::value ) + if ( (*this).get_allocator() != other.get_allocator() ) + matrix_private::checked_count( (*this).get_allocator(), other.row_, other.col_ ); + storage_ = std::move( other.storage_ ); + row_ = other.row_; + col_ = other.col_; + other.storage_.clear(); + other.row_ = 0; + other.col_ = 0; return *this; } - matrix( const self_type& other ) noexcept : row_{ other.row_ }, col_{ other.col_ }, dat_{ nullptr }, allocator_{ other.allocator_ } + // S3-R5: the selected allocator may differ from other's (and so may its max_size); check before copying. + matrix( const self_type& other ) noexcept : row_{ other.row_ }, col_{ other.col_ }, + storage_( other.storage_, matrix_private::checked_allocator( allocator_traits_type::select_on_container_copy_construction( other.storage_.get_allocator() ), other.row_, other.col_ ) ) { - if ( row_*col_ != 0 ) - { - dat_ = allocator_.allocate( row_*col_ ); - std::copy_n( other.dat_, row_*col_, dat_ ); - } } template < typename T, Allocator A > - matrix( matrix< T, A> const& other ) noexcept : row_{ other.row_ }, col_{ other.col_ }, dat_{ nullptr }, allocator_{ other.allocator_ } + matrix( matrix< T, A> const& other ) noexcept : matrix{ allocator_type( other.get_allocator() ), other.row(), other.col() } { - if ( row_*col_ != 0 ) - { - dat_ = allocator_.allocate( row_*col_ ); - std::copy_n( other.dat_, row_*col_, dat_ ); - } + std::copy( other.begin(), other.end(), (*this).begin() ); } - explicit matrix( const size_type r = 0, const size_type c = 0, value_type const& v = value_type{} ) noexcept : row_{ r } , col_{ c }, dat_{ nullptr } + explicit matrix( const size_type r = 0, const size_type c = 0, value_type const& v = value_type{} ) noexcept : matrix{ allocator_type{}, r, c, v } { - if ( r*c != 0 ) - { - dat_ = allocator_.allocate( r*c ); - std::fill_n( dat_, r*c, v ); - } } - explicit matrix( allocator_type a, const size_type r = 0, const size_type c = 0, value_type const& v = value_type{} ) noexcept : row_{ r } , col_{ c }, dat_{ nullptr }, allocator_{ a } + explicit matrix( allocator_type a, const size_type r = 0, const size_type c = 0, value_type const& v = value_type{} ) noexcept : row_{ r }, col_{ c }, + storage_( matrix_private::checked_count( a, r, c ), v, a ) { - if ( r*c != 0 ) - { - dat_ = allocator_.allocate( r*c ); - std::fill_n( dat_, r*c, v ); - } } template< typename T, Allocator A > - matrix( matrix const& other, std::initializer_list rr, std::initializer_list rc ) noexcept : row_{0}, col_{0}, dat_{nullptr}, allocator_{ other.get_allocator() } + matrix( matrix const& other, std::initializer_list rr, std::initializer_list rc ) noexcept : storage_( allocator_type( other.get_allocator() ) ) { - better_assert( rr.size() == 2 && "row dims not match!" ); - better_assert( rc.size() == 2 && "col dims not match!" ); + matrix_private::check_clone_lists( rr.size(), rc.size() ); auto [rr0, rr1] = std::make_pair( *(rr.begin()), *(rr.begin()+1) ); auto [rc0, rc1] = std::make_pair( *(rc.begin()), *(rc.begin()+1) ); (*this).clone( other, rr0, rr1, rc0, rc1 ); } - matrix( matrix const& other, std::initializer_list rr, std::initializer_list rc ) noexcept : row_{0}, col_{0}, dat_{nullptr}, allocator_{ other.get_allocator() } + matrix( matrix const& other, std::initializer_list rr, std::initializer_list rc ) noexcept : storage_( other.get_allocator() ) { - better_assert( rr.size() == 2 && "row dims not match!" ); - better_assert( rc.size() == 2 && "col dims not match!" ); + matrix_private::check_clone_lists( rr.size(), rc.size() ); auto [rr0, rr1] = std::make_pair( *(rr.begin()), *(rr.begin()+1) ); auto [rc0, rc1] = std::make_pair( *(rc.begin()), *(rc.begin()+1) ); (*this).clone( other, rr0, rr1, rc0, rc1 ); @@ -3868,13 +5320,22 @@ namespace feng self_type& operator = ( const self_type& rhs ) noexcept { - ( *this ).copy( rhs ); + if ( this == std::addressof( rhs ) ) return *this; // S2-R4: self-assignment keeps the contents + // S3-R1: check the count against the allocator the destination will hold, then let the vector honour POCCA. + if constexpr ( allocator_traits_type::propagate_on_container_copy_assignment::value ) + matrix_private::checked_count( rhs.get_allocator(), rhs.row_, rhs.col_ ); + else + matrix_private::checked_count( (*this).get_allocator(), rhs.row_, rhs.col_ ); + storage_ = rhs.storage_; + row_ = rhs.row_; + col_ = rhs.col_; return *this; } template < typename T, Allocator A > self_type& operator = ( const matrix< T, A >& rhs ) noexcept { + if ( static_cast< void const* >( this ) == static_cast< void const* >( std::addressof( rhs ) ) ) return *this; // S2-R4 ( *this ).copy( rhs ); return *this; } @@ -3885,22 +5346,25 @@ namespace feng return *this; } - //matrix( matrix_view const& view ) noexcept; - template< typename T > - auto const astype() const noexcept + [[nodiscard]] auto astype() const noexcept { matrix:: template rebind_alloc > ans{ (*this).get_allocator(), (*this).row(), (*this).col() }; std::copy( (*this).begin(), (*this).end(), ans.begin() ); //TODO: should move to crtp_xxx return ans; } - //template < typename Type, class Alloc > - matrix( matrix_view const& view ) noexcept + // S3-R4 (F03), S4-R2: copies exactly the viewed rectangle, row by row, using the parent's allocator. + matrix( matrix_view const& v ) noexcept : matrix{ v.get_allocator(), v.row(), v.col() } + { + for ( size_type r = 0; r != v.row(); ++r ) + std::copy( v.row_begin( r ), v.row_end( r ), (*this).row_begin( r ) ); + } + + matrix( mutable_matrix_view const& v ) noexcept : matrix{ v.get_allocator(), v.row(), v.col() } { - (*this).resize( view.row_dim_.second-view.row_dim_.first, view.col_dim_.second-view.col_dim_.first ); - for ( auto const row : matrix_details::range((*this).row() ) )//copy row by row - std::copy( view.matrix_.row_begin(row+view.row_dim_.first), view.matrix_.row_end(row+view.row_dim_.second), (*this).row_begin(row) ); + for ( size_type r = 0; r != v.row(); ++r ) + std::copy( v.row_begin( r ), v.row_end( r ), (*this).row_begin( r ) ); } };//struct matrix @@ -3932,7 +5396,7 @@ namespace feng inline constexpr bool is_complex_matrix_v = is_complex_matrix::value; template< typename M > - concept ComplexMatrix = is_complex_matrix_v; + concept ComplexMatrix = Matrix && is_complex_matrix_v; // refines Matrix so the complex overloads win // // - end of Matrix and ComplexMatrix concepts @@ -3940,6 +5404,92 @@ namespace feng namespace matrix_details { + // integral 0 < floating 1 < complex 2 + template< matrix_scalar T > + struct element_kind : std::integral_constant< int, std::is_integral_v< T > ? 0 : ( std::is_floating_point_v< T > ? 1 : 2 ) > {}; + + template< typename T > + struct element_real { using type = T; }; + template< typename X > + struct element_real< std::complex< X > > { using type = X; }; + template< typename T > + using element_real_t = typename element_real< T >::type; + + // matrix (+) matrix: std::common_type_t for two real types; complex> when either is complex. + template< typename T, typename U > + struct common_element { using type = T; }; // same non-scalar element type: unchanged + template< matrix_scalar T, matrix_scalar U > + struct common_element< T, U > + { + using type = std::conditional_t< element_kind< T >::value == 2 || element_kind< U >::value == 2, + std::complex< std::common_type_t< element_real_t< T >, element_real_t< U > > >, + std::common_type_t< T, U > >; + }; + template< typename T, typename U > + using common_element_t = typename common_element< T, U >::type; + + // matrix (+) scalar: T when kind(S) <= kind(T), else common_element_t. + template< typename T, typename S > + struct scalar_result { using type = T; }; // same non-scalar element type: unchanged + template< matrix_scalar T, matrix_scalar S > + struct scalar_result< T, S > + { + using type = std::conditional_t< ( element_kind< S >::value <= element_kind< T >::value ), T, common_element_t< T, S > >; + }; + template< typename T, typename S > + using scalar_result_t = typename scalar_result< T, S >::type; + + // The operand pairs the operators accept: two scalars, or a non-scalar element type with itself (as before S6). + template< typename S, typename T > + concept scalar_operand_for = ( matrix_scalar< S > && matrix_scalar< T > ) || ( !matrix_scalar< T > && std::same_as< S, T > ); + template< typename T, typename U > + concept element_pair = ( matrix_scalar< T > && matrix_scalar< U > ) || std::same_as< T, U >; + + template< typename R, typename T, Allocator A > + using promoted_matrix_t = matrix< R, typename std::allocator_traits< A >::template rebind_alloc< R > >; + + // A copy of m with elements static_cast to R; the allocator is m's (as a copy selects it) rebound to R. + template< typename R, typename T, Allocator A > + promoted_matrix_t< R, T, A > promote( matrix< T, A > const& m ) noexcept + { + if constexpr ( std::same_as< promoted_matrix_t< R, T, A >, matrix< T, A > > ) + return m; + else + { + typename std::allocator_traits< A >::template rebind_alloc< R > alloc( std::allocator_traits< A >::select_on_container_copy_construction( m.get_allocator() ) ); + promoted_matrix_t< R, T, A > ans{ alloc, m.row(), m.col() }; + std::transform( m.begin(), m.end(), ans.begin(), []( T const& x ) noexcept { return static_cast< R >( x ); } ); + return ans; + } + } + + // S9-R6: the one shape check for the matrix operands of an elementwise function (map, the binary family, fma); + // a mismatch aborts with ": operand shape mismatch, RxC and RxC" (or "RxC, RxC and RxC"). + template< Matrix M, Matrix N > + void expect_same_shape( char const* name, M const& m, N const& n ) noexcept + { + FENG_MATRIX_EXPECTS( m.row() == n.row() && m.col() == n.col(), name, ": operand shape mismatch, ", m.row(), "x", m.col(), " and ", n.row(), "x", n.col() ); + } + + template< Matrix M, Matrix N, Matrix L > + void expect_same_shape( char const* name, M const& m, N const& n, L const& l ) noexcept + { + FENG_MATRIX_EXPECTS( m.row() == n.row() && m.col() == n.col() && m.row() == l.row() && m.col() == l.col(), name, ": operand shape mismatch, ", m.row(), "x", m.col(), ", ", n.row(), "x", n.col(), " and ", l.row(), "x", l.col() ); + } + + // rhs as an operand of a compound operator on a matrix: itself when the types match, else converted. + template< typename R, Allocator B, typename T, Allocator A > + decltype( auto ) operand_as( matrix< R, B > const& target, matrix< T, A > const& rhs ) noexcept + { + if constexpr ( std::same_as< matrix< R, B >, matrix< T, A > > ) + return ( rhs ); + else + { + matrix< R, B > ans{ target.get_allocator(), rhs.row(), rhs.col() }; + std::transform( rhs.begin(), rhs.end(), ans.begin(), []( T const& x ) noexcept { return static_cast< R >( x ); } ); + return ans; + } + } namespace map_impl_private { @@ -3959,6 +5509,7 @@ namespace feng template< typename T, Allocator A, typename T2, Allocator A2 > auto map_impl( matrix const& mat, matrix const& nat ) noexcept { + matrix_details::expect_same_shape( "map", mat, nat ); return [&]( auto const& func ) noexcept { typedef typename std::invoke_result_t value_type; @@ -3972,6 +5523,7 @@ namespace feng template< typename T, Allocator A, typename T2, Allocator A2, typename T3, Allocator A3 > auto map_impl( matrix const& mat, matrix const& nat, matrix const& lat ) noexcept { + matrix_details::expect_same_shape( "map", mat, nat, lat ); return [&]( auto const& func ) noexcept { typedef typename std::invoke_result_t value_type; @@ -4009,10 +5561,11 @@ namespace feng } } + // S4-R4 (F12): the returned lambda holds a copy of func, so it may outlive the argument. template< typename Func > auto map( Func const& func ) noexcept { - return [&]( auto const& ... mat ) noexcept + return [func]( auto const& ... mat ) noexcept { return map_impl_private::map_impl( mat ... )( func ); }; @@ -4020,43 +5573,29 @@ namespace feng namespace reduce_impl_private { + // S4-R4, S6-R1 (F11, F12): index reads only, so no pointer past mat.end() is formed; the fold is + // matrix_details::reduce_range. An optional trailing worker count defaults to work_workers( n, reduce_grain ) (S10-R3). template< typename T, Allocator A > auto reduce_impl( matrix const& mat ) noexcept { - return [&]( auto const& func, auto const& init ) noexcept + return [&mat]( auto const& func, auto const& init, auto const... workers ) noexcept { - auto const& stride_func = [&]( auto start_itor, auto stride, auto end_itor ) noexcept - { - T init_ = init; - for ( auto itor = start_itor; itor < end_itor; itor += stride ) - init_ = func( init_, *itor ); - return init_; - }; - - std::uint_least64_t parallel_size = std::thread::hardware_concurrency(); - - //direct reduce - if ( parallel_size<= 1 || mat.size() < 32 ) - return stride_func( mat.begin(), 1, mat.end() ); - - //parallel reduce to cache - std::vector result_cache( parallel_size ); - auto const& paralle_func = [&]( std::uint_least64_t idx ) noexcept - { - result_cache[idx] = stride_func( mat.begin()+idx, parallel_size, mat.end() ); - }; - matrix_details::parallel( paralle_func, 0UL, parallel_size, 0UL ); - - //final reduce - return stride_func( result_cache.begin(), 1, result_cache.end() ); + static_assert( sizeof...( workers ) <= 1, "reduce_impl( mat )( func, init [, workers] )" ); + std::size_t const n = mat.size(); + T const* const data = mat.data(); + std::size_t w = work_workers( n, reduce_grain ); + ( ( w = static_cast( workers ) ), ... ); + auto const at = [data]( std::size_t i ) noexcept -> T const& { return data[i]; }; + return matrix_details::reduce_range( at, n, static_cast( init ), func, w ); }; } }//reduce_impl_private + // S4-R4 (F12): the returned lambda holds copies of func and init, so it may outlive both. template< typename Func, typename Type > auto reduce( Func const& func, Type init ) noexcept { - return [&]( auto const& mat ) noexcept + return [func, init]( auto const& mat ) noexcept { return reduce_impl_private::reduce_impl( mat )( func, init ); }; @@ -4064,113 +5603,161 @@ namespace feng } - /* - template < typename Type, class Alloc > - matrix_view::matrix_view( matrix const& mat, std::pair const& row_dim, std::pair const& col_dim ) noexcept: matrix_{ mat } + // S4-R2, S4-R3 (D-019): the const view of rows [r0, r1) and columns [c0, c1) of an lvalue owner; each list + // holds exactly two values and the ranges must lie in the owner (checked in every build). + template< typename Type, Allocator Alloc, typename Integer_Type > + [[nodiscard]] matrix_view + make_view( matrix const& owner, std::initializer_list row_dim, std::initializer_list col_dim ) noexcept { - row_dim_.first = (row_dim.first >= matrix_.row() || row_dim.first >= row_dim.second) ? 0 : row_dim.first; - col_dim_.first = (col_dim.first >= matrix_.col() || col_dim.first >= col_dim.second) ? 0 : col_dim.first; - row_dim_.second = (row_dim.second <= matrix_.row()) ? row_dim.second : matrix_.row(); - col_dim_.second = (col_dim.second <= matrix_.col()) ? col_dim.second : matrix_.col(); + auto const rows = matrix_private::view_extent( row_dim, "row" ); + auto const cols = matrix_private::view_extent( col_dim, "column" ); + return matrix_view{ owner, rows, cols }; } - */ - //matrix const& matrix_; - //std::pair row_dim_; - //std::pair col_dim_; + // S4-R3: a view of a temporary owner would dangle. + template< typename Type, Allocator Alloc, typename Integer_Type > + matrix_view + make_view( matrix&& owner, std::initializer_list row_dim, std::initializer_list col_dim ) = delete; - /* - template < typename Type, class Alloc > - matrix::matrix( matrix_view const& view ) noexcept + template< typename Type, Allocator Alloc, typename Integer_Type > + matrix_view + make_view( matrix const&& owner, std::initializer_list row_dim, std::initializer_list col_dim ) = delete; + + // S4-R2, S4-R3 (D-019): the mutable view of rows [r0, r1) and columns [c0, c1) of a non-const lvalue owner. + template< typename Type, Allocator Alloc, typename Integer_Type > + [[nodiscard]] mutable_matrix_view + make_mutable_view( matrix& owner, std::initializer_list row_dim, std::initializer_list col_dim ) noexcept { - (*this).resize( view.row_dim_.second-view.row_dim_.first, view.col_dim_.second-view.col_dim_.first ); - for ( auto const row : matrix_details::range((*this).row() ) )//copy row by row - std::copy( view.matrix_.row_begin(row+view.row_dim_.first), view.matrix_.row_end(row+view.row_dim_.second), (*this).row_begin(row) ); + auto const rows = matrix_private::view_extent( row_dim, "row" ); + auto const cols = matrix_private::view_extent( col_dim, "column" ); + return mutable_matrix_view{ owner, rows, cols }; } - */ + // S4-R3: a mutable view of a const owner would write through it; one of a temporary owner would dangle. template< typename Type, Allocator Alloc, typename Integer_Type > - matrix_view const - make_view( matrix const& matrix_, std::initializer_list row_dim, std::initializer_list col_dim ) noexcept - { - typedef crtp_typedef< Type, Alloc > type_proxy_type; - typedef typename type_proxy_type::size_type size_type; + mutable_matrix_view + make_mutable_view( matrix const& owner, std::initializer_list row_dim, std::initializer_list col_dim ) = delete; - better_assert( row_dim.size() == 2 && "Error: row parameters for a matrix view should be 2!" ); - better_assert( col_dim.size() == 2 && "Error: col parameters for a matrix view should be 2!" ); - return matrix_view - { - matrix_, - std::make_pair( static_cast(*(row_dim.begin())), static_cast(*(row_dim.begin()+1)) ), - std::make_pair( static_cast(*(col_dim.begin())), static_cast(*(col_dim.begin()+1)) ) - }; + template< typename Type, Allocator Alloc, typename Integer_Type > + mutable_matrix_view + make_mutable_view( matrix&& owner, std::initializer_list row_dim, std::initializer_list col_dim ) = delete; + + template< typename Type, Allocator Alloc, typename Integer_Type > + mutable_matrix_view + make_mutable_view( matrix const&& owner, std::initializer_list row_dim, std::initializer_list col_dim ) = delete; + + // S9-R4 (D-032): spans over an owner's m.size() contiguous elements and over row r's m.col() elements + // (std::span< T const > for a const owner); r must name a row. Rvalue owners are rejected, the span would dangle. + template< typename T, Allocator A > + [[nodiscard]] std::span< T > as_span( matrix< T, A >& m ) noexcept { return { std::to_address( m.data() ), m.size() }; } + template< typename T, Allocator A > + [[nodiscard]] std::span< T const > as_span( matrix< T, A > const& m ) noexcept { return { std::to_address( m.data() ), m.size() }; } + template< typename T, Allocator A > + std::span< T const > as_span( matrix< T, A > const&& m ) = delete; + + template< typename M > requires is_matrix_v< std::remove_const_t< M > > + [[nodiscard]] auto row_span( M& m, std::size_t r ) noexcept + { + FENG_MATRIX_EXPECTS( r < m.row(), "row_span: row ", r, " outside a matrix with ", m.row(), " rows" ); + return as_span( m ).subspan( r * m.col(), m.col() ); } + template< typename T, Allocator A > + std::span< T const > row_span( matrix< T, A > const&& m, std::size_t r ) = delete; - template < typename T1, Allocator A1, typename T2, Allocator A2 > - const matrix< T1, A1 > - operator+( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) +#if defined( __cpp_lib_mdspan ) + // S9-R4: the layout_right mdspan of extents (row, col) aliasing m.data(). + template< typename T, Allocator A > + [[nodiscard]] auto to_mdspan( matrix< T, A >& m ) noexcept { return std::mdspan< T, std::dextents< std::size_t, 2 > >( std::to_address( m.data() ), m.row(), m.col() ); } + template< typename T, Allocator A > + [[nodiscard]] auto to_mdspan( matrix< T, A > const& m ) noexcept { return std::mdspan< T const, std::dextents< std::size_t, 2 > >( std::to_address( m.data() ), m.row(), m.col() ); } + template< typename T, Allocator A > + std::mdspan< T const, std::dextents< std::size_t, 2 > > to_mdspan( matrix< T, A > const&& m ) = delete; +#endif +#if defined( __cpp_lib_submdspan ) + // S9-R4: rows [r0, r1) and columns [c0, c1) of to_mdspan( m ), validated as make_view's ranges (S4-R3). + template< typename M, typename Integer_Type > requires is_matrix_v< std::remove_const_t< M > > + [[nodiscard]] auto submdspan( M& m, std::initializer_list< Integer_Type > row_dim, std::initializer_list< Integer_Type > col_dim ) noexcept + { + auto const [r0, r1] = matrix_private::view_extent( row_dim, "row" ); + auto const [c0, c1] = matrix_private::view_extent( col_dim, "column" ); + matrix_private::check_view_range( r0, r1, c0, c1, m.row(), m.col() ); + return std::submdspan( to_mdspan( m ), std::pair{ r0, r1 }, std::pair{ c0, c1 } ); + } + template< typename T, Allocator A, typename Integer_Type > + void submdspan( matrix< T, A > const&& m, std::initializer_list< Integer_Type > row_dim, std::initializer_list< Integer_Type > col_dim ) = delete; +#endif + + // S6-R5 (D-023): matrix (+) matrix gives matrix< common_element_t< T1, T2 >, A1 rebound >; when that is + // matrix< T1, A1 > the result is a copy of lhs as before, otherwise lhs promoted; rhs is converted to the result. + template < typename T1, Allocator A1, typename T2, Allocator A2 > requires matrix_details::element_pair< T1, T2 > + matrix_details::promoted_matrix_t< matrix_details::common_element_t< T1, T2 >, T1, A1 > + operator+( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) noexcept { - matrix< T1, A1 > ans{ lhs }; - ans += rhs; + FENG_MATRIX_EXPECTS( lhs.row() == rhs.row() && lhs.col() == rhs.col(), "operator +: operand shape mismatch, ", lhs.row(), "x", lhs.col(), " + ", rhs.row(), "x", rhs.col() ); + auto ans = matrix_details::promote< matrix_details::common_element_t< T1, T2 > >( lhs ); + ans += matrix_details::operand_as( ans, rhs ); return ans; } - template < typename T1, Allocator A1, typename T2, Allocator A2 > - const matrix< T1, A1 > - operator-( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) + template < typename T1, Allocator A1, typename T2, Allocator A2 > requires matrix_details::element_pair< T1, T2 > + matrix_details::promoted_matrix_t< matrix_details::common_element_t< T1, T2 >, T1, A1 > + operator-( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) noexcept { - matrix< T1, A1 > ans{ lhs }; - ans -= rhs; + FENG_MATRIX_EXPECTS( lhs.row() == rhs.row() && lhs.col() == rhs.col(), "operator -: operand shape mismatch, ", lhs.row(), "x", lhs.col(), " - ", rhs.row(), "x", rhs.col() ); + auto ans = matrix_details::promote< matrix_details::common_element_t< T1, T2 > >( lhs ); + ans -= matrix_details::operand_as( ans, rhs ); return ans; } - template < typename T1, Allocator A1, typename T2, Allocator A2 > - const matrix< T1, A1 > - operator*( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) + template < typename T1, Allocator A1, typename T2, Allocator A2 > requires matrix_details::element_pair< T1, T2 > + matrix_details::promoted_matrix_t< matrix_details::common_element_t< T1, T2 >, T1, A1 > + operator*( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) noexcept { - matrix< T1, A1 > ans( lhs ); - ans *= rhs; + FENG_MATRIX_EXPECTS( lhs.col() == rhs.row(), "operator *: operand shape mismatch, ", lhs.row(), "x", lhs.col(), " * ", rhs.row(), "x", rhs.col() ); + auto ans = matrix_details::promote< matrix_details::common_element_t< T1, T2 > >( lhs ); + ans *= matrix_details::operand_as( ans, rhs ); return ans; } - template < typename T1, Allocator A1, typename T2, Allocator A2 > - const matrix< T1, A1 > - operator/( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) + template < typename T1, Allocator A1, typename T2, Allocator A2 > requires matrix_details::element_pair< T1, T2 > + matrix_details::promoted_matrix_t< matrix_details::common_element_t< T1, T2 >, T1, A1 > + operator/( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) noexcept { - matrix< T1, A1 > ans( lhs ); - ans /= rhs; + FENG_MATRIX_EXPECTS( rhs.row() == rhs.col() && lhs.col() == rhs.row(), "operator /: operand shape mismatch, ", lhs.row(), "x", lhs.col(), " / ", rhs.row(), "x", rhs.col() ); + auto ans = matrix_details::promote< matrix_details::common_element_t< T1, T2 > >( lhs ); + ans /= matrix_details::operand_as( ans, rhs ); return ans; } template < typename T1, Allocator A1, typename T2, Allocator A2 > - bool operator<( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) + bool operator<( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) noexcept { better_assert( lhs.row() == rhs.row() ); better_assert( lhs.col() == rhs.col() ); return std::lexicographical_compare( lhs.begin(), lhs.end(), rhs.begin(), rhs.end() ); } template < typename T1, Allocator A1, typename T2, Allocator A2 > - bool operator==( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) + bool operator==( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) noexcept { better_assert( lhs.row() == rhs.row() ); better_assert( lhs.col() == rhs.col() ); return std::equal( lhs.begin(), lhs.end(), rhs.begin() ); } template < typename T1, Allocator A1, typename T2, Allocator A2 > - bool operator>( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) + bool operator>( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) noexcept { return !( ( lhs < rhs ) || ( lhs == rhs ) ); } template < typename T1, Allocator A1, typename T2, Allocator A2 > - bool operator>=( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) + bool operator>=( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) noexcept { return !( lhs < rhs ); } template < typename T1, Allocator A1, typename T2, Allocator A2 > - bool operator<=( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) + bool operator<=( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) noexcept { return !( lhs > rhs ); } template < typename T1, Allocator A1, typename T2, Allocator A2 > - const matrix< T1, A1 > - operator||( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) + matrix< T1, A1 > + operator||( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) noexcept { if ( lhs.row() == 0 ) return rhs; @@ -4194,8 +5781,8 @@ namespace feng return ans; } template < typename T1, Allocator A1, typename T2, Allocator A2 > - const matrix< T1, A1 > - operator&&( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) + matrix< T1, A1 > + operator&&( const matrix< T1, A1 >& lhs, const matrix< T2, A2 >& rhs ) noexcept { if ( lhs.col() == 0 ) return rhs; @@ -4219,8 +5806,8 @@ namespace feng return ans; } template < typename T, Allocator A, typename T_ > - const matrix< T, A > - operator*( const matrix< T, A >& lhs, const T_* const rhs ) + matrix< T, A > + operator*( const matrix< T, A >& lhs, const T_* const rhs ) noexcept { matrix< T, A > ans( lhs.row(), 1 ); @@ -4230,8 +5817,8 @@ namespace feng return ans; } template < typename T, Allocator A, typename T_ > - const matrix< T, A > - operator*( const T_* lhs, const matrix< T, A >& rhs ) + matrix< T, A > + operator*( const T_* lhs, const matrix< T, A >& rhs ) noexcept { matrix< T, A > ans( 1, rhs.col() ); @@ -4241,10 +5828,10 @@ namespace feng return ans; } template < typename T, Allocator A, typename T_ > - const matrix< T, A > - operator*( const matrix< T, A >& lhs, const std::valarray< T_ >& rhs ) + matrix< T, A > + operator*( const matrix< T, A >& lhs, const std::valarray< T_ >& rhs ) noexcept { - better_assert( lhs.col() == rhs.size() ); + better_assert( lhs.col() == rhs.size(), "matrix * valarray: shape mismatch, matrix columns ", lhs.col(), " but valarray length ", rhs.size() ); matrix< T, A > ans( lhs.row(), 1 ); for ( std::uint_least64_t i = 0; i < lhs.row(); ++i ) @@ -4253,11 +5840,11 @@ namespace feng return ans; } template < typename T, Allocator A, typename T_ > - const matrix< T, A > - operator*( const std::valarray< T_ >& lhs, const matrix< T, A >& rhs ) + matrix< T, A > + operator*( const std::valarray< T_ >& lhs, const matrix< T, A >& rhs ) noexcept { - better_assert( rhs.row() == lhs.size() ); - matrix< T, A > ans( 1, lhs.row() ); + better_assert( rhs.row() == lhs.size(), "valarray * matrix: shape mismatch, valarray length ", lhs.size(), " but matrix rows ", rhs.row() ); + matrix< T, A > ans( 1, rhs.col() ); for ( std::uint_least64_t i = 0; i < rhs.col(); ++i ) ans[0][i] = std::inner_product( std::begin( lhs ), std::begin( lhs ) + rhs.row(), rhs.col_begin( i ), T() ); @@ -4265,10 +5852,10 @@ namespace feng return ans; } template < typename T, Allocator A, typename T_ > - const matrix< T, A > - operator*( const matrix< T, A >& lhs, const std::vector< T_ >& rhs ) + matrix< T, A > + operator*( const matrix< T, A >& lhs, const std::vector< T_ >& rhs ) noexcept { - better_assert( lhs.col() == rhs.size() ); + better_assert( lhs.col() == rhs.size(), "matrix * vector: shape mismatch, matrix columns ", lhs.col(), " but vector length ", rhs.size() ); matrix< T, A > ans( lhs.row(), 1 ); for ( std::uint_least64_t i = 0; i < lhs.row(); ++i ) @@ -4277,91 +5864,94 @@ namespace feng return ans; } template < typename T, Allocator A, typename T_ > - const matrix< T, A > - operator*( const std::vector< T_ >& lhs, const matrix< T, A >& rhs ) + matrix< T, A > + operator*( const std::vector< T_ >& lhs, const matrix< T, A >& rhs ) noexcept { - better_assert( rhs.col() == lhs.size() ); + // S2-R4 (B-003): the vector length must equal the matrix rows; only rhs.row() elements are read. + better_assert( rhs.row() == lhs.size(), "vector * matrix: shape mismatch, vector length ", lhs.size(), " but matrix rows ", rhs.row() ); matrix< T, A > ans( 1, rhs.col() ); for ( std::uint_least64_t i = 0; i < rhs.col(); ++i ) - ans[0][i] = std::inner_product( lhs.begin(), lhs.end(), rhs.col_begin( i ), T() ); + ans[0][i] = std::inner_product( lhs.begin(), lhs.begin() + rhs.row(), rhs.col_begin( i ), T() ); return ans; } template < typename T1, Allocator A1, typename T2, Allocator A2 > - matrix< T1, A1 > const blkdiag( const matrix< T1, A1 >& m1, const matrix< T2, A2 >& m2 ) + [[nodiscard]] matrix< T1, A1 > blkdiag( const matrix< T1, A1 >& m1, const matrix< T2, A2 >& m2 ) noexcept { - return ( m1 || zeros( m1, m1.row(), m2.col() ) ) && ( zeros( m1, m2.row(), m1.col() ) || m2 ); + return ( m1 || matrix< T1, A1 >{ m1.get_allocator(), m1.row(), m2.col() } ) && ( matrix< T1, A1 >{ m1.get_allocator(), m2.row(), m1.col() } || m2 ); } template < typename T1, Allocator A1, typename T2, Allocator A2, typename... Matrices > - matrix< T1, A1 > const blkdiag( const matrix< T1, A1 >& m1, const matrix< T2, A2 >& m2, const Matrices& ... matrices ) + [[nodiscard]] matrix< T1, A1 > blkdiag( const matrix< T1, A1 >& m1, const matrix< T2, A2 >& m2, const Matrices& ... matrices ) noexcept { return blkdiag( blkdiag( m1, m2 ), matrices... ); } template < typename T, Allocator A, typename... Matrices > - matrix< T, A > const blk_diag( const matrix< T, A >& m, const Matrices& ... matrices ) + [[nodiscard]] matrix< T, A > blk_diag( const matrix< T, A >& m, const Matrices& ... matrices ) noexcept { return blkdiag( m, matrices... ); } template < typename T, Allocator A, typename... Matrices > - matrix< T, A > const block_diag( const matrix< T, A >& m, const Matrices& ... matrices ) + [[nodiscard]] matrix< T, A > block_diag( const matrix< T, A >& m, const Matrices& ... matrices ) noexcept { return blkdiag( m, matrices... ); } - template < typename T, Allocator A> - matrix< std::complex< T >, A> const ctranspose( const matrix< std::complex< T >, A>& m ) + template < ComplexMatrix CMat > + [[nodiscard]] CMat ctranspose( CMat const& m ) noexcept { return conj( m.transpose() ); } - template < typename T, Allocator A> - T const det( const matrix< T, A >& m ) + template < typename T, Allocator A> requires linalg_element< T > + [[nodiscard]] T det( const matrix< T, A >& m ) noexcept { return m.det(); } template < typename T, Allocator A> - matrix< T, A > const diag( const matrix< T, A >& m, const std::ptrdiff_t offset = 0 ) + [[nodiscard]] matrix< T, A > diag( const matrix< T, A >& m, const std::ptrdiff_t offset = 0 ) noexcept { const std::uint_least64_t dim = std::min( m.row(), m.col() ) + ( offset > 0 ? offset : -offset ); matrix< T, A > ans{ dim, dim }; - std::copy( m.diag_begin(), m.diag_end(), ans.diag_begin( offset ) ); + if ( m.diag_begin() != m.diag_end() ) // an empty source leaves diagonal `offset` absent from ans + std::copy( m.diag_begin(), m.diag_end(), ans.diag_begin( offset ) ); return ans; } namespace diag_private { template < typename Itor > - matrix< typename std::iterator_traits< Itor >::value_type > const - impl_diag( Itor first, Itor last, const std::ptrdiff_t offset = 0 ) + matrix< typename std::iterator_traits< Itor >::value_type > + impl_diag( Itor first, Itor last, const std::ptrdiff_t offset = 0 ) noexcept { std::uint_least64_t dim = std::distance( first, last ) + ( offset > 0 ? offset : -offset ); matrix< typename std::iterator_traits< Itor >::value_type > ans{ dim, dim }; - std::copy( first, last, ans.diag_begin( offset ) ); + if ( first != last ) // an empty source leaves diagonal `offset` absent from ans + std::copy( first, last, ans.diag_begin( offset ) ); return ans; } } template < typename T, Allocator A> - matrix< T > const diag( const std::vector< T, A >& v, const std::ptrdiff_t offset = 0 ) + [[nodiscard]] matrix< T > diag( const std::vector< T, A >& v, const std::ptrdiff_t offset = 0 ) noexcept { return diag_private::impl_diag( v.begin(), v.end(), offset ); } template < typename T, Allocator A> - matrix< T > const diag( const std::deque< T, A >& v, const std::ptrdiff_t offset = 0 ) + [[nodiscard]] matrix< T > diag( const std::deque< T, A >& v, const std::ptrdiff_t offset = 0 ) noexcept { return diag_private::impl_diag( v.begin(), v.end(), offset ); } template < typename T, typename C, Allocator A> - matrix< T > const diag( const std::set< T, C, A >& v, const std::ptrdiff_t offset = 0 ) + [[nodiscard]] matrix< T > diag( const std::set< T, C, A >& v, const std::ptrdiff_t offset = 0 ) noexcept { return diag_private::impl_diag( v.begin(), v.end(), offset ); } template < typename T, typename C, Allocator A> - matrix< T > const diag( const std::multiset< T, C, A >& v, const std::ptrdiff_t offset = 0 ) + [[nodiscard]] matrix< T > diag( const std::multiset< T, C, A >& v, const std::ptrdiff_t offset = 0 ) noexcept { return diag_private::impl_diag( v.begin(), v.end(), offset ); } template < typename T > - matrix< T > const diag( const std::valarray< T >& v, const std::ptrdiff_t offset = 0 ) + [[nodiscard]] matrix< T > diag( const std::valarray< T >& v, const std::ptrdiff_t offset = 0 ) noexcept { return diag_private::impl_diag( std::begin( v ), std::end( v ), offset ); } @@ -4370,25 +5960,25 @@ namespace feng struct impl_make_diag { std::uint_least64_t pos; - impl_make_diag( const std::uint_least64_t pos_ = 0 ) + impl_make_diag( const std::uint_least64_t pos_ = 0 ) noexcept : pos( pos_ ) { } template < typename T, Allocator A, typename Arg, typename... Args > - void operator()( matrix< T, A >& m, const Arg& arg, const Args& ... args ) const + void operator()( matrix< T, A >& m, const Arg& arg, const Args& ... args ) const noexcept { m[pos][pos] = arg; impl_make_diag( pos + 1 )( m, args... ); } template < typename T, Allocator A, typename Arg > - void operator()( matrix< T, A >& m, const Arg& arg ) const + void operator()( matrix< T, A >& m, const Arg& arg ) const noexcept { *( m.diag_rbegin() ) = arg; } }; } template < typename T, typename... Tn > - matrix< T > const make_diag( const T& v1, const Tn& ... vn ) + [[nodiscard]] matrix< T > make_diag( const T& v1, const Tn& ... vn ) noexcept { const std::uint_least64_t n = 1 + sizeof...( vn ); matrix< T > ans{ n, n }; @@ -4396,18 +5986,18 @@ namespace feng return ans; } template < typename T, Allocator A> - void display( const matrix< T, A >& m ) + void display( const matrix< T, A >& m ) noexcept { std::cout << m << std::endl; } template < typename T, Allocator A> - void disp( const matrix< T, A >& m ) + void disp( const matrix< T, A >& m ) noexcept { display( m ); } template < typename Matrix1, typename Matrix2 > - typename Matrix1::value_type - dot( const Matrix1& m1, const Matrix2& m2 ) + [[nodiscard]] typename Matrix1::value_type + dot( const Matrix1& m1, const Matrix2& m2 ) noexcept { better_assert( m1.row() == m2.row() ); better_assert( m1.col() == m2.col() ); @@ -4418,7 +6008,7 @@ namespace feng template < typename T > struct one_maker { - T operator()() const + T operator()() const noexcept { return T( 1 ); } @@ -4426,80 +6016,66 @@ namespace feng template < typename T > struct one_maker< std::complex< T >> { - std::complex< T > const operator()() const + std::complex< T > operator()() const noexcept { return std::complex< T >( T( 1 ), T( 0 ) ); } }; }; template < typename T, typename A = std::allocator< T > > - matrix< T, A > const eye( const std::uint_least64_t r, const std::uint_least64_t c ) + [[nodiscard]] matrix< T, A > eye( const std::uint_least64_t r, const std::uint_least64_t c ) noexcept { matrix< T > ans{ r, c }; std::fill( ans.diag_begin(), ans.diag_end(), eye_private::one_maker< T >()() ); return ans; } template < typename T, typename A = std::allocator< T > > - matrix< T, A > const eye( const std::uint_least64_t n ) + [[nodiscard]] matrix< T, A > eye( const std::uint_least64_t n ) noexcept { return eye< T, A >( n, n ); } template < typename T, typename A = std::allocator< T > > - matrix< T, A > const eye( const matrix< T, A >& m ) + [[nodiscard]] matrix< T, A > eye( const matrix< T, A >& m ) noexcept { return eye< T, A >( m.row(), m.col() ); } + // S2-R3 (D-004): dim 1 maps row i to row rows-1-i, dim 2 maps column j to column cols-1-j; fewer than two rows + // or columns returns the copy; any other dim aborts with `flipdim`. template < typename T, Allocator A> - matrix< T, A > const flipdim( const matrix< T, A >& m, const std::uint_least64_t dim ) + [[nodiscard]] matrix< T, A > flipdim( const matrix< T, A >& m, const std::uint_least64_t dim ) noexcept { + FENG_MATRIX_EXPECTS( 1 == dim || 2 == dim, "matrix flipdim: dim should be 1 or 2, got ", dim ); matrix< T, A > ans{ m }; if ( 1 == dim ) { - std::uint_least64_t index_upper = 0; - std::uint_least64_t index_lower = m.row() - 1; - - while ( index_lower > index_upper ) - { - std::swap_ranges( ans.row_begin( index_lower ), ans.row_end( index_lower ), ans.row_begin( index_upper ) ); - --index_lower; - ++index_upper; - } - + if ( ans.row() < 2 ) + return ans; + for ( std::uint_least64_t upper = 0, lower = ans.row() - 1; upper < lower; ++upper, --lower ) + std::swap_ranges( ans.row_begin( upper ), ans.row_end( upper ), ans.row_begin( lower ) ); return ans; } - if ( 2 == dim ) - { - std::uint_least64_t index_left = 0; - std::uint_least64_t index_right = m.col() - 1; - - while ( index_right > index_left ) - { - std::swap_ranges( ans.col_begin( index_left ), ans.col_end( index_left ), ans.row_begin( index_right ) ); - --index_right; - ++index_left; - } - + if ( ans.col() < 2 ) return ans; - } - - better_assert( !"the second argument of flipdim should be '1' or '2'" ); + for ( std::uint_least64_t r = 0; r != ans.row(); ++r ) + std::reverse( ans.row_begin( r ), ans.row_end( r ) ); return ans; } + // D-004: MATLAB/NumPy convention, fliplr flips columns and flipud flips rows. template < typename T, Allocator A> - matrix< T, A > const fliplr( const matrix< T, A >& m ) + [[nodiscard]] matrix< T, A > fliplr( const matrix< T, A >& m ) noexcept { - return flipdim( m, 1 ); + return flipdim( m, 2 ); } template < typename T, Allocator A> - matrix< T, A > const flipud( const matrix< T, A >& m ) + [[nodiscard]] matrix< T, A > flipud( const matrix< T, A >& m ) noexcept { - return flipdim( m, 2 ); + return flipdim( m, 1 ); } template < typename T, - typename A = std::allocator< typename std::remove_const< typename std::remove_reference< T >::result_type >::result_type >> - matrix< T, A > const hilb( const std::uint_least64_t n ) + typename A = std::allocator< std::remove_cvref_t< T > >> + [[nodiscard]] matrix< T, A > hilb( const std::uint_least64_t n ) noexcept { matrix< T, A > ans( n, n ); @@ -4513,13 +6089,13 @@ namespace feng return ans; } template < typename T, - typename A = std::allocator< typename std::remove_const< typename std::remove_reference< T >::result_type >::result_type >> - matrix< T, A > const hilbert( const std::uint_least64_t n ) + typename A = std::allocator< std::remove_cvref_t< T > >> + [[nodiscard]] matrix< T, A > hilbert( const std::uint_least64_t n ) noexcept { return hilb< T, A >( n ); } template < typename Matrix > - Matrix const hilb( const std::uint_least64_t n, const Matrix& ) + [[nodiscard]] Matrix hilb( const std::uint_least64_t n, const Matrix& ) noexcept { typedef typename Matrix::value_type value_type; Matrix ans( n, n ); @@ -4534,102 +6110,102 @@ namespace feng return ans; } template < typename Matrix > - Matrix const hilbert( const std::uint_least64_t n, const Matrix& m ) + [[nodiscard]] Matrix hilbert( const std::uint_least64_t n, const Matrix& m ) noexcept { return hilb( n, m ); } - template < typename Matrix > - Matrix const inverse( const Matrix& m ) + template < typename Matrix > requires linalg_element< typename Matrix::value_type > + [[nodiscard]] Matrix inverse( const Matrix& m ) noexcept { return m.inverse(); } - template < typename Matrix > - Matrix const inv( const Matrix& m ) + template < typename Matrix > requires linalg_element< typename Matrix::value_type > + [[nodiscard]] Matrix inv( const Matrix& m ) noexcept { return m.inverse(); } template < typename T, Allocator A> - bool is_column( const matrix< T, A >& m ) + [[nodiscard]] bool is_column( const matrix< T, A >& m ) noexcept { return m.col() == 1; } template < typename T, Allocator A> - bool is_column_matrix( const matrix< T, A >& m ) + [[nodiscard]] bool is_column_matrix( const matrix< T, A >& m ) noexcept { return is_column( m ); } template < typename T, Allocator A> - bool iscolumn( const matrix< T, A >& m ) + [[nodiscard]] bool iscolumn( const matrix< T, A >& m ) noexcept { return is_column( m ); } template < typename T, Allocator A> - bool is_empty( const matrix< T, A >& m ) + [[nodiscard]] bool is_empty( const matrix< T, A >& m ) noexcept { return m.size() == 0; } template < typename T, Allocator A> - bool is_empty_matrix( const matrix< T, A >& m ) + [[nodiscard]] bool is_empty_matrix( const matrix< T, A >& m ) noexcept { return is_empty( m ); } template < typename T, Allocator A> - bool isempty( const matrix< T, A >& m ) + [[nodiscard]] bool isempty( const matrix< T, A >& m ) noexcept { return is_empty( m ); } template < typename T, Allocator A> - bool is_equal( const matrix< T, A >& m1, const matrix< T, A > m2 ) + [[nodiscard]] bool is_equal( const matrix< T, A >& m1, const matrix< T, A > m2 ) noexcept { return m1 == m2; } template < typename M1, typename M2, typename... Mn > - bool is_equal( const M1& m1, const M2& m2, const Mn& ... mn ) + [[nodiscard]] bool is_equal( const M1& m1, const M2& m2, const Mn& ... mn ) noexcept { return is_equal( m1, m2 ) && is_equal( m2, mn... ); } template < typename T, Allocator A> - bool isequal( const matrix< T, A >& m1, const matrix< T, A > m2 ) + [[nodiscard]] bool isequal( const matrix< T, A >& m1, const matrix< T, A > m2 ) noexcept { return is_equal( m1, m2 ); } template < typename M1, typename M2, typename... Mn > - bool isequal( const M1& m1, const M2& m2, const Mn& ... mn ) + [[nodiscard]] bool isequal( const M1& m1, const M2& m2, const Mn& ... mn ) noexcept { return is_equal( m1, m2 ) && is_equal( m2, mn... ); } template < typename T, Allocator A> - matrix< bool > is_inf( const matrix< T, A >& m ) + [[nodiscard]] matrix< std::uint8_t > is_inf( const matrix< T, A >& m ) noexcept // D-017: 0/1 byte mask { - matrix< bool > ans( m.row(), m.col() ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( const T & v, bool & a ) + matrix< std::uint8_t > ans( m.row(), m.col() ); + matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( const T & v, std::uint8_t & a ) { - a = std::isinf( v ) ? true : false; + a = std::isinf( v ) ? 1 : 0; } ); return ans; } template < typename T, Allocator A> - matrix< bool > isinf( const matrix< T, A >& m ) + [[nodiscard]] matrix< std::uint8_t > isinf( const matrix< T, A >& m ) noexcept { return is_inf( m ); } template < typename T, Allocator A> - matrix< bool > is_nan( const matrix< T, A >& m ) + [[nodiscard]] matrix< std::uint8_t > is_nan( const matrix< T, A >& m ) noexcept // D-017: 0/1 byte mask { - matrix< bool > ans( m.row(), m.col() ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( const T & v, bool & a ) + matrix< std::uint8_t > ans( m.row(), m.col() ); + matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( const T & v, std::uint8_t & a ) { - a = std::isnan( v ) ? true : false; + a = std::isnan( v ) ? 1 : 0; } ); return ans; } template < typename T, Allocator A> - matrix< bool > isnan( const matrix< T, A >& m ) + [[nodiscard]] matrix< std::uint8_t > isnan( const matrix< T, A >& m ) noexcept { return is_nan( m ); } template < typename T, Allocator A, typename F > - bool is_orthogonal( const matrix< T, A >& m, F f ) + [[nodiscard]] bool is_orthogonal( const matrix< T, A >& m, F f ) noexcept { if ( m.row() != m.col() ) return false; @@ -4642,7 +6218,7 @@ namespace feng return std::all_of( mm.begin(), mm.end(), f ); } template < typename T, Allocator A> - bool is_orthogonal( const matrix< T, A >& m ) + [[nodiscard]] bool is_orthogonal( const matrix< T, A >& m ) noexcept { return is_orthogonal( m, []( const T v ) { @@ -4650,7 +6226,7 @@ namespace feng } ); } template < typename T, Allocator A> - bool is_positive_definite( const matrix< T, A >& m ) + [[nodiscard]] bool is_positive_definite( const matrix< T, A >& m ) noexcept { typedef matrix< T, A > matrix_type; typedef typename matrix_type::range_type range_type; @@ -4669,22 +6245,22 @@ namespace feng return true; } template < typename T, Allocator A> - bool is_row( const matrix< T, A >& m ) + [[nodiscard]] bool is_row( const matrix< T, A >& m ) noexcept { return m.row() == 1; } template < typename T, Allocator A> - bool is_row_matrix( const matrix< T, A >& m ) + [[nodiscard]] bool is_row_matrix( const matrix< T, A >& m ) noexcept { return is_row( m ); } template < typename T, Allocator A> - bool isrow( const matrix< T, A >& m ) + [[nodiscard]] bool isrow( const matrix< T, A >& m ) noexcept { return is_row( m ); } template < typename T, Allocator A, typename F > - bool is_symmetric( const matrix< T, A >& m, F f ) + [[nodiscard]] bool is_symmetric( const matrix< T, A >& m, F f ) noexcept { if ( m.row() != m.col() ) return false; @@ -4697,7 +6273,7 @@ namespace feng } template < typename T, Allocator A> - bool is_symmetric( const matrix< T, A >& m ) + [[nodiscard]] bool is_symmetric( const matrix< T, A >& m ) noexcept { return is_symmetric( m, []( const T v1, const T v2 ) { @@ -4705,17 +6281,20 @@ namespace feng } ); } - inline matrix< std::uint_least64_t > const magic( const std::uint_least64_t n ) noexcept + // S9-R1: the element type and allocator are parameters; the construction runs in std::uint_least64_t + // arithmetic and stores each value as T, so magic( n ) is unchanged. + template < typename T = std::uint_least64_t, typename A = std::allocator< T > > + [[nodiscard]] matrix< T, A > magic( const std::uint_least64_t n ) noexcept { - matrix< std::uint_least64_t > ans{ n, n }; - - if ( 2 == n ) return ans; // no magic for n = 2 - if ( 3 == n ) - return matrix< std::uint_least64_t >{ 3, 3, { 8, 1, 6, 3, 5, 7, 4, 9, 2 } }; + return matrix< T, A >{ 3, 3, { 8, 1, 6, 3, 5, 7, 4, 9, 2 } }; if ( 4 == n ) - return matrix< std::uint_least64_t >{ 4, 4, { 16, 3, 2, 13, 5, 10, 11, 8, 9, 6, 7, 12, 4, 15, 14, 1} }; + return matrix< T, A >{ 4, 4, { 16, 3, 2, 13, 5, 10, 11, 8, 9, 6, 7, 12, 4, 15, 14, 1} }; + + matrix< T, A > ans{ n, n }; // after the literal cases: one allocation per result + + if ( 2 == n ) return ans; // no magic for n = 2 // odd case if ( n & 1 ) @@ -4723,7 +6302,7 @@ namespace feng for ( std::uint_least64_t i = 0; i < n; ++i ) for ( std::uint_least64_t j = 0; j < n; ++j ) //ans[( ( n - 1 ) / 2 + i - j + n ) % n][( 3 * n - 1 + j - 2 * i ) % n] = i * n + j + 1; - ans[n - (( 3 * n - 1 + j - 2 * i ) % n) - 1][n - (( ( n - 1 ) / 2 + i - j + n ) % n) - 1] = i * n + j + 1; + ans[n - (( 3 * n - 1 + j - 2 * i ) % n) - 1][n - (( ( n - 1 ) / 2 + i - j + n ) % n) - 1] = static_cast< T >( i * n + j + 1 ); return ans; } @@ -4732,14 +6311,14 @@ namespace feng if ( n & 2 ) { auto const half = n >> 1; - auto const& m_half = magic( half ); + auto const m_half = magic< std::uint_least64_t >( half ); // L for ( auto r : matrix_details::range( (half+1) >> 1 ) ) for ( auto c : matrix_details::range( half ) ) { auto const val = m_half[r][c]; - ans[r<<1][c<<1] = val << 2; ans[r<<1][(c<<1)+1] = (val << 2) - 3; - ans[(r<<1)+1][c<<1] = (val << 2) - 2; ans[(r<<1)+1][(c<<1)+1] = (val << 2) - 1; + ans[r<<1][c<<1] = static_cast< T >( val << 2 ); ans[r<<1][(c<<1)+1] = static_cast< T >( (val << 2) - 3 ); + ans[(r<<1)+1][c<<1] = static_cast< T >( (val << 2) - 2 ); ans[(r<<1)+1][(c<<1)+1] = static_cast< T >( (val << 2) - 1 ); } // U @@ -4747,8 +6326,8 @@ namespace feng { auto const r = (half+1) >> 1; auto const val = m_half[r][c]; - ans[r<<1][c<<1] = (val << 2) - 3; ans[r<<1][(c<<1)+1] = (val << 2); - ans[(r<<1)+1][c<<1] = (val << 2) - 2; ans[(r<<1)+1][(c<<1)+1] = (val << 2) - 1; + ans[r<<1][c<<1] = static_cast< T >( (val << 2) - 3 ); ans[r<<1][(c<<1)+1] = static_cast< T >( (val << 2) ); + ans[(r<<1)+1][c<<1] = static_cast< T >( (val << 2) - 2 ); ans[(r<<1)+1][(c<<1)+1] = static_cast< T >( (val << 2) - 1 ); } // swap central block @@ -4757,14 +6336,14 @@ namespace feng { auto const [r,c] = std::make_tuple( (half-1)>>1, (half-1)>>1 ); auto const val = m_half[r][c]; - ans[r<<1][c<<1] = (val << 2) - 3; ans[r<<1][(c<<1)+1] = (val << 2); - ans[(r<<1)+1][c<<1] = (val << 2) - 2; ans[(r<<1)+1][(c<<1)+1] = (val << 2) - 1; + ans[r<<1][c<<1] = static_cast< T >( (val << 2) - 3 ); ans[r<<1][(c<<1)+1] = static_cast< T >( (val << 2) ); + ans[(r<<1)+1][c<<1] = static_cast< T >( (val << 2) - 2 ); ans[(r<<1)+1][(c<<1)+1] = static_cast< T >( (val << 2) - 1 ); } { auto const [r,c] = std::make_tuple( (half+1)>>1, (half+1)>>1 ); auto const val = m_half[r][c]; - ans[r<<1][c<<1] = (val << 2); ans[r<<1][(c<<1)+1] = (val << 2) - 3; - ans[(r<<1)+1][c<<1] = (val << 2) - 2; ans[(r<<1)+1][(c<<1)+1] = (val << 2) - 1; + ans[r<<1][c<<1] = static_cast< T >( (val << 2) ); ans[r<<1][(c<<1)+1] = static_cast< T >( (val << 2) - 3 ); + ans[(r<<1)+1][c<<1] = static_cast< T >( (val << 2) - 2 ); ans[(r<<1)+1][(c<<1)+1] = static_cast< T >( (val << 2) - 1 ); } } // X @@ -4772,15 +6351,15 @@ namespace feng for ( auto c : matrix_details::range( half ) ) { auto const& val = m_half[r][c]; - ans[r<<1][c<<1] = (val << 2) - 3; ans[r<<1][(c<<1)+1] = (val << 2); - ans[(r<<1)+1][c<<1] = (val << 2) - 1; ans[(r<<1)+1][(c<<1)+1] = (val << 2) - 2; + ans[r<<1][c<<1] = static_cast< T >( (val << 2) - 3 ); ans[r<<1][(c<<1)+1] = static_cast< T >( (val << 2) ); + ans[(r<<1)+1][c<<1] = static_cast< T >( (val << 2) - 1 ); ans[(r<<1)+1][(c<<1)+1] = static_cast< T >( (val << 2) - 2 ); } return ans; } // doubly even - std::iota( ans.begin(), ans.end(), 1 ); + std::iota( ans.begin(), ans.end(), static_cast< T >( 1 ) ); std::reverse( ans.diag_begin(), ans.diag_end() ); std::reverse( ans.anti_diag_begin(), ans.anti_diag_end() ); std::swap_ranges( ans.lower_diag_begin( n >> 1 ), ans.lower_diag_end( n >> 1 ), ans.upper_diag_rbegin( n >> 1 ) ); @@ -4789,21 +6368,21 @@ namespace feng } template< Matrix Mat > - auto const max( Mat const& m ) + [[nodiscard]] auto max( Mat const& m ) noexcept { - better_assert( m.size() ); + FENG_MATRIX_EXPECTS( m.size() != 0, "feng::max: empty matrix, shape ", m.row(), "x", m.col() ); return *std::max_element( m.begin(), m.end() ); } template< Matrix Mat > - auto const min( Mat const& m ) + [[nodiscard]] auto min( Mat const& m ) noexcept { - better_assert( m.size() ); + FENG_MATRIX_EXPECTS( m.size() != 0, "feng::min: empty matrix, shape ", m.row(), "x", m.col() ); return *std::min_element( m.begin(), m.end() ); } template < typename T > - matrix arange( std::uint_least64_t start, const std::uint_least64_t stop, const std::uint_least64_t step = 1ULL ) + [[nodiscard]] matrix arange( std::uint_least64_t start, const std::uint_least64_t stop, const std::uint_least64_t step = 1ULL ) noexcept { matrix ans{ 1, stop-start/step }; for ( auto& v : ans ) @@ -4815,7 +6394,7 @@ namespace feng } template < typename T > - matrix arange( const std::uint_least64_t length ) + [[nodiscard]] matrix arange( const std::uint_least64_t length ) noexcept { matrix ans{ 1, length }; std::iota( ans.begin(), ans.end(), T{0} ); @@ -4823,7 +6402,7 @@ namespace feng } template < typename T > - matrix linspace( T start, T stop, const std::uint_least64_t num = 50ULL, bool end_point=true ) + [[nodiscard]] matrix linspace( T start, T stop, const std::uint_least64_t num = 50ULL, bool end_point=true ) noexcept { if ( 0 == num ) return matrix{}; @@ -4841,452 +6420,520 @@ namespace feng } template < Matrix Mat > - Mat const ones_like( Mat const& mat ) noexcept + [[nodiscard]] Mat ones_like( Mat const& mat ) noexcept { return Mat{ mat.get_allocator(), mat.row(), mat.col(), typename Mat::value_type{1} }; } template < typename T > - matrix> const ones( std::integral auto r, std::integral auto c ) noexcept + [[nodiscard]] matrix> ones( std::integral auto r, std::integral auto c ) noexcept { matrix< T > ans{ static_cast(r), static_cast(c), T{ 1 } }; return ans; } template < typename T > - matrix> const ones( std::integral auto n ) noexcept + [[nodiscard]] matrix> ones( std::integral auto n ) noexcept { return ones< T >( n, n ); } template < typename T, Allocator A> - matrix< T, A > const ones( A const& alloc, std::integral auto r, std::integral auto c ) noexcept + [[nodiscard]] matrix< T, A > ones( A const& alloc, std::integral auto r, std::integral auto c ) noexcept { - return { alloc, static_cast(r) , static_cast(c), T{1} }; + return matrix< T, A >{ alloc, static_cast(r) , static_cast(c), T{1} }; } template < typename T, Allocator A> - matrix< T, A > const ones( A const& alloc, std::integral auto n ) noexcept + [[nodiscard]] matrix< T, A > ones( A const& alloc, std::integral auto n ) noexcept { - return { alloc, static_cast(n) , static_cast(n), T{1} }; + return matrix< T, A >{ alloc, static_cast(n) , static_cast(n), T{1} }; } template < Matrix Mat > - Mat const zeros_like( Mat const& mat ) noexcept + [[nodiscard]] Mat zeros_like( Mat const& mat ) noexcept { return Mat{ mat.get_allocator(), mat.row(), mat.col(), typename Mat::value_type{} }; } template < typename T > - matrix> const zeros( std::integral auto r, std::integral auto c ) noexcept + [[nodiscard]] matrix> zeros( std::integral auto r, std::integral auto c ) noexcept { matrix< T > ans{ static_cast(r), static_cast(c), T{} }; return ans; } template < typename T > - matrix> const zeros( std::integral auto n ) noexcept + [[nodiscard]] matrix> zeros( std::integral auto n ) noexcept { return zeros< T >( n, n ); } template < typename T, Allocator A> - matrix< T, A > const zeros( A const& alloc, std::integral auto r, std::integral auto c ) noexcept + [[nodiscard]] matrix< T, A > zeros( A const& alloc, std::integral auto r, std::integral auto c ) noexcept { - return { alloc, static_cast(r) , static_cast(c), T{} }; + return matrix< T, A >{ alloc, static_cast(r) , static_cast(c), T{} }; } template < typename T, Allocator A> - matrix< T, A > const zeros( A const& alloc, std::integral auto n ) noexcept + [[nodiscard]] matrix< T, A > zeros( A const& alloc, std::integral auto n ) noexcept { - return { alloc, static_cast(n) , static_cast(n), T{} }; + return matrix< T, A >{ alloc, static_cast(n) , static_cast(n), T{} }; } template < typename T > - auto const empty( const std::integral auto r, const std::integral auto c ) noexcept + [[nodiscard]] auto empty( const std::integral auto r, const std::integral auto c ) noexcept { return matrix< T >{ static_cast(r), static_cast(c) }; } template < typename T, Allocator A> - matrix< T, A > const empty( A const& alloc, std::integral auto r, std::integral auto c ) noexcept + [[nodiscard]] matrix< T, A > empty( A const& alloc, std::integral auto r, std::integral auto c ) noexcept { - return { alloc, r , c }; + return matrix< T, A >{ alloc, static_cast(r), static_cast(c) }; } - template < typename T, typename A_ = std::allocator< T >> - std::uint_least64_t - singular_value_decomposition( matrix const& A, - matrix& u, - matrix& w, - matrix& v, - std::uint_least64_t const max_its = 1000 ) - { - typedef T value_type; - const value_type zero{ 0 }; - const value_type one{ 1 }; - auto const [m, n] = A.shape(); - u = A; - w.resize( n, n ); - v.resize( n, n ); - std::uint_least64_t i{ 0 }, l{ 0 }; - value_type c{ 0 }, f{ 0 }, h{ 0 }; - std::vector< value_type > arr( n ); - value_type g = zero; - value_type s = zero; - value_type scale = zero; - value_type anorm = zero; - - for ( i = 0; i < n; ++i ) - { - l = i + 2; - arr[i] = scale * g; - g = zero; - s = zero; - scale = zero; - - if ( i < m ) - { - scale = std::accumulate( u.col_begin( i ) + i, u.col_end( i ), value_type( 0 ), []( value_type v1, value_type v2 ) { return v1 + std::abs( v2 ); } ); - - if ( scale != zero ) - { - matrix_details::for_each( u.col_begin( i ) + i, u.col_end( i ), [scale]( value_type & v ) { v /= scale; } ); - const value_type tmp_s = std::inner_product( u.col_begin( i ) + i, u.col_end( i ), u.col_begin( i ) + i, value_type( 0 ) ); - g = ( u[i][i] >= zero ) ? -std::sqrt( tmp_s ) : std::sqrt( tmp_s ); - const value_type tmp_h = u[i][i] * g - tmp_s; - u[i][i] -= g; - - for ( std::uint_least64_t j = l - 1; j < n; ++j ) - { - const value_type tmp_ss = std::inner_product( u.col_begin( i ) + i, u.col_end( i ), u.col_begin( j ) + i, value_type( 0 ) ); - std::transform( u.col_begin( j ) + i, u.col_end( j ), u.col_begin( i ) + i, u.col_begin( j ) + i, [tmp_ss, tmp_h]( value_type v1, value_type v2 ) { return v1 + tmp_ss * v2 / tmp_h; } ); - } - - matrix_details::for_each( u.col_begin( i ) + i, u.col_end( i ), [scale]( value_type & v ) { v *= scale; } ); - } - } - - w[i][i] = scale * g; - g = zero; - s = zero; - scale = zero; - - if ( i + 1 <= m && i != n ) - { - scale = std::accumulate( u.row_begin( i ) + l - 1, u.row_end( i ), value_type( 0 ), []( value_type v1, value_type v2 ) { return v1 + std::abs( v2 ); } ); + // S7-R3 (F13, D-026, D-028): one-sided Hestenes Jacobi SVD of an m×n A, real or complex. The kernel runs on + // B = A (m ≥ n) or B = Aᴴ (m < n, the roles of U and V are then swapped), with complex rotations for complex T; + // a sweep visits every column pair and B has converged when every pair has |b_iᴴb_j| ≤ ε·‖b_i‖‖b_j‖. Thin + // factors: u() is m×k, v() is n×k, k = min(m, n), and s() is a std::vector of the k singular values (the real + // type of T), descending, with A = U·diag(s)·Vᴴ. A u column (or, for m < n, v column) whose singular value is + // exactly zero is completed by Gram–Schmidt on unit vectors, so UᴴU = VᴴV = I on all k columns. status() is ok, + // not_converged (the sweep limit was reached; the factors hold the last iterate) or nonfinite (a NaN or inf in + // A; the factors are then empty). A is scaled by its largest |a_ij| for the iteration and s is scaled back. + template < typename T, Allocator A_ = std::allocator< T > > requires linalg_element< T > + class svd_factorization + { + public: + using matrix_type = matrix< T, A_ >; + using real_type = matrix_details::linalg_real_t< T >; - if ( scale != zero ) - { - matrix_details::for_each( u.row_begin( i ) + l - 1, u.row_end( i ), [scale]( value_type & v ) { v /= scale; } ); - auto const tmp_s = std::inner_product( u.row_begin( i ) + l - 1, u.row_end( i ), u.row_begin( i ) + l - 1, value_type( 0 ) ); - g = ( u[i][l - 1] >= zero ) ? -std::sqrt( tmp_s ) : std::sqrt( tmp_s ); - auto const tmp_h = u[i][l - 1] * g - tmp_s; - u[i][l - 1] -= g; - std::transform( u.row_begin( i ) + l - 1, u.row_end( i ), arr.begin() + l - 1, [tmp_h]( value_type v ) { return v / tmp_h; } ); - - for ( std::uint_least64_t j = l - 1; j < m; ++j ) - { - const value_type tmp_ss = std::inner_product( u.row_begin( j ) + l - 1, u.row_end( j ), u.row_begin( i ) + l - 1, value_type( 0 ) ); - std::transform( u.row_begin( j ) + l - 1, u.row_end( j ), arr.begin() + l - 1, u.row_begin( j ) + l - 1, [tmp_ss]( value_type v1, value_type v2 ) { return v1 + tmp_ss * v2; } ); - } + svd_factorization() noexcept = default; + explicit svd_factorization( matrix_type const& a, std::size_t max_sweeps = 64 ) noexcept : m_( a.row() ), n_( a.col() ) + { + factor( a, max_sweeps ); + } - matrix_details::for_each( u.row_begin( i ) + l - 1, u.row_end( i ), [scale]( value_type & v ) - { - v *= scale; - } ); - } - } + [[nodiscard]] linalg_status status() const noexcept { return status_; } + [[nodiscard]] bool ok() const noexcept { return status_ == linalg_status::ok; } + [[nodiscard]] std::size_t sweeps() const noexcept { return sweeps_; } + [[nodiscard]] matrix_type const& u() const noexcept { return u_; } + [[nodiscard]] matrix_type const& v() const noexcept { return v_; } + [[nodiscard]] std::vector< real_type > const& s() const noexcept { return s_; } - anorm = std::max( anorm, ( std::fabs( w[i][i] ) + std::fabs( arr[i] ) ) ); + // the relative cutoff: rtol < 0 selects the default max(m, n)·ε (numpy 2 pinv, D-028) + [[nodiscard]] real_type cutoff( real_type rtol = real_type( -1 ) ) const noexcept + { + if ( rtol < real_type( 0 ) ) rtol = static_cast< real_type >( std::max( m_, n_ ) ) * std::numeric_limits< real_type >::epsilon(); + return s_.empty() ? real_type( 0 ) : rtol * s_[0]; } - - for ( i = n - 1;; --i ) + // the number of s_i > rtol·s_1 + [[nodiscard]] std::size_t rank( real_type rtol = real_type( -1 ) ) const noexcept { - if ( i < n - 1 ) - { - if ( g != zero ) - { - auto const tmp_uil = u[i][l]; - std::transform( u.row_begin( i ) + l, u.row_end( i ), v.col_begin( i ) + l, [g, tmp_uil]( value_type val ) { return val / ( tmp_uil * g ); } ); - - for ( std::uint_least64_t j = l; j < n; j++ ) - { - const auto tmp_s = std::inner_product( u.row_begin( i ) + l, u.row_end( i ), v.col_begin( j ) + l, value_type( 0 ) ); - std::transform( v.col_begin( j ) + l, v.col_end( j ), v.col_begin( i ) + l, v.col_begin( j ) + l, [tmp_s]( value_type v1, value_type v2 ) { return v1 + v2 * tmp_s; } ); - } - } - - std::fill( v.row_begin( i ) + l, v.row_end( i ), zero ); - std::fill( v.col_begin( i ) + l, v.col_end( i ), zero ); - } - - v[i][i] = one; - g = arr[i]; - l = i; - - if ( !i ) - break; + real_type const tol = cutoff( rtol ); + return static_cast< std::size_t >( std::count_if( s_.begin(), s_.end(), [tol]( real_type x ) { return x > tol; } ) ); } - - for ( i = std::min( m, n ) - 1;; --i ) + // A⁺ = V·diag(1/s_i)·Uᴴ (n×m) with every s_i ≤ rtol·s_1 zeroed; a status other than ok and an empty value + // when the factorization is not ok + [[nodiscard]] linalg_result< matrix_type > pinverse( real_type rtol = real_type( -1 ) ) const noexcept { - auto const tmp_l = i + 1; - auto const tmp_g = w[i][i]; - std::fill( u.row_begin( i ) + tmp_l, u.row_end( i ), zero ); - - if ( tmp_g != zero ) - { - for ( std::uint_least64_t j = tmp_l; j < n; j++ ) + if ( !ok() ) return { matrix_type{}, status_ }; + std::size_t const r = rank( rtol ); + matrix_type x( n_, m_ ); + std::fill( x.begin(), x.end(), T{ 0 } ); + for ( std::size_t i = 0; i != n_; ++i ) + for ( std::size_t c = 0; c != r; ++c ) { - auto const tmp_s = std::inner_product( u.col_begin( i ) + tmp_l, u.col_end( i ), u.col_begin( j ) + tmp_l, value_type( 0 ) ); - auto const tmp_f = tmp_s / ( u[i][i] * tmp_g ); - std::transform( u.col_begin( j ) + i, u.col_end( j ), u.col_begin( i ) + i, u.col_begin( j ) + i, [tmp_f]( value_type v1, value_type v2 ) { return v1 + tmp_f * v2; } ); + T const vi = v_[i][c] / s_[c]; + T* const xi = x.data() + i * m_; + for ( std::size_t j = 0; j != m_; ++j ) xi[j] += vi * conj_( u_[j][c] ); } + if ( !matrix_details::all_finite( x.data(), x.size() ) ) return { matrix_type{}, linalg_status::nonfinite }; + return { std::move( x ), linalg_status::ok }; + } - matrix_details::for_each( u.col_begin( i ) + i, u.col_end( i ), [tmp_g]( value_type & v ) { v /= tmp_g; } ); - } - else - std::fill( u.col_begin( i ) + i, u.col_end( i ), zero ); - - ++u[i][i]; - - if ( !i ) - break; + private: + static T conj_( T const& x ) noexcept + { + if constexpr ( matrix_private::is_std_complex_v< T > ) return std::conj( x ); + else return x; } - for ( std::uint_least64_t k = n - 1;; --k ) + // makes the k-th column of the column-major M×k array `q` (columns 0..k-1 orthonormal) a unit vector + // orthogonal to them: the first unit vector e_t whose residual after two Gram–Schmidt passes exceeds 1/2 + static void complete_( std::vector< T >& q, std::size_t M, std::size_t k ) noexcept { - for ( std::uint_least64_t its = 0; its < max_its; its++ ) + T* const x = q.data() + k * M; + for ( std::size_t t = 0; t != M; ++t ) { - bool flag = true; - std::uint_least64_t tmp_nm = 0; - - for ( l = k;; l-- ) - { - tmp_nm = l - 1; - - if ( std::fabs( arr[l] ) + anorm == anorm ) - { - flag = false; - break; - } - - if ( std::fabs( w[l - 1][l - 1] ) + anorm == anorm ) + std::fill( x, x + M, T{ 0 } ); + x[t] = T{ 1 }; + for ( int pass = 0; pass != 2; ++pass ) + for ( std::size_t c = 0; c != k; ++c ) { - break; + T const* const qc = q.data() + c * M; + T d{ 0 }; + for ( std::size_t i = 0; i != M; ++i ) d += conj_( qc[i] ) * x[i]; + for ( std::size_t i = 0; i != M; ++i ) x[i] -= d * qc[i]; } - - if ( l == 0 ) - break; + real_type nrm{ 0 }; + for ( std::size_t i = 0; i != M; ++i ) nrm += std::norm( x[i] ); + nrm = std::sqrt( nrm ); + if ( nrm > real_type( 0.5 ) ) + { + for ( std::size_t i = 0; i != M; ++i ) x[i] /= nrm; + return; } + } + } - if ( flag ) + void factor( matrix_type const& a, std::size_t max_sweeps ) noexcept + { + using R = real_type; + T const* const ad = a.data(); + if ( !matrix_details::all_finite( ad, m_ * n_ ) ) { status_ = linalg_status::nonfinite; return; } + bool const tall = m_ >= n_; + std::size_t const M = tall ? m_ : n_; // B is M×N, M ≥ N + std::size_t const N = tall ? n_ : m_; + R scale{ 0 }; + for ( std::size_t i = 0; i != m_ * n_; ++i ) scale = std::max( scale, R( std::abs( ad[i] ) ) ); + R const inv_scale = scale > R( 0 ) ? R( 1 ) / scale : R( 1 ); + // column j of B at b[j·M ..), column j of W at w[j·N ..); B·W stays the rotated B and W is unitary + std::vector< T > b( M * N ), w( N * N, T{ 0 } ); + for ( std::size_t i = 0; i != m_; ++i ) + for ( std::size_t j = 0; j != n_; ++j ) { - c = zero; - s = one; + T const x = ad[i * n_ + j] * inv_scale; + if ( tall ) b[j * M + i] = x; + else b[i * M + j] = conj_( x ); + } + for ( std::size_t j = 0; j != N; ++j ) w[j * N + j] = T{ 1 }; - for ( i = l - 1; i < k + 1; ++i ) + R const eps = std::numeric_limits< R >::epsilon(); + bool converged = N < 2; + while ( !converged && sweeps_ < max_sweeps ) + { + ++sweeps_; + bool rotated = false; + for ( std::size_t p = 0; p + 1 < N; ++p ) + for ( std::size_t q = p + 1; q != N; ++q ) { - f = s * arr[i]; - arr[i] = c * arr[i]; - - if ( std::fabs( f ) + anorm == anorm ) + T* const bp = b.data() + p * M; + T* const bq = b.data() + q * M; + R alpha{ 0 }, beta{ 0 }; + T gamma{ 0 }; + for ( std::size_t i = 0; i != M; ++i ) { - break; + alpha += std::norm( bp[i] ); + beta += std::norm( bq[i] ); + gamma += conj_( bp[i] ) * bq[i]; } - - g = w[i][i]; - h = std::hypot( f, g ); - w[i][i] = h; - h = one / h; - c = g * h; - s = -f * h; - - for ( std::uint_least64_t j = 0; j < m; ++j ) + R const g = std::abs( gamma ); + if ( g == R( 0 ) || g <= eps * std::sqrt( alpha ) * std::sqrt( beta ) ) continue; + rotated = true; + // with e = γ/|γ| the pair (b_p, ē·b_q) has the real inner product |γ|: rotate it by (c, s) + R const zeta = ( beta - alpha ) / ( R( 2 ) * g ); + R const t = ( zeta >= R( 0 ) ? R( 1 ) : R( -1 ) ) / ( std::abs( zeta ) + std::hypot( R( 1 ), zeta ) ); + R const c = R( 1 ) / std::sqrt( R( 1 ) + t * t ); + R const s = c * t; + T const ebar = conj_( gamma / g ); + auto const rotate = [c, s, ebar]( T* x, T* y, std::size_t len ) noexcept { - value_type y = u[j][tmp_nm]; - value_type z = u[j][i]; - u[j][tmp_nm] = y * c + z * s; - u[j][i] = z * c - y * s; - } + for ( std::size_t i = 0; i != len; ++i ) + { + T const xi = x[i]; + T const yi = ebar * y[i]; + x[i] = c * xi - s * yi; + y[i] = s * xi + c * yi; + } + }; + rotate( bp, bq, M ); + rotate( w.data() + p * N, w.data() + q * N, N ); } - } + converged = !rotated; + } + status_ = converged ? linalg_status::ok : linalg_status::not_converged; - value_type z = w[k][i]; + // singular values, descending order, then normalized left vectors of B + std::vector< R > norms( N ); + for ( std::size_t j = 0; j != N; ++j ) + { + R acc{ 0 }; + for ( std::size_t i = 0; i != M; ++i ) acc += std::norm( b[j * M + i] ); + norms[j] = std::sqrt( acc ); + } + std::vector< std::size_t > idx( N ); + for ( std::size_t j = 0; j != N; ++j ) idx[j] = j; + std::stable_sort( idx.begin(), idx.end(), [&norms]( std::size_t x, std::size_t y ) { return norms[x] > norms[y]; } ); + std::vector< T > ub( M * N ), vb( N * N ); + s_.resize( N ); + for ( std::size_t r = 0; r != N; ++r ) + { + std::size_t const j = idx[r]; + s_[r] = norms[j] * scale; + std::copy( w.begin() + j * N, w.begin() + j * N + N, vb.begin() + r * N ); + if ( norms[j] > R( 0 ) ) + for ( std::size_t i = 0; i != M; ++i ) ub[r * M + i] = b[j * M + i] / norms[j]; + } + for ( std::size_t r = 0; r != N; ++r ) // exact zeros sort last + if ( norms[idx[r]] == R( 0 ) ) complete_( ub, M, r ); - if ( l == k ) - { - if ( z < zero ) - { - w[k][k] = -z; - matrix_details::for_each( v.col_begin( k ), v.col_end( k ), []( value_type & v ) { v = -v; } ); - } - - break; - } - - if ( ( its + 1 ) == max_its ) - return 1; - - value_type x = w[l][l]; - value_type y = w[k - 1][k - 1]; - g = arr[k - 1]; - h = arr[k]; - f = ( ( y - z ) * ( y + z ) + ( g - h ) * ( g + h ) ) / ( 2.0 * h * y ); - g = std::hypot( f, one ); - g = ( f >= zero ) ? g : -g; - f = ( ( x - z ) * ( x + z ) + h * ( ( y / ( f + g ) ) ) - h ) / x; - c = s = one; - - for ( std::uint_least64_t j = l; j <= k - 1; j++ ) - { - g = arr[j + 1]; - y = w[j + 1][j + 1]; - h = s * g; - g = c * g; - z = std::hypot( f, h ); - arr[j] = z; - c = f / z; - s = h / z; - f = x * c + g * s; - g = g * c - x * s; - h = y * s; - y *= c; - matrix_details::for_each( v.col_begin( j ), v.col_end( j ), v.col_begin( j + 1 ), [c, s]( value_type & v1, value_type & v2 ) - { - const auto vv1( v1 ); - const auto vv2( v2 ); - v1 = vv1 * c + vv2 * s; - v2 = vv2 * c - vv1 * s; - } ); - w[j][j] = std::hypot( f, h ); + // A = B (tall): U = U_B, V = W; A = Bᴴ (wide): U = W, V = U_B + auto const fill = []( matrix_type& out, std::vector< T > const& src, std::size_t rows, std::size_t k ) + { + out.resize( rows, k ); + for ( std::size_t i = 0; i != rows; ++i ) + for ( std::size_t c = 0; c != k; ++c ) out[i][c] = src[c * rows + i]; + }; + fill( u_, tall ? ub : vb, m_, N ); + fill( v_, tall ? vb : ub, n_, N ); + } - if ( value_type( 0 ) != w[j][j] ) - { - c = f / w[j][j]; - s = h / w[j][j]; - } + matrix_type u_; + matrix_type v_; + std::vector< real_type > s_; + linalg_status status_ = linalg_status::ok; + std::size_t sweeps_ = 0; + std::size_t m_ = 0; + std::size_t n_ = 0; + }; - matrix_details::for_each( u.col_begin( j ), u.col_end( j ), u.col_begin( j + 1 ), [c, s]( value_type & v1, value_type & v2 ) - { - const auto vv1( v1 ); - const auto vv2( v2 ); - v1 = vv1 * c + vv2 * s; - v2 = vv2 * c - vv1 * s; - } ); - f = c * g + s * y; - x = c * y - s * g; - } + template < typename T, Allocator A > requires linalg_element< T > + [[nodiscard]] svd_factorization< T, A > svd_factor( matrix< T, A > const& a, std::size_t max_sweeps = 64 ) noexcept + { + return svd_factorization< T, A >( a, max_sweeps ); + } - arr[l] = zero; - arr[k] = f; - w[k][k] = x; - i = k; - } + // The one pseudoinverse (S7-R3, D-026, D-028): out = A⁺ (n×m) with s_i ≤ rtol·s_1 zeroed, rtol < 0 selecting + // max(m, n)·ε; out is assigned only when the status is ok. + template < typename T, Allocator A > requires linalg_element< T > + [[nodiscard]] linalg_status pinverse( matrix< T, A > const& a, matrix< T, A >& out, matrix_details::linalg_real_t< T > rtol = -1 ) noexcept + { + auto r = svd_factor( a ).pinverse( rtol ); + if ( r.ok() ) out = std::move( r.value ); + return r.status; + } - if ( !k ) - break; - } + // value-returning forms: an empty 0×0 matrix when the SVD is not ok (D-026) + template < typename T, Allocator A > requires linalg_element< T > + [[nodiscard]] matrix< T, A > pinverse( matrix< T, A > const& a, matrix_details::linalg_real_t< T > rtol = -1 ) noexcept + { + matrix< T, A > out; + (void)pinverse( a, out, rtol ); // the status is reported as the empty result + return out; + } + template < typename T, Allocator A > requires linalg_element< T > + [[nodiscard]] matrix< T, A > pinv( matrix< T, A > const& a, matrix_details::linalg_real_t< T > rtol = -1 ) noexcept + { + return pinverse( a, rtol ); + } + template < typename T, Allocator A > requires linalg_element< T > + [[nodiscard]] matrix< T, A > svd_inverse( matrix< T, A > const& a ) noexcept + { + return pinverse( a ); + } + // Legacy SVD: max_its is the sweep limit. On success returns 0 with u m×k, w k×k diagonal (the singular values, + // descending) and v n×k, k = min(m, n), so A = u·w·vᴴ; otherwise returns 1 with u, w and v unchanged. + template < typename T, typename A_ = std::allocator< T > > requires linalg_element< T > + [[nodiscard]] std::uint_least64_t + singular_value_decomposition( matrix const& A, + matrix& u, + matrix& w, + matrix& v, + std::uint_least64_t const max_its = 64 ) noexcept + { + auto const f = svd_factor( A, static_cast< std::size_t >( max_its ) ); + if ( !f.ok() ) + return 1; + std::size_t const k = f.s().size(); + matrix d( k, k ); + std::fill( d.begin(), d.end(), T{ 0 } ); + for ( std::size_t i = 0; i != k; ++i ) d[i][i] = T( f.s()[i] ); + u = f.u(); + v = f.v(); + w = std::move( d ); return 0; } - template< typename T, Allocator A> - std::optional< std::tuple, matrix, matrix> > + // (u, w, v) as above, or nullopt when the SVD is not ok + template< typename T, Allocator A> requires linalg_element< T > + [[nodiscard]] std::optional< std::tuple, matrix, matrix> > singular_value_decomposition( matrix const& a ) noexcept { - auto const [row, col] = a.shape(); - auto const max_iteration = std::max( std::uint_least64_t{100}, std::max( row, col ) ); - if ( matrix u, w, v; singular_value_decomposition( a, u, w, v, max_iteration ) ) // fail + if ( matrix u, w, v; singular_value_decomposition( a, u, w, v ) ) // fail return {}; else // success - return std::forward_as_tuple( u, w, v ); + return std::make_tuple( std::move( u ), std::move( w ), std::move( v ) ); } - template< typename T, Allocator A> - auto svd( matrix const& a ) noexcept + template< typename T, Allocator A> requires linalg_element< T > + [[nodiscard]] auto svd( matrix const& a ) noexcept { return singular_value_decomposition( a ); } - template < typename T, Allocator A> - matrix const svd_inverse( matrix const& a ) + // S6-R3 (F17, D-024): random matrices from a caller-owned std::uniform_random_bit_generator. Elements are + // drawn in row-major order from the engine alone (no global state). Floating parts lie in the open interval + // (0, 1): a draw equal to 0 or 1 is redrawn. Integral elements come from std::uniform_int_distribution over + // [numeric_limits::min(), max()]; types the distribution does not accept (bool and the character types) + // are drawn as int or unsigned (long long or unsigned long long when wider) and narrowed. + namespace matrix_details { - matrix u; - matrix w; - matrix v; - singular_value_decomposition( a, u, v, w ); - matrix_details::for_each( v.begin(), v.end(), []( auto & val ) { if ( std::abs( val ) > 1.0e-10 ) val = 1.0 / val; }); - return w * v.transpose() * u.transpose(); + template< typename T > + inline constexpr bool random_dependent_false_v = false; + + template< typename T > + inline constexpr bool uniform_int_type_v = + std::is_same_v< T, short > || std::is_same_v< T, int > || std::is_same_v< T, long > || std::is_same_v< T, long long > || + std::is_same_v< T, unsigned short > || std::is_same_v< T, unsigned int > || std::is_same_v< T, unsigned long > || + std::is_same_v< T, unsigned long long >; + + template< typename T > + using uniform_draw_t = std::conditional_t< uniform_int_type_v< T >, T, + std::conditional_t< ( sizeof( T ) <= sizeof( int ) ), + std::conditional_t< std::is_signed_v< T >, int, unsigned >, + std::conditional_t< std::is_signed_v< T >, long long, unsigned long long > > >; + + template< std::floating_point X, typename G > + X real_open_unit( G& g ) noexcept + { + std::uniform_real_distribution< X > dist{ X{ 0 }, X{ 1 } }; + for ( ;; ) + { + X const x = dist( g ); + if ( X{ 0 } < x && x < X{ 1 } ) return x; + } + } + + // a seed for the legacy overloads: the nonzero seed as given, else a steady_clock count mixed with an + // object address (no hardware random device: it may throw) + inline std::uint_least64_t legacy_seed( unsigned int seed, void const* address ) noexcept + { + if ( 0 != seed ) return seed; + auto const ticks = static_cast< std::uint_least64_t >( std::chrono::steady_clock::now().time_since_epoch().count() ); + auto const where = static_cast< std::uint_least64_t >( reinterpret_cast< std::uintptr_t >( address ) ); + return ticks ^ ( where * 0x9E3779B97F4A7C15ULL ); + } } - template < typename Matrix > - Matrix const pinverse( const Matrix& m ) + + template < typename T = double, typename A = std::allocator< T >, typename G > + requires std::uniform_random_bit_generator< std::remove_cvref_t< G > > + [[nodiscard]] matrix< T, A > random_impl( const std::uint_least64_t r, const std::uint_least64_t c, G& g ) noexcept + { + matrix< T, A > ans{ r, c }; + if constexpr ( std::is_floating_point_v< T > ) + { + for ( auto& x : ans ) x = matrix_details::real_open_unit< T >( g ); + } + else if constexpr ( matrix_private::is_std_complex_v< T > ) + { + using X = typename T::value_type; + for ( auto& x : ans ) + { + X const re = matrix_details::real_open_unit< X >( g ); + X const im = matrix_details::real_open_unit< X >( g ); + x = T{ re, im }; + } + } + else if constexpr ( std::is_integral_v< T > ) + { + using D = matrix_details::uniform_draw_t< T >; + std::uniform_int_distribution< D > dist{ static_cast< D >( std::numeric_limits< T >::min() ), static_cast< D >( std::numeric_limits< T >::max() ) }; + for ( auto& x : ans ) x = static_cast< T >( dist( g ) ); + } + else + { + static_assert( matrix_details::random_dependent_false_v< T >, "feng::random/rand: the element type must be a floating-point, std::complex or integral type" ); + } + return ans; + } + + // engine overloads + template < typename T = double, typename A = std::allocator< T >, typename G > + requires std::uniform_random_bit_generator< std::remove_cvref_t< G > > + [[nodiscard]] matrix< T, A > random( std::integral auto r, std::integral auto c, G&& g ) noexcept { - Matrix u, w, v; - singular_value_decomposition( m, u, w, v ); - return v * w * u.transpose(); + return random_impl< T, A >( r, c, g ); } - template < typename Matrix > - Matrix const pinv( const Matrix& m ) + template < typename T = double, typename A = std::allocator< T >, typename G > + requires std::uniform_random_bit_generator< std::remove_cvref_t< G > > + [[nodiscard]] matrix< T, A > random( std::integral auto n, G&& g ) noexcept + { + return random_impl< T, A >( n, n, g ); + } + template < typename T = double, typename A = std::allocator< T >, typename G > + requires std::uniform_random_bit_generator< std::remove_cvref_t< G > > + [[nodiscard]] matrix< T, A > rand( const std::uint_least64_t r, const std::uint_least64_t c, G&& g ) noexcept + { + return random_impl< T, A >( r, c, g ); + } + template < typename T = double, typename A = std::allocator< T >, typename G > + requires std::uniform_random_bit_generator< std::remove_cvref_t< G > > + [[nodiscard]] matrix< T, A > rand( const std::uint_least64_t n, G&& g ) noexcept + { + return random_impl< T, A >( n, n, g ); + } + template < typename T, Allocator A, typename G > + requires std::uniform_random_bit_generator< std::remove_cvref_t< G > > + [[nodiscard]] matrix< T, A > rand_like( matrix< T, A > const& mat, G&& g ) noexcept { - return pinverse( m ); + auto const [row, col] = mat.shape(); + return random_impl< T, A >( row, col, g ); + } + template < typename T, Allocator A, typename G > + requires std::uniform_random_bit_generator< std::remove_cvref_t< G > > + [[nodiscard]] matrix< T, A > random_like( matrix< T, A > const& mat, G&& g ) noexcept + { + auto const [row, col] = mat.shape(); + return random_impl< T, A >( row, col, g ); } - //generating a matrix uniformly in (0, 1) + // legacy overloads: a local std::mt19937_64 seeded per matrix_details::legacy_seed template < typename T = double, typename A = std::allocator< T > > - matrix< T, A > const rand( const std::uint_least64_t r, const std::uint_least64_t c, unsigned int seed = 0 ) noexcept + [[nodiscard]] matrix< T, A > rand( const std::uint_least64_t r, const std::uint_least64_t c, unsigned int seed = 0 ) noexcept { - matrix< T, A > ans{ r, c }; - if ( 0 == seed ) - std::srand( static_cast< unsigned int >( static_cast< std::uint_least64_t >( std::time( nullptr ) ) + reinterpret_cast< std::uint_least64_t >( &ans ) ) ); - else - std::srand( seed ); - - auto const& generator = []() noexcept - { - return ( static_cast( std::rand() ) + 1 ) / ( static_cast( RAND_MAX ) + 2 ); // make sure in open bounds range (0, 1) - }; - std::generate( ans.begin(), ans.end(), generator ); - return ans; + std::mt19937_64 engine{ 0 }; + engine.seed( matrix_details::legacy_seed( seed, &engine ) ); + return random_impl< T, A >( r, c, engine ); } template < typename T = double, typename A = std::allocator< T > > - matrix< T, A > const rand( const std::uint_least64_t n ) + [[nodiscard]] matrix< T, A > rand( const std::uint_least64_t n ) noexcept { return rand< T, A >( n, n ); } template < typename T = double, typename A = std::allocator< T > > - matrix< T, A > const random( std::integral auto r, std::integral auto c ) + [[nodiscard]] matrix< T, A > random( std::integral auto r, std::integral auto c ) noexcept { return rand< T, A >( r, c ); } template < typename T = double, typename A = std::allocator< T > > - matrix< T, A > const random( const std::integral auto n ) + [[nodiscard]] matrix< T, A > random( const std::integral auto n ) noexcept { return rand< T, A >( n ); } template < typename T, Allocator A> - matrix< T, A > const rand_like( matrix const& mat ) noexcept + [[nodiscard]] matrix< T, A > rand_like( matrix const& mat ) noexcept { auto const[row, col] = mat.shape(); return random( row, col ); } template < typename T, Allocator A> - matrix< T, A > const random_like( matrix const& mat ) noexcept + [[nodiscard]] matrix< T, A > random_like( matrix const& mat ) noexcept { return rand_like(mat); } - template < typename T, Allocator A> //pytorch style - matrix< T, A > const randn_like( matrix const& mat ) noexcept + template < typename T, Allocator A> //pytorch style; uniform in (0, 1), an alias of rand_like + [[nodiscard]] matrix< T, A > randn_like( matrix const& mat ) noexcept { return rand_like(mat); } template < typename T, Allocator A> - const matrix< T, A > - repmat( const matrix< T, A >& m, const std::uint_least64_t r, const std::uint_least64_t c ) + [[nodiscard]] matrix< T, A > + repmat( const matrix< T, A >& m, const std::uint_least64_t r, const std::uint_least64_t c ) noexcept { better_assert( r ); better_assert( c ); @@ -5304,8 +6951,8 @@ namespace feng } template < typename Itor1, typename Itor2, - typename A = std::allocator< typename std::remove_const< typename std::remove_reference< typename std::iterator_traits< Itor1 >::value_type >::result_type >::result_type >> - matrix< typename std::iterator_traits< Itor1 >::value_type, A > const toeplitz( Itor1 i1_, Itor1 _i1, Itor2 i2_, Itor2 _i2 ) + typename A = std::allocator< std::remove_cvref_t< typename std::iterator_traits< Itor1 >::value_type > >> + [[nodiscard]] matrix< typename std::iterator_traits< Itor1 >::value_type, A > toeplitz( Itor1 i1_, Itor1 _i1, Itor2 i2_, Itor2 _i2 ) noexcept { std::uint_least64_t r = std::distance( i1_, _i1 ); std::uint_least64_t c = std::distance( i2_, _i2 ); @@ -5321,23 +6968,23 @@ namespace feng } template < typename Itor, - typename A = std::allocator< typename std::remove_const< typename std::remove_reference< typename std::iterator_traits< Itor >::value_type >::result_type >::result_type >> - matrix< typename std::iterator_traits< Itor >::value_type, A > const toeplitz( Itor i_, Itor _i ) + typename A = std::allocator< std::remove_cvref_t< typename std::iterator_traits< Itor >::value_type > >> + [[nodiscard]] matrix< typename std::iterator_traits< Itor >::value_type, A > toeplitz( Itor i_, Itor _i ) noexcept { return toeplitz( i_, _i, i_, _i ); } template < typename Matrix > - typename Matrix::value_type tr( const Matrix& m ) + [[nodiscard]] typename Matrix::value_type tr( const Matrix& m ) noexcept { return m.tr(); } template < typename Matrix > - Matrix const transpose( const Matrix& m ) + [[nodiscard]] Matrix transpose( const Matrix& m ) noexcept { return m.transpose(); } template < typename T, Allocator A> - matrix< T, A > const tril( const matrix< T, A >& m ) + [[nodiscard]] matrix< T, A > tril( const matrix< T, A >& m ) noexcept { matrix< T, A > ans{ m.row(), m.col() }; @@ -5347,7 +6994,7 @@ namespace feng return ans; } template < typename T, Allocator A> - matrix< T, A > const triu( const matrix< T, A >& m ) + [[nodiscard]] matrix< T, A > triu( const matrix< T, A >& m ) noexcept { matrix< T, A > ans{ m.row(), m.col() }; @@ -5356,155 +7003,79 @@ namespace feng return ans; } - template < typename T, Allocator A> - const matrix< std::complex< T >, A> - operator+( const matrix< std::complex< T >, A>& lhs, const T& rhs ) - { - matrix< std::complex< T >, A> ans( lhs ); - std::transform( ans.begin(), ans.end(), ans.begin(), [rhs]( std::complex< T > const & x ) - { - return rhs + x; - } ); - return ans; - } - template < typename T, Allocator A> - const matrix< std::complex< T >, A> - operator+( const T& lhs, const matrix< std::complex< T >, A>& rhs ) - { - return rhs + lhs; - } - template < typename T, Allocator A> - const matrix< std::complex< T >, A> - operator-( const matrix< std::complex< T >, A>& lhs, const T& rhs ) - { - matrix< std::complex< T >, A> ans( lhs ); - std::transform( ans.begin(), ans.end(), ans.begin(), [rhs]( std::complex< T > const & x ) - { - return x - rhs; - } ); - return ans; - } - template < typename T, Allocator A> - const matrix< std::complex< T >, A> - operator-( const T& lhs, const matrix< std::complex< T >, A>& rhs ) - { - matrix< std::complex< T >, A> ans( rhs ); - std::transform( ans.begin(), ans.end(), ans.begin(), [lhs]( std::complex< T > const & x ) - { - return -lhs + x; - } ); - return ans; - } - template < typename T, Allocator A> - const matrix< std::complex< T >, A> - operator*( const matrix< std::complex< T >, A>& lhs, const T& rhs ) - { - matrix< std::complex< T >, A> ans( lhs ); - std::transform( ans.begin(), ans.end(), ans.begin(), [rhs]( std::complex< T > const & x ) - { - return x * rhs; - } ); - return ans; - } - template < typename T, Allocator A> - const matrix< std::complex< T >, A> - operator*( const T& lhs, const matrix< std::complex< T >, A>& rhs ) - { - return rhs * lhs; - } - template < typename T, Allocator A> - const matrix< std::complex< T >, A> - operator/( const matrix< std::complex< T >, A>& lhs, const T& rhs ) + // S6-R5 (D-023): one template per operator and side. The result is matrix< scalar_result_t< T, S >, A rebound >: + // T (and lhs's allocator, as a copy selects it) when the scalar's kind is not higher than T's, the scalar + // converted to T, or to T's value type for complex T, first; otherwise the matrix is promoted to the result type. + // Signed overflow and integer division by zero are preconditions; unsigned arithmetic wraps. + namespace matrix_details { - matrix< std::complex< T >, A> ans( lhs ); - std::transform( ans.begin(), ans.end(), ans.begin(), [rhs]( std::complex< T > const & x ) + template< typename T, Allocator A, typename S, typename Op > + promoted_matrix_t< scalar_result_t< T, S >, T, A > scalar_apply( matrix< T, A > const& m, S const& s, Op op ) noexcept { - return x / rhs; - } ); - return ans; - } - template < typename T, Allocator A> - const matrix< std::complex< T >, A> - operator/( const T& lhs, const matrix< std::complex< T >, A>& rhs ) - { - return lhs * rhs.inverse(); + using R = scalar_result_t< T, S >; + auto ans = promote< R >( m ); + auto const v = scalar_as< R >( s ); + std::transform( ans.begin(), ans.end(), ans.begin(), [&v, &op]( R const& x ) noexcept { return static_cast< R >( op( x, v ) ); } ); + return ans; + } } - template < typename T, Allocator A> - const matrix< T, A > - operator+( const matrix< T, A >& lhs, const T& rhs ) + + template < typename T, Allocator A, typename S > requires matrix_details::scalar_operand_for< S, T > + matrix_details::promoted_matrix_t< matrix_details::scalar_result_t< T, S >, T, A > + operator+( const matrix< T, A >& lhs, const S& rhs ) noexcept { - matrix< T, A > ans( lhs ); - std::transform( ans.begin(), ans.end(), ans.begin(), [rhs]( T x ) - { - return rhs + x; - } ); - return ans; + return matrix_details::scalar_apply( lhs, rhs, []( auto const& x, auto const& y ) noexcept { return x + y; } ); } - template < typename T, Allocator A> - const matrix< T, A > - operator+( const T& lhs, const matrix< T, A >& rhs ) + template < typename T, Allocator A, typename S > requires matrix_details::scalar_operand_for< S, T > + matrix_details::promoted_matrix_t< matrix_details::scalar_result_t< T, S >, T, A > + operator+( const S& lhs, const matrix< T, A >& rhs ) noexcept { - return rhs + lhs; + return matrix_details::scalar_apply( rhs, lhs, []( auto const& x, auto const& y ) noexcept { return y + x; } ); } - template < typename T, Allocator A> - const matrix< T, A > - operator-( const matrix< T, A >& lhs, const T& rhs ) + template < typename T, Allocator A, typename S > requires matrix_details::scalar_operand_for< S, T > + matrix_details::promoted_matrix_t< matrix_details::scalar_result_t< T, S >, T, A > + operator-( const matrix< T, A >& lhs, const S& rhs ) noexcept { - matrix< T, A > ans( lhs ); - std::transform( ans.begin(), ans.end(), ans.begin(), [rhs]( T x ) - { - return x - rhs; - } ); - return ans; + return matrix_details::scalar_apply( lhs, rhs, []( auto const& x, auto const& y ) noexcept { return x - y; } ); } - template < typename T, Allocator A> - const matrix< T, A > - operator-( const T& lhs, const matrix< T, A >& rhs ) + template < typename T, Allocator A, typename S > requires matrix_details::scalar_operand_for< S, T > + matrix_details::promoted_matrix_t< matrix_details::scalar_result_t< T, S >, T, A > + operator-( const S& lhs, const matrix< T, A >& rhs ) noexcept { - matrix< T, A > ans( rhs ); - std::transform( ans.begin(), ans.end(), ans.begin(), [lhs]( T x ) - { - return -lhs + x; - } ); - return ans; + return matrix_details::scalar_apply( rhs, lhs, []( auto const& x, auto const& y ) noexcept { return y - x; } ); } - template < typename T, Allocator A> - const matrix< T, A > - operator*( const matrix< T, A >& lhs, const T& rhs ) + template < typename T, Allocator A, typename S > requires matrix_details::scalar_operand_for< S, T > + matrix_details::promoted_matrix_t< matrix_details::scalar_result_t< T, S >, T, A > + operator*( const matrix< T, A >& lhs, const S& rhs ) noexcept { - matrix< T, A > ans( lhs ); - std::transform( ans.begin(), ans.end(), ans.begin(), [rhs]( T x ) - { - return x * rhs; - } ); - return ans; + return matrix_details::scalar_apply( lhs, rhs, []( auto const& x, auto const& y ) noexcept { return x * y; } ); } - template < typename T, Allocator A> - const matrix< T, A > - operator*( const T& lhs, const matrix< T, A >& rhs ) + template < typename T, Allocator A, typename S > requires matrix_details::scalar_operand_for< S, T > + matrix_details::promoted_matrix_t< matrix_details::scalar_result_t< T, S >, T, A > + operator*( const S& lhs, const matrix< T, A >& rhs ) noexcept { - return rhs * lhs; + return matrix_details::scalar_apply( rhs, lhs, []( auto const& x, auto const& y ) noexcept { return y * x; } ); } - template < typename T, Allocator A> - const matrix< T, A > - operator/( const matrix< T, A >& lhs, const T& rhs ) + template < typename T, Allocator A, typename S > requires matrix_details::scalar_operand_for< S, T > + matrix_details::promoted_matrix_t< matrix_details::scalar_result_t< T, S >, T, A > + operator/( const matrix< T, A >& lhs, const S& rhs ) noexcept { - matrix< T, A > ans( lhs ); - std::transform( ans.begin(), ans.end(), ans.begin(), [rhs]( T x ) - { - return x / rhs; - } ); - return ans; + return matrix_details::scalar_apply( lhs, rhs, []( auto const& x, auto const& y ) noexcept { return x / y; } ); } - template < typename T, Allocator A> - const matrix< T, A > - operator/( const T& lhs, const matrix< T, A >& rhs ) + // s / m keeps its meaning, s times m.inverse(); only its result type follows D-023. + template < typename T, Allocator A, typename S > requires matrix_details::scalar_operand_for< S, T > + matrix_details::promoted_matrix_t< matrix_details::scalar_result_t< T, S >, T, A > + operator/( const S& lhs, const matrix< T, A >& rhs ) noexcept { - return lhs * rhs.inverse(); + using R = matrix_details::scalar_result_t< T, S >; + if constexpr ( std::same_as< R, T > ) + return rhs.inverse() * matrix_details::scalar_as< R >( lhs ); + else + return matrix_details::promote< R >( rhs ).inverse() * matrix_details::scalar_as< R >( lhs ); } template < typename T, Allocator A> - const matrix< T, A > - operator||( const matrix< T, A >& lhs, const T& rhs ) + matrix< T, A > + operator||( const matrix< T, A >& lhs, const T& rhs ) noexcept { matrix< T, A > ans( lhs.row(), lhs.col() + 1 ); @@ -5515,8 +7086,8 @@ namespace feng return ans; } template < typename T, Allocator A> - const matrix< T, A > - operator||( const T& lhs, const matrix< T, A >& rhs ) + matrix< T, A > + operator||( const T& lhs, const matrix< T, A >& rhs ) noexcept { matrix< T, A > ans( rhs.row(), rhs.col() + 1 ); @@ -5527,8 +7098,8 @@ namespace feng return ans; } template < typename T, Allocator A> - const matrix< T, A > - operator&&( const matrix< T, A >& lhs, const T& rhs ) + matrix< T, A > + operator&&( const matrix< T, A >& lhs, const T& rhs ) noexcept { matrix< T, A > ans( lhs.row() + 1, lhs.col() ); @@ -5539,8 +7110,8 @@ namespace feng return ans; } template < typename T, Allocator A> - const matrix< T, A > - operator&&( const T& lhs, const matrix< T, A >& rhs ) + matrix< T, A > + operator&&( const T& lhs, const matrix< T, A >& rhs ) noexcept { matrix< T, A > ans( rhs.row() + 1, rhs.col() ); @@ -5551,8 +7122,8 @@ namespace feng return ans; } template < typename T, Allocator A> - const matrix< T, A > - operator^( const matrix< T, A >& lhs, std::uint_least64_t n ) + matrix< T, A > + operator^( const matrix< T, A >& lhs, std::uint_least64_t n ) noexcept { better_assert( lhs.row() == lhs.col() ); auto const r = lhs.row(); @@ -5564,15 +7135,15 @@ namespace feng return lhs; if ( n & 1 ) - return lhs ^ ( n - 1 ) * lhs; + return ( lhs ^ ( n - 1 ) ) * lhs; auto const& lhs_2 = lhs ^ ( n >> 1 ); return lhs_2 * lhs_2; } template < typename T1, Allocator A1, typename T2, Allocator A2, typename T3, Allocator A3 > - int backward_substitution( const matrix< T1, A1 >& A, + [[nodiscard]] int backward_substitution( const matrix< T1, A1 >& A, matrix< T2, A2 >& x, - const matrix< T3, A3 >& b ) + const matrix< T3, A3 >& b ) noexcept { typedef matrix< T1, A1 > matrix_type; typedef typename matrix_type::value_type value_type; @@ -5596,177 +7167,316 @@ namespace feng return 0; } + namespace iterative_private + { + // Shared set-up of cgs and bicgstab (S7-R5, D-029): the starting iterate is x when x is n×1 and nonzero, + // else b (the legacy rule); the vectors are plain arrays of A's element type. + template < typename T > + struct krylov + { + std::size_t n; + std::vector< T > a, b, x; + + template < typename M1, typename M2, typename M3 > + krylov( M1 const& A, M2 const& x0, M3 const& b0 ) : n( A.row() ), a( n * n ), b( n ), x( n ) + { + for ( std::size_t i = 0; i != n; ++i ) + { + for ( std::size_t j = 0; j != n; ++j ) a[i * n + j] = static_cast< T >( A[i][j] ); + b[i] = static_cast< T >( b0[i][0] ); + } + bool const use_x = x0.row() == n && x0.col() == 1 && std::any_of( x0.begin(), x0.end(), []( auto v ) { return v != decltype( v )( 0 ); } ); + for ( std::size_t i = 0; i != n; ++i ) x[i] = use_x ? static_cast< T >( x0[i][0] ) : b[i]; + } + bool finite_input() const noexcept + { + return matrix_details::all_finite( a.data(), a.size() ) && matrix_details::all_finite( b.data(), b.size() ); + } + void mul( std::vector< T > const& v, std::vector< T >& out ) const noexcept // out = A·v + { + for ( std::size_t i = 0; i != n; ++i ) + { + T s{ 0 }; + for ( std::size_t j = 0; j != n; ++j ) s += a[i * n + j] * v[j]; + out[i] = s; + } + } + static T dot( std::vector< T > const& u, std::vector< T > const& v ) noexcept + { + T s{ 0 }; + for ( std::size_t i = 0; i != u.size(); ++i ) s += u[i] * v[i]; + return s; + } + static T norm( std::vector< T > const& v ) noexcept + { + T s{ 0 }; + for ( auto const e : v ) s += e * e; + return std::sqrt( s ); + } + T residual( std::vector< T > const& xv, std::vector< T >& tmp ) const noexcept // ‖b − A·xv‖₂ + { + mul( xv, tmp ); + for ( std::size_t i = 0; i != n; ++i ) tmp[i] = b[i] - tmp[i]; + return norm( tmp ); + } + static bool usable( T v ) noexcept { return v != T( 0 ) && std::isfinite( v ); } + template < typename M > + void store( M& out, std::vector< T > const& v ) const noexcept + { + if ( out.row() != n || out.col() != 1 ) out.resize( n, 1 ); + for ( std::size_t i = 0; i != n; ++i ) out[i][0] = static_cast< typename M::value_type >( v[i] ); + } + }; + }//namespace iterative_private + + // S7-R5 (D-029): BiCGSTAB for A·x = b. Returns 0 when ‖b − A·x‖₂ ≤ eps·‖b‖₂ (a zero b gives x = 0), and 1 for a + // nonfinite A or b (x unchanged), a breakdown (a zero or nonfinite denominator) or max_loops iterations without + // convergence; x then holds the last finite iterate. template < typename T1, Allocator A1, typename T2, Allocator A2, typename T3, Allocator A3 > - int biconjugate_gradient_stabilized_method( const matrix< T1, A1 >& A, + [[nodiscard]] int biconjugate_gradient_stabilized_method( const matrix< T1, A1 >& A, matrix< T2, A2 >& x, const matrix< T3, A3 >& b, const std::uint_least64_t max_loops = 100, - const T1 eps = 1.0e-10 ) + const T1 eps = 1.0e-10 ) noexcept { typedef T1 value_type; - better_assert( A.row() == A.col() ); - better_assert( A.row() == b.row() ); - better_assert( b.col() == 1 ); - auto const n = A.row(); - - if ( ( n != x.row() ) || ( 1 != x.col() ) || ( 0 == std::count_if( x.begin(), x.end(), []( T2 const v ) - { - return v != T2( 0 ); - } ) ) ) - x = b; - auto r = b - A * x; - auto const r_ = r; - auto p = r; - auto s = p; - auto ap = p; - auto as = p; - auto new_r = r; - auto rem = r; - auto const EPS = eps * n; - value_type const zero = value_type( 0 ); - - if ( dot( r, r ) < EPS ) + better_assert( A.row() == A.col(), "bicgstab: expecting a square A, but got ", A.row(), "x", A.col() ); + better_assert( A.row() == b.row() && b.col() == 1, "bicgstab: expecting a column b with ", A.row(), " rows, but got ", b.row(), "x", b.col() ); + iterative_private::krylov< value_type > k( A, x, b ); + if ( !k.finite_input() ) return 1; + std::size_t const n = k.n; + value_type const bnorm = k.norm( k.b ); + if ( bnorm == value_type( 0 ) ) + { + std::fill( k.x.begin(), k.x.end(), value_type( 0 ) ); + k.store( x, k.x ); return 0; - + } + value_type const tol = eps * bnorm; + std::vector< value_type > r( n ), tmp( n ), p( n, 0 ), v( n, 0 ), s( n ), t( n ), xn( n ); + if ( !std::isfinite( k.residual( k.x, r ) ) ) return 1; + if ( k.norm( r ) <= tol ) { k.store( x, k.x ); return 0; } + std::vector< value_type > const r_hat = r; + value_type rho_old( 1 ), alpha( 1 ), omega( 1 ); + int result = 1; for ( std::uint_least64_t loops = 0; loops != max_loops; ++loops ) { - ap = A * p; - auto const alpha = dot( r, r_ ) / dot( ap, r_ ); - - if ( zero == alpha ) - return 1; - - if ( std::isinf( alpha ) || std::isnan( alpha ) ) - return 1; - - s = r - alpha * ap; - as = A * s; - auto const omega = dot( as, s ) / dot( as, as ); - - if ( std::isinf( omega ) || std::isnan( omega ) ) - return 1; - - x += alpha * p + omega * s; - new_r = s - omega * as; - auto const beta = dot( new_r, r_ ) * alpha / dot( r, r_ ) / omega; - - if ( std::isinf( beta ) || std::isnan( beta ) ) - return 1; - - r = new_r; - p = r + beta * ( p - omega * ap ); - rem = A * x - b; - - if ( dot( rem, rem ) <= EPS ) - return 0; - } - - return 0; + value_type const rho = k.dot( r_hat, r ); + if ( !k.usable( rho ) ) break; + if ( loops == 0 ) + p = r; + else + { + value_type const beta = ( rho / rho_old ) * ( alpha / omega ); + if ( !std::isfinite( beta ) ) break; + for ( std::size_t i = 0; i != n; ++i ) p[i] = r[i] + beta * ( p[i] - omega * v[i] ); + } + k.mul( p, v ); + value_type const den = k.dot( r_hat, v ); + if ( !k.usable( den ) ) break; + alpha = rho / den; + if ( !std::isfinite( alpha ) ) break; + for ( std::size_t i = 0; i != n; ++i ) { s[i] = r[i] - alpha * v[i]; xn[i] = k.x[i] + alpha * p[i]; } + if ( !matrix_details::all_finite( xn.data(), n ) ) break; + if ( k.residual( xn, tmp ) <= tol ) { k.x = xn; result = 0; break; } + k.mul( s, t ); + value_type const tt = k.dot( t, t ); + if ( !k.usable( tt ) ) { k.x = xn; break; } + omega = k.dot( t, s ) / tt; + if ( !k.usable( omega ) ) { k.x = xn; break; } + for ( std::size_t i = 0; i != n; ++i ) { xn[i] += omega * s[i]; r[i] = s[i] - omega * t[i]; } + if ( !matrix_details::all_finite( xn.data(), n ) ) break; + k.x = xn; + if ( k.residual( k.x, tmp ) <= tol ) { result = 0; break; } + rho_old = rho; + } + k.store( x, k.x ); + return result; } template < typename T1, Allocator A1, typename T2, Allocator A2, typename T3, Allocator A3 > - int bicgstab( const matrix< T1, A1 >& A, + [[nodiscard]] int bicgstab( const matrix< T1, A1 >& A, matrix< T2, A2 >& x, const matrix< T3, A3 >& b, const std::uint_least64_t max_loops = 100, - const T1 eps = 1.0e-10 ) + const T1 eps = 1.0e-10 ) noexcept { - return biconjugate_gradient_stablized_method( A, x, b, max_loops, eps ); + return biconjugate_gradient_stabilized_method( A, x, b, max_loops, eps ); } - template < typename Matrix1, typename Matrix2 > - void cholesky_decomposition( const Matrix1& m, Matrix2& a ) + // S7-R4 (F13, D-026, D-027): Cholesky factorization A = L·Lᴴ of a real symmetric or complex Hermitian + // positive definite A. status() is ok or not_positive_definite; not_positive_definite also covers a + // non-symmetric (non-Hermitian) A, |a_ij − conj(a_ji)| > n·ε·‖A‖∞, a nonfinite input and a singular or + // indefinite A: a pivot d_k = a_kk − Σ|l_kj|² that is not > n·ε·max_i |a_ii| (the D-027 scale). The factor is + // computed from the lower triangle; l() is lower triangular with a real positive diagonal when ok and an + // empty 0×0 matrix otherwise (never NaN). + template < typename T, Allocator A_ = std::allocator< T > > requires linalg_element< T > + class cholesky_factorization { - typedef typename Matrix1::value_type value_type; - better_assert( m.row() == m.col() ); - a = m; - const std::uint_least64_t n = m.row(); + public: + using matrix_type = matrix< T, A_ >; + using real_type = matrix_details::linalg_real_t< T >; - for ( std::uint_least64_t i = 0; i < n; ++i ) - for ( std::uint_least64_t j = i; j < n; ++j ) + cholesky_factorization() noexcept = default; + explicit cholesky_factorization( matrix_type const& a ) noexcept + { + better_assert( a.row() == a.col(), "feng::cholesky_factor: expecting a square matrix, but got ", a.row(), "x", a.col() ); + factor( a ); + } + + [[nodiscard]] linalg_status status() const noexcept { return status_; } + [[nodiscard]] bool ok() const noexcept { return status_ == linalg_status::ok; } + [[nodiscard]] matrix_type const& l() const noexcept { return l_; } + + private: + static T conj_( T const& x ) noexcept + { + if constexpr ( matrix_private::is_std_complex_v< T > ) return std::conj( x ); + else return x; + } + + void factor( matrix_type const& a ) noexcept + { + using R = real_type; + std::size_t const n = a.row(); + T const* const ad = a.data(); + status_ = linalg_status::not_positive_definite; + if ( !matrix_details::all_finite( ad, n * n ) ) return; + R norm_inf{ 0 }, max_diag{ 0 }; + for ( std::size_t i = 0; i != n; ++i ) { - const value_type sum = a[i][j] - std::inner_product( a.row_begin( i ), a.row_begin( i ) + i, a.row_begin( j ), value_type( 0 ) ); - a[j][i] = ( i == j ) ? std::sqrt( sum ) : ( sum / a[i][i] ); + R s{ 0 }; + for ( std::size_t j = 0; j != n; ++j ) s += std::abs( ad[i * n + j] ); + norm_inf = std::max( norm_inf, s ); + max_diag = std::max( max_diag, R( std::abs( ad[i * n + i] ) ) ); + } + R const eps = std::numeric_limits< R >::epsilon(); + R const sym_tol = static_cast< R >( n ) * eps * norm_inf; + for ( std::size_t i = 0; i != n; ++i ) + for ( std::size_t j = i; j != n; ++j ) + if ( std::abs( ad[i * n + j] - conj_( ad[j * n + i] ) ) > sym_tol ) return; + R const pivot_tol = static_cast< R >( n ) * eps * max_diag; + matrix_type l( a ); + T* const ld = l.data(); + std::fill( ld, ld + n * n, T{ 0 } ); + for ( std::size_t j = 0; j != n; ++j ) + { + R d = std::real( ad[j * n + j] ); + for ( std::size_t k = 0; k != j; ++k ) d -= std::norm( ld[j * n + k] ); + if ( !( d > pivot_tol ) || !std::isfinite( d ) ) return; + R const ljj = std::sqrt( d ); + ld[j * n + j] = T( ljj ); + for ( std::size_t i = j + 1; i != n; ++i ) + { + T s = ad[i * n + j]; + for ( std::size_t k = 0; k != j; ++k ) s -= ld[i * n + k] * conj_( ld[j * n + k] ); + ld[i * n + j] = s / ljj; + } } + if ( !matrix_details::all_finite( ld, n * n ) ) return; + l_ = std::move( l ); + status_ = linalg_status::ok; + } + + matrix_type l_; + linalg_status status_ = linalg_status::ok; + }; + + template < typename T, Allocator A > requires linalg_element< T > + [[nodiscard]] cholesky_factorization< T, A > cholesky_factor( matrix< T, A > const& a ) noexcept + { + return cholesky_factorization< T, A >( a ); + } - for ( std::uint_least64_t i = 1; i < n; ++i ) - std::fill( a.upper_diag_begin( i ), a.upper_diag_end( i ), value_type() ); + // Legacy Cholesky: 0 with a = L (A = L·Lᴴ), or 1 with a unchanged when cholesky_factor is not ok (S7-R4). + template < typename Matrix1, typename Matrix2 > requires linalg_element< typename Matrix1::value_type > + [[nodiscard]] int cholesky_decomposition( const Matrix1& m, Matrix2& a ) noexcept + { + better_assert( m.row() == m.col(), "cholesky_decomposition: expecting a square matrix, but got ", m.row(), "x", m.col() ); + auto const f = cholesky_factor( m ); + if ( !f.ok() ) + return 1; + a = f.l(); + return 0; } + // S7-R5 (D-029): conjugate gradient squared for A·x = b; the same return and x rules as bicgstab. template < typename T1, Allocator A1, typename T2, Allocator A2, typename T3, Allocator A3 > - int conjugate_gradient_squared( const matrix< T1, A1 >& A, + [[nodiscard]] int conjugate_gradient_squared( const matrix< T1, A1 >& A, matrix< T2, A2 >& x, const matrix< T3, A3 >& b, const std::uint_least64_t max_loops = 100, - const T1 eps = 1.0e-10 ) + const T1 eps = 1.0e-10 ) noexcept { - typedef matrix< T1, A1 > matrix_type; - typedef typename matrix_type::value_type value_type; - typedef typename matrix_type::size_type size_type; - better_assert( A.row() == A.col() ); - better_assert( A.row() == b.row() ); - better_assert( b.col() == 1 ); - size_type const n = A.row(); - x.resize( n, 1 ); - - if ( dot( x, x ) == value_type() ) - x = b; - - matrix_type const r_ = b - A * x; - matrix_type r = r_; - matrix_type p = r_; - matrix_type u = r_; - matrix_type ap = r_; - matrix_type q = r_; - matrix_type new_r = r_; - matrix_type uq = r_; - matrix_type rem = r_; - value_type const EPS = n * eps * eps; - - if ( dot( r_, r_ ) < EPS ) + typedef T1 value_type; + better_assert( A.row() == A.col(), "cgs: expecting a square A, but got ", A.row(), "x", A.col() ); + better_assert( A.row() == b.row() && b.col() == 1, "cgs: expecting a column b with ", A.row(), " rows, but got ", b.row(), "x", b.col() ); + iterative_private::krylov< value_type > k( A, x, b ); + if ( !k.finite_input() ) return 1; + std::size_t const n = k.n; + value_type const bnorm = k.norm( k.b ); + if ( bnorm == value_type( 0 ) ) + { + std::fill( k.x.begin(), k.x.end(), value_type( 0 ) ); + k.store( x, k.x ); return 0; - - for ( std::uint_least64_t i = 0; i != max_loops; ++i ) + } + value_type const tol = eps * bnorm; + std::vector< value_type > r( n ), tmp( n ), u( n ), p( n ), q( n ), uq( n ), v( n ), xn( n ); + if ( !std::isfinite( k.residual( k.x, r ) ) ) return 1; + if ( k.norm( r ) <= tol ) { k.store( x, k.x ); return 0; } + std::vector< value_type > const r_hat = r; + value_type rho_old( 1 ); + int result = 1; + for ( std::uint_least64_t loops = 0; loops != max_loops; ++loops ) { - ap = A * p; - value_type const alpha = dot( r, r_ ) / dot( ap, r_ ); - - if ( std::isinf( alpha ) || std::isnan( alpha ) ) + value_type const rho = k.dot( r_hat, r ); + if ( !k.usable( rho ) ) break; + if ( loops == 0 ) { - return 1; + u = r; + p = u; } - - q = u - alpha * ap; - uq = u + q; - x += alpha * uq; - rem = A * x - b; - - if ( dot( rem, rem ) < EPS ) - return 0; - - new_r = r - alpha * A * uq; - value_type const beta = dot( new_r, r_ ) / dot( r, r_ ); - - if ( std::isinf( beta ) || std::isnan( beta ) ) + else { - return 1; + value_type const beta = rho / rho_old; + if ( !std::isfinite( beta ) ) break; + for ( std::size_t i = 0; i != n; ++i ) + { + u[i] = r[i] + beta * q[i]; + p[i] = u[i] + beta * ( q[i] + beta * p[i] ); + } } - - r = new_r; - u = r + beta * q; - p = u + beta * ( q + beta * p ); + k.mul( p, v ); + value_type const den = k.dot( r_hat, v ); + if ( !k.usable( den ) ) break; + value_type const alpha = rho / den; + if ( !std::isfinite( alpha ) ) break; + for ( std::size_t i = 0; i != n; ++i ) + { + q[i] = u[i] - alpha * v[i]; + uq[i] = u[i] + q[i]; + xn[i] = k.x[i] + alpha * uq[i]; + } + if ( !matrix_details::all_finite( xn.data(), n ) ) break; + k.x = xn; + k.mul( uq, tmp ); + for ( std::size_t i = 0; i != n; ++i ) r[i] -= alpha * tmp[i]; + if ( k.residual( k.x, tmp ) <= tol ) { result = 0; break; } + rho_old = rho; } - - return 0; + k.store( x, k.x ); + return result; } template < typename T1, Allocator A1, typename T2, Allocator A2, typename T3, Allocator A3 > - int cgs( const matrix< T1, A1 >& A, + [[nodiscard]] int cgs( const matrix< T1, A1 >& A, matrix< T2, A2 >& x, const matrix< T3, A3 >& b, const std::uint_least64_t max_loops = 100, - const T1 eps = 1.0e-10 ) + const T1 eps = 1.0e-10 ) noexcept { return conjugate_gradient_squared( A, x, b, max_loops, eps ); } + // Experimental (S7, D-029): not qualified; householder has no status and is not covered by the S7 tests. template < typename Matrix1, typename Matrix2, typename Matrix3 > - void householder( const Matrix1& A, Matrix2& Q, Matrix3& D ) + void householder( const Matrix1& A, Matrix2& Q, Matrix3& D ) noexcept { typedef Matrix1 matrix_type; typedef typename matrix_type::value_type value_type; @@ -5774,9 +7484,8 @@ namespace feng better_assert( A.row() == A.col() ); size_type const n = A.row(); value_type const zero = value_type( 0 ); - value_type const one = value_type( 1 ); value_type const two = value_type( 2 ); - Matrix1 const I = eye( n, A ); + Matrix1 const I = eye< value_type >( n ); Q = eye< value_type >( n ); D = A; Matrix1 x( n, 1 ); @@ -5817,7 +7526,7 @@ namespace feng namespace eigen_jacobi_private { template < typename Matrix > - typename Matrix::value_type norm( const Matrix& A ) + typename Matrix::value_type norm( const Matrix& A ) noexcept { typedef typename Matrix::value_type value_type; auto A_ = abs( A ); @@ -5832,7 +7541,7 @@ namespace feng return std::sqrt( sum ) * max_elem; } template < typename Matrix1, typename Matrix2 > - void rotate( Matrix1& A, Matrix2& V, const std::uint_least64_t p, const std::uint_least64_t q ) + void rotate( Matrix1& A, Matrix2& V, const std::uint_least64_t p, const std::uint_least64_t q ) noexcept { typedef typename Matrix1::value_type value_type; auto const one = value_type( 1 ); @@ -5863,27 +7572,28 @@ namespace feng } } } + // Experimental (S7, D-029): not qualified; eigen_jacobi has an unbounded rotation loop and no status. template < typename Matrix1, typename Matrix2, typename T = double > - std::uint_least64_t eigen_jacobi( const Matrix1& A, Matrix2& V, std::vector< T >& Lambda, const T eps = T( 1.0e-10 ) ) + std::uint_least64_t eigen_jacobi( const Matrix1& A, Matrix2& V, std::vector< T >& Lambda, const T eps = T( 1.0e-10 ) ) noexcept { Lambda.resize( A.row() ); return eigen_jacobi( A, V, Lambda.begin(), eps ); } template < typename Matrix1, typename Matrix2, typename T = double > - std::uint_least64_t eigen_jacobi( const Matrix1& A, Matrix2& V, std::valarray< T >& Lambda, const T eps = T( 1.0e-10 ) ) + std::uint_least64_t eigen_jacobi( const Matrix1& A, Matrix2& V, std::valarray< T >& Lambda, const T eps = T( 1.0e-10 ) ) noexcept { Lambda.resize( A.row() ); - return eigen_jacobi( A, V, Lambda.begin(), eps ); + return eigen_jacobi( A, V, std::begin( Lambda ), eps ); } template < typename Matrix1, typename Matrix2, typename T, typename A_, typename T_ = double > - std::uint_least64_t eigen_jacobi( const Matrix1& A, Matrix2& V, matrix< T, A_ >& Lambda, const T_ eps = T_( 1.0e-10 ) ) + std::uint_least64_t eigen_jacobi( const Matrix1& A, Matrix2& V, matrix< T, A_ >& Lambda, const T_ eps = T_( 1.0e-10 ) ) noexcept { Lambda.resize( A.row(), A.col() ); Lambda = T( 0 ); return eigen_jacobi( A, V, Lambda.diag_begin(), eps ); } template < typename Matrix1, typename Matrix2, typename Otor, typename T = double > - std::uint_least64_t eigen_jacobi( const Matrix1& A, Matrix2& V, Otor o, const T eps = T( 1.0e-10 ) ) + std::uint_least64_t eigen_jacobi( const Matrix1& A, Matrix2& V, Otor o, const T eps = T( 1.0e-10 ) ) noexcept { typedef typename Matrix1::value_type value_type; typedef typename Matrix1::size_type size_type; @@ -5926,15 +7636,12 @@ namespace feng return size_type( -1 ); } + // Experimental (S7, D-029): not qualified; cyclic_eigen_jacobi ignores eps and reports no status. template < typename Matrix1, typename Matrix2, typename Otor, typename T = double > - std::uint_least64_t cyclic_eigen_jacobi( const Matrix1& A, Matrix2& V, Otor o, std::uint_least64_t max_rot = 80, const T eps = T( 1.0e-10 ) ) + std::uint_least64_t cyclic_eigen_jacobi( const Matrix1& A, Matrix2& V, Otor o, std::uint_least64_t max_rot = 80, [[maybe_unused]] const T eps = T( 1.0e-10 ) ) noexcept { typedef typename Matrix1::value_type value_type; typedef typename Matrix1::size_type size_type; - auto const compare_func = [eps]( const value_type lhs, const value_type rhs ) - { - return std::abs( lhs - rhs ) < eps; - }; better_assert( A.row() == A.col() ); size_type i = 0; auto a = A; @@ -5961,45 +7668,46 @@ namespace feng return i * n * n; } template < typename Matrix1, typename Matrix2, typename T = double > - std::uint_least64_t cyclic_eigen_jacobi( const Matrix1& A, Matrix2& V, std::vector< T >& Lambda, std::uint_least64_t const max_rot = 80, const T eps = T( 1.0e-10 ) ) + std::uint_least64_t cyclic_eigen_jacobi( const Matrix1& A, Matrix2& V, std::vector< T >& Lambda, std::uint_least64_t const max_rot = 80, const T eps = T( 1.0e-10 ) ) noexcept { Lambda.resize( A.row() ); return cyclic_eigen_jacobi( A, V, Lambda.begin(), max_rot, eps ); } template < typename Matrix1, typename Matrix2, typename T = double > - std::uint_least64_t cyclic_eigen_jacobi( const Matrix1& A, Matrix2& V, std::valarray< T >& Lambda, std::uint_least64_t const max_rot = 80, const T eps = T( 1.0e-10 ) ) + std::uint_least64_t cyclic_eigen_jacobi( const Matrix1& A, Matrix2& V, std::valarray< T >& Lambda, std::uint_least64_t const max_rot = 80, const T eps = T( 1.0e-10 ) ) noexcept { Lambda.resize( A.row() ); - return cyclic_eigen_jacobi( A, V, Lambda.begin(), max_rot, eps ); + return cyclic_eigen_jacobi( A, V, std::begin( Lambda ), max_rot, eps ); } template < typename Matrix1, typename Matrix2, typename T, typename A_, typename T_ = double > - std::uint_least64_t cyclic_eigen_jacobi( const Matrix1& A, Matrix2& V, matrix< T, A_ >& Lambda, std::uint_least64_t const max_rot = 80, const T_ eps = T_( 1.0e-10 ) ) + std::uint_least64_t cyclic_eigen_jacobi( const Matrix1& A, Matrix2& V, matrix< T, A_ >& Lambda, std::uint_least64_t const max_rot = 80, const T_ eps = T_( 1.0e-10 ) ) noexcept { Lambda.resize( A.row(), A.col() ); Lambda = T( 0 ); return cyclic_eigen_jacobi( A, V, Lambda.diag_begin(), max_rot, eps ); } + // Experimental (S7, D-029): not qualified; eigen_real_symmetric rests on householder and eigen_jacobi. template < typename Matrix1, typename Matrix2, typename T = double > - void eigen_real_symmetric( const Matrix1& A, Matrix2& V, std::vector< T >& Lambda, const T eps = T( 1.0e-10 ) ) + void eigen_real_symmetric( const Matrix1& A, Matrix2& V, std::vector< T >& Lambda, const T eps = T( 1.0e-10 ) ) noexcept { Lambda.resize( A.row() ); return eigen_real_symmetric( A, V, Lambda.begin(), eps ); } template < typename Matrix1, typename Matrix2, typename T = double > - void eigen_real_symmetric( const Matrix1& A, Matrix2& V, std::valarray< T >& Lambda, const T eps = T( 1.0e-10 ) ) + void eigen_real_symmetric( const Matrix1& A, Matrix2& V, std::valarray< T >& Lambda, const T eps = T( 1.0e-10 ) ) noexcept { Lambda.resize( A.row() ); - return eigen_real_symmetric( A, V, Lambda.begin(), eps ); + return eigen_real_symmetric( A, V, std::begin( Lambda ), eps ); } template < typename Matrix1, typename Matrix2, typename T, typename A_, typename T_ = double > - void eigen_real_symmetric( const Matrix1& A, Matrix2& V, matrix< T, A_ >& Lambda, const T_ eps = T_( 1.0e-10 ) ) + void eigen_real_symmetric( const Matrix1& A, Matrix2& V, matrix< T, A_ >& Lambda, const T_ eps = T_( 1.0e-10 ) ) noexcept { Lambda.resize( A.row(), A.col() ); Lambda = T( 0 ); return eigen_real_symmetric( A, V, Lambda.diag_begin(), eps ); } template < typename Matrix1, typename Matrix2, typename Otor, typename T = double > - void eigen_real_symmetric( const Matrix1& A, Matrix2& V, Otor o, const T eps = T( 1.0e-10 ) ) + void eigen_real_symmetric( const Matrix1& A, Matrix2& V, Otor o, const T eps = T( 1.0e-10 ) ) noexcept { better_assert( A.row() == A.col() ); Matrix1 D( A ); @@ -6008,35 +7716,36 @@ namespace feng eigen_jacobi( D, V, o, eps ); V = Q * V; } + // Experimental (S7, D-029): not qualified; eigen_hermitian rests on eigen_real_symmetric. template < typename Complex_Matrix1, typename Complex_Matrix2, typename T = double > - void eigen_hermitian( const Complex_Matrix1& A, Complex_Matrix2& V, std::vector< T >& Lambda, const T eps = T( 1.0e-20 ) ) + void eigen_hermitian( const Complex_Matrix1& A, Complex_Matrix2& V, std::vector< T >& Lambda, const T eps = T( 1.0e-20 ) ) noexcept { Lambda.resize( A.row() ); return eigen_hermitian_impl( A, V, Lambda.begin(), eps ); } template < typename Complex_Matrix1, typename Complex_Matrix2, typename T = double > - void eigen_hermitian( const Complex_Matrix1& A, Complex_Matrix2& V, std::valarray< T >& Lambda, const T eps = T( 1.0e-20 ) ) + void eigen_hermitian( const Complex_Matrix1& A, Complex_Matrix2& V, std::valarray< T >& Lambda, const T eps = T( 1.0e-20 ) ) noexcept { Lambda.resize( A.row() ); - return eigen_hermitian_impl( A, V, Lambda.begin(), eps ); + return eigen_hermitian_impl( A, V, std::begin( Lambda ), eps ); } template < typename Complex_Matrix1, typename Complex_Matrix2, typename T, typename A_, typename T_ = double > - void eigen_hermitian( const Complex_Matrix1& A, Complex_Matrix2& V, matrix< T, A_ >& Lambda, const T_ eps = T_( 1.0e-20 ) ) + void eigen_hermitian( const Complex_Matrix1& A, Complex_Matrix2& V, matrix< T, A_ >& Lambda, const T_ eps = T_( 1.0e-20 ) ) noexcept { Lambda.resize( A.row(), A.col() ); Lambda = T( 0 ); return eigen_hermitian_impl( A, V, Lambda.diag_begin(), eps ); } template < typename T1, Allocator A1, typename T2, Allocator A2, typename Otor, typename T = double > - void eigen_hermitian_impl( const matrix< std::complex< T1 >, A1 >& A, matrix< std::complex< T2 >, A2 >& V, Otor o, const T eps = T( 1.0e-20 ) ) + void eigen_hermitian_impl( const matrix< std::complex< T1 >, A1 >& A, matrix< std::complex< T2 >, A2 >& V, Otor o, const T eps = T( 1.0e-20 ) ) noexcept { better_assert( A.row() == A.col() ); std::uint_least64_t const n = A.row(); auto const A_ = real( A ); auto const B_ = imag( A ); auto const AA = ( A_ || ( -B_ ) ) && ( B_ || A_ ); - matrix< T1, A1 > VV( n + n, n + n ); - matrix< T1, A1 > LL( n + n, n + n ); + matrix< T1, typename std::allocator_traits< A1 >::template rebind_alloc< T1 > > VV( n + n, n + n ); + matrix< T1, typename std::allocator_traits< A1 >::template rebind_alloc< T1 > > LL( n + n, n + n ); eigen_real_symmetric( AA, VV, LL, eps ); std::vector< T1 > vec( n + n ); std::copy( LL.diag_begin(), LL.diag_end(), vec.begin() ); @@ -6058,8 +7767,9 @@ namespace feng *o++ = vec[i + i]; } } + // Experimental (S7, D-029): not qualified; eigen_power_iteration has an unbounded loop and no status. template < typename T, typename A_, typename O > - T eigen_power_iteration( const matrix< T, A_ >& A, O output, const T eps = T( 1.0e-5 ) ) + T eigen_power_iteration( const matrix< T, A_ >& A, O output, const T eps = T( 1.0e-5 ) ) noexcept { better_assert( A.row() == A.col() ); matrix< T, A_ > b( A.col(), 1 ); @@ -6087,13 +7797,13 @@ namespace feng return T( 0 ); } template < typename T, typename A_ > - T eigen_power_iteration( const matrix< T, A_ >& A, const T eps = T( 1.0e-5 ) ) + T eigen_power_iteration( const matrix< T, A_ >& A, const T eps = T( 1.0e-5 ) ) noexcept { matrix< T, A_ > b( A.col(), 1 ); return eigen_power_iteration( A, b.begin(), eps ); } template < typename T, typename A_, typename O > - T eigen_power_iteration( const matrix< std::complex< T >, A_ >& A, O output, const T eps = T( 1.0e-5 ) ) + T eigen_power_iteration( const matrix< std::complex< T >, A_ >& A, O output, const T eps = T( 1.0e-5 ) ) noexcept { better_assert( A.row() == A.col() ); matrix< std::complex< T >, A_ > b( A.col(), 1 ); @@ -6143,14 +7853,14 @@ namespace feng return T( 0 ); } template < typename T, typename A_ > - T eigen_power_iteration( const matrix< std::complex< T >, A_ >& A, const T eps = T( 1.0e-5 ) ) + T eigen_power_iteration( const matrix< std::complex< T >, A_ >& A, const T eps = T( 1.0e-5 ) ) noexcept { matrix< std::complex< T >, A_ > b( A.col(), 1 ); return eigen_power_iteration( A, b.begin(), eps ); } /* template < typename Matrix > - typename Matrix::value_type + [[nodiscard]] typename Matrix::value_type norm( const Matrix& A ) { typedef typename Matrix::value_type value_type; @@ -6165,7 +7875,7 @@ namespace feng } template < typename Matrix > - typename Matrix::value_type + [[nodiscard]] typename Matrix::value_type norm( const Matrix& A, const std::uint_least64_t n ) { typedef typename Matrix::value_type value_type; @@ -6192,7 +7902,7 @@ namespace feng } */ template < Matrix Mat > - auto norm_1( Mat const& A ) + [[nodiscard]] auto norm_1( Mat const& A ) noexcept { typedef typename Mat::value_type value_type; std::vector< value_type > m( A.col() ); @@ -6206,7 +7916,7 @@ namespace feng } template < typename T, typename A_ > - T norm_1( const matrix< std::complex< T >, A_ >& A ) + [[nodiscard]] T norm_1( const matrix< std::complex< T >, A_ >& A ) noexcept { std::vector< T > m( A.col() ); @@ -6218,7 +7928,7 @@ namespace feng return *( std::max_element( m.begin(), m.end() ) ); } template < Matrix Mat > - auto norm_2( Mat const& A ) + [[nodiscard]] auto norm_2( Mat const& A ) noexcept { return std::sqrt( eigen_power_iteration( A ) ); } @@ -6236,35 +7946,41 @@ namespace feng typedef typename fix_complex_value_type< T >::value_type value_type; }; } - template < Matrix Mat > - Mat expm( Mat const& A ) + // S7-R5 (D-029): matrix exponential by scaling and squaring with the degree-13 Padé approximant (Higham 2005). + // expm(0) = I and a diagonal A gives diag(exp(a_ii)) exactly (shortcut); the scaling 2^-s is applied with + // std::ldexp; the Padé quotient F = (V − U)⁻¹(V + U) is solved with lu_factor; a nonfinite A (or a failed + // Padé solve) gives an all-NaN matrix of A's shape. Real and complex elements. + template < Matrix Mat > requires linalg_element< typename Mat::value_type > + [[nodiscard]] Mat expm( Mat const& A ) noexcept { typedef Mat matrix_type; - typedef typename matrix_type::value_type value_type_; - typedef typename expm_private::fix_complex_value_type< value_type_ >::value_type value_type; - typedef typename matrix_type::size_type size_type; - better_assert( A.row() == A.col() ); - static const value_type theta[] = { 0.000000000000000e+000, - 3.650024139523051e-008, - 5.317232856892575e-004, - 1.495585217958292e-002, - 8.536352760102745e-002, - 2.539398330063230e-001, - 5.414660951208968e-001, - 9.504178996162932e-001, - 1.473163964234804e+000, - 2.097847961257068e+000, - 2.811644121620263e+000, - 3.602330066265032e+000, - 4.458935413036850e+000, - 5.371920351148152e+000 - }; - value_type const norm_A = norm_1( A ); - value_type const ratio = norm_A / theta[13]; - size_type const s = ratio < value_type( 1 ) ? 0 : static_cast< size_type >( std::ceil( std::log2( ratio ) ) ); - value_type const s__2 = s ? value_type( 1 << s ) : value_type( 1 ); - matrix_type const& _A = A / s__2; - size_type const n = _A.row(); + typedef typename matrix_type::value_type T; + typedef typename expm_private::fix_complex_value_type< T >::value_type value_type; + better_assert( A.row() == A.col(), "feng::expm: expecting a square matrix, but got ", A.row(), "x", A.col() ); + std::size_t const n = A.row(); + auto nan_result = [&A]() + { + matrix_type ans( A ); + value_type const q = std::numeric_limits< value_type >::quiet_NaN(); + if constexpr ( matrix_private::is_std_complex_v< T > ) std::fill( ans.begin(), ans.end(), T( q, q ) ); + else std::fill( ans.begin(), ans.end(), T( q ) ); + return ans; + }; + if ( !matrix_details::all_finite( A.data(), n * n ) ) return nan_result(); + + bool diagonal = true; + for ( std::size_t i = 0; i != n && diagonal; ++i ) + for ( std::size_t j = 0; j != n; ++j ) + if ( i != j && A[i][j] != T( 0 ) ) { diagonal = false; break; } + if ( diagonal ) + { + matrix_type ans( A ); + std::fill( ans.begin(), ans.end(), T( 0 ) ); + for ( std::size_t i = 0; i != n; ++i ) ans[i][i] = std::exp( A[i][i] ); + return ans; + } + + static const value_type theta13 = 5.371920351148152e+000; static value_type const c[] = { 0.000000000000000, 6.4764752532480000e+16, 3.2382376266240000e+16, @@ -6281,91 +7997,358 @@ namespace feng 1.82e+2, 1 }; - matrix_type const& _A2 = _A * _A; - matrix_type const& _A4 = _A2 * _A2; - matrix_type const& _A6 = _A2 * _A4; - matrix_type const& U = _A * ( _A6 * ( c[14] * _A6 + c[12] * _A4 + c[10] * _A2 ) + c[8] * _A6 + c[6] * _A4 + c[4] * _A2 + c[2] * eye< value_type >( n, n ) ); - matrix_type const& V = _A6 * ( c[13] * _A6 + c[11] * _A4 + c[9] * _A2 ) + c[7] * _A6 + c[5] * _A4 + c[3] * _A2 + c[1] * eye< value_type >( n, n ); - matrix_type const& VU = V + U; - matrix_type const& UV = V - U; - matrix_type F = VU / UV; - - for ( size_type i = 0; i != s; ++i ) - F *= F; - + value_type norm_A( 0 ); // ‖A‖₁ + for ( std::size_t j = 0; j != n; ++j ) + { + value_type sum( 0 ); + for ( std::size_t i = 0; i != n; ++i ) sum += std::abs( A[i][j] ); + norm_A = std::max( norm_A, sum ); + } + value_type const ratio = norm_A / theta13; + int const s = ratio > value_type( 1 ) ? static_cast< int >( std::ceil( std::log2( ratio ) ) ) : 0; + matrix_type _A( A ); + if ( s ) + for ( auto& v : _A ) + { + if constexpr ( matrix_private::is_std_complex_v< T > ) v = T( std::ldexp( v.real(), -s ), std::ldexp( v.imag(), -s ) ); + else v = std::ldexp( v, -s ); + } + matrix_type I( A ); + std::fill( I.begin(), I.end(), T( 0 ) ); + for ( std::size_t i = 0; i != n; ++i ) I[i][i] = T( 1 ); + matrix_type const _A2 = _A * _A; + matrix_type const _A4 = _A2 * _A2; + matrix_type const _A6 = _A2 * _A4; + matrix_type const U = _A * ( _A6 * ( c[14] * _A6 + c[12] * _A4 + c[10] * _A2 ) + c[8] * _A6 + c[6] * _A4 + c[4] * _A2 + c[2] * I ); + matrix_type const V = _A6 * ( c[13] * _A6 + c[11] * _A4 + c[9] * _A2 ) + c[7] * _A6 + c[5] * _A4 + c[3] * _A2 + c[1] * I; + auto quotient = lu_factor( matrix_type( V - U ) ).solve( matrix_type( V + U ) ); + if ( !quotient.ok() ) return nan_result(); + matrix_type F = std::move( quotient.value ); + for ( int i = 0; i != s; ++i ) + F = F * F; return F; } - namespace fft_private + + // S7-R5 (D-026): exp(A) into out; nonfinite (out unchanged) for a nonfinite A or a nonfinite result, else ok + template < typename T, Allocator A_ > requires linalg_element< T > + [[nodiscard]] linalg_status expm( matrix< T, A_ > const& a, matrix< T, A_ >& out ) noexcept + { + if ( !matrix_details::all_finite( a.data(), a.size() ) ) return linalg_status::nonfinite; + matrix< T, A_ > e = expm( a ); + if ( !matrix_details::all_finite( e.data(), e.size() ) ) return linalg_status::nonfinite; + out = std::move( e ); + return linalg_status::ok; + } + // S8-R2, S8-R3 (F14, D-007, D-031): numpy.fft.fft2/ifft2 by a separable in-house FFT (radix-2 for power-of-two + // lengths, Bluestein through a power-of-two convolution otherwise) and fftshift/ifftshift as numpy rolls. + namespace matrix_details { template < typename T > - struct add_complex + struct fft_complex; + template < std::integral T > + struct fft_complex< T > { - typedef std::complex< T > result_type; + using type = std::complex< double >; + }; + template < std::floating_point T > + struct fft_complex< T > + { + using type = std::complex< T >; }; template < typename T > - struct add_complex< std::complex< T >> + struct fft_complex< std::complex< T > > { - typedef std::complex< T > result_type; + using type = std::complex< T >; }; - } + // D-031: float, double, long double -> complex of the same real type; complex stays; integers -> complex + template < typename T > + using fft_complex_t = typename fft_complex< T >::type; - template < Matrix Mat > - auto fft( Mat const& x ) - { - typedef typename Mat::value_type value_type; - typedef typename fft_private::add_complex< value_type >::result_type complex_type; - matrix< complex_type > X( x.row(), x.col() ); - auto make_omege = []( auto k, auto n, auto N ) + template < typename R > + inline constexpr R fft_pi = static_cast< R >( 3.141592653589793238462643383279502884L ); + + // twiddle table for a power-of-two length n and a sign: tw[j] = exp(sign 2 pi i j / n), j < n/2, each by std::polar + template < typename R > + void fft_twiddles( std::vector< std::complex< R > >& tw, std::size_t n, int sign ) { - double const pi = 3.1415926535897932384626433; - double const theta = -pi * 2.0 * k * n / static_cast< double >( N ); - return complex_type{ std::cos( theta ), std::sin( theta ) }; - }; - std::uint_least64_t const R = X.row(); - std::uint_least64_t const C = X.col(); + tw.resize( n / 2 ); + R const step = static_cast< R >( sign ) * R( 2 ) * fft_pi< R > / static_cast< R >( n ); + for ( std::size_t j = 0; j != n / 2; ++j ) + tw[j] = std::polar( R( 1 ), step * static_cast< R >( j ) ); + } - for ( std::uint_least64_t r = 0; r != R; ++r ) - for ( std::uint_least64_t c = 0; c != C; ++c ) + // in-place iterative radix-2 transform of a power-of-two length n with a precomputed twiddle table tw + template < typename R > + void fft_radix2_apply( std::complex< R >* data, std::size_t n, std::complex< R > const* tw ) noexcept + { + for ( std::size_t i = 1, j = 0; i < n; ++i ) + { + std::size_t bit = n >> 1; + for ( ; j & bit; bit >>= 1 ) + j ^= bit; + j ^= bit; + if ( i < j ) std::swap( data[i], data[j] ); + } + for ( std::size_t len = 2; len <= n; len <<= 1 ) { - complex_type X_rc{ 0.0, 0.0 }; + std::size_t const half = len >> 1; + std::size_t const stride = n / len; + for ( std::size_t i = 0; i < n; i += len ) + for ( std::size_t j = 0; j != half; ++j ) + { + std::complex< R > const u = data[i + j]; + std::complex< R > const v = data[i + j + half] * tw[j * stride]; + data[i + j] = u + v; + data[i + j + half] = u - v; + } + } + } - for ( std::uint_least64_t r_ = 0; r_ != R; ++r_ ) - { - complex_type tmp{ 0.0, 0.0 }; + // in-place iterative radix-2 transform of a power-of-two length n; sign -1 forward, +1 inverse (unscaled); + // twiddles exp(sign 2 pi i j / n) computed per index with std::polar, no recurrence (per-call tables: the + // reference path behind fft2_reference) + template < typename R > + void fft_radix2( std::complex< R >* data, std::size_t n, int sign ) noexcept + { + std::vector< std::complex< R > > tw; + fft_twiddles( tw, n, sign ); + fft_radix2_apply( data, n, tw.data() ); + } + + // Bluestein chirp w_k = exp(sign i pi (k^2 mod 2n) / n), k < n + template < typename R > + void fft_chirp( std::vector< std::complex< R > >& w, std::size_t n, int sign ) + { + w.resize( n ); + std::uint_least64_t const two_n = 2 * static_cast< std::uint_least64_t >( n ); + std::uint_least64_t q = 0; // k^2 mod 2n, exact in 64-bit integers: (k+1)^2 = k^2 + 2k + 1 + for ( std::size_t k = 0; k != n; ++k ) + { + w[k] = std::polar( R( 1 ), static_cast< R >( sign ) * fft_pi< R > * static_cast< R >( q ) / static_cast< R >( n ) ); + q = ( q + 2 * static_cast< std::uint_least64_t >( k ) + 1 ) % two_n; + } + } + + // Bluestein: X_k = w_k sum_j (x_j w_j) conj(w_{k-j}), w_k = exp(sign i pi (k^2 mod 2n) / n), the + // convolution done by radix-2 transforms of length M, the power of two >= 2n - 1 (reference path) + template < typename R > + void fft_bluestein( std::complex< R >* data, std::size_t n, int sign ) noexcept + { + std::size_t const m = std::bit_ceil( 2 * n - 1 ); + std::vector< std::complex< R > > w; + fft_chirp( w, n, sign ); + std::vector< std::complex< R > > a( m ), b( m ); + for ( std::size_t k = 0; k != n; ++k ) + a[k] = data[k] * w[k]; + b[0] = std::conj( w[0] ); + for ( std::size_t k = 1; k != n; ++k ) + b[k] = b[m - k] = std::conj( w[k] ); + fft_radix2( a.data(), m, -1 ); + fft_radix2( b.data(), m, -1 ); + for ( std::size_t k = 0; k != m; ++k ) + a[k] *= b[k]; + fft_radix2( a.data(), m, +1 ); + R const inv_m = R( 1 ) / static_cast< R >( m ); // m is a power of two: exact + for ( std::size_t k = 0; k != n; ++k ) + data[k] = w[k] * ( a[k] * inv_m ); + } + + // 1-D transform of a contiguous sequence of length n (reference path) + template < typename R > + void fft_1d( std::complex< R >* data, std::size_t n, int sign ) noexcept + { + if ( n < 2 ) return; + if ( std::has_single_bit( n ) ) + fft_radix2( data, n, sign ); + else + fft_bluestein( data, n, sign ); + } - for ( std::uint_least64_t c_ = 0; c_ != C; ++c_ ) - tmp += x[r][c] * make_omege( c, c_, C ); + // S10-R3 (B-042, fft-plans): everything a length-n transform of one sign needs, built once per fft2 call + // by the same expressions as fft_radix2 / fft_bluestein: the twiddle table (radix-2 n), or the chirp w, the + // forward transform of b and the length-m tables for -1 and +1 (Bluestein n), plus the length-m scratch a + template < typename R > + struct fft_plan + { + std::size_t n = 0; + std::size_t m = 0; // 0 for radix-2 (or n < 2), else the Bluestein convolution length + std::vector< std::complex< R > > tw; // radix-2: exp(sign 2 pi i j / n) + std::vector< std::complex< R > > w; // Bluestein chirp + std::vector< std::complex< R > > b_hat; // forward radix-2 transform of b + std::vector< std::complex< R > > tw_fwd; // length-m tables, sign -1 + std::vector< std::complex< R > > tw_inv; // and +1 + std::vector< std::complex< R > > a; // scratch - X_rc += tmp * make_omege( r, r_, R ); + fft_plan( std::size_t n_, int sign ) : n{ n_ } + { + if ( n < 2 ) return; + if ( std::has_single_bit( n ) ) + { + fft_twiddles( tw, n, sign ); + return; } + m = std::bit_ceil( 2 * n - 1 ); + fft_chirp( w, n, sign ); + fft_twiddles( tw_fwd, m, -1 ); + fft_twiddles( tw_inv, m, +1 ); + b_hat.assign( m, std::complex< R >{} ); + b_hat[0] = std::conj( w[0] ); + for ( std::size_t k = 1; k != n; ++k ) + b_hat[k] = b_hat[m - k] = std::conj( w[k] ); + fft_radix2_apply( b_hat.data(), m, tw_fwd.data() ); + a.resize( m ); + } - X[r][c] = X_rc; + void operator()( std::complex< R >* data ) noexcept + { + if ( n < 2 ) return; + if ( m == 0 ) + { + fft_radix2_apply( data, n, tw.data() ); + return; + } + for ( std::size_t k = 0; k != n; ++k ) + a[k] = data[k] * w[k]; + std::fill( a.begin() + static_cast< std::ptrdiff_t >( n ), a.end(), std::complex< R >{} ); + fft_radix2_apply( a.data(), m, tw_fwd.data() ); + for ( std::size_t k = 0; k != m; ++k ) + a[k] *= b_hat[k]; + fft_radix2_apply( a.data(), m, tw_inv.data() ); + R const inv_m = R( 1 ) / static_cast< R >( m ); // m is a power of two: exact + for ( std::size_t k = 0; k != n; ++k ) + data[k] = w[k] * ( a[k] * inv_m ); } + }; - return X; - } + // the input converted to the complex result type + template < Matrix Mat > + auto fft2_load( Mat const& x ) + { + using complex_type = fft_complex_t< typename Mat::value_type >; + using real_type = typename complex_type::value_type; + std::size_t const r = x.row(); + std::size_t const c = x.col(); + matrix< complex_type > X( r, c ); + if ( r == 0 || c == 0 ) return X; + for ( std::size_t i = 0; i != r; ++i ) + for ( std::size_t j = 0; j != c; ++j ) + { + auto const& v = x[i][j]; + if constexpr ( std::is_same_v< typename Mat::value_type, complex_type > ) + X[i][j] = v; + else + X[i][j] = complex_type{ static_cast< real_type >( v ), real_type( 0 ) }; + } + return X; + } - template < Matrix Mat > - auto fftshift( Mat const& x ) - { - auto X = fft( x ); - std::uint_least64_t const R = X.row(); - std::uint_least64_t const C = X.col(); - std::uint_least64_t const row_starter = ( R >> 1 ) + ( R & 1 ); + // ifft (sign +1) divides by R*C + template < typename C > + void fft2_scale( matrix< C >& X, int sign ) noexcept + { + using real_type = typename C::value_type; + if ( sign > 0 ) + { + real_type const n = static_cast< real_type >( X.row() ) * static_cast< real_type >( X.col() ); + for ( auto& v : X ) v /= n; + } + } - for ( std::uint_least64_t index = 0; row_starter + index < R; ++index ) - std::swap_ranges( X.row_begin( index ), X.row_end( index ), X.row_begin( row_starter + index ) ); + // 2-D transform: rows in place, then each column gathered, transformed and scattered, through one plan per + // length (the row plan reused for the columns when r == c) + template < Matrix Mat > + auto fft2( Mat const& x, int sign ) noexcept + { + using complex_type = fft_complex_t< typename Mat::value_type >; + using real_type = typename complex_type::value_type; + std::size_t const r = x.row(); + std::size_t const c = x.col(); + auto X = fft2_load( x ); + if ( r == 0 || c == 0 ) return X; + fft_plan< real_type > row_plan( c, sign ); + for ( std::size_t i = 0; i != r; ++i ) + row_plan( X.data() + i * c ); + if ( r > 1 ) + { + std::optional< fft_plan< real_type > > own; + if ( r != c ) own.emplace( r, sign ); + fft_plan< real_type >& col_plan = r == c ? row_plan : *own; + std::vector< complex_type > col( r ); + for ( std::size_t j = 0; j != c; ++j ) + { + for ( std::size_t i = 0; i != r; ++i ) col[i] = X[i][j]; + col_plan( col.data() ); + for ( std::size_t i = 0; i != r; ++i ) X[i][j] = col[i]; + } + } + fft2_scale( X, sign ); + return X; + } - std::uint_least64_t const col_starter = ( C >> 1 ) + ( C & 1 ); + // S10-R3 reference: the per-row transform fft2 replaced (tables rebuilt for every row and column); tests only + template < Matrix Mat > + auto fft2_reference( Mat const& x, int sign ) noexcept + { + std::size_t const r = x.row(); + std::size_t const c = x.col(); + auto X = fft2_load( x ); + if ( r == 0 || c == 0 ) return X; + for ( std::size_t i = 0; i != r; ++i ) + fft_1d( X.data() + i * c, c, sign ); + if ( r > 1 ) + { + std::vector< typename decltype( X )::value_type > col( r ); + for ( std::size_t j = 0; j != c; ++j ) + { + for ( std::size_t i = 0; i != r; ++i ) col[i] = X[i][j]; + fft_1d( col.data(), r, sign ); + for ( std::size_t i = 0; i != r; ++i ) X[i][j] = col[i]; + } + } + fft2_scale( X, sign ); + return X; + } + + // out[(i + dr) mod R][(j + dc) mod C] = x[i][j], a new matrix of x's type and allocator + template < Matrix Mat > + std::remove_cvref_t< Mat > roll2( Mat const& x, std::ptrdiff_t dr, std::ptrdiff_t dc ) noexcept + { + using out_type = std::remove_cvref_t< Mat >; + std::size_t const r = x.row(); + std::size_t const c = x.col(); + out_type out{ x.get_allocator(), r, c }; + if ( r == 0 || c == 0 ) return out; + std::ptrdiff_t const sr = static_cast< std::ptrdiff_t >( r ); + std::ptrdiff_t const sc = static_cast< std::ptrdiff_t >( c ); + std::size_t const sdr = static_cast< std::size_t >( ( dr % sr + sr ) % sr ); + std::size_t const sdc = static_cast< std::size_t >( ( dc % sc + sc ) % sc ); + for ( std::size_t i = 0; i != r; ++i ) + { + auto const src = x.row_begin( i ); + auto const dst = out.row_begin( ( i + sdr ) % r ); + std::copy( src, src + static_cast< std::ptrdiff_t >( c - sdc ), dst + static_cast< std::ptrdiff_t >( sdc ) ); + std::copy( src + static_cast< std::ptrdiff_t >( c - sdc ), src + sc, dst ); + } + return out; + } + }//namespace matrix_details - for ( std::uint_least64_t index = 0; col_starter + index < C; ++index ) - std::swap_ranges( X.col_begin( index ), X.col_end( index ), X.col_begin( col_starter + index ) ); + // numpy.fft.fft2: X[k][l] = sum x[m][n] exp(-2 pi i (km/R + ln/C)) + template < Matrix Mat > + [[nodiscard]] auto fft( Mat const& x ) noexcept + { + return matrix_details::fft2( x, -1 ); + } - return X; + // numpy.fft.fftshift: roll by (R/2, C/2), no transform + template < Matrix Mat > + [[nodiscard]] auto fftshift( Mat const& x ) noexcept + { + return matrix_details::roll2( x, static_cast< std::ptrdiff_t >( x.row() / 2 ), static_cast< std::ptrdiff_t >( x.col() / 2 ) ); } template < Matrix Mat > - int forward_substitution( Mat const& A, Mat& x, Mat const& b ) + [[nodiscard]] int forward_substitution( Mat const& A, Mat& x, Mat const& b ) noexcept { typedef Mat matrix_type; typedef typename matrix_type::value_type value_type; @@ -6389,147 +8372,274 @@ namespace feng return 0; } - template < Matrix Mat > - std::optional gauss_jordan_elimination( Mat const& m ) noexcept + // S7-R4 (F15, D-026): reduced row echelon form of any m×n A. Columns c = 0..n−1 are visited in turn; the pivot + // of column c is the first entry of largest |·| in rows r..m−1 and counts only when it exceeds + // max(m, n)·ε·‖A‖∞ (MATLAB's rref tolerance); the row index r advances only on a pivot, so zero and dependent + // columns are skipped (their entries in rows r..m−1 are set to 0). Pivot entries are exactly 1 and the other + // entries of a pivot column exactly 0. status is ok, or nonfinite (r = A, no pivots) for a NaN or inf in A. + template < typename T, Allocator A_ = std::allocator< T > > + struct rref_result { - auto const& [row, col] = m.shape(); - better_assert( row < col && "matrix row must be less than colum to execut a Gauss-Jordan Elimination" ); - - auto a = m; - - for ( auto i : matrix_details::range(row) ) - { - auto const p = std::distance( a.col_begin( i ), std::max_element( a.col_begin( i ) + i, a.col_end( i ), [](auto x, auto y){ return std::abs(x) < std::abs(y); } ) ); - - if ( p != static_cast(i) ) - std::swap_ranges( a.row_begin( i ) + i, a.row_end( i ), a.row_begin( p ) + i ); - - auto const factor = a[i][i]; - - if ( std::abs(factor) < 1.0e-10) return {}; - - matrix_details::for_each( a.row_rbegin( i ), a.row_rend( i ) - i, [factor](auto& v){ v /= factor; } ); - - for ( auto j : matrix_details::range(row) ) - { - if ( i == j ) continue; + matrix< T, A_ > r; + std::vector< std::size_t > pivot_columns; + std::size_t rank = 0; + linalg_status status = linalg_status::ok; + }; - auto const ratio = a[j][i]; - std::transform( a.row_rbegin( j ), a.row_rend( j ) - i, a.row_rbegin( i ), a.row_rbegin( j ), [ratio](auto x, auto y){ return x - y*ratio; } ); + template < typename T, Allocator A_ > requires linalg_element< T > + [[nodiscard]] rref_result< T, A_ > row_echelon( matrix< T, A_ > const& m ) noexcept + { + using R = matrix_details::linalg_real_t< T >; + rref_result< T, A_ > ans{ m, {}, 0, linalg_status::ok }; + std::size_t const rows = m.row(); + std::size_t const cols = m.col(); + T* const a = ans.r.data(); + if ( !matrix_details::all_finite( a, rows * cols ) ) { ans.status = linalg_status::nonfinite; return ans; } + R norm_inf{ 0 }; + for ( std::size_t i = 0; i != rows; ++i ) + { + R s{ 0 }; + for ( std::size_t j = 0; j != cols; ++j ) s += std::abs( a[i * cols + j] ); + norm_inf = std::max( norm_inf, s ); + } + R const tol = static_cast< R >( std::max( rows, cols ) ) * std::numeric_limits< R >::epsilon() * norm_inf; + std::size_t r = 0; + for ( std::size_t c = 0; c != cols && r != rows; ++c ) + { + std::size_t p = r; + R best = std::abs( a[r * cols + c] ); + for ( std::size_t i = r + 1; i != rows; ++i ) + if ( R const v = std::abs( a[i * cols + c] ); v > best ) { best = v; p = i; } + if ( !( best > tol ) ) + { + for ( std::size_t i = r; i != rows; ++i ) a[i * cols + c] = T( 0 ); + continue; + } + if ( p != r ) std::swap_ranges( a + r * cols, a + r * cols + cols, a + p * cols ); + T* const pr = a + r * cols; + T const pivot = pr[c]; + for ( std::size_t j = c + 1; j != cols; ++j ) pr[j] /= pivot; + pr[c] = T( 1 ); + for ( std::size_t i = 0; i != rows; ++i ) + { + if ( i == r ) continue; + T* const ri = a + i * cols; + T const f = ri[c]; + if ( f == T( 0 ) ) continue; + for ( std::size_t j = c + 1; j != cols; ++j ) ri[j] -= f * pr[j]; + ri[c] = T( 0 ); } + ans.pivot_columns.push_back( c ); + ++r; } + // columns left once every row holds a pivot need no zeroing: rows r..m−1 do not exist + ans.rank = r; + return ans; + } - return a; + // Legacy spellings (S7-R4): the RREF of any shape, nullopt only for a nonfinite input + template < Matrix Mat > requires linalg_element< typename Mat::value_type > + [[nodiscard]] std::optional gauss_jordan_elimination( Mat const& m ) noexcept + { + auto res = row_echelon( m ); + if ( res.status != linalg_status::ok ) + return {}; + return std::optional{ std::move( res.r ) }; } // matlab alias - template < Matrix Mat > - std::optional rref( Mat const& m ) noexcept + template < Matrix Mat > requires linalg_element< typename Mat::value_type > + [[nodiscard]] std::optional rref( Mat const& m ) noexcept { return gauss_jordan_elimination( m ); } - namespace ifft_private + // numpy.fft.ifft2: exp(+2 pi i (km/R + ln/C)), divided by R*C + template < typename T, Allocator A > + [[nodiscard]] auto ifft( matrix const& x ) noexcept { - template < typename T > - struct add_complex - { - typedef std::complex< T > result_type; - }; - template < typename T > - struct add_complex< std::complex< T >> - { - typedef std::complex< T > result_type; - }; + return matrix_details::fft2( x, +1 ); } - template < typename T, Allocator A > - auto ifft( matrix const& x ) + // numpy.fft.ifftshift: roll by (-(R/2), -(C/2)), no transform + template < Matrix Mat > + [[nodiscard]] auto ifftshift( Mat const& x ) noexcept { - typedef typename ifft_private::add_complex< T >::result_type complex_type; - matrix< complex_type > X( x.row(), x.col() ); - auto make_omege = []( auto k, auto n, auto N ) - { - double const pi = 3.1415926535897932384626433; - double const theta = pi * 2.0 * k * n / static_cast< double >( N ); - return complex_type{ std::cos( theta ), std::sin( theta ) }; - }; - std::uint_least64_t const R = X.row(); - std::uint_least64_t const C = X.col(); - - for ( std::uint_least64_t r = 0; r != R; ++r ) - for ( std::uint_least64_t c = 0; c != C; ++c ) - { - complex_type X_rc{ 0.0, 0.0 }; + return matrix_details::roll2( x, -static_cast< std::ptrdiff_t >( x.row() / 2 ), -static_cast< std::ptrdiff_t >( x.col() / 2 ) ); + } - for ( std::uint_least64_t r_ = 0; r_ != R; ++r_ ) - { - complex_type tmp{ 0.0, 0.0 }; + // S7-R1 (F13, D-026, D-027): the partial-pivoting LU object. Factors of a square A with P·A = L·U; row i of + // P·A is row pivots()[i] of A; status ok, singular (rank < n) or nonfinite. + template < typename T, Allocator A_ = std::allocator< T > > requires linalg_element< T > + class lu_factorization + { + public: + using matrix_type = matrix< T, A_ >; - for ( std::uint_least64_t c_ = 0; c_ != C; ++c_ ) - tmp += x[r][c] * make_omege( c, c_, C ); + lu_factorization() noexcept = default; + explicit lu_factorization( matrix_type const& a ) noexcept : lu_( a ), piv_( a.row() ) + { + better_assert( a.row() == a.col(), "feng::lu_factor: expecting a square matrix, but got ", a.row(), "x", a.col() ); + info_ = matrix_details::lu_in_place( lu_.data(), lu_.row(), piv_.data() ); + } - X_rc += tmp * make_omege( r, r_, R ); - } + [[nodiscard]] linalg_status status() const noexcept { return info_.status; } + [[nodiscard]] bool ok() const noexcept { return info_.status == linalg_status::ok; } + [[nodiscard]] std::size_t rank() const noexcept { return info_.rank; } + [[nodiscard]] std::size_t size() const noexcept { return lu_.row(); } + [[nodiscard]] std::vector< std::size_t > const& pivots() const noexcept { return piv_; } - X[r][c] = X_rc; + // unit lower triangular factor + [[nodiscard]] matrix_type l() const noexcept + { + std::size_t const n = size(); + matrix_type ans( n, n ); + std::fill( ans.begin(), ans.end(), T{ 0 } ); + for ( std::size_t i = 0; i != n; ++i ) + { + for ( std::size_t j = 0; j != i; ++j ) ans[i][j] = lu_[i][j]; + ans[i][i] = T{ 1 }; } + return ans; + } + // upper triangular factor + [[nodiscard]] matrix_type u() const noexcept + { + std::size_t const n = size(); + matrix_type ans( n, n ); + std::fill( ans.begin(), ans.end(), T{ 0 } ); + for ( std::size_t i = 0; i != n; ++i ) + for ( std::size_t j = i; j != n; ++j ) ans[i][j] = lu_[i][j]; + return ans; + } + // permutation matrix: P[i][pivots()[i]] = 1 + [[nodiscard]] matrix_type p() const noexcept + { + std::size_t const n = size(); + matrix_type ans( n, n ); + std::fill( ans.begin(), ans.end(), T{ 0 } ); + for ( std::size_t i = 0; i != n; ++i ) ans[i][piv_[i]] = T{ 1 }; + return ans; + } + // sign(P)·∏u_kk; exactly 0 when rank < n, 1 for 0×0, NaN for a nonfinite input + [[nodiscard]] T det() const noexcept + { + return matrix_details::lu_det( lu_.data(), size(), info_ ); + } + // X with A·X = B (B n×k); singular or nonfinite: that status and an empty value + [[nodiscard]] linalg_result< matrix_type > solve( matrix_type const& b ) const noexcept + { + better_assert( b.row() == size(), "feng::lu_factorization::solve: B must have ", size(), " rows, but got ", b.row(), "x", b.col() ); + if ( !ok() ) return { matrix_type{}, info_.status }; + matrix_type x( size(), b.col() ); + matrix_details::lu_solve( lu_.data(), size(), piv_.data(), b.data(), b.col(), x.data() ); + if ( !matrix_details::all_finite( x.data(), x.size() ) ) return { matrix_type{}, linalg_status::nonfinite }; + return { std::move( x ), linalg_status::ok }; + } + [[nodiscard]] linalg_result< matrix_type > inverse() const noexcept + { + matrix_type id( size(), size() ); + std::fill( id.begin(), id.end(), T{ 0 } ); + for ( std::size_t i = 0; i != size(); ++i ) id[i][i] = T{ 1 }; + return solve( id ); + } - return X; - } - template < Matrix Mat > - auto ifftshift( Mat const& x ) + private: + matrix_type lu_; + std::vector< std::size_t > piv_; + matrix_details::lu_info info_{ linalg_status::ok, 0, 1 }; + }; + + template < typename T, Allocator A > requires linalg_element< T > + [[nodiscard]] lu_factorization< T, A > lu_factor( matrix< T, A > const& a ) noexcept { - auto X = ifft( x ); - std::uint_least64_t const R = X.row(); - std::uint_least64_t const C = X.col(); - std::uint_least64_t const row_starter = ( R >> 1 ) + ( R & 1 ); + return lu_factorization< T, A >( a ); + } - for ( std::uint_least64_t index = 0; row_starter + index < R; ++index ) - std::swap_ranges( X.row_begin( index ), X.row_end( index ), X.row_begin( row_starter + index ) ); + template < typename T, Allocator A > requires linalg_element< T > + [[nodiscard]] linalg_result< matrix< T, A > > solve( matrix< T, A > const& a, matrix< T, A > const& b ) noexcept + { + better_assert( a.row() == a.col() && a.row() == b.row(), "feng::solve: expecting a square A and B with as many rows, but got A ", a.row(), "x", a.col(), " and B ", b.row(), "x", b.col() ); + return lu_factor( a ).solve( b ); + } - std::uint_least64_t const col_starter = ( C >> 1 ) + ( C & 1 ); + template < typename T, Allocator A > requires linalg_element< T > + [[nodiscard]] linalg_result< matrix< T, A > > try_inverse( matrix< T, A > const& a ) noexcept + { + better_assert( a.row() == a.col(), "feng::try_inverse: expecting a square matrix, but got ", a.row(), "x", a.col() ); + return lu_factor( a ).inverse(); + } - for ( std::uint_least64_t index = 0; col_starter + index < C; ++index ) - std::swap_ranges( X.col_begin( index ), X.col_end( index ), X.col_begin( col_starter + index ) ); + // S9-R5 (D-032): the S5 loaders' outcome and file formats, for load_expected; declared in every mode. + enum class io_status { ok, failed }; + enum class io_format { txt, binary, npy }; - return X; +#if defined( __cpp_lib_expected ) + // S9-R5: std::expected views of the status results: the value (or factorization) when ok, else the status. + template < typename V > + [[nodiscard]] std::expected< V, linalg_status > to_expected( linalg_result< V > r ) noexcept + { + if ( !r.ok() ) return std::unexpected( r.status ); + return std::expected< V, linalg_status >{ std::in_place, std::move( r.value ) }; } - template< Matrix Mat > - int lu_decomposition( Mat const& A, Mat& L, Mat& U ) + template < typename F > requires requires( F const& f ) { { f.status() } -> std::same_as< linalg_status >; } + [[nodiscard]] std::expected< F, linalg_status > to_expected( F f ) noexcept { - typedef typename Mat::value_type value_type; - better_assert( A.row() == A.col() && "Square Matrix Requred!" ); - - const std::uint_least64_t n = A.row(); - L.resize( n, n ); - std::fill( L.begin(), L.end(), value_type{0} ); - std::fill( L.diag_begin(), L.diag_end(), value_type( 1 ) ); - - U.resize( n, n ); - std::fill( U.begin(), U.end(), value_type{0} ); + if ( f.status() != linalg_status::ok ) return std::unexpected( f.status() ); + return std::expected< F, linalg_status >{ std::in_place, std::move( f ) }; + } - for ( std::uint_least64_t j = 0; j < n; ++j ) - { - for ( std::uint_least64_t i = 0; i < j + 1; ++i ) - { - U[i][j] = A[i][j] - std::inner_product( L.row_begin( i ), L.row_begin( i ) + i, U.col_begin( j ), value_type() ); - } + template < typename T, Allocator A > + [[nodiscard]] std::expected< rref_result< T, A >, linalg_status > to_expected( rref_result< T, A > r ) noexcept + { + if ( r.status != linalg_status::ok ) return std::unexpected( r.status ); + return std::expected< rref_result< T, A >, linalg_status >{ std::in_place, std::move( r ) }; + } - for ( std::uint_least64_t i = j + 1; i < n; ++i ) - { - L[i][j] = ( A[i][j] - std::inner_product( L.row_begin( i ), L.row_begin( i ) + j, U.col_begin( j ), value_type() ) ) / U[j][j]; + // S9-R5: the matrix load_txt, load_binary or load_npy reads from path, or io_status::failed (the loader has + // printed its one stderr line). + template < typename T, Allocator A = std::allocator< T > > + [[nodiscard]] std::expected< matrix< T, A >, io_status > load_expected( std::string const& path, io_format format ) noexcept + { + matrix< T, A > m; + bool const ok = format == io_format::npy ? m.load_npy( path ) : format == io_format::txt ? m.load_txt( path ) : m.load_binary( path ); + if ( !ok ) return std::unexpected( io_status::failed ); + return std::expected< matrix< T, A >, io_status >{ std::in_place, std::move( m ) }; + } +#endif - if ( std::isinf( L[i][j] ) || std::isnan( L[i][j] ) ) - return 1; - } - } + // out is assigned only when the status is ok (D-026) + template < typename T, Allocator A > requires linalg_element< T > + [[nodiscard]] linalg_status inverse( matrix< T, A > const& a, matrix< T, A >& out ) noexcept + { + better_assert( a.row() == a.col(), "feng::inverse: expecting a square matrix, but got ", a.row(), "x", a.col() ); + return matrix_details::lu_inverse( a, out ); + } + // Legacy two-output LU (MATLAB form): L = Pᵀ·L₀ is a permuted unit lower triangle and A = L·U. Returns 0, or 1 + // with L and U unchanged when the factorization is not ok (singular or nonfinite). + template< Matrix Mat > requires linalg_element< typename Mat::value_type > + [[nodiscard]] int lu_decomposition( Mat const& A, Mat& L, Mat& U ) noexcept + { + typedef typename Mat::value_type value_type; + better_assert( A.row() == A.col(), "lu_decomposition: expecting a square matrix, but got ", A.row(), "x", A.col() ); + auto const f = lu_factor( A ); + if ( !f.ok() ) + return 1; + std::size_t const n = A.row(); + auto const& piv = f.pivots(); + Mat l0 = f.l(); + Mat l( n, n ); + std::fill( l.begin(), l.end(), value_type{ 0 } ); + for ( std::size_t i = 0; i != n; ++i ) // row piv[i] of Pᵀ·L₀ is row i of L₀ + std::copy( l0.row_begin( i ), l0.row_end( i ), l.row_begin( piv[i] ) ); + U = f.u(); + L = std::move( l ); return 0; } - template< Matrix Mat > - std::optional> lu_decomposition( Mat const& A ) + template< Matrix Mat > requires linalg_element< typename Mat::value_type > + [[nodiscard]] std::optional> lu_decomposition( Mat const& A ) noexcept { if ( Mat L, U; lu_decomposition( A, L, U ) == 1 ) return {}; @@ -6537,31 +8647,20 @@ namespace feng return std::make_tuple( L, U ); } - template< Matrix Mat > - int lu_solver( Mat const& A, Mat& x, Mat const& b ) + // Legacy solver for a column b: 0 with x = A⁻¹b, or 1 with x unchanged when A is singular or nonfinite. + template< Matrix Mat > requires linalg_element< typename Mat::value_type > + [[nodiscard]] int lu_solver( Mat const& A, Mat& x, Mat const& b ) noexcept { - typedef Mat matrix_type; - better_assert( A.row() == A.col() ); - better_assert( A.row() == b.row() ); - better_assert( b.col() == 1 ); - matrix_type L, U; - - if ( lu_decomposition( A, L, U ) ) - return 1; - - matrix_type Y; - - if ( forward_substitution( L, Y, b ) ) + better_assert( A.row() == A.col() && A.row() == b.row() && b.col() == 1, "lu_solver: expecting a square A and a column b with as many rows, but got A ", A.row(), "x", A.col(), " and b ", b.row(), "x", b.col() ); + auto r = lu_factor( A ).solve( b ); + if ( !r.ok() ) return 1; - - if ( backward_substitution( U, x, Y ) ) - return 1; - + x = std::move( r.value ); return 0; } - template< Matrix Mat > - std::optional lu_solver( Mat const& A, Mat const& b ) + template< Matrix Mat > requires linalg_element< typename Mat::value_type > + [[nodiscard]] std::optional lu_solver( Mat const& A, Mat const& b ) noexcept { if ( Mat x; lu_solver(A, x, b) == 1 ) return {}; @@ -6569,133 +8668,181 @@ namespace feng return x; } + namespace matrix_details + { + // S8-R1 (F15, D-030): the full 2-D convolution of non-empty A (ra x ca) and B (rb x cb), written directly: + // out[i][j] = sum of A[p][q] * B[i-p][j-q] over the overlap (the kernel reversed), no padded copy. Rows of + // the result are split with parallel; small problems stay on the calling thread. + template< Matrix Mat > + Mat conv_full( Mat const& A, Mat const& B ) noexcept + { + using value_type = typename Mat::value_type; + std::size_t const ra = A.row(), ca = A.col(), rb = B.row(), cb = B.col(); + std::size_t const ro = ra + rb - 1, co = ca + cb - 1; + Mat out{ A.get_allocator(), ro, co }; + value_type const* const a = A.data(); + value_type const* const b = B.data(); + value_type* const o = out.data(); + auto const row = [=]( std::size_t i ) noexcept + { + std::size_t const p0 = i + 1 > rb ? i + 1 - rb : 0; + std::size_t const p1 = std::min( i + 1, ra ); + for ( std::size_t j = 0; j != co; ++j ) + { + std::size_t const q0 = j + 1 > cb ? j + 1 - cb : 0; + std::size_t const q1 = std::min( j + 1, ca ); + value_type acc{ 0 }; + for ( std::size_t p = p0; p != p1; ++p ) + { + value_type const* const ap = a + p * ca; + value_type const* const bp = b + ( i - p ) * cb; + for ( std::size_t q = q0; q != q1; ++q ) + acc += ap[q] * bp[j - q]; + } + o[i * co + j] = acc; + } + }; + std::size_t const work = ro * co * std::min( ra * ca, rb * cb ); + parallel( row, std::size_t{ 0 }, ro, work < ( std::size_t{ 1 } << 16 ) ? std::numeric_limits< unsigned long >::max() : 0UL ); + return out; + } + } + + // S8-R1 (F15): scipy.signal.convolve2d( A, B, "full" ); an operand with a zero dimension gives 0x0 (D-030). template< Matrix Mat > - Mat conv( Mat const& A, Mat const& B ) noexcept + [[nodiscard]] Mat conv( Mat const& A, Mat const& B ) noexcept { if ( ( 0 == A.size() ) || ( 0 == B.size() ) ) - return Mat{0, 0}; - - if ( A.size() > B.size() ) - return conv( B, A ); - - Mat padded_B{ B.row()+2*A.row()-2, B.col()+2*A.col()-2 }; - for ( auto row : matrix_details::range(B.row()) ) - std::copy( B.row_begin(row), B.row_end(row), padded_B.row_begin(row+A.row()-1)+A.col()-1 ); - - Mat ans{ A.row()+B.row()-1, A.col()+B.col()-1 }; - - auto const& product = []( Mat const& a, auto const& b, auto row, auto const col ) noexcept - { - typename Mat::value_type ans{0}; - for ( auto r : matrix_details::range(row) ) - for ( auto c : matrix_details::range(col) ) - ans += a[r][c] * b[r][c]; - return ans; - }; - - auto const& func = [&]( std::uint_least64_t row ) - { - for ( auto col : matrix_details::range(ans.col() )) - { - auto const& view = make_view( padded_B, {row, row+A.row()}, {col, col+A.col()} ); - ans[row][col] = product( A, view, A.row(), A.col() ); - } - }; - - matrix_details::parallel( func, 0UL, ans.row(), 0UL ); - - return ans; + return Mat{ A.get_allocator(), 0, 0 }; + return matrix_details::conv_full( A, B ); } + // S8-R1 (F15): scipy.signal.convolve2d( A, B, mode ) for mode "full", "same" or "valid"; any other mode aborts + // (D-011). same is A's shape cropped from full at ((rb-1)/2, (cb-1)/2); valid needs one operand to contain the + // other in both dimensions and aborts otherwise. Empty operands (D-030): full and valid give 0x0, same gives + // zeros of A's shape. template< Matrix Mat > - Mat conv( Mat const& A, Mat const& B, std::string const& mode ) noexcept + [[nodiscard]] Mat conv( Mat const& A, Mat const& B, std::string const& mode ) noexcept { - auto const& default_conv = conv( A, B ); + bool const full = mode == "full", same = mode == "same", valid = mode == "valid"; + better_assert( full || same || valid, "conv: unknown mode '", mode, "'; expecting \"full\", \"same\" or \"valid\"" ); auto const [ra, ca] = A.shape(); auto const [rb, cb] = B.shape(); - if ( mode == std::string{"same"} ) + if ( ( 0 == A.size() ) || ( 0 == B.size() ) ) + return same ? Mat{ A.get_allocator(), ra, ca } : Mat{ A.get_allocator(), 0, 0 }; + + if ( full ) + return matrix_details::conv_full( A, B ); + + if ( same ) { - better_assert( rb > 1, " For a convolution in 'same' mode, the row of the second matrix is at least 1, but now has ", rb ); - better_assert( rb > 1, " For a convolution in 'same' mode, the column of the second matrix is at least 1, but now has ", cb ); - return { default_conv, { (rb-1)>>1, ra + ((rb-1)>>1) }, { (cb-1)>>1, ca + ((cb-1)>>1) } }; + Mat const f = matrix_details::conv_full( A, B ); + return { f, { (rb-1)/2, ra + (rb-1)/2 }, { (cb-1)/2, ca + (cb-1)/2 } }; } - if ( mode == std::string{"valid"} ) + bool const a_contains_b = ra >= rb && ca >= cb; + bool const b_contains_a = rb >= ra && cb >= ca; + better_assert( a_contains_b || b_contains_a, "conv: 'valid' mode needs one operand to contain the other in both dimensions, but got ", ra, "x", ca, " and ", rb, "x", cb ); + if ( a_contains_b ) { - better_assert( ra >= rb-1, " For a convolution in 'valid' mode, the row of first matrix is supposed to be at least larger than the second matrix's row by 1", - " but the first matrix is with row ", ra, " and the second matrix is with row ", rb ); - better_assert( ca >= cb-1, " For a convolution in 'valid' mode, the column of first matrix is supposed to be at least larger than the second matrix's column by 1", - " but the first matrix is with column ", ca, " and the second matrix is with column ", cb ); - return { default_conv, { rb-1, ra }, { cb-1, ca } }; + Mat const f = matrix_details::conv_full( A, B ); + return { f, { rb-1, ra }, { cb-1, ca } }; } - - return default_conv; + Mat const f = matrix_details::conv_full( B, A ); + return { f, { ra-1, rb }, { ca-1, cb } }; } template< typename ... Args > - auto conv2( Args const& ... args ) + [[nodiscard]] auto conv2( Args const& ... args ) noexcept { return conv( args... ); } - inline std::optional,3>> load_bmp( std::string const& file_path ) + namespace matrix_details { - std::ifstream ifs{file_path, std::ios::binary}; - if ( !ifs ) - { - std::cerr << "load_bmp::Failed to open file " << file_path << "\n"; - return {}; // <- failed to load file - } - - std::vector const file_content{(std::istreambuf_iterator(ifs)), (std::istreambuf_iterator())}; - if( file_content.size() <= 54 ) - { - std::cerr << "load_bmp::file " << file_path << " has contents less than 54 byte.\n"; - return {}; // <- failed for bad file - } - - std::uint_least64_t const col = std::uint_least64_t{file_content[18]} | (std::uint_least64_t{file_content[19]} << 8) | - (std::uint_least64_t{file_content[20]} << 16) | (std::uint_least64_t{file_content[21]} << 24); - std::uint_least64_t const row = std::uint_least64_t{file_content[22]} | (std::uint_least64_t{file_content[23]} << 8) | - (std::uint_least64_t{file_content[24]} << 16) | (std::uint_least64_t{file_content[25]} << 24); - std::uint_least64_t const padding = ( 4 - ( ( col * 3 ) & 0x3 ) ) & 0x3; - - if ( 54+(3*col+padding)*row != file_content.size() ) - { - std::cerr << "load_bmp::file " << file_path << " has insistant contents with its header.\n"; - std::cerr << "Additional Information: col-" << col << ", row-" << row << ", padding-" << padding << ", file size-" << file_content.size() << "\n"; - std::cerr << "Expected size-" << 54+(3*col+padding)*row << ", but the file has " << file_content.size() << " bytes\n"; - return {}; //<- error with the data size - } - - std::array,3> ans; - for ( auto& mat : ans ) mat.resize( row, col ); - auto& [mat_r, mat_g, mat_b] = ans; - - auto pos_itor = file_content.begin()+54; - for ( auto r : matrix_details::range( row ) ) + // S5-R3 (F08): parses a complete BMP file image of `size` bytes into its red, green and blue channels. + // Accepts the 14-byte file header with signature BM, an info header of size 40, 52, 56, 108 or 124 inside + // the file, planes 1, compression 0 (BI_RGB), 24 or 32 bits per pixel (alpha ignored), width > 0, height + // != 0 and != INT32_MIN, a pixel-data offset at or past the end of the info header and offset + + // stride * |height| <= size in checked 64-bit arithmetic (trailing bytes allowed). A positive height is + // stored bottom-up, a negative one top-down. On failure returns an empty optional and sets *why when given. + inline std::optional,3>> parse_bmp( std::uint8_t const* bytes, std::size_t size, char const** why = nullptr ) noexcept { - for ( auto c : matrix_details::range( col ) ) + auto fail = [why]( char const* reason ) noexcept -> std::optional,3>> { - mat_b[row-r-1][c] = *pos_itor++; - mat_g[row-r-1][c] = *pos_itor++; - mat_r[row-r-1][c] = *pos_itor++; + if ( why ) *why = reason; + return std::nullopt; + }; + auto u16 = [bytes]( std::size_t at ) noexcept { return static_cast( bytes[at] ) | ( static_cast( bytes[at+1] ) << 8 ); }; + auto u32 = [u16]( std::size_t at ) noexcept { return u16( at ) | ( u16( at+2 ) << 16 ); }; + + if ( bytes == nullptr || size < 18 ) return fail( "file too short for the BMP headers" ); + if ( bytes[0] != 'B' || bytes[1] != 'M' ) return fail( "bad BMP signature" ); + std::uint32_t const info_size = u32( 14 ); + if ( info_size != 40 && info_size != 52 && info_size != 56 && info_size != 108 && info_size != 124 ) + return fail( "unsupported BMP info header size" ); + std::uint64_t const headers_end = 14 + std::uint64_t{ info_size }; + if ( headers_end > size ) return fail( "BMP info header extends past the end of the file" ); + std::int32_t const width = static_cast( u32( 18 ) ); + std::int32_t const height = static_cast( u32( 22 ) ); + std::uint32_t const planes = u16( 26 ); + std::uint32_t const bpp = u16( 28 ); + std::uint32_t const compression = u32( 30 ); + std::uint64_t const offset = u32( 10 ); + if ( planes != 1 ) return fail( "BMP planes is not 1" ); + if ( compression != 0 ) return fail( "compressed BMP is not supported" ); + if ( bpp != 24 && bpp != 32 ) return fail( "unsupported BMP bit depth" ); + if ( width <= 0 ) return fail( "BMP width is not positive" ); + if ( height == 0 || height == std::numeric_limits::min() ) return fail( "bad BMP height" ); + if ( offset < headers_end ) return fail( "BMP pixel data overlaps the headers" ); + + std::uint64_t const cols = static_cast( width ); + std::uint64_t const rows = height < 0 ? static_cast( -static_cast( height ) ) : static_cast( height ); + std::uint64_t bits = 0, stride = 0, pixels = 0, pixels_end = 0; + if ( !checked_mul( cols, std::uint64_t{ bpp }, bits ) || !checked_add( bits, std::uint64_t{ 31 }, bits ) ) + return fail( "BMP row size overflows" ); + stride = bits / 32 * 4; + if ( !checked_mul( stride, rows, pixels ) || !checked_add( offset, pixels, pixels_end ) ) + return fail( "BMP pixel array size overflows" ); + if ( pixels_end > size ) return fail( "BMP pixel data extends past the end of the file" ); + + std::array,3> ans{ matrix( rows, cols ), matrix( rows, cols ), matrix( rows, cols ) }; + auto& [mat_r, mat_g, mat_b] = ans; + std::uint64_t const step = bpp / 8; + for ( std::uint64_t k = 0; k != rows; ++k ) + { + std::uint64_t const r = height > 0 ? rows - 1 - k : k; + std::uint8_t const* p = bytes + offset + k * stride; + for ( std::uint64_t c = 0; c != cols; ++c, p += step ) + { + mat_b[r][c] = p[0]; + mat_g[r][c] = p[1]; + mat_r[r][c] = p[2]; + } } - std::advance( pos_itor, padding ); + return ans; } + }//namespace matrix_details - return {ans}; + // S5-R3/S5-R4 (D-011): reads the whole file and parses it with matrix_details::parse_bmp; on failure returns an + // empty optional and prints one stderr line naming load_bmp and the reason. Never aborts. + [[nodiscard]] inline std::optional,3>> load_bmp( std::string const& file_path ) noexcept + { + std::vector bytes; + if ( !matrix_details::read_file( file_path.c_str(), bytes, "feng::load_bmp" ) ) + return std::nullopt; + char const* why = "invalid BMP file"; + auto ans = matrix_details::parse_bmp( bytes.data(), bytes.size(), &why ); + if ( !ans ) + std::cerr << "feng::load_bmp -- " << why << ": " << file_path << "\n"; + return ans; } template< Matrix Mat > - auto pooling( Mat const& mat, std::uint_least64_t dim_r, std::uint_least64_t dim_c, std::string const& pooling_action = "mean" ) + [[nodiscard]] auto pooling( Mat const& mat, std::uint_least64_t dim_r, std::uint_least64_t dim_c, std::string const& pooling_action = "mean" ) noexcept { - if (dim_r == 0 || dim_c == 0) return Mat{}; - - if (dim_r==1 && dim_c==1) return mat; - typedef typename Mat::value_type T; std::map< std::string, std::pair > > function_list = @@ -6741,6 +8888,10 @@ namespace feng auto iterator = function_list.find( pooling_action ); better_assert( iterator != function_list.end(), "Error: Unknow pooling action [[", pooling_action, "]], only [mean], [max], [min] supported!" ); + if (dim_r == 0 || dim_c == 0) return Mat{}; + + if (dim_r==1 && dim_c==1) return mat; + auto init_value =(*iterator).second.first; auto const& the_function =(*iterator).second.second; auto const [row, col] = mat.shape(); @@ -6757,19 +8908,22 @@ namespace feng } }; - matrix_details::parallel( make_pooling, 0UL, new_row, 0UL ); + matrix_details::parallel_work( make_pooling, 0UL, new_row, matrix_details::saturating_work( new_col, matrix_details::saturating_work( dim_r, dim_c ) ), matrix_details::callback_grain ); return ans; } template< Matrix Mat > - auto pooling( Mat const& mat, std::uint_least64_t dim, std::string const& pooling_action = "mean" ) + [[nodiscard]] auto pooling( Mat const& mat, std::uint_least64_t dim, std::string const& pooling_action = "mean" ) noexcept { return pooling( mat, dim, dim, pooling_action ); } template< Matrix Mat > - void save_as_bmp( std::string const& file_name, Mat const& red_channel, Mat const& green_channel, Mat const& blue_channel ) + // S5-R4 (D-011): true on success; false with one stderr line on a directory, open, write or close failure, never + // aborting (mismatched channel shapes remain a contract violation). S5-R3: the file loads back through load_bmp + // to the scaled channels in the same orientation. + [[nodiscard]] bool save_as_bmp( std::string const& file_name, Mat const& red_channel, Mat const& green_channel, Mat const& blue_channel ) noexcept { better_assert( red_channel.row()==green_channel.row(), "Row not match for red and green matrix! The row for red is ", red_channel.row(), " but for green is ", green_channel.row() ); better_assert( red_channel.row()==blue_channel.row(), "Row not match for red and blue matrix! The row for red is ", red_channel.row(), " but for blue is ", blue_channel.row() ); @@ -6785,36 +8939,40 @@ namespace feng auto const& [red_mx, red_mn] = std::make_pair( *std::max_element(red_channel.begin(), red_channel.end() ), *std::min_element(red_channel.begin(), red_channel.end() ) ); auto const& [green_mx, green_mn] = std::make_pair( *std::max_element(green_channel.begin(), green_channel.end() ), *std::min_element(green_channel.begin(), green_channel.end() ) ); auto const& [blue_mx, blue_mn] = std::make_pair( *std::max_element(blue_channel.begin(), blue_channel.end() ), *std::min_element(blue_channel.begin(), blue_channel.end() ) ); + // encode_bmp_stream writes its row k as the k-th stored (bottom-up) row, so channel row k holds image row + // row-1-k (the member save_as_bmp flips the same way) for ( auto r : matrix_details::range(row) ) for ( auto c : matrix_details::range(col) ) { - channel_r[r][c] = static_cast( 256.0 * (red_channel[r][c] - red_mn) / (red_mx-red_mn+0.1) ); - channel_g[r][c] = static_cast( 256.0 * (green_channel[r][c] - green_mn) / (green_mx-green_mn+0.1) ); - channel_b[r][c] = static_cast( 256.0 * (blue_channel[r][c] - blue_mn) / (blue_mx-blue_mn+0.1) ); + auto const k = row - 1 - r; + channel_r[k][c] = static_cast( 256.0 * (red_channel[r][c] - red_mn) / (red_mx-red_mn+0.1) ); + channel_g[k][c] = static_cast( 256.0 * (green_channel[r][c] - green_mn) / (green_mx-green_mn+0.1) ); + channel_b[k][c] = static_cast( 256.0 * (blue_channel[r][c] - blue_mn) / (blue_mx-blue_mn+0.1) ); } // encode rgb to bitmap stream auto const& encoding = matrix_details::encode_bmp_stream( channel_r, channel_g, channel_b ); - better_assert( encoding, "Failed to convert the 3 matrix to bmp stream in save_as_bmp function, where the file_name is ", file_name ); + if ( !encoding ) return matrix_details::write_failed( "feng::save_as_bmp", "failed to encode the BMP stream", file_name ); // write file stream std::string new_file_name{ file_name }; std::string const extension{ ".bmp" }; if ( ( new_file_name.size() < 4 ) || ( std::string{ new_file_name.begin() + new_file_name.size() - 4, new_file_name.end() } != extension ) ) new_file_name += extension; - std::ofstream stream( new_file_name.c_str(), std::ios_base::out | std::ios_base::binary ); - better_assert( stream, "Failed to create file ", new_file_name, " when executing save_as_bmp with file_name = ", file_name, " and row = ", row, " col = ", col ); - stream.write( reinterpret_cast((*encoding).data()), (*encoding).size() ); + return matrix_details::write_stream( "feng::save_as_bmp", new_file_name, std::ios_base::out | std::ios_base::binary, + [&encoding]( std::ofstream& stream ) + { stream.write( reinterpret_cast((*encoding).data()), static_cast( (*encoding).size() ) ); } ); } + // S5-R4 (D-011): the member save_as_bmp's result. template< Matrix Mat > - void save_as_bmp( std::string const& file_name, Mat const& mat, std::string const& colormap = std::string{"parula"} ) + [[nodiscard]] bool save_as_bmp( std::string const& file_name, Mat const& mat, std::string const& colormap = std::string{"parula"} ) noexcept { - mat.save_as_bmp( file_name, colormap ); + return mat.save_as_bmp( file_name, colormap ); } template< typename T > - auto meshgrid( T const& x, T const& y ) noexcept + [[nodiscard]] auto meshgrid( T const& x, T const& y ) noexcept { unsigned long const row = static_cast( y ); unsigned long const col = static_cast( x ); @@ -6831,359 +8989,126 @@ namespace feng } }; - matrix_details::parallel( parallel_func, 0UL, row, 0UL ); + matrix_details::parallel_work( parallel_func, 0UL, row, matrix_details::saturating_work( col, 2 ), matrix_details::elementwise_grain ); return std::make_pair( mat_y, mat_x ); } + namespace matrix_details + { + // S9-R6: the one elementwise transform. Returns matrix< R, A rebound to R > of m's shape holding f( x ) for + // each element x of m, assigned (so converted) to R; R is f's result type unless given as transform< R >. + // The allocator is m's, rebound; the elements are written by the parallel for_each (S6). + // Further matrix operands ns (same shape as m; checked by transform_checked) are read alongside: f( x, y... ). + template < typename R = void, typename T, Allocator A, typename F, Matrix... Ns > + auto transform( matrix< T, A > const& m, F const& f, Ns const&... ns ) noexcept + { + using result_type = std::conditional_t< std::is_void_v< R >, std::invoke_result_t< F const&, T const&, typename Ns::value_type const&... >, R >; + using result_alloc = typename std::allocator_traits< A >::template rebind_alloc< result_type >; + matrix< result_type, result_alloc > ans{ result_alloc( m.get_allocator() ), m.row(), m.col() }; + matrix_details::for_each( ans.begin(), ans.end(), m.begin(), ns.begin()..., [&f]( result_type& v, T const& x, auto const&... y ) { v = f( x, y... ); } ); + return ans; + } + + // Two or three matrix operands: the shape check (expect_same_shape, under name), then transform. + template < typename R = void, typename F, Matrix M, Matrix... Ns > + auto transform_checked( char const* name, F const& f, M const& m, Ns const&... ns ) noexcept + { + matrix_details::expect_same_shape( name, m, ns... ); + return matrix_details::transform< R >( m, f, ns... ); + } + } + // // - begin of unary functions // - + // Each keeps its pre-S9 result type: m's type, or the integer type cmath returns (ilogb, lrint, llrint, lround, llround). template< Matrix Mat > - auto abs( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::abs(x); } ); - return ans; - } - + [[nodiscard]] auto abs( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::abs( x ); } ); } template< Matrix Mat > - auto exp( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::exp(x); } ); - return ans; - } - + [[nodiscard]] auto exp( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::exp( x ); } ); } template< Matrix Mat > - auto exp2( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::exp2(x); } ); - return ans; - } - + [[nodiscard]] auto exp2( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::exp2( x ); } ); } template< Matrix Mat > - auto expm1( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::expm1(x); } ); - return ans; - } - + [[nodiscard]] auto expm1( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::expm1( x ); } ); } template< Matrix Mat > - auto log( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::log(x); } ); - return ans; - } - + [[nodiscard]] auto log( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::log( x ); } ); } template< Matrix Mat > - auto log10( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::log10(x); } ); - return ans; - } - + [[nodiscard]] auto log10( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::log10( x ); } ); } template< Matrix Mat > - auto log1p( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::log1p(x); } ); - return ans; - } - + [[nodiscard]] auto log1p( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::log1p( x ); } ); } template< Matrix Mat > - auto log2( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::log2(x); } ); - return ans; - } - + [[nodiscard]] auto log2( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::log2( x ); } ); } template< Matrix Mat > - auto sqrt( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::sqrt(x); } ); - return ans; - } - + [[nodiscard]] auto sqrt( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::sqrt( x ); } ); } template< Matrix Mat > - auto cbrt( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::cbrt(x); } ); - return ans; - } - + [[nodiscard]] auto cbrt( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::cbrt( x ); } ); } template< Matrix Mat > - auto sin( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::sin(x); } ); - return ans; - } - + [[nodiscard]] auto sin( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::sin( x ); } ); } template< Matrix Mat > - auto cos( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::cos(x); } ); - return ans; - } - + [[nodiscard]] auto cos( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::cos( x ); } ); } template< Matrix Mat > - auto tan( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::tan(x); } ); - return ans; - } - + [[nodiscard]] auto tan( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::tan( x ); } ); } template< Matrix Mat > - auto asin( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::asin(x); } ); - return ans; - } - + [[nodiscard]] auto asin( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::asin( x ); } ); } template< Matrix Mat > - auto acos( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::acos(x); } ); - return ans; - } - + [[nodiscard]] auto acos( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::acos( x ); } ); } template< Matrix Mat > - auto atan( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::atan(x); } ); - return ans; - } - + [[nodiscard]] auto atan( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::atan( x ); } ); } template< Matrix Mat > - auto sinh( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::sinh(x); } ); - return ans; - } - + [[nodiscard]] auto sinh( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::sinh( x ); } ); } template< Matrix Mat > - auto cosh( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::cosh(x); } ); - return ans; - } - + [[nodiscard]] auto cosh( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::cosh( x ); } ); } template< Matrix Mat > - auto tanh( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::tanh(x); } ); - return ans; - } - + [[nodiscard]] auto tanh( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::tanh( x ); } ); } template< Matrix Mat > - auto asinh( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::asinh(x); } ); - return ans; - } - + [[nodiscard]] auto asinh( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::asinh( x ); } ); } template< Matrix Mat > - auto acosh( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::acosh(x); } ); - return ans; - } - + [[nodiscard]] auto acosh( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::acosh( x ); } ); } template< Matrix Mat > - auto atanh( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::atanh(x); } ); - return ans; - } - + [[nodiscard]] auto atanh( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::atanh( x ); } ); } template< Matrix Mat > - auto erf( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::erf(x); } ); - return ans; - } - + [[nodiscard]] auto erf( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::erf( x ); } ); } template< Matrix Mat > - auto erfc( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::erfc(x); } ); - return ans; - } - + [[nodiscard]] auto erfc( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::erfc( x ); } ); } template< Matrix Mat > - auto tgamma( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::tgamma(x); } ); - return ans; - } - + [[nodiscard]] auto tgamma( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::tgamma( x ); } ); } template< Matrix Mat > - auto lgamma( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::lgamma(x); } ); - return ans; - } - + [[nodiscard]] auto lgamma( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::lgamma( x ); } ); } template< Matrix Mat > - auto trunc( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::trunc(x); } ); - return ans; - } - + [[nodiscard]] auto trunc( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::trunc( x ); } ); } template< Matrix Mat > - auto round( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::round(x); } ); - return ans; - } - + [[nodiscard]] auto round( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::round( x ); } ); } template< Matrix Mat > - auto ceil( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::ceil(x); } ); - return ans; - } - + [[nodiscard]] auto ceil( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::ceil( x ); } ); } template< Matrix Mat > - auto floor( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::floor(x); } ); - return ans; - } - + [[nodiscard]] auto floor( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::floor( x ); } ); } template< Matrix Mat > - auto rint( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::rint(x); } ); - return ans; - } - + [[nodiscard]] auto rint( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::rint( x ); } ); } template< Matrix Mat > - auto logb( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::logb(x); } ); - return ans; - } - + [[nodiscard]] auto logb( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::logb( x ); } ); } template< Matrix Mat > - auto comp_ellint_1( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::comp_ellint_1(x); } ); - return ans; - } - + [[nodiscard]] auto comp_ellint_1( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::comp_ellint_1( x ); } ); } template< Matrix Mat > - auto comp_ellint_2( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::comp_ellint_2(x); } ); - return ans; - } - + [[nodiscard]] auto comp_ellint_2( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::comp_ellint_2( x ); } ); } template< Matrix Mat > - auto comp_ellint_3( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::comp_ellint_3(x); } ); - return ans; - } - + [[nodiscard]] auto comp_ellint_3( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::comp_ellint_3( x ); } ); } template< Matrix Mat > - auto expint( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::expint(x); } ); - return ans; - } - + [[nodiscard]] auto expint( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::expint( x ); } ); } template< Matrix Mat > - auto riemann_zeta( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::riemann_zeta(x); } ); - return ans; - } - + [[nodiscard]] auto riemann_zeta( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::riemann_zeta( x ); } ); } template< Matrix Mat > - auto nearbyint( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::nearbyint(x); } ); - return ans; - } - + [[nodiscard]] auto nearbyint( Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, []( auto const x ){ return std::nearbyint( x ); } ); } template< Matrix Mat > - auto ilogb( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::ilogb(x); } ); - return ans.template astype(); // <- in cmath, ilogb returns an int - } - + [[nodiscard]] auto ilogb( Mat const& m ) noexcept { return matrix_details::transform< int >( m, []( auto const x ){ return std::ilogb( x ); } ); } template< Matrix Mat > - auto lrint( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::lrint(x); } ); - return ans.template astype(); // <- in cmath, lrint returns a long - } - + [[nodiscard]] auto lrint( Mat const& m ) noexcept { return matrix_details::transform< long >( m, []( auto const x ){ return std::lrint( x ); } ); } template< Matrix Mat > - auto llrint( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::llrint(x); } ); - return ans.template astype(); // <- in cmath, llrint returns a long long - } - + [[nodiscard]] auto llrint( Mat const& m ) noexcept { return matrix_details::transform< long long >( m, []( auto const x ){ return std::llrint( x ); } ); } template< Matrix Mat > - auto lround( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::lround(x); } ); - return ans.template astype(); // <- in cmath, lround returns a long - } - + [[nodiscard]] auto lround( Mat const& m ) noexcept { return matrix_details::transform< long >( m, []( auto const x ){ return std::lround( x ); } ); } template< Matrix Mat > - auto llround( Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::llround(x); } ); - return ans.template astype(); // <- in cmath, llround returns a long long - } - + [[nodiscard]] auto llround( Mat const& m ) noexcept { return matrix_details::transform< long long >( m, []( auto const x ){ return std::llround( x ); } ); } // // - end of unary functions @@ -7191,435 +9116,131 @@ namespace feng // - // - begin of trinary functions + // - begin of trinary and binary functions // + // Each keeps m's type (zeros_like( m ) before S9): the matrix-matrix forms check shapes in transform_checked, + // the scalar forms capture the scalar. template< Matrix Mat > - auto fma( Mat const& m1, Mat const& m2, Mat const& m3 ) - { - auto ans = zeros_like( m1 ); - matrix_details::for_each( m1.begin(), m1.end(), m2.begin(), m3.begin(), ans.begin(), []( auto const x1, auto const x2, auto const x3, auto& v ){ v = std::fma(x1, x2, x3); } ); - return ans; - } - - // - // - end of trinary functions - // - - // - // - begin of binary functions - // + [[nodiscard]] auto fma( Mat const& m1, Mat const& m2, Mat const& m3 ) noexcept { return matrix_details::transform_checked< typename Mat::value_type >( "fma", []( auto const x, auto const y, auto const z ){ return std::fma( x, y, z ); }, m1, m2, m3 ); } template< Matrix Mat, Matrix Nat > - auto ldexp( Mat const& m, Nat const& n ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), n.begin(), ans.begin(), []( auto const xm, auto const xn, auto& v ){ v = std::ldexp(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto ldexp( Mat const& m, std::floating_point auto xn ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::ldexp(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto ldexp( std::floating_point auto xn, Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::ldexp(xn, xm); } ); - return ans; - } + [[nodiscard]] auto ldexp( Mat const& m, Nat const& n ) noexcept { return matrix_details::transform_checked< typename Mat::value_type >( "ldexp", []( auto const x, auto const y ){ return std::ldexp( x, y ); }, m, n ); } + template< Matrix Mat > + [[nodiscard]] auto ldexp( Mat const& m, std::floating_point auto y ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::ldexp( x, y ); } ); } + template< Matrix Mat > + [[nodiscard]] auto ldexp( std::floating_point auto y, Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::ldexp( y, x ); } ); } template< Matrix Mat, Matrix Nat > - auto scalbn( Mat const& m, Nat const& n ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), n.begin(), ans.begin(), []( auto const xm, auto const xn, auto& v ){ v = std::scalbn(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto scalbn( Mat const& m, std::floating_point auto xn ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::scalbn(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto scalbn( std::floating_point auto xn, Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::scalbn(xn, xm); } ); - return ans; - } + [[nodiscard]] auto scalbn( Mat const& m, Nat const& n ) noexcept { return matrix_details::transform_checked< typename Mat::value_type >( "scalbn", []( auto const x, auto const y ){ return std::scalbn( x, y ); }, m, n ); } + template< Matrix Mat > + [[nodiscard]] auto scalbn( Mat const& m, std::floating_point auto y ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::scalbn( x, y ); } ); } + template< Matrix Mat > + [[nodiscard]] auto scalbn( std::floating_point auto y, Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::scalbn( y, x ); } ); } template< Matrix Mat, Matrix Nat > - auto scalbln( Mat const& m, Nat const& n ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), n.begin(), ans.begin(), []( auto const xm, auto const xn, auto& v ){ v = std::scalbln(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto scalbln( Mat const& m, std::floating_point auto xn ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::scalbln(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto scalbln( std::floating_point auto xn, Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::scalbln(xn, xm); } ); - return ans; - } + [[nodiscard]] auto scalbln( Mat const& m, Nat const& n ) noexcept { return matrix_details::transform_checked< typename Mat::value_type >( "scalbln", []( auto const x, auto const y ){ return std::scalbln( x, y ); }, m, n ); } + template< Matrix Mat > + [[nodiscard]] auto scalbln( Mat const& m, std::floating_point auto y ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::scalbln( x, y ); } ); } + template< Matrix Mat > + [[nodiscard]] auto scalbln( std::floating_point auto y, Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::scalbln( y, x ); } ); } template< Matrix Mat, Matrix Nat > - auto pow( Mat const& m, Nat const& n ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), n.begin(), ans.begin(), []( auto const xm, auto const xn, auto& v ){ v = std::pow(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto pow( Mat const& m, std::integral auto xn ) // <- pow accept integral as the second argument - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::pow(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto pow( Mat const& m, std::floating_point auto xn ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::pow(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto pow( std::floating_point auto xn, Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::pow(xn, xm); } ); - return ans; - } + [[nodiscard]] auto pow( Mat const& m, Nat const& n ) noexcept { return matrix_details::transform_checked< typename Mat::value_type >( "pow", []( auto const x, auto const y ){ return std::pow( x, y ); }, m, n ); } + template< Matrix Mat > + [[nodiscard]] auto pow( Mat const& m, std::integral auto y ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::pow( x, y ); } ); } // <- pow accept integral as the second argument + template< Matrix Mat > + [[nodiscard]] auto pow( Mat const& m, std::floating_point auto y ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::pow( x, y ); } ); } + template< Matrix Mat > + [[nodiscard]] auto pow( std::floating_point auto y, Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::pow( y, x ); } ); } template< Matrix Mat, Matrix Nat > - auto hypot( Mat const& m, Nat const& n ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), n.begin(), ans.begin(), []( auto const xm, auto const xn, auto& v ){ v = std::hypot(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto hypot( Mat const& m, std::floating_point auto xn ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::hypot(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto hypot( std::floating_point auto xn, Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::hypot(xn, xm); } ); - return ans; - } + [[nodiscard]] auto hypot( Mat const& m, Nat const& n ) noexcept { return matrix_details::transform_checked< typename Mat::value_type >( "hypot", []( auto const x, auto const y ){ return std::hypot( x, y ); }, m, n ); } + template< Matrix Mat > + [[nodiscard]] auto hypot( Mat const& m, std::floating_point auto y ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::hypot( x, y ); } ); } + template< Matrix Mat > + [[nodiscard]] auto hypot( std::floating_point auto y, Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::hypot( y, x ); } ); } template< Matrix Mat, Matrix Nat > - auto fmod( Mat const& m, Nat const& n ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), n.begin(), ans.begin(), []( auto const xm, auto const xn, auto& v ){ v = std::fmod(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto fmod( Mat const& m, std::floating_point auto xn ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::fmod(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto fmod( std::floating_point auto xn, Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::fmod(xn, xm); } ); - return ans; - } + [[nodiscard]] auto fmod( Mat const& m, Nat const& n ) noexcept { return matrix_details::transform_checked< typename Mat::value_type >( "fmod", []( auto const x, auto const y ){ return std::fmod( x, y ); }, m, n ); } + template< Matrix Mat > + [[nodiscard]] auto fmod( Mat const& m, std::floating_point auto y ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::fmod( x, y ); } ); } + template< Matrix Mat > + [[nodiscard]] auto fmod( std::floating_point auto y, Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::fmod( y, x ); } ); } template< Matrix Mat, Matrix Nat > - auto remainder( Mat const& m, Nat const& n ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), n.begin(), ans.begin(), []( auto const xm, auto const xn, auto& v ){ v = std::remainder(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto remainder( Mat const& m, std::floating_point auto xn ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::remainder(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto remainder( std::floating_point auto xn, Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::remainder(xn, xm); } ); - return ans; - } + [[nodiscard]] auto remainder( Mat const& m, Nat const& n ) noexcept { return matrix_details::transform_checked< typename Mat::value_type >( "remainder", []( auto const x, auto const y ){ return std::remainder( x, y ); }, m, n ); } + template< Matrix Mat > + [[nodiscard]] auto remainder( Mat const& m, std::floating_point auto y ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::remainder( x, y ); } ); } + template< Matrix Mat > + [[nodiscard]] auto remainder( std::floating_point auto y, Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::remainder( y, x ); } ); } template< Matrix Mat, Matrix Nat > - auto copysign( Mat const& m, Nat const& n ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), n.begin(), ans.begin(), []( auto const xm, auto const xn, auto& v ){ v = std::copysign(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto copysign( Mat const& m, std::floating_point auto xn ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::copysign(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto copysign( std::floating_point auto xn, Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::copysign(xn, xm); } ); - return ans; - } + [[nodiscard]] auto copysign( Mat const& m, Nat const& n ) noexcept { return matrix_details::transform_checked< typename Mat::value_type >( "copysign", []( auto const x, auto const y ){ return std::copysign( x, y ); }, m, n ); } + template< Matrix Mat > + [[nodiscard]] auto copysign( Mat const& m, std::floating_point auto y ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::copysign( x, y ); } ); } + template< Matrix Mat > + [[nodiscard]] auto copysign( std::floating_point auto y, Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::copysign( y, x ); } ); } template< Matrix Mat, Matrix Nat > - auto nextafter( Mat const& m, Nat const& n ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), n.begin(), ans.begin(), []( auto const xm, auto const xn, auto& v ){ v = std::nextafter(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto nextafter( Mat const& m, std::floating_point auto xn ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::nextafter(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto nextafter( std::floating_point auto xn, Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::nextafter(xn, xm); } ); - return ans; - } + [[nodiscard]] auto nextafter( Mat const& m, Nat const& n ) noexcept { return matrix_details::transform_checked< typename Mat::value_type >( "nextafter", []( auto const x, auto const y ){ return std::nextafter( x, y ); }, m, n ); } + template< Matrix Mat > + [[nodiscard]] auto nextafter( Mat const& m, std::floating_point auto y ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::nextafter( x, y ); } ); } + template< Matrix Mat > + [[nodiscard]] auto nextafter( std::floating_point auto y, Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::nextafter( y, x ); } ); } template< Matrix Mat, Matrix Nat > - auto fdim( Mat const& m, Nat const& n ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), n.begin(), ans.begin(), []( auto const xm, auto const xn, auto& v ){ v = std::fdim(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto fdim( Mat const& m, std::floating_point auto xn ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::fdim(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto fdim( std::floating_point auto xn, Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::fdim(xn, xm); } ); - return ans; - } + [[nodiscard]] auto fdim( Mat const& m, Nat const& n ) noexcept { return matrix_details::transform_checked< typename Mat::value_type >( "fdim", []( auto const x, auto const y ){ return std::fdim( x, y ); }, m, n ); } + template< Matrix Mat > + [[nodiscard]] auto fdim( Mat const& m, std::floating_point auto y ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::fdim( x, y ); } ); } + template< Matrix Mat > + [[nodiscard]] auto fdim( std::floating_point auto y, Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::fdim( y, x ); } ); } template< Matrix Mat, Matrix Nat > - auto fmax( Mat const& m, Nat const& n ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), n.begin(), ans.begin(), []( auto const xm, auto const xn, auto& v ){ v = std::fmax(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto fmax( Mat const& m, std::floating_point auto xn ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::fmax(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto fmax( std::floating_point auto xn, Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::fmax(xn, xm); } ); - return ans; - } + [[nodiscard]] auto fmax( Mat const& m, Nat const& n ) noexcept { return matrix_details::transform_checked< typename Mat::value_type >( "fmax", []( auto const x, auto const y ){ return std::fmax( x, y ); }, m, n ); } + template< Matrix Mat > + [[nodiscard]] auto fmax( Mat const& m, std::floating_point auto y ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::fmax( x, y ); } ); } + template< Matrix Mat > + [[nodiscard]] auto fmax( std::floating_point auto y, Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::fmax( y, x ); } ); } template< Matrix Mat, Matrix Nat > - auto fmin( Mat const& m, Nat const& n ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), n.begin(), ans.begin(), []( auto const xm, auto const xn, auto& v ){ v = std::fmin(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto fmin( Mat const& m, std::floating_point auto xn ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::fmin(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto fmin( std::floating_point auto xn, Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::fmin(xn, xm); } ); - return ans; - } + [[nodiscard]] auto fmin( Mat const& m, Nat const& n ) noexcept { return matrix_details::transform_checked< typename Mat::value_type >( "fmin", []( auto const x, auto const y ){ return std::fmin( x, y ); }, m, n ); } + template< Matrix Mat > + [[nodiscard]] auto fmin( Mat const& m, std::floating_point auto y ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::fmin( x, y ); } ); } + template< Matrix Mat > + [[nodiscard]] auto fmin( std::floating_point auto y, Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::fmin( y, x ); } ); } template< Matrix Mat, Matrix Nat > - auto atan2( Mat const& m, Nat const& n ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), n.begin(), ans.begin(), []( auto const xm, auto const xn, auto& v ){ v = std::atan2(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto atan2( Mat const& m, std::floating_point auto xn ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::atan2(xm, xn); } ); - return ans; - } - - template< Matrix Mat> - auto atan2( std::floating_point auto xn, Mat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), [xn]( auto const xm, auto& v ){ v = std::atan2(xn, xm); } ); - return ans; - } - + [[nodiscard]] auto atan2( Mat const& m, Nat const& n ) noexcept { return matrix_details::transform_checked< typename Mat::value_type >( "atan2", []( auto const x, auto const y ){ return std::atan2( x, y ); }, m, n ); } + template< Matrix Mat > + [[nodiscard]] auto atan2( Mat const& m, std::floating_point auto y ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::atan2( x, y ); } ); } + template< Matrix Mat > + [[nodiscard]] auto atan2( std::floating_point auto y, Mat const& m ) noexcept { return matrix_details::transform< typename Mat::value_type >( m, [y]( auto const x ){ return std::atan2( y, x ); } ); } // - // - end of binary functions + // - end of trinary and binary functions // - - - // // - begin of Complex Functions // - + // real, imag, abs, arg and norm return the complex value type, rebinding m's allocator; conj, proj and polar keep m's type. template< ComplexMatrix CMat > - auto real( CMat const& cm ) - { - typedef typename CMat::value_type complex_type; - typedef typename complex_type::value_type value_type; - typedef typename CMat::allocator_type Alloc; - matrix:: template rebind_alloc > ans{ cm.row(), cm.col() }; - matrix_details::for_each( cm.begin(), cm.end(), ans.begin(), []( auto const& val, auto& v ){ v = std::real(val); } ); - return ans; - } - + [[nodiscard]] auto real( CMat const& cm ) noexcept { return matrix_details::transform< typename CMat::value_type::value_type >( cm, []( auto const& x ){ return std::real( x ); } ); } template< ComplexMatrix CMat > - auto imag( CMat const& cm ) - { - typedef typename CMat::value_type complex_type; - typedef typename complex_type::value_type value_type; - typedef typename CMat::allocator_type Alloc; - matrix:: template rebind_alloc > ans{ cm.row(), cm.col() }; - matrix_details::for_each( cm.begin(), cm.end(), ans.begin(), []( auto const& val, auto& v ){ v = std::imag(val); } ); - return ans; - } - + [[nodiscard]] auto imag( CMat const& cm ) noexcept { return matrix_details::transform< typename CMat::value_type::value_type >( cm, []( auto const& x ){ return std::imag( x ); } ); } template< ComplexMatrix CMat > - auto abs( CMat const& cm ) - { - typedef typename CMat::value_type complex_type; - typedef typename complex_type::value_type value_type; - typedef typename CMat::allocator_type Alloc; - matrix:: template rebind_alloc > ans{ cm.row(), cm.col() }; - matrix_details::for_each( cm.begin(), cm.end(), ans.begin(), []( auto const& val, auto& v ){ v = std::abs(val); } ); - return ans; - } - + [[nodiscard]] auto abs( CMat const& cm ) noexcept { return matrix_details::transform< typename CMat::value_type::value_type >( cm, []( auto const& x ){ return std::abs( x ); } ); } template< ComplexMatrix CMat > - auto arg( CMat const& cm ) - { - typedef typename CMat::value_type complex_type; - typedef typename complex_type::value_type value_type; - typedef typename CMat::allocator_type Alloc; - matrix:: template rebind_alloc > ans{ cm.row(), cm.col() }; - matrix_details::for_each( cm.begin(), cm.end(), ans.begin(), []( auto const& val, auto& v ){ v = std::arg(val); } ); - return ans; - } - + [[nodiscard]] auto arg( CMat const& cm ) noexcept { return matrix_details::transform< typename CMat::value_type::value_type >( cm, []( auto const& x ){ return std::arg( x ); } ); } template< ComplexMatrix CMat > - auto norm( CMat const& cm ) - { - typedef typename CMat::value_type complex_type; - typedef typename complex_type::value_type value_type; - typedef typename CMat::allocator_type Alloc; - matrix:: template rebind_alloc > ans{ cm.row(), cm.col() }; - matrix_details::for_each( cm.begin(), cm.end(), ans.begin(), []( auto const& val, auto& v ){ v = std::norm(val); } ); - return ans; - } - + [[nodiscard]] auto norm( CMat const& cm ) noexcept { return matrix_details::transform< typename CMat::value_type::value_type >( cm, []( auto const& x ){ return std::norm( x ); } ); } template< ComplexMatrix CMat > - auto conj( CMat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::conj(x); } ); - return ans; - } - + [[nodiscard]] auto conj( CMat const& m ) noexcept { return matrix_details::transform< typename CMat::value_type >( m, []( auto const x ){ return std::conj( x ); } ); } template< ComplexMatrix CMat > - auto proj( CMat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::proj(x); } ); - return ans; - } - + [[nodiscard]] auto proj( CMat const& m ) noexcept { return matrix_details::transform< typename CMat::value_type >( m, []( auto const x ){ return std::proj( x ); } ); } template< ComplexMatrix CMat > - auto polar( CMat const& m ) - { - auto ans = zeros_like( m ); - matrix_details::for_each( m.begin(), m.end(), ans.begin(), []( auto const x, auto& v ){ v = std::polar(x); } ); - return ans; - } + [[nodiscard]] auto polar( CMat const& m ) noexcept { return matrix_details::transform< typename CMat::value_type >( m, []( auto const x ){ return std::polar( x ); } ); } // // - end of Complex Functions @@ -7631,29 +9252,73 @@ namespace feng /// template< Matrix Mat > - auto sum( Mat const& m ) + [[nodiscard]] auto sum( Mat const& m ) noexcept { return matrix_details::reduce( m.begin(), m.end(), typename Mat::value_type{}, []( auto const x, auto const y ){ return x+y; } ); } + // S6-R4 (F15, D-011, D-025): mean, variance and standard_deviation compute in stat_type_t (double for + // integral T, T for floating T, complex for complex T) and return the mean's real type for the spread + // (X for complex); variance divides sum |x - mean|^2 by n - ddof. Empty input or n <= ddof aborts. + namespace matrix_details + { + template< typename T > + struct stat_type { using type = T; using real_type = T; }; + template< std::integral T > + struct stat_type< T > { using type = double; using real_type = double; }; + template< typename X > + struct stat_type< std::complex< X > > { using type = std::complex< X >; using real_type = X; }; + + template< typename T > + using stat_type_t = typename stat_type< T >::type; + template< typename T > + using stat_real_t = typename stat_type< T >::real_type; + + template< Matrix Mat > + stat_real_t< typename Mat::value_type > variance_unchecked( Mat const& m, std::uint_least64_t ddof ) noexcept + { + using S = stat_type_t< typename Mat::value_type >; + using R = stat_real_t< typename Mat::value_type >; + S total{}; + for ( auto const& x : m ) total += static_cast< S >( x ); + S const mu = total / static_cast< R >( m.size() ); + R acc{}; + for ( auto const& x : m ) + { + S const d = static_cast< S >( x ) - mu; + if constexpr ( matrix_private::is_std_complex_v< S > ) + acc += std::norm( d ); + else + acc += d * d; + } + return acc / static_cast< R >( m.size() - ddof ); + } + } + template< Matrix Mat > - auto mean( Mat const& m ) + [[nodiscard]] auto mean( Mat const& m ) noexcept { - return sum( m ) / m.size(); + using S = matrix_details::stat_type_t< typename Mat::value_type >; + using R = matrix_details::stat_real_t< typename Mat::value_type >; + FENG_MATRIX_EXPECTS( m.size() != 0, "feng::mean: empty matrix, shape ", m.row(), "x", m.col() ); + S total{}; + for ( auto const& x : m ) total += static_cast< S >( x ); + return S{ total / static_cast< R >( m.size() ) }; } template< Matrix Mat > - auto variance( Mat const& m ) + [[nodiscard]] auto variance( Mat const& m, std::uint_least64_t ddof = 0 ) noexcept { - return mean( pow( m-mean(m), 2.0 ) ); + FENG_MATRIX_EXPECTS( m.size() != 0 && m.size() > ddof, "feng::variance: needs more elements than ddof, shape ", m.row(), "x", m.col(), ", ddof ", ddof ); + return matrix_details::variance_unchecked( m, ddof ); } template< Matrix Mat > - auto standard_deviation( Mat const& m ) + [[nodiscard]] auto standard_deviation( Mat const& m, std::uint_least64_t ddof = 0 ) noexcept { - if ( m.size() <= 1 ) - return typename Mat::value_type{}; - return std::sqrt( sum( pow( m-mean( m ), 2.0 ) ) / ( m.size() - 1 ) ); + FENG_MATRIX_EXPECTS( m.size() != 0 && m.size() > ddof, "feng::standard_deviation: needs more elements than ddof, shape ", m.row(), "x", m.col(), ", ddof ", ddof ); + using std::sqrt; + return sqrt( matrix_details::variance_unchecked( m, ddof ) ); } /// @@ -7666,9 +9331,9 @@ namespace feng /// template< typename T > - auto clip( T const lower, T const upper ) + [[nodiscard]] auto clip( T const lower, T const upper ) noexcept { - return [lower, upper]( Mat const& m ) + return [lower, upper]( Mat const& m ) noexcept { auto ans{ m }; matrix_details::for_each( ans.begin(), ans.end(), [lower, upper]( auto & v ){ v = v < lower ? lower : v > upper ? upper : v; } ); @@ -7682,6 +9347,17 @@ namespace feng } //namespace feng +// S4-R2 (F06): views do not own their elements, so iterators taken from a temporary view stay valid while the +// owner lives. +namespace std::ranges +{ + template < feng::matrix_element Type, feng::Allocator Alloc > + inline constexpr bool enable_borrowed_range< feng::matrix_view< Type, Alloc > > = true; + + template < feng::matrix_element Type, feng::Allocator Alloc > + inline constexpr bool enable_borrowed_range< feng::mutable_matrix_view< Type, Alloc > > = true; +} + #undef better_assert #endif diff --git a/tests/api/access.cc b/tests/api/access.cc new file mode 100644 index 0000000..5f5000e --- /dev/null +++ b/tests/api/access.cc @@ -0,0 +1,93 @@ +// S1-R4 API family `access`: operator[], item, data, shape, size, row/col/diag/anti-diag/direct iterators, and the +// S9-R4 span, mdspan and submdspan adapters. +// Compiled alone by `tools/check.sh api`; calling each API instantiates its template. +#include "../../matrix.hpp" + +#include +#include + +namespace +{ + template < typename T, typename It > + T walk( It first, It last ) + { + T acc{}; + for ( ; first != last; ++first ) + acc += *first; + return acc; + } + +// Every begin/end flavour of one iterator family, on the mutable and the const matrix, with an index argument. +#define API_ACCESS_FAMILY( prefix, index ) \ + acc += walk( m.prefix##_begin( index ), m.prefix##_end( index ) ); \ + acc += walk( cm.prefix##_begin( index ), cm.prefix##_end( index ) ); \ + acc += walk( cm.prefix##_cbegin( index ), cm.prefix##_cend( index ) ); \ + acc += walk( m.prefix##_rbegin( index ), m.prefix##_rend( index ) ); \ + acc += walk( cm.prefix##_rbegin( index ), cm.prefix##_rend( index ) ); \ + acc += walk( cm.prefix##_crbegin( index ), cm.prefix##_crend( index ) ); + + template < typename T > + T exercise_access() + { + feng::matrix m{ 4, 5, T{ 1 } }; + feng::matrix const& cm = m; + T acc{}; + + // element access + m[1][2] = T{ 2 }; + acc += cm[1][2]; + m( 2, 3 ) = T{ 3 }; + acc += cm( 2, 3 ); + acc += feng::matrix{ 1, 1, T{ 4 } }.item(); + *m.data() = T{ 5 }; + acc += *cm.data(); + + // shape and size + auto const [r, c] = cm.shape(); + acc += static_cast( static_cast( r + c + cm.row() + cm.col() + cm.size() ) ); + + // direct iterators + acc += walk( m.begin(), m.end() ); + acc += walk( cm.begin(), cm.end() ); + acc += walk( cm.cbegin(), cm.cend() ); + acc += walk( m.rbegin(), m.rend() ); + acc += walk( cm.rbegin(), cm.rend() ); + acc += walk( cm.crbegin(), cm.crend() ); + + // row, column, diagonal and anti-diagonal iterators + API_ACCESS_FAMILY( row, 1 ) + API_ACCESS_FAMILY( col, 2 ) + API_ACCESS_FAMILY( diag, 1 ) + API_ACCESS_FAMILY( diag, -1 ) + API_ACCESS_FAMILY( upper_diag, 1 ) + API_ACCESS_FAMILY( lower_diag, 1 ) + API_ACCESS_FAMILY( anti_diag, 1 ) + API_ACCESS_FAMILY( anti_diag, -1 ) + API_ACCESS_FAMILY( upper_anti_diag, 1 ) + API_ACCESS_FAMILY( lower_anti_diag, 1 ) + + // the default-index overloads + acc += walk( m.row_begin(), m.row_end() ); + acc += walk( m.diag_begin(), m.diag_end() ); + acc += walk( m.anti_diag_begin(), m.anti_diag_end() ); + + // S9-R4: span adapters, and the mdspan / submdspan adapters where the library has them + acc += walk( feng::as_span( m ).begin(), feng::as_span( m ).end() ); + acc += walk( feng::as_span( cm ).begin(), feng::as_span( cm ).end() ); + acc += feng::row_span( m, 1 )[0] + feng::row_span( cm, 2 )[1]; +#if defined( __cpp_lib_mdspan ) + acc += feng::to_mdspan( m )[1, 2] + feng::to_mdspan( cm )[2, 3]; +#endif +#if defined( __cpp_lib_submdspan ) + acc += feng::submdspan( m, { 1, 3 }, { 0, 2 } )[0, 1] + feng::submdspan( cm, { 0, 1 }, { 0, 5 } )[0, 4]; +#endif + return acc; + } + +#undef API_ACCESS_FAMILY +} // namespace + +double api_access_double() { return exercise_access(); } +float api_access_float() { return exercise_access(); } +std::complex api_access_complex() { return exercise_access>(); } +int api_access_int() { return exercise_access(); } diff --git a/tests/api/allocator.cc b/tests/api/allocator.cc new file mode 100644 index 0000000..25ee293 --- /dev/null +++ b/tests/api/allocator.cc @@ -0,0 +1,65 @@ +// S1-R4 API family `allocator`: matrix with a non-default stateless allocator, get_allocator. Compiled alone by +// `tools/check.sh api` and never run. +#include "../../matrix.hpp" + +#include +#include +#include +#include + +namespace +{ + // A small stateless allocator that is not std::allocator: it forwards to the global operator new/delete. + template < typename T > + struct api_allocator + { + using value_type = T; + + api_allocator() noexcept = default; + template < typename U > + api_allocator( api_allocator const& ) noexcept + { + } + + T* allocate( std::size_t n ) { return static_cast( ::operator new( n * sizeof( T ) ) ); } + void deallocate( T* p, std::size_t ) noexcept { ::operator delete( p ); } + + template < typename U > + bool operator==( api_allocator const& ) const noexcept + { + return true; + } + }; + + template < typename T > + T exercise_allocator() + { + using alloc_type = api_allocator; + using matrix_type = feng::matrix; + alloc_type const alloc{}; + T acc{}; + + matrix_type a{ alloc, 3, 3, T{ 1 } }; + matrix_type b{ 3, 3, T{ 2 } }; + matrix_type c{ a }; + matrix_type d{ alloc, 3, 3 }; + d = b; + c.swap( d ); + c.resize( 2, 2 ); + + alloc_type const got = a.get_allocator(); + acc += ( got == alloc ) ? T{ 1 } : T{}; + + matrix_type const s = a + b; + matrix_type const p = a * b; + matrix_type const t = a.transpose(); + acc += s[0][0] + p[0][0] + t[0][0] + c[0][0]; + acc += feng::sum( a ); + return acc; + } +} // namespace + +double api_allocator_double() { return exercise_allocator(); } +float api_allocator_float() { return exercise_allocator(); } +std::complex api_allocator_complex() { return exercise_allocator>(); } +int api_allocator_int() { return exercise_allocator(); } diff --git a/tests/api/arithmetic.cc b/tests/api/arithmetic.cc new file mode 100644 index 0000000..dc50e6f --- /dev/null +++ b/tests/api/arithmetic.cc @@ -0,0 +1,93 @@ +// S1-R4 API family `arithmetic`: + - * / with scalars, matrices, valarray and vector; compound and prefix +// operators; `^` power. Compiled alone by `tools/check.sh api`; calling each operator instantiates its template. +#include "../../matrix.hpp" + +#include +#include +#include + +namespace +{ + template < typename T > + T sink( feng::matrix const& m ) + { + return m.size() ? m[0][0] : T{}; + } + + template < typename T > + T exercise_arithmetic() + { + feng::matrix a{ 3, 3, T{ 1 } }; + feng::matrix b{ 3, 3, T{ 2 } }; + T const s{ 3 }; + T acc{}; + + // matrix with matrix + acc += sink( a + b ); + acc += sink( a - b ); + acc += sink( a * b ); + + // matrix with scalar, both sides + acc += sink( a + s ); + acc += sink( s + a ); + acc += sink( a - s ); + acc += sink( s - a ); + acc += sink( a * s ); + acc += sink( s * a ); + acc += sink( a / s ); + + // valarray, vector and pointer products + std::valarray const va( T{ 1 }, 3 ); + std::vector const ve( 3, T{ 1 } ); + acc += sink( a * va ); + acc += sink( va * a ); + acc += sink( a * ve ); + acc += sink( ve * a ); + acc += sink( a * ve.data() ); + acc += sink( ve.data() * a ); + + // compound operators + feng::matrix c{ a }; + c += b; + c += s; + c -= b; + c -= s; + c *= b; + c *= s; + c /= s; + acc += sink( c ); + + // prefix operators + acc += sink( -a ); + acc += sink( +a ); + + // power + acc += sink( a ^ 0 ); + acc += sink( a ^ 5 ); + return acc; + } + + template < typename T > + T exercise_division() + { + // matrix / matrix and scalar / matrix go through the inverse, so floating-point and complex types only. + feng::matrix a = feng::eye( 3, 3 ); + feng::matrix b = feng::eye( 3, 3 ); + T const s{ 2 }; + T acc{}; + acc += sink( a / b ); + acc += sink( s / a ); + feng::matrix c{ a }; + c /= b; + acc += sink( c ); + return acc; + } +} // namespace + +double api_arithmetic_double() { return exercise_arithmetic() + exercise_division(); } +float api_arithmetic_float() { return exercise_arithmetic() + exercise_division(); } +std::complex api_arithmetic_complex() +{ + return exercise_arithmetic>() + exercise_division>(); +} +int api_arithmetic_int() { return exercise_arithmetic(); } diff --git a/tests/api/construction.cc b/tests/api/construction.cc new file mode 100644 index 0000000..188d94e --- /dev/null +++ b/tests/api/construction.cc @@ -0,0 +1,129 @@ +// S1-R4 API family `construction`: constructors, zeros, ones, eye, arange, linspace, magic, hilbert, toeplitz, diag, +// make_diag, blkdiag, meshgrid, *_like, rand/random/randn. Compiled alone by `tools/check.sh api`; calling each API +// instantiates its template. +#include "../../matrix.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + template < typename M > + auto sink( M const& m ) -> typename M::value_type + { + return m.size() ? m[0][0] : typename M::value_type{}; + } + + template < typename T > + T exercise_constructors() + { + using matrix_type = feng::matrix; + T acc{}; + + // constructors: default, sized, filled, initializer list, allocator, copy, move, converting + matrix_type const a; + matrix_type const b{ 2, 3 }; + matrix_type const c{ 2, 3, T{ 1 } }; + matrix_type const d{ 2, 2, { T{ 1 }, T{ 2 }, T{ 3 }, T{ 4 } } }; + matrix_type const e{ std::allocator{}, 2, 2, T{ 1 } }; + matrix_type f{ c }; + matrix_type const g{ std::move( f ) }; + feng::matrix const h{ feng::matrix{ 2, 2, 1 } }; + matrix_type i; + i = c; + i = matrix_type{ 2, 2 }; + i = T{ 1 }; + acc += sink( a ) + sink( b ) + sink( c ) + sink( d ) + sink( e ) + sink( g ) + sink( i ); + acc += static_cast( static_cast( h.size() ) ); + + // zeros, ones, empty, eye + acc += sink( feng::zeros( 2, 3 ) ); + acc += sink( feng::zeros( 2 ) ); + acc += sink( feng::zeros( std::allocator{}, 2, 3 ) ); + acc += sink( feng::zeros( std::allocator{}, 2 ) ); + acc += sink( feng::ones( 2, 3 ) ); + acc += sink( feng::ones( 2 ) ); + acc += sink( feng::ones( std::allocator{}, 2, 3 ) ); + acc += sink( feng::ones( std::allocator{}, 2 ) ); + acc += sink( feng::empty( 2, 3 ) ); + acc += sink( feng::empty( std::allocator{}, 2UL, 3UL ) ); + acc += sink( feng::eye( 2, 3 ) ); + acc += sink( feng::eye( 3 ) ); + acc += sink( feng::eye( c ) ); + + // *_like + acc += sink( feng::zeros_like( c ) ); + acc += sink( feng::ones_like( c ) ); + + // diag, make_diag, blkdiag + acc += sink( feng::diag( d ) ); + acc += sink( feng::diag( d, 1 ) ); + acc += sink( feng::diag( std::vector( 3, T{ 1 } ) ) ); + acc += sink( feng::diag( std::deque( 3, T{ 1 } ), -1 ) ); + acc += sink( feng::diag( std::valarray( T{ 1 }, 3 ) ) ); + acc += sink( feng::make_diag( T{ 1 }, T{ 2 }, T{ 3 } ) ); + acc += sink( feng::blkdiag( c, d ) ); + acc += sink( feng::blkdiag( c, d, e ) ); + acc += sink( feng::blk_diag( c, d ) ); + acc += sink( feng::block_diag( c, d ) ); + + // toeplitz and hilbert through a prototype matrix + std::vector const col{ T{ 1 }, T{ 2 }, T{ 3 } }; + acc += sink( feng::toeplitz( col.begin(), col.end() ) ); + acc += sink( feng::toeplitz( col.begin(), col.end(), col.begin(), col.end() ) ); + acc += sink( feng::repmat( d, 2, 2 ) ); + return acc; + } + + template < typename T > + T exercise_real() + { + // APIs restricted to ordered (real or integer) element types + T acc{}; + std::set const s{ T{ 1 }, T{ 2 } }; + std::multiset const ms{ T{ 1 }, T{ 1 } }; + acc += sink( feng::diag( s ) ); + acc += sink( feng::diag( ms ) ); + acc += sink( feng::arange( 0, 6, 2 ) ); + acc += sink( feng::arange( 5 ) ); + acc += sink( feng::linspace( T{ 0 }, T{ 10 }, 5 ) ); + acc += sink( feng::linspace( T{ 0 }, T{ 10 }, 5, false ) ); + acc += sink( feng::hilbert( 3 ) ); + acc += sink( feng::hilb( 3 ) ); + acc += sink( feng::hilbert( 3, feng::matrix{} ) ); + acc += sink( feng::hilb( 3, feng::matrix{} ) ); + auto const [mx, my] = feng::meshgrid( T{ 3 }, T{ 2 } ); + acc += sink( mx ) + sink( my ); + return acc; + } + + template < typename T > + T exercise_random() + { + feng::matrix const m{ 2, 2, T{ 1 } }; + T acc{}; + acc += sink( feng::rand( 2, 3 ) ); + acc += sink( feng::rand( 2, 3, 42U ) ); + acc += sink( feng::rand( 2 ) ); + acc += sink( feng::random( 2, 3 ) ); + acc += sink( feng::random( 2 ) ); + acc += sink( feng::rand_like( m ) ); + acc += sink( feng::random_like( m ) ); + acc += sink( feng::randn_like( m ) ); + return acc; + } +} // namespace + +double api_construction_double() +{ + return exercise_constructors() + exercise_real() + exercise_random() + + static_cast( feng::magic( 4 )[0][0] ); +} +float api_construction_float() { return exercise_constructors() + exercise_real() + exercise_random(); } +std::complex api_construction_complex() { return exercise_constructors>(); } +int api_construction_int() { return exercise_constructors() + exercise_real(); } diff --git a/tests/api/elementwise.cc b/tests/api/elementwise.cc new file mode 100644 index 0000000..8fac969 --- /dev/null +++ b/tests/api/elementwise.cc @@ -0,0 +1,152 @@ +// S1-R4 API family `elementwise`: the unary and binary `` wrappers, abs, real, imag, conj, arg, polar, clip, +// astype, apply. Compiled alone by `tools/check.sh api`; calling each API instantiates its template. +#include "../../matrix.hpp" + +#include + +namespace +{ + template < typename M > + auto sink( M const& m ) -> typename M::value_type + { + return m.size() ? m[0][0] : typename M::value_type{}; + } + + template < typename T > + double exercise_unary() + { + feng::matrix const m{ 3, 3, T{ 0.5 } }; + double acc{}; + acc += sink( feng::abs( m ) ); + acc += sink( feng::exp( m ) ); + acc += sink( feng::exp2( m ) ); + acc += sink( feng::expm1( m ) ); + acc += sink( feng::log( m ) ); + acc += sink( feng::log10( m ) ); + acc += sink( feng::log1p( m ) ); + acc += sink( feng::log2( m ) ); + acc += sink( feng::sqrt( m ) ); + acc += sink( feng::cbrt( m ) ); + acc += sink( feng::sin( m ) ); + acc += sink( feng::cos( m ) ); + acc += sink( feng::tan( m ) ); + acc += sink( feng::asin( m ) ); + acc += sink( feng::acos( m ) ); + acc += sink( feng::atan( m ) ); + acc += sink( feng::sinh( m ) ); + acc += sink( feng::cosh( m ) ); + acc += sink( feng::tanh( m ) ); + acc += sink( feng::asinh( m ) ); + acc += sink( feng::acosh( m ) ); + acc += sink( feng::atanh( m ) ); + acc += sink( feng::erf( m ) ); + acc += sink( feng::erfc( m ) ); + acc += sink( feng::tgamma( m ) ); + acc += sink( feng::lgamma( m ) ); + acc += sink( feng::trunc( m ) ); + acc += sink( feng::round( m ) ); + acc += sink( feng::ceil( m ) ); + acc += sink( feng::floor( m ) ); + acc += sink( feng::rint( m ) ); + acc += sink( feng::logb( m ) ); + acc += sink( feng::comp_ellint_1( m ) ); + acc += sink( feng::comp_ellint_2( m ) ); + acc += sink( feng::expint( m ) ); + acc += sink( feng::riemann_zeta( m ) ); + acc += sink( feng::nearbyint( m ) ); + acc += sink( feng::ilogb( m ) ); + acc += static_cast( sink( feng::lrint( m ) ) ); + acc += static_cast( sink( feng::llrint( m ) ) ); + acc += static_cast( sink( feng::lround( m ) ) ); + acc += static_cast( sink( feng::llround( m ) ) ); + acc += sink( feng::fma( m, m, m ) ); + return acc; + } + + template < typename T > + double exercise_binary() + { + feng::matrix const m{ 3, 3, T{ 0.5 } }; + feng::matrix const n{ 3, 3, T{ 2 } }; + double const x = 2.0; + double acc{}; + acc += sink( feng::ldexp( m, feng::matrix{ 3, 3, 1 } ) ); + acc += sink( feng::ldexp( m, x ) ); + acc += sink( feng::scalbn( m, feng::matrix{ 3, 3, 1 } ) ); + acc += sink( feng::scalbln( m, feng::matrix{ 3, 3, 1L } ) ); + acc += sink( feng::pow( m, n ) ); + acc += sink( feng::pow( m, 2 ) ); + acc += sink( feng::pow( m, x ) ); + acc += sink( feng::pow( x, m ) ); + acc += sink( feng::hypot( m, n ) ); + acc += sink( feng::hypot( m, x ) ); + acc += sink( feng::hypot( x, m ) ); + acc += sink( feng::fmod( m, n ) ); + acc += sink( feng::fmod( m, x ) ); + acc += sink( feng::fmod( x, m ) ); + acc += sink( feng::remainder( m, n ) ); + acc += sink( feng::remainder( m, x ) ); + acc += sink( feng::remainder( x, m ) ); + acc += sink( feng::copysign( m, n ) ); + acc += sink( feng::copysign( m, x ) ); + acc += sink( feng::copysign( x, m ) ); + acc += sink( feng::nextafter( m, n ) ); + acc += sink( feng::nextafter( m, x ) ); + acc += sink( feng::nextafter( x, m ) ); + acc += sink( feng::fdim( m, n ) ); + acc += sink( feng::fdim( m, x ) ); + acc += sink( feng::fdim( x, m ) ); + acc += sink( feng::fmax( m, n ) ); + acc += sink( feng::fmax( m, x ) ); + acc += sink( feng::fmax( x, m ) ); + acc += sink( feng::fmin( m, n ) ); + acc += sink( feng::fmin( m, x ) ); + acc += sink( feng::fmin( x, m ) ); + acc += sink( feng::atan2( m, n ) ); + acc += sink( feng::atan2( m, x ) ); + acc += sink( feng::atan2( x, m ) ); + return acc; + } + + template < typename T > + T exercise_misc() + { + feng::matrix m{ 3, 3, T{ 1 } }; + T acc{}; + acc += sink( feng::abs( m ) ); + acc += sink( feng::clip( T{ 0 }, T{ 2 } )( m ) ); + acc += static_cast( sink( m.template astype() ) ); + acc += static_cast( sink( m.template astype() ) ); + m.apply( []( T& v ) { v += T{ 1 }; } ); + m.elementwise_apply( []( T& v ) { v += T{ 1 }; } ); + m.map( []( T& v ) { v += T{ 1 }; } ); + acc += sink( m ); + return acc; + } + + double exercise_complex() + { + using C = std::complex; + feng::matrix m{ 3, 3, C{ 1.0, 2.0 } }; + double acc{}; + acc += sink( feng::real( m ) ); + acc += sink( feng::imag( m ) ); + acc += sink( feng::abs( m ) ); + acc += sink( feng::arg( m ) ); + acc += sink( feng::norm( m ) ); + acc += std::real( sink( feng::conj( m ) ) ); + acc += std::real( sink( feng::proj( m ) ) ); + acc += std::real( sink( m.astype>() ) ); + m.apply( []( C& v ) { v *= 2.0; } ); + acc += std::real( sink( m ) ); + return acc; + } +} // namespace + +double api_elementwise_double() { return exercise_unary() + exercise_binary() + exercise_misc(); } +float api_elementwise_float() +{ + return static_cast( exercise_unary() + exercise_binary() ) + exercise_misc(); +} +double api_elementwise_complex() { return exercise_complex(); } +int api_elementwise_int() { return exercise_misc(); } diff --git a/tests/api/image.cc b/tests/api/image.cc new file mode 100644 index 0000000..cd0df86 --- /dev/null +++ b/tests/api/image.cc @@ -0,0 +1,57 @@ +// S1-R4 API family `image`: save_as_bmp, save_as_png, save_as_pgm, load_bmp, plot, colour maps. Compiled alone +// by `tools/check.sh api` and never run; file names point under build/tmp. +#include "../../matrix.hpp" + +#include +#include + +namespace +{ + template < typename T > + int exercise_image() + { + feng::matrix a{ 4, 4, T{ 1 } }; + std::string const base{ "build/tmp/api_image" }; + int acc = 0; + + // member writers, with the default and a named colour map + acc += a.save_as_bmp( base + "_m.bmp" ) ? 1 : 0; + acc += a.save_as_bmp( base + "_jet.bmp", "jet" ) ? 1 : 0; + acc += a.save_as_bmp( ( base + "_c.bmp" ).c_str() ) ? 1 : 0; + acc += a.save_as_png( base + "_m.png" ) ? 1 : 0; + acc += a.save_as_png( base + "_gray.png", "gray" ) ? 1 : 0; + acc += a.save_as_pgm( base + "_m.pgm" ) ? 1 : 0; + acc += a.save_as_pgm( ( base + "_c.pgm" ).c_str() ) ? 1 : 0; + acc += a.plot( base + "_plot.bmp" ) ? 1 : 0; + acc += a.plot( base + "_hot.bmp", "hot" ) ? 1 : 0; + + // free writers: one channel with a colour map, and three channels; each returns bool (S5-R4) + acc += feng::save_as_bmp( base + "_f.bmp", a ) ? 1 : 0; + acc += feng::save_as_bmp( base + "_fj.bmp", a, "jet" ) ? 1 : 0; + bool const rgb_ok = feng::save_as_bmp( base + "_rgb.bmp", a, a, a ); + acc += rgb_ok ? 1 : 0; + return acc; + } + + int exercise_load_and_maps() + { + int acc = 0; + auto const loaded = feng::load_bmp( "build/tmp/api_image_rgb.bmp" ); + if ( loaded ) + acc += static_cast( ( *loaded )[0].size() ); + + // colour maps: the named table and a custom map + auto const& maps = feng::matrix_details::bmp_details::color_maps; + auto const [r, g, b] = maps.at( "parula" )( 0.5 ); + acc += r + g + b; + auto const custom = feng::matrix_details::bmp_details::make_color_map( { 0.0, 1.0 }, { { 0, 0, 0 }, { 255, 255, 255 } } ); + auto const [cr, cg, cb] = custom( 0.25 ); + acc += cr + cg + cb; + return acc; + } +} // namespace + +int api_image_double() { return exercise_image() + exercise_load_and_maps(); } +int api_image_float() { return exercise_image(); } +int api_image_int() { return exercise_image(); } +int api_image_uint8() { return exercise_image(); } diff --git a/tests/api/io.cc b/tests/api/io.cc new file mode 100644 index 0000000..a7f84c5 --- /dev/null +++ b/tests/api/io.cc @@ -0,0 +1,75 @@ +// S1-R4 API family `io`: save_as_txt, load_txt, save_as_binary, load_binary, save_as_npy, load_npy, stream +// operators, disp/display. Compiled alone by `tools/check.sh api` and never run; file names point under build/tmp. +#include "../../matrix.hpp" + +#include +#include +#include + +namespace +{ + template < typename T > + T sink( feng::matrix const& m ) + { + return m.size() ? m[0][0] : T{}; + } + + template < typename T > + T exercise_files() + { + feng::matrix a{ 2, 3, T{ 1 } }; + feng::matrix b; + std::string const txt{ "build/tmp/api_io.txt" }; + std::string const bin{ "build/tmp/api_io.bin" }; + bool ok = true; + ok = a.save_as_txt( txt ) && ok; + ok = a.save_as_txt( txt.c_str() ) && ok; + ok = b.load_txt( txt ) && ok; + ok = b.load_txt( txt.c_str() ) && ok; + ok = a.save_as_binary( bin ) && ok; + ok = a.save_as_binary( bin.c_str() ) && ok; + ok = b.load_binary( bin ) && ok; + ok = b.load_binary( bin.c_str() ) && ok; + return ok ? sink( b ) : T{}; + } + + template < typename T > + T exercise_npy() + { + feng::matrix a{ 2, 3, T{ 1 } }; + feng::matrix b; + std::string const npy{ "build/tmp/api_io.npy" }; + bool ok = a.save_as_npy( npy ); + ok = a.save_as_npy( npy.c_str() ) && ok; + ok = b.load_npy( npy ) && ok; + ok = b.load_npy( npy.c_str() ) && ok; + return ok ? sink( b ) : T{}; + } + + template < typename T > + T exercise_streams() + { + feng::matrix a{ 2, 2, T{ 1 } }; + std::ostringstream os; + os << a; + feng::matrix b{ 2, 2 }; + std::istringstream is{ os.str() }; + is >> b; + feng::display( a ); + feng::disp( a ); + return sink( b ); + } + + template < typename T > + T exercise_io() + { + return exercise_files() + exercise_streams(); + } +} // namespace + +double api_io_double() { return exercise_io() + exercise_npy(); } +float api_io_float() { return exercise_io() + exercise_npy(); } +std::complex api_io_complex() { + return exercise_io>() + exercise_npy>(); +} +int api_io_int() { return exercise_io() + exercise_npy(); } diff --git a/tests/api/linalg.cc b/tests/api/linalg.cc new file mode 100644 index 0000000..2bbe9d2 --- /dev/null +++ b/tests/api/linalg.cc @@ -0,0 +1,206 @@ +// S1-R4 API family `linalg`: det, inverse/inv, lu_factor (S7-R1), svd_factor and status pinverse (S7-R3), solve, try_inverse, lu_decomposition, lu_solver, svd, pinv/pinverse, +// cholesky_factor, row_echelon and status expm (S7-R4, S7-R5), cholesky_decomposition, rref, gauss_jordan_elimination, householder, forward/backward_substitution, expm, eigen_*, +// conjugate gradient solvers, the S9-R5 to_expected / load_expected adapters. Compiled alone by `tools/check.sh api`; calling each API instantiates its template. +#include "../../matrix.hpp" + +#include +#include +#include + +namespace +{ + template < typename M > + auto sink( M const& m ) -> typename M::value_type + { + return m.size() ? m[0][0] : typename M::value_type{}; + } + + template < typename T > + T exercise_inverse() + { + // determinant and inverse: real and complex + feng::matrix const a = feng::eye( 3, 3 ); + T acc{}; + acc += a.det(); + acc += feng::det( a ); + acc += sink( a.inverse() ); + acc += sink( feng::inverse( a ) ); + acc += sink( feng::inv( a ) ); + acc += sink( feng::expm( a ) ); + + // S7-R1, S7-R2: the LU object, status results and the status overload of inverse + feng::matrix const b{ 3, 2, T{ 1 } }; + auto const f = feng::lu_factor( a ); + acc += static_cast( f.rank() ) + static_cast( f.pivots()[0] ) + f.det(); + acc += f.status() == feng::linalg_status::ok ? T{ 1 } : T{ 0 }; + acc += sink( f.l() ) + sink( f.u() ) + sink( f.p() ); + if ( auto const r = f.solve( b ); r ) + acc += sink( r.value ); + if ( auto const r = f.inverse(); r.ok() ) + acc += sink( r.value ); + if ( auto const r = feng::solve( a, b ); r.status == feng::linalg_status::ok ) + acc += sink( r.value ); + if ( auto const r = feng::try_inverse( a ); r ) + acc += sink( r.value ); + feng::matrix out; + if ( feng::inverse( a, out ) == feng::linalg_status::ok ) + acc += sink( out ); + + // S7-R4, S7-R5: the Cholesky object, row_echelon and the status overload of expm + auto const c = feng::cholesky_factor( a ); + acc += c.status() == feng::linalg_status::ok && c.ok() ? sink( c.l() ) : T{ 0 }; + auto const e = feng::row_echelon( b ); + acc += sink( e.r ) + static_cast( static_cast( e.rank + e.pivot_columns.size() ) ); + acc += e.status == feng::linalg_status::ok ? T{ 1 } : T{ 0 }; + if ( auto const r = feng::rref( b ); r ) + acc += sink( *r ); + if ( feng::expm( a, out ) == feng::linalg_status::ok ) + acc += sink( out ); + acc += static_cast( static_cast( feng::cholesky_decomposition( a, out ) ) ); +#if defined( __cpp_lib_expected ) + // S9-R5: std::expected adapters over the status results and the loaders + if ( auto const x = feng::to_expected( feng::try_inverse( a ) ); x ) + acc += sink( *x ); + if ( auto const x = feng::to_expected( f.solve( b ) ); x ) + acc += sink( *x ); + if ( auto const x = feng::to_expected( f.inverse() ); x ) + acc += sink( *x ); + if ( auto const x = feng::to_expected( feng::solve( a, b ) ); x ) + acc += sink( *x ); + if ( auto const x = feng::to_expected( feng::lu_factor( a ) ); x ) + acc += x->det(); + if ( auto const x = feng::to_expected( feng::svd_factor( a ) ); x ) + acc += sink( x->u() ); + if ( auto const x = feng::to_expected( feng::cholesky_factor( a ) ); x ) + acc += sink( x->l() ); + if ( auto const x = feng::to_expected( feng::row_echelon( b ) ); x ) + acc += sink( x->r ); + for ( auto const fmt : { feng::io_format::txt, feng::io_format::binary, feng::io_format::npy } ) + if ( auto const x = feng::load_expected( "missing.npy", fmt ); x ) + acc += sink( *x ); +#endif + return acc; + } + + template < typename T > + T exercise_svd() + { + // S7-R3: the SVD object, the status pinverse and the legacy SVD and pseudoinverse spellings + feng::matrix const a = feng::eye( 3, 2 ); + feng::matrix u, w, v, x; + T acc{}; + auto const f = feng::svd_factor( a ); + auto const g = feng::svd_factor( a, 8 ); + acc += sink( f.u() ) + sink( f.v() ) + T( f.s()[0] ) + T( static_cast( f.sweeps() + f.rank() + g.rank( 0.5 ) ) ); + acc += f.status() == feng::linalg_status::ok ? T{ 1 } : T{ 0 }; + if ( auto const r = f.pinverse(); r ) + acc += sink( r.value ); + if ( auto const r = f.pinverse( 1.0e-3 ); r.ok() ) + acc += sink( r.value ); +#if defined( __cpp_lib_expected ) + // S9-R5: std::expected adapters over the SVD object and its pinverse + if ( auto const x = feng::to_expected( f ); x ) + acc += sink( x->v() ); + if ( auto const x = feng::to_expected( f.pinverse() ); x ) + acc += sink( *x ); +#endif + if ( feng::pinverse( a, x ) == feng::linalg_status::ok ) + acc += sink( x ); + if ( feng::pinverse( a, x, 1.0e-3 ) == feng::linalg_status::ok ) + acc += sink( x ); + acc += static_cast( static_cast( feng::singular_value_decomposition( a, u, w, v ) ) ); + acc += static_cast( static_cast( feng::singular_value_decomposition( a, u, w, v, 16 ) ) ); + if ( auto const s = feng::singular_value_decomposition( a ); s ) + acc += sink( std::get<0>( *s ) ); + if ( auto const s = feng::svd( a ); s ) + acc += sink( std::get<1>( *s ) ); + acc += sink( feng::svd_inverse( a ) ); + acc += sink( feng::pinverse( a ) ) + sink( feng::pinverse( a, 1.0e-3 ) ); + acc += sink( feng::pinv( a ) ) + sink( feng::pinv( a, 1.0e-3 ) ); + return acc; + } + + template < typename T > + T exercise_real() + { + feng::matrix const a = feng::eye( 3, 3 ); + feng::matrix const b{ 3, 1, T{ 1 } }; + feng::matrix x, l, u, w, v, q, d; + T acc{}; + + // LU + acc += static_cast( feng::lu_decomposition( a, l, u ) ); + if ( auto const lu = feng::lu_decomposition( a ); lu ) + acc += sink( std::get<0>( *lu ) ) + sink( std::get<1>( *lu ) ); + acc += static_cast( feng::lu_solver( a, x, b ) ); + if ( auto const s = feng::lu_solver( a, b ); s ) + acc += sink( *s ); + + // substitution, Cholesky, Householder, row reduction + acc += static_cast( feng::forward_substitution( a, x, b ) ); + acc += static_cast( feng::backward_substitution( a, x, b ) ); + acc += static_cast( feng::cholesky_decomposition( a, l ) ); + feng::householder( a, q, d ); + acc += sink( l ) + sink( q ) + sink( d ); + if ( auto const r = feng::rref( a ); r ) + acc += sink( *r ); + if ( auto const g = feng::gauss_jordan_elimination( a ); g ) + acc += sink( *g ); + + // SVD and pseudo-inverse + acc += static_cast( feng::singular_value_decomposition( a, u, w, v ) ); + if ( auto const s = feng::singular_value_decomposition( a ); s ) + acc += sink( std::get<0>( *s ) ); + if ( auto const s = feng::svd( a ); s ) + acc += sink( std::get<1>( *s ) ); + acc += sink( feng::svd_inverse( a ) ); + acc += sink( feng::pinverse( a ) ); + acc += sink( feng::pinv( a ) ); + + // conjugate gradient solvers + acc += static_cast( feng::conjugate_gradient_squared( a, x, b ) ); + acc += static_cast( feng::cgs( a, x, b ) ); + acc += static_cast( feng::biconjugate_gradient_stabilized_method( a, x, b ) ); + acc += static_cast( feng::bicgstab( a, x, b ) ); + + // eigen solvers + std::vector lv; + std::valarray la; + feng::matrix lm; + acc += static_cast( feng::eigen_jacobi( a, v, lv ) ); + acc += static_cast( feng::eigen_jacobi( a, v, la ) ); + acc += static_cast( feng::eigen_jacobi( a, v, lm ) ); + acc += static_cast( feng::cyclic_eigen_jacobi( a, v, lv ) ); + acc += static_cast( feng::cyclic_eigen_jacobi( a, v, la ) ); + acc += static_cast( feng::cyclic_eigen_jacobi( a, v, lm ) ); + feng::eigen_real_symmetric( a, v, lv ); + feng::eigen_real_symmetric( a, v, la ); + feng::eigen_real_symmetric( a, v, lm ); + acc += feng::eigen_power_iteration( a ); + acc += feng::eigen_power_iteration( a, x.begin() ); + return acc; + } + + template < typename T > + T exercise_hermitian() + { + using C = std::complex; + feng::matrix const a = feng::eye( 3, 3 ); + feng::matrix v, x{ 3, 1 }; + std::vector lv; + std::valarray la; + feng::matrix lm; + feng::eigen_hermitian( a, v, lv ); + feng::eigen_hermitian( a, v, la ); + feng::eigen_hermitian( a, v, lm ); + T acc = std::real( sink( v ) ); + acc += feng::eigen_power_iteration( a ); + acc += feng::eigen_power_iteration( a, x.begin() ); + return acc; + } +} // namespace + +double api_linalg_double() { return exercise_inverse() + exercise_svd() + exercise_real() + exercise_hermitian(); } +float api_linalg_float() { return exercise_inverse() + exercise_svd() + exercise_real(); } +std::complex api_linalg_complex() { return exercise_inverse>() + exercise_svd>(); } +std::complex api_linalg_complex_float() { return exercise_svd>(); } diff --git a/tests/api/link_a.cc b/tests/api/link_a.cc new file mode 100644 index 0000000..92a461c --- /dev/null +++ b/tests/api/link_a.cc @@ -0,0 +1,33 @@ +// S1-R4 link pair, part A (with main): instantiates the same matrix templates as link_b.cc so the two objects +// carry the same inline and template definitions; linking them checks the header for ODR and duplicate-symbol +// defects. Exits 0 when both parts compute the same results. +#include "../../matrix.hpp" + +#include + +double link_b_double(); +std::complex link_b_complex(); + +namespace +{ + template < typename T > + T compute() + { + feng::matrix a = feng::eye( 4, 4 ); + a = a * T{ 2 } + a; + auto const p = a ^ 3; + auto const blk = p.clone( { 0, 2 }, { 0, 2 } ); + return blk[1][1] + p[3][3]; + } +} // namespace + +int main() +{ + if ( compute() != link_b_double() ) + return 1; + if ( compute>() != link_b_complex() ) + return 2; + if ( compute() != 54.0 ) + return 3; + return 0; +} diff --git a/tests/api/link_b.cc b/tests/api/link_b.cc new file mode 100644 index 0000000..4d195cc --- /dev/null +++ b/tests/api/link_b.cc @@ -0,0 +1,20 @@ +// S1-R4 link pair, part B: the same templates as link_a.cc, in a second translation unit. +#include "../../matrix.hpp" + +#include + +namespace +{ + template < typename T > + T compute() + { + feng::matrix a = feng::eye( 4, 4 ); + a = a * T{ 2 } + a; + auto const p = a ^ 3; + auto const blk = p.clone( { 0, 2 }, { 0, 2 } ); + return blk[1][1] + p[3][3]; + } +} // namespace + +double link_b_double() { return compute(); } +std::complex link_b_complex() { return compute>(); } diff --git a/tests/api/reductions.cc b/tests/api/reductions.cc new file mode 100644 index 0000000..382407e --- /dev/null +++ b/tests/api/reductions.cc @@ -0,0 +1,110 @@ +// S1-R4 API family `reductions`: sum, mean, variance, standard_deviation, min, max, minmax, norm, norm_1, norm_2, tr, +// is_* predicates, isequal. Compiled alone by `tools/check.sh api`; calling each API instantiates its template. +#include "../../matrix.hpp" + +#include + +namespace +{ + template < typename T > + T exercise_sums() + { + // sum, norm_1, tr: every element type + feng::matrix const m{ 3, 3, T{ 2 } }; + T acc{}; + acc += feng::sum( m ); + acc += static_cast( feng::norm_1( m ) ); + acc += feng::tr( m ); + acc += m.tr(); + return acc; + } + + template < typename T > + T exercise_statistics() + { + feng::matrix const m{ 3, 3, T{ 2 } }; + T acc{}; + acc += feng::variance( m ); + acc += feng::standard_deviation( m ); + acc += feng::norm_2( m ); + return acc; + } + + template < typename T > + T exercise_ordered() + { + // min, max, minmax, mean, ordering operators: ordered element types + feng::matrix const m{ 3, 3, T{ 2 } }; + auto const less = []( T const& x, T const& y ) { return x < y; }; + T acc{}; + acc += feng::min( m ); + acc += feng::max( m ); + acc += feng::mean( m ); // complex mean compiles since S6 (returns std::complex) + acc += m.min(); + acc += m.max(); + acc += m.min( less ); + acc += m.max( less ); + auto const [lo, hi] = m.minmax(); + auto const [lo2, hi2] = m.minmax( less ); + acc += lo + hi + lo2 + hi2; + feng::matrix const n{ m }; + acc += static_cast( ( m < n ) + ( m > n ) + ( m <= n ) + ( m >= n ) ); + return acc; + } + + template < typename T > + int exercise_predicates() + { + feng::matrix const m = feng::eye( 3, 3 ); + feng::matrix const n{ m }; + int acc{}; + acc += feng::is_column( m ) + feng::is_column_matrix( m ) + feng::iscolumn( m ); + acc += feng::is_row( m ) + feng::is_row_matrix( m ) + feng::isrow( m ); + acc += feng::is_empty( m ) + feng::is_empty_matrix( m ) + feng::isempty( m ); + acc += feng::is_equal( m, n ) + feng::is_equal( m, n, m ); + acc += feng::isequal( m, n ) + feng::isequal( m, n, m ); + acc += feng::is_symmetric( m ); + acc += feng::is_symmetric( m, []( T const& x, T const& y ) { return x == y; } ); + acc += feng::is_orthogonal( m ); + acc += feng::is_orthogonal( m, []( T const& v ) { return v == T{}; } ); + acc += ( m == n ); + return acc; + } + + template < typename T > + int exercise_real_predicates() + { + feng::matrix const m = feng::eye( 3, 3 ); + int acc{}; + acc += feng::is_positive_definite( m ); + acc += feng::is_inf( m )[0][0] + feng::isinf( m )[0][0]; + acc += feng::is_nan( m )[0][0] + feng::isnan( m )[0][0]; + return acc; + } + + double exercise_complex() + { + feng::matrix> const m{ 3, 3, std::complex{ 1.0, 1.0 } }; + double acc{}; + acc += feng::norm_1( m ); + acc += feng::norm( m )[0][0]; + acc += std::real( feng::sum( m ) + feng::tr( m ) ); + return acc; + } +} // namespace + +double api_reductions_double() +{ + return exercise_sums() + exercise_statistics() + exercise_ordered() + + exercise_predicates() + exercise_real_predicates() + exercise_complex(); +} +float api_reductions_float() +{ + return exercise_sums() + exercise_statistics() + exercise_ordered() + + static_cast( exercise_predicates() + exercise_real_predicates() ); +} +std::complex api_reductions_complex() +{ + return exercise_sums>() + static_cast( exercise_predicates>() ); +} +int api_reductions_int() { return exercise_sums() + exercise_ordered() + exercise_predicates(); } diff --git a/tests/api/shape.cc b/tests/api/shape.cc new file mode 100644 index 0000000..a13db6d --- /dev/null +++ b/tests/api/shape.cc @@ -0,0 +1,52 @@ +// S1-R4 API family `shape`: reshape, resize, transpose, ctranspose, fliplr, flipud, flipdim, tril, triu, clear, swap, +// shrink_to_size. Compiled alone by `tools/check.sh api`; calling each API instantiates its template. +#include "../../matrix.hpp" + +#include + +namespace +{ + template < typename M > + auto sink( M const& m ) -> typename M::value_type + { + return m.size() ? m[0][0] : typename M::value_type{}; + } + + template < typename T > + T exercise_shape() + { + feng::matrix m{ 4, 6, T{ 1 } }; + T acc{}; + + acc += sink( m.reshape( 6, 4 ) ); + acc += sink( m.resize( 3, 8 ) ); + acc += sink( m.resize( 5, 5 ) ); + acc += sink( m.shrink_to_size( 3, 3 ) ); + acc += sink( m.transpose() ); + acc += sink( feng::transpose( m ) ); + acc += sink( feng::fliplr( m ) ); + acc += sink( feng::flipud( m ) ); + acc += sink( feng::flipdim( m, 1 ) ); + acc += sink( feng::flipdim( m, 2 ) ); + acc += sink( feng::tril( m ) ); + acc += sink( feng::triu( m ) ); + + feng::matrix other{ 2, 2, T{ 2 } }; + m.swap( other ); + acc += sink( m ) + sink( other ); + m.clear(); + acc += static_cast( static_cast( m.size() ) ); + return acc; + } + + std::complex exercise_ctranspose() + { + feng::matrix> const m{ 2, 3, std::complex{ 1.0, 2.0 } }; + return sink( feng::ctranspose( m ) ); + } +} // namespace + +double api_shape_double() { return exercise_shape(); } +float api_shape_float() { return exercise_shape(); } +std::complex api_shape_complex() { return exercise_shape>() + exercise_ctranspose(); } +int api_shape_int() { return exercise_shape(); } diff --git a/tests/api/signal.cc b/tests/api/signal.cc new file mode 100644 index 0000000..f68ec44 --- /dev/null +++ b/tests/api/signal.cc @@ -0,0 +1,66 @@ +// S1-R4 API family `signal`: conv, conv2, fft, ifft, fftshift, ifftshift, pooling. +// Compiled alone by `tools/check.sh api`; calling each API instantiates its template. +#include "../../matrix.hpp" + +#include +#include + +namespace +{ + template < typename M > + auto sink( M const& m ) -> typename M::value_type + { + return m.size() ? m[0][0] : typename M::value_type{}; + } + + template < typename T > + T exercise_conv() + { + feng::matrix const a{ 5, 6, T{ 1 } }; + feng::matrix const k{ 3, 3, T{ 1 } }; + T acc{}; + acc += sink( feng::conv( a, k ) ); + acc += sink( feng::conv( a, k, std::string{ "same" } ) ); + acc += sink( feng::conv( a, k, std::string{ "valid" } ) ); + acc += sink( feng::conv2( a, k ) ); + acc += sink( feng::conv2( a, k, std::string{ "same" } ) ); + return acc; + } + + template < typename T > + T exercise_pooling() + { + feng::matrix const a{ 4, 6, T{ 1 } }; + T acc{}; + acc += sink( feng::pooling( a, 2, 3 ) ); + acc += sink( feng::pooling( a, 2, 2, "max" ) ); + acc += sink( feng::pooling( a, 2, 2, "min" ) ); + acc += sink( feng::pooling( a, 2 ) ); + acc += sink( feng::pooling( a, 2, "max" ) ); + return acc; + } + + // fft, ifft and the shifts on every element type of D-031: float, double, long double, int and complex + template < typename T > + std::complex exercise_fft() + { + feng::matrix const a{ 4, 4, T{ 1 } }; + std::complex acc{}; + acc += sink( feng::fft( a ) ); + acc += sink( feng::ifft( a ) ); + acc += sink( feng::ifft( feng::fft( a ) ) ); + acc += sink( feng::fftshift( a ) ); + acc += sink( feng::ifftshift( a ) ); + return acc; + } +} // namespace + +double api_signal_double() { return exercise_conv() + exercise_pooling(); } +float api_signal_float() { return exercise_conv() + exercise_pooling(); } +std::complex api_signal_complex() +{ + return exercise_conv>() + exercise_fft() + exercise_fft>() + + exercise_fft() + exercise_fft() + exercise_fft() + + exercise_fft>(); +} +int api_signal_int() { return exercise_conv() + exercise_pooling(); } diff --git a/tests/api/views.cc b/tests/api/views.cc new file mode 100644 index 0000000..4f10c6c --- /dev/null +++ b/tests/api/views.cc @@ -0,0 +1,58 @@ +// S1-R4 API family `views`: make_view, matrix_view, every clone overload, copy, slicing. +// Compiled alone by `tools/check.sh api`; calling each API instantiates its template. +#include "../../matrix.hpp" + +#include +#include +#include + +namespace +{ + template < typename T > + T exercise_views() + { + using size_type = typename feng::matrix::size_type; + feng::matrix m{ 5, 6, T{ 1 } }; + feng::matrix const& cm = m; + T acc{}; + + // make_view and matrix_view, and the view's read API + auto v = feng::make_view( cm, { 1UL, 4UL }, { 2UL, 5UL } ); + feng::matrix_view> w{ cm, std::make_pair( size_type{ 0 }, size_type{ 2 } ), + std::make_pair( size_type{ 1 }, size_type{ 3 } ) }; + auto const [vr, vc] = v.shape(); + acc += static_cast( static_cast( vr + vc + v.row() + v.col() + v.size() + w.size() ) ); + acc += v[0][0]; + acc += v( 1, 1 ); + acc += *v.row_begin( 0 ); + acc += *v.row_cbegin( 1 ); + acc += w[1][1]; + + // every clone overload + feng::matrix a; + a.clone( cm, { 1, 3 }, { 0, 2 } ); + a.clone( cm, 1, 3, 0, 2 ); + auto const b = cm.clone( { 0, 2 }, { 1, 4 } ); + auto const c = cm.clone( 0, 2, 1, 4 ); + acc += a[0][0] + b[0][0] + c[0][0]; + + // copy, whole and into a block + feng::matrix d; + d.copy( cm ); + d.copy( a, { 0, 2 }, { 0, 2 } ); + acc += d[0][0]; + + // slicing constructors + typename feng::matrix::range_type const rr{ 1, 3 }, rc{ 2, 4 }; + feng::matrix const s1{ cm, { 1, 3 }, { 2, 4 } }; + feng::matrix const s2{ cm, rr, rc }; + feng::matrix const s3{ cm, 1, 3, 2, 4 }; + acc += s1[0][0] + s2[0][0] + s3[0][0]; + return acc; + } +} // namespace + +double api_views_double() { return exercise_views(); } +float api_views_float() { return exercise_views(); } +std::complex api_views_complex() { return exercise_views>(); } +int api_views_int() { return exercise_views(); } diff --git a/tests/cases/load_npy.hpp b/tests/cases/load_npy.hpp index c1b3fe9..9d0ed8e 100644 --- a/tests/cases/load_npy.hpp +++ b/tests/cases/load_npy.hpp @@ -3,13 +3,13 @@ TEST_CASE( "Loading npy files", "[load_npy]" ) { { feng::matrix m; - m.load_npy( "./images/u8.npy" ); + (void)m.load_npy( "./images/u8.npy" ); REQUIRE( m[0][0] == 4 ); REQUIRE( m[0][1] == 1 ); REQUIRE( m[0][2] == 8 ); REQUIRE( m[1][0] == 9 ); REQUIRE( m[1][1] == 1 ); REQUIRE( m[1][2] == 5 ); } { feng::matrix m; - m.load_npy( "./images/8.npy" ); + (void)m.load_npy( "./images/8.npy" ); REQUIRE( m[0][0] == 4 ); REQUIRE( m[0][1] == 1 ); REQUIRE( m[0][2] == 8 ); REQUIRE( m[1][0] == 9 ); REQUIRE( m[1][1] == 1 ); REQUIRE( m[1][2] == 5 ); } @@ -17,13 +17,13 @@ TEST_CASE( "Loading npy files", "[load_npy]" ) // [9.510697 , 1.8137231, 5.7381544]] { feng::matrix m; - m.load_npy( "./images/32.npy" ); + (void)m.load_npy( "./images/32.npy" ); REQUIRE( std::abs(4.815519-m[0][0]) < 1.0e-5 ); REQUIRE( std::abs(1.0601262-m[0][1]) < 1.0e-5 ); REQUIRE( std::abs(8.989337-m[0][2]) < 1.0e-5 ); REQUIRE( std::abs(9.510697-m[1][0]) < 1.0e-5 ); REQUIRE( std::abs(1.8137231-m[1][1]) < 1.0e-5 ); REQUIRE( std::abs(5.7381544-m[1][2]) < 1.0e-5 ); } { feng::matrix m; - m.load_npy( "./images/64.npy" ); + (void)m.load_npy( "./images/64.npy" ); REQUIRE( std::abs(4.815519-m[0][0]) < 1.0e-5 ); REQUIRE( std::abs(1.0601262-m[0][1]) < 1.0e-5 ); REQUIRE( std::abs(8.989337-m[0][2]) < 1.0e-5 ); REQUIRE( std::abs(9.510697-m[1][0]) < 1.0e-5 ); REQUIRE( std::abs(1.8137231-m[1][1]) < 1.0e-5 ); REQUIRE( std::abs(5.7381544-m[1][2]) < 1.0e-5 ); } diff --git a/tests/cases/opencv.hpp b/tests/cases/opencv.hpp index 5e383f0..68293fd 100644 --- a/tests/cases/opencv.hpp +++ b/tests/cases/opencv.hpp @@ -1,4 +1,4 @@ -#ifdef OPENCV +#ifdef FENG_MATRIX_OPENCV TEST_CASE( "From OPENCV", "[from_opencv]" ) { diff --git a/tests/cases/s10_r3.hpp b/tests/cases/s10_r3.hpp new file mode 100644 index 0000000..e7246cb --- /dev/null +++ b/tests/cases/s10_r3.hpp @@ -0,0 +1,303 @@ +// S10-R3 (PR-13): the cache-blocked GEMM kernel behind operator*= gives results bit-identical to the strided +// inner_product kernel it replaced (matrix_details::gemm_reference), for every element type and shape, and its row +// split over forced worker counts equals the 1-worker run. +#include +#include +#include + +namespace s10_r3 +{ + template < typename T > + T value( std::size_t i, int salt ) + { + if constexpr ( std::is_same_v< T, int > ) + return static_cast< int >( ( i * 7 + static_cast< std::size_t >( salt ) * 3 ) % 11 ) - 5; + else if constexpr ( std::is_same_v< T, std::complex< double > > ) + return { 0.1 * static_cast< double >( ( i + static_cast< std::size_t >( salt ) ) % 13 ) - 0.37 * static_cast< double >( i % 7 ), + 1.0 / 3.0 + 1e-3 * static_cast< double >( i ) - 0.21 * static_cast< double >( ( i * 5 ) % 9 ) }; + else + return static_cast< T >( 0.1 * static_cast< double >( ( i + static_cast< std::size_t >( salt ) ) % 13 ) - 0.37 * static_cast< double >( i % 7 ) + 1e-3 * static_cast< double >( i ) ); + } + + template < typename T > + feng::matrix< T > sample( std::size_t r, std::size_t c, int salt ) + { + feng::matrix< T > m( r, c ); + for ( std::size_t i = 0; i != m.size(); ++i ) + m.data()[i] = value< T >( i, salt ); + return m; + } + + template < typename T > + bool same_bits( feng::matrix< T > const& x, feng::matrix< T > const& y ) + { + if ( x.row() != y.row() || x.col() != y.col() ) return false; + if ( x.size() == 0 ) return true; + return std::memcmp( x.data(), y.data(), x.size() * sizeof( T ) ) == 0; + } + + template < typename T > + void check( std::size_t m, std::size_t k, std::size_t n ) + { + INFO( m << "x" << k << " * " << k << "x" << n ); + auto const a = sample< T >( m, k, 1 ); + auto const b = sample< T >( k, n, 2 ); + auto const ref = feng::matrix_details::gemm_reference( a, b ); + REQUIRE( ref.row() == m ); + REQUIRE( ref.col() == n ); + auto c = a; + c *= b; + REQUIRE( same_bits( c, ref ) ); + REQUIRE( same_bits( a * b, ref ) ); + auto d = a; + d.direct_multiply( b ); + REQUIRE( same_bits( d, ref ) ); + } + + template < typename T > + void check_all_shapes() + { + check< T >( 0, 4, 3 ); + check< T >( 0, 0, 0 ); + check< T >( 3, 0, 5 ); + check< T >( 1, 1, 1 ); + check< T >( 1, 9, 1 ); + check< T >( 1, 1, 9 ); + check< T >( 1, 7, 9 ); + check< T >( 9, 7, 1 ); + check< T >( 16, 16, 16 ); + check< T >( 17, 17, 17 ); + check< T >( 18, 18, 18 ); + check< T >( 33, 65, 31 ); + check< T >( 300, 7, 5 ); + check< T >( 5, 7, 300 ); + check< T >( 131, 131, 1 ); + check< T >( 70, 140, 270 ); + } + + template < typename T > + void check_self_square( std::size_t n ) + { + auto a = sample< T >( n, n, 3 ); + auto const ref = feng::matrix_details::gemm_reference( a, a ); + a *= a; + REQUIRE( same_bits( a, ref ) ); + } + + template < typename T > + void check_workers( std::size_t m, std::size_t k, std::size_t n ) + { + INFO( m << "x" << k << " * " << k << "x" << n ); + auto const a = sample< T >( m, k, 4 ); + auto const b = sample< T >( k, n, 5 ); + feng::matrix< T > one( m, n ); + feng::matrix_details::gemm_blocked( a.data(), b.data(), one.data(), m, k, n, 1 ); + REQUIRE( same_bits( one, feng::matrix_details::gemm_reference( a, b ) ) ); + for ( std::size_t w : { std::size_t{ 2 }, std::size_t{ 3 }, std::size_t{ 7 } } ) + { + INFO( "workers " << w ); + feng::matrix< T > c( m, n ); + feng::matrix_details::gemm_blocked( a.data(), b.data(), c.data(), m, k, n, w ); + REQUIRE( same_bits( c, one ) ); + } + } +} + +TEST_CASE( "S10-R3 blocked GEMM equals the reference kernel", "[S10][S10-R3]" ) +{ + SECTION( "int" ) { s10_r3::check_all_shapes< int >(); } + SECTION( "float" ) { s10_r3::check_all_shapes< float >(); } + SECTION( "double" ) { s10_r3::check_all_shapes< double >(); } + SECTION( "complex" ) { s10_r3::check_all_shapes< std::complex< double > >(); } + SECTION( "a *= a" ) + { + s10_r3::check_self_square< double >( 1 ); + s10_r3::check_self_square< double >( 18 ); + s10_r3::check_self_square< std::complex< double > >( 33 ); + s10_r3::check_self_square< int >( 64 ); + } + SECTION( "forced worker counts equal the 1-worker run" ) + { + s10_r3::check_workers< double >( 37, 29, 41 ); + s10_r3::check_workers< double >( 2, 5, 3 ); + s10_r3::check_workers< double >( 50, 50, 1 ); + s10_r3::check_workers< float >( 0, 3, 4 ); + s10_r3::check_workers< std::complex< double > >( 23, 19, 17 ); + s10_r3::check_workers< int >( 13, 11, 9 ); + } +} + +// S10-R3 (D-008, par-thresholds): the default worker counts are work-based (matrix_details::work_workers); every +// thresholded path equals its serial reference. Elementwise ops visit each index once, so any split gives the +// 1-worker bits; reductions keep reduce_range's chunked fold with the worker count work_workers picks. +namespace s10_r3 +{ + template < typename T > + feng::matrix< T > serial_add( feng::matrix< T > const& a, feng::matrix< T > const& b ) + { + feng::matrix< T > c = a; + T* x = c.data(); + T const* y = b.data(); + feng::matrix_details::parallel_workers( [x, y]( std::size_t i ) { x[i] += y[i]; }, std::size_t{ 0 }, c.size(), 1 ); + return c; + } + + template < typename T > + void check_elementwise( std::size_t r, std::size_t c ) + { + INFO( "elementwise " << r << "x" << c ); + auto const a = sample< T >( r, c, 6 ); + auto const b = sample< T >( r, c, 7 ); + auto const ref_add = serial_add( a, b ); + REQUIRE( same_bits( a + b, ref_add ) ); + auto s = a; + s += b; + REQUIRE( same_bits( s, ref_add ) ); + + feng::matrix< T > ref_minus = a; + for ( std::size_t i = 0; i != ref_minus.size(); ++i ) ref_minus.data()[i] -= b.data()[i]; + auto m = a; + m -= b; + REQUIRE( same_bits( m, ref_minus ) ); + + feng::matrix< T > ref_neg = a; + for ( std::size_t i = 0; i != ref_neg.size(); ++i ) ref_neg.data()[i] = -ref_neg.data()[i]; + REQUIRE( same_bits( -a, ref_neg ) ); + + auto const twice = []( T& v ) { v = v + v; }; + feng::matrix< T > ref_apply = a; + for ( std::size_t i = 0; i != ref_apply.size(); ++i ) twice( ref_apply.data()[i] ); + auto ap = a; + ap.apply( twice ); + REQUIRE( same_bits( ap, ref_apply ) ); + + // for_each (behind transform and the unary maps) + auto const f = []( T const& x ) { return x * x - x; }; + feng::matrix< T > ref_map( r, c ); + for ( std::size_t i = 0; i != a.size(); ++i ) ref_map.data()[i] = f( a.data()[i] ); + REQUIRE( same_bits( feng::matrix_details::transform( a, f ), ref_map ) ); + + // row-wise copy into a larger matrix and clone back out + feng::matrix< T > big( r + 2, c + 3 ); + for ( std::size_t i = 0; i != big.size(); ++i ) big.data()[i] = value< T >( i, 8 ); + feng::matrix< T > ref_big = big; + for ( std::size_t i = 0; i != r; ++i ) + for ( std::size_t j = 0; j != c; ++j ) + ref_big[i + 1][j + 2] = a[i][j]; + big.copy( a, { 1, 1 + r }, { 2, 2 + c } ); + REQUIRE( same_bits( big, ref_big ) ); + if ( r != 0 && c != 0 ) // clone rejects an empty range + { + feng::matrix< T > cl; + cl.clone( big, 1, 1 + r, 2, 2 + c ); + REQUIRE( same_bits( cl, a ) ); + } + } + + template < typename T > + void check_reduce( std::size_t n ) + { + INFO( "reduce n = " << n ); + feng::matrix< T > m( 1, n ); + for ( std::size_t i = 0; i != n; ++i ) m.data()[i] = value< T >( i, 9 ); + auto const plus = []( T const& x, T const& y ) { return x + y; }; + std::size_t const w = feng::matrix_details::work_workers( n, feng::matrix_details::reduce_grain ); + auto const at = [&m]( std::size_t i ) noexcept -> T const& { return m.data()[i]; }; + T const chunked = feng::matrix_details::reduce_range< T >( at, n, T{}, plus, w ); + T serial{}; + for ( std::size_t i = 0; i != n; ++i ) serial = plus( serial, m.data()[i] ); + if ( w == 1 ) REQUIRE( std::memcmp( &chunked, &serial, sizeof( T ) ) == 0 ); + T const by_iter = feng::matrix_details::reduce( m.begin(), m.end(), T{}, plus ); + T const by_impl = feng::matrix_details::reduce_impl_private::reduce_impl( m )( plus, T{} ); + T const by_sum = feng::sum( m ); + REQUIRE( std::memcmp( &by_iter, &chunked, sizeof( T ) ) == 0 ); + REQUIRE( std::memcmp( &by_impl, &chunked, sizeof( T ) ) == 0 ); + REQUIRE( std::memcmp( &by_sum, &chunked, sizeof( T ) ) == 0 ); + } + + template < typename T > + void check_thresholded() + { + for ( auto [r, c] : { std::pair< std::size_t, std::size_t >{ 0, 0 }, { 0, 5 }, { 1, 1 }, { 16, 16 }, { 17, 17 }, { 18, 18 }, + { 70000, 5 }, { 5, 70000 } } ) + check_elementwise< T >( r, c ); + for ( std::size_t n : { std::size_t{ 0 }, std::size_t{ 1 }, std::size_t{ 16 }, std::size_t{ 17 }, std::size_t{ 18 }, + std::size_t{ 1000 }, feng::matrix_details::reduce_grain, 3 * feng::matrix_details::reduce_grain + 7 } ) + check_reduce< T >( n ); + check< T >( 16, 16, 16 ); + check< T >( 17, 17, 17 ); + check< T >( 18, 18, 18 ); + check< T >( 1, 1, 1 ); + check< T >( 0, 4, 3 ); + check< T >( 400, 64, 64 ); // tall: above the GEMM grain + check< T >( 64, 64, 400 ); // wide + } +} + +TEST_CASE( "S10-R3 thresholded parallel helpers equal the serial path", "[S10][S10-R3]" ) +{ + SECTION( "work_workers: 1 for little work or in serial builds, else min( cores, work / grain )" ) + { + using feng::matrix_details::work_workers; + REQUIRE( work_workers( 0, 1024 ) == 1 ); + REQUIRE( work_workers( 16, feng::matrix_details::elementwise_grain ) == 1 ); + REQUIRE( work_workers( feng::matrix_details::elementwise_grain - 1, feng::matrix_details::elementwise_grain ) == 1 ); + REQUIRE( work_workers( 5, 0 ) >= 1 ); + std::size_t const three = work_workers( 3 * feng::matrix_details::gemm_grain + 1, feng::matrix_details::gemm_grain ); + std::size_t const huge = work_workers( std::numeric_limits< std::size_t >::max(), 1 ); +#ifdef FENG_MATRIX_PARALLEL + unsigned const hc = std::thread::hardware_concurrency(); + std::size_t const cores = hc == 0 ? 1 : hc; + REQUIRE( three == std::min< std::size_t >( cores, 3 ) ); + REQUIRE( huge == cores ); +#else + REQUIRE( three == 1 ); + REQUIRE( huge == 1 ); +#endif + } + SECTION( "int" ) { s10_r3::check_thresholded< int >(); } + SECTION( "float" ) { s10_r3::check_thresholded< float >(); } + SECTION( "double" ) { s10_r3::check_thresholded< double >(); } + SECTION( "complex" ) { s10_r3::check_thresholded< std::complex< double > >(); } +} + +// S10-R3 (B-042, fft-plans): fft2 builds one plan per length per call (twiddle tables, Bluestein chirp and its +// transform) and reuses it for every row and column; the per-row transform it replaced is +// matrix_details::fft2_reference. The plan's values come from the same expressions, so the bits agree. +namespace s10_r3 +{ + template < typename C > + bool same_complex( feng::matrix< C > const& x, feng::matrix< C > const& y ) + { + if ( x.row() != y.row() || x.col() != y.col() ) return false; + for ( std::size_t i = 0; i != x.size(); ++i ) + if ( !( x.data()[i].real() == y.data()[i].real() && x.data()[i].imag() == y.data()[i].imag() ) ) return false; + return true; + } + + template < typename T > + void check_fft() + { + for ( auto [r, c] : { std::pair< std::size_t, std::size_t >{ 0, 0 }, { 1, 1 }, { 1, 13 }, { 1, 32 }, { 13, 1 }, { 32, 1 }, + { 16, 16 }, { 17, 17 }, { 18, 18 }, { 8, 12 }, { 12, 8 }, { 125, 125 }, { 128, 64 }, { 250, 3 } } ) + { + INFO( "fft " << r << "x" << c ); + auto const x = sample< T >( r, c, 10 ); + auto const f = feng::fft( x ); + auto const fr = feng::matrix_details::fft2_reference( x, -1 ); + REQUIRE( f.row() == r ); + REQUIRE( f.col() == c ); + REQUIRE( same_complex( f, fr ) ); + auto const g = feng::ifft( x ); + REQUIRE( same_complex( g, feng::matrix_details::fft2_reference( x, +1 ) ) ); + } + } +} + +TEST_CASE( "S10-R3 fft with reused plans equals the per-row transform", "[S10][S10-R3]" ) +{ + SECTION( "int" ) { s10_r3::check_fft< int >(); } + SECTION( "float" ) { s10_r3::check_fft< float >(); } + SECTION( "double" ) { s10_r3::check_fft< double >(); } + SECTION( "complex" ) { s10_r3::check_fft< std::complex< double > >(); } +} diff --git a/tests/cases/s1_f16.hpp b/tests/cases/s1_f16.hpp new file mode 100644 index 0000000..88cd3f0 --- /dev/null +++ b/tests/cases/s1_f16.hpp @@ -0,0 +1,121 @@ +// S1-R4 (D-015): the F16 templates (matrix power, valarray products, clone overloads). +#include +#include +#include + +namespace s1_f16 +{ + // A small, well-conditioned matrix whose powers stay bounded (spectral radius below 1). + inline feng::matrix sample_square() + { + feng::matrix a{ 3, 3 }; + double const v[3][3] = { { 0.50, 0.10, -0.20 }, { 0.05, 0.40, 0.10 }, { -0.10, 0.20, 0.30 } }; + for ( std::size_t r = 0; r != 3; ++r ) + for ( std::size_t c = 0; c != 3; ++c ) + a[r][c] = v[r][c]; + return a; + } + + inline feng::matrix sample_rect( std::size_t rows, std::size_t cols ) + { + feng::matrix a{ rows, cols }; + for ( std::size_t r = 0; r != rows; ++r ) + for ( std::size_t c = 0; c != cols; ++c ) + a[r][c] = static_cast( 10 * r + c ) + 0.5; + return a; + } + + inline bool near( feng::matrix const& x, feng::matrix const& y, double tol = 1.0e-12 ) + { + if ( x.row() != y.row() || x.col() != y.col() ) + return false; + for ( std::size_t r = 0; r != x.row(); ++r ) + for ( std::size_t c = 0; c != x.col(); ++c ) + if ( std::abs( x[r][c] - y[r][c] ) > tol * ( 1.0 + std::abs( y[r][c] ) ) ) + return false; + return true; + } + + inline bool equal_block( feng::matrix const& block, feng::matrix const& src, + std::size_t r0, std::size_t r1, std::size_t c0, std::size_t c1 ) + { + if ( block.row() != r1 - r0 || block.col() != c1 - c0 ) + return false; + for ( std::size_t r = r0; r != r1; ++r ) + for ( std::size_t c = c0; c != c1; ++c ) + if ( block[r - r0][c - c0] != src[r][c] ) + return false; + return true; + } +} // namespace s1_f16 + +TEST_CASE( "S1 matrix power equals repeated multiplication", "[S1][S1-R4]" ) +{ + auto const a = s1_f16::sample_square(); + auto const id = feng::eye( 3, 3 ); + + REQUIRE( s1_f16::near( a ^ 0, id ) ); + + feng::matrix expected{ id }; + for ( unsigned n = 1; n <= 13; ++n ) + { + expected = expected * a; + if ( n == 1 || n == 2 || n == 3 || n == 13 ) + { + INFO( "n = " << n ); + REQUIRE( s1_f16::near( a ^ n, expected ) ); + } + } +} + +TEST_CASE( "S1 valarray products match explicit sums", "[S1][S1-R4]" ) +{ + std::size_t const rows = 3, cols = 4; + auto const m = s1_f16::sample_rect( rows, cols ); + + // valarray x matrix: (1 x rows) * (rows x cols) -> 1 x cols + std::valarray const left = { 1.5, -2.0, 0.25 }; + auto const lm = left * m; + REQUIRE( lm.row() == 1 ); + REQUIRE( lm.col() == cols ); + for ( std::size_t c = 0; c != cols; ++c ) + { + double s = 0.0; + for ( std::size_t r = 0; r != rows; ++r ) + s += left[r] * m[r][c]; + REQUIRE( std::abs( lm[0][c] - s ) < 1.0e-12 * ( 1.0 + std::abs( s ) ) ); + } + + // matrix x valarray: (rows x cols) * (cols x 1) -> rows x 1 + std::valarray const right = { 0.5, 1.0, -1.0, 2.0 }; + auto const mr = m * right; + REQUIRE( mr.row() == rows ); + REQUIRE( mr.col() == 1 ); + for ( std::size_t r = 0; r != rows; ++r ) + { + double s = 0.0; + for ( std::size_t c = 0; c != cols; ++c ) + s += m[r][c] * right[c]; + REQUIRE( std::abs( mr[r][0] - s ) < 1.0e-12 * ( 1.0 + std::abs( s ) ) ); + } +} + +TEST_CASE( "S1 every clone overload returns the selected block", "[S1][S1-R4]" ) +{ + auto const src = s1_f16::sample_rect( 5, 6 ); + std::size_t const r0 = 1, r1 = 4, c0 = 2, c1 = 5; + + feng::matrix by_braces; + by_braces.clone( src, { r0, r1 }, { c0, c1 } ); + REQUIRE( s1_f16::equal_block( by_braces, src, r0, r1, c0, c1 ) ); + + feng::matrix by_indices; + by_indices.clone( src, r0, r1, c0, c1 ); + REQUIRE( s1_f16::equal_block( by_indices, src, r0, r1, c0, c1 ) ); + + auto const const_braces = src.clone( { r0, r1 }, { c0, c1 } ); + REQUIRE( s1_f16::equal_block( const_braces, src, r0, r1, c0, c1 ) ); + + auto const const_indices = src.clone( r0, r1, c0, c1 ); + REQUIRE( s1_f16::equal_block( const_indices, src, r0, r1, c0, c1 ) ); +} diff --git a/tests/cases/s2_death.hpp b/tests/cases/s2_death.hpp new file mode 100644 index 0000000..8601caf --- /dev/null +++ b/tests/cases/s2_death.hpp @@ -0,0 +1,124 @@ +// S2-R1: fork-based death tests. The callable runs in a child process whose stderr goes to a pipe; the parent +// collects that text and the wait status. A child whose callable returns exits 0 through _exit, which fails the +// death requirement. The child restores SIG_DFL for SIGABRT, sends stdout to /dev/null and exits 2 if the callable +// throws. +#ifndef S2_DEATH_HPP_INCLUDED +#define S2_DEATH_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace s2_death +{ + struct outcome + { + bool signaled = false; // WIFSIGNALED + int signal = 0; // WTERMSIG when signaled + bool exited = false; // WIFEXITED + int exit_code = -1; // WEXITSTATUS when exited + std::string err; // everything the child wrote to stderr + }; + + template < typename Callable > + outcome run( Callable&& fn ) + { + outcome out; + int fds[2]; + if ( ::pipe( fds ) != 0 ) + { + out.err = "s2_death: pipe failed"; + return out; + } + std::fflush( stdout ); + std::fflush( stderr ); + pid_t const pid = ::fork(); + if ( pid < 0 ) + { + ::close( fds[0] ); + ::close( fds[1] ); + out.err = "s2_death: fork failed"; + return out; + } + if ( pid == 0 ) + { + ::close( fds[0] ); + ::dup2( fds[1], STDERR_FILENO ); + ::close( fds[1] ); + // Catch's SIGABRT handler would report a fatal error from the child; the default disposition keeps + // the child's death silent apart from the contract message. + ::signal( SIGABRT, SIG_DFL ); + int const null_fd = ::open( "/dev/null", O_WRONLY ); + if ( null_fd >= 0 ) + { + ::dup2( null_fd, STDOUT_FILENO ); + if ( null_fd != STDOUT_FILENO ) ::close( null_fd ); + } +#if defined( __cpp_exceptions ) + try { std::forward< Callable >( fn )(); } + catch ( ... ) { ::_exit( 2 ); } +#else + std::forward< Callable >( fn )(); +#endif + ::_exit( 0 ); + } + ::close( fds[1] ); + char buf[4096]; + for ( ;; ) + { + ssize_t const n = ::read( fds[0], buf, sizeof( buf ) ); + if ( n > 0 ) { out.err.append( buf, static_cast< std::size_t >( n ) ); continue; } + if ( n < 0 && errno == EINTR ) continue; + break; + } + ::close( fds[0] ); + int status = 0; + while ( ::waitpid( pid, &status, 0 ) < 0 && errno == EINTR ) {} + if ( WIFSIGNALED( status ) ) { out.signaled = true; out.signal = WTERMSIG( status ); } + if ( WIFEXITED( status ) ) { out.exited = true; out.exit_code = WEXITSTATUS( status ); } + return out; + } + + inline bool contains( std::string const& text, std::string const& needle ) + { + return text.find( needle ) != std::string::npos; + } + + inline std::size_t line_count( std::string const& text ) + { + std::size_t n = 0; + for ( char ch : text ) n += ( ch == '\n' ); + if ( !text.empty() && text.back() != '\n' ) ++n; + return n; + } +} + +// Requires that `callable` aborts through the contract-violation path with `expected` in its message and that +// no sanitizer diagnostic appears. A function, so lambdas with commas need no extra parentheses. +namespace s2_death +{ + template < typename Callable > + void require_death( Callable&& fn, std::string const& expected ) + { + outcome const out = run( std::forward< Callable >( fn ) ); + INFO( "child stderr: " << out.err ); + INFO( "expected substring: " << expected ); + REQUIRE( out.signaled ); + REQUIRE( out.signal == SIGABRT ); + REQUIRE( contains( out.err, "contract violation" ) ); + REQUIRE( contains( out.err, expected ) ); + REQUIRE_FALSE( contains( out.err, "AddressSanitizer" ) ); + REQUIRE_FALSE( contains( out.err, "runtime error:" ) ); + } +} +#define S2_REQUIRE_DEATH( ... ) s2_death::require_death( __VA_ARGS__ ) + +#endif // S2_DEATH_HPP_INCLUDED diff --git a/tests/cases/s2_r1.hpp b/tests/cases/s2_r1.hpp new file mode 100644 index 0000000..e3196b9 --- /dev/null +++ b/tests/cases/s2_r1.hpp @@ -0,0 +1,30 @@ +// S2-R1 (PR-2): one always-on violation path; runs in debug and NDEBUG builds. +#include "./s2_death.hpp" + +TEST_CASE( "S2 violation path aborts with one message", "[S2][S2-R1]" ) +{ + SECTION( "m(5, 0) on a 2x2 matrix aborts with exactly one stderr line" ) + { + auto const out = s2_death::run( []{ feng::matrix m{ 2, 2 }; double volatile x = m( 5, 0 ); (void)x; } ); + INFO( "child stderr: " << out.err ); + REQUIRE( out.signaled ); + REQUIRE( out.signal == SIGABRT ); + REQUIRE( out.err.rfind( "feng::matrix: contract violation: ", 0 ) == 0 ); + REQUIRE( s2_death::line_count( out.err ) == 1 ); + REQUIRE( out.err.back() == '\n' ); + REQUIRE_FALSE( s2_death::contains( out.err, "AddressSanitizer" ) ); + REQUIRE_FALSE( s2_death::contains( out.err, "runtime error:" ) ); + } + SECTION( "the message names the site's text" ) + { + S2_REQUIRE_DEATH( []{ feng::matrix m{ 2, 2 }; double volatile x = m( 5, 0 ); (void)x; }, "Row index out of boundary" ); + S2_REQUIRE_DEATH( []{ feng::matrix m{ 2, 2 }; m( 0, 7 ) = 1.0; }, "Column index out of boundary" ); + } + SECTION( "a child whose callable returns exits 0 and is not a death" ) + { + auto const out = s2_death::run( []{} ); + REQUIRE_FALSE( out.signaled ); + REQUIRE( out.exited ); + REQUIRE( out.exit_code == 0 ); + } +} diff --git a/tests/cases/s2_r2.hpp b/tests/cases/s2_r2.hpp new file mode 100644 index 0000000..df7a0e0 --- /dev/null +++ b/tests/cases/s2_r2.hpp @@ -0,0 +1,387 @@ +// S2-R2 (PR-3): sizes are checked before the allocator is called. +#include "./s2_death.hpp" + +#include +#include +#include + +namespace s2_r2 +{ + // Stateless allocator that reports every allocate call on stderr, so a death test can prove none happened. + template < typename T > + struct counting_allocator + { + typedef T value_type; + counting_allocator() noexcept = default; + template < typename U > counting_allocator( counting_allocator const& ) noexcept {} + T* allocate( std::size_t n ) + { + std::fputs( "ALLOCATE\n", stderr ); + return std::allocator{}.allocate( n ); + } + void deallocate( T* p, std::size_t n ) noexcept { std::allocator{}.deallocate( p, n ); } + template < typename U > bool operator==( counting_allocator const& ) const noexcept { return true; } + }; + + // Same as counting_allocator but with a small max_size, so the max_size bound is the first one to fire. + template < typename T > + struct small_allocator : counting_allocator + { + small_allocator() noexcept = default; + template < typename U > small_allocator( small_allocator const& ) noexcept {} + std::size_t max_size() const noexcept { return 1000; } + template < typename U > bool operator==( small_allocator const& ) const noexcept { return true; } + }; + + inline constexpr std::size_t two_62 = std::size_t{ 1 } << 62; +} + +TEST_CASE( "S2 huge extents abort before allocation", "[S2][S2-R2]" ) +{ + using s2_r2::two_62; + using counted = feng::matrix< double, s2_r2::counting_allocator >; + + SECTION( "the counting allocator is observable" ) + { + auto const out = s2_death::run( []{ counted m{ 2, 2 }; (void)m; } ); + REQUIRE( out.exited ); + REQUIRE( s2_death::contains( out.err, "ALLOCATE" ) ); + } + SECTION( "2^62 x 8 overflows rows*cols" ) + { + S2_REQUIRE_DEATH( []{ feng::matrix m{ two_62, 8 }; (void)m; }, "size" ); + auto const out = s2_death::run( []{ counted m{ two_62, 8 }; (void)m; } ); + INFO( "child stderr: " << out.err ); + REQUIRE( out.signaled ); + REQUIRE( out.signal == SIGABRT ); + REQUIRE( s2_death::contains( out.err, "size" ) ); + REQUIRE_FALSE( s2_death::contains( out.err, "ALLOCATE" ) ); + } + SECTION( "2^62 x 1 doubles overflow the byte count" ) + { + S2_REQUIRE_DEATH( []{ feng::matrix m{ two_62, 1 }; (void)m; }, "byte count" ); + auto const out = s2_death::run( []{ counted m{ s2_r2::counting_allocator{}, two_62, 1 }; (void)m; } ); + INFO( "child stderr: " << out.err ); + REQUIRE( out.signaled ); + REQUIRE( out.signal == SIGABRT ); + REQUIRE( s2_death::contains( out.err, "contract violation" ) ); + REQUIRE( s2_death::contains( out.err, "byte count" ) ); + REQUIRE_FALSE( s2_death::contains( out.err, "ALLOCATE" ) ); + } + SECTION( "100 x 20 is above an allocator max_size of 1000" ) + { + using small = feng::matrix< double, s2_r2::small_allocator >; + auto const ok = s2_death::run( []{ small m{ 10, 10 }; (void)m; } ); + REQUIRE( ok.exited ); + REQUIRE( s2_death::contains( ok.err, "ALLOCATE" ) ); + auto const out = s2_death::run( []{ small m{ 100, 20 }; (void)m; } ); + INFO( "child stderr: " << out.err ); + REQUIRE( out.signaled ); + REQUIRE( out.signal == SIGABRT ); + REQUIRE( s2_death::contains( out.err, "contract violation" ) ); + REQUIRE( s2_death::contains( out.err, "max_size" ) ); + REQUIRE( s2_death::contains( out.err, "size" ) ); + REQUIRE_FALSE( s2_death::contains( out.err, "ALLOCATE" ) ); + } + SECTION( "the (row, col, {values}) constructor checks before allocating" ) + { + // 2^32 * 2^32 wraps to 0 in 64 bits; the product must be rejected, not treated as empty. + auto const out = s2_death::run( []{ counted m{ std::size_t{ 1 } << 32, std::size_t{ 1 } << 32, { 1.0, 2.0 } }; (void)m; } ); + INFO( "child stderr: " << out.err ); + REQUIRE( out.signaled ); + REQUIRE( out.signal == SIGABRT ); + REQUIRE( s2_death::contains( out.err, "contract violation" ) ); + REQUIRE( s2_death::contains( out.err, "size" ) ); + REQUIRE_FALSE( s2_death::contains( out.err, "ALLOCATE" ) ); + } +} + +#include +#include +#include +#include +#include + +#include + +namespace s2_r2 +{ + using counted = feng::matrix< double, counting_allocator >; + + // Marks the point after which a death test requires no allocate call. + inline void ready() { std::fputs( "READY\n", stderr ); std::fflush( stderr ); } + + // Child-side checks of a death: SIGABRT, `expected` in the message, no ALLOCATE after READY. + template < typename Callable > + void require_death_without_allocate( Callable&& fn, std::string const& expected ) + { + auto const out = s2_death::run( std::forward< Callable >( fn ) ); + INFO( "child stderr: " << out.err ); + INFO( "expected substring: " << expected ); + REQUIRE( out.signaled ); + REQUIRE( out.signal == SIGABRT ); + REQUIRE( s2_death::contains( out.err, "contract violation" ) ); + REQUIRE( s2_death::contains( out.err, expected ) ); + REQUIRE_FALSE( s2_death::contains( out.err, "AddressSanitizer" ) ); + REQUIRE_FALSE( s2_death::contains( out.err, "runtime error:" ) ); + std::size_t const at = out.err.find( "READY\n" ); + REQUIRE( at != std::string::npos ); + REQUIRE_FALSE( s2_death::contains( out.err.substr( at ), "ALLOCATE" ) ); + } + + inline feng::matrix numbered( std::size_t r, std::size_t c ) + { + feng::matrix m{ r, c }; + for ( std::size_t i = 0; i != m.size(); ++i ) m.data()[i] = static_cast( i ); + return m; + } + + inline counted numbered_counted( std::size_t r, std::size_t c ) + { + counted m{ r, c }; + for ( std::size_t i = 0; i != m.size(); ++i ) m.data()[i] = static_cast( i ); + return m; + } +} + +TEST_CASE( "S2 at and call operator abort out of range", "[S2][S2-R2]" ) +{ + using s2_r2::numbered; + using view_type = feng::matrix_view< double, std::allocator >; + + SECTION( "in-range at and () agree, const and non-const" ) + { + auto m = numbered( 3, 4 ); + auto const& cm = m; + for ( std::size_t r = 0; r != 3; ++r ) + for ( std::size_t c = 0; c != 4; ++c ) + { + REQUIRE( m.at( r, c ) == static_cast( r * 4 + c ) ); + REQUIRE( cm.at( r, c ) == m( r, c ) ); + REQUIRE( &cm.at( r, c ) == &m.at( r, c ) ); + } + m.at( 2, 3 ) = -1.0; + REQUIRE( m( 2, 3 ) == -1.0 ); + view_type const v{ m, { 1, 3 }, { 1, 4 } }; + REQUIRE( v.at( 0, 0 ) == m( 1, 1 ) ); + REQUIRE( v.at( 1, 2 ) == -1.0 ); + REQUIRE( v( 1, 2 ) == v.at( 1, 2 ) ); + } + SECTION( "row or column at the extent aborts with index" ) + { + S2_REQUIRE_DEATH( []{ auto m = s2_r2::numbered( 3, 4 ); m.at( 3, 0 ) = 1.0; }, "index" ); + S2_REQUIRE_DEATH( []{ auto m = s2_r2::numbered( 3, 4 ); m.at( 0, 4 ) = 1.0; }, "index" ); + S2_REQUIRE_DEATH( []{ auto const m = s2_r2::numbered( 3, 4 ); double volatile x = m.at( 3, 0 ); (void)x; }, "index" ); + S2_REQUIRE_DEATH( []{ auto const m = s2_r2::numbered( 3, 4 ); double volatile x = m.at( 0, 4 ); (void)x; }, "index" ); + S2_REQUIRE_DEATH( []{ auto m = s2_r2::numbered( 3, 4 ); m( 3, 0 ) = 1.0; }, "index" ); + S2_REQUIRE_DEATH( []{ auto m = s2_r2::numbered( 3, 4 ); m( 0, 4 ) = 1.0; }, "index" ); + S2_REQUIRE_DEATH( []{ auto const m = s2_r2::numbered( 3, 4 ); double volatile x = m( 3, 0 ); (void)x; }, "index" ); + S2_REQUIRE_DEATH( []{ auto const m = s2_r2::numbered( 3, 4 ); double volatile x = m( 0, 4 ); (void)x; }, "index" ); + S2_REQUIRE_DEATH( []{ auto m = s2_r2::numbered( 3, 4 ); m.at( std::size_t( -1 ), 0 ) = 1.0; }, "index" ); + } + SECTION( "m[r] aborts when r >= row()" ) + { + S2_REQUIRE_DEATH( []{ auto m = s2_r2::numbered( 3, 4 ); double* volatile p = m[3]; (void)p; }, "index" ); + S2_REQUIRE_DEATH( []{ auto const m = s2_r2::numbered( 3, 4 ); double const* volatile p = m[3]; (void)p; }, "index" ); + } + SECTION( "views check against their own extents" ) + { + S2_REQUIRE_DEATH( []{ auto m = s2_r2::numbered( 3, 4 ); view_type const v{ m, { 1, 3 }, { 1, 4 } }; double volatile x = v.at( 2, 0 ); (void)x; }, "index" ); + S2_REQUIRE_DEATH( []{ auto m = s2_r2::numbered( 3, 4 ); view_type const v{ m, { 1, 3 }, { 1, 4 } }; double volatile x = v.at( 0, 3 ); (void)x; }, "index" ); + S2_REQUIRE_DEATH( []{ auto m = s2_r2::numbered( 3, 4 ); view_type const v{ m, { 1, 3 }, { 1, 4 } }; double volatile x = v( 2, 0 ); (void)x; }, "index" ); + S2_REQUIRE_DEATH( []{ auto m = s2_r2::numbered( 3, 4 ); view_type const v{ m, { 1, 3 }, { 1, 4 } }; double volatile x = v( 0, 3 ); (void)x; }, "index" ); + } +} + +TEST_CASE( "S2 empty matrices have no accessible elements", "[S2][S2-R2]" ) +{ + std::pair< std::size_t, std::size_t > const shapes[] = { { 0, 0 }, { 0, 5 }, { 5, 0 } }; + for ( auto const& [r, c] : shapes ) + { + INFO( "shape " << r << "x" << c ); + feng::matrix m{ r, c }; + REQUIRE( m.row() == r ); + REQUIRE( m.col() == c ); + REQUIRE( m.size() == 0 ); + REQUIRE( m.begin() == m.end() ); + auto const& cm = m; + REQUIRE( cm.begin() == cm.end() ); + std::pair< std::size_t, std::size_t > const probes[] = { { 0, 0 }, { 0, 4 }, { 4, 0 }, { 4, 4 }, { 5, 5 } }; + for ( auto const& [pr, pc] : probes ) + { + INFO( "probe (" << pr << ", " << pc << ")" ); + S2_REQUIRE_DEATH( [&]{ feng::matrix e{ r, c }; e.at( pr, pc ) = 1.0; }, "index" ); + S2_REQUIRE_DEATH( [&]{ feng::matrix const e{ r, c }; double volatile x = e.at( pr, pc ); (void)x; }, "index" ); + S2_REQUIRE_DEATH( [&]{ feng::matrix e{ r, c }; e( pr, pc ) = 1.0; }, "index" ); + S2_REQUIRE_DEATH( [&]{ feng::matrix const e{ r, c }; double volatile x = e( pr, pc ); (void)x; }, "index" ); + } + } +} + +TEST_CASE( "S2 negative signed dimensions abort before allocation", "[S2][S2-R2]" ) +{ + using s2_r2::counted; + SECTION( "non-negative signed dimensions build the matrix" ) + { + int const r = 2, c = 3; + feng::matrix m{ r, c, { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 } }; + REQUIRE( m.row() == 2 ); + REQUIRE( m.col() == 3 ); + REQUIRE( m( 1, 2 ) == 6.0 ); + } + SECTION( "a negative row or column aborts with size" ) + { + s2_r2::require_death_without_allocate( []{ int volatile r = -1; s2_r2::ready(); counted m{ int( r ), 2, { 1.0 } }; (void)m; }, "size" ); + s2_r2::require_death_without_allocate( []{ int volatile c = -3; s2_r2::ready(); counted m{ 2, int( c ), { 1.0 } }; (void)m; }, "size" ); + s2_r2::require_death_without_allocate( []{ long long volatile r = -1; s2_r2::ready(); counted m{ (long long)( r ), 0LL, {} }; (void)m; }, "size" ); + s2_r2::require_death_without_allocate( []{ short volatile c = -1; s2_r2::ready(); counted m{ 0, (short)( c ), {} }; (void)m; }, "size" ); + } +} + +TEST_CASE( "S2 reshape checks the element count and overflow", "[S2][S2-R2]" ) +{ + SECTION( "a matching reshape keeps the data" ) + { + auto m = s2_r2::numbered( 2, 6 ); + double const* const p = m.data(); + m.reshape( 3, 4 ); + REQUIRE( m.row() == 3 ); + REQUIRE( m.col() == 4 ); + REQUIRE( m.data() == p ); + REQUIRE( m( 2, 3 ) == 11.0 ); + } + SECTION( "a different element count aborts with reshape" ) + { + S2_REQUIRE_DEATH( []{ auto m = s2_r2::numbered( 2, 6 ); m.reshape( 4, 4 ); }, "reshape" ); + S2_REQUIRE_DEATH( []{ auto m = s2_r2::numbered( 2, 6 ); m.reshape( 0, 12 ); }, "reshape" ); + } + SECTION( "a wrapping r*c aborts with reshape" ) + { + // 2^32 * 2^32 wraps to 0 and 2^63 * 2 wraps to 0: both equal an empty matrix's size() unless checked. + S2_REQUIRE_DEATH( []{ feng::matrix m; m.reshape( std::size_t{ 1 } << 32, std::size_t{ 1 } << 32 ); }, "reshape" ); + S2_REQUIRE_DEATH( []{ feng::matrix m{ 0, 3 }; m.reshape( std::size_t{ 1 } << 63, 2 ); }, "reshape" ); + // (2^64 - 1) * 13 wraps to 2^64 - 13; 12 + 2^64 wraps to 12 for r*c = 12 via r = 2^62 + 3, c = 4. + S2_REQUIRE_DEATH( []{ auto m = s2_r2::numbered( 2, 6 ); m.reshape( ( std::size_t{ 1 } << 62 ) + 3, 4 ); }, "reshape" ); + } +} + +TEST_CASE( "S2 clone bounds abort before reading the source", "[S2][S2-R2]" ) +{ + using s2_r2::counted; + using s2_r2::numbered_counted; + using s2_r2::require_death_without_allocate; + using s2_r2::ready; + + SECTION( "an in-bounds clone copies the block" ) + { + auto const src = s2_r2::numbered( 4, 5 ); + feng::matrix a; + a.clone( src, 1, 4, 2, 5 ); + REQUIRE( a.row() == 3 ); + REQUIRE( a.col() == 3 ); + REQUIRE( a( 0, 0 ) == src( 1, 2 ) ); + REQUIRE( a( 2, 2 ) == src( 3, 4 ) ); + auto const b = src.clone( { 0, 4 }, { 0, 5 } ); + REQUIRE( b == src ); + feng::matrix const c{ src, { 3, 4 }, { 4, 5 } }; + REQUIRE( c.size() == 1 ); + REQUIRE( c( 0, 0 ) == 19.0 ); + } + SECTION( "member clone( other, r0, r1, c0, c1 )" ) + { + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); counted d; ready(); d.clone( s, 0, 5, 0, 5 ); }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); counted d; ready(); d.clone( s, 0, 4, 0, 6 ); }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); counted d; ready(); d.clone( s, 2, 2, 0, 5 ); }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); counted d; ready(); d.clone( s, 3, 1, 0, 5 ); }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); counted d; ready(); d.clone( s, 0, 4, 5, 5 ); }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); counted d; ready(); d.clone( s, 0, 4, 4, 2 ); }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); counted d; ready(); d.clone( s, 0, std::size_t( -1 ), 0, 5 ); }, "clone" ); + } + SECTION( "member clone with brace lists" ) + { + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); counted d; ready(); d.clone( s, { 0, 5 }, { 0, 5 } ); }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); counted d; ready(); d.clone( s, { 0, 1, 2 }, { 0, 5 } ); }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); counted d; ready(); d.clone( s, { 0, 4 }, { 1 } ); }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); counted d; ready(); d.clone( s, {}, { 0, 5 } ); }, "clone" ); + } + SECTION( "const clone( r0, r1, c0, c1 ) and clone( {r0, r1}, {c0, c1} )" ) + { + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); ready(); auto d = s.clone( 0, 5, 0, 5 ); (void)d; }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); ready(); auto d = s.clone( 1, 1, 0, 5 ); (void)d; }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); ready(); auto d = s.clone( { 0, 4 }, { 0, 6 } ); (void)d; }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); ready(); auto d = s.clone( { 0 }, { 0, 5 } ); (void)d; }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); ready(); auto d = s.clone( { 0, 4 }, { 0, 1, 5 } ); (void)d; }, "clone" ); + } + SECTION( "slicing constructors" ) + { + using range = std::pair< std::size_t, std::size_t >; + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); ready(); counted d{ s, { 0, 5 }, { 0, 5 } }; (void)d; }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); ready(); counted d{ s, { 0, 4, 1 }, { 0, 5 } }; (void)d; }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); ready(); counted d{ s, { 0, 4 }, { 5 } }; (void)d; }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); ready(); counted d{ s, range{ 0, 4 }, range{ 3, 3 } }; (void)d; }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); ready(); counted d{ s, 0, 4, 0, 9 }; (void)d; }, "clone" ); + require_death_without_allocate( []{ auto const s = numbered_counted( 4, 5 ); ready(); counted d{ s, 2, 1, 0, 5 }; (void)d; }, "clone" ); + // the converting (other element type) slicing constructor + S2_REQUIRE_DEATH( []{ feng::matrix const s{ 4, 5 }; feng::matrix d{ s, { 0, 5 }, { 0, 5 } }; (void)d; }, "clone" ); + S2_REQUIRE_DEATH( []{ feng::matrix const s{ 4, 5 }; feng::matrix d{ s, { 0, 4 }, { 0 } }; (void)d; }, "clone" ); + } +} + +TEST_CASE( "S2 byte counts above PTRDIFF_MAX abort before allocation", "[S2][S2-R2]" ) +{ + using s2_r2::counted; + // The counting allocator keeps the default max_size (SIZE_MAX / sizeof(T)), so only the PTRDIFF_MAX bound + // stands between 2^60 doubles (2^63 bytes) and the allocator. + s2_r2::require_death_without_allocate( []{ s2_r2::ready(); counted m{ std::size_t{ 1 } << 60, 1 }; (void)m; }, "PTRDIFF_MAX" ); + s2_r2::require_death_without_allocate( []{ s2_r2::ready(); counted m{ s2_r2::counting_allocator{}, 1, std::size_t{ 1 } << 61 }; (void)m; }, "size" ); + S2_REQUIRE_DEATH( []{ feng::matrix m{ std::size_t{ 1 } << 63, 1 }; (void)m; }, "PTRDIFF_MAX" ); + S2_REQUIRE_DEATH( []{ feng::matrix m{ std::size_t{ 1 } << 63, 1 }; (void)m; }, "size" ); +} + +TEST_CASE( "S2 resize and shrink_to_size check sizes before allocation", "[S2][S2-R2]" ) +{ + using s2_r2::counted; + // 2^32 x 2^32 wraps to 0, which equals an empty matrix's size(); it must not be taken as a reshape. + s2_r2::require_death_without_allocate( []{ counted m; s2_r2::ready(); m.resize( std::size_t{ 1 } << 32, std::size_t{ 1 } << 32 ); }, "size" ); + s2_r2::require_death_without_allocate( []{ counted m{ 2, 2 }; s2_r2::ready(); m.resize( std::size_t{ 1 } << 62, 8 ); }, "size" ); + s2_r2::require_death_without_allocate( []{ counted m{ 2, 2 }; s2_r2::ready(); m.shrink_to_size( std::size_t{ 1 } << 62, 8 ); }, "size" ); + s2_r2::require_death_without_allocate( []{ counted m{ 2, 2 }; s2_r2::ready(); m.shrink_to_size( std::size_t{ 1 } << 60, 1 ); }, "PTRDIFF_MAX" ); +} + +TEST_CASE( "S2 death helper isolates the child", "[S2][S2-R2]" ) +{ + SECTION( "the child runs with SIG_DFL for SIGABRT" ) + { + auto const out = s2_death::run( []{ struct sigaction sa{}; ::sigaction( SIGABRT, nullptr, &sa ); if ( sa.sa_handler != SIG_DFL ) ::_exit( 4 ); } ); + REQUIRE( out.exited ); + REQUIRE( out.exit_code == 0 ); + } + SECTION( "the child's stdout is /dev/null" ) + { + auto const out = s2_death::run( [] + { + struct stat o{}, n{}; + if ( ::fstat( STDOUT_FILENO, &o ) != 0 || ::stat( "/dev/null", &n ) != 0 ) ::_exit( 5 ); + if ( o.st_rdev != n.st_rdev || !S_ISCHR( o.st_mode ) ) ::_exit( 3 ); + } ); + REQUIRE( out.exited ); + REQUIRE( out.exit_code == 0 ); + } + SECTION( "an abort prints only the contract message" ) + { + auto const out = s2_death::run( []{ feng::matrix m{ 1, 1 }; m.at( 1, 1 ) = 0.0; } ); + INFO( "child stderr: " << out.err ); + REQUIRE( out.signaled ); + REQUIRE( out.signal == SIGABRT ); + REQUIRE( s2_death::line_count( out.err ) == 1 ); + REQUIRE( out.err.rfind( "feng::matrix: contract violation: ", 0 ) == 0 ); + } +#if defined( __cpp_exceptions ) + SECTION( "a throwing callable exits with code 2" ) + { + auto const out = s2_death::run( []{ throw std::runtime_error( "s2 death helper" ); } ); + REQUIRE( out.exited ); + REQUIRE( out.exit_code == 2 ); + } +#endif +} diff --git a/tests/cases/s2_r3.hpp b/tests/cases/s2_r3.hpp new file mode 100644 index 0000000..d66f9d3 --- /dev/null +++ b/tests/cases/s2_r3.hpp @@ -0,0 +1,143 @@ +// S2-R3 (PR-4): crop/pad copies the overlapping block exactly; flips are exact (D-004 axes). +#include "./s2_death.hpp" + +#include +#include +#include + +namespace s2_r3 +{ + inline feng::matrix distinct( std::size_t rows, std::size_t cols ) + { + feng::matrix m{ rows, cols }; + for ( std::size_t r = 0; r != rows; ++r ) + for ( std::size_t c = 0; c != cols; ++c ) + m[r][c] = static_cast( r * cols + c + 1 ); + return m; + } + + inline bool same( feng::matrix const& a, feng::matrix const& b ) + { + if ( a.row() != b.row() || a.col() != b.col() ) return false; + for ( std::size_t r = 0; r != a.row(); ++r ) + for ( std::size_t c = 0; c != a.col(); ++c ) + if ( a[r][c] != b[r][c] ) return false; + return true; + } + + inline std::vector< std::pair< std::size_t, std::size_t > > flip_shapes() + { + return { { 3, 5 }, { 5, 3 }, { 4, 4 }, { 1, 1 }, { 0, 0 }, { 0, 3 }, { 3, 0 } }; + } + + inline void check_shrink( std::size_t r0, std::size_t c0, std::size_t r1, std::size_t c1 ) + { + feng::matrix const original = distinct( r0, c0 ); + feng::matrix m = original; + m.shrink_to_size( r1, c1 ); + REQUIRE( m.row() == r1 ); + REQUIRE( m.col() == c1 ); + for ( std::size_t r = 0; r != r1; ++r ) + for ( std::size_t c = 0; c != c1; ++c ) + { + INFO( r0 << "x" << c0 << " -> " << r1 << "x" << c1 << ", r = " << r << ", c = " << c ); + double const expected = ( r < r0 && c < c0 ) ? original[r][c] : 0.0; + REQUIRE( m[r][c] == expected ); + } + } +} + +TEST_CASE( "S2 shrink_to_size 5x5 to 5x3 is exact", "[S2][S2-R3]" ) +{ + feng::matrix m{ 5, 5 }; + for ( std::size_t r = 0; r != 5; ++r ) + for ( std::size_t c = 0; c != 5; ++c ) + m[r][c] = static_cast( r * 5 + c ); + m.shrink_to_size( 5, 3 ); + REQUIRE( m.row() == 5 ); + REQUIRE( m.col() == 3 ); + for ( std::size_t r = 0; r != 5; ++r ) + for ( std::size_t c = 0; c != 3; ++c ) + { + INFO( "r = " << r << ", c = " << c ); + REQUIRE( m[r][c] == static_cast( r * 5 + c ) ); + } +} + +TEST_CASE( "S2 shrink_to_size crops and pads exactly", "[S2][S2-R3]" ) +{ + s2_r3::check_shrink( 5, 5, 5, 3 ); + s2_r3::check_shrink( 3, 10, 5, 2 ); + s2_r3::check_shrink( 2, 3, 4, 5 ); +} + +TEST_CASE( "S2 shrink_to_size aborts on a zero extent", "[S2][S2-R3]" ) +{ + S2_REQUIRE_DEATH( []{ feng::matrix m{ 2, 2 }; m.shrink_to_size( 0, 2 ); }, "shrink_to_size" ); + S2_REQUIRE_DEATH( []{ feng::matrix m{ 2, 2 }; m.shrink_to_size( 2, 0 ); }, "shrink_to_size" ); +} + +TEST_CASE( "S2 flipdim 1 reverses the rows", "[S2][S2-R3]" ) +{ + for ( auto [rows, cols] : s2_r3::flip_shapes() ) + { + INFO( rows << "x" << cols ); + feng::matrix const m = s2_r3::distinct( rows, cols ); + feng::matrix const f = feng::flipdim( m, 1 ); + REQUIRE( f.row() == rows ); + REQUIRE( f.col() == cols ); + for ( std::size_t r = 0; r != rows; ++r ) + for ( std::size_t c = 0; c != cols; ++c ) + REQUIRE( f[r][c] == m[rows - 1 - r][c] ); + } +} + +TEST_CASE( "S2 flipdim 2 reverses the columns", "[S2][S2-R3]" ) +{ + for ( auto [rows, cols] : s2_r3::flip_shapes() ) + { + INFO( rows << "x" << cols ); + feng::matrix const m = s2_r3::distinct( rows, cols ); + feng::matrix const f = feng::flipdim( m, 2 ); + REQUIRE( f.row() == rows ); + REQUIRE( f.col() == cols ); + for ( std::size_t r = 0; r != rows; ++r ) + for ( std::size_t c = 0; c != cols; ++c ) + REQUIRE( f[r][c] == m[r][cols - 1 - c] ); + } +} + +TEST_CASE( "S2 flipdim twice restores the input", "[S2][S2-R3]" ) +{ + for ( auto [rows, cols] : s2_r3::flip_shapes() ) + { + INFO( rows << "x" << cols ); + feng::matrix const m = s2_r3::distinct( rows, cols ); + REQUIRE( s2_r3::same( feng::flipdim( feng::flipdim( m, 1 ), 1 ), m ) ); + REQUIRE( s2_r3::same( feng::flipdim( feng::flipdim( m, 2 ), 2 ), m ) ); + } +} + +TEST_CASE( "S2 fliplr flips columns and flipud flips rows", "[S2][S2-R3]" ) +{ + for ( auto [rows, cols] : s2_r3::flip_shapes() ) + { + INFO( rows << "x" << cols ); + feng::matrix const m = s2_r3::distinct( rows, cols ); + REQUIRE( s2_r3::same( feng::fliplr( m ), feng::flipdim( m, 2 ) ) ); + REQUIRE( s2_r3::same( feng::flipud( m ), feng::flipdim( m, 1 ) ) ); + } + feng::matrix const m = s2_r3::distinct( 2, 3 ); + feng::matrix const lr = feng::fliplr( m ); + feng::matrix const ud = feng::flipud( m ); + REQUIRE( lr[0][0] == 3.0 ); + REQUIRE( lr[1][2] == 4.0 ); + REQUIRE( ud[0][0] == 4.0 ); + REQUIRE( ud[1][2] == 3.0 ); +} + +TEST_CASE( "S2 flipdim aborts on another dim", "[S2][S2-R3]" ) +{ + S2_REQUIRE_DEATH( []{ feng::matrix m{ 2, 2 }; (void)feng::flipdim( m, 0 ); }, "flipdim" ); + S2_REQUIRE_DEATH( []{ feng::matrix m{ 2, 2 }; (void)feng::flipdim( m, 3 ); }, "flipdim" ); +} diff --git a/tests/cases/s2_r4.hpp b/tests/cases/s2_r4.hpp new file mode 100644 index 0000000..2293d98 --- /dev/null +++ b/tests/cases/s2_r4.hpp @@ -0,0 +1,318 @@ +// S2-R4 (PR-3): operand shapes are checked before traversal; pooling modes; self-assignment and overlap policy. +#include "./s2_death.hpp" + +#include +#include + +TEST_CASE( "S2 valarray length 3 times 2x3 aborts", "[S2][S2-R4]" ) +{ + SECTION( "length 2 gives the explicit sums" ) + { + feng::matrix m{ 2, 3 }; + for ( std::size_t r = 0; r != 2; ++r ) + for ( std::size_t c = 0; c != 3; ++c ) + m[r][c] = static_cast( 10 * r + c ) + 0.5; + std::valarray const v{ 2.0, -3.0 }; + auto const p = v * m; + REQUIRE( p.row() == 1 ); + REQUIRE( p.col() == 3 ); + for ( std::size_t c = 0; c != 3; ++c ) + REQUIRE( p[0][c] == 2.0 * m[0][c] - 3.0 * m[1][c] ); + } + SECTION( "length 3 aborts with a shape message" ) + { + S2_REQUIRE_DEATH( []{ + feng::matrix m{ 2, 3 }; + std::valarray const v{ 1.0, 2.0, 3.0 }; + auto const p = v * m; + (void)p; + }, "shape" ); + } +} + +TEST_CASE( "S2 left vector product checks rows", "[S2][S2-R4]" ) +{ + SECTION( "length 2 times 2x3 gives the explicit sums" ) + { + feng::matrix m{ 2, 3 }; + for ( std::size_t r = 0; r != 2; ++r ) + for ( std::size_t c = 0; c != 3; ++c ) + m[r][c] = static_cast( 10 * r + c ) + 0.5; + std::vector const v{ 2.0, -3.0 }; + auto const p = v * m; + REQUIRE( p.row() == 1 ); + REQUIRE( p.col() == 3 ); + for ( std::size_t c = 0; c != 3; ++c ) + REQUIRE( p[0][c] == 2.0 * m[0][c] - 3.0 * m[1][c] ); + } + SECTION( "length 3 times 2x3 aborts with a shape message" ) + { + S2_REQUIRE_DEATH( []{ + feng::matrix m{ 2, 3 }; + std::vector const v{ 1.0, 2.0, 3.0 }; + auto const p = v * m; + (void)p; + }, "shape" ); + } + SECTION( "2x3 times a vector of length 2 aborts with a shape message" ) + { + S2_REQUIRE_DEATH( []{ + feng::matrix m{ 2, 3 }; + std::vector const v{ 1.0, 2.0 }; + auto const p = m * v; + (void)p; + }, "shape" ); + } + SECTION( "2x3 times a valarray of length 2 aborts with a shape message" ) + { + S2_REQUIRE_DEATH( []{ + feng::matrix m{ 2, 3 }; + std::valarray const v{ 1.0, 2.0 }; + auto const p = m * v; + (void)p; + }, "shape" ); + } +} + +TEST_CASE( "S2 binary and ternary element-wise maps check shapes", "[S2][S2-R4]" ) +{ + SECTION( "binary map_impl" ) + { + S2_REQUIRE_DEATH( []{ + feng::matrix a{ 2, 3 }; + feng::matrix b{ 3, 2 }; + auto const p = feng::matrix_details::map( []( double x, double y ){ return x + y; } )( a, b ); + (void)p; + }, "shape" ); + } + SECTION( "ternary map_impl" ) + { + S2_REQUIRE_DEATH( []{ + feng::matrix a{ 2, 3 }; + feng::matrix b{ 2, 3 }; + feng::matrix c{ 2, 2 }; + auto const p = feng::matrix_details::map( []( double x, double y, double z ){ return x + y + z; } )( a, b, c ); + (void)p; + }, "shape" ); + } + SECTION( "hypot" ) + { + S2_REQUIRE_DEATH( []{ + feng::matrix a{ 2, 3 }; + feng::matrix b{ 3, 2 }; + auto const p = feng::hypot( a, b ); + (void)p; + }, "shape" ); + } + SECTION( "fmin" ) + { + S2_REQUIRE_DEATH( []{ + feng::matrix a{ 2, 3 }; + feng::matrix b{ 2, 4 }; + auto const p = feng::fmin( a, b ); + (void)p; + }, "shape" ); + } + SECTION( "fma" ) + { + S2_REQUIRE_DEATH( []{ + feng::matrix a{ 2, 3 }; + feng::matrix b{ 2, 3 }; + feng::matrix c{ 3, 3 }; + auto const p = feng::fma( a, b, c ); + (void)p; + }, "shape" ); + } + SECTION( "matching shapes still work" ) + { + feng::matrix a{ 2, 3 }; + feng::matrix b{ 2, 3 }; + for ( std::size_t i = 0; i != 6; ++i ) { a.data()[i] = 3.0; b.data()[i] = 4.0; } + auto const h = feng::hypot( a, b ); + auto const s = feng::matrix_details::map( []( double x, double y, double z ){ return x + y + z; } )( a, b, a ); + for ( std::size_t i = 0; i != 6; ++i ) + { + REQUIRE( h.data()[i] == 5.0 ); + REQUIRE( s.data()[i] == 10.0 ); + } + } +} + +TEST_CASE( "S2 matrix operators check shapes", "[S2][S2-R4]" ) +{ + SECTION( "+=" ) + { + S2_REQUIRE_DEATH( []{ feng::matrix a{ 2, 3 }; feng::matrix const b{ 3, 2 }; a += b; }, "shape" ); + } + SECTION( "-=" ) + { + S2_REQUIRE_DEATH( []{ feng::matrix a{ 2, 3 }; feng::matrix const b{ 2, 2 }; a -= b; }, "shape" ); + } + SECTION( "/=" ) + { + S2_REQUIRE_DEATH( []{ feng::matrix a{ 2, 2 }; feng::matrix const b{ 3, 3 }; a /= b; }, "shape" ); + } + SECTION( "+" ) + { + S2_REQUIRE_DEATH( []{ feng::matrix const a{ 2, 3 }; feng::matrix const b{ 3, 2 }; auto const p = a + b; (void)p; }, "shape" ); + } + SECTION( "-" ) + { + S2_REQUIRE_DEATH( []{ feng::matrix const a{ 2, 3 }; feng::matrix const b{ 2, 4 }; auto const p = a - b; (void)p; }, "shape" ); + } + SECTION( "/" ) + { + S2_REQUIRE_DEATH( []{ feng::matrix const a{ 2, 2 }; feng::matrix const b{ 3, 3 }; auto const p = a / b; (void)p; }, "shape" ); + } + SECTION( "*= keeps its dims check with a shape message" ) + { + S2_REQUIRE_DEATH( []{ feng::matrix a{ 2, 3 }; feng::matrix const b{ 2, 3 }; a *= b; }, "shape" ); + } +} + +TEST_CASE( "S2 pooling rejects an unknown action", "[S2][S2-R4]" ) +{ + SECTION( "dim 2" ) + { + S2_REQUIRE_DEATH( []{ + feng::matrix m{ 4, 4 }; + auto const p = feng::pooling( m, 2, "median" ); + (void)p; + }, "pooling" ); + } + SECTION( "dim 1, before the identity early return" ) + { + S2_REQUIRE_DEATH( []{ + feng::matrix m{ 4, 4 }; + auto const p = feng::pooling( m, 1, "median" ); + (void)p; + }, "pooling" ); + } + SECTION( "dim 0, before the empty early return" ) + { + S2_REQUIRE_DEATH( []{ + feng::matrix m{ 4, 4 }; + auto const p = feng::pooling( m, 0, "median" ); + (void)p; + }, "pooling" ); + } + SECTION( "dims (1, 1), two-dim form" ) + { + S2_REQUIRE_DEATH( []{ + feng::matrix m{ 4, 4 }; + auto const p = feng::pooling( m, 1, 1, "median" ); + (void)p; + }, "pooling" ); + } +} + +namespace s2_r4_private +{ + inline feng::matrix filled( std::size_t r, std::size_t c ) + { + feng::matrix m{ r, c }; + for ( std::size_t i = 0; i != r; ++i ) + for ( std::size_t j = 0; j != c; ++j ) + m[i][j] = static_cast( ( 7 * i + 3 * j ) % 11 ) - 5.0; + return m; + } + + // A plain copy of the block [r0, r1) x [c0, c1) of m, made element by element. + inline feng::matrix block( feng::matrix const& m, std::size_t r0, std::size_t r1, std::size_t c0, std::size_t c1 ) + { + feng::matrix b{ r1 - r0, c1 - c0 }; + for ( std::size_t i = r0; i != r1; ++i ) + for ( std::size_t j = c0; j != c1; ++j ) + b[i - r0][j - c0] = m[i][j]; + return b; + } + + inline bool same( feng::matrix const& a, feng::matrix const& b ) + { + if ( a.row() != b.row() || a.col() != b.col() ) return false; + for ( std::size_t i = 0; i != a.row(); ++i ) + for ( std::size_t j = 0; j != a.col(); ++j ) + if ( a[i][j] != b[i][j] ) return false; + return true; + } +} + +TEST_CASE( "S2 self-assignment keeps the contents", "[S2][S2-R4]" ) +{ + auto a = s2_r4_private::filled( 5, 7 ); + auto const before = s2_r4_private::filled( 5, 7 ); + double const* const storage = a.data(); + + auto& self = a; + a = self; + REQUIRE( s2_r4_private::same( a, before ) ); + REQUIRE( a.data() == storage ); + + a.operator=< double, std::allocator >( self ); + REQUIRE( s2_r4_private::same( a, before ) ); + REQUIRE( a.data() == storage ); + + a.copy( self ); + REQUIRE( s2_r4_private::same( a, before ) ); + REQUIRE( a.data() == storage ); +} + +TEST_CASE( "S2 self multiply uses the old matrix", "[S2][S2-R4]" ) +{ + for ( std::size_t n : { std::size_t{ 3 }, std::size_t{ 18 }, std::size_t{ 32 } } ) + { + INFO( "n = " << n ); + auto a = s2_r4_private::filled( n, n ); + auto const old = s2_r4_private::filled( n, n ); + feng::matrix expected{ n, n }; + for ( std::size_t i = 0; i != n; ++i ) + for ( std::size_t j = 0; j != n; ++j ) + { + double s = 0.0; + for ( std::size_t k = 0; k != n; ++k ) s += old[i][k] * old[k][j]; + expected[i][j] = s; + } + a *= a; + REQUIRE( s2_r4_private::same( a, expected ) ); + } +} + +TEST_CASE( "S2 overlapping slice copy uses a snapshot", "[S2][S2-R4]" ) +{ + SECTION( "a view of the destination copied into an overlapping block" ) + { + auto m = s2_r4_private::filled( 5, 6 ); + auto const snapshot = s2_r4_private::block( m, 0, 3, 0, 4 ); + auto expected = s2_r4_private::filled( 5, 6 ); + expected.copy( snapshot, { 1, 4 }, { 1, 5 } ); + + m.copy( feng::make_view( m, { 0, 3 }, { 0, 4 } ), { 1, 4 }, { 1, 5 } ); + REQUIRE( s2_r4_private::same( m, expected ) ); + } + SECTION( "a view of the destination copied backwards into an overlapping block" ) + { + auto m = s2_r4_private::filled( 5, 6 ); + auto const snapshot = s2_r4_private::block( m, 1, 4, 2, 6 ); + auto expected = s2_r4_private::filled( 5, 6 ); + expected.copy( snapshot, { 0, 3 }, { 0, 4 } ); + + m.copy( feng::make_view( m, { 1, 4 }, { 2, 6 } ), { 0, 3 }, { 0, 4 } ); + REQUIRE( s2_r4_private::same( m, expected ) ); + } + SECTION( "the whole matrix copied onto itself" ) + { + auto m = s2_r4_private::filled( 4, 4 ); + auto const before = s2_r4_private::filled( 4, 4 ); + m.copy( m, { 0, 4 }, { 0, 4 } ); + REQUIRE( s2_r4_private::same( m, before ) ); + } + SECTION( "a disjoint matrix copies as before" ) + { + auto m = s2_r4_private::filled( 5, 6 ); + auto const src = s2_r4_private::filled( 2, 2 ); + m.copy( src, { 3, 5 }, { 4, 6 } ); + for ( std::size_t i = 0; i != 2; ++i ) + for ( std::size_t j = 0; j != 2; ++j ) + REQUIRE( m[3 + i][4 + j] == src[i][j] ); + } +} diff --git a/tests/cases/s3_alloc.hpp b/tests/cases/s3_alloc.hpp new file mode 100644 index 0000000..47edad8 --- /dev/null +++ b/tests/cases/s3_alloc.hpp @@ -0,0 +1,139 @@ +// S3-R2 (PR-5): test helpers for element lifetimes and stateful allocators (R04). +// - s3_alloc::tracked: a nothrow element type that counts live objects, constructions and destructions. +// - s3_alloc::registry: per resource id, the set of live allocations; a deallocation of a pointer the resource did +// not allocate (or with a different count) is recorded as a failure that the tests check. +// - s3_alloc::tracking_allocator: stateful (a resource id), is_always_equal false, == compares +// ids; the three propagation traits are template parameters so a test can loop over all 8 combinations. +#ifndef S3_ALLOC_HPP_INCLUDED +#define S3_ALLOC_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace s3_alloc +{ + struct tracked + { + static inline long live = 0; + static inline long constructed = 0; + static inline long destroyed = 0; + + static void reset() noexcept { live = 0; constructed = 0; destroyed = 0; } + + int value = 0; + + tracked() noexcept { ++live; ++constructed; } + tracked( int v ) noexcept : value{ v } { ++live; ++constructed; } + tracked( tracked const& other ) noexcept : value{ other.value } { ++live; ++constructed; } + tracked( tracked&& other ) noexcept : value{ other.value } { ++live; ++constructed; } + tracked& operator = ( tracked const& other ) noexcept { value = other.value; return *this; } + tracked& operator = ( tracked&& other ) noexcept { value = other.value; return *this; } + ~tracked() noexcept { --live; ++destroyed; } + + friend bool operator == ( tracked const& a, tracked const& b ) noexcept { return a.value == b.value; } + }; + + struct registry + { + // resource id -> (pointer -> element count) of its live allocations + static inline std::map< int, std::map< void const*, std::size_t > > live_allocations; + static inline std::vector< std::string > failures; + static inline long allocations = 0; + + static void reset() { live_allocations.clear(); failures.clear(); allocations = 0; } + + static std::size_t live( int id ) + { + auto const it = live_allocations.find( id ); + return it == live_allocations.end() ? 0 : it->second.size(); + } + + static std::size_t live_total() + { + std::size_t n = 0; + for ( auto const& [id, ptrs] : live_allocations ) n += ptrs.size(); + return n; + } + + static void on_allocate( int id, void const* p, std::size_t n ) + { + ++allocations; + live_allocations[id][p] = n; + } + + // Returns true when the pointer is live on `id` with count `n`; otherwise records a failure and forgets the + // pointer wherever it is live, so the memory is still released exactly once. + static bool on_deallocate( int id, void const* p, std::size_t n ) + { + auto& mine = live_allocations[id]; + auto const it = mine.find( p ); + if ( it != mine.end() && it->second == n ) + { + mine.erase( it ); + return true; + } + failures.push_back( "resource " + std::to_string( id ) + " deallocated a pointer it did not allocate (count " + std::to_string( n ) + ")" ); + for ( auto& [other, ptrs] : live_allocations ) ptrs.erase( p ); + return false; + } + }; + + template < typename T, bool POCCA, bool POCMA, bool POCS > + struct tracking_allocator + { + typedef T value_type; + typedef std::bool_constant< POCCA > propagate_on_container_copy_assignment; + typedef std::bool_constant< POCMA > propagate_on_container_move_assignment; + typedef std::bool_constant< POCS > propagate_on_container_swap; + typedef std::false_type is_always_equal; + + template < typename U > + struct rebind { typedef tracking_allocator< U, POCCA, POCMA, POCS > other; }; + + int id = 0; + + tracking_allocator() noexcept = default; + explicit tracking_allocator( int resource ) noexcept : id{ resource } {} + template < typename U > + tracking_allocator( tracking_allocator< U, POCCA, POCMA, POCS > const& other ) noexcept : id{ other.id } {} + + T* allocate( std::size_t n ) + { + T* const p = std::allocator< T >{}.allocate( n ); + registry::on_allocate( id, p, n ); + return p; + } + + void deallocate( T* p, std::size_t n ) noexcept + { + registry::on_deallocate( id, p, n ); + std::allocator< T >{}.deallocate( p, n ); + } + + friend bool operator == ( tracking_allocator const& a, tracking_allocator const& b ) noexcept { return a.id == b.id; } + }; + + // Resets the registry and the tracked counters; call at the start of each test. + inline void reset() + { + registry::reset(); + tracked::reset(); + } + + // Every allocation released by the resource that made it, every element destroyed exactly once. + // Uses Catch macros, so include this header after catch.hpp. + inline void require_balanced() + { + INFO( "first failure: " << ( registry::failures.empty() ? std::string{ "none" } : registry::failures.front() ) ); + REQUIRE( registry::failures.empty() ); + REQUIRE( registry::live_total() == 0 ); + REQUIRE( tracked::live == 0 ); + REQUIRE( tracked::constructed == tracked::destroyed ); + } +} + +#endif // S3_ALLOC_HPP_INCLUDED diff --git a/tests/cases/s3_r1.hpp b/tests/cases/s3_r1.hpp new file mode 100644 index 0000000..be47fa4 --- /dev/null +++ b/tests/cases/s3_r1.hpp @@ -0,0 +1,340 @@ +// S3-R1 (PR-5): private RAII storage and the size invariant. +#include "./s3_alloc.hpp" + +#include +#include + +namespace s3_r1 +{ + using s3_alloc::require_balanced; + + template< typename M > concept has_public_row_field = requires( M& m ) { m.row_; }; + template< typename M > concept has_public_col_field = requires( M& m ) { m.col_; }; + template< typename M > concept has_public_dat_field = requires( M& m ) { m.dat_; }; + template< typename M > concept has_public_allocator_field = requires( M& m ) { m.allocator_; }; + + static_assert( !has_public_row_field< feng::matrix > ); + static_assert( !has_public_col_field< feng::matrix > ); + static_assert( !has_public_dat_field< feng::matrix > ); + static_assert( !has_public_allocator_field< feng::matrix > ); + static_assert( !has_public_row_field< feng::matrix > && !has_public_dat_field< feng::matrix > ); + + template< typename M > + void require_consistent( M const& m ) + { + REQUIRE( m.size() == m.row() * m.col() ); + REQUIRE( static_cast< std::size_t >( m.end() - m.begin() ) == m.size() ); + std::size_t visited = 0; + for ( auto const& v : m ) { (void)v; ++visited; } + REQUIRE( visited == m.size() ); + } + + // Goes through a reference so the compiler sees no direct self-move. + template< typename M > + void self_move_assign( M& m, M& alias ) { m = std::move( alias ); } + + inline feng::matrix filled( std::size_t r, std::size_t c ) + { + feng::matrix m{ r, c }; + for ( std::size_t i = 0; i != r * c; ++i ) m.data()[i] = static_cast( i ) + 0.25; + return m; + } +} + +TEST_CASE( "S3 self-move-assign keeps a consistent 3x4 matrix", "[S3][S3-R1]" ) +{ + auto m = s3_r1::filled( 3, 4 ); + s3_r1::self_move_assign( m, m ); + REQUIRE( m.row() == 3 ); + REQUIRE( m.col() == 4 ); + REQUIRE( m.size() == 12 ); + REQUIRE( m.end() - m.begin() == 12 ); + std::size_t i = 0; + for ( auto const& v : m ) + { + REQUIRE( v == static_cast( i ) + 0.25 ); + ++i; + } + REQUIRE( i == 12 ); +} + +TEST_CASE( "S3 moved-from matrix is empty and consistent", "[S3][S3-R1]" ) +{ + SECTION( "move construction" ) + { + auto a = s3_r1::filled( 3, 4 ); + feng::matrix b{ std::move( a ) }; + REQUIRE( a.row() == 0 ); + REQUIRE( a.col() == 0 ); + REQUIRE( a.size() == 0 ); + s3_r1::require_consistent( a ); + REQUIRE( b.row() == 3 ); + REQUIRE( b.col() == 4 ); + REQUIRE( b[2][3] == 11.25 ); + s3_r1::require_consistent( b ); + } + SECTION( "move assignment" ) + { + auto a = s3_r1::filled( 3, 4 ); + auto b = s3_r1::filled( 2, 5 ); + b = std::move( a ); + REQUIRE( a.row() == 0 ); + REQUIRE( a.col() == 0 ); + REQUIRE( a.size() == 0 ); + s3_r1::require_consistent( a ); + REQUIRE( b.row() == 3 ); + REQUIRE( b.col() == 4 ); + REQUIRE( b[1][0] == 4.25 ); + s3_r1::require_consistent( b ); + a = s3_r1::filled( 1, 2 ); // a moved-from matrix is usable again + REQUIRE( a.size() == 2 ); + s3_r1::require_consistent( a ); + } +} + +namespace s3_r1 +{ + template < bool POCS > + using tracked_matrix = feng::matrix< s3_alloc::tracked, s3_alloc::tracking_allocator< s3_alloc::tracked, false, false, POCS > >; + template < typename T, bool POCS > + using counted_matrix = feng::matrix< T, s3_alloc::tracking_allocator< T, false, false, POCS > >; + + template < typename M > + void fill_tracked( M& m, int base ) + { + for ( std::size_t i = 0; i != m.size(); ++i ) m.data()[i].value = base + static_cast< int >( i ); + } + + // Element-lifetime and resource balance across the shape-changing operations, the allocator kept throughout. + template < bool POCS > + void tracked_operations() + { + typedef tracked_matrix< POCS > matrix_type; + typedef typename matrix_type::allocator_type allocator_type; + s3_alloc::reset(); + { + matrix_type m{ allocator_type{ 3 }, 3, 4 }; + fill_tracked( m, 0 ); + require_consistent( m ); + + m.resize( 5, 6 ); + require_consistent( m ); + REQUIRE( m.get_allocator().id == 3 ); + m.resize( 6, 5 ); // same size: reshapes + require_consistent( m ); + fill_tracked( m, 0 ); + m.reshape( 5, 6 ); + require_consistent( m ); + REQUIRE( m.row() == 5 ); + + auto const c = m.clone( 1, 4, 2, 5 ); + require_consistent( c ); + REQUIRE( c.get_allocator().id == 3 ); + REQUIRE( c[0][0].value == 1 * 6 + 2 ); + m.clone( c, 0, 2, 0, 3 ); + require_consistent( m ); + REQUIRE( m.get_allocator().id == 3 ); + REQUIRE( m.row() == 2 ); + + m.shrink_to_size( 4, 4 ); + require_consistent( m ); + REQUIRE( m.get_allocator().id == 3 ); + REQUIRE( m[1][2].value == c[1][2].value ); + REQUIRE( m[3][3].value == 0 ); + + auto const t = m.transpose(); + require_consistent( t ); + REQUIRE( t.get_allocator().id == 3 ); + REQUIRE( t[2][1].value == m[1][2].value ); + + auto const same = m.template astype< s3_alloc::tracked >(); + require_consistent( same ); + REQUIRE( same.get_allocator().id == 3 ); + + // copy(rhs) on unequal resources: the destination keeps its own allocator (F04). + matrix_type dst{ allocator_type{ 4 }, 1, 2 }; + dst.copy( t ); + require_consistent( dst ); + REQUIRE( dst.get_allocator().id == 4 ); + REQUIRE( dst.row() == 4 ); + REQUIRE( dst[2][1].value == t[2][1].value ); + dst.copy( m ); // same shape: no reallocation + require_consistent( dst ); + REQUIRE( dst.get_allocator().id == 4 ); + REQUIRE( dst[1][2].value == m[1][2].value ); + REQUIRE( s3_alloc::registry::live( 4 ) == 1 ); + + m.clear(); + require_consistent( m ); + REQUIRE( m.get_allocator().id == 3 ); + } + require_balanced(); + REQUIRE( s3_alloc::registry::live( 3 ) == 0 ); + REQUIRE( s3_alloc::registry::live( 4 ) == 0 ); + } + + // Arithmetic, astype and converting constructors on a stateful allocator: results use the operand's resource. + template < bool POCS > + void counted_arithmetic() + { + typedef counted_matrix< double, POCS > matrix_type; + typedef typename matrix_type::allocator_type allocator_type; + s3_alloc::reset(); + { + matrix_type m{ allocator_type{ 5 }, 2, 3 }; + for ( std::size_t i = 0; i != m.size(); ++i ) m.data()[i] = static_cast( i ) + 0.5; + matrix_type sq{ allocator_type{ 5 }, 2, 2 }; + sq[0][0] = 2.0; sq[0][1] = 1.0; sq[1][0] = 1.0; sq[1][1] = 3.0; + auto const t = m.transpose(); + for ( auto const& r : { m + m, m - m, m + 1.0, 1.0 + m, m - 1.0, 1.0 - m, m * 2.0, 2.0 * m, m / 2.0, sq * m, sq / sq } ) + { + require_consistent( r ); + REQUIRE( r.get_allocator().id == 5 ); + } + auto p = m * t; + require_consistent( p ); + REQUIRE( p.get_allocator().id == 5 ); + p *= sq; + require_consistent( p ); + REQUIRE( p.get_allocator().id == 5 ); + p /= sq; + require_consistent( p ); + REQUIRE( p.get_allocator().id == 5 ); + p += 1.0; p -= p; p *= 2.0; p /= 2.0; + require_consistent( p ); + REQUIRE( p.get_allocator().id == 5 ); + + auto const f = m.template astype< float >(); + require_consistent( f ); + REQUIRE( f.get_allocator().id == 5 ); + REQUIRE( f[1][2] == 5.5f ); + + counted_matrix< float, POCS > const g{ m }; + require_consistent( g ); + REQUIRE( g.get_allocator().id == 5 ); + REQUIRE( g[1][1] == 4.5f ); + + counted_matrix< float, POCS > h{ typename counted_matrix< float, POCS >::allocator_type{ 6 }, 1, 1 }; + h.copy( m ); + require_consistent( h ); + REQUIRE( h.get_allocator().id == 6 ); + REQUIRE( h[0][2] == 2.5f ); + } + require_balanced(); + } +} + +TEST_CASE( "S3 size equals rows times cols after every operation", "[S3][S3-R1]" ) +{ + SECTION( "std::allocator, double" ) + { + auto m = s3_r1::filled( 3, 4 ); + s3_r1::require_consistent( m ); + + feng::matrix copy{ m }; + s3_r1::require_consistent( copy ); + REQUIRE( copy == m ); + + feng::matrix assigned{ 7, 7 }; + assigned = m; + s3_r1::require_consistent( assigned ); + REQUIRE( assigned == m ); + + feng::matrix& alias = assigned; + assigned = alias; + s3_r1::require_consistent( assigned ); + REQUIRE( assigned == m ); + + feng::matrix other{ 2, 2, 1.0 }; + other.swap( copy ); + s3_r1::require_consistent( other ); + s3_r1::require_consistent( copy ); + REQUIRE( copy.row() == 2 ); + REQUIRE( other.row() == 3 ); + + m.resize( 5, 6 ); + s3_r1::require_consistent( m ); + m.reshape( 6, 5 ); + s3_r1::require_consistent( m ); + REQUIRE( m.row() == 6 ); + + auto const c = m.clone( 1, 4, 2, 5 ); + s3_r1::require_consistent( c ); + REQUIRE( c.size() == 9 ); + + m.shrink_to_size( 2, 3 ); + s3_r1::require_consistent( m ); + + auto const f = m.astype(); + s3_r1::require_consistent( f ); + + auto const t = m.transpose(); + s3_r1::require_consistent( t ); + REQUIRE( t.row() == 3 ); + + auto const sum = m + m; + s3_r1::require_consistent( sum ); + auto const prod = m * t; + s3_r1::require_consistent( prod ); + REQUIRE( prod.row() == 2 ); + REQUIRE( prod.col() == 2 ); + + auto const diff = m - m; + s3_r1::require_consistent( diff ); + REQUIRE( diff[1][2] == 0.0 ); + + feng::matrix const sq{ 2, 2, { 2.0, 1.0, 1.0, 3.0 } }; + auto const quot = sq / sq; + s3_r1::require_consistent( quot ); + REQUIRE( quot.row() == 2 ); + + for ( auto const& s : { m + 1.0, 1.0 + m, m - 1.0, 1.0 - m, m * 2.0, 2.0 * m, m / 2.0 } ) + { + s3_r1::require_consistent( s ); + REQUIRE( s.row() == 2 ); + REQUIRE( s.col() == 3 ); + } + REQUIRE( ( m * 2.0 )[1][2] == 2.0 * m[1][2] ); + + auto compound = m; + compound += m; s3_r1::require_consistent( compound ); + compound -= m; s3_r1::require_consistent( compound ); + compound += 1.0; s3_r1::require_consistent( compound ); + compound -= 1.0; s3_r1::require_consistent( compound ); + compound *= 2.0; s3_r1::require_consistent( compound ); + compound /= 2.0; s3_r1::require_consistent( compound ); + REQUIRE( compound == m ); + compound *= t; s3_r1::require_consistent( compound ); + REQUIRE( compound.row() == 2 ); + REQUIRE( compound.col() == 2 ); + compound /= sq; s3_r1::require_consistent( compound ); + REQUIRE( compound.col() == 2 ); + + feng::matrix const converted{ m }; + s3_r1::require_consistent( converted ); + REQUIRE( converted.row() == 2 ); + REQUIRE( converted[1][2] == static_cast( m[1][2] ) ); + feng::matrix const to_int{ m }; + s3_r1::require_consistent( to_int ); + + feng::matrix copied{ 4, 4 }; + copied.copy( m ); + s3_r1::require_consistent( copied ); + REQUIRE( copied.row() == 2 ); + REQUIRE( copied.col() == 3 ); + REQUIRE( copied[0][1] == static_cast( m[0][1] ) ); + feng::matrix assigned_from_double{ 1, 1 }; + assigned_from_double = m; + s3_r1::require_consistent( assigned_from_double ); + REQUIRE( assigned_from_double.size() == 6 ); + + m.clear(); + REQUIRE( m.row() == 0 ); + REQUIRE( m.col() == 0 ); + s3_r1::require_consistent( m ); + } + SECTION( "tracked elements, POCS false" ) { s3_r1::tracked_operations< false >(); } + SECTION( "tracked elements, POCS true" ) { s3_r1::tracked_operations< true >(); } + SECTION( "stateful allocator arithmetic, POCS false" ) { s3_r1::counted_arithmetic< false >(); } + SECTION( "stateful allocator arithmetic, POCS true" ) { s3_r1::counted_arithmetic< true >(); } +} diff --git a/tests/cases/s3_r2.hpp b/tests/cases/s3_r2.hpp new file mode 100644 index 0000000..159c04e --- /dev/null +++ b/tests/cases/s3_r2.hpp @@ -0,0 +1,226 @@ +// S3-R2 (PR-5): element lifetimes and stateful allocators (R04). +#include "./s3_alloc.hpp" + +#include +#include +#include + +namespace s3_r2 +{ + template < bool POCCA, bool POCMA, bool POCS > + using tracked_matrix = feng::matrix< s3_alloc::tracked, s3_alloc::tracking_allocator< s3_alloc::tracked, POCCA, POCMA, POCS > >; + + using s3_alloc::require_balanced; + + // Copy-assign a 3x4 matrix on resource 1 into a 2x5 matrix on resource 2 (unequal allocators), then destroy both. + template < bool POCCA, bool POCMA, bool POCS > + void copy_assign_unequal() + { + typedef tracked_matrix< POCCA, POCMA, POCS > matrix_type; + typedef typename matrix_type::allocator_type allocator_type; + s3_alloc::reset(); + { + matrix_type const src{ allocator_type{ 1 }, 3, 4, s3_alloc::tracked{ 7 } }; + matrix_type dst{ allocator_type{ 2 }, 2, 5, s3_alloc::tracked{ 9 } }; + REQUIRE( src.get_allocator() != dst.get_allocator() ); + dst = src; + REQUIRE( dst.get_allocator().id == ( POCCA ? 1 : 2 ) ); + REQUIRE( src.get_allocator().id == 1 ); + REQUIRE( dst.row() == 3 ); + REQUIRE( dst.col() == 4 ); + REQUIRE( dst.size() == 12 ); + for ( auto const& e : dst ) REQUIRE( e.value == 7 ); + REQUIRE( s3_alloc::registry::live( 1 ) == ( POCCA ? 2 : 1 ) ); + REQUIRE( s3_alloc::registry::live( 2 ) == ( POCCA ? 0 : 1 ) ); + } + REQUIRE( s3_alloc::registry::live( 1 ) == 0 ); + REQUIRE( s3_alloc::registry::live( 2 ) == 0 ); + require_balanced(); + } +} + +TEST_CASE( "S3 unequal non-propagating copy-assign frees each resource's own allocations", "[S3][S3-R2]" ) +{ + SECTION( "POCMA false, POCS false" ) { s3_r2::copy_assign_unequal< false, false, false >(); } + SECTION( "POCMA false, POCS true" ) { s3_r2::copy_assign_unequal< false, false, true >(); } + SECTION( "POCMA true, POCS false" ) { s3_r2::copy_assign_unequal< false, true, false >(); } + SECTION( "POCMA true, POCS true" ) { s3_r2::copy_assign_unequal< false, true, true >(); } +} + +namespace s3_r2 +{ + // Fills m with base, base+1, ... in row order. + template < typename M > + void fill_from( M& m, int base ) + { + for ( std::size_t i = 0; i != m.size(); ++i ) m.data()[i].value = base + static_cast< int >( i ); + } + + // m is r x c with size r*c, end-begin == size, and holds base, base+1, ... in row order. + template < typename M > + void require_holds( M const& m, std::size_t r, std::size_t c, int base ) + { + REQUIRE( m.row() == r ); + REQUIRE( m.col() == c ); + REQUIRE( m.size() == r * c ); + REQUIRE( static_cast< std::size_t >( m.end() - m.begin() ) == m.size() ); + int expected = base; + for ( auto const& e : m ) REQUIRE( e.value == expected++ ); + REQUIRE( expected == base + static_cast< int >( r * c ) ); + } + + template < typename M > + void require_moved_from( M const& m ) + { + REQUIRE( m.row() == 0 ); + REQUIRE( m.col() == 0 ); + REQUIRE( m.size() == 0 ); + REQUIRE( m.begin() == m.end() ); + } + + // Goes through references so the compiler sees no direct self-assignment. + template < typename M > void self_copy_assign( M& m, M const& alias ) { m = alias; } + template < typename M > void self_move_assign( M& m, M& alias ) { m = std::move( alias ); } + + // a: 3x4 on resource 1, holding 0..11; b: 2x5 on resource `other`, holding 100..109. Every scenario runs in + // its own scope and ends with require_balanced(). + template < bool POCCA, bool POCMA, bool POCS > + void every_operation( int const other ) + { + typedef tracked_matrix< POCCA, POCMA, POCS > matrix_type; + typedef typename matrix_type::allocator_type allocator_type; + bool const equal = other == 1; + auto make_a = []{ matrix_type m{ allocator_type{ 1 }, 3, 4 }; fill_from( m, 0 ); return m; }; + auto make_b = [other]{ matrix_type m{ allocator_type{ other }, 2, 5 }; fill_from( m, 100 ); return m; }; + + SECTION( "copy-construct" ) + { + s3_alloc::reset(); + { + auto const a = make_a(); + matrix_type c{ a }; + REQUIRE( c.get_allocator() == std::allocator_traits< allocator_type >::select_on_container_copy_construction( a.get_allocator() ) ); + REQUIRE( c.get_allocator().id == 1 ); + require_holds( c, 3, 4, 0 ); + require_holds( a, 3, 4, 0 ); + } + require_balanced(); + } + SECTION( "copy-assign" ) + { + s3_alloc::reset(); + { + auto const a = make_a(); + auto b = make_b(); + b = a; + REQUIRE( b.get_allocator().id == ( POCCA ? 1 : other ) ); + REQUIRE( a.get_allocator().id == 1 ); + require_holds( b, 3, 4, 0 ); + require_holds( a, 3, 4, 0 ); + } + require_balanced(); + } + SECTION( "move-construct" ) + { + s3_alloc::reset(); + { + auto a = make_a(); + matrix_type c{ std::move( a ) }; + REQUIRE( c.get_allocator().id == 1 ); + require_holds( c, 3, 4, 0 ); + require_moved_from( a ); + } + require_balanced(); + } + SECTION( "move-assign" ) + { + s3_alloc::reset(); + { + auto a = make_a(); + auto b = make_b(); + b = std::move( a ); + REQUIRE( b.get_allocator().id == ( POCMA ? 1 : other ) ); + require_holds( b, 3, 4, 0 ); + require_moved_from( a ); + a = make_a(); // a moved-from matrix is usable again + require_holds( a, 3, 4, 0 ); + } + require_balanced(); + } + SECTION( "member swap" ) + { + s3_alloc::reset(); + { + auto a = make_a(); + auto b = make_b(); + a.swap( b ); + REQUIRE( a.get_allocator().id == ( POCS ? other : 1 ) ); + REQUIRE( b.get_allocator().id == ( POCS ? 1 : other ) ); + require_holds( a, 2, 5, 100 ); + require_holds( b, 3, 4, 0 ); + if ( !equal ) + { + REQUIRE( s3_alloc::registry::live( 1 ) == 1 ); + REQUIRE( s3_alloc::registry::live( other ) == 1 ); + } + } + require_balanced(); + } + SECTION( "free swap" ) + { + s3_alloc::reset(); + { + auto a = make_a(); + auto b = make_b(); + using std::swap; + swap( a, b ); + REQUIRE( a.get_allocator().id == ( POCS ? other : 1 ) ); + REQUIRE( b.get_allocator().id == ( POCS ? 1 : other ) ); + require_holds( a, 2, 5, 100 ); + require_holds( b, 3, 4, 0 ); + } + require_balanced(); + } + SECTION( "self-copy-assign" ) + { + s3_alloc::reset(); + { + auto a = make_a(); + self_copy_assign( a, a ); + REQUIRE( a.get_allocator().id == 1 ); + require_holds( a, 3, 4, 0 ); + } + require_balanced(); + } + SECTION( "self-move-assign" ) + { + s3_alloc::reset(); + { + auto a = make_a(); + self_move_assign( a, a ); + REQUIRE( a.get_allocator().id == 1 ); + require_holds( a, 3, 4, 0 ); + } + require_balanced(); + } + } + + template < bool POCCA, bool POCMA, bool POCS > + void every_resource_pair() + { + SECTION( "equal resources" ) { every_operation< POCCA, POCMA, POCS >( 1 ); } + SECTION( "unequal resources" ) { every_operation< POCCA, POCMA, POCS >( 2 ); } + } +} + +TEST_CASE( "S3 every propagation combination balances each resource", "[S3][S3-R2]" ) +{ + SECTION( "POCCA 0, POCMA 0, POCS 0" ) { s3_r2::every_resource_pair< false, false, false >(); } + SECTION( "POCCA 0, POCMA 0, POCS 1" ) { s3_r2::every_resource_pair< false, false, true >(); } + SECTION( "POCCA 0, POCMA 1, POCS 0" ) { s3_r2::every_resource_pair< false, true, false >(); } + SECTION( "POCCA 0, POCMA 1, POCS 1" ) { s3_r2::every_resource_pair< false, true, true >(); } + SECTION( "POCCA 1, POCMA 0, POCS 0" ) { s3_r2::every_resource_pair< true, false, false >(); } + SECTION( "POCCA 1, POCMA 0, POCS 1" ) { s3_r2::every_resource_pair< true, false, true >(); } + SECTION( "POCCA 1, POCMA 1, POCS 0" ) { s3_r2::every_resource_pair< true, true, false >(); } + SECTION( "POCCA 1, POCMA 1, POCS 1" ) { s3_r2::every_resource_pair< true, true, true >(); } +} diff --git a/tests/cases/s3_r3.hpp b/tests/cases/s3_r3.hpp new file mode 100644 index 0000000..42a96f1 --- /dev/null +++ b/tests/cases/s3_r3.hpp @@ -0,0 +1,78 @@ +// S3-R3 (PR-5): the element-type constraint feng::matrix_element. +#include +#include +#include + +static_assert( feng::matrix_element< int > ); +static_assert( feng::matrix_element< double > ); +static_assert( feng::matrix_element< std::uint8_t > ); +static_assert( feng::matrix_element< std::complex< float > > ); +static_assert( feng::matrix_element< std::complex< double > > ); +static_assert( feng::matrix_element< std::complex< long double > > ); +static_assert( !feng::matrix_element< bool > ); +static_assert( !feng::matrix_element< std::string > ); + +namespace s3_r3 +{ + template < typename... Ts > + inline constexpr bool all_elements = ( feng::matrix_element< Ts > && ... ); + + struct throwing_copy + { + throwing_copy() noexcept = default; + throwing_copy( throwing_copy const& ) noexcept( false ) {} + throwing_copy( throwing_copy&& ) noexcept = default; + throwing_copy& operator=( throwing_copy const& ) noexcept = default; + throwing_copy& operator=( throwing_copy&& ) noexcept = default; + }; + + struct throwing_move + { + throwing_move() noexcept = default; + throwing_move( throwing_move const& ) noexcept = default; + throwing_move( throwing_move&& ) noexcept( false ) {} + throwing_move& operator=( throwing_move const& ) noexcept = default; + throwing_move& operator=( throwing_move&& ) noexcept = default; + }; + + struct throwing_destructor + { + ~throwing_destructor() noexcept( false ) {} + }; + + // D-018: the size constructors default-construct elements. + struct no_default + { + explicit no_default( int ) noexcept {} + }; + + struct throwing_default + { + throwing_default() noexcept( false ) {} + }; +} + +// Every standard arithmetic type except bool, plus the fixed-width aliases. +static_assert( s3_r3::all_elements< char, signed char, unsigned char, short, unsigned short, int, unsigned int, long, + unsigned long, long long, unsigned long long, float, double, long double, + std::size_t, std::int8_t, std::int16_t, std::int32_t, std::int64_t, std::uint8_t, + std::uint16_t, std::uint32_t, std::uint64_t > ); +static_assert( s3_r3::all_elements< std::complex< float >, std::complex< double >, std::complex< long double > > ); +static_assert( !feng::matrix_element< s3_r3::throwing_copy > ); +static_assert( !feng::matrix_element< s3_r3::throwing_move > ); +static_assert( !feng::matrix_element< s3_r3::throwing_destructor > ); +static_assert( !feng::matrix_element< s3_r3::no_default > ); +static_assert( !feng::matrix_element< s3_r3::throwing_default > ); +static_assert( !feng::matrix_element< int const > ); +static_assert( !feng::matrix_element< int& > ); + +TEST_CASE( "S3 matrix_element accepts arithmetic and complex types and rejects bool and std::string", "[S3][S3-R3]" ) +{ + static_assert( feng::matrix_element< int > && feng::matrix_element< double > && feng::matrix_element< std::uint8_t > ); + static_assert( feng::matrix_element< std::complex< float > > && feng::matrix_element< std::complex< double > > && feng::matrix_element< std::complex< long double > > ); + static_assert( !feng::matrix_element< bool > && !feng::matrix_element< std::string > ); + feng::matrix< std::uint8_t > const mask{ 2, 3 }; + feng::matrix< std::complex< long double > > const z{ 3, 2 }; + REQUIRE( mask.size() == 6 ); + REQUIRE( z.size() == 6 ); +} diff --git a/tests/cases/s3_r4.hpp b/tests/cases/s3_r4.hpp new file mode 100644 index 0000000..316f2c9 --- /dev/null +++ b/tests/cases/s3_r4.hpp @@ -0,0 +1,51 @@ +// S3-R4 (PR-5): matrix from a view copies exactly the viewed rectangle (F03). +#include + +namespace s3_r4 +{ + inline feng::matrix distinct( std::size_t r, std::size_t c ) + { + feng::matrix m{ r, c }; + for ( std::size_t i = 0; i != r; ++i ) + for ( std::size_t j = 0; j != c; ++j ) + m[i][j] = static_cast( 100 * i + j ) + 0.5; + return m; + } + + inline void require_rectangle( feng::matrix const& m, std::size_t r0, std::size_t r1, std::size_t c0, std::size_t c1 ) + { + auto const view = feng::make_view( m, { r0, r1 }, { c0, c1 } ); + feng::matrix const out{ view }; + REQUIRE( out.row() == r1 - r0 ); + REQUIRE( out.col() == c1 - c0 ); + REQUIRE( out.size() == out.row() * out.col() ); + REQUIRE( static_cast< std::size_t >( out.end() - out.begin() ) == out.size() ); + auto it = out.begin(); + for ( std::size_t i = r0; i != r1; ++i ) + for ( std::size_t j = c0; j != c1; ++j ) + REQUIRE( *it++ == m[i][j] ); + } +} + +TEST_CASE( "S3 matrix from an offset view copies exactly the rectangle", "[S3][S3-R4]" ) +{ + auto const m = s3_r4::distinct( 5, 6 ); + SECTION( "2x3 view at offset (1, 2)" ) + { + auto const view = feng::make_view( m, { 1UL, 3UL }, { 2UL, 5UL } ); + feng::matrix const out{ view }; + REQUIRE( out.row() == 2 ); + REQUIRE( out.col() == 3 ); + REQUIRE( out.size() == 6 ); + double const expected[] = { 102.5, 103.5, 104.5, 202.5, 203.5, 204.5 }; + std::size_t k = 0; + for ( auto const& v : out ) REQUIRE( v == expected[k++] ); + REQUIRE( k == 6 ); + } + SECTION( "1x1 view" ) { s3_r4::require_rectangle( m, 3, 4, 4, 5 ); } + SECTION( "full-size view" ) { s3_r4::require_rectangle( m, 0, 5, 0, 6 ); } + SECTION( "last-column view" ) { s3_r4::require_rectangle( m, 0, 5, 5, 6 ); } + SECTION( "first-row view" ) { s3_r4::require_rectangle( m, 0, 1, 0, 6 ); } + SECTION( "interior 1xN view" ) { s3_r4::require_rectangle( m, 4, 5, 1, 5 ); } + SECTION( "interior Nx1 view" ) { s3_r4::require_rectangle( m, 1, 4, 0, 1 ); } +} diff --git a/tests/cases/s3_r5.hpp b/tests/cases/s3_r5.hpp new file mode 100644 index 0000000..616570b --- /dev/null +++ b/tests/cases/s3_r5.hpp @@ -0,0 +1,263 @@ +// S3-R5 (PR-2): noexcept special members and the allocation check before allocating. +#include "./s2_death.hpp" + +#include +#include + +namespace s3_r5 +{ + typedef feng::matrix< double > md; + static_assert( std::is_nothrow_default_constructible_v< md > ); + static_assert( std::is_nothrow_copy_constructible_v< md > ); + static_assert( std::is_nothrow_move_constructible_v< md > ); + static_assert( std::is_nothrow_copy_assignable_v< md > ); + static_assert( std::is_nothrow_move_assignable_v< md > ); + static_assert( std::is_nothrow_destructible_v< md > ); + static_assert( noexcept( std::declval< md& >().swap( std::declval< md& >() ) ) ); + static_assert( std::is_nothrow_swappable_v< md > ); +} + +TEST_CASE( "S3 huge resize aborts before allocating", "[S3][S3-R5]" ) +{ + S2_REQUIRE_DEATH( []{ feng::matrix m; m.resize( 1ULL << 61, 8 ); }, "size" ); +} + +// S3-R5 (D-012): every public API family is noexcept; one or more calls per S1 API family (docs/stages/S1/spec.md), +// plus the special members, swap, resize, clone, astype, I/O and callback-taking functions. String arguments are +// passed as lvalues and defaulted string parameters explicitly: building a std::string at the call site can throw, +// which is the caller's expression, not the library's. +namespace s3_r5_api +{ + using std::declval; + typedef feng::matrix< double > md; + typedef feng::matrix< std::complex< double > > mc; + typedef feng::matrix< float, std::allocator< float > > mf; + typedef feng::matrix_view< double, std::allocator< double > > vd; + inline auto const unary = []( double& x ) noexcept { x += 1.0; }; + + // construction + static_assert( noexcept( md{} ) ); + static_assert( noexcept( md{ 3, 4 } ) ); + static_assert( noexcept( md{ 3, 4, 1.0 } ) ); + static_assert( noexcept( md{ declval< md const& >() } ) ); + static_assert( noexcept( md{ declval< md&& >() } ) ); + static_assert( noexcept( feng::zeros< double >( 3, 4 ) ) ); + static_assert( noexcept( feng::ones< double >( 3 ) ) ); + static_assert( noexcept( feng::eye< double >( 3 ) ) ); + static_assert( noexcept( feng::arange< double >( 0, 4 ) ) ); + static_assert( noexcept( feng::linspace( 0.0, 1.0 ) ) ); + static_assert( noexcept( feng::magic( 4 ) ) ); + static_assert( noexcept( feng::hilbert< double >( 3 ) ) ); + static_assert( noexcept( feng::rand< double >( 3 ) ) ); + static_assert( noexcept( feng::zeros_like( declval< md const& >() ) ) ); + static_assert( noexcept( feng::blkdiag( declval< md const& >(), declval< md const& >() ) ) ); + static_assert( noexcept( feng::make_diag( declval< md const& >() ) ) ); + // access + static_assert( noexcept( declval< md& >()[ 0 ] ) ); + static_assert( noexcept( declval< md& >()[ 0 ][ 0 ] ) ); + static_assert( noexcept( declval< md const& >().item() ) ); + static_assert( noexcept( declval< md& >().data() ) ); + static_assert( noexcept( declval< md const& >().shape() ) ); + static_assert( noexcept( declval< md const& >().size() ) ); + static_assert( noexcept( declval< md& >().begin() ) ); + static_assert( noexcept( declval< md& >().row_begin( 0 ) ) ); + static_assert( noexcept( declval< md& >().col_begin( 0 ) ) ); + static_assert( noexcept( declval< md& >().diag_begin() ) ); + // views + static_assert( noexcept( feng::make_view( declval< md const& >(), { 0, 1 }, { 0, 1 } ) ) ); + static_assert( noexcept( declval< md const& >().clone( 0, 1, 0, 1 ) ) ); + static_assert( noexcept( declval< md& >().clone( declval< md const& >(), 0, 1, 0, 1 ) ) ); + static_assert( noexcept( md{ declval< vd const& >() } ) ); + // shape + static_assert( noexcept( declval< md& >().reshape( 2, 2 ) ) ); + static_assert( noexcept( declval< md& >().resize( 2, 2 ) ) ); + static_assert( noexcept( declval< md const& >().transpose() ) ); + static_assert( noexcept( feng::transpose( declval< md const& >() ) ) ); + static_assert( noexcept( feng::fliplr( declval< md const& >() ) ) ); + static_assert( noexcept( feng::tril( declval< md const& >() ) ) ); + static_assert( noexcept( declval< md& >().clear() ) ); + static_assert( noexcept( declval< md& >().shrink_to_size( 1, 1 ) ) ); + // arithmetic + static_assert( noexcept( declval< md const& >() + declval< md const& >() ) ); + static_assert( noexcept( declval< md const& >() - 1.0 ) ); + static_assert( noexcept( 2.0 * declval< md const& >() ) ); + static_assert( noexcept( declval< md const& >() * declval< md const& >() ) ); + static_assert( noexcept( declval< md const& >() / 2.0 ) ); + static_assert( noexcept( declval< md& >() += 1.0 ) ); + static_assert( noexcept( -declval< md const& >() ) ); + static_assert( noexcept( declval< md const& >() ^ 2 ) ); + // elementwise + static_assert( noexcept( feng::sin( declval< md const& >() ) ) ); + static_assert( noexcept( feng::abs( declval< md const& >() ) ) ); + static_assert( noexcept( feng::pow( declval< md const& >(), 2.0 ) ) ); + static_assert( noexcept( feng::real( declval< mc const& >() ) ) ); + static_assert( noexcept( feng::clip( 0.0, 1.0 )( declval< md const& >() ) ) ); + static_assert( noexcept( declval< md const& >().astype< float >() ) ); + // reductions + static_assert( noexcept( feng::sum( declval< md const& >() ) ) ); + static_assert( noexcept( feng::mean( declval< md const& >() ) ) ); + static_assert( noexcept( feng::variance( declval< md const& >() ) ) ); + static_assert( noexcept( feng::max( declval< md const& >() ) ) ); + static_assert( noexcept( declval< md const& >().minmax() ) ); + static_assert( noexcept( feng::norm_1( declval< md const& >() ) ) ); + static_assert( noexcept( feng::tr( declval< md const& >() ) ) ); + static_assert( noexcept( feng::isequal( declval< md const& >(), declval< md const& >() ) ) ); + static_assert( noexcept( feng::is_symmetric( declval< md const& >() ) ) ); + // linalg + static_assert( noexcept( declval< md const& >().det() ) ); + static_assert( noexcept( feng::det( declval< md const& >() ) ) ); + static_assert( noexcept( feng::inverse( declval< md const& >() ) ) ); + static_assert( noexcept( feng::lu_solver( declval< md const& >(), declval< md const& >() ) ) ); + static_assert( noexcept( feng::pinv( declval< md const& >() ) ) ); + static_assert( noexcept( feng::expm( declval< md const& >() ) ) ); + // signal + static_assert( noexcept( feng::conv( declval< md const& >(), declval< md const& >() ) ) ); + static_assert( noexcept( feng::fft( declval< mc const& >() ) ) ); + static_assert( noexcept( feng::fftshift( declval< md const& >() ) ) ); + static_assert( noexcept( feng::pooling( declval< md const& >(), 2, declval< std::string const& >() ) ) ); + // io + static_assert( noexcept( declval< md const& >().save_as_txt( "x" ) ) ); + static_assert( noexcept( declval< md& >().load_txt( "x" ) ) ); + static_assert( noexcept( declval< md const& >().save_as_binary( "x" ) ) ); + static_assert( noexcept( declval< md& >().load_binary( declval< std::string const& >() ) ) ); + static_assert( noexcept( declval< md& >().load_npy( "x" ) ) ); + static_assert( noexcept( declval< md const& >().save_as_npy( "x" ) ) ); + static_assert( noexcept( declval< std::ostream& >() << declval< md const& >() ) ); + static_assert( noexcept( declval< std::istream& >() >> declval< md& >() ) ); + static_assert( noexcept( feng::disp( declval< md const& >() ) ) ); + // image + static_assert( noexcept( declval< md const& >().save_as_bmp( declval< std::string const& >(), declval< std::string const& >() ) ) ); + static_assert( noexcept( declval< md const& >().save_as_png( declval< std::string const& >(), declval< std::string const& >() ) ) ); + static_assert( noexcept( declval< md const& >().save_as_pgm( "x" ) ) ); + static_assert( noexcept( declval< md const& >().plot( declval< std::string const& >(), declval< std::string const& >() ) ) ); + static_assert( noexcept( feng::load_bmp( declval< std::string const& >() ) ) ); + static_assert( noexcept( feng::save_as_bmp( declval< std::string const& >(), declval< md const& >(), declval< std::string const& >() ) ) ); + // allocator + static_assert( noexcept( mf{ std::allocator< float >{}, 2, 2 } ) ); + static_assert( noexcept( declval< md const& >().get_allocator() ) ); + // special members, swap, callbacks + static_assert( noexcept( declval< md& >() = declval< md const& >() ) ); + static_assert( noexcept( declval< md& >() = declval< md&& >() ) ); + static_assert( noexcept( declval< md& >().~md() ) ); + static_assert( noexcept( declval< md& >().swap( declval< md& >() ) ) ); + static_assert( noexcept( swap( declval< md& >(), declval< md& >() ) ) ); + static_assert( noexcept( declval< md& >().apply( unary ) ) ); + static_assert( noexcept( feng::matrix_details::for_each( declval< double* >(), declval< double* >(), unary ) ) ); +} + +TEST_CASE( "S3 every public API family is noexcept", "[S3][S3-R5]" ) +{ + // The namespace-scope static_asserts above are the check; this case records that they compiled. + REQUIRE( noexcept( s3_r5_api::md{ 2, 2 } ) ); + REQUIRE( noexcept( std::declval< s3_r5_api::md& >().resize( 3, 3 ) ) ); +} + +// S3-R5 (D-012): the size check runs on the allocator that will own the storage, before allocate() is called. +// budget_allocator: stateful, max_size() = budget bytes / sizeof(T) (default budget 100 bytes: 12 doubles, 100 +// chars); copy construction selects a default-budget allocator; no propagation; == compares budgets. Once armed, +// allocate() writes a marker to stderr, so a death test can show that the abort came before any allocation. +#include +#include + +namespace s3_r5_budget +{ + inline bool armed = false; + inline char const marker[] = "budget_allocator::allocate called"; + + template < typename T > + struct budget_allocator + { + typedef T value_type; + typedef std::false_type propagate_on_container_copy_assignment; + typedef std::false_type propagate_on_container_move_assignment; + typedef std::false_type propagate_on_container_swap; + typedef std::false_type is_always_equal; + + std::size_t budget = 100; + + budget_allocator() noexcept = default; + explicit budget_allocator( std::size_t b ) noexcept : budget{ b } {} + template < typename U > + budget_allocator( budget_allocator< U > const& other ) noexcept : budget{ other.budget } {} + + T* allocate( std::size_t n ) + { + if ( armed ) { [[maybe_unused]] auto const w = ::write( STDERR_FILENO, marker, sizeof( marker ) - 1 ); } + return std::allocator< T >{}.allocate( n ); + } + void deallocate( T* p, std::size_t n ) noexcept { std::allocator< T >{}.deallocate( p, n ); } + std::size_t max_size() const noexcept { return budget / sizeof( T ); } + budget_allocator select_on_container_copy_construction() const noexcept { return budget_allocator{}; } + + template < typename U > + friend bool operator == ( budget_allocator const& a, budget_allocator< U > const& b ) noexcept { return a.budget == b.budget; } + }; + + typedef budget_allocator< double > ad; + typedef feng::matrix< double, ad > md; + typedef feng::matrix< char, budget_allocator< char > > mc; + static_assert( std::is_same_v< md::allocator_type::value_type, md::value_type > ); // the accepted value_type case + + // Dies with a "size" message, and the dying child never reached an armed allocate(). + template < typename Callable > + void require_size_abort_before_allocate( Callable fn ) + { + S2_REQUIRE_DEATH( fn, "size" ); + s2_death::outcome const out = s2_death::run( fn ); + INFO( "child stderr: " << out.err ); + REQUIRE( out.signaled ); + REQUIRE_FALSE( s2_death::contains( out.err, marker ) ); + } +} + +TEST_CASE( "S3 copy-assign checks the size before allocating", "[S3][S3-R5]" ) +{ + using namespace s3_r5_budget; + + SECTION( "the marker shows when allocate() runs" ) + { + s2_death::outcome const out = s2_death::run( []{ armed = true; md m{ ad{ 1000 }, 2, 2 }; } ); + REQUIRE( out.exited ); + REQUIRE( s2_death::contains( out.err, marker ) ); + } + SECTION( "copy assignment checks against the destination's allocator" ) + { + require_size_abort_before_allocate( []{ md src{ ad{ 1000 }, 5, 5 }; md dst{ ad{ 100 } }; armed = true; dst = src; } ); + } + SECTION( "copy construction checks against the selected allocator" ) + { + require_size_abort_before_allocate( []{ md src{ ad{ 1000 }, 5, 5 }; armed = true; md dst{ src }; (void)dst; } ); + } + SECTION( "converting construction checks against the rebound allocator" ) + { + require_size_abort_before_allocate( []{ mc src{ budget_allocator< char >{ 100 }, 5, 5 }; armed = true; md dst{ src }; (void)dst; } ); + } + SECTION( "astype checks against the rebound allocator" ) + { + require_size_abort_before_allocate( []{ mc src{ budget_allocator< char >{ 100 }, 5, 5 }; armed = true; auto dst = src.astype< double >(); (void)dst; } ); + } + SECTION( "move assignment between unequal allocators checks against the destination's" ) + { + require_size_abort_before_allocate( []{ md src{ ad{ 1000 }, 5, 5 }; md dst{ ad{ 100 } }; armed = true; dst = std::move( src ); } ); + } + SECTION( "element-wise swap checks against each side's allocator" ) + { + require_size_abort_before_allocate( []{ md a{ ad{ 1000 }, 5, 5 }; md b{ ad{ 100 } }; armed = true; a.swap( b ); } ); + } + SECTION( "resize checks against the matrix's allocator" ) + { + require_size_abort_before_allocate( []{ md m{ ad{ 100 } }; armed = true; m.resize( 5, 5 ); } ); + } + SECTION( "within budget every path still works" ) + { + md src{ ad{ 1000 }, 3, 3, 2.0 }; + md dst{ ad{ 100 } }; + dst = src; + REQUIRE( dst.size() == 9 ); + md copy{ src }; + REQUIRE( copy.get_allocator().budget == 100 ); + REQUIRE( copy[2][2] == 2.0 ); + auto const as_char = src.astype< char >(); + REQUIRE( as_char.size() == 9 ); + } +} diff --git a/tests/cases/s4_r1.hpp b/tests/cases/s4_r1.hpp new file mode 100644 index 0000000..6ba125d --- /dev/null +++ b/tests/cases/s4_r1.hpp @@ -0,0 +1,301 @@ +// S4-R1 (PR-6): stride iterators with logical ends (F05); index checks in every build, address checks in the +// checked-iterator build (FENG_MATRIX_CHECKED_ITERATORS, D-020). +#include "./s2_death.hpp" + +#include +#include +#include +#include +#include + +namespace s4_r1 +{ + inline feng::matrix numbered( std::size_t r, std::size_t c ) + { + feng::matrix m{ r, c }; + for ( std::size_t i = 0; i != r; ++i ) + for ( std::size_t j = 0; j != c; ++j ) + m( i, j ) = static_cast( i * 10 + j ); + return m; + } + + template < typename It > + std::vector walk( It first, It last ) + { + std::vector ans; + for ( ; first != last; ++first ) ans.push_back( *first ); + return ans; + } +} + +TEST_CASE( "S4 one-column anti-diagonal traverses forward and backward", "[S4][S4-R1]" ) +{ + SECTION( "5x1 anti-diagonals have length 1, forward and reverse" ) + { + auto m = s4_r1::numbered( 5, 1 ); + for ( std::ptrdiff_t k = -4; k <= 0; ++k ) + { + INFO( "k = " << k ); + REQUIRE( std::distance( m.anti_diag_begin( k ), m.anti_diag_end( k ) ) == 1 ); + REQUIRE( s4_r1::walk( m.anti_diag_begin( k ), m.anti_diag_end( k ) ) == std::vector{ m( static_cast( -k ), 0 ) } ); + REQUIRE( s4_r1::walk( m.anti_diag_rbegin( k ), m.anti_diag_rend( k ) ) == std::vector{ m( static_cast( -k ), 0 ) } ); + REQUIRE( std::distance( m.anti_diag_crbegin( k ), m.anti_diag_crend( k ) ) == 1 ); + } + } + SECTION( "the last column of a 3x4 matrix has distance 3, forward and reverse" ) + { + auto m = s4_r1::numbered( 3, 4 ); + REQUIRE( std::distance( m.col_begin( 3 ), m.col_end( 3 ) ) == 3 ); + REQUIRE( s4_r1::walk( m.col_begin( 3 ), m.col_end( 3 ) ) == std::vector{ 3.0, 13.0, 23.0 } ); + REQUIRE( s4_r1::walk( m.col_rbegin( 3 ), m.col_rend( 3 ) ) == std::vector{ 23.0, 13.0, 3.0 } ); + auto const& cm = m; + REQUIRE( s4_r1::walk( cm.col_cbegin( 3 ), cm.col_cend( 3 ) ) == std::vector{ 3.0, 13.0, 23.0 } ); + } + SECTION( "diagonals of a 3x4 matrix match the index oracle" ) + { + auto m = s4_r1::numbered( 3, 4 ); + for ( std::ptrdiff_t k = -2; k <= 3; ++k ) + { + std::vector diag, anti; + for ( std::ptrdiff_t r = 0; r != 3; ++r ) + { + std::ptrdiff_t const c = r + k; + if ( c >= 0 && c < 4 ) diag.push_back( m( static_cast( r ), static_cast( c ) ) ); + } + // anti-diagonal k: k > 0 starts at (0, 3-k), k <= 0 at (-k, 3); each step goes one row down, one column left + std::ptrdiff_t r = k > 0 ? 0 : -k, c = k > 0 ? 3 - k : 3; + for ( ; r < 3 && c >= 0; ++r, --c ) anti.push_back( m( static_cast( r ), static_cast( c ) ) ); + INFO( "k = " << k ); + REQUIRE( s4_r1::walk( m.diag_begin( k ), m.diag_end( k ) ) == diag ); + REQUIRE( std::distance( m.diag_begin( k ), m.diag_end( k ) ) == static_cast( diag.size() ) ); + REQUIRE( s4_r1::walk( m.diag_rbegin( k ), m.diag_rend( k ) ) == std::vector( diag.rbegin(), diag.rend() ) ); + REQUIRE( s4_r1::walk( m.anti_diag_begin( k ), m.anti_diag_end( k ) ) == anti ); + REQUIRE( s4_r1::walk( m.anti_diag_rbegin( k ), m.anti_diag_rend( k ) ) == std::vector( anti.rbegin(), anti.rend() ) ); + } + } + SECTION( "diagonal 0 of an empty matrix is an empty range" ) + { + feng::matrix m; + REQUIRE( m.diag_begin() == m.diag_end() ); + REQUIRE( m.anti_diag_begin() == m.anti_diag_end() ); + } +} + +TEST_CASE( "S4 column and diagonal indices outside the matrix abort", "[S4][S4-R1]" ) +{ + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); auto it = m.col_begin( 4 ); (void)it; }, "matrix iterator" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); auto it = m.diag_begin( 4 ); (void)it; }, "matrix iterator" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); auto it = m.diag_begin( -3 ); (void)it; }, "matrix iterator" ); +} + +TEST_CASE( "S4 diag of an empty matrix with an offset keeps its result", "[S4][S4-R1]" ) +{ + auto all_zero = []( feng::matrix const& m ) { return std::all_of( m.begin(), m.end(), []( double x ) { return x == 0.0; } ); }; + feng::matrix const empty; + auto const d0 = feng::diag( empty, 0 ); + REQUIRE( d0.row() == 0 ); + REQUIRE( d0.col() == 0 ); + auto const d1 = feng::diag( empty, 1 ); + REQUIRE( d1.row() == 1 ); + REQUIRE( d1.col() == 1 ); + REQUIRE( all_zero( d1 ) ); + auto const dm2 = feng::diag( empty, -2 ); + REQUIRE( dm2.row() == 2 ); + REQUIRE( dm2.col() == 2 ); + REQUIRE( all_zero( dm2 ) ); + auto const v3 = feng::diag( std::vector{}, 3 ); + REQUIRE( v3.row() == 3 ); + REQUIRE( v3.col() == 3 ); + REQUIRE( all_zero( v3 ) ); + + auto const placed = feng::diag( s4_r1::numbered( 2, 2 ), 1 ); + REQUIRE( placed.row() == 3 ); + REQUIRE( placed.col() == 3 ); + for ( std::size_t i = 0; i != 3; ++i ) + for ( std::size_t j = 0; j != 3; ++j ) + { + INFO( "(" << i << ", " << j << ")" ); + double const expected = ( j == i + 1 ) ? static_cast( i * 11 ) : 0.0; + REQUIRE( placed( i, j ) == expected ); + } +} + +#ifdef FENG_MATRIX_CHECKED_ITERATORS +TEST_CASE( "S4 checked iterators abort on a past-the-end dereference", "[S4][S4-R1]" ) +{ + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); double volatile x = *m.col_end( 1 ); (void)x; }, "matrix iterator" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); double volatile x = m.col_begin( 0 )[3]; (void)x; }, "matrix iterator" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); auto it = m.col_begin( 0 ) - 1; (void)it; }, "matrix iterator" ); +} +#endif + +namespace s4_r1 +{ + template < typename It > + std::vector walk_back( It first, It last ) + { + std::vector ans; + while ( last != first ) ans.push_back( *--last ); + return ans; + } + + // the oracle, independent of the library's range code: elements (i, j) of an r x c matrix with + // pred(i, j), in increasing row order (each diagonal and anti-diagonal holds at most one element per row) + template < typename Pred > + std::vector oracle( feng::matrix const& m, Pred pred ) + { + std::vector ans; + for ( std::ptrdiff_t i = 0; i != static_cast( m.row() ); ++i ) + for ( std::ptrdiff_t j = 0; j != static_cast( m.col() ); ++j ) + if ( pred( i, j ) ) ans.push_back( m( static_cast( i ), static_cast( j ) ) ); + return ans; + } +} + +// every variant of range family `fam` at argument `arg` of the matrix `m` (and its const alias `cm`) visits +// `expected` forward and its reverse backward, and every distance equals the oracle length +#define S4_R1_REQUIRE_RANGE( fam, arg, expected ) \ + do { \ + std::vector const ex_ = ( expected ); \ + std::vector const rev_( ex_.rbegin(), ex_.rend() ); \ + std::ptrdiff_t const n_ = static_cast( ex_.size() ); \ + INFO( #fam " " << ( arg ) ); \ + REQUIRE( s4_r1::walk( m.fam##_begin( arg ), m.fam##_end( arg ) ) == ex_ ); \ + REQUIRE( s4_r1::walk_back( m.fam##_begin( arg ), m.fam##_end( arg ) ) == rev_ ); \ + REQUIRE( std::distance( m.fam##_begin( arg ), m.fam##_end( arg ) ) == n_ ); \ + REQUIRE( s4_r1::walk( cm.fam##_begin( arg ), cm.fam##_end( arg ) ) == ex_ ); \ + REQUIRE( std::distance( cm.fam##_begin( arg ), cm.fam##_end( arg ) ) == n_ ); \ + REQUIRE( s4_r1::walk( cm.fam##_cbegin( arg ), cm.fam##_cend( arg ) ) == ex_ ); \ + REQUIRE( s4_r1::walk_back( cm.fam##_cbegin( arg ), cm.fam##_cend( arg ) ) == rev_ ); \ + REQUIRE( std::distance( cm.fam##_cbegin( arg ), cm.fam##_cend( arg ) ) == n_ ); \ + REQUIRE( s4_r1::walk( m.fam##_rbegin( arg ), m.fam##_rend( arg ) ) == rev_ ); \ + REQUIRE( s4_r1::walk_back( m.fam##_rbegin( arg ), m.fam##_rend( arg ) ) == ex_ ); \ + REQUIRE( std::distance( m.fam##_rbegin( arg ), m.fam##_rend( arg ) ) == n_ ); \ + REQUIRE( s4_r1::walk( cm.fam##_rbegin( arg ), cm.fam##_rend( arg ) ) == rev_ ); \ + REQUIRE( std::distance( cm.fam##_rbegin( arg ), cm.fam##_rend( arg ) ) == n_ ); \ + REQUIRE( s4_r1::walk( cm.fam##_crbegin( arg ), cm.fam##_crend( arg ) ) == rev_ ); \ + REQUIRE( std::distance( cm.fam##_crbegin( arg ), cm.fam##_crend( arg ) ) == n_ ); \ + } while ( false ) + +TEST_CASE( "S4 every diagonal of every shape up to 4x5 traverses exactly", "[S4][S4-R1]" ) +{ + for ( std::size_t r = 1; r <= 4; ++r ) + for ( std::size_t c = 1; c <= 5; ++c ) + { + INFO( "shape " << r << "x" << c ); + auto m = s4_r1::numbered( r, c ); + auto const& cm = m; + std::ptrdiff_t const R = static_cast( r ), C = static_cast( c ); + for ( std::size_t j = 0; j != c; ++j ) + { + std::ptrdiff_t const jj = static_cast( j ); + S4_R1_REQUIRE_RANGE( col, j, s4_r1::oracle( m, [=]( std::ptrdiff_t, std::ptrdiff_t y ) { return y == jj; } ) ); + } + for ( std::ptrdiff_t k = -( R - 1 ); k < C; ++k ) + { + auto const diag = s4_r1::oracle( m, [=]( std::ptrdiff_t x, std::ptrdiff_t y ) { return y - x == k; } ); + auto const anti = s4_r1::oracle( m, [=]( std::ptrdiff_t x, std::ptrdiff_t y ) { return x + y == C - 1 - k; } ); + REQUIRE_FALSE( diag.empty() ); + REQUIRE_FALSE( anti.empty() ); + S4_R1_REQUIRE_RANGE( diag, k, diag ); + S4_R1_REQUIRE_RANGE( anti_diag, k, anti ); + std::size_t const u = static_cast( k < 0 ? -k : k ); + if ( k >= 0 ) + { + S4_R1_REQUIRE_RANGE( upper_diag, u, diag ); + S4_R1_REQUIRE_RANGE( upper_anti_diag, u, anti ); + } + if ( k <= 0 ) + { + S4_R1_REQUIRE_RANGE( lower_diag, u, diag ); + S4_R1_REQUIRE_RANGE( lower_anti_diag, u, anti ); + } + } + if ( c == 1 ) + for ( std::ptrdiff_t k = -( R - 1 ); k <= 0; ++k ) + REQUIRE( std::distance( m.anti_diag_begin( k ), m.anti_diag_end( k ) ) == 1 ); + } +} + +TEST_CASE( "S4 every range family rejects a column or diagonal index outside the matrix", "[S4][S4-R1]" ) +{ + auto const run = []( auto f ) { S2_REQUIRE_DEATH( [f]{ auto m = s4_r1::numbered( 3, 4 ); f( m ); }, "matrix iterator" ); }; + SECTION( "column index col()" ) + { + run( []( feng::matrix& m ) { auto it = m.col_begin( 4 ); (void)it; } ); + run( []( feng::matrix& m ) { auto it = m.col_end( 4 ); (void)it; } ); + run( []( feng::matrix& m ) { auto it = std::as_const( m ).col_cbegin( 4 ); (void)it; } ); + run( []( feng::matrix& m ) { auto it = m.col_rbegin( 4 ); (void)it; } ); + run( []( feng::matrix& m ) { auto it = std::as_const( m ).col_crend( 4 ); (void)it; } ); + } + SECTION( "diagonal k = col() and k = -row()" ) + { + run( []( feng::matrix& m ) { auto it = m.diag_end( 4 ); (void)it; } ); + run( []( feng::matrix& m ) { auto it = std::as_const( m ).diag_cbegin( -3 ); (void)it; } ); + run( []( feng::matrix& m ) { auto it = m.diag_rbegin( -3 ); (void)it; } ); + run( []( feng::matrix& m ) { auto it = std::as_const( m ).diag_crend( 4 ); (void)it; } ); + } + SECTION( "upper and lower diagonal indices col() and row()" ) + { + run( []( feng::matrix& m ) { auto it = m.upper_diag_begin( 4 ); (void)it; } ); + run( []( feng::matrix& m ) { auto it = std::as_const( m ).upper_diag_crbegin( 4 ); (void)it; } ); + run( []( feng::matrix& m ) { auto it = m.lower_diag_end( 3 ); (void)it; } ); + run( []( feng::matrix& m ) { auto it = std::as_const( m ).lower_diag_cbegin( 3 ); (void)it; } ); + } + SECTION( "anti-diagonal k = col() and k = -row()" ) + { + run( []( feng::matrix& m ) { auto it = m.anti_diag_begin( 4 ); (void)it; } ); + run( []( feng::matrix& m ) { auto it = std::as_const( m ).anti_diag_cend( -3 ); (void)it; } ); + run( []( feng::matrix& m ) { auto it = m.anti_diag_rend( -3 ); (void)it; } ); + run( []( feng::matrix& m ) { auto it = std::as_const( m ).anti_diag_crbegin( 4 ); (void)it; } ); + } + SECTION( "upper and lower anti-diagonal indices col() and row()" ) + { + run( []( feng::matrix& m ) { auto it = m.upper_anti_diag_begin( 4 ); (void)it; } ); + run( []( feng::matrix& m ) { auto it = std::as_const( m ).upper_anti_diag_cend( 4 ); (void)it; } ); + run( []( feng::matrix& m ) { auto it = m.lower_anti_diag_rbegin( 3 ); (void)it; } ); + run( []( feng::matrix& m ) { auto it = std::as_const( m ).lower_anti_diag_crend( 3 ); (void)it; } ); + } +} + +#ifdef FENG_MATRIX_CHECKED_ITERATORS +TEST_CASE( "S4 checked iterators abort on every position or address outside the owner", "[S4][S4-R1]" ) +{ + using it_t = feng::stride_iterator; + SECTION( "dereference and subscript outside [0, count)" ) + { + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); double volatile x = *m.col_end( 0 ); (void)x; }, "matrix iterator: dereference" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); double volatile x = m.col_begin( 0 )[3]; (void)x; }, "matrix iterator: dereference" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); double volatile x = m.col_end( 0 )[-4]; (void)x; }, "matrix iterator: dereference" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); double* volatile p = m.diag_end().operator->(); (void)p; }, "matrix iterator: dereference" ); + } + SECTION( "index moved outside [0, count]" ) + { + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); auto it = m.col_end( 1 ); ++it; }, "matrix iterator: position" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); auto it = m.col_end( 1 ); it++; }, "matrix iterator: position" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); auto it = m.col_begin( 1 ); --it; }, "matrix iterator: position" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); auto it = m.col_begin( 1 ); it--; }, "matrix iterator: position" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); auto it = m.col_begin( 1 ); it += 4; }, "matrix iterator: position" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); auto it = m.col_end( 1 ); it -= 4; }, "matrix iterator: position" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); auto it = m.anti_diag_begin() + 4; (void)it; }, "matrix iterator: position" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); auto it = 4 + m.anti_diag_begin(); (void)it; }, "matrix iterator: position" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r1::numbered( 3, 4 ); auto it = m.diag_begin() - 1; (void)it; }, "matrix iterator: position" ); + } + SECTION( "first or last address outside the owner, through the public constructor" ) + { + // the owner is the first four elements of an eight-element buffer, so every address below is valid to form + S2_REQUIRE_DEATH( []{ double buf[8] = {}; it_t it( buf + 5, 1, 1, 0, buf, 4 ); (void)it; }, "matrix iterator: first address outside" ); + S2_REQUIRE_DEATH( []{ double buf[8] = {}; it_t it( buf, 1, 1, 0, buf + 1, 3 ); (void)it; }, "matrix iterator: first address outside" ); + S2_REQUIRE_DEATH( []{ double buf[8] = {}; it_t it( buf + 4, 1, 1, 0, buf, 4 ); (void)it; }, "matrix iterator: first element" ); + S2_REQUIRE_DEATH( []{ double buf[8] = {}; it_t it( buf, 2, 3, 0, buf, 4 ); (void)it; }, "matrix iterator: last element" ); + S2_REQUIRE_DEATH( []{ double buf[8] = {}; it_t it( buf + 1, -1, 3, 0, buf, 4 ); (void)it; }, "matrix iterator: last element" ); + S2_REQUIRE_DEATH( []{ double buf[8] = {}; it_t it( buf, 1, 2, 3, buf, 4 ); (void)it; }, "matrix iterator: position" ); + // the legal limits: an empty range at data()+size() and a full range with stride 0 + double buf[8] = {}; + it_t const e( buf + 4, 1, 0, 0, buf, 4 ); + it_t const z( buf + 3, 0, 5, 0, buf, 4 ); + REQUIRE( e - e == 0 ); + REQUIRE( ( z + 5 ) - z == 5 ); + } +} +#endif diff --git a/tests/cases/s4_r2.hpp b/tests/cases/s4_r2.hpp new file mode 100644 index 0000000..3969327 --- /dev/null +++ b/tests/cases/s4_r2.hpp @@ -0,0 +1,440 @@ +// S4-R2 (PR-6): const and mutable views carry a pointer to the first viewed element, the extents and the parent's +// row stride (F06, F05; D-019). +#include +#include +#include + +namespace s4_r2 +{ + inline feng::matrix distinct( std::size_t r, std::size_t c ) + { + feng::matrix m{ r, c }; + for ( std::size_t i = 0; i != r; ++i ) + for ( std::size_t j = 0; j != c; ++j ) + m( i, j ) = static_cast( 100 * i + j ) + 0.5; + return m; + } + + template < typename It > + std::vector walk( It first, It last ) + { + std::vector ans; + for ( ; first != last; ++first ) ans.push_back( *first ); + return ans; + } + + // reads of a 2x3 view at rows [1, 3), columns [2, 5) of distinct( 5, 6 ) + template < typename View > + void require_reads( View const& v, feng::matrix const& m ) + { + REQUIRE( v.row() == 2 ); + REQUIRE( v.col() == 3 ); + REQUIRE( v.size() == 6 ); + REQUIRE( v.row_stride() == 6 ); + auto const [vr, vc] = v.shape(); + REQUIRE( vr == 2 ); + REQUIRE( vc == 3 ); + for ( std::size_t r = 0; r != 2; ++r ) + for ( std::size_t c = 0; c != 3; ++c ) + { + REQUIRE( v( r, c ) == m( r + 1, c + 2 ) ); + REQUIRE( v.at( r, c ) == m( r + 1, c + 2 ) ); + REQUIRE( v[r][c] == m( r + 1, c + 2 ) ); + REQUIRE( &v.at( r, c ) == &m.at( r + 1, c + 2 ) ); + } + REQUIRE( walk( v.row_begin( 0 ), v.row_end( 0 ) ) == std::vector{ 102.5, 103.5, 104.5 } ); + REQUIRE( walk( v.row_cbegin( 1 ), v.row_cend( 1 ) ) == std::vector{ 202.5, 203.5, 204.5 } ); + for ( std::size_t c = 0; c != 3; ++c ) + { + double const top = m( 1, c + 2 ), bottom = m( 2, c + 2 ); + REQUIRE( std::distance( v.col_begin( c ), v.col_end( c ) ) == 2 ); + REQUIRE( walk( v.col_begin( c ), v.col_end( c ) ) == std::vector{ top, bottom } ); + REQUIRE( walk( v.col_cbegin( c ), v.col_cend( c ) ) == std::vector{ top, bottom } ); + REQUIRE( walk( v.col_rbegin( c ), v.col_rend( c ) ) == std::vector{ bottom, top } ); + } + std::vector const all{ 102.5, 103.5, 104.5, 202.5, 203.5, 204.5 }; + REQUIRE( std::distance( v.begin(), v.end() ) == 6 ); + REQUIRE( walk( v.begin(), v.end() ) == all ); + REQUIRE( walk( v.cbegin(), v.cend() ) == all ); + REQUIRE( walk( v.rbegin(), v.rend() ) == std::vector( all.rbegin(), all.rend() ) ); + REQUIRE( v.begin()[4] == 203.5 ); + REQUIRE( v.get_allocator() == m.get_allocator() ); + } + + // every parent element outside rows [1, 3), columns [2, 5) still holds its distinct value + inline void require_outside_untouched( feng::matrix const& m ) + { + auto const fresh = distinct( 5, 6 ); + for ( std::size_t i = 0; i != 5; ++i ) + for ( std::size_t j = 0; j != 6; ++j ) + if ( !( i >= 1 && i < 3 && j >= 2 && j < 5 ) ) + REQUIRE( m( i, j ) == fresh( i, j ) ); + } + + inline void require_inside( feng::matrix const& m, std::vector const& expected ) + { + std::size_t k = 0; + for ( std::size_t i = 1; i != 3; ++i ) + for ( std::size_t j = 2; j != 5; ++j ) + REQUIRE( m( i, j ) == expected[k++] ); + } + + inline void require_six( feng::matrix const& n ) + { + REQUIRE( n.row() == 2 ); + REQUIRE( n.col() == 3 ); + REQUIRE( n.size() == 6 ); + REQUIRE( std::vector( n.begin(), n.end() ) == std::vector{ 102.5, 103.5, 104.5, 202.5, 203.5, 204.5 } ); + } +} + +TEST_CASE( "S4 offset 2x3 view of a 5x6 matrix reads writes copies and iterates its elements", "[S4][S4-R2]" ) +{ + auto m = s4_r2::distinct( 5, 6 ); + auto const& cm = m; + + SECTION( "the const view reads exactly the six elements, forward and reverse" ) + { + auto const v = feng::make_view( cm, { 1, 3 }, { 2, 5 } ); + s4_r2::require_reads( v, m ); + feng::matrix_view> const w{ cm, { 1, 3 }, { 2, 5 } }; + s4_r2::require_reads( w, m ); + } + SECTION( "the mutable view reads exactly the six elements and converts to the const view" ) + { + auto v = feng::make_mutable_view( m, { 1, 3 }, { 2, 5 } ); + s4_r2::require_reads( v, m ); + feng::matrix_view> const w = v; + s4_r2::require_reads( w, m ); + feng::mutable_matrix_view> u{ m, { 1, 3 }, { 2, 5 } }; + s4_r2::require_reads( u, m ); + } + SECTION( "writes through at, (), [][] change exactly the viewed elements" ) + { + auto v = feng::make_mutable_view( m, { 1, 3 }, { 2, 5 } ); + v.at( 0, 0 ) = -1.0; + v( 0, 2 ) = -2.0; + v[1][1] = -3.0; + *v.row_begin( 1 ) = -4.0; + s4_r2::require_inside( m, { -1.0, 103.5, -2.0, -4.0, -3.0, 204.5 } ); + s4_r2::require_outside_untouched( m ); + } + SECTION( "std::fill over begin..end changes exactly the viewed elements" ) + { + auto v = feng::make_mutable_view( m, { 1, 3 }, { 2, 5 } ); + std::fill( v.begin(), v.end(), 7.0 ); + s4_r2::require_inside( m, std::vector( 6, 7.0 ) ); + s4_r2::require_outside_untouched( m ); + } + SECTION( "writes through a column iterator change exactly that view column" ) + { + auto v = feng::make_mutable_view( m, { 1, 3 }, { 2, 5 } ); + std::fill( v.col_begin( 1 ), v.col_end( 1 ), 9.0 ); + s4_r2::require_inside( m, { 102.5, 9.0, 104.5, 202.5, 9.0, 204.5 } ); + s4_r2::require_outside_untouched( m ); + } + SECTION( "a matrix from either view and copy from a view hold exactly the six values" ) + { + auto const cv = feng::make_view( cm, { 1, 3 }, { 2, 5 } ); + auto const mv = feng::make_mutable_view( m, { 1, 3 }, { 2, 5 } ); + feng::matrix const a{ cv }; + feng::matrix const b{ mv }; + s4_r2::require_six( a ); + s4_r2::require_six( b ); + REQUIRE( a.get_allocator() == m.get_allocator() ); + feng::matrix n{ 4, 4 }; + n.copy( cv ); + s4_r2::require_six( n ); + feng::matrix p; + p.copy( mv ); + s4_r2::require_six( p ); + } +} + +// S4-T8 (S4-R2): every listed view shape (1x1, full-size, first-row, last-column, 1xN, Nx1) on 5x6, 1x6 and 6x1 +// owners of distinct values, through make_view and make_mutable_view, on the stateful test allocator. +#include "./s3_alloc.hpp" +#include + +namespace s4_r2_shapes +{ + using alloc_type = s3_alloc::tracking_allocator< double, false, false, false >; + using owner_type = feng::matrix< double, alloc_type >; + + struct shape_case + { + char const* name; + std::size_t rows, cols; // owner + std::size_t r0, r1, c0, c1; // view + }; + + inline std::vector< shape_case > const& cases() + { + static std::vector< shape_case > const all{ + { "1x1 at (2, 3) of 5x6", 5, 6, 2, 3, 3, 4 }, + { "full-size 5x6", 5, 6, 0, 5, 0, 6 }, + { "first row of 5x6", 5, 6, 0, 1, 0, 6 }, + { "last column of 5x6", 5, 6, 0, 5, 5, 6 }, + { "1x4 at (3, 1) of 5x6", 5, 6, 3, 4, 1, 5 }, + { "4x1 at (1, 2) of 5x6", 5, 6, 1, 5, 2, 3 }, + { "1x4 at (0, 1) of 1x6", 1, 6, 0, 1, 1, 5 }, + { "full-size 1x6", 1, 6, 0, 1, 0, 6 }, + { "3x1 at (2, 0) of 6x1", 6, 1, 2, 5, 0, 1 }, + { "full-size 6x1", 6, 1, 0, 6, 0, 1 }, + }; + return all; + } + + // the owner's element (i, j) before any write: distinct over the whole owner + inline double original( std::size_t i, std::size_t j ) { return 1000.0 * static_cast< double >( i ) + static_cast< double >( j ) + 0.25; } + + inline owner_type make_owner( shape_case const& s, int id ) + { + owner_type m{ alloc_type{ id }, s.rows, s.cols }; + double* const p = m.data(); + for ( std::size_t i = 0; i != s.rows; ++i ) + for ( std::size_t j = 0; j != s.cols; ++j ) + p[ i * s.cols + j ] = original( i, j ); + return m; + } + + inline bool inside( shape_case const& s, std::size_t i, std::size_t j ) { return i >= s.r0 && i < s.r1 && j >= s.c0 && j < s.c1; } + + // oracle: the viewed values in row order, by index arithmetic on the owner's storage + inline std::vector< double > viewed( shape_case const& s, owner_type const& m ) + { + std::vector< double > ans; + double const* const p = m.data(); + for ( std::size_t i = s.r0; i != s.r1; ++i ) + for ( std::size_t j = s.c0; j != s.c1; ++j ) + ans.push_back( p[ i * s.cols + j ] ); + return ans; + } + + template < typename It > + std::vector< double > walk( It first, It last ) + { + std::vector< double > ans; + for ( ; first != last; ++first ) ans.push_back( *first ); + return ans; + } + + template < typename View > + void require_reads( shape_case const& s, View const& v, owner_type const& m ) + { + std::size_t const vr = s.r1 - s.r0, vc = s.c1 - s.c0; + double const* const p = m.data(); + auto const at = [&]( std::size_t r, std::size_t c ) { return p[ ( s.r0 + r ) * s.cols + ( s.c0 + c ) ]; }; + REQUIRE( v.row() == vr ); + REQUIRE( v.col() == vc ); + REQUIRE( v.size() == vr * vc ); + REQUIRE( v.row_stride() == s.cols ); + auto const [sr, sc] = v.shape(); + REQUIRE( sr == vr ); + REQUIRE( sc == vc ); + for ( std::size_t r = 0; r != vr; ++r ) + for ( std::size_t c = 0; c != vc; ++c ) + { + REQUIRE( v( r, c ) == at( r, c ) ); + REQUIRE( v.at( r, c ) == at( r, c ) ); + REQUIRE( v[r][c] == at( r, c ) ); + REQUIRE( &v.at( r, c ) == p + ( s.r0 + r ) * s.cols + ( s.c0 + c ) ); + } + for ( std::size_t r = 0; r != vr; ++r ) + { + std::vector< double > row; + for ( std::size_t c = 0; c != vc; ++c ) row.push_back( at( r, c ) ); + REQUIRE( std::distance( v.row_begin( r ), v.row_end( r ) ) == static_cast< std::ptrdiff_t >( vc ) ); + REQUIRE( walk( v.row_begin( r ), v.row_end( r ) ) == row ); + REQUIRE( walk( v.row_cbegin( r ), v.row_cend( r ) ) == row ); + } + for ( std::size_t c = 0; c != vc; ++c ) + { + std::vector< double > col; + for ( std::size_t r = 0; r != vr; ++r ) col.push_back( at( r, c ) ); + std::vector< double > const rcol( col.rbegin(), col.rend() ); + REQUIRE( std::distance( v.col_begin( c ), v.col_end( c ) ) == static_cast< std::ptrdiff_t >( vr ) ); + REQUIRE( std::distance( v.col_rbegin( c ), v.col_rend( c ) ) == static_cast< std::ptrdiff_t >( vr ) ); + REQUIRE( walk( v.col_begin( c ), v.col_end( c ) ) == col ); + REQUIRE( walk( v.col_cbegin( c ), v.col_cend( c ) ) == col ); + REQUIRE( walk( v.col_rbegin( c ), v.col_rend( c ) ) == rcol ); + REQUIRE( walk( v.col_crbegin( c ), v.col_crend( c ) ) == rcol ); + } + std::vector< double > const all = viewed( s, m ); + std::vector< double > const rall( all.rbegin(), all.rend() ); + REQUIRE( std::distance( v.begin(), v.end() ) == static_cast< std::ptrdiff_t >( all.size() ) ); + REQUIRE( std::distance( v.rbegin(), v.rend() ) == static_cast< std::ptrdiff_t >( all.size() ) ); + REQUIRE( walk( v.begin(), v.end() ) == all ); + REQUIRE( walk( v.cbegin(), v.cend() ) == all ); + REQUIRE( walk( v.rbegin(), v.rend() ) == rall ); + REQUIRE( walk( v.crbegin(), v.crend() ) == rall ); + REQUIRE( v.get_allocator() == m.get_allocator() ); + } + + // after a write of `written` (row order) through a view, the viewed elements hold it and the rest are original + inline void require_written( shape_case const& s, owner_type const& m, std::vector< double > const& written ) + { + REQUIRE( viewed( s, m ) == written ); + double const* const p = m.data(); + for ( std::size_t i = 0; i != s.rows; ++i ) + for ( std::size_t j = 0; j != s.cols; ++j ) + if ( !inside( s, i, j ) ) + REQUIRE( p[ i * s.cols + j ] == original( i, j ) ); + } + + // values -1, -2, ... in row order of the view + inline std::vector< double > marks( shape_case const& s ) + { + std::vector< double > ans( ( s.r1 - s.r0 ) * ( s.c1 - s.c0 ) ); + for ( std::size_t k = 0; k != ans.size(); ++k ) ans[k] = -1.0 - static_cast< double >( k ); + return ans; + } + + template < typename Write > + void check_write( shape_case const& s, Write write ) + { + auto m = make_owner( s, 3 ); + auto v = feng::make_mutable_view( m, { s.r0, s.r1 }, { s.c0, s.c1 } ); + write( v, s.c1 - s.c0 ); + require_written( s, m, marks( s ) ); + } + + inline void require_matrix( owner_type const& n, std::size_t r, std::size_t c, std::vector< double > const& values ) + { + REQUIRE( n.row() == r ); + REQUIRE( n.col() == c ); + REQUIRE( n.size() == values.size() ); + REQUIRE( std::vector< double >( n.begin(), n.end() ) == values ); + } +} + +TEST_CASE( "S4 every listed view shape reads writes and copies exactly", "[S4][S4-R2]" ) +{ + using namespace s4_r2_shapes; + s3_alloc::reset(); + for ( auto const& s : cases() ) + { + INFO( "view " << s.name ); + std::size_t const vr = s.r1 - s.r0, vc = s.c1 - s.c0; + { + auto m = make_owner( s, 1 ); + auto const& cm = m; + auto const cv = feng::make_view( cm, { s.r0, s.r1 }, { s.c0, s.c1 } ); + require_reads( s, cv, m ); + auto const mv = feng::make_mutable_view( m, { s.r0, s.r1 }, { s.c0, s.c1 } ); + require_reads( s, mv, m ); + feng::matrix_view< double, alloc_type > const conv = mv; + require_reads( s, conv, m ); + } + // writes through the mutable view change exactly the viewed elements + check_write( s, []( auto& v, std::size_t ) { for ( std::size_t r = 0; r != v.row(); ++r ) for ( std::size_t c = 0; c != v.col(); ++c ) v.at( r, c ) = -1.0 - static_cast< double >( r * v.col() + c ); } ); + check_write( s, []( auto& v, std::size_t ) { for ( std::size_t r = 0; r != v.row(); ++r ) for ( std::size_t c = 0; c != v.col(); ++c ) v( r, c ) = -1.0 - static_cast< double >( r * v.col() + c ); } ); + check_write( s, []( auto& v, std::size_t ) { for ( std::size_t r = 0; r != v.row(); ++r ) for ( std::size_t c = 0; c != v.col(); ++c ) v[r][c] = -1.0 - static_cast< double >( r * v.col() + c ); } ); + check_write( s, []( auto& v, std::size_t ) { double x = -1.0; for ( auto it = v.begin(); it != v.end(); ++it ) *it = x--; } ); + check_write( s, []( auto& v, std::size_t ) { + for ( std::size_t c = 0; c != v.col(); ++c ) + { + std::size_t r = 0; + for ( auto it = v.col_begin( c ); it != v.col_end( c ); ++it, ++r ) *it = -1.0 - static_cast< double >( r * v.col() + c ); + } + } ); + check_write( s, []( auto& v, std::size_t ) { + for ( std::size_t c = 0; c != v.col(); ++c ) + { + std::size_t r = v.row(); + for ( auto it = v.col_rbegin( c ); it != v.col_rend( c ); ++it ) { --r; *it = -1.0 - static_cast< double >( r * v.col() + c ); } + } + } ); + check_write( s, []( auto& v, std::size_t ) { std::size_t k = v.size(); for ( auto it = v.rbegin(); it != v.rend(); ++it ) { --k; *it = -1.0 - static_cast< double >( k ); } } ); + { + auto m = make_owner( s, 3 ); + auto v = feng::make_mutable_view( m, { s.r0, s.r1 }, { s.c0, s.c1 } ); + std::fill( v.begin(), v.end(), -7.0 ); + require_written( s, m, std::vector< double >( vr * vc, -7.0 ) ); + } + // copies from either view hold exactly the viewed values in row order + { + auto m = make_owner( s, 4 ); + auto const& cm = m; + auto const expected = viewed( s, m ); + auto const cv = feng::make_view( cm, { s.r0, s.r1 }, { s.c0, s.c1 } ); + auto const mv = feng::make_mutable_view( m, { s.r0, s.r1 }, { s.c0, s.c1 } ); + owner_type const a{ cv }; + owner_type const b{ mv }; + require_matrix( a, vr, vc, expected ); + require_matrix( b, vr, vc, expected ); + REQUIRE( a.get_allocator().id == 4 ); + REQUIRE( b.get_allocator().id == 4 ); + owner_type n{ alloc_type{ 5 }, 2, 2 }; + n.copy( cv ); + require_matrix( n, vr, vc, expected ); + owner_type p{ alloc_type{ 5 } }; + p.copy( mv ); + require_matrix( p, vr, vc, expected ); + // S3 (F04): the destination of copy keeps its own allocator + REQUIRE( n.get_allocator().id == 5 ); + REQUIRE( p.get_allocator().id == 5 ); + require_written( s, m, expected ); + } + } + s3_alloc::require_balanced(); +} + +TEST_CASE( "S4 copy from a view of the destination snapshots first", "[S4][S4-R2]" ) +{ + using namespace s4_r2_shapes; + s3_alloc::reset(); + for ( auto const& s : cases() ) + { + INFO( "view " << s.name ); + std::size_t const vr = s.r1 - s.r0, vc = s.c1 - s.c0; + { + auto m = make_owner( s, 6 ); + auto const expected = viewed( s, m ); + auto const& cm = m; + m.copy( feng::make_view( cm, { s.r0, s.r1 }, { s.c0, s.c1 } ) ); + require_matrix( m, vr, vc, expected ); + REQUIRE( m.get_allocator().id == 6 ); + } + { + auto m = make_owner( s, 6 ); + auto const expected = viewed( s, m ); + m.copy( feng::make_mutable_view( m, { s.r0, s.r1 }, { s.c0, s.c1 } ) ); + require_matrix( m, vr, vc, expected ); + REQUIRE( m.get_allocator().id == 6 ); + } + } + s3_alloc::require_balanced(); +} + +// S4-R2, S4-R1 (D-020): in the checked-iterator build the view element iterator aborts on a dereference or +// subscript outside [0, count) and on a position outside [0, count], for both view types. The unchecked build +// keeps the case with in-range checks only, so the name matches in every lane. +#include "./s2_death.hpp" + +TEST_CASE( "S4 checked view iterators abort outside the view", "[S4][S4-R2]" ) +{ +#ifdef FENG_MATRIX_CHECKED_ITERATORS + // const view: rows [1, 3), columns [2, 5) of a 5x6 owner, six elements + S2_REQUIRE_DEATH( []{ auto m = s4_r2::distinct( 5, 6 ); auto const& cm = m; auto v = feng::make_view( cm, { 1, 3 }, { 2, 5 } ); double volatile x = *v.end(); (void)x; }, "matrix iterator" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r2::distinct( 5, 6 ); auto const& cm = m; auto v = feng::make_view( cm, { 1, 3 }, { 2, 5 } ); double volatile x = v.begin()[6]; (void)x; }, "matrix iterator" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r2::distinct( 5, 6 ); auto const& cm = m; auto v = feng::make_view( cm, { 1, 3 }, { 2, 5 } ); double volatile x = v.end()[-7]; (void)x; }, "matrix iterator" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r2::distinct( 5, 6 ); auto const& cm = m; auto v = feng::make_view( cm, { 1, 3 }, { 2, 5 } ); auto it = v.begin(); --it; (void)it; }, "matrix iterator" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r2::distinct( 5, 6 ); auto const& cm = m; auto v = feng::make_view( cm, { 1, 3 }, { 2, 5 } ); auto it = v.end() + 1; (void)it; }, "matrix iterator" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r2::distinct( 5, 6 ); auto const& cm = m; auto v = feng::make_view( cm, { 1, 3 }, { 2, 5 } ); auto it = v.cbegin() - 1; (void)it; }, "matrix iterator" ); + // mutable view of the same elements + S2_REQUIRE_DEATH( []{ auto m = s4_r2::distinct( 5, 6 ); auto v = feng::make_mutable_view( m, { 1, 3 }, { 2, 5 } ); *v.end() = 1.0; }, "matrix iterator" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r2::distinct( 5, 6 ); auto v = feng::make_mutable_view( m, { 1, 3 }, { 2, 5 } ); v.begin()[6] = 1.0; }, "matrix iterator" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r2::distinct( 5, 6 ); auto v = feng::make_mutable_view( m, { 1, 3 }, { 2, 5 } ); v.begin()[-1] = 1.0; }, "matrix iterator" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r2::distinct( 5, 6 ); auto v = feng::make_mutable_view( m, { 1, 3 }, { 2, 5 } ); auto it = v.begin(); it -= 1; (void)it; }, "matrix iterator" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r2::distinct( 5, 6 ); auto v = feng::make_mutable_view( m, { 1, 3 }, { 2, 5 } ); auto it = v.end(); ++it; (void)it; }, "matrix iterator" ); + S2_REQUIRE_DEATH( []{ auto m = s4_r2::distinct( 5, 6 ); auto v = feng::make_mutable_view( m, { 1, 3 }, { 2, 5 } ); auto it = v.begin() + 7; (void)it; }, "matrix iterator" ); +#endif + // the boundary positions themselves are legal in every build + auto m = s4_r2::distinct( 5, 6 ); + auto v = feng::make_mutable_view( m, { 1, 3 }, { 2, 5 } ); + REQUIRE( v.end() - v.begin() == 6 ); + REQUIRE( v.begin()[5] == m( 2, 4 ) ); + REQUIRE( *( v.end() - 1 ) == m( 2, 4 ) ); + REQUIRE( v.end()[-6] == m( 1, 2 ) ); +} diff --git a/tests/cases/s4_r3.hpp b/tests/cases/s4_r3.hpp new file mode 100644 index 0000000..c508635 --- /dev/null +++ b/tests/cases/s4_r3.hpp @@ -0,0 +1,127 @@ +// S4-R3 (PR-6): view ranges are validated in every build, never normalized (F06). +#include "./s2_death.hpp" + +#include +#include +#include +#include + +TEST_CASE( "S4 view ranges outside the owner abort", "[S4][S4-R3]" ) +{ + feng::matrix m{ 4, 5 }; + auto const& cm = m; + + SECTION( "make_view aborts on a range outside the owner" ) + { + S2_REQUIRE_DEATH( [&] { auto v = feng::make_view( cm, { 0, 5 }, { 0, 1 } ); (void)v; }, "matrix view:" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_view( cm, { 3, 2 }, { 0, 1 } ); (void)v; }, "matrix view:" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_view( cm, { 0, 1 }, { 0, 6 } ); (void)v; }, "matrix view:" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_view( cm, { 0, 1, 2 }, { 0, 1 } ); (void)v; }, "matrix view:" ); + } + SECTION( "make_mutable_view aborts on a range outside the owner" ) + { + S2_REQUIRE_DEATH( [&] { auto v = feng::make_mutable_view( m, { 0, 5 }, { 0, 1 } ); (void)v; }, "matrix view:" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_mutable_view( m, { 3, 2 }, { 0, 1 } ); (void)v; }, "matrix view:" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_mutable_view( m, { 0, 1 }, { 0, 6 } ); (void)v; }, "matrix view:" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_mutable_view( m, { 0, 1 }, { 0, 1, 2 } ); (void)v; }, "matrix view:" ); + } + SECTION( "an empty row range gives an empty view" ) + { + auto const v = feng::make_view( cm, { 2, 2 }, { 0, 5 } ); + REQUIRE( v.row() == 0 ); + REQUIRE( v.col() == 5 ); + REQUIRE( v.size() == 0 ); + REQUIRE( v.begin() == v.end() ); + auto const w = feng::make_mutable_view( m, { 2, 2 }, { 0, 5 } ); + REQUIRE( w.row() == 0 ); + REQUIRE( w.col() == 5 ); + REQUIRE( w.begin() == w.end() ); + feng::matrix const n{ v }; + REQUIRE( n.size() == 0 ); + } +} + +TEST_CASE( "S4 every view factory and constructor rejects ranges outside the owner", "[S4][S4-R3]" ) +{ + // R = 4 rows, C = 5 columns + using range = std::pair; + using cview = feng::matrix_view>; + using mview = feng::mutable_matrix_view>; + feng::matrix m{ 4, 5 }; + auto const& cm = m; + + SECTION( "make_view: r0 > r1, r1 > R, c0 > c1, c1 > C" ) + { + S2_REQUIRE_DEATH( [&] { auto v = feng::make_view( cm, { 3, 2 }, { 0, 5 } ); (void)v; }, "matrix view: row range" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_view( cm, { 0, 5 }, { 0, 5 } ); (void)v; }, "matrix view: row range" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_view( cm, { 0, 4 }, { 3, 2 } ); (void)v; }, "matrix view: column range" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_view( cm, { 0, 4 }, { 0, 6 } ); (void)v; }, "matrix view: column range" ); + } + SECTION( "make_mutable_view: r0 > r1, r1 > R, c0 > c1, c1 > C" ) + { + S2_REQUIRE_DEATH( [&] { auto v = feng::make_mutable_view( m, { 3, 2 }, { 0, 5 } ); (void)v; }, "matrix view: row range" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_mutable_view( m, { 0, 5 }, { 0, 5 } ); (void)v; }, "matrix view: row range" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_mutable_view( m, { 0, 4 }, { 3, 2 } ); (void)v; }, "matrix view: column range" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_mutable_view( m, { 0, 4 }, { 0, 6 } ); (void)v; }, "matrix view: column range" ); + } + SECTION( "matrix_view constructor: r0 > r1, r1 > R, c0 > c1, c1 > C" ) + { + S2_REQUIRE_DEATH( [&] { cview v( cm, range{ 3, 2 }, range{ 0, 5 } ); (void)v; }, "matrix view: row range" ); + S2_REQUIRE_DEATH( [&] { cview v( cm, range{ 0, 5 }, range{ 0, 5 } ); (void)v; }, "matrix view: row range" ); + S2_REQUIRE_DEATH( [&] { cview v( cm, range{ 0, 4 }, range{ 3, 2 } ); (void)v; }, "matrix view: column range" ); + S2_REQUIRE_DEATH( [&] { cview v( cm, range{ 0, 4 }, range{ 0, 6 } ); (void)v; }, "matrix view: column range" ); + } + SECTION( "mutable_matrix_view constructor: r0 > r1, r1 > R, c0 > c1, c1 > C" ) + { + S2_REQUIRE_DEATH( [&] { mview v( m, range{ 3, 2 }, range{ 0, 5 } ); (void)v; }, "matrix view: row range" ); + S2_REQUIRE_DEATH( [&] { mview v( m, range{ 0, 5 }, range{ 0, 5 } ); (void)v; }, "matrix view: row range" ); + S2_REQUIRE_DEATH( [&] { mview v( m, range{ 0, 4 }, range{ 3, 2 } ); (void)v; }, "matrix view: column range" ); + S2_REQUIRE_DEATH( [&] { mview v( m, range{ 0, 4 }, range{ 0, 6 } ); (void)v; }, "matrix view: column range" ); + } + SECTION( "factory lists of size 0, 1 and 3" ) + { + S2_REQUIRE_DEATH( [&] { auto v = feng::make_view( cm, {}, { 0, 1 } ); (void)v; }, "matrix view: the row range needs exactly two values, got 0" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_view( cm, { 1 }, { 0, 1 } ); (void)v; }, "matrix view: the row range needs exactly two values, got 1" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_view( cm, { 0, 1, 2 }, { 0, 1 } ); (void)v; }, "matrix view: the row range needs exactly two values, got 3" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_view( cm, { 0, 1 }, {} ); (void)v; }, "matrix view: the column range needs exactly two values, got 0" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_view( cm, { 0, 1 }, { 1 } ); (void)v; }, "matrix view: the column range needs exactly two values, got 1" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_view( cm, { 0, 1 }, { 0, 1, 2 } ); (void)v; }, "matrix view: the column range needs exactly two values, got 3" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_mutable_view( m, {}, { 0, 1 } ); (void)v; }, "matrix view: the row range needs exactly two values, got 0" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_mutable_view( m, { 1 }, { 0, 1 } ); (void)v; }, "matrix view: the row range needs exactly two values, got 1" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_mutable_view( m, { 0, 1, 2 }, { 0, 1 } ); (void)v; }, "matrix view: the row range needs exactly two values, got 3" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_mutable_view( m, { 0, 1 }, {} ); (void)v; }, "matrix view: the column range needs exactly two values, got 0" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_mutable_view( m, { 0, 1 }, { 1 } ); (void)v; }, "matrix view: the column range needs exactly two values, got 1" ); + S2_REQUIRE_DEATH( [&] { auto v = feng::make_mutable_view( m, { 0, 1 }, { 0, 1, 2 } ); (void)v; }, "matrix view: the column range needs exactly two values, got 3" ); + } + SECTION( "empty ranges r0 == r1 and c0 == c1 give empty views; the owner's limits are accepted" ) + { + auto const require_empty = []( auto const& v, std::size_t rows, std::size_t cols ) + { + REQUIRE( v.row() == rows ); + REQUIRE( v.col() == cols ); + REQUIRE( v.size() == 0 ); + REQUIRE( v.begin() == v.end() ); + REQUIRE( std::distance( v.begin(), v.end() ) == 0 ); + }; + for ( std::size_t i = 0; i <= 4; ++i ) + { + INFO( "row range [" << i << ", " << i << ")" ); + int const ii = static_cast( i ); + require_empty( feng::make_view( cm, { ii, ii }, { 0, 5 } ), 0, 5 ); + require_empty( feng::make_mutable_view( m, { ii, ii }, { 0, 5 } ), 0, 5 ); + require_empty( cview( cm, range{ i, i }, range{ 0, 5 } ), 0, 5 ); + require_empty( mview( m, range{ i, i }, range{ 0, 5 } ), 0, 5 ); + } + for ( std::size_t j = 0; j <= 5; ++j ) + { + INFO( "column range [" << j << ", " << j << ")" ); + int const jj = static_cast( j ); + require_empty( feng::make_view( cm, { 0, 4 }, { jj, jj } ), 4, 0 ); + require_empty( feng::make_mutable_view( m, { 0, 4 }, { jj, jj } ), 4, 0 ); + require_empty( cview( cm, range{ 0, 4 }, range{ j, j } ), 4, 0 ); + require_empty( mview( m, range{ 0, 4 }, range{ j, j } ), 4, 0 ); + } + REQUIRE( feng::make_view( cm, { 0, 4 }, { 0, 5 } ).size() == 20 ); + REQUIRE( mview( m, range{ 3, 4 }, range{ 4, 5 } ).size() == 1 ); + } +} diff --git a/tests/cases/s4_r4.hpp b/tests/cases/s4_r4.hpp new file mode 100644 index 0000000..943afae --- /dev/null +++ b/tests/cases/s4_r4.hpp @@ -0,0 +1,85 @@ +// S4-R4 (PR-6): the curried map and reduce helpers hold copies of their arguments (F12). +#include +#include +#include +#include + +TEST_CASE( "S4 stored curried reduce outlives its init", "[S4][S4-R4]" ) +{ + feng::matrix m{ 3, 5 }; + std::iota( m.begin(), m.end(), 1.0 ); + + SECTION( "reduce( func, init ) keeps copies of func and init" ) + { + std::vector weights_copy; + double init_copy = 0.0; + auto f = [&] + { + std::vector weights( 4, 1.0 ); + double init = 7.0; + weights_copy = weights; + init_copy = init; + auto func = [weights]( double a, double b ) noexcept { return a + b * weights[0]; }; + return feng::matrix_details::reduce( func, init ); + }(); + auto const plus = []( double a, double b ) { return a + b; }; + REQUIRE( m.size() < 32 ); + REQUIRE( f( m ) == std::accumulate( m.begin(), m.end(), init_copy, plus ) ); + REQUIRE( weights_copy.size() == 4 ); + } + SECTION( "map( func ) keeps a copy of func" ) + { + feng::matrix b{ 3, 5 }; + std::iota( b.begin(), b.end(), 100.0 ); + auto g = [] + { + std::vector scale( 3, 2.0 ); + auto func = [scale]( double x, double y ) noexcept { return x * scale[1] + y; }; + return feng::matrix_details::map( func ); + }(); + auto const r = g( m, b ); + REQUIRE( r.row() == 3 ); + REQUIRE( r.col() == 5 ); + for ( std::size_t i = 0; i != r.size(); ++i ) + REQUIRE( r.data()[i] == m.data()[i] * 2.0 + b.data()[i] ); + } +} + +// S4-R4 (F12): reduce( func, init ) folds init in exactly once, serial or parallel, and never forms a pointer past +// m.end(); the values are small integers so every sum is exact in double whatever the grouping. +TEST_CASE( "S4 curried reduce includes init once at every size", "[S4][S4-R4]" ) +{ + auto const plus = []( double a, double b ) noexcept { return a + b; }; + for ( std::size_t n : { std::size_t{ 0 }, std::size_t{ 1 }, std::size_t{ 31 }, std::size_t{ 32 }, std::size_t{ 33 }, std::size_t{ 1000 } } ) + { + INFO( "size " << n ); + feng::matrix m{ 1, n }; + std::iota( m.begin(), m.end(), 1.0 ); + double const init = 1000003.0; + auto const f = feng::matrix_details::reduce( plus, init ); + REQUIRE( f( m ) == std::accumulate( m.begin(), m.end(), init, plus ) ); + auto const f0 = feng::matrix_details::reduce( plus, 0.0 ); + REQUIRE( f0( m ) == std::accumulate( m.begin(), m.end(), 0.0, plus ) ); + } + + SECTION( "a stored map whose callable owned a heap value runs after that scope ends" ) + { + auto g = [] + { + auto offset = std::make_unique( 3.0 ); + auto func = [offset = std::shared_ptr( std::move( offset ) )]( double x, double y ) noexcept { return x - y + *offset; }; + return feng::matrix_details::map( func ); + }(); + for ( std::size_t n : { std::size_t{ 1 }, std::size_t{ 33 }, std::size_t{ 1000 } } ) + { + INFO( "size " << n ); + feng::matrix a{ 1, n }, b{ 1, n }; + std::iota( a.begin(), a.end(), 10.0 ); + std::iota( b.begin(), b.end(), 1.0 ); + auto const r = g( a, b ); + REQUIRE( r.size() == n ); + for ( std::size_t i = 0; i != n; ++i ) + REQUIRE( r.data()[i] == 12.0 ); + } + } +} diff --git a/tests/cases/s4_r5.hpp b/tests/cases/s4_r5.hpp new file mode 100644 index 0000000..ccde5ab --- /dev/null +++ b/tests/cases/s4_r5.hpp @@ -0,0 +1,82 @@ +// S4-R5 (PR-6, PR-14): the stride iterators model the standard iterator concepts. +#include +#include +#include +#include +#include + +TEST_CASE( "S4 stride iterators satisfy the standard iterator concepts", "[S4][S4-R5]" ) +{ + using it = feng::stride_iterator; + using cit = feng::stride_iterator; + static_assert( std::random_access_iterator ); + static_assert( std::random_access_iterator ); + static_assert( std::random_access_iterator> ); + static_assert( std::random_access_iterator> ); + static_assert( std::sized_sentinel_for ); + static_assert( std::sized_sentinel_for ); + static_assert( std::sized_sentinel_for, std::reverse_iterator> ); + static_assert( std::sized_sentinel_for, std::reverse_iterator> ); + static_assert( std::output_iterator ); + static_assert( std::is_convertible_v ); + + feng::matrix m{ 4, 3 }; + std::iota( m.begin(), m.end(), 0.0 ); + auto col = std::ranges::subrange( m.col_begin( 1 ), m.col_end( 1 ) ); + REQUIRE( std::ranges::size( col ) == 4 ); + REQUIRE( std::ranges::equal( col, std::vector{ 1.0, 4.0, 7.0, 10.0 } ) ); + std::ranges::fill( col, -1.0 ); + REQUIRE( std::ranges::count( m, -1.0 ) == 4 ); + REQUIRE( m[2][1] == -1.0 ); + auto const& cm = m; + auto ccol = std::ranges::subrange( cm.col_begin( 1 ), cm.col_end( 1 ) ); + REQUIRE( std::ranges::all_of( ccol, []( double x ) { return x == -1.0; } ) ); + REQUIRE( *std::ranges::max_element( std::ranges::subrange( cm.col_begin( 2 ), cm.col_end( 2 ) ) ) == 11.0 ); +} + +// S4-R5 (PR-6, PR-14): the view element iterator models the standard iterator concepts and both view types are +// borrowed random-access ranges; the owner is not borrowed. +TEST_CASE( "S4 view types are borrowed random-access ranges", "[S4][S4-R5]" ) +{ + using A = feng::matrix::allocator_type; + using vit = feng::view_iterator; + using cvit = feng::view_iterator; + using view = feng::matrix_view; + using mview = feng::mutable_matrix_view; + static_assert( std::random_access_iterator ); + static_assert( std::random_access_iterator ); + static_assert( std::random_access_iterator> ); + static_assert( std::random_access_iterator> ); + static_assert( std::sized_sentinel_for ); + static_assert( std::sized_sentinel_for ); + static_assert( std::sized_sentinel_for, std::reverse_iterator> ); + static_assert( std::sized_sentinel_for, std::reverse_iterator> ); + static_assert( std::output_iterator ); + static_assert( std::is_convertible_v ); + static_assert( std::ranges::random_access_range ); + static_assert( std::ranges::random_access_range ); + static_assert( std::ranges::borrowed_range ); + static_assert( std::ranges::borrowed_range ); + static_assert( !std::ranges::borrowed_range> ); + static_assert( std::is_convertible_v ); + + feng::matrix m{ 4, 5 }; + std::iota( m.begin(), m.end(), 0.0 ); + auto mv = feng::make_mutable_view( m, { 1, 3 }, { 1, 4 } ); + REQUIRE( std::ranges::size( mv ) == 6 ); + REQUIRE( std::ranges::equal( mv, std::vector{ 6.0, 7.0, 8.0, 11.0, 12.0, 13.0 } ) ); + std::ranges::fill( mv, -1.0 ); + REQUIRE( std::ranges::count( m, -1.0 ) == 6 ); + REQUIRE( m( 2, 3 ) == -1.0 ); + REQUIRE( m( 2, 4 ) == 14.0 ); + view const v = mv; + REQUIRE( std::ranges::all_of( v, []( double x ) { return x == -1.0; } ) ); + auto const& cm = m; + auto const cv = feng::make_view( cm, { 0, 4 }, { 4, 5 } ); + REQUIRE( *std::ranges::max_element( cv ) == 19.0 ); + // a borrowed range: the iterator from a temporary view stays usable while the owner lives + auto const it = std::ranges::find( feng::make_view( cm, { 3, 4 }, { 0, 5 } ), 17.0 ); + REQUIRE( *it == 17.0 ); + cvit const cit = mv.begin(); + REQUIRE( *cit == -1.0 ); +} diff --git a/tests/cases/s5_r1.hpp b/tests/cases/s5_r1.hpp new file mode 100644 index 0000000..86f85e5 --- /dev/null +++ b/tests/cases/s5_r1.hpp @@ -0,0 +1,587 @@ +// S5-R1 (PR-7, F07): the NPY loader validates every header field; S5-R4 (PR-2, D-011): it is transactional. +// numpy-made fixtures live in ./tests/fixtures/s5/ (tests/fixtures/s5/make_npy.py); the suite runs from the repo root. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // getpid: per-process temp names + +namespace s5_r1 +{ + inline std::string fixture( char const* name ) { return std::string{ "./tests/fixtures/s5/" } + name; } + + // The process id keeps two suite binaries run by hand at once from sharing a temp path. + inline std::string temp_path( std::string const& name ) + { + return ( std::filesystem::temp_directory_path() / ( "feng_s5_r1_" + std::to_string( ::getpid() ) + "_" + name ) ).string(); + } + + inline std::string temp_file( std::string const& name, std::string const& bytes ) + { + auto const path = s5_r1::temp_path( name ); + std::ofstream ofs( path, std::ios::binary | std::ios::trunc ); + ofs.write( bytes.data(), static_cast( bytes.size() ) ); + ofs.close(); + return path; + } + + // An NPY file with the given header text (a newline is appended) and payload; version 1 uses a 2-byte length. + inline std::string npy_bytes( std::string header, std::string const& payload, int version = 1 ) + { + header += '\n'; + std::string out{ "\x93NUMPY", 6 }; + out += static_cast( version ); + out += '\0'; + std::size_t const n = header.size(); + out += static_cast( n & 0xff ); + out += static_cast( ( n >> 8 ) & 0xff ); + if ( version != 1 ) + { + out += static_cast( ( n >> 16 ) & 0xff ); + out += static_cast( ( n >> 24 ) & 0xff ); + } + return out + header + payload; + } + + template < typename T > + std::string payload_of( std::vector const& v ) + { + std::string s( v.size() * sizeof( T ), '\0' ); + if ( !v.empty() ) std::memcpy( s.data(), v.data(), s.size() ); + return s; + } + + inline feng::matrix prefilled() + { + feng::matrix m{ 2, 2 }; + m[0][0] = 1.0; m[0][1] = 2.0; m[1][0] = 3.0; m[1][1] = 4.0; + return m; + } + + inline bool is_prefilled( feng::matrix const& m ) + { + return m.row() == 2 && m.col() == 2 && m[0][0] == 1.0 && m[0][1] == 2.0 && m[1][0] == 3.0 && m[1][1] == 4.0; + } + + // Loads the synthetic file into a pre-filled 2x2 matrix; true when the load failed and kept it unchanged. + inline bool rejected( std::string const& name, std::string const& bytes ) + { + auto m = prefilled(); + bool const ok = m.load_npy( temp_file( name, bytes ) ); + return !ok && is_prefilled( m ); + } + + inline std::string const six_doubles = payload_of( std::vector{ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 } ); +} + +TEST_CASE( "S5 load_npy rejects a 3-byte file and keeps the destination", "[S5][S5-R4]" ) +{ + auto m = s5_r1::prefilled(); + std::ostringstream captured; + auto* const old = std::cerr.rdbuf( captured.rdbuf() ); + bool const ok = m.load_npy( s5_r1::temp_file( "three_bytes.npy", std::string{ "\x93NU", 3 } ) ); + std::cerr.rdbuf( old ); + REQUIRE( !ok ); + REQUIRE( s5_r1::is_prefilled( m ) ); + std::string const text = captured.str(); + REQUIRE( text.find( "load_npy" ) != std::string::npos ); + REQUIRE( std::count( text.begin(), text.end(), '\n' ) == 1 ); +} + +TEST_CASE( "S5 load_npy fails on a missing file and keeps the destination", "[S5][S5-R4]" ) +{ + auto m = s5_r1::prefilled(); + auto const path = s5_r1::temp_path( "does_not_exist.npy" ); + std::filesystem::remove( path ); + REQUIRE( !m.load_npy( path ) ); + REQUIRE( s5_r1::is_prefilled( m ) ); + REQUIRE( !m.load_npy( std::filesystem::temp_directory_path().string() ) ); // a directory + REQUIRE( s5_r1::is_prefilled( m ) ); +} + +TEST_CASE( "S5 load_npy rejects a shape whose byte size overflows", "[S5][S5-R1]" ) +{ + auto const bytes = s5_r1::npy_bytes( "{'descr': '{ 1.0 } ) ); + REQUIRE( s5_r1::rejected( "huge_shape.npy", bytes ) ); + REQUIRE( s5_r1::rejected( "huge_dim.npy", s5_r1::npy_bytes( "{'descr': ' f; + REQUIRE( f.load_npy( s5_r1::fixture( "f4_le.npy" ) ) ); + REQUIRE( f.row() == 2 ); REQUIRE( f.col() == 3 ); + REQUIRE( f[0][0] == 1.5f ); REQUIRE( f[0][1] == -2.25f ); REQUIRE( f[0][2] == 3.0f ); + REQUIRE( f[1][0] == 4.125f ); REQUIRE( f[1][1] == 5.0f ); REQUIRE( f[1][2] == -6.5f ); + + feng::matrix i8; + REQUIRE( !i8.load_npy( s5_r1::fixture( "f8_le.npy" ) ) ); + feng::matrix u4; + REQUIRE( !u4.load_npy( s5_r1::fixture( "i4_be.npy" ) ) ); + feng::matrix i1; + REQUIRE( !i1.load_npy( s5_r1::fixture( "u1.npy" ) ) ); +} + +TEST_CASE( "S5 load_npy loads little- and big-endian numpy files to numpy's values", "[S5][S5-R1]" ) +{ + for ( char const* name : { "f8_le.npy", "f8_be.npy" } ) + { + auto m = s5_r1::prefilled(); + REQUIRE( m.load_npy( s5_r1::fixture( name ) ) ); + REQUIRE( m.row() == 2 ); REQUIRE( m.col() == 3 ); + REQUIRE( m[0][0] == 1.5 ); REQUIRE( m[0][1] == -2.25 ); REQUIRE( m[0][2] == 3.0 ); + REQUIRE( m[1][0] == 4.125 ); REQUIRE( m[1][1] == 5.0 ); REQUIRE( m[1][2] == -6.5 ); + } + feng::matrix i; + REQUIRE( i.load_npy( s5_r1::fixture( "i4_be.npy" ) ) ); + REQUIRE( i.row() == 2 ); REQUIRE( i.col() == 3 ); + REQUIRE( i[0][0] == 1 ); REQUIRE( i[0][1] == -2 ); REQUIRE( i[0][2] == 3 ); + REQUIRE( i[1][0] == 70000 ); REQUIRE( i[1][1] == -80000 ); REQUIRE( i[1][2] == 2147483647 ); + for ( char const* name : { "c16_le.npy", "c16_be.npy" } ) + { + feng::matrix> c; + REQUIRE( c.load_npy( s5_r1::fixture( name ) ) ); + REQUIRE( c.row() == 2 ); REQUIRE( c.col() == 2 ); + REQUIRE( c[0][0] == std::complex{ 1.0, 2.0 } ); REQUIRE( c[0][1] == std::complex{ -3.5, 0.25 } ); + REQUIRE( c[1][0] == std::complex{ 0.0, -1.0 } ); REQUIRE( c[1][1] == std::complex{ 7.0, 0.0 } ); + } + feng::matrix u; + REQUIRE( u.load_npy( s5_r1::fixture( "u1.npy" ) ) ); + REQUIRE( u.row() == 2 ); REQUIRE( u.col() == 3 ); + REQUIRE( u[0][0] == 0 ); REQUIRE( u[0][1] == 1 ); REQUIRE( u[0][2] == 255 ); + REQUIRE( u[1][0] == 128 ); REQUIRE( u[1][1] == 7 ); REQUIRE( u[1][2] == 42 ); +} + +TEST_CASE( "S5 load_npy loads a Fortran-order 2x3 as the logical matrix", "[S5][S5-R1]" ) +{ + feng::matrix m; + REQUIRE( m.load_npy( s5_r1::fixture( "fortran_2x3.npy" ) ) ); + REQUIRE( m.row() == 2 ); REQUIRE( m.col() == 3 ); + REQUIRE( m[0][0] == 1.5 ); REQUIRE( m[0][1] == -2.25 ); REQUIRE( m[0][2] == 3.0 ); + REQUIRE( m[1][0] == 4.125 ); REQUIRE( m[1][1] == 5.0 ); REQUIRE( m[1][2] == -6.5 ); +} + +TEST_CASE( "S5 load_npy loads a 1-D (3,) array as 1x3 and rejects rank 0 and 3", "[S5][S5-R1]" ) +{ + feng::matrix m; + REQUIRE( m.load_npy( s5_r1::fixture( "one_d.npy" ) ) ); + REQUIRE( m.row() == 1 ); REQUIRE( m.col() == 3 ); + REQUIRE( m[0][0] == 10.0 ); REQUIRE( m[0][1] == 20.0 ); REQUIRE( m[0][2] == 30.0 ); + + auto p = s5_r1::prefilled(); + REQUIRE( !p.load_npy( s5_r1::fixture( "three_d.npy" ) ) ); + REQUIRE( s5_r1::is_prefilled( p ) ); + REQUIRE( s5_r1::rejected( "rank0.npy", s5_r1::npy_bytes( "{'descr': '{ 1.0 } ) ) ) ); +} + +TEST_CASE( "S5 load_npy rejects object and structured dtypes", "[S5][S5-R1]" ) +{ + for ( char const* name : { "object.npy", "structured.npy" } ) + { + auto m = s5_r1::prefilled(); + REQUIRE( !m.load_npy( s5_r1::fixture( name ) ) ); + REQUIRE( s5_r1::is_prefilled( m ) ); + } +} + +TEST_CASE( "S5 load_npy accepts the dict literal variants numpy may write", "[S5][S5-R1]" ) +{ + using s5_r1::npy_bytes; + using s5_r1::six_doubles; + for ( std::string const& header : { + std::string{ "{\"descr\": \" m; + REQUIRE( m.load_npy( s5_r1::temp_file( "variant.npy", npy_bytes( header, six_doubles, version ) ) ) ); + REQUIRE( m.row() == 2 ); REQUIRE( m.col() == 3 ); + REQUIRE( m[0][0] == 1.0 ); REQUIRE( m[1][2] == 6.0 ); + } + } + feng::matrix e; + REQUIRE( e.load_npy( s5_r1::temp_file( "empty.npy", npy_bytes( "{'descr': ' i1; + REQUIRE( i1.load_npy( s5_r1::temp_file( "i1.npy", npy_bytes( "{'descr': '|i1', 'fortran_order': False, 'shape': (1, 2), }", std::string{ "\x01\xff", 2 } ) ) ) ); + REQUIRE( i1.row() == 1 ); REQUIRE( i1.col() == 2 ); + REQUIRE( i1[0][0] == 1 ); REQUIRE( i1[0][1] == -1 ); +} + +// ---- S5-T7 (S5-R1): every fixture, every truncation, the dtype x element-type grid, malformed numbers ---- +namespace s5_r1 +{ + using all_element_types = std::tuple< std::int8_t, std::int16_t, std::int32_t, std::int64_t, std::uint8_t, std::uint16_t, + std::uint32_t, std::uint64_t, float, double, std::complex< float >, + std::complex< double >, long double >; + + template < typename T > + feng::matrix< T > prefilled_of() + { + feng::matrix< T > m{ 2, 2 }; + for ( std::size_t k = 0; k != 4; ++k ) m.data()[k] = static_cast< T >( k + 1 ); + return m; + } + + template < typename T > + bool is_prefilled_of( feng::matrix< T > const& m ) + { + if ( m.row() != 2 || m.col() != 2 ) return false; + for ( std::size_t k = 0; k != 4; ++k ) + if ( !( m.data()[k] == static_cast< T >( k + 1 ) ) ) return false; + return true; + } + + inline std::vector< std::uint8_t > file_bytes( std::string const& path ) + { + std::ifstream ifs( path, std::ios::binary ); + return std::vector< std::uint8_t >( std::istreambuf_iterator< char >( ifs ), std::istreambuf_iterator< char >() ); + } + + // In-memory parse of `bytes` into a pre-filled matrix: 1 parsed (`loaded` receives the result), 0 failed with + // the destination unchanged, -1 failed but changed the destination. + template < typename T > + int parses( std::string const& bytes, feng::matrix< T >& loaded ) + { + std::vector< std::uint8_t > const buffer( bytes.begin(), bytes.end() ); // exact size, so ASan sees overreads + auto m = prefilled_of< T >(); + if ( !feng::matrix_details::parse_npy< T >( buffer.data(), buffer.size(), m ) ) return is_prefilled_of( m ) ? 0 : -1; + loaded = m; + return 1; + } + + // Loads fixture `name` into matrix and compares it with numpy's values (row-major). + template < typename T > + bool loads_to( char const* name, std::size_t r, std::size_t c, std::vector< T > const& expected ) + { + auto m = prefilled_of< T >(); + if ( !m.load_npy( fixture( name ) ) ) return false; + if ( m.row() != r || m.col() != c || expected.size() != r * c ) return false; + for ( std::size_t k = 0; k != expected.size(); ++k ) + if ( !( m.data()[k] == expected[k] ) ) return false; + return true; + } + + // True when fixture `name` fails, with the destination unchanged, for every element type except `Except`. + template < typename Except = void > + bool rejected_by_all_but( char const* name ) + { + bool all = true; + std::apply( [&]( auto... tag ) + { + ( [&]( auto t ) + { + using U = decltype( t ); + if constexpr ( !std::is_same_v< U, Except > ) + { + auto m = prefilled_of< U >(); + if ( m.load_npy( fixture( name ) ) || !is_prefilled_of( m ) ) all = false; + } + }( tag ), ... ); + }, + all_element_types{} ); + return all; + } + + template < typename T > + constexpr char kind_of() noexcept + { + if constexpr ( std::is_same_v< T, float > || std::is_same_v< T, double > ) return 'f'; + else if constexpr ( std::is_same_v< T, std::complex< float > > || std::is_same_v< T, std::complex< double > > ) return 'c'; + else if constexpr ( std::is_integral_v< T > && std::is_signed_v< T > ) return 'i'; + else if constexpr ( std::is_integral_v< T > ) return 'u'; + else return '?'; // long double: no NPY dtype (D-021) + } +} + +TEST_CASE( "S5 load_npy loads every numpy fixture to numpy's values", "[S5][S5-R1]" ) +{ + using namespace s5_r1; + using cf = std::complex< float >; + using cd = std::complex< double >; + std::vector< double > const base{ 1.5, -2.25, 3.0, 4.125, 5.0, -6.5 }; + std::vector< float > const basef{ 1.5f, -2.25f, 3.0f, 4.125f, 5.0f, -6.5f }; + std::vector< std::int8_t > const i1{ -128, -1, 0, 1, 64, 127 }; + std::vector< std::int16_t > const i2{ -32768, -2, 0, 1, 300, 32767 }; + std::vector< std::int32_t > const i4{ 1, -2, 3, 70000, -80000, 2147483647 }; + std::vector< std::int64_t > const i8{ std::numeric_limits< std::int64_t >::min(), -1, 0, 1, 1099511627776LL, std::numeric_limits< std::int64_t >::max() }; + std::vector< std::uint8_t > const u1{ 0, 1, 255, 128, 7, 42 }; + std::vector< std::uint16_t > const u2{ 0, 1, 65535, 256, 4660, 65280 }; + std::vector< std::uint32_t > const u4{ 0, 1, 4294967295u, 65536, 305419896u, 4278190080u }; + std::vector< std::uint64_t > const u8{ 0, 1, 18446744073709551615ULL, 4294967296ULL, 0x0123456789ABCDEFULL, 0xFF00000000000000ULL }; + std::vector< cf > const c8{ { 1.0f, 2.0f }, { -3.5f, 0.25f }, { 0.0f, -1.0f }, { 7.0f, 0.0f } }; + std::vector< cd > const c16{ { 1.0, 2.0 }, { -3.5, 0.25 }, { 0.0, -1.0 }, { 7.0, 0.0 } }; + + // Each loadable fixture loads to numpy's values into its element type and into no other. + for ( char const* name : { "f8_le.npy", "f8_be.npy", "f8_v2.npy", "f8_v3.npy", "fortran_2x3.npy" } ) + { + REQUIRE( loads_to< double >( name, 2, 3, base ) ); + REQUIRE( rejected_by_all_but< double >( name ) ); + } + REQUIRE( loads_to< double >( "one_d.npy", 1, 3, { 10.0, 20.0, 30.0 } ) ); + REQUIRE( rejected_by_all_but< double >( "one_d.npy" ) ); + for ( char const* name : { "f4_le.npy", "f4_be.npy" } ) + { + REQUIRE( loads_to< float >( name, 2, 3, basef ) ); + REQUIRE( rejected_by_all_but< float >( name ) ); + } + REQUIRE( loads_to< std::int8_t >( "i1.npy", 2, 3, i1 ) ); + REQUIRE( rejected_by_all_but< std::int8_t >( "i1.npy" ) ); + for ( char const* name : { "i2_le.npy", "i2_be.npy" } ) + { + REQUIRE( loads_to< std::int16_t >( name, 2, 3, i2 ) ); + REQUIRE( rejected_by_all_but< std::int16_t >( name ) ); + } + for ( char const* name : { "i4_le.npy", "i4_be.npy" } ) + { + REQUIRE( loads_to< std::int32_t >( name, 2, 3, i4 ) ); + REQUIRE( rejected_by_all_but< std::int32_t >( name ) ); + } + for ( char const* name : { "i8_le.npy", "i8_be.npy" } ) + { + REQUIRE( loads_to< std::int64_t >( name, 2, 3, i8 ) ); + REQUIRE( rejected_by_all_but< std::int64_t >( name ) ); + } + REQUIRE( loads_to< std::uint8_t >( "u1.npy", 2, 3, u1 ) ); + REQUIRE( rejected_by_all_but< std::uint8_t >( "u1.npy" ) ); + for ( char const* name : { "u2_le.npy", "u2_be.npy" } ) + { + REQUIRE( loads_to< std::uint16_t >( name, 2, 3, u2 ) ); + REQUIRE( rejected_by_all_but< std::uint16_t >( name ) ); + } + for ( char const* name : { "u4_le.npy", "u4_be.npy" } ) + { + REQUIRE( loads_to< std::uint32_t >( name, 2, 3, u4 ) ); + REQUIRE( rejected_by_all_but< std::uint32_t >( name ) ); + } + for ( char const* name : { "u8_le.npy", "u8_be.npy" } ) + { + REQUIRE( loads_to< std::uint64_t >( name, 2, 3, u8 ) ); + REQUIRE( rejected_by_all_but< std::uint64_t >( name ) ); + } + for ( char const* name : { "c8_le.npy", "c8_be.npy" } ) + { + REQUIRE( loads_to< cf >( name, 2, 2, c8 ) ); + REQUIRE( rejected_by_all_but< cf >( name ) ); + } + for ( char const* name : { "c16_le.npy", "c16_be.npy" } ) + { + REQUIRE( loads_to< cd >( name, 2, 2, c16 ) ); + REQUIRE( rejected_by_all_but< cd >( name ) ); + } + // 3-D, object, structured, bool and f2 fixtures fail for every element type. + for ( char const* name : { "three_d.npy", "object.npy", "structured.npy", "bool.npy", "f2.npy" } ) + REQUIRE( rejected_by_all_but( name ) ); +} + +TEST_CASE( "S5 load_npy fails at every truncation of a valid v1.0 and v2.0 file", "[S5][S5-R1]" ) +{ + using namespace s5_r1; + for ( char const* name : { "f8_le.npy", "f8_v2.npy" } ) + { + auto const bytes = file_bytes( fixture( name ) ); + REQUIRE( bytes.size() == 176 ); + { + auto whole = prefilled_of< double >(); + REQUIRE( feng::matrix_details::parse_npy< double >( bytes.data(), bytes.size(), whole ) ); + } + for ( std::size_t n = 0; n != bytes.size(); ++n ) + { + std::vector< std::uint8_t > const prefix( bytes.begin(), bytes.begin() + static_cast< std::ptrdiff_t >( n ) ); + auto m = prefilled_of< double >(); + INFO( name << " cut at " << n ); + REQUIRE( !feng::matrix_details::parse_npy< double >( prefix.data(), prefix.size(), m ) ); + REQUIRE( is_prefilled_of( m ) ); + } + } + // The file loader agrees for a few cuts (empty file, inside the magic, the header and the payload). + for ( std::size_t n : { std::size_t{ 0 }, std::size_t{ 5 }, std::size_t{ 60 }, std::size_t{ 175 } } ) + { + auto const bytes = file_bytes( fixture( "f8_v2.npy" ) ); + REQUIRE( rejected( "cut_v2.npy", std::string( bytes.begin(), bytes.begin() + static_cast< std::ptrdiff_t >( n ) ) ) ); + } +} + +TEST_CASE( "S5 load_npy loads a dtype only into the element type of the same kind and size", "[S5][S5-R1]" ) +{ + using namespace s5_r1; + struct dtype { char kind; std::size_t size; }; + dtype const dtypes[] = { { 'i', 1 }, { 'i', 2 }, { 'i', 4 }, { 'i', 8 }, { 'u', 1 }, { 'u', 2 }, { 'u', 4 }, { 'u', 8 }, + { 'f', 4 }, { 'f', 8 }, { 'c', 8 }, { 'c', 16 } }; + std::size_t accepted = 0; + for ( char const order : { '<', '>', '=', '|' } ) + for ( dtype const d : dtypes ) + { + std::string const descr = std::string{ order, d.kind } + std::to_string( d.size ); + std::string payload( 2 * d.size, '\0' ); + for ( std::size_t k = 0; k != payload.size(); ++k ) payload[k] = static_cast< char >( k + 1 ); + auto const bytes = npy_bytes( "{'descr': '" + descr + "', 'fortran_order': False, 'shape': (1, 2), }", payload ); + bool const foreign = ( order == '>' && std::endian::native == std::endian::little ) + || ( order == '<' && std::endian::native == std::endian::big ); + std::apply( [&]( auto... tag ) + { + ( [&]( auto t ) + { + using T = decltype( t ); + bool const expect = kind_of< T >() == d.kind && sizeof( T ) == d.size && ( order != '|' || d.size == 1 ); + feng::matrix< T > loaded; + int const result = parses< T >( bytes, loaded ); + INFO( descr << " into an element of " << sizeof( T ) << " bytes, kind " << kind_of< T >() ); + REQUIRE( result == ( expect ? 1 : 0 ) ); + if ( result != 1 ) return; + ++accepted; + REQUIRE( loaded.row() == 1 ); + REQUIRE( loaded.col() == 2 ); + // Foreign order reverses each component (each half of a complex element). + std::string want = payload; + std::size_t const unit = d.kind == 'c' ? d.size / 2 : d.size; + if ( foreign ) + for ( std::size_t u = 0; u != want.size(); u += unit ) + std::reverse( want.begin() + static_cast< std::ptrdiff_t >( u ), + want.begin() + static_cast< std::ptrdiff_t >( u + unit ) ); + REQUIRE( std::memcmp( loaded.data(), want.data(), want.size() ) == 0 ); + }( tag ), ... ); + }, + all_element_types{} ); + } + REQUIRE( accepted == 12 * 3 + 2 ); // '<', '>' and '=' for every dtype; '|' only for i1 and u1 +} + +TEST_CASE( "S5 load_npy rejects malformed dicts and numbers with leading zeros, signs or overflow", "[S5][S5-R1]" ) +{ + using s5_r1::npy_bytes; + using s5_r1::rejected; + using s5_r1::six_doubles; + auto const header = []( std::string const& descr, std::string const& shape ) + { return "{'descr': '" + descr + "', 'fortran_order': False, 'shape': " + shape + ", }"; }; + REQUIRE( !rejected( "control.npy", npy_bytes( header( " i; + REQUIRE( !i.load_npy( s5_r1::temp_file( "i04.npy", npy_bytes( header( "( ifs ), std::istreambuf_iterator< char >() }; + REQUIRE( text.size() > 100000 ); + std::string hits; + for ( std::size_t p = text.find( "std::sto" ); p != std::string::npos; p = text.find( "std::sto", p + 1 ) ) + { + std::size_t e = p + 8; + while ( e != text.size() && ( std::isalnum( static_cast< unsigned char >( text[e] ) ) || text[e] == '_' ) ) ++e; + std::string const name = text.substr( p + 5, e - p - 5 ); + for ( char const* banned : { "stoi", "stol", "stoll", "stoul", "stoull", "stof", "stod", "stold" } ) + if ( name == banned ) hits += "std::" + name + " at offset " + std::to_string( p ) + "; "; + } + INFO( hits ); + REQUIRE( hits.empty() ); +} diff --git a/tests/cases/s5_r2.hpp b/tests/cases/s5_r2.hpp new file mode 100644 index 0000000..9b4a649 --- /dev/null +++ b/tests/cases/s5_r2.hpp @@ -0,0 +1,553 @@ +// S5-R2 (PR-7, F08): text and native binary inputs are bounded and typed; S5-R4 (PR-2, D-011): their loaders are +// transactional. Temporary files go under std::filesystem::temp_directory_path(). +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // getpid: per-process temp names + +namespace s5_r2 +{ + // The process id keeps two suite binaries run by hand at once from sharing a temp path. + inline std::string temp_path( std::string const& name ) + { + return ( std::filesystem::temp_directory_path() / ( "feng_s5_r2_" + std::to_string( ::getpid() ) + "_" + name ) ).string(); + } + + inline std::string temp_file( std::string const& name, std::string const& bytes ) + { + auto const path = temp_path( name ); + std::ofstream ofs( path, std::ios::binary | std::ios::trunc ); + ofs.write( bytes.data(), static_cast( bytes.size() ) ); + ofs.close(); + return path; + } + + template < typename T = double > + feng::matrix prefilled() + { + feng::matrix m{ 2, 2 }; + m[0][0] = T{ 1 }; m[0][1] = T{ 2 }; m[1][0] = T{ 3 }; m[1][1] = T{ 4 }; + return m; + } + + template < typename T = double > + bool is_prefilled( feng::matrix const& m ) + { + return m.row() == 2 && m.col() == 2 && m[0][0] == T{ 1 } && m[0][1] == T{ 2 } && m[1][0] == T{ 3 } && m[1][1] == T{ 4 }; + } + + // Loads the text into a pre-filled 2x2 matrix; true when the load failed and left it unchanged. + template < typename T = double > + bool txt_rejected( std::string const& name, std::string const& text ) + { + auto m = prefilled(); + bool const ok = m.load_txt( temp_file( name, text ) ); + return !ok && is_prefilled( m ); + } + + template < typename T = double > + bool bin_rejected( std::string const& name, std::string const& bytes ) + { + auto m = prefilled(); + bool const ok = m.load_binary( temp_file( name, bytes ) ); + return !ok && is_prefilled( m ); + } + + // A native binary image: two size_type counts, then the raw elements. + template < typename T > + std::string binary_bytes( std::uint64_t r, std::uint64_t c, std::vector const& v ) + { + using size_type = typename feng::matrix::size_type; + size_type const rr = static_cast( r ); + size_type const cc = static_cast( c ); + std::string s( 2 * sizeof( size_type ) + v.size() * sizeof( T ), '\0' ); + std::memcpy( s.data(), &rr, sizeof( rr ) ); + std::memcpy( s.data() + sizeof( rr ), &cc, sizeof( cc ) ); + if ( !v.empty() ) std::memcpy( s.data() + 2 * sizeof( size_type ), v.data(), v.size() * sizeof( T ) ); + return s; + } + + template < typename T > + bool same( feng::matrix const& a, feng::matrix const& b ) + { + return a.row() == b.row() && a.col() == b.col() && std::equal( a.begin(), a.end(), b.begin() ); + } +} + +TEST_CASE( "S5 load_txt rejects empty and ragged text and keeps the destination", "[S5][S5-R2][S5-R4]" ) +{ + REQUIRE( s5_r2::txt_rejected( "empty.txt", "" ) ); + REQUIRE( s5_r2::txt_rejected( "blank.txt", " \n\t\r\n,;\n\n" ) ); + REQUIRE( s5_r2::txt_rejected( "ragged.txt", "1 2 3\n4 5\n" ) ); + REQUIRE( s5_r2::txt_rejected( "ragged2.txt", "1\n2 3\n" ) ); + + auto m = s5_r2::prefilled(); + std::ostringstream captured; + auto* const old = std::cerr.rdbuf( captured.rdbuf() ); + bool const ok = m.load_txt( s5_r2::temp_file( "ragged3.txt", "1,2\n3\n" ) ); + std::cerr.rdbuf( old ); + REQUIRE( !ok ); + REQUIRE( s5_r2::is_prefilled( m ) ); + std::string const text = captured.str(); + REQUIRE( text.find( "load_txt" ) != std::string::npos ); + REQUIRE( std::count( text.begin(), text.end(), '\n' ) == 1 ); + + auto const missing = s5_r2::temp_path( "does_not_exist.txt" ); + std::filesystem::remove( missing ); + REQUIRE( !m.load_txt( missing ) ); + REQUIRE( !m.load_txt( std::filesystem::temp_directory_path().string() ) ); + REQUIRE( s5_r2::is_prefilled( m ) ); +} + +TEST_CASE( "S5 load_txt rejects truncated and oversized tokens", "[S5][S5-R2][S5-R4]" ) +{ + REQUIRE( s5_r2::txt_rejected( "trunc_exp.txt", "1 2\n3 1.5e\n" ) ); + REQUIRE( s5_r2::txt_rejected( "junk.txt", "1 2\n3 4x\n" ) ); + REQUIRE( s5_r2::txt_rejected( "double_plus.txt", "1 ++2\n" ) ); + REQUIRE( s5_r2::txt_rejected( "plus_minus.txt", "1 +-2\n" ) ); + REQUIRE( s5_r2::txt_rejected( "huge_double.txt", "1 1e999\n" ) ); + REQUIRE( s5_r2::txt_rejected( "u8_300.txt", "1 300\n" ) ); + REQUIRE( s5_r2::txt_rejected( "u8_neg.txt", "1 -1\n" ) ); + REQUIRE( s5_r2::txt_rejected( "int_fraction.txt", "1 2.5\n" ) ); + REQUIRE( s5_r2::txt_rejected( "int_huge.txt", "1 99999999999999999999\n" ) ); + REQUIRE( s5_r2::txt_rejected( "i8_128.txt", "128\n" ) ); + REQUIRE( s5_r2::txt_rejected>( "cx_open.txt", "(1,2 3\n" ) ); + REQUIRE( s5_r2::txt_rejected>( "cx_one.txt", "(1)\n" ) ); + REQUIRE( s5_r2::txt_rejected>( "cx_three.txt", "(1,2,3)\n" ) ); +} + +TEST_CASE( "S5 load_txt accepts separators, blank lines, CRLF, a plus sign and complex pairs", "[S5][S5-R2]" ) +{ + feng::matrix m; + REQUIRE( m.load_txt( s5_r2::temp_file( "seps.txt", "\n1,2;3\r\n \n+4\t5 -6.25e1\r\n\n" ) ) ); + REQUIRE( m.row() == 2 ); REQUIRE( m.col() == 3 ); + REQUIRE( m[0][0] == 1.0 ); REQUIRE( m[0][1] == 2.0 ); REQUIRE( m[0][2] == 3.0 ); + REQUIRE( m[1][0] == 4.0 ); REQUIRE( m[1][1] == 5.0 ); REQUIRE( m[1][2] == -62.5 ); + + feng::matrix u; + REQUIRE( u.load_txt( s5_r2::temp_file( "u8.txt", "0 255\n+7 32" ) ) ); + REQUIRE( u.row() == 2 ); REQUIRE( u.col() == 2 ); + REQUIRE( u[0][0] == 0 ); REQUIRE( u[0][1] == 255 ); REQUIRE( u[1][0] == 7 ); REQUIRE( u[1][1] == 32 ); + + feng::matrix> z; + REQUIRE( z.load_txt( s5_r2::temp_file( "cx.txt", "(1,2) 3\n(-1.5,+0.5),(4, 0)\n" ) ) ); + REQUIRE( z.row() == 2 ); REQUIRE( z.col() == 2 ); + REQUIRE( z[0][0] == std::complex( 1, 2 ) ); REQUIRE( z[0][1] == std::complex( 3, 0 ) ); + REQUIRE( z[1][0] == std::complex( -1.5, 0.5 ) ); REQUIRE( z[1][1] == std::complex( 4, 0 ) ); +} + +TEST_CASE( "S5 save_as_txt output loads back for double, int and uint8_t", "[S5][S5-R2]" ) +{ + { + feng::matrix a{ 2, 3 }; + a[0][0] = 0.1; a[0][1] = 1.0 / 3.0; a[0][2] = -2.5e-300; + a[1][0] = 1.0e300; a[1][1] = std::numeric_limits::min(); a[1][2] = -0.0; + auto const path = s5_r2::temp_path( "rt_double.txt" ); + REQUIRE( a.save_as_txt( path ) ); + feng::matrix b; + REQUIRE( b.load_txt( path ) ); + REQUIRE( s5_r2::same( a, b ) ); + } + { + feng::matrix a{ 2, 2 }; + a[0][0] = std::numeric_limits::min(); a[0][1] = std::numeric_limits::max(); a[1][0] = 0; a[1][1] = -7; + auto const path = s5_r2::temp_path( "rt_int.txt" ); + REQUIRE( a.save_as_txt( path ) ); + feng::matrix b; + REQUIRE( b.load_txt( path ) ); + REQUIRE( s5_r2::same( a, b ) ); + } + { + feng::matrix a{ 2, 3 }; + a[0][0] = 0; a[0][1] = 9; a[0][2] = 10; a[1][0] = 32; a[1][1] = 44; a[1][2] = 255; + auto const path = s5_r2::temp_path( "rt_u8.txt" ); + REQUIRE( a.save_as_txt( path ) ); + feng::matrix b; + REQUIRE( b.load_txt( path ) ); + REQUIRE( s5_r2::same( a, b ) ); + } + { + feng::matrix a{ 1, 3 }; + a[0][0] = -128; a[0][1] = 0; a[0][2] = 127; + auto const path = s5_r2::temp_path( "rt_i8.txt" ); + REQUIRE( a.save_as_txt( path ) ); + feng::matrix b; + REQUIRE( b.load_txt( path ) ); + REQUIRE( s5_r2::same( a, b ) ); + } +} + +TEST_CASE( "S5 operator>> parses text and sets failbit without touching the matrix on bad input", "[S5][S5-R2][S5-R4]" ) +{ + feng::matrix m; + std::istringstream good{ "1\t2\t\n3\t4\t\n" }; + good >> m; + REQUIRE( !good.fail() ); + REQUIRE( s5_r2::is_prefilled( m ) ); + + std::istringstream ragged{ "1 2\n3\n" }; + ragged >> m; + REQUIRE( ragged.fail() ); + REQUIRE( s5_r2::is_prefilled( m ) ); + + std::istringstream empty{ "" }; + empty >> m; + REQUIRE( empty.fail() ); + REQUIRE( s5_r2::is_prefilled( m ) ); +} + +TEST_CASE( "S5 load_binary rejects short, long and overflowing-count files into a pre-filled matrix", "[S5][S5-R2][S5-R4]" ) +{ + REQUIRE( s5_r2::bin_rejected( "empty.bin", "" ) ); + REQUIRE( s5_r2::bin_rejected( "three.bin", "abc" ) ); + auto const header_only = s5_r2::binary_bytes( 2, 2, {} ); + REQUIRE( s5_r2::bin_rejected( "one_count.bin", header_only.substr( 0, 8 ) ) ); + REQUIRE( s5_r2::bin_rejected( "short.bin", s5_r2::binary_bytes( 2, 2, { 1, 2, 3 } ) ) ); + REQUIRE( s5_r2::bin_rejected( "long.bin", s5_r2::binary_bytes( 2, 2, { 1, 2, 3, 4, 5 } ) ) ); + auto trailing = s5_r2::binary_bytes( 2, 2, { 1, 2, 3, 4 } ); + trailing += '\0'; + REQUIRE( s5_r2::bin_rejected( "trailing.bin", trailing ) ); + REQUIRE( s5_r2::bin_rejected( "count_overflow.bin", s5_r2::binary_bytes( 1ull << 32, 1ull << 32, { 1 } ) ) ); + REQUIRE( s5_r2::bin_rejected( "bytes_overflow.bin", s5_r2::binary_bytes( 1ull << 62, 1, { 1 } ) ) ); + REQUIRE( s5_r2::bin_rejected( "max_counts.bin", s5_r2::binary_bytes( ~0ull, ~0ull, { 1 } ) ) ); + REQUIRE( s5_r2::bin_rejected( "huge_rows.bin", s5_r2::binary_bytes( 1ull << 40, 1, { 1 } ) ) ); + + auto m = s5_r2::prefilled(); + std::ostringstream captured; + auto* const old = std::cerr.rdbuf( captured.rdbuf() ); + bool const ok = m.load_binary( s5_r2::temp_file( "short2.bin", s5_r2::binary_bytes( 2, 2, { 1 } ) ) ); + std::cerr.rdbuf( old ); + REQUIRE( !ok ); + REQUIRE( s5_r2::is_prefilled( m ) ); + std::string const text = captured.str(); + REQUIRE( text.find( "load_binary" ) != std::string::npos ); + REQUIRE( std::count( text.begin(), text.end(), '\n' ) == 1 ); + + auto const missing = s5_r2::temp_path( "does_not_exist.bin" ); + std::filesystem::remove( missing ); + REQUIRE( !m.load_binary( missing ) ); + REQUIRE( s5_r2::is_prefilled( m ) ); +} + +TEST_CASE( "S5 load_binary round-trips 0x3, 3x0 and filled matrices", "[S5][S5-R2]" ) +{ + for ( auto const& [r, c] : { std::pair{ 0, 3 }, { 3, 0 }, { 2, 3 } } ) + { + feng::matrix a{ r, c }; + for ( std::size_t k = 0; k != a.size(); ++k ) a.data()[k] = 0.5 * static_cast( k ) - 1.0; + auto const path = s5_r2::temp_path( "rt_" + std::to_string( r ) + "x" + std::to_string( c ) + ".bin" ); + REQUIRE( a.save_as_binary( path ) ); + auto b = s5_r2::prefilled(); + REQUIRE( b.load_binary( path ) ); + REQUIRE( b.row() == r ); + REQUIRE( b.col() == c ); + REQUIRE( s5_r2::same( a, b ) ); + } + feng::matrix> z{ 1, 2 }; + z[0][0] = { 1.0f, -2.0f }; z[0][1] = { 0.25f, 8.0f }; + auto const path = s5_r2::temp_path( "rt_cf.bin" ); + REQUIRE( z.save_as_binary( path ) ); + feng::matrix> w; + REQUIRE( w.load_binary( path ) ); + REQUIRE( s5_r2::same( z, w ) ); +} + +namespace s5_r2 +{ + // Records the largest single request (in bytes) any destination matrix made through this allocator. + inline std::size_t largest_request = 0; + + template < typename T > + struct max_request_allocator + { + using value_type = T; + max_request_allocator() noexcept = default; + template < typename U > max_request_allocator( max_request_allocator const& ) noexcept {} + T* allocate( std::size_t n ) + { + largest_request = std::max( largest_request, n * sizeof( T ) ); + return std::allocator{}.allocate( n ); + } + void deallocate( T* p, std::size_t n ) noexcept { std::allocator{}.deallocate( p, n ); } + template < typename U > friend bool operator == ( max_request_allocator const&, max_request_allocator const& ) noexcept { return true; } + }; + + template < typename T > + using tracked_matrix = feng::matrix< T, max_request_allocator >; + + // A version 1.0 NPY image with the given header dict and payload; the header is padded to a multiple of 64. + inline std::string npy_bytes( std::string const& dict, std::string const& payload ) + { + std::string header = dict; + while ( ( 10 + header.size() + 1 ) % 64 != 0 ) header += ' '; + header += '\n'; + std::string s = std::string( "\x93NUMPY\x01\x00", 8 ); + s += static_cast( header.size() & 0xff ); + s += static_cast( header.size() >> 8 ); + return s + header + payload; + } + + template < typename T > + std::string raw( std::vector const& v ) + { + std::string s( v.size() * sizeof( T ), '\0' ); + if ( !v.empty() ) std::memcpy( s.data(), v.data(), s.size() ); + return s; + } + + // Runs `load` on the bytes into a pre-filled tracked matrix; true when the largest destination request is at + // most sizeof(T) * (file size + 1). + template < typename T, typename Load > + bool bounded( std::string const& name, std::string const& bytes, Load load ) + { + tracked_matrix m{ 2, 2, T{ 1 } }; + auto const path = temp_file( name, bytes ); + largest_request = 0; + (void)load( m, path ); + return largest_request <= sizeof( T ) * ( bytes.size() + 1 ); + } + + template < typename T > + std::vector extremes() + { + if constexpr ( std::is_integral_v ) + return { std::numeric_limits::lowest(), std::numeric_limits::max(), T{ 0 }, T{ 1 }, static_cast( std::numeric_limits::max() - 1 ), static_cast( std::numeric_limits::lowest() + 1 ) }; + else + return { std::numeric_limits::lowest(), std::numeric_limits::max(), std::numeric_limits::min(), std::numeric_limits::denorm_min(), + -std::numeric_limits::denorm_min(), std::numeric_limits::epsilon(), T{ 1 } / T{ 3 }, T{ 0 } }; + } + + template < typename T > + std::vector extreme_elements() + { + if constexpr ( std::is_floating_point_v || std::is_integral_v ) + return extremes(); + else + { + using F = typename T::value_type; + auto const e = extremes(); + std::vector v; + for ( std::size_t k = 0; k != e.size(); ++k ) v.emplace_back( e[k], e[e.size() - 1 - k] ); + return v; + } + } + + // A 2 x (n/2) matrix holding the type's extreme values. + template < typename T > + feng::matrix extreme_matrix() + { + auto const v = extreme_elements(); + feng::matrix a{ 2, static_cast( v.size() / 2 ) }; + std::copy( v.begin(), v.begin() + static_cast( a.size() ), a.begin() ); + return a; + } + + template < typename T > + bool txt_round_trips( std::string const& name ) + { + auto const a = extreme_matrix(); + auto const path = temp_path( name ); + if ( !a.save_as_txt( path ) ) return false; + feng::matrix b; + return b.load_txt( path ) && same( a, b ); + } + + template < typename T > + bool bin_round_trips( std::string const& name ) + { + auto const a = extreme_matrix(); + auto const path = temp_path( name ); + if ( !a.save_as_binary( path ) ) return false; + auto b = prefilled(); + return b.load_binary( path ) && same( a, b ); + } + + // Integer type T rejects max+1 and min-1 (given as text). + template < typename T > + bool int_out_of_range_rejected( std::string const& above, std::string const& below ) + { + return txt_rejected( "above.txt", "1 " + above + "\n" ) && txt_rejected( "below.txt", "1 " + below + "\n" ); + } +} + +TEST_CASE( "S5 every loader's largest allocation is bounded by the file size", "[S5][S5-R2]" ) +{ + auto txt = []( auto& m, std::string const& p ) { return m.load_txt( p ); }; + auto bin = []( auto& m, std::string const& p ) { return m.load_binary( p ); }; + auto npy = []( auto& m, std::string const& p ) { return m.load_npy( p ); }; + + // load_txt: valid, ragged, bad token, blank and empty inputs. + for ( std::string const& text : std::vector{ "1 2 3\n4 5 6\n", "1\n", "1,2;3 4\t5\r\n", "1 2\n3\n", "1 2\n3 x\n", "\n\n", "", + std::string( 4096, '1' ) + "\n", "(1,2) (3,4)\n" } ) + { + REQUIRE( s5_r2::bounded( "bnd.txt", text, txt ) ); + REQUIRE( s5_r2::bounded( "bnd.txt", text, txt ) ); + REQUIRE( s5_r2::bounded>( "bnd.txt", text, txt ) ); + } + // The densest text: one-character tokens with one-character separators. + std::string dense; + for ( int k = 0; k != 1000; ++k ) dense += "1 "; + REQUIRE( s5_r2::bounded( "dense.txt", dense, txt ) ); + REQUIRE( s5_r2::largest_request == 1000 ); + REQUIRE( s5_r2::bounded( "dense.txt", dense + "\n" + dense, txt ) ); + + // load_binary: valid, empty-shape, short, long and huge declared shapes. + REQUIRE( s5_r2::bounded( "bnd.bin", s5_r2::binary_bytes( 2, 3, { 1, 2, 3, 4, 5, 6 } ), bin ) ); + REQUIRE( s5_r2::largest_request == 6 * sizeof( double ) ); // the destination allocator is the one tracked + REQUIRE( s5_r2::bounded( "bnd.bin", s5_r2::binary_bytes( 0, 3, {} ), bin ) ); + REQUIRE( s5_r2::bounded( "bnd.bin", s5_r2::binary_bytes( 2, 3, { 1 } ), bin ) ); + REQUIRE( s5_r2::bounded( "bnd.bin", s5_r2::binary_bytes( 1, 1, { 1, 2 } ), bin ) ); + REQUIRE( s5_r2::bounded( "bnd.bin", s5_r2::binary_bytes( 1ull << 40, 1ull << 20, { 1 } ), bin ) ); + REQUIRE( s5_r2::bounded( "bnd.bin", s5_r2::binary_bytes( 1ull << 28, 1, { 1 } ), bin ) ); + REQUIRE( s5_r2::bounded( "bnd.bin", s5_r2::binary_bytes( ~0ull, ~0ull, {} ), bin ) ); + REQUIRE( s5_r2::bounded( "bnd.bin", "abc", bin ) ); + REQUIRE( s5_r2::bounded( "bnd.bin", s5_r2::binary_bytes( 1ull << 30, 1ull << 30, { 1, 2, 3 } ), bin ) ); + + // load_npy: valid C and Fortran order, short payloads and headers declaring huge shapes. + auto const six = s5_r2::raw( { 1, 2, 3, 4, 5, 6 } ); + REQUIRE( s5_r2::bounded( "bnd.npy", s5_r2::npy_bytes( "{'descr': '( "bnd.npy", s5_r2::npy_bytes( "{'descr': '( "bnd.npy", s5_r2::npy_bytes( "{'descr': '( "bnd.npy", s5_r2::npy_bytes( "{'descr': '( "bnd.npy", s5_r2::npy_bytes( "{'descr': '( "bnd.npy", s5_r2::npy_bytes( "{'descr': '( "bnd.npy", s5_r2::npy_bytes( "{'descr': '( "bnd.npy", s5_r2::npy_bytes( "{'descr': '( "bnd.npy", "\x93NUMPY", npy ) ); + REQUIRE( s5_r2::bounded( "bnd.npy", s5_r2::npy_bytes( "{'descr': '|u1', 'fortran_order': False, 'shape': (65536, 65536), }", "abc" ), npy ) ); + REQUIRE( s5_r2::bounded( "bnd.npy", s5_r2::npy_bytes( "{'descr': '|u1', 'fortran_order': False, 'shape': (1, 3), }", "abc" ), npy ) ); +} + +TEST_CASE( "S5 load_txt delimiters, out-of-range values per type, underflow and complex forms", "[S5][S5-R2][S5-R4]" ) +{ + // Each delimiter alone separates tokens; CRLF and blank lines are accepted. + for ( std::string const& sep : std::vector{ ",", ";", " ", "\t", "\r" } ) + { + feng::matrix m; + REQUIRE( m.load_txt( s5_r2::temp_file( "sep.txt", "1" + sep + "2\r\n\r\n\n+3" + sep + "-4\r\n" ) ) ); + REQUIRE( m.row() == 2 ); REQUIRE( m.col() == 2 ); + REQUIRE( m[0][0] == 1.0 ); REQUIRE( m[0][1] == 2.0 ); REQUIRE( m[1][0] == 3.0 ); REQUIRE( m[1][1] == -4.0 ); + } + + REQUIRE( s5_r2::int_out_of_range_rejected( "128", "-129" ) ); + REQUIRE( s5_r2::int_out_of_range_rejected( "32768", "-32769" ) ); + REQUIRE( s5_r2::int_out_of_range_rejected( "2147483648", "-2147483649" ) ); + REQUIRE( s5_r2::int_out_of_range_rejected( "9223372036854775808", "-9223372036854775809" ) ); + REQUIRE( s5_r2::int_out_of_range_rejected( "256", "-1" ) ); + REQUIRE( s5_r2::int_out_of_range_rejected( "65536", "-1" ) ); + REQUIRE( s5_r2::int_out_of_range_rejected( "4294967296", "-1" ) ); + REQUIRE( s5_r2::int_out_of_range_rejected( "18446744073709551616", "-1" ) ); + REQUIRE( s5_r2::txt_rejected( "f_1e39.txt", "1 1e39\n" ) ); + REQUIRE( s5_r2::txt_rejected( "f_m1e39.txt", "1 -1e39\n" ) ); + REQUIRE( s5_r2::txt_rejected( "d_1e999.txt", "1 1e999\n" ) ); + // Underflow: the implementation rejects 1e-400 into double as out of range (from_chars reports it). + REQUIRE( s5_r2::txt_rejected( "d_1em400.txt", "1 1e-400\n" ) ); + REQUIRE( s5_r2::txt_rejected>( "cx_1e999.txt", "(1,1e999)\n" ) ); + + // The largest in-range integers load. + feng::matrix i; + REQUIRE( i.load_txt( s5_r2::temp_file( "i64.txt", "9223372036854775807 -9223372036854775808\n" ) ) ); + REQUIRE( i[0][0] == std::numeric_limits::max() ); + REQUIRE( i[0][1] == std::numeric_limits::min() ); + + feng::matrix> z; + REQUIRE( z.load_txt( s5_r2::temp_file( "cxf.txt", "(+1.5,-2)\t7\r\n-3;( 0 , 1 )\r\n" ) ) ); + REQUIRE( z.row() == 2 ); REQUIRE( z.col() == 2 ); + REQUIRE( z[0][0] == std::complex( 1.5f, -2.0f ) ); REQUIRE( z[0][1] == std::complex( 7.0f, 0.0f ) ); + REQUIRE( z[1][0] == std::complex( -3.0f, 0.0f ) ); REQUIRE( z[1][1] == std::complex( 0.0f, 1.0f ) ); +} + +TEST_CASE( "S5 save_as_txt output loads back for every arithmetic and complex type at its extremes", "[S5][S5-R2]" ) +{ + REQUIRE( s5_r2::txt_round_trips( "rtx_i8.txt" ) ); + REQUIRE( s5_r2::txt_round_trips( "rtx_i16.txt" ) ); + REQUIRE( s5_r2::txt_round_trips( "rtx_i32.txt" ) ); + REQUIRE( s5_r2::txt_round_trips( "rtx_i64.txt" ) ); + REQUIRE( s5_r2::txt_round_trips( "rtx_u8.txt" ) ); + REQUIRE( s5_r2::txt_round_trips( "rtx_u16.txt" ) ); + REQUIRE( s5_r2::txt_round_trips( "rtx_u32.txt" ) ); + REQUIRE( s5_r2::txt_round_trips( "rtx_u64.txt" ) ); + REQUIRE( s5_r2::txt_round_trips( "rtx_f.txt" ) ); + REQUIRE( s5_r2::txt_round_trips( "rtx_d.txt" ) ); + REQUIRE( s5_r2::txt_round_trips( "rtx_ld.txt" ) ); + REQUIRE( s5_r2::txt_round_trips>( "rtx_cf.txt" ) ); + REQUIRE( s5_r2::txt_round_trips>( "rtx_cd.txt" ) ); + REQUIRE( s5_r2::txt_round_trips>( "rtx_cld.txt" ) ); +} + +TEST_CASE( "S5 operator>> reads every element type and leaves the matrix unchanged on a bad token", "[S5][S5-R2][S5-R4]" ) +{ + feng::matrix> z; + std::istringstream good{ "(1,2);3\r\n\n4 (5,-6)\n" }; + good >> z; + REQUIRE( !good.fail() ); + REQUIRE( z.row() == 2 ); REQUIRE( z.col() == 2 ); + REQUIRE( z[0][0] == std::complex( 1, 2 ) ); REQUIRE( z[1][1] == std::complex( 5, -6 ) ); + + auto m = s5_r2::prefilled(); + std::istringstream bad_token{ "1 2\n3 x\n" }; + bad_token >> m; + REQUIRE( bad_token.fail() ); + REQUIRE( s5_r2::is_prefilled( m ) ); + + std::istringstream out_of_range{ "1 2\n3 256\n" }; + out_of_range >> m; + REQUIRE( out_of_range.fail() ); + REQUIRE( s5_r2::is_prefilled( m ) ); + + std::istringstream ok{ "4 3\n2 1\n" }; + ok >> m; + REQUIRE( !ok.fail() ); + REQUIRE( m[0][0] == 4 ); REQUIRE( m[1][1] == 1 ); +} + +TEST_CASE( "S5 load_binary fails at every truncation and round-trips every element type", "[S5][S5-R2][S5-R4]" ) +{ + auto const valid = s5_r2::binary_bytes( 2, 2, { 5, 6, 7, 8 } ); + for ( std::size_t k = 0; k != valid.size(); ++k ) + REQUIRE( s5_r2::bin_rejected( "trunc.bin", valid.substr( 0, k ) ) ); + { + auto m = s5_r2::prefilled(); + REQUIRE( m.load_binary( s5_r2::temp_file( "full.bin", valid ) ) ); + REQUIRE( m[0][0] == 5.0 ); REQUIRE( m[1][1] == 8.0 ); + } + REQUIRE( s5_r2::bin_rejected( "trail.bin", valid + "x" ) ); + REQUIRE( s5_r2::bin_rejected( "rc_overflow.bin", s5_r2::binary_bytes( 1ull << 33, 1ull << 31, {} ) ) ); + REQUIRE( s5_r2::bin_rejected( "byte_overflow.bin", s5_r2::binary_bytes( 1ull << 61, 1, {} ) ) ); + REQUIRE( s5_r2::bin_rejected( "u8_overflow.bin", s5_r2::binary_bytes( ~0ull, 2, {} ) ) ); + + REQUIRE( s5_r2::bin_round_trips( "rtb_i8.bin" ) ); + REQUIRE( s5_r2::bin_round_trips( "rtb_i16.bin" ) ); + REQUIRE( s5_r2::bin_round_trips( "rtb_i32.bin" ) ); + REQUIRE( s5_r2::bin_round_trips( "rtb_i64.bin" ) ); + REQUIRE( s5_r2::bin_round_trips( "rtb_u8.bin" ) ); + REQUIRE( s5_r2::bin_round_trips( "rtb_u16.bin" ) ); + REQUIRE( s5_r2::bin_round_trips( "rtb_u32.bin" ) ); + REQUIRE( s5_r2::bin_round_trips( "rtb_u64.bin" ) ); + REQUIRE( s5_r2::bin_round_trips( "rtb_f.bin" ) ); + REQUIRE( s5_r2::bin_round_trips( "rtb_d.bin" ) ); + REQUIRE( s5_r2::bin_round_trips( "rtb_ld.bin" ) ); + REQUIRE( s5_r2::bin_round_trips>( "rtb_cf.bin" ) ); + REQUIRE( s5_r2::bin_round_trips>( "rtb_cd.bin" ) ); + REQUIRE( s5_r2::bin_round_trips>( "rtb_cld.bin" ) ); + + for ( auto const& [r, c] : { std::pair{ 0, 5 }, { 5, 0 }, { 0, 0 } } ) + { + feng::matrix> a{ r, c }; + auto const path = s5_r2::temp_path( "rtb_empty.bin" ); + REQUIRE( a.save_as_binary( path ) ); + auto b = s5_r2::prefilled>(); + REQUIRE( b.load_binary( path ) ); + REQUIRE( b.row() == r ); REQUIRE( b.col() == c ); + } +} diff --git a/tests/cases/s5_r3.hpp b/tests/cases/s5_r3.hpp new file mode 100644 index 0000000..6a9c50f --- /dev/null +++ b/tests/cases/s5_r3.hpp @@ -0,0 +1,284 @@ +// S5-R3 (PR-7, F08): the BMP loader validates the headers with checked arithmetic; S5-R4 (PR-2, D-011): it fails +// with an empty optional and one stderr line instead of aborting. BMP bytes are built here; temporary files go under +// std::filesystem::temp_directory_path(). +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // getpid: per-process temp names + +namespace s5_r3 +{ + // The process id keeps two suite binaries run by hand at once from sharing a temp path. + inline std::string temp_path( std::string const& name ) + { + return ( std::filesystem::temp_directory_path() / ( "feng_s5_r3_" + std::to_string( ::getpid() ) + "_" + name ) ).string(); + } + + inline std::string temp_file( std::string const& name, std::vector const& bytes ) + { + auto const path = temp_path( name ); + std::ofstream ofs( path, std::ios::binary | std::ios::trunc ); + ofs.write( reinterpret_cast( bytes.data() ), static_cast( bytes.size() ) ); + ofs.close(); + return path; + } + + inline void put16( std::vector& b, std::size_t at, std::uint32_t v ) + { + b[at] = static_cast( v & 0xff ); + b[at + 1] = static_cast( ( v >> 8 ) & 0xff ); + } + + inline void put32( std::vector& b, std::size_t at, std::uint32_t v ) + { + put16( b, at, v & 0xffff ); + put16( b, at + 2, v >> 16 ); + } + + struct header + { + std::int32_t width = 3; + std::int32_t height = 2; + std::uint32_t info_size = 40; + std::uint32_t bpp = 24; + std::uint32_t compression = 0; + std::uint32_t planes = 1; + std::uint32_t extra_offset = 0; // gap between the info header and the pixels + std::size_t trailing = 0; + }; + + // The colour of logical (top-down) pixel (r, c): {red, green, blue}. + inline std::array colour( std::size_t r, std::size_t c ) + { + return { static_cast( 10 * r + c ), static_cast( 100 + 10 * r + c ), static_cast( 200 + 10 * r + c ) }; + } + + // A BMP whose logical image is colour(r, c); a positive height stores rows bottom-up, a negative one top-down. + inline std::vector bmp( header const& h ) + { + std::uint32_t const offset = 14 + h.info_size + h.extra_offset; + std::size_t const rows = static_cast( h.height < 0 ? -static_cast( h.height ) : h.height ); + std::size_t const cols = static_cast( h.width ); + std::size_t const bytes_per_pixel = h.bpp / 8; + std::size_t const stride = ( cols * h.bpp + 31 ) / 32 * 4; + std::vector b( offset + stride * rows + h.trailing, 0xEE ); + std::fill( b.begin(), b.begin() + offset, std::uint8_t{ 0 } ); + b[0] = 'B'; b[1] = 'M'; + put32( b, 2, static_cast( b.size() ) ); + put32( b, 10, offset ); + put32( b, 14, h.info_size ); + put32( b, 18, static_cast( h.width ) ); + put32( b, 22, static_cast( h.height ) ); + put16( b, 26, h.planes ); + put16( b, 28, h.bpp ); + put32( b, 30, h.compression ); + for ( std::size_t k = 0; bytes_per_pixel >= 3 && k != rows; ++k ) + { + std::size_t const r = h.height > 0 ? rows - 1 - k : k; // the logical row stored k-th + for ( std::size_t c = 0; c != cols; ++c ) + { + auto const [red, green, blue] = colour( r, c ); + std::size_t const at = offset + k * stride + c * bytes_per_pixel; + b[at] = blue; b[at + 1] = green; b[at + 2] = red; + if ( bytes_per_pixel == 4 ) b[at + 3] = 0x7F; + } + } + return b; + } + + inline bool matches( std::optional, 3>> const& img, std::size_t rows, std::size_t cols ) + { + if ( !img ) return false; + for ( auto const& m : *img ) + if ( m.row() != rows || m.col() != cols ) return false; + for ( std::size_t r = 0; r != rows; ++r ) + for ( std::size_t c = 0; c != cols; ++c ) + { + auto const [red, green, blue] = colour( r, c ); + if ( ( *img )[0][r][c] != red || ( *img )[1][r][c] != green || ( *img )[2][r][c] != blue ) return false; + } + return true; + } + + inline bool rejected( std::string const& name, std::vector const& bytes ) + { + std::ostringstream captured; + auto* const old = std::cerr.rdbuf( captured.rdbuf() ); + auto const img = feng::load_bmp( temp_file( name, bytes ) ); + std::cerr.rdbuf( old ); + std::string const text = captured.str(); + return !img && text.find( "load_bmp" ) != std::string::npos && std::count( text.begin(), text.end(), '\n' ) == 1; + } +} + +TEST_CASE( "S5 load_bmp rejects width 2147483647 and height -2147483648", "[S5][S5-R3][S5-R4]" ) +{ + auto b = s5_r3::bmp( {} ); + s5_r3::put32( b, 18, 2147483647u ); + s5_r3::put32( b, 22, 0x80000000u ); + REQUIRE( s5_r3::rejected( "huge.bmp", b ) ); + s5_r3::put32( b, 22, 0x80000001u ); // -2147483647: the pixel array would not fit in the file + REQUIRE( s5_r3::rejected( "huge2.bmp", b ) ); + s5_r3::put32( b, 22, 2147483647u ); + REQUIRE( s5_r3::rejected( "huge3.bmp", b ) ); +} + +TEST_CASE( "S5 bottom-up and top-down files load to the same channels", "[S5][S5-R3]" ) +{ + // 24- and 32-bit (alpha ignored), bottom-up and top-down, widths whose rows need 0 to 3 padding bytes + for ( std::uint32_t bpp : { 24u, 32u } ) + for ( std::int32_t width : { 1, 2, 3, 4, 5 } ) + for ( std::int32_t height : { 3, -3, 1, -1 } ) + { + INFO( "bpp " << bpp << ", width " << width << ", height " << height ); + s5_r3::header h; + h.bpp = bpp; h.width = width; h.height = height; + auto const rows = static_cast( height < 0 ? -height : height ); + REQUIRE( s5_r3::matches( feng::load_bmp( s5_r3::temp_file( "orient.bmp", s5_r3::bmp( h ) ) ), rows, static_cast( width ) ) ); + } + + // every supported info header size, alone, with a gap before the pixels and with trailing bytes + for ( std::uint32_t info : { 40u, 52u, 56u, 108u, 124u } ) + for ( std::uint32_t bpp : { 24u, 32u } ) + for ( std::uint32_t gap : { 0u, 6u } ) + for ( std::size_t trailing : { std::size_t{ 0 }, std::size_t{ 3 } } ) + { + INFO( "info " << info << ", bpp " << bpp << ", gap " << gap << ", trailing " << trailing ); + s5_r3::header g; + g.width = 5; g.height = info % 2 ? 3 : -3; g.info_size = info; g.bpp = bpp; + g.extra_offset = gap; g.trailing = trailing; + REQUIRE( s5_r3::matches( feng::load_bmp( s5_r3::temp_file( "var.bmp", s5_r3::bmp( g ) ) ), 3, 5 ) ); + } +} + +TEST_CASE( "S5 load_bmp rejects truncations and unsupported headers", "[S5][S5-R3][S5-R4]" ) +{ + auto const good = s5_r3::bmp( {} ); + s5_r3::header h32; h32.bpp = 32; h32.info_size = 124; + for ( auto const& valid : { good, s5_r3::bmp( h32 ) } ) + for ( std::size_t n = 0; n != valid.size(); ++n ) + { + INFO( "truncated to " << n << " of " << valid.size() << " bytes" ); + REQUIRE( s5_r3::rejected( "trunc.bmp", std::vector( valid.begin(), valid.begin() + static_cast( n ) ) ) ); + } + + auto sig = good; sig[1] = 'A'; + REQUIRE( s5_r3::rejected( "sig.bmp", sig ) ); + for ( std::uint32_t info : { 0u, 12u, 39u, 41u, 64u, 125u, 100000u } ) + { + auto b = good; s5_r3::put32( b, 14, info ); + REQUIRE( s5_r3::rejected( "info.bmp", b ) ); + } + for ( std::uint32_t comp : { 1u, 2u, 3u, 6u } ) + { + s5_r3::header h; h.compression = comp; + REQUIRE( s5_r3::rejected( "comp.bmp", s5_r3::bmp( h ) ) ); + } + for ( std::uint32_t bpp : { 1u, 4u, 8u, 16u } ) + { + s5_r3::header h; h.bpp = bpp; + auto b = s5_r3::bmp( h ); + b.resize( b.size() + 64, 0 ); + REQUIRE( s5_r3::rejected( "bpp.bmp", b ) ); + } + { s5_r3::header h; h.planes = 2; REQUIRE( s5_r3::rejected( "planes.bmp", s5_r3::bmp( h ) ) ); } + { auto b = good; s5_r3::put32( b, 18, 0 ); REQUIRE( s5_r3::rejected( "w0.bmp", b ) ); } + { auto b = good; s5_r3::put32( b, 18, 0xFFFFFFFFu ); REQUIRE( s5_r3::rejected( "wneg.bmp", b ) ); } + { auto b = good; s5_r3::put32( b, 18, 0x80000000u ); REQUIRE( s5_r3::rejected( "wmin.bmp", b ) ); } + { auto b = good; s5_r3::put32( b, 22, 0 ); REQUIRE( s5_r3::rejected( "h0.bmp", b ) ); } + { auto b = good; s5_r3::put32( b, 22, 0x80000000u ); REQUIRE( s5_r3::rejected( "hmin.bmp", b ) ); } + // the pixel-data offset one byte before the end of each info header + for ( std::uint32_t info : { 40u, 124u } ) + { + s5_r3::header h; h.info_size = info; + auto b = s5_r3::bmp( h ); + s5_r3::put32( b, 10, 14 + info - 1 ); + REQUIRE( s5_r3::rejected( "offset_in_info.bmp", b ) ); + } + { auto b = good; s5_r3::put32( b, 10, 50 ); REQUIRE( s5_r3::rejected( "offset_low.bmp", b ) ); } + { auto b = good; s5_r3::put32( b, 10, 0xFFFFFFF0u ); REQUIRE( s5_r3::rejected( "offset_high.bmp", b ) ); } + + // width x height whose stride or pixel-array arithmetic overflows, or merely exceeds a 54-byte image: parse_bmp + // must reject them from the header alone, before allocating any channel + for ( auto const [w, hgt] : std::vector>{ + { 0x7FFFFFFFu, 0x80000001u }, { 0x7FFFFFFFu, 0x7FFFFFFFu }, { 65536u, 65536u }, { 0x7FFFFFFFu, 1u }, { 1u, 0x7FFFFFFFu } } ) + for ( std::uint32_t bpp : { 24u, 32u } ) + { + INFO( "width " << w << ", height " << hgt << ", bpp " << bpp ); + auto b = good; + b.resize( 54 ); + s5_r3::put32( b, 18, w ); s5_r3::put32( b, 22, hgt ); s5_r3::put16( b, 28, bpp ); + REQUIRE( !feng::matrix_details::parse_bmp( b.data(), b.size() ) ); + REQUIRE( s5_r3::rejected( "overflow.bmp", b ) ); + } + + auto const missing = s5_r3::temp_path( "does_not_exist.bmp" ); + std::filesystem::remove( missing ); + REQUIRE( !feng::load_bmp( missing ) ); + REQUIRE( !feng::load_bmp( std::filesystem::temp_directory_path().string() ) ); +} + +TEST_CASE( "S5 a save_as_bmp file loads back to its channels", "[S5][S5-R3]" ) +{ + auto const& map = feng::matrix_details::bmp_details::color_maps.at( "parula" ); + for ( unsigned cols : { 1u, 2u, 3u, 5u } ) + { + INFO( "member save_as_bmp, 3 x " << cols ); + feng::matrix m{ 3, cols }; + for ( std::size_t k = 0; k != m.size(); ++k ) m.data()[k] = static_cast( ( k * 7 ) % 11 ); + auto const path = s5_r3::temp_path( "saved.bmp" ); + REQUIRE( m.save_as_bmp( path, "parula" ) ); + auto const img = feng::load_bmp( path ); + REQUIRE( img ); + auto const [mn, mx] = m.minmax(); + for ( auto const& channel : *img ) { REQUIRE( channel.row() == 3 ); REQUIRE( channel.col() == cols ); } + for ( std::size_t r = 0; r != 3; ++r ) + for ( std::size_t c = 0; c != cols; ++c ) + { + auto const [red, green, blue] = map( ( m[r][c] - mn ) / ( mx - mn + 1.0e-10 ) ); + REQUIRE( ( *img )[0][r][c] == red ); + REQUIRE( ( *img )[1][r][c] == green ); + REQUIRE( ( *img )[2][r][c] == blue ); + } + std::filesystem::remove( path ); + } + + // channels with minimum 0 and maximum 255 are written unscaled, so load_bmp returns them exactly + for ( unsigned cols : { 1u, 2u, 3u, 5u } ) + { + INFO( "free save_as_bmp( name, r, g, b ), 3 x " << cols ); + feng::matrix r{ 3, cols }, g{ 3, cols }, b{ 3, cols }; + for ( std::size_t k = 0; k != r.size(); ++k ) + { + r.data()[k] = static_cast( ( k * 37 ) % 256 ); + g.data()[k] = static_cast( ( k * 91 + 5 ) % 256 ); + b.data()[k] = static_cast( ( k * 53 + 11 ) % 256 ); + } + for ( auto* m : { &r, &g, &b } ) + { + ( *m )[0][0] = 0.0; + ( *m )[2][cols - 1] = 255.0; + } + auto const path = s5_r3::temp_path( "saved_rgb.bmp" ); + REQUIRE( feng::save_as_bmp( path, r, g, b ) ); + auto const img = feng::load_bmp( path ); + REQUIRE( img ); + for ( auto const& channel : *img ) { REQUIRE( channel.row() == 3 ); REQUIRE( channel.col() == cols ); } + for ( std::size_t y = 0; y != 3; ++y ) + for ( std::size_t x = 0; x != cols; ++x ) + { + INFO( "pixel " << y << ", " << x ); + REQUIRE( ( *img )[0][y][x] == static_cast( r[y][x] ) ); + REQUIRE( ( *img )[1][y][x] == static_cast( g[y][x] ) ); + REQUIRE( ( *img )[2][y][x] == static_cast( b[y][x] ) ); + } + std::filesystem::remove( path ); + } +} diff --git a/tests/cases/s5_r4.hpp b/tests/cases/s5_r4.hpp new file mode 100644 index 0000000..9b5f624 --- /dev/null +++ b/tests/cases/s5_r4.hpp @@ -0,0 +1,365 @@ +// S5-R4 (PR-2, PR-7, D-011): every file writer returns false with one stderr line, and never aborts, on a directory, +// open, write or close failure, and true on success; D-022: save_as_npy writes a v1.0 header numpy reads and that +// load_npy loads back. S5-R3: the free three-channel save_as_bmp loads back to its channels. Temporary files go under +// std::filesystem::temp_directory_path(). +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // getpid: per-process temp names + +namespace s5_r4 +{ + // The process id keeps two suite binaries run by hand at once from sharing a temp path. + inline std::string temp_path( std::string const& name ) + { + return ( std::filesystem::temp_directory_path() / ( "feng_s5_r4_" + std::to_string( ::getpid() ) + "_" + name ) ).string(); + } + + inline std::vector file_bytes( std::string const& path ) + { + std::ifstream ifs( path, std::ios::binary ); + return std::vector{ std::istreambuf_iterator( ifs ), std::istreambuf_iterator() }; + } + + // Redirects std::cerr into a string for the lifetime of the object. + struct capture_cerr + { + std::ostringstream text; + std::streambuf* old; + capture_cerr() : old( std::cerr.rdbuf( text.rdbuf() ) ) {} + ~capture_cerr() { std::cerr.rdbuf( old ); } + std::string str() const { return text.str(); } + }; + + struct writer + { + char const* name; // what the stderr line must name + char const* extension; // the writer's file extension + std::function call; + }; + + inline feng::matrix const& sample() + { + static feng::matrix const m = [] + { + feng::matrix x{ 3, 4 }; + for ( std::size_t k = 0; k != x.size(); ++k ) x.data()[k] = static_cast( ( k * 5 ) % 7 ) - 1.5; + return x; + }(); + return m; + } + + // Every writer overload: member txt, binary, bmp, png, pgm, npy (string and char const*), and the free bmp ones. + inline std::vector writers() + { + auto const& m = sample(); + return { + { "save_as_txt", ".txt", [&m]( std::string const& p ) { return m.save_as_txt( p ); } }, + { "save_as_txt", ".txt", [&m]( std::string const& p ) { return m.save_as_txt( p.c_str() ); } }, + { "save_as_binary", ".bin", [&m]( std::string const& p ) { return m.save_as_binary( p ); } }, + { "save_as_binary", ".bin", [&m]( std::string const& p ) { return m.save_as_binary( p.c_str() ); } }, + { "save_as_bmp", ".bmp", [&m]( std::string const& p ) { return m.save_as_bmp( p ); } }, + { "save_as_bmp", ".bmp", [&m]( std::string const& p ) { return m.save_as_bmp( p.c_str() ); } }, + { "save_as_png", ".png", [&m]( std::string const& p ) { return m.save_as_png( p ); } }, + { "save_as_pgm", ".pgm", [&m]( std::string const& p ) { return m.save_as_pgm( p ); } }, + { "save_as_pgm", ".pgm", [&m]( std::string const& p ) { return m.save_as_pgm( p.c_str() ); } }, + { "save_as_npy", ".npy", [&m]( std::string const& p ) { return m.save_as_npy( p ); } }, + { "save_as_npy", ".npy", [&m]( std::string const& p ) { return m.save_as_npy( p.c_str() ); } }, + { "save_as_bmp", ".bmp", [&m]( std::string const& p ) { return feng::save_as_bmp( p, m, m, m ); } }, + { "save_as_bmp", ".bmp", [&m]( std::string const& p ) { return feng::save_as_bmp( p, m, std::string{ "jet" } ); } }, + }; + } + + inline void require_failure( writer const& w, std::string const& path ) + { + INFO( w.name << " into " << path ); + capture_cerr cap; + bool const ok = w.call( path ); + std::string const text = cap.str(); + REQUIRE( !ok ); + REQUIRE( text.find( w.name ) != std::string::npos ); + REQUIRE( text.find( path ) != std::string::npos ); + REQUIRE( text.find( '\n' ) == text.size() - 1 ); // one line + } +} // namespace s5_r4 + +TEST_CASE( "S5 save_as_png into a missing directory returns false", "[S5][S5-R4]" ) +{ + s5_r4::capture_cerr cap; + bool const ok = s5_r4::sample().save_as_png( "/nonexistent-dir/x.png" ); + std::string const text = cap.str(); + REQUIRE( !ok ); + REQUIRE( text.find( "save_as_png" ) != std::string::npos ); + REQUIRE( text.find( "/nonexistent-dir/x.png" ) != std::string::npos ); +} + +TEST_CASE( "S5 every writer reports a write failure on /dev/full", "[S5][S5-R4]" ) +{ + // write or close failure: a symlink to /dev/full opens but every write fails with ENOSPC + // the write-failure path must be exercised: a host without /dev/full fails this test rather than skipping it + REQUIRE( std::filesystem::exists( "/dev/full" ) ); + for ( auto const& w : s5_r4::writers() ) + { + auto const link = s5_r4::temp_path( std::string{ "full" } + w.extension ); + std::filesystem::remove( link ); + std::filesystem::create_symlink( "/dev/full", link ); + s5_r4::require_failure( w, link ); + std::filesystem::remove( link ); + } + + // open and directory failures: the parent path is a regular file + auto const plain = s5_r4::temp_path( "plain_file" ); + std::filesystem::remove_all( plain ); + { + std::ofstream ofs( plain ); + ofs << "not a directory\n"; + } + for ( auto const& w : s5_r4::writers() ) + { + s5_r4::require_failure( w, plain + "/x" + w.extension ); // open failure + s5_r4::require_failure( w, plain + "/sub/x" + w.extension ); // directory failure + } + std::filesystem::remove( plain ); + + // directory failure: the parent directory does not exist and cannot be created + for ( auto const& w : s5_r4::writers() ) + s5_r4::require_failure( w, std::string{ "/nonexistent-dir/x" } + w.extension ); +} + +TEST_CASE( "S5 every writer returns true on success", "[S5][S5-R4]" ) +{ + int k = 0; + for ( auto const& w : s5_r4::writers() ) + { + auto const path = s5_r4::temp_path( "ok_" + std::to_string( k++ ) + w.extension ); + std::filesystem::remove( path ); + INFO( w.name << " into " << path ); + REQUIRE( w.call( path ) ); + REQUIRE( std::filesystem::file_size( path ) > 0 ); + std::filesystem::remove( path ); + } +} + +namespace s5_r4 +{ + template < typename T > + void npy_round_trip( feng::matrix const& a, std::string const& name ) + { + INFO( name ); + auto const path = temp_path( name ); + REQUIRE( a.save_as_npy( path ) ); + auto const bytes = file_bytes( path ); + REQUIRE( bytes.size() >= 10 ); + std::size_t const header_len = static_cast( bytes[8] ) | ( static_cast( static_cast( bytes[9] ) ) << 8 ); + REQUIRE( ( 10 + header_len ) % 64 == 0 ); + REQUIRE( bytes[9 + header_len] == '\n' ); + REQUIRE( bytes.size() == 10 + header_len + sizeof( T ) * a.size() ); + feng::matrix b{ 1, 1 }; + REQUIRE( b.load_npy( path ) ); + REQUIRE( b.row() == a.row() ); + REQUIRE( b.col() == a.col() ); + for ( std::size_t k = 0; k != a.size(); ++k ) REQUIRE( b.data()[k] == a.data()[k] ); + std::filesystem::remove( path ); + } +} // namespace s5_r4 + +TEST_CASE( "S5 save_as_npy output loads back through load_npy", "[S5][S5-R4]" ) +{ + feng::matrix d{ 2, 3 }; + feng::matrix f{ 3, 2 }; + feng::matrix i{ 2, 4 }; + feng::matrix> c{ 2, 2 }; + for ( std::size_t k = 0; k != d.size(); ++k ) d.data()[k] = 1.0 / ( 1.0 + static_cast( k ) ) - 0.3; + for ( std::size_t k = 0; k != f.size(); ++k ) f.data()[k] = static_cast( k ) * -1.25f + 0.1f; + for ( std::size_t k = 0; k != i.size(); ++k ) i.data()[k] = static_cast( k * 70001 ) - 2147483647; + for ( std::size_t k = 0; k != c.size(); ++k ) c.data()[k] = std::complex{ 0.5 * static_cast( k ), -1.0 / ( 1.0 + static_cast( k ) ) }; + s5_r4::npy_round_trip( d, "rt_f8.npy" ); + s5_r4::npy_round_trip( f, "rt_f4.npy" ); + s5_r4::npy_round_trip( i, "rt_i4.npy" ); + s5_r4::npy_round_trip( c, "rt_c16.npy" ); + feng::matrix empty{ 0, 3 }; + s5_r4::npy_round_trip( empty, "rt_empty.npy" ); +} + +TEST_CASE( "S5 save_as_npy writes the bytes numpy writes", "[S5][S5-R4]" ) +{ + feng::matrix base{ 2, 3 }; + double const values[] = { 1.5, -2.25, 3.0, 4.125, 5.0, -6.5 }; + std::copy( std::begin( values ), std::end( values ), base.data() ); + auto const path = s5_r4::temp_path( "numpy_f8.npy" ); + REQUIRE( base.save_as_npy( path ) ); + auto const ours = s5_r4::file_bytes( path ); + auto const numpys = s5_r4::file_bytes( "./tests/fixtures/s5/f8_le.npy" ); + REQUIRE( numpys.size() == 128 + 6 * 8 ); + REQUIRE( ours.size() == numpys.size() ); + std::size_t const compare = std::endian::native == std::endian::little ? numpys.size() : 128; // payload is native order + for ( std::size_t k = 0; k != compare; ++k ) + { + if ( k == 18 && std::endian::native != std::endian::little ) continue; // the '<' of the descr + INFO( "byte " << k ); + REQUIRE( ours[k] == numpys[k] ); + } + std::filesystem::remove( path ); + + feng::matrix u{ 1, 2, std::uint8_t{ 7 } }; + REQUIRE( u.save_as_npy( path ) ); + auto const ub = s5_r4::file_bytes( path ); + std::string const header( ub.begin() + 10, ub.begin() + 10 + 60 ); + REQUIRE( header.find( "{'descr': '|u1', 'fortran_order': False, 'shape': (1, 2), }" ) == 0 ); + std::filesystem::remove( path ); +} + +TEST_CASE( "S5 a free three-channel save_as_bmp file loads back to its channels", "[S5][S5-R3]" ) +{ + // channels with minimum 0 and maximum 255 are written unscaled, so load_bmp must return them exactly + feng::matrix r{ 3, 4 }, g{ 3, 4 }, b{ 3, 4 }; + for ( std::size_t k = 0; k != r.size(); ++k ) + { + r.data()[k] = static_cast( k * 23 ); + g.data()[k] = static_cast( ( 11 - k ) * 19 ); + b.data()[k] = static_cast( ( k * 7 ) % 12 * 21 ); + } + for ( auto* m : { &r, &g, &b } ) + { + ( *m )[0][1] = 0.0; + ( *m )[2][2] = 255.0; + } + auto const path = s5_r4::temp_path( "rgb.bmp" ); + REQUIRE( feng::save_as_bmp( path, r, g, b ) ); + auto const img = feng::load_bmp( path ); + REQUIRE( img ); + for ( auto const& channel : *img ) { REQUIRE( channel.row() == 3 ); REQUIRE( channel.col() == 4 ); } + for ( std::size_t y = 0; y != 3; ++y ) + for ( std::size_t x = 0; x != 4; ++x ) + { + INFO( "pixel " << y << ", " << x ); + REQUIRE( ( *img )[0][y][x] == static_cast( r[y][x] ) ); + REQUIRE( ( *img )[1][y][x] == static_cast( g[y][x] ) ); + REQUIRE( ( *img )[2][y][x] == static_cast( b[y][x] ) ); + } + std::filesystem::remove( path ); +} + +namespace s5_r4 +{ + inline std::string bytes_file( std::string const& name, std::string const& bytes ) + { + auto const path = temp_path( name ); + std::ofstream ofs( path, std::ios::binary | std::ios::trunc ); + ofs.write( bytes.data(), static_cast( bytes.size() ) ); + return path; + } + + inline feng::matrix prefilled() + { + feng::matrix m{ 2, 2 }; + m[0][0] = 1.0; m[0][1] = 2.0; m[1][0] = 3.0; m[1][1] = 4.0; + return m; + } + + inline bool is_prefilled( feng::matrix const& m ) + { + return m.row() == 2 && m.col() == 2 && m[0][0] == 1.0 && m[0][1] == 2.0 && m[1][0] == 3.0 && m[1][1] == 4.0; + } + + // One stderr line naming the loader. + inline bool one_line_naming( std::string const& text, char const* loader ) + { + return text.find( loader ) != std::string::npos && !text.empty() && text.find( '\n' ) == text.size() - 1; + } +} // namespace s5_r4 + +TEST_CASE( "S5 every loader fails on every failure class and keeps a pre-filled destination", "[S5][S5-R4]" ) +{ + using size_type = feng::matrix::size_type; + std::string short_binary( 2 * sizeof( size_type ) + sizeof( double ), '\0' ); // declares 2 x 3, holds one element + size_type const two = 2, three = 3; + std::memcpy( short_binary.data(), &two, sizeof( two ) ); + std::memcpy( short_binary.data() + sizeof( two ), &three, sizeof( three ) ); + std::string npy_f4 = std::string( "\x93NUMPY\x01\x00", 8 ) + '\x76' + '\x00' + "{'descr': ' + { + // the same bytes are a well-formed ', so the double rejection is the dtype + feng::matrix f{ 2, 2 }; + REQUIRE( npy_f4.size() == 128 + 4 ); + REQUIRE( f.load_npy( s5_r4::bytes_file( "f4_ok.npy", npy_f4 ) ) ); + REQUIRE( f.row() == 1 ); + REQUIRE( f.col() == 1 ); + REQUIRE( f[0][0] == 0.0f ); + } + std::string bmp_rle( 58, '\0' ); // a 1 x 1 BMP with compression 1 (RLE8) + bmp_rle[0] = 'B'; bmp_rle[1] = 'M'; bmp_rle[10] = 54; bmp_rle[14] = 40; bmp_rle[18] = 1; bmp_rle[22] = 1; + bmp_rle[26] = 1; bmp_rle[28] = 24; bmp_rle[30] = 1; + + auto const missing = s5_r4::temp_path( "missing_input" ); + std::filesystem::remove_all( missing ); + auto const directory = std::filesystem::temp_directory_path().string(); + struct failure { char const* what; std::string txt, bin, npy, bmp; }; + std::vector const failures{ + { "missing file", missing, missing, missing, missing }, + { "directory", directory, directory, directory, directory }, + { "empty file", s5_r4::bytes_file( "empty.txt", "" ), s5_r4::bytes_file( "empty.bin", "" ), s5_r4::bytes_file( "empty.npy", "" ), s5_r4::bytes_file( "empty.bmp", "" ) }, + { "3-byte file", s5_r4::bytes_file( "three.txt", "abc" ), s5_r4::bytes_file( "three.bin", "abc" ), s5_r4::bytes_file( "three.npy", "abc" ), s5_r4::bytes_file( "three.bmp", "BMa" ) }, + { "rejected input", s5_r4::bytes_file( "ragged.txt", "1 2\n3\n" ), s5_r4::bytes_file( "short.bin", short_binary ), s5_r4::bytes_file( "f4.npy", npy_f4 ), + s5_r4::bytes_file( "rle.bmp", bmp_rle ) }, + }; + + for ( auto const& f : failures ) + { + INFO( f.what ); + { + auto m = s5_r4::prefilled(); + s5_r4::capture_cerr cap; + bool const ok = m.load_txt( f.txt ); + std::string const text = cap.str(); + REQUIRE( !ok ); + REQUIRE( s5_r4::is_prefilled( m ) ); + REQUIRE( s5_r4::one_line_naming( text, "load_txt" ) ); + } + { + auto m = s5_r4::prefilled(); + s5_r4::capture_cerr cap; + bool const ok = m.load_binary( f.bin ); + std::string const text = cap.str(); + REQUIRE( !ok ); + REQUIRE( s5_r4::is_prefilled( m ) ); + REQUIRE( s5_r4::one_line_naming( text, "load_binary" ) ); + } + { + auto m = s5_r4::prefilled(); + s5_r4::capture_cerr cap; + bool const ok = m.load_npy( f.npy ); + std::string const text = cap.str(); + REQUIRE( !ok ); + REQUIRE( s5_r4::is_prefilled( m ) ); + REQUIRE( s5_r4::one_line_naming( text, "load_npy" ) ); + } + { + // operator>> reports through the stream state (failbit), not stderr + auto m = s5_r4::prefilled(); + std::ifstream ifs( f.txt ); + ifs >> m; + REQUIRE( ifs.fail() ); + REQUIRE( s5_r4::is_prefilled( m ) ); + } + { + s5_r4::capture_cerr cap; + auto const img = feng::load_bmp( f.bmp ); + std::string const text = cap.str(); + REQUIRE( !img ); + REQUIRE( s5_r4::one_line_naming( text, "load_bmp" ) ); + } + } +} diff --git a/tests/cases/s5_r5.hpp b/tests/cases/s5_r5.hpp new file mode 100644 index 0000000..0f4dbf2 --- /dev/null +++ b/tests/cases/s5_r5.hpp @@ -0,0 +1,59 @@ +// S5-R5 (PR-7): replays the fuzz regressions (tests/fuzz/regressions//, if any) and the seed corpora +// (tests/fuzz/corpus//) through the parsers with the harness checks of tests/fuzz/fuzz_checks.hpp. +// Runs from the repo root. +#include "../fuzz/fuzz_checks.hpp" + +#include +#include +#include +#include + +namespace s5_r5 +{ + using check_fn = char const* (*)( std::uint8_t const*, std::size_t ); + + struct target + { + char const* name; + check_fn check; + }; + + inline std::vector< std::filesystem::path > files_under( std::filesystem::path const& dir ) + { + std::vector< std::filesystem::path > ans; + std::error_code ec; + if ( !std::filesystem::is_directory( dir, ec ) ) return ans; + for ( auto const& e : std::filesystem::directory_iterator( dir ) ) + if ( e.is_regular_file() ) ans.push_back( e.path() ); + return ans; + } +} + +TEST_CASE( "S5 fuzz regressions replay without failure", "[S5][S5-R5]" ) +{ + s5_r5::target const targets[] = { + { "npy", fuzz_checks::check_npy }, + { "bmp", fuzz_checks::check_bmp }, + { "binary", fuzz_checks::check_binary }, + { "text", fuzz_checks::check_text }, + }; + for ( auto const& t : targets ) + { + std::size_t seeds = 0; + for ( char const* root : { "./tests/fuzz/regressions/", "./tests/fuzz/corpus/" } ) + { + auto const files = s5_r5::files_under( std::string{ root } + t.name ); + if ( std::string{ root }.find( "corpus" ) != std::string::npos ) seeds = files.size(); + for ( auto const& f : files ) + { + std::vector< std::uint8_t > bytes; + REQUIRE( feng::matrix_details::read_file( f.string().c_str(), bytes ) ); + INFO( "input " << f.string() ); + char const* const failure = t.check( bytes.data(), bytes.size() ); + REQUIRE( failure == nullptr ); + } + } + INFO( "target " << t.name ); + REQUIRE( seeds > 0 ); + } +} diff --git a/tests/cases/s6_r1.hpp b/tests/cases/s6_r1.hpp new file mode 100644 index 0000000..38cce64 --- /dev/null +++ b/tests/cases/s6_r1.hpp @@ -0,0 +1,299 @@ +// S6-R1 (PR-8): one partition drives parallel and both reductions; every index once, init once, every worker +// joined (F11, F12). +#include +#include +#include +#include +#include +#include +#include +#include + +namespace s6_r1 +{ + // Visits [first, last) with `workers` and checks: every index exactly once (atomic counters), nothing outside + // the range touched, and every chunk's plain slot written before return (a missing join is a TSan race). + template< typename I > + void check_visits( I first, I last, std::size_t workers ) + { + std::size_t const n = first < last ? static_cast( last - first ) : 0; + std::size_t const pad = 4; + std::size_t const base = static_cast( first < last ? first : last ); + std::unique_ptr[]> visits{ new std::atomic[ base + n + pad ] }; + for ( std::size_t i = 0; i != base + n + pad; ++i ) visits[i].store( 0 ); + std::vector slots( n, 0 ); // plain, non-atomic; one slot per index, written by its worker + feng::matrix_details::parallel_workers( [&]( I i ) + { + visits[static_cast( i )].fetch_add( 1 ); + slots[static_cast( i - first )] = static_cast( i ) + 1; + }, first, last, workers ); + for ( std::size_t i = 0; i != base + n + pad; ++i ) + { + bool const inside = i >= static_cast( first ) && i < base + n && n != 0; + REQUIRE( visits[i].load() == ( inside ? 1 : 0 ) ); + } + for ( std::size_t j = 0; j != n; ++j ) + REQUIRE( slots[j] == static_cast( first ) + j + 1 ); + } + + template< typename I > + void check_chunks( I first, I last, std::size_t workers ) + { + std::size_t const n = first < last ? static_cast( last - first ) : 0; + std::size_t const w = feng::matrix_details::effective_workers( first, last, workers ); + if ( n == 0 ) { REQUIRE( w == 0 ); return; } + REQUIRE( w == std::clamp( workers, std::size_t{1}, n ) ); + I expected = first; + for ( std::size_t k = 0; k != w; ++k ) + { + auto const [b, e] = feng::matrix_details::chunk_bounds( first, last, workers, k ); + REQUIRE( b == expected ); // contiguous, in order, disjoint + REQUIRE( b < e ); // non-empty + std::size_t const len = static_cast( e - b ); + REQUIRE( len == n / w + ( k < n % w ? 1 : 0 ) ); + expected = e; + } + REQUIRE( expected == last ); // covers [first, last) + } + + // Each chunk sets its own plain slot; the caller reads them after return with no other synchronisation. + inline void check_chunk_slots( std::size_t first, std::size_t last, std::size_t workers ) + { + std::size_t const w = feng::matrix_details::effective_workers( first, last, workers ); + std::vector slot( w, 0 ); + feng::matrix_details::parallel_workers( [&]( std::size_t k ) + { + auto const [b, e] = feng::matrix_details::chunk_bounds( first, last, workers, k ); + slot[k] = e - b; + }, std::size_t{0}, w, w ); + std::size_t total = 0; + for ( auto s : slot ) { REQUIRE( s > 0 ); total += s; } + REQUIRE( total == last - first ); + } + + inline std::vector worker_counts( std::size_t n ) + { + return { 0, 1, 2, 7, 32, n + 1, n + 100 }; + } +} + +TEST_CASE( "S6-R1 parallel visits every index once for injected worker counts", "[S6][S6-R1]" ) +{ + SECTION( "[0, n) for n in 1, 10, 100, 1000" ) + { + for ( std::size_t n : { 1UL, 10UL, 100UL, 1000UL } ) + for ( std::size_t w : s6_r1::worker_counts( n ) ) + { + CAPTURE( n ); CAPTURE( w ); + s6_r1::check_visits( std::size_t{0}, n, w ); + s6_r1::check_chunks( std::size_t{0}, n, w ); + s6_r1::check_chunk_slots( 0, n, w ); + } + } + SECTION( "[5, 17) with 32 workers and other counts" ) + { + s6_r1::check_visits( 5, 17, 32 ); + s6_r1::check_chunks( 5, 17, 32 ); + s6_r1::check_chunk_slots( 5, 17, 32 ); + for ( std::size_t w : s6_r1::worker_counts( 12 ) ) + { + CAPTURE( w ); + s6_r1::check_visits( 5UL, 17UL, w ); + s6_r1::check_chunks( 5UL, 17UL, w ); + s6_r1::check_chunk_slots( 5, 17, w ); + } + s6_r1::check_visits( 1000, 1777, 7 ); + s6_r1::check_chunks( 1000, 1777, 7 ); + } + SECTION( "empty and reversed ranges call nothing" ) + { + for ( std::size_t w : { 0UL, 1UL, 2UL, 7UL, 32UL } ) + { + int calls = 0; + feng::matrix_details::parallel_workers( [&]( int ) { ++calls; }, 3, 3, w ); + feng::matrix_details::parallel_workers( [&]( int ) { ++calls; }, 9, 3, w ); + feng::matrix_details::parallel_workers( [&]( std::size_t ) { ++calls; }, 0UL, 0UL, w ); + REQUIRE( calls == 0 ); + REQUIRE( feng::matrix_details::effective_workers( 3, 3, w ) == 0 ); + REQUIRE( feng::matrix_details::effective_workers( 9, 3, w ) == 0 ); + s6_r1::check_chunks( 3, 3, w ); + } + } + SECTION( "public parallel( func, first, last, threshold ) and parallel( func, last ) visit every index once" ) + { + for ( std::size_t n : { 0UL, 1UL, 33UL, 2000UL } ) + for ( unsigned long threshold : { 0UL, 1024UL } ) + { + CAPTURE( n ); CAPTURE( threshold ); + std::unique_ptr[]> visits{ new std::atomic[ n + 8 ] }; + for ( std::size_t i = 0; i != n + 8; ++i ) visits[i].store( 0 ); + feng::matrix_details::parallel( [&]( std::size_t i ) { visits[i].fetch_add( 1 ); }, std::size_t{3}, n + 3, threshold ); + for ( std::size_t i = 0; i != n + 8; ++i ) + REQUIRE( visits[i].load() == ( i >= 3 && i < n + 3 ? 1 : 0 ) ); + } + std::unique_ptr[]> visits{ new std::atomic[ 50 ] }; + for ( std::size_t i = 0; i != 50; ++i ) visits[i].store( 0 ); + feng::matrix_details::parallel( [&]( std::size_t i ) { visits[i].fetch_add( 1 ); }, std::size_t{50} ); + for ( std::size_t i = 0; i != 50; ++i ) + REQUIRE( visits[i].load() == 1 ); + } + SECTION( "default_workers is 1 for small or serial jobs and at least 1 otherwise" ) + { + REQUIRE( feng::matrix_details::default_workers( 10, 1024 ) == 1 ); + REQUIRE( feng::matrix_details::default_workers( 0, 0 ) == 1 ); + std::size_t const big = feng::matrix_details::default_workers( 1UL << 20, 32 ); +#ifdef FENG_MATRIX_PARALLEL + unsigned const hc = std::thread::hardware_concurrency(); + REQUIRE( big == ( hc == 0 ? 1 : hc ) ); +#else + REQUIRE( big == 1 ); +#endif + } +} + +TEST_CASE( "S6-R1 both reductions include init exactly once", "[S6][S6-R1]" ) +{ + auto const plus = []( auto a, auto b ) { return a + b; }; + + SECTION( "ten elements, init 100, workers 0: 100 + sum, for matrix and matrix" ) + { + feng::matrix md( 2, 5 ); + std::iota( md.begin(), md.end(), 1.0 ); + feng::matrix mi( 2, 5 ); + std::iota( mi.begin(), mi.end(), 1 ); + REQUIRE( feng::matrix_details::reduce( md.begin(), md.end(), 100.0, plus, 0 ) == 155.0 ); + REQUIRE( feng::matrix_details::reduce( mi.begin(), mi.end(), 100, plus, 0 ) == 155 ); + REQUIRE( feng::matrix_details::reduce_impl_private::reduce_impl( md )( plus, 100.0, 0 ) == 155.0 ); + REQUIRE( feng::matrix_details::reduce_impl_private::reduce_impl( mi )( plus, 100, 0 ) == 155 ); + } + SECTION( "every worker count, sizes 0, 1, 10, 1000: both reductions equal std::accumulate" ) + { + for ( std::size_t n : { 0UL, 1UL, 10UL, 1000UL } ) + { + feng::matrix mi( 1, n ); + std::iota( mi.begin(), mi.end(), -7 ); + feng::matrix md( n, 1 ); + std::iota( md.begin(), md.end(), 0.5 ); + int const ei = std::accumulate( mi.begin(), mi.end(), 100, plus ); + double const ed = std::accumulate( md.begin(), md.end(), 100.0, plus ); + for ( std::size_t w : s6_r1::worker_counts( n ) ) + { + CAPTURE( n ); CAPTURE( w ); + REQUIRE( feng::matrix_details::reduce( mi.begin(), mi.end(), 100, plus, w ) == ei ); + REQUIRE( feng::matrix_details::reduce( md.begin(), md.end(), 100.0, plus, w ) == ed ); + REQUIRE( feng::matrix_details::reduce_impl_private::reduce_impl( mi )( plus, 100, w ) == ei ); + REQUIRE( feng::matrix_details::reduce_impl_private::reduce_impl( md )( plus, 100.0, w ) == ed ); + } + // default worker count + REQUIRE( feng::matrix_details::reduce( mi.begin(), mi.end(), 100, plus ) == ei ); + REQUIRE( feng::matrix_details::reduce_impl_private::reduce_impl( mi )( plus, 100 ) == ei ); + REQUIRE( feng::matrix_details::reduce( plus, 100 )( mi ) == ei ); + } + } + SECTION( "string concatenation shows order and a single init (iterator reduce)" ) + { + std::vector v; + for ( char c = 'a'; c != 'a' + 26; ++c ) v.emplace_back( 1, c ); + auto const cat = []( std::string const& a, std::string const& b ) { return a + b; }; + std::string const expected = std::accumulate( v.begin(), v.end(), std::string{ "<" }, cat ); + REQUIRE( expected == " a x + b stored as complex( a, b ); compose( f, g ) = g after f: associative, not commutative. + using C = std::complex; + auto const compose = []( C f, C g ) { return C{ f.real() * g.real(), g.real() * f.imag() + g.imag() }; }; + feng::matrix m( 2, 6 ); + for ( std::size_t i = 0; i != m.size(); ++i ) + m.data()[i] = C{ ( i % 2 == 0 ) ? 2.0 : 1.0, static_cast( i + 1 ) }; + C const init{ 1.0, 3.0 }; + C const expected = std::accumulate( m.begin(), m.end(), init, compose ); + REQUIRE( expected != std::accumulate( m.begin(), m.end(), C{ 1.0, 0.0 }, compose ) ); + for ( std::size_t w : s6_r1::worker_counts( m.size() ) ) + { + CAPTURE( w ); + REQUIRE( feng::matrix_details::reduce_impl_private::reduce_impl( m )( compose, init, w ) == expected ); + REQUIRE( feng::matrix_details::reduce( m.begin(), m.end(), init, compose, w ) == expected ); + } + } +} + +// S10-T7 (S10-R3, S10-R5): the work-based default worker counts (matrix_details::work_workers) keep small calls on +// the caller, so this case, run by the tsan lane, drives the same partition with more than one worker through the +// GEMM row split (injected and default counts), parallel_work and the default reductions on inputs above the grains. +TEST_CASE( "S6-R1 GEMM row split and work-based defaults run the partition on several workers", "[S6][S6-R1]" ) +{ + SECTION( "gemm_blocked: injected worker counts equal the 1-worker run" ) + { + std::size_t const m = 37, k = 29, n = 41; + feng::matrix a( m, k ), b( k, n ); + for ( std::size_t i = 0; i != a.size(); ++i ) a.data()[i] = 0.25 * static_cast( i % 17 ) - 1.0; + for ( std::size_t i = 0; i != b.size(); ++i ) b.data()[i] = 0.5 * static_cast( i % 13 ) - 2.0; + feng::matrix one( m, n ); + feng::matrix_details::gemm_blocked( a.data(), b.data(), one.data(), m, k, n, 1 ); + for ( std::size_t w : { 2UL, 3UL, 7UL, m, m + 5 } ) + { + CAPTURE( w ); + feng::matrix c( m, n ); + feng::matrix_details::gemm_blocked( a.data(), b.data(), c.data(), m, k, n, w ); + REQUIRE( c == one ); + } + } + SECTION( "operator* above the GEMM grain equals the 1-worker run" ) + { + // 128 * 64 * 64 = 2^19 multiply-adds: two workers by default in parallel builds + std::size_t const m = 128, k = 64, n = 64; + REQUIRE( m * k * n >= 2 * feng::matrix_details::gemm_grain ); + feng::matrix a( m, k ), b( k, n ); + for ( std::size_t i = 0; i != a.size(); ++i ) a.data()[i] = 0.125 * static_cast( i % 11 ) - 0.5; + for ( std::size_t i = 0; i != b.size(); ++i ) b.data()[i] = 0.375 * static_cast( i % 7 ) - 1.0; + feng::matrix one( m, n ); + feng::matrix_details::gemm_blocked( a.data(), b.data(), one.data(), m, k, n, 1 ); + REQUIRE( ( a * b ) == one ); + } + SECTION( "parallel_work and elementwise add above the elementwise grain visit every index once" ) + { + std::size_t const n = 2 * feng::matrix_details::elementwise_grain + 3; +#ifdef FENG_MATRIX_PARALLEL + if ( std::thread::hardware_concurrency() > 1 ) + REQUIRE( feng::matrix_details::work_workers( n, feng::matrix_details::elementwise_grain ) == 2 ); +#endif + std::unique_ptr[]> visits{ new std::atomic[ n ] }; + for ( std::size_t i = 0; i != n; ++i ) visits[i].store( 0 ); + std::vector slots( n, 0 ); + feng::matrix_details::parallel_work( [&]( std::size_t i ) { visits[i].fetch_add( 1 ); slots[i] = i + 1; }, + std::size_t{ 0 }, n, 1, feng::matrix_details::elementwise_grain ); + bool once = true; + for ( std::size_t i = 0; i != n; ++i ) once = once && visits[i].load() == 1 && slots[i] == i + 1; + REQUIRE( once ); + + feng::matrix x( 1, n ), y( 1, n ); + std::iota( x.begin(), x.end(), 0 ); + std::iota( y.begin(), y.end(), 7 ); + auto const z = x + y; + bool sums = true; + for ( std::size_t i = 0; i != n; ++i ) sums = sums && z.data()[i] == static_cast( 2 * i + 7 ); + REQUIRE( sums ); + } + SECTION( "default reductions above the reduce grain equal reduce_range with the same worker count" ) + { + std::size_t const n = 3 * feng::matrix_details::reduce_grain + 7; + std::size_t const w = feng::matrix_details::work_workers( n, feng::matrix_details::reduce_grain ); + feng::matrix m( 1, n ); + for ( std::size_t i = 0; i != n; ++i ) m.data()[i] = 0.1 * static_cast( i % 9 ) - 0.3; + auto const plus = []( double p, double q ) { return p + q; }; + auto const at = [&m]( std::size_t i ) noexcept -> double const& { return m.data()[i]; }; + double const chunked = feng::matrix_details::reduce_range( at, n, 0.0, plus, w ); + REQUIRE( feng::matrix_details::reduce( m.begin(), m.end(), 0.0, plus ) == chunked ); + REQUIRE( feng::matrix_details::reduce_impl_private::reduce_impl( m )( plus, 0.0 ) == chunked ); + REQUIRE( feng::sum( m ) == chunked ); + } +} diff --git a/tests/cases/s6_r3.hpp b/tests/cases/s6_r3.hpp new file mode 100644 index 0000000..0d48bfa --- /dev/null +++ b/tests/cases/s6_r3.hpp @@ -0,0 +1,227 @@ +// S6-R3 (PR-8): caller-owned random engines (F17, D-024). Elements come from the engine alone, in row-major +// order; floating parts lie in (0, 1); integral elements follow std::uniform_int_distribution over the full range. +#include +#include +#include +#include +#include + +namespace s6_r3 +{ + template< typename T > + bool in_open_unit( T x ) { return T{0} < x && x < T{1}; } + + template< typename T > + void check_open_interval() + { + std::mt19937 g{ 2026 }; + auto const m = feng::random( 64, 64, g ); + REQUIRE( m.row() == 64 ); + REQUIRE( m.col() == 64 ); + bool all_inside = true; + for ( auto const x : m ) all_inside = all_inside && in_open_unit( x ); + REQUIRE( all_inside ); + } + + template< typename T > + bool element_in_open_unit( T const& x ) + { + if constexpr ( std::is_floating_point_v ) return in_open_unit( x ); + else return in_open_unit( x.real() ) && in_open_unit( x.imag() ); + } + + template< typename M > + bool all_in_open_unit( M const& m ) + { + bool all_inside = true; + for ( auto const& x : m ) all_inside = all_inside && element_in_open_unit( x ); + return all_inside; + } + + // every engine overload: random( r, c, g ), random( n, g ), rand( r, c, g ), rand( n, g ), rand_like, random_like + template< typename T > + void check_open_interval_all_overloads() + { + std::mt19937_64 g{ 2027 }; + auto const a = feng::random( 16, 24, g ); + auto const b = feng::random( 20, g ); + auto const c = feng::rand( 24, 16, g ); + auto const d = feng::rand( 18, g ); + feng::matrix const shape{ 12, 10 }; + auto const e = feng::rand_like( shape, g ); + auto const f = feng::random_like( shape, g ); + REQUIRE( a.row() == 16 ); + REQUIRE( a.col() == 24 ); + REQUIRE( b.row() == 20 ); + REQUIRE( b.col() == 20 ); + REQUIRE( c.row() == 24 ); + REQUIRE( c.col() == 16 ); + REQUIRE( d.row() == 18 ); + REQUIRE( d.col() == 18 ); + REQUIRE( e.row() == 12 ); + REQUIRE( e.col() == 10 ); + REQUIRE( f.row() == 12 ); + REQUIRE( f.col() == 10 ); + REQUIRE( all_in_open_unit( a ) ); + REQUIRE( all_in_open_unit( b ) ); + REQUIRE( all_in_open_unit( c ) ); + REQUIRE( all_in_open_unit( d ) ); + REQUIRE( all_in_open_unit( e ) ); + REQUIRE( all_in_open_unit( f ) ); + } + + template< typename T > + void check_int_oracle() + { + std::mt19937_64 g{ 11 }; + std::mt19937_64 h{ 11 }; + auto const m = feng::random( 9, 7, g ); + using D = std::conditional_t< ( sizeof( T ) < sizeof( int ) ), std::conditional_t< std::is_signed_v, int, unsigned >, T >; + std::uniform_int_distribution dist{ static_cast( std::numeric_limits::min() ), static_cast( std::numeric_limits::max() ) }; + bool same = true; + for ( auto const x : m ) same = same && ( x == static_cast( dist( h ) ) ); + REQUIRE( same ); + } +} + +TEST_CASE( "S6-R3 equally seeded engines give identical matrices", "[S6][S6-R3]" ) +{ + { + std::mt19937 g{ 5 }, h{ 5 }; + REQUIRE( feng::random( 13, 17, g ) == feng::random( 13, 17, h ) ); + REQUIRE( feng::rand( 13, 17, g ) == feng::rand( 13, 17, h ) ); + REQUIRE( feng::rand( 6, g ) == feng::random( 6, h ) ); + feng::matrix const shape{ 4, 9 }; + auto const a = feng::rand_like( shape, g ); + auto const b = feng::random_like( shape, h ); + REQUIRE( a.row() == 4 ); + REQUIRE( a.col() == 9 ); + REQUIRE( a == b ); + } + { + // the legacy seed overload forwards to the same implementation through a local std::mt19937_64 + std::mt19937_64 g{ 7 }; + REQUIRE( feng::rand( 5, 8, 7 ) == feng::random( 5, 8, g ) ); + REQUIRE( feng::rand( 3, 4 ).row() == 3 ); // still the seed overload + } + { + // row-major order: element k is the k-th draw + std::mt19937 g{ 3 }, h{ 3 }; + auto const m = feng::random( 3, 5, g ); + auto const v = feng::random( 1, 15, h ); + bool same = true; + for ( std::size_t r = 0; r != 3; ++r ) + for ( std::size_t c = 0; c != 5; ++c ) + same = same && ( m[r][c] == v[0][r * 5 + c] ); + REQUIRE( same ); + } +} + +TEST_CASE( "S6-R3 floating and complex elements lie in the open interval (0, 1)", "[S6][S6-R3]" ) +{ + s6_r3::check_open_interval(); + s6_r3::check_open_interval(); + s6_r3::check_open_interval(); + std::mt19937 g{ 99 }; + auto const m = feng::random>( 32, 32, g ); + bool all_inside = true; + for ( auto const z : m ) all_inside = all_inside && s6_r3::in_open_unit( z.real() ) && s6_r3::in_open_unit( z.imag() ); + REQUIRE( all_inside ); + // real part is drawn before the imaginary part + std::mt19937 h{ 99 }, k{ 99 }; + auto const z = feng::random>( 1, 1, h )[0][0]; + auto const p = feng::random( 1, 2, k ); + REQUIRE( z.real() == p[0][0] ); + REQUIRE( z.imag() == p[0][1] ); + s6_r3::check_open_interval_all_overloads(); + s6_r3::check_open_interval_all_overloads(); + s6_r3::check_open_interval_all_overloads(); + s6_r3::check_open_interval_all_overloads>(); + s6_r3::check_open_interval_all_overloads>(); + s6_r3::check_open_interval_all_overloads>(); +} + +TEST_CASE( "S6-R3 integral elements follow a uniform integer distribution", "[S6][S6-R3]" ) +{ + s6_r3::check_int_oracle(); + s6_r3::check_int_oracle(); + s6_r3::check_int_oracle(); + s6_r3::check_int_oracle(); + s6_r3::check_int_oracle(); + s6_r3::check_int_oracle(); + s6_r3::check_int_oracle(); + s6_r3::check_int_oracle(); + s6_r3::check_int_oracle(); + s6_r3::check_int_oracle(); + s6_r3::check_int_oracle(); + s6_r3::check_int_oracle(); + std::mt19937 g{ 1 }; + auto const s = feng::random( 32, 32, g ); + auto const u = feng::random( 32, 32, g ); + bool s_neg = false, s_pos = false, u_low = false, u_high = false; + for ( auto const x : s ) { s_neg = s_neg || x < 0; s_pos = s_pos || x > 0; } + for ( auto const x : u ) { u_low = u_low || x < 128; u_high = u_high || x >= 128; } + REQUIRE( s_neg ); + REQUIRE( s_pos ); + REQUIRE( u_low ); + REQUIRE( u_high ); +} + +TEST_CASE( "S6-R3 engines used from concurrent threads do not interfere", "[S6][S6-R3]" ) +{ + feng::matrix a, b; + std::thread ta( [&a]{ std::mt19937 g{ 42 }; a = feng::random( 256, 256, g ); } ); + std::thread tb( [&b]{ std::mt19937 g{ 42 }; b = feng::random( 256, 256, g ); } ); + ta.join(); + tb.join(); + REQUIRE( a.row() == 256 ); + REQUIRE( a.col() == 256 ); + REQUIRE( a == b ); + std::mt19937 g{ 42 }; + REQUIRE( a == feng::random( 256, 256, g ) ); + // a second pair drawing int + feng::matrix p, q; + std::thread tp( [&p]{ std::mt19937 e{ 42 }; p = feng::random( 256, 256, e ); } ); + std::thread tq( [&q]{ std::mt19937 e{ 42 }; q = feng::random( 256, 256, e ); } ); + tp.join(); + tq.join(); + REQUIRE( p.row() == 256 ); + REQUIRE( p.col() == 256 ); + REQUIRE( p == q ); +} + +TEST_CASE( "S6-R3 legacy seed-only overloads keep their shapes and the open interval", "[S6][S6-R3]" ) +{ + feng::matrix const shape{ 5, 3 }; + auto const a = feng::rand( 4, 6, 9 ); + auto const b = feng::rand( 7 ); + auto const c = feng::random( 3, 8 ); + auto const d = feng::random( 6 ); + auto const e = feng::rand_like( shape ); + auto const f = feng::random_like( shape ); + auto const h = feng::randn_like( shape ); + REQUIRE( a.row() == 4 ); + REQUIRE( a.col() == 6 ); + REQUIRE( b.row() == 7 ); + REQUIRE( b.col() == 7 ); + REQUIRE( c.row() == 3 ); + REQUIRE( c.col() == 8 ); + REQUIRE( d.row() == 6 ); + REQUIRE( d.col() == 6 ); + REQUIRE( e.row() == 5 ); + REQUIRE( e.col() == 3 ); + REQUIRE( f.row() == 5 ); + REQUIRE( f.col() == 3 ); + REQUIRE( h.row() == 5 ); + REQUIRE( h.col() == 3 ); + REQUIRE( s6_r3::all_in_open_unit( a ) ); + REQUIRE( s6_r3::all_in_open_unit( b ) ); + REQUIRE( s6_r3::all_in_open_unit( c ) ); + REQUIRE( s6_r3::all_in_open_unit( d ) ); + REQUIRE( s6_r3::all_in_open_unit( e ) ); + REQUIRE( s6_r3::all_in_open_unit( f ) ); + REQUIRE( s6_r3::all_in_open_unit( h ) ); + // the only legacy overload taking a seed: a nonzero seed equals the engine overload with std::mt19937_64{ seed } + std::mt19937_64 g{ 9 }; + REQUIRE( a == feng::random( 4, 6, g ) ); +} diff --git a/tests/cases/s6_r4.hpp b/tests/cases/s6_r4.hpp new file mode 100644 index 0000000..18506de --- /dev/null +++ b/tests/cases/s6_r4.hpp @@ -0,0 +1,132 @@ +// S6-R4 (PR-9): extrema start from element 0; mean, variance and standard_deviation result types, the shared +// ddof parameter (D-025) and empty-input aborts (D-011) (F15). +#include +#include +#include +#include + +#include "s2_death.hpp" + +namespace s6_r4 +{ + template< typename T > + void check_negative_extrema() + { + feng::matrix const m{ 1, 2, { T( -3 ), T( -1 ) } }; + REQUIRE( m.max() == T( -1 ) ); + REQUIRE( m.min() == T( -3 ) ); + auto const less = []( T const& x, T const& y ) { return x < y; }; + REQUIRE( m.max( less ) == T( -1 ) ); + REQUIRE( m.min( less ) == T( -3 ) ); + auto const [lo, hi] = m.minmax(); + REQUIRE( lo == T( -3 ) ); + REQUIRE( hi == T( -1 ) ); + auto const [lo2, hi2] = m.minmax( less ); + REQUIRE( lo2 == T( -3 ) ); + REQUIRE( hi2 == T( -1 ) ); + REQUIRE( feng::max( m ) == T( -1 ) ); + REQUIRE( feng::min( m ) == T( -3 ) ); + feng::matrix const p{ 1, 2, { T( 1 ), T( 3 ) } }; + REQUIRE( p.min() == T( 1 ) ); + REQUIRE( p.max() == T( 3 ) ); + auto const [plo, phi] = p.minmax(); + REQUIRE( plo == T( 1 ) ); + REQUIRE( phi == T( 3 ) ); + REQUIRE( feng::min( p ) == T( 1 ) ); + } + + template< typename M > + using mean_t = decltype( feng::mean( std::declval< M const& >() ) ); + template< typename M > + using var_t = decltype( feng::variance( std::declval< M const& >() ) ); + template< typename M > + using std_t = decltype( feng::standard_deviation( std::declval< M const& >() ) ); + + using mi = feng::matrix; + using mu = feng::matrix; + using mf = feng::matrix; + using md = feng::matrix; + using mc = feng::matrix>; + static_assert( std::is_same_v< std::remove_cv_t< mean_t >, double > ); + static_assert( std::is_same_v< std::remove_cv_t< mean_t >, double > ); + static_assert( std::is_same_v< std::remove_cv_t< mean_t >, float > ); + static_assert( std::is_same_v< std::remove_cv_t< mean_t >, double > ); + static_assert( std::is_same_v< std::remove_cv_t< mean_t >, std::complex > ); + static_assert( std::is_same_v< std::remove_cv_t< var_t >, double > ); + static_assert( std::is_same_v< std::remove_cv_t< var_t >, double > ); + static_assert( std::is_same_v< std::remove_cv_t< var_t >, float > ); + static_assert( std::is_same_v< std::remove_cv_t< var_t >, double > ); + static_assert( std::is_same_v< std::remove_cv_t< var_t >, double > ); + static_assert( std::is_same_v< std::remove_cv_t< std_t >, double > ); + static_assert( std::is_same_v< std::remove_cv_t< std_t >, double > ); + static_assert( std::is_same_v< std::remove_cv_t< std_t >, float > ); + static_assert( std::is_same_v< std::remove_cv_t< std_t >, double > ); + static_assert( std::is_same_v< std::remove_cv_t< std_t >, double > ); + using mcf = feng::matrix>; + static_assert( std::is_same_v< std::remove_cv_t< var_t >, float > ); + static_assert( noexcept( feng::variance( std::declval< md const& >(), 1 ) ) ); + static_assert( noexcept( feng::standard_deviation( std::declval< md const& >(), 1 ) ) ); +} + +TEST_CASE( "S6-R4 max and min of all-negative matrices", "[S6][S6-R4]" ) +{ + s6_r4::check_negative_extrema(); + s6_r4::check_negative_extrema(); + s6_r4::check_negative_extrema(); +} + +TEST_CASE( "S6-R4 mean of integer matrices is a floating value", "[S6][S6-R4]" ) +{ + REQUIRE( feng::mean( feng::matrix{ 1, 2, { 1, 2 } } ) == 1.5 ); + REQUIRE( feng::mean( feng::matrix{ 1, 2, { -1, -2 } } ) == -1.5 ); + REQUIRE( feng::mean( feng::matrix{ 1, 2, { 1U, 2U } } ) == 1.5 ); + REQUIRE( feng::mean( feng::matrix{ 1, 2, { 1.0f, 2.0f } } ) == 1.5f ); + auto const z = feng::mean( feng::matrix>{ 1, 2, { { 1.0, 2.0 }, { 2.0, -4.0 } } } ); + REQUIRE( z == std::complex{ 1.5, -1.0 } ); +} + +TEST_CASE( "S6-R4 variance and standard_deviation share ddof", "[S6][S6-R4]" ) +{ + feng::matrix const m{ 1, 4, { 1, 2, 3, 4 } }; + // mean 2.5; squared deviations 2.25 + 0.25 + 0.25 + 2.25 = 5 + REQUIRE( feng::variance( m ) == 1.25 ); + REQUIRE( feng::variance( m, 0 ) == 1.25 ); + REQUIRE( std::abs( feng::variance( m, 1 ) - 5.0 / 3.0 ) < 1.0e-12 ); + REQUIRE( std::abs( feng::standard_deviation( m ) - std::sqrt( 1.25 ) ) < 1.0e-12 ); + REQUIRE( std::abs( feng::standard_deviation( m, 1 ) - std::sqrt( 5.0 / 3.0 ) ) < 1.0e-12 ); + feng::matrix const d{ 2, 2, { 1.0, 2.0, 3.0, 4.0 } }; + REQUIRE( feng::variance( d ) == 1.25 ); + REQUIRE( feng::standard_deviation( d, 1 ) * feng::standard_deviation( d, 1 ) == Approx( feng::variance( d, 1 ) ) ); + feng::matrix const u{ 1, 2, { 1U, 3U } }; + REQUIRE( feng::variance( u ) == 1.0 ); // no unsigned wrap of x - mean + // complex: |z - mean|^2 with mean (0, 0): |1+i|^2 = |-1-i|^2 = 2 + feng::matrix> const c{ 1, 2, { { 1.0, 1.0 }, { -1.0, -1.0 } } }; + REQUIRE( feng::variance( c ) == 2.0 ); + REQUIRE( feng::standard_deviation( c ) == std::sqrt( 2.0 ) ); + feng::matrix> const cf{ 1, 2, { { 1.0f, 1.0f }, { -1.0f, -1.0f } } }; + auto const vf = feng::variance( cf ); + static_assert( std::is_same_v< std::remove_cv_t< decltype( vf ) >, float > ); + REQUIRE( vf == 2.0f ); +} + +TEST_CASE( "S6-R4 empty inputs abort with a message", "[S6][S6-R4]" ) +{ + S2_REQUIRE_DEATH( []{ feng::matrix const e; (void)e.max(); }, "matrix max: empty matrix" ); + S2_REQUIRE_DEATH( []{ feng::matrix const e; (void)e.min(); }, "matrix min: empty matrix" ); + S2_REQUIRE_DEATH( []{ feng::matrix const e; (void)e.minmax(); }, "matrix minmax: empty matrix" ); + S2_REQUIRE_DEATH( []{ feng::matrix const e; (void)feng::max( e ); }, "feng::max: empty matrix" ); + S2_REQUIRE_DEATH( []{ feng::matrix const e; (void)feng::min( e ); }, "feng::min: empty matrix" ); + S2_REQUIRE_DEATH( []{ feng::matrix const e; (void)feng::mean( e ); }, "feng::mean: empty matrix" ); + S2_REQUIRE_DEATH( []{ feng::matrix const e; (void)feng::variance( e ); }, "feng::variance: " ); + S2_REQUIRE_DEATH( []{ feng::matrix const e; (void)feng::standard_deviation( e ); }, "feng::standard_deviation: " ); + S2_REQUIRE_DEATH( []{ feng::matrix const one{ 1, 1, { 2.0 } }; (void)feng::variance( one, 1 ); }, "feng::variance: " ); + S2_REQUIRE_DEATH( []{ feng::matrix const one{ 1, 1, { 2.0 } }; (void)feng::standard_deviation( one, 1 ); }, "feng::standard_deviation: " ); + S2_REQUIRE_DEATH( []{ feng::matrix const e; (void)e.max( []( double x, double y ) { return x < y; } ); }, "matrix max: empty matrix" ); + S2_REQUIRE_DEATH( []{ feng::matrix const e; (void)e.min( []( double x, double y ) { return x < y; } ); }, "matrix min: empty matrix" ); + S2_REQUIRE_DEATH( []{ feng::matrix const e; (void)e.minmax( []( double x, double y ) { return x < y; } ); }, "matrix minmax: empty matrix" ); + S2_REQUIRE_DEATH( []{ feng::matrix> const e; (void)feng::mean( e ); }, "feng::mean: empty matrix" ); + S2_REQUIRE_DEATH( []{ feng::matrix> const e; (void)feng::variance( e ); }, "feng::variance: needs more elements than ddof" ); + S2_REQUIRE_DEATH( []{ feng::matrix> const e; (void)feng::standard_deviation( e ); }, "feng::standard_deviation: needs more elements than ddof" ); + S2_REQUIRE_DEATH( []{ feng::matrix const e; (void)feng::variance( e ); }, "feng::variance: needs more elements than ddof" ); + S2_REQUIRE_DEATH( []{ feng::matrix const e; (void)feng::standard_deviation( e ); }, "feng::standard_deviation: needs more elements than ddof" ); +} diff --git a/tests/cases/s6_r5.hpp b/tests/cases/s6_r5.hpp new file mode 100644 index 0000000..e308ff4 --- /dev/null +++ b/tests/cases/s6_r5.hpp @@ -0,0 +1,336 @@ +// S6-R5 (PR-9): the promotion policy for scalar and mixed-type arithmetic (D-023, F17). +// matrix (+) matrix gives common_element_t; matrix (+) scalar keeps T unless the scalar's kind +// (integral < floating < complex) is higher; compound assignment keeps T; the result allocator is A rebound. +#include +#include +#include +#include +#include +#include + +#include "./s3_alloc.hpp" + +namespace s6_r5 +{ + using cf = std::complex; + using cd = std::complex; + + template< typename T > + using mat = feng::matrix; + + template< typename T, typename S > using add_t = std::remove_cvref_t< decltype( std::declval< mat const& >() + std::declval< S const& >() ) >; + template< typename T, typename S > using radd_t = std::remove_cvref_t< decltype( std::declval< S const& >() + std::declval< mat const& >() ) >; + template< typename T, typename S > using sub_t = std::remove_cvref_t< decltype( std::declval< mat const& >() - std::declval< S const& >() ) >; + template< typename T, typename S > using rsub_t = std::remove_cvref_t< decltype( std::declval< S const& >() - std::declval< mat const& >() ) >; + template< typename T, typename S > using mul_t = std::remove_cvref_t< decltype( std::declval< mat const& >() * std::declval< S const& >() ) >; + template< typename T, typename S > using rmul_t = std::remove_cvref_t< decltype( std::declval< S const& >() * std::declval< mat const& >() ) >; + template< typename T, typename S > using div_t = std::remove_cvref_t< decltype( std::declval< mat const& >() / std::declval< S const& >() ) >; + template< typename T, typename S > using rdiv_t = std::remove_cvref_t< decltype( std::declval< S const& >() / std::declval< mat const& >() ) >; + + template< typename T, typename U > using madd_t = std::remove_cvref_t< decltype( std::declval< mat const& >() + std::declval< mat const& >() ) >; + template< typename T, typename U > using msub_t = std::remove_cvref_t< decltype( std::declval< mat const& >() - std::declval< mat const& >() ) >; + template< typename T, typename U > using mmul_t = std::remove_cvref_t< decltype( std::declval< mat const& >() * std::declval< mat const& >() ) >; + template< typename T, typename U > using mdiv_t = std::remove_cvref_t< decltype( std::declval< mat const& >() / std::declval< mat const& >() ) >; + + // The expected element type, written out independently of the library's traits. + template< typename T > constexpr int kind() { if constexpr ( std::is_integral_v ) return 0; else if constexpr ( std::is_floating_point_v ) return 1; else return 2; } + template< typename T > struct real_of { using type = T; }; + template< typename X > struct real_of< std::complex > { using type = X; }; + template< typename T, typename U > + using expected_common_t = std::conditional_t< kind() == 2 || kind() == 2, + std::complex< std::common_type_t< typename real_of::type, typename real_of::type > >, + std::common_type_t< T, U > >; + template< typename T, typename S > + using expected_scalar_t = std::conditional_t< ( kind() <= kind() ), T, expected_common_t< T, S > >; + + template< typename T, typename S > + constexpr bool scalar_pair() + { + using R = expected_scalar_t< T, S >; + static_assert( std::is_same_v< feng::matrix_details::scalar_result_t< T, S >, R > ); + static_assert( std::is_same_v< add_t, mat > ); + static_assert( std::is_same_v< radd_t, mat > ); + static_assert( std::is_same_v< sub_t, mat > ); + static_assert( std::is_same_v< rsub_t, mat > ); + static_assert( std::is_same_v< mul_t, mat > ); + static_assert( std::is_same_v< rmul_t, mat > ); + static_assert( std::is_same_v< div_t, mat > ); + static_assert( std::is_same_v< rdiv_t, mat > ); + return true; + } + + template< typename T, typename U > + constexpr bool matrix_pair() + { + using R = expected_common_t< T, U >; + static_assert( std::is_same_v< feng::matrix_details::common_element_t< T, U >, R > ); + static_assert( std::is_same_v< madd_t, mat > ); + static_assert( std::is_same_v< msub_t, mat > ); + static_assert( std::is_same_v< mmul_t, mat > ); + static_assert( std::is_same_v< mdiv_t, mat > ); + return true; + } + + template< typename T, typename... S > + constexpr bool scalar_row() { return ( scalar_pair< T, S >() && ... ); } + template< typename T, typename... U > + constexpr bool matrix_row() { return ( matrix_pair< T, U >() && ... ); } + + template< typename... T > + constexpr bool scalar_grid() { return ( scalar_row< T, int, unsigned, std::uint8_t, float, double, cf, cd >() && ... ); } + template< typename... T > + constexpr bool matrix_grid() { return ( matrix_row< T, int, unsigned, std::uint8_t, float, double, cf, cd >() && ... ); } + + static_assert( scalar_grid< int, unsigned, std::uint8_t, float, double, cf, cd >() ); + static_assert( matrix_grid< int, unsigned, std::uint8_t, float, double, cf, cd >() ); + + // The traits as D-023 states them, spot-checked by hand. + static_assert( feng::matrix_details::element_kind::value == 0 ); + static_assert( feng::matrix_details::element_kind::value == 1 ); + static_assert( feng::matrix_details::element_kind::value == 2 ); + static_assert( std::is_same_v< feng::matrix_details::common_element_t< cf, double >, cd > ); + static_assert( std::is_same_v< feng::matrix_details::common_element_t< int, unsigned >, unsigned > ); + static_assert( std::is_same_v< feng::matrix_details::scalar_result_t< float, double >, float > ); + static_assert( std::is_same_v< feng::matrix_details::scalar_result_t< std::uint8_t, int >, std::uint8_t > ); + static_assert( std::is_same_v< feng::matrix_details::scalar_result_t< int, double >, double > ); + static_assert( std::is_same_v< feng::matrix_details::scalar_result_t< float, cd >, cd > ); + static_assert( std::is_same_v< feng::matrix_details::scalar_result_t< cf, double >, cf > ); + + // A scalar is an arithmetic type or a std::complex: matrices, pointers, valarray and vector are not. + static_assert( feng::matrix_details::matrix_scalar && feng::matrix_details::matrix_scalar ); + static_assert( !feng::matrix_details::matrix_scalar< mat > ); + static_assert( !feng::matrix_details::matrix_scalar< double const* > ); + static_assert( !feng::matrix_details::matrix_scalar< std::valarray > ); + static_assert( !feng::matrix_details::matrix_scalar< std::vector > ); + + // Compound assignment keeps the left element type. + template< typename T, typename S > using cadd_t = decltype( std::declval< mat& >() += std::declval< S const& >() ); + template< typename T, typename S > using cmul_t = decltype( std::declval< mat& >() *= std::declval< S const& >() ); + static_assert( std::is_same_v< cadd_t< int, double >, mat& > ); + static_assert( std::is_same_v< cmul_t< float, double >, mat& > ); + static_assert( std::is_same_v< cadd_t< double, mat >, mat& > ); + static_assert( std::is_same_v< cmul_t< double, mat >, mat& > ); +} + +TEST_CASE( "S6-R5 scalar result types", "[S6][S6-R5]" ) +{ + using namespace s6_r5; + + SECTION( "matrix * 0.5 promotes to double" ) + { + mat const m{ 1, 2, { 1, 2 } }; + auto const r = m * 0.5; + static_assert( std::is_same_v< std::remove_cvref_t< decltype( r ) >, mat > ); + REQUIRE( r[0][0] == 0.5 ); + REQUIRE( r[0][1] == 1.0 ); + auto const l = 0.5 * m; + REQUIRE( l[0][0] == 0.5 ); + REQUIRE( l[0][1] == 1.0 ); + auto const d = m / 4.0; + REQUIRE( d[0][0] == 0.25 ); + REQUIRE( d[0][1] == 0.5 ); + auto const s = 3.5 - m; + REQUIRE( s[0][0] == 2.5 ); + REQUIRE( s[0][1] == 1.5 ); + } + + SECTION( "matrix * 2 compiles and keeps double" ) + { + mat const m{ 1, 2, { 1.5, -2.0 } }; + auto const r = m * 2; + static_assert( std::is_same_v< std::remove_cvref_t< decltype( r ) >, mat > ); + REQUIRE( r[0][0] == 3.0 ); + REQUIRE( r[0][1] == -4.0 ); + auto const p = 1 + m; + REQUIRE( p[0][0] == 2.5 ); + REQUIRE( p[0][1] == -1.0 ); + } + + SECTION( "matrix * 2.0 stays float, the scalar converted to float first" ) + { + mat const m{ 1, 2, { 1.0f, 3.0f } }; + auto const r = m * 2.0; + static_assert( std::is_same_v< std::remove_cvref_t< decltype( r ) >, mat > ); + REQUIRE( r[0][0] == 2.0f ); + REQUIRE( r[0][1] == 6.0f ); + // 0.1 rounded to float first, then multiplied in float. + auto const t = m * 0.1; + REQUIRE( t[0][1] == 3.0f * static_cast( 0.1 ) ); + } + + SECTION( "matrix + 1 stays uint8_t and wraps" ) + { + mat const m{ 1, 2, { std::uint8_t( 10 ), std::uint8_t( 255 ) } }; + auto const r = m + 1; + static_assert( std::is_same_v< std::remove_cvref_t< decltype( r ) >, mat > ); + REQUIRE( r[0][0] == 11 ); + REQUIRE( r[0][1] == 0 ); + auto const s = 1 - m; + REQUIRE( s[0][0] == std::uint8_t( 1 - 10 ) ); + REQUIRE( s[0][1] == 2 ); + } + + SECTION( "complex matrices with real scalars keep the complex type" ) + { + mat const m{ 1, 2, { cd( 1.0, 2.0 ), cd( -1.0, 0.5 ) } }; + auto const r = m * 2; + static_assert( std::is_same_v< std::remove_cvref_t< decltype( r ) >, mat > ); + REQUIRE( r[0][0] == cd( 2.0, 4.0 ) ); + REQUIRE( r[0][1] == cd( -2.0, 1.0 ) ); + auto const a = 1.0 + m; + REQUIRE( a[0][0] == cd( 2.0, 2.0 ) ); + auto const s = 1 - m; + REQUIRE( s[0][1] == cd( 2.0, -0.5 ) ); + mat const f{ 1, 1, { cf( 1.0f, 1.0f ) } }; + auto const g = f * 2.0; + static_assert( std::is_same_v< std::remove_cvref_t< decltype( g ) >, mat > ); + REQUIRE( g[0][0] == cf( 2.0f, 2.0f ) ); + } + + SECTION( "real matrices with complex scalars promote to complex" ) + { + mat const m{ 1, 2, { 1, 2 } }; + auto const r = m * cd( 0.0, 1.0 ); + static_assert( std::is_same_v< std::remove_cvref_t< decltype( r ) >, mat > ); + REQUIRE( r[0][0] == cd( 0.0, 1.0 ) ); + REQUIRE( r[0][1] == cd( 0.0, 2.0 ) ); + } + + SECTION( "scalar / matrix is the scalar times the inverse" ) + { + mat const m{ 2, 2, { 2, 0, 0, 4 } }; + auto const r = 1.0 / m; + static_assert( std::is_same_v< std::remove_cvref_t< decltype( r ) >, mat > ); + REQUIRE( r[0][0] == 0.5 ); + REQUIRE( r[0][1] == 0.0 ); + REQUIRE( r[1][1] == 0.25 ); + mat const d{ 2, 2, { 2.0, 0.0, 0.0, 4.0 } }; + auto const q = 2 / d; + REQUIRE( q[0][0] == 1.0 ); + REQUIRE( q[1][1] == 0.5 ); + } + + SECTION( "compound assignment keeps T, the scalar converted as by static_cast" ) + { + mat m{ 1, 2, { 3, 5 } }; + m *= 0.5; + // 0.5 becomes static_cast( 0.5 ) == 0 before the multiplication. + REQUIRE( m[0][0] == 0 ); + REQUIRE( m[0][1] == 0 ); + mat n{ 1, 2, { 3, 5 } }; + n *= 2.9; + REQUIRE( n[0][0] == 6 ); + REQUIRE( n[0][1] == 10 ); + mat f{ 1, 1, { 1.0f } }; + f += 2.0; + REQUIRE( f[0][0] == 3.0f ); + } + + SECTION( "the result allocator is A rebound to the result type" ) + { + using alloc_i = s3_alloc::tracking_allocator< int, false, false, false >; + using alloc_d = s3_alloc::tracking_allocator< double, false, false, false >; + s3_alloc::reset(); + { + feng::matrix< int, alloc_i > const m{ alloc_i{ 7 }, 1, 2 }; + auto const r = m * 0.5; + static_assert( std::is_same_v< std::remove_cvref_t< decltype( r ) >, feng::matrix< double, alloc_d > > ); + REQUIRE( r.get_allocator().id == 7 ); + auto const l = 2.0 + m; + REQUIRE( l.get_allocator().id == 7 ); + auto const k = m + 1; + static_assert( std::is_same_v< std::remove_cvref_t< decltype( k ) >, feng::matrix< int, alloc_i > > ); + REQUIRE( k.get_allocator().id == 7 ); + } + s3_alloc::require_balanced(); + } +} + +TEST_CASE( "S6-R5 mixed matrix result types", "[S6][S6-R5]" ) +{ + using namespace s6_r5; + + SECTION( "int + double gives double" ) + { + mat const a{ 1, 2, { 1, 2 } }; + mat const b{ 1, 2, { 0.5, 0.25 } }; + auto const r = a + b; + static_assert( std::is_same_v< std::remove_cvref_t< decltype( r ) >, mat > ); + REQUIRE( r[0][0] == 1.5 ); + REQUIRE( r[0][1] == 2.25 ); + auto const s = b - a; + REQUIRE( s[0][0] == -0.5 ); + REQUIRE( s[0][1] == -1.75 ); + } + + SECTION( "double + complex gives complex" ) + { + mat const a{ 1, 1, { 1.0 } }; + mat const b{ 1, 1, { cf( 0.5f, 2.0f ) } }; + auto const r = a + b; + static_assert( std::is_same_v< std::remove_cvref_t< decltype( r ) >, mat > ); + REQUIRE( r[0][0] == cd( 1.5, 2.0 ) ); + } + + SECTION( "product and division of mixed types" ) + { + mat const a{ 1, 2, { 1, 2 } }; + mat const b{ 2, 1, { 0.5, 0.25 } }; + auto const p = a * b; + static_assert( std::is_same_v< std::remove_cvref_t< decltype( p ) >, mat > ); + REQUIRE( p.row() == 1 ); + REQUIRE( p.col() == 1 ); + REQUIRE( p[0][0] == 1.0 ); + mat const c{ 1, 2, { 1, 2 } }; + mat const d{ 2, 2, { 2.0, 0.0, 0.0, 4.0 } }; + auto const q = c / d; + static_assert( std::is_same_v< std::remove_cvref_t< decltype( q ) >, mat > ); + REQUIRE( q[0][0] == 0.5 ); + REQUIRE( q[0][1] == 0.5 ); + } + + SECTION( "same-type results are unchanged" ) + { + mat const a{ 1, 2, { 1, 2 } }; + mat const b{ 1, 2, { 3, 4 } }; + auto const r = a + b; + static_assert( std::is_same_v< std::remove_cvref_t< decltype( r ) >, mat > ); + REQUIRE( r[0][0] == 4 ); + REQUIRE( r[0][1] == 6 ); + } + + SECTION( "compound assignment with another element type keeps T" ) + { + mat a{ 1, 2, { 1, 2 } }; + mat const b{ 1, 2, { 0.5, 1.5 } }; + a += b; + // each element of b becomes static_cast first: 0 and 1. + REQUIRE( a[0][0] == 1 ); + REQUIRE( a[0][1] == 3 ); + mat c{ 1, 2, { 1.0, 2.0 } }; + mat const d{ 1, 2, { 1, 1 } }; + c -= d; + REQUIRE( c[0][0] == 0.0 ); + REQUIRE( c[0][1] == 1.0 ); + } + + SECTION( "the result allocator is the left allocator rebound" ) + { + using alloc_i = s3_alloc::tracking_allocator< int, false, false, false >; + using alloc_d = s3_alloc::tracking_allocator< double, false, false, false >; + s3_alloc::reset(); + { + feng::matrix< int, alloc_i > const a{ alloc_i{ 9 }, 1, 2 }; + mat const b{ 1, 2, { 0.5, 0.5 } }; + auto const r = a + b; + static_assert( std::is_same_v< std::remove_cvref_t< decltype( r ) >, feng::matrix< double, alloc_d > > ); + REQUIRE( r.get_allocator().id == 9 ); + REQUIRE( r[0][0] == 0.5 ); + feng::matrix< int, alloc_i > const c{ alloc_i{ 9 }, 1, 2, 1 }; + auto const s = c + a; + static_assert( std::is_same_v< std::remove_cvref_t< decltype( s ) >, feng::matrix< int, alloc_i > > ); + REQUIRE( s.get_allocator().id == 9 ); + } + s3_alloc::require_balanced(); + } +} diff --git a/tests/cases/s7_r1.hpp b/tests/cases/s7_r1.hpp new file mode 100644 index 0000000..72fc102 --- /dev/null +++ b/tests/cases/s7_r1.hpp @@ -0,0 +1,331 @@ +// S7-R1 (PR-10, PR-2): one partial-pivoting LU object; legacy lu_decomposition and lu_solver rest on it (F13). +// The helpers in namespace s7 are shared with tests/cases/s7_r2.hpp (included after this file). +#include +#include +#include +#include +#include +#include +#include +#include + +#include "./s2_death.hpp" + +namespace s7 +{ + template< typename T > struct real_of { using type = T; }; + template< typename T > struct real_of< std::complex< T > > { using type = T; }; + template< typename T > using real_t = typename real_of< T >::type; + + template< typename T > real_t< T > eps() { return std::numeric_limits< real_t< T > >::epsilon(); } + + template< typename T > using mat = feng::matrix< T >; + + // ∞-norm: largest absolute row sum + template< typename T > + real_t< T > norm_inf( mat< T > const& m ) + { + real_t< T > best{ 0 }; + for ( std::size_t r = 0; r != m.row(); ++r ) + { + real_t< T > s{ 0 }; + for ( std::size_t c = 0; c != m.col(); ++c ) s += std::abs( m[r][c] ); + best = s > best ? s : best; + } + return best; + } + + template< typename T > + mat< T > from_real( std::size_t r, std::size_t c, std::vector< double > const& v ) + { + mat< T > m{ r, c }; + for ( std::size_t i = 0; i != r * c; ++i ) m[i / c][i % c] = static_cast< T >( static_cast< real_t< T > >( v[i] ) ); + return m; + } + + template< typename T > + mat< T > hilbert( std::size_t n ) + { + mat< T > m{ n, n }; + for ( std::size_t i = 0; i != n; ++i ) + for ( std::size_t j = 0; j != n; ++j ) + m[i][j] = T( real_t< T >( 1 ) / real_t< T >( i + j + 1 ) ); + return m; + } + + template< typename T > + mat< T > random_square( std::size_t n, std::uint_least64_t seed ) + { + std::mt19937_64 g{ seed }; + return feng::random< T >( n, n, g ); + } + + // the R09 inputs: identity, diagonal, row swap, 4×4 block exchange, random n = 1..12, Hilbert 8 and the + // near-singular 2×2 + template< typename T > + std::vector< std::pair< std::string, mat< T > > > r09_inputs() + { + std::vector< std::pair< std::string, mat< T > > > v; + v.emplace_back( "identity 5", feng::eye< T >( 5, 5 ) ); + v.emplace_back( "diagonal", from_real< T >( 3, 3, { 2, 0, 0, 0, -3, 0, 0, 0, 0.5 } ) ); + v.emplace_back( "row swap", from_real< T >( 3, 3, { 0, 1, 0, 1, 0, 0, 0, 0, 1 } ) ); + v.emplace_back( "block exchange", from_real< T >( 4, 4, { 0, 0, 1, 2, 0, 0, 3, 4, 5, 6, 0, 0, 7, 8, 0, 0 } ) ); + for ( std::size_t n = 1; n <= 12; ++n ) + v.emplace_back( "random " + std::to_string( n ), random_square< T >( n, 0x5701ULL + n ) ); + if constexpr ( sizeof( real_t< T > ) >= sizeof( double ) ) + { + v.emplace_back( "hilbert 8", hilbert< T >( 8 ) ); + v.emplace_back( "near singular 2x2", from_real< T >( 2, 2, { 1, 1, 1, 1 + 1e-10 } ) ); + } + else + { + // float: Hilbert 8 and 1 + 1e-10 are singular at ε = 1.2e-7 (float_singular_inputs); these keep the + // near-singular shape at float precision + v.emplace_back( "hilbert 4", hilbert< T >( 4 ) ); + v.emplace_back( "near singular 2x2", from_real< T >( 2, 2, { 1, 1, 1, 1 + 1e-3 } ) ); + } + return v; + } + + // the double near-singular inputs read singular in float (D-027 tolerance n·ε·max|U|), with the rank shown + template< typename T > + std::vector< std::pair< mat< T >, std::size_t > > float_singular_inputs() + { + return { { hilbert< T >( 8 ), 6 }, { from_real< T >( 2, 2, { 1, 1, 1, 1 + 1e-10 } ), 1 } }; + } + + template< typename T > + mat< T > permute_rows( mat< T > const& a, std::vector< std::size_t > const& piv ) + { + mat< T > pa{ a.row(), a.col() }; + for ( std::size_t i = 0; i != a.row(); ++i ) + for ( std::size_t j = 0; j != a.col(); ++j ) pa[i][j] = a[piv[i]][j]; + return pa; + } + + template< typename T > + void check_lu_reconstructs() + { + for ( auto const& [name, a] : r09_inputs< T >() ) + { + INFO( name ); + std::size_t const n = a.row(); + auto const f = feng::lu_factor( a ); + REQUIRE( f.status() == feng::linalg_status::ok ); + REQUIRE( f.rank() == n ); + auto const& piv = f.pivots(); + REQUIRE( piv.size() == n ); + mat< T > const l = f.l(), u = f.u(), p = f.p(); + REQUIRE( l.row() == n ); REQUIRE( l.col() == n ); + REQUIRE( u.row() == n ); REQUIRE( u.col() == n ); + for ( std::size_t i = 0; i != n; ++i ) + { + REQUIRE( l[i][i] == T( 1 ) ); + for ( std::size_t j = i + 1; j != n; ++j ) { REQUIRE( l[i][j] == T( 0 ) ); REQUIRE( u[j][i] == T( 0 ) ); } + for ( std::size_t j = 0; j != i; ++j ) REQUIRE( std::abs( l[i][j] ) <= real_t< T >( 1 ) ); // partial pivoting + } + mat< T > const pa = permute_rows( a, piv ); + REQUIRE( norm_inf< T >( p * a - pa ) == real_t< T >( 0 ) ); + real_t< T > const res = norm_inf< T >( pa - l * u ); + REQUIRE( res <= real_t< T >( 4 ) * real_t< T >( n ) * eps< T >() * norm_inf( a ) ); + } + if constexpr ( sizeof( real_t< T > ) < sizeof( double ) ) + for ( auto const& [a, rank] : float_singular_inputs< T >() ) + { + std::size_t const n = a.row(); + auto const f = feng::lu_factor( a ); + REQUIRE( f.status() == feng::linalg_status::singular ); + REQUIRE( f.rank() == rank ); + REQUIRE( norm_inf< T >( permute_rows( a, f.pivots() ) - f.l() * f.u() ) <= real_t< T >( 4 ) * real_t< T >( n ) * eps< T >() * norm_inf( a ) ); + } + } + + // ‖AX − B‖ / (‖A‖‖X‖ + ‖B‖) / (n·ε) + template< typename T > + real_t< T > solve_ratio( mat< T > const& a, mat< T > const& x, mat< T > const& b ) + { + real_t< T > const n = static_cast< real_t< T > >( a.row() ); + return norm_inf< T >( a * x - b ) / ( norm_inf( a ) * norm_inf( x ) + norm_inf( b ) ) / ( n * eps< T >() ); + } +} + +TEST_CASE( "S7-R1 pivoted LU reconstructs P·A on the R09 inputs", "[S7][S7-R1]" ) +{ + s7::check_lu_reconstructs< double >(); + s7::check_lu_reconstructs< std::complex< double > >(); + s7::check_lu_reconstructs< float >(); + s7::check_lu_reconstructs< std::complex< float > >(); +} + +namespace s7 +{ + template< typename T > + void check_pivot_rules() + { + // the first entry of largest |·| is the pivot: rows 1 and 2 tie at 3 in column 0 + auto const a = from_real< T >( 3, 3, { 1, 2, 3, 3, 1, 1, -3, 4, 2 } ); + auto const f = feng::lu_factor( a ); + REQUIRE( f.pivots()[0] == 1 ); + // a zero column skips elimination and the factorization reports singular, rank 2 + auto const z = from_real< T >( 3, 3, { 0, 1, 2, 0, 3, 4, 0, 5, 7 } ); + auto const fz = feng::lu_factor( z ); + REQUIRE( fz.status() == feng::linalg_status::singular ); + REQUIRE( fz.rank() == 2 ); + REQUIRE( norm_inf< T >( permute_rows( z, fz.pivots() ) - fz.l() * fz.u() ) <= real_t< T >( 12 ) * eps< T >() * norm_inf( z ) ); + // [[1, 2, 3], [4, 5, 6], [7, 8, 9]] is singular + auto const s = from_real< T >( 3, 3, { 1, 2, 3, 4, 5, 6, 7, 8, 9 } ); + REQUIRE( feng::lu_factor( s ).status() == feng::linalg_status::singular ); + REQUIRE( feng::lu_factor( s ).rank() == 2 ); + // nonfinite input + auto nf = feng::eye< T >( 3, 3 ); + nf[1][2] = T( std::numeric_limits< real_t< T > >::quiet_NaN() ); + REQUIRE( feng::lu_factor( nf ).status() == feng::linalg_status::nonfinite ); + nf[1][2] = T( std::numeric_limits< real_t< T > >::infinity() ); + REQUIRE( feng::lu_factor( nf ).status() == feng::linalg_status::nonfinite ); + // 0×0 is a valid, ok factorization + auto const e = feng::lu_factor( mat< T >{} ); + REQUIRE( e.status() == feng::linalg_status::ok ); + REQUIRE( e.rank() == 0 ); + } + + template< typename T > + void check_solve() + { + real_t< T > worst{ 0 }; + for ( auto const& [name, a] : r09_inputs< T >() ) + { + INFO( name ); + std::size_t const n = a.row(); + for ( std::size_t k : { std::size_t{ 1 }, std::size_t{ 3 } } ) + { + std::mt19937_64 g{ 0x57B0ULL + n * 7 + k }; + mat< T > const b = feng::random< T >( n, k, g ); + auto const r = feng::lu_factor( a ).solve( b ); + REQUIRE( r.ok() ); + REQUIRE( static_cast< bool >( r ) ); + REQUIRE( r.status == feng::linalg_status::ok ); + REQUIRE( r.value.row() == n ); REQUIRE( r.value.col() == k ); + real_t< T > const ratio = solve_ratio( a, r.value, b ); + worst = ratio > worst ? ratio : worst; + REQUIRE( ratio <= real_t< T >( 16 ) ); + auto const r2 = feng::solve( a, b ); + REQUIRE( r2.ok() ); + REQUIRE( norm_inf< T >( r2.value - r.value ) == real_t< T >( 0 ) ); + } + } + INFO( "worst solve residual ratio " << worst ); + // singular: status singular and an empty value + auto const s = from_real< T >( 3, 3, { 1, 2, 3, 4, 5, 6, 7, 8, 9 } ); + auto const b = from_real< T >( 3, 1, { 1, 1, 1 } ); + auto const r = feng::solve( s, b ); + REQUIRE( !r.ok() ); + REQUIRE( !static_cast< bool >( r ) ); + REQUIRE( r.status == feng::linalg_status::singular ); + REQUIRE( r.value.size() == 0 ); + } + + template< typename T > + void check_legacy() + { + for ( auto const& [name, a] : r09_inputs< T >() ) + { + INFO( name ); + std::size_t const n = a.row(); + mat< T > l, u; + REQUIRE( feng::lu_decomposition( a, l, u ) == 0 ); + // MATLAB's two-output form: L = Pᵀ·L₀ is a permuted unit lower triangle, A = L·U + auto const f = feng::lu_factor( a ); + REQUIRE( norm_inf< T >( l - f.p().transpose() * f.l() ) == real_t< T >( 0 ) ); + REQUIRE( norm_inf< T >( u - f.u() ) == real_t< T >( 0 ) ); + REQUIRE( norm_inf< T >( a - l * u ) <= real_t< T >( 4 ) * real_t< T >( n ) * eps< T >() * norm_inf( a ) ); + auto const lu = feng::lu_decomposition( a ); + REQUIRE( lu.has_value() ); + REQUIRE( norm_inf< T >( std::get< 0 >( *lu ) - l ) == real_t< T >( 0 ) ); + REQUIRE( norm_inf< T >( std::get< 1 >( *lu ) - u ) == real_t< T >( 0 ) ); + + std::mt19937_64 g{ 0x57C0ULL + n }; + mat< T > const b = feng::random< T >( n, 1, g ); + mat< T > x; + REQUIRE( feng::lu_solver( a, x, b ) == 0 ); + REQUIRE( solve_ratio( a, x, b ) <= real_t< T >( 16 ) ); + auto const ox = feng::lu_solver( a, b ); + REQUIRE( ox.has_value() ); + REQUIRE( norm_inf< T >( *ox - x ) == real_t< T >( 0 ) ); + } + // not ok: 1 / nullopt, outputs unchanged + for ( auto const& s : { from_real< T >( 3, 3, { 1, 2, 3, 4, 5, 6, 7, 8, 9 } ), + from_real< T >( 2, 2, { 1, std::numeric_limits< double >::quiet_NaN(), 0, 1 } ) } ) + { + mat< T > l{ 1, 1, T( 7 ) }, u{ 1, 2, T( 9 ) }; + REQUIRE( feng::lu_decomposition( s, l, u ) == 1 ); + REQUIRE( l.row() == 1 ); REQUIRE( l.col() == 1 ); REQUIRE( l[0][0] == T( 7 ) ); + REQUIRE( u.row() == 1 ); REQUIRE( u.col() == 2 ); REQUIRE( u[0][1] == T( 9 ) ); + REQUIRE( !feng::lu_decomposition( s ).has_value() ); + mat< T > const b{ s.row(), 1, T( 1 ) }; + mat< T > x{ 2, 2, T( 5 ) }; + REQUIRE( feng::lu_solver( s, x, b ) == 1 ); + REQUIRE( x.row() == 2 ); REQUIRE( x.col() == 2 ); REQUIRE( x[1][1] == T( 5 ) ); + REQUIRE( !feng::lu_solver( s, b ).has_value() ); + } + } +} + +TEST_CASE( "S7-R1 pivot choice, rank and status", "[S7][S7-R1]" ) +{ + s7::check_pivot_rules< double >(); + s7::check_pivot_rules< std::complex< double > >(); + s7::check_pivot_rules< float >(); + s7::check_pivot_rules< std::complex< float > >(); +} + +TEST_CASE( "S7-R1 solve meets the residual bound c = 16", "[S7][S7-R1]" ) +{ + s7::check_solve< double >(); + s7::check_solve< std::complex< double > >(); + s7::check_solve< float >(); + s7::check_solve< std::complex< float > >(); +} + +TEST_CASE( "S7-R1 legacy lu_decomposition and lu_solver rest on the LU object", "[S7][S7-R1]" ) +{ + s7::check_legacy< double >(); + s7::check_legacy< std::complex< double > >(); + s7::check_legacy< float >(); + s7::check_legacy< std::complex< float > >(); +} + +namespace s7 +{ + // the callable aborts (SIGABRT) with exactly one contract-violation line on stderr naming `expected` + template< typename Callable > + void require_one_message_death( Callable&& fn, std::string const& expected ) + { + s2_death::outcome const out = s2_death::run( std::forward< Callable >( fn ) ); + INFO( "child stderr: " << out.err ); + REQUIRE( out.signaled ); + REQUIRE( out.signal == SIGABRT ); + REQUIRE( s2_death::line_count( out.err ) == 1 ); + REQUIRE( s2_death::contains( out.err, "contract violation" ) ); + REQUIRE( s2_death::contains( out.err, expected ) ); + } +} + +TEST_CASE( "S7-R1 contract violations abort", "[S7][S7-R1]" ) +{ + feng::matrix< double > const a{ 2, 3, 1.0 }; + feng::matrix< double > const sq = feng::eye< double >( 3, 3 ); + feng::matrix< double > const b{ 2, 1, 1.0 }; + s7::require_one_message_death( [&] { auto f = feng::lu_factor( a ); (void)f; }, "feng::lu_factor: expecting a square matrix" ); + s7::require_one_message_death( [&] { auto d = a.det(); (void)d; }, "matrix::det: expecting a square matrix" ); + s7::require_one_message_death( [&] { auto d = feng::det( a ); (void)d; }, "expecting a square matrix" ); + s7::require_one_message_death( [&] { auto x = a.inverse(); (void)x; }, "matrix::inverse: expecting a square matrix" ); + s7::require_one_message_death( [&] { auto x = feng::inverse( a ); (void)x; }, "expecting a square matrix" ); + s7::require_one_message_death( [&] { feng::matrix< double > out; auto st = feng::inverse( a, out ); (void)st; }, "feng::inverse: expecting a square matrix" ); + s7::require_one_message_death( [&] { auto r = feng::lu_factor( sq ).solve( b ); (void)r; }, "feng::lu_factorization::solve: B must have 3 rows" ); + s7::require_one_message_death( [&] { auto r = feng::solve( sq, b ); (void)r; }, "feng::solve: expecting a square A and B with as many rows" ); + std::complex< float > const one{ 1.0f, 0.0f }; + feng::matrix< std::complex< float > > const ca{ 3, 2, one }; + s7::require_one_message_death( [&] { auto f = feng::lu_factor( ca ); (void)f; }, "feng::lu_factor: expecting a square matrix" ); + s7::require_one_message_death( [&] { auto d = ca.det(); (void)d; }, "matrix::det: expecting a square matrix" ); + s7::require_one_message_death( [&] { auto x = ca.inverse(); (void)x; }, "matrix::inverse: expecting a square matrix" ); +} diff --git a/tests/cases/s7_r2.hpp b/tests/cases/s7_r2.hpp new file mode 100644 index 0000000..b554c62 --- /dev/null +++ b/tests/cases/s7_r2.hpp @@ -0,0 +1,120 @@ +// S7-R2 (PR-10, PR-2): det and inverse from the LU factors (F13). Uses the helpers of tests/cases/s7_r1.hpp. +namespace s7 +{ + template< typename T > + void check_det() + { + using R = real_t< T >; + // block exchange: 4 within 8·n·ε·|4| + auto const bx = from_real< T >( 4, 4, { 0, 0, 1, 2, 0, 0, 3, 4, 5, 6, 0, 0, 7, 8, 0, 0 } ); + REQUIRE( std::abs( bx.det() - T( 4 ) ) <= R( 8 ) * R( 4 ) * eps< T >() * R( 4 ) ); + REQUIRE( std::abs( feng::det( bx ) - T( 4 ) ) <= R( 8 ) * R( 4 ) * eps< T >() * R( 4 ) ); + REQUIRE( std::abs( feng::lu_factor( bx ).det() - T( 4 ) ) <= R( 8 ) * R( 4 ) * eps< T >() * R( 4 ) ); + // exactly 0 when rank < n: [[1..9]], a zero row, a repeated column + for ( auto const& s : { from_real< T >( 3, 3, { 1, 2, 3, 4, 5, 6, 7, 8, 9 } ), + from_real< T >( 3, 3, { 1, 2, 3, 0, 0, 0, 4, 5, 6 } ), + from_real< T >( 3, 3, { 0.1, 2, 0.1, 0.3, 4, 0.3, 0.7, 6, 0.7 } ) } ) + { + REQUIRE( s.det() == T( 0 ) ); + REQUIRE( feng::det( s ) == T( 0 ) ); + } + // 1 for 0×0 (numpy) + REQUIRE( mat< T >{}.det() == T( 1 ) ); + REQUIRE( feng::det( mat< T >{} ) == T( 1 ) ); + // identity, diagonal and row swap are exact + REQUIRE( feng::eye< T >( 6, 6 ).det() == T( 1 ) ); + REQUIRE( from_real< T >( 3, 3, { 2, 0, 0, 0, -3, 0, 0, 0, 0.5 } ).det() == T( -3 ) ); + REQUIRE( from_real< T >( 3, 3, { 0, 1, 0, 1, 0, 0, 0, 0, 1 } ).det() == T( -1 ) ); + } + + template< typename T > + void check_det_product() + { + using R = real_t< T >; + for ( std::size_t n = 1; n <= 12; ++n ) + { + INFO( "n = " << n ); + // random entries in (0, 1) plus n·I: the bound has no κ factor, and the det error of plain random + // (0, 1) fixtures grows with κ (measured up to 272·n·ε at n = 11) + mat< T > const shift = feng::eye< T >( n, n ) * T( R( n ) ); + mat< T > const a = random_square< T >( n, 0x57D0ULL + n ) + shift; + mat< T > const b = random_square< T >( n, 0x57E0ULL + n ) + shift; + T const dab = ( a * b ).det(), da = a.det(), db = b.det(); + REQUIRE( std::abs( dab - da * db ) <= R( 64 ) * R( n ) * eps< T >() * std::abs( da * db ) ); + } + } + + template< typename T > + void check_inverse() + { + using R = real_t< T >; + for ( auto const& [name, a] : r09_inputs< T >() ) + { + INFO( name ); + std::size_t const n = a.row(); + mat< T > const x = a.inverse(); + REQUIRE( x.row() == n ); REQUIRE( x.col() == n ); + R const kappa = norm_inf( a ) * norm_inf( x ); + R const ratio = norm_inf< T >( a * x - feng::eye< T >( n, n ) ) / ( R( n ) * eps< T >() * kappa ); + REQUIRE( ratio <= R( 16 ) ); + REQUIRE( norm_inf< T >( feng::inverse( a ) - x ) == R( 0 ) ); + REQUIRE( norm_inf< T >( feng::inv( a ) - x ) == R( 0 ) ); + auto const t = feng::try_inverse( a ); + REQUIRE( t.ok() ); + REQUIRE( norm_inf< T >( t.value - x ) == R( 0 ) ); + auto const f = feng::lu_factor( a ).inverse(); + REQUIRE( f.ok() ); + REQUIRE( norm_inf< T >( f.value - x ) == R( 0 ) ); + mat< T > out; + REQUIRE( feng::inverse( a, out ) == feng::linalg_status::ok ); + REQUIRE( norm_inf< T >( out - x ) == R( 0 ) ); + } + // singular and nonfinite: statuses, out unchanged, value-returning forms give 0×0 (D-026) + auto nf = feng::eye< T >( 3, 3 ); + nf[0][1] = T( std::numeric_limits< R >::infinity() ); + std::pair< mat< T >, feng::linalg_status > const bad[] = { + { from_real< T >( 3, 3, { 1, 2, 3, 4, 5, 6, 7, 8, 9 } ), feng::linalg_status::singular }, + { from_real< T >( 2, 2, { 0, 0, 0, 0 } ), feng::linalg_status::singular }, + { nf, feng::linalg_status::nonfinite } }; + for ( auto const& [s, st] : bad ) + { + REQUIRE( s.inverse().size() == 0 ); + REQUIRE( s.inverse().row() == 0 ); + REQUIRE( feng::inverse( s ).size() == 0 ); + REQUIRE( feng::inv( s ).size() == 0 ); + auto const t = feng::try_inverse( s ); + REQUIRE( t.status == st ); + REQUIRE( t.value.size() == 0 ); + REQUIRE( feng::lu_factor( s ).inverse().status == st ); + mat< T > out{ 1, 2, T( 3 ) }; + REQUIRE( feng::inverse( s, out ) == st ); + REQUIRE( out.row() == 1 ); REQUIRE( out.col() == 2 ); REQUIRE( out[0][1] == T( 3 ) ); + } + // 0×0 inverts to 0×0 with status ok + REQUIRE( feng::try_inverse( mat< T >{} ).ok() ); + } +} + +TEST_CASE( "S7-R2 det of the block-exchange and singular matrices", "[S7][S7-R2]" ) +{ + s7::check_det< double >(); + s7::check_det< std::complex< double > >(); + s7::check_det< float >(); + s7::check_det< std::complex< float > >(); +} + +TEST_CASE( "S7-R2 det of a product is the product of dets", "[S7][S7-R2]" ) +{ + s7::check_det_product< double >(); + s7::check_det_product< std::complex< double > >(); + s7::check_det_product< float >(); + s7::check_det_product< std::complex< float > >(); +} + +TEST_CASE( "S7-R2 inverse from the factors and its failure statuses", "[S7][S7-R2]" ) +{ + s7::check_inverse< double >(); + s7::check_inverse< std::complex< double > >(); + s7::check_inverse< float >(); + s7::check_inverse< std::complex< float > >(); +} diff --git a/tests/cases/s7_r3.hpp b/tests/cases/s7_r3.hpp new file mode 100644 index 0000000..4916941 --- /dev/null +++ b/tests/cases/s7_r3.hpp @@ -0,0 +1,434 @@ +// S7-R3 (PR-10, PR-2): one-sided Jacobi SVD for real and complex inputs of every shape, and one pseudoinverse +// with a relative cutoff behind pinverse, pinv and svd_inverse (F13, D-026, D-028). Uses the helpers of +// tests/cases/s7_r1.hpp (namespace s7). Set S7_RATIOS=1 to print the worst measured error/tolerance ratios. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace s7r3 +{ + using s7::mat; + using s7::real_t; + using s7::eps; + + template< typename T > constexpr bool is_cplx = !std::is_same_v< T, real_t< T > >; + + // worst error/tolerance ratio per check, printed by the last case when S7_RATIOS is set + inline double& worst( int i ) + { + static double w[8] = {}; + return w[i]; + } + inline char const* const worst_name[8] = { "reconstruction", "U^H U = I", "V^H V = I", "singular values", + "AXA = A", "XAX = X", "(AX)^H = AX", "(XA)^H = XA" }; + inline void track( int i, double err, double tol ) + { + double const r = tol > 0 ? err / tol : ( err > 0 ? 1e300 : 0.0 ); + worst( i ) = std::max( worst( i ), r ); + } + + template< typename T > + real_t< T > frob( mat< T > const& m ) + { + real_t< T > s{ 0 }; + for ( auto const& x : m ) s += std::norm( x ); + return std::sqrt( s ); + } + + template< typename T > + T cj( T const& x ) + { + if constexpr ( is_cplx< T > ) return std::conj( x ); + else return x; + } + + // conjugate transpose, written out here so the test does not lean on the library under test + template< typename T > + mat< T > ct( mat< T > const& m ) + { + mat< T > r{ m.col(), m.row() }; + for ( std::size_t i = 0; i != m.row(); ++i ) + for ( std::size_t j = 0; j != m.col(); ++j ) r[j][i] = cj( m[i][j] ); + return r; + } + + template< typename T > + mat< T > eye( std::size_t n ) + { + mat< T > r{ n, n }; + std::fill( r.begin(), r.end(), T( 0 ) ); + for ( std::size_t i = 0; i != n; ++i ) r[i][i] = T( 1 ); + return r; + } + + // a unitary n×n matrix: a product of 3·n² seeded Givens rotations (with random phases for complex T) + template< typename T > + mat< T > givens_unitary( std::size_t n, std::uint_least64_t seed ) + { + using R = real_t< T >; + mat< T > q = eye< T >( n ); + if ( n < 2 ) return q; + std::mt19937_64 g{ seed }; + std::size_t const count = 3 * n * n; + auto const draws = feng::random< double >( count, 4, g ); + R const two_pi = R( 6.283185307179586476925286766559 ); + for ( std::size_t k = 0; k != count; ++k ) + { + std::size_t const i = static_cast< std::size_t >( draws[k][0] * n ) % n; + std::size_t j = static_cast< std::size_t >( draws[k][1] * ( n - 1 ) ) % ( n - 1 ); + if ( j >= i ) ++j; + R const th = two_pi * R( draws[k][2] ); + R const c = std::cos( th ), s = std::sin( th ); + T ph = T( 1 ); + if constexpr ( is_cplx< T > ) ph = std::polar( R( 1 ), two_pi * R( draws[k][3] ) ); + for ( std::size_t r = 0; r != n; ++r ) // q ← q·G on columns i, j + { + T const x = q[r][i], y = q[r][j]; + q[r][i] = c * x - s * ph * y; + q[r][j] = s * cj( ph ) * x + c * y; + } + } + return q; + } + + // Q1·Σ·Q2ᴴ with the chosen singular values on the diagonal of the m×n Σ + template< typename T > + mat< T > with_singular_values( std::size_t m, std::size_t n, std::vector< double > const& sigma, std::uint_least64_t seed ) + { + mat< T > d{ m, n }; + std::fill( d.begin(), d.end(), T( 0 ) ); + for ( std::size_t i = 0; i != sigma.size(); ++i ) d[i][i] = T( real_t< T >( sigma[i] ) ); + return givens_unitary< T >( m, seed ) * d * ct( givens_unitary< T >( n, seed + 1 ) ); + } + + template< typename T > + mat< T > seeded_random( std::size_t r, std::size_t c, std::uint_least64_t seed ) + { + std::mt19937_64 g{ seed }; + return feng::random< T >( r, c, g ); + } + + template< typename T > + struct fixture + { + std::string name; + mat< T > a; + std::vector< double > sigma; // the known singular values, descending; empty when not known + std::size_t rank; + }; + + template< typename T > + std::vector< fixture< T > > fixtures() + { + std::vector< fixture< T > > v; + std::vector< double > const sq{ 4, 2.5, 1, 0.5, 0.125 }; + std::vector< double > const tw{ 3, 2, 1, 0.25 }; + v.push_back( { "square 5x5", with_singular_values< T >( 5, 5, sq, 0x5731ULL ), sq, 5 } ); + v.push_back( { "tall 7x4", with_singular_values< T >( 7, 4, tw, 0x5732ULL ), tw, 4 } ); + v.push_back( { "wide 4x7", with_singular_values< T >( 4, 7, tw, 0x5733ULL ), tw, 4 } ); + v.push_back( { "rank deficient 6x3 * 3x5", seeded_random< T >( 6, 3, 0x5734ULL ) * seeded_random< T >( 3, 5, 0x5735ULL ), {}, 3 } ); + // diagonal with signs (and a phase for complex): singular values are the sorted |d_ii| + mat< T > d{ 4, 4 }; + std::fill( d.begin(), d.end(), T( 0 ) ); + d[0][0] = T( -2 ); d[1][1] = T( 5 ); d[2][2] = T( 0.5 ); d[3][3] = T( 3 ); + if constexpr ( is_cplx< T > ) d[2][2] = T( 0.3, -0.4 ); + v.push_back( { "diagonal 4x4", d, { 5, 3, 2, 0.5 }, 4 } ); + // an exact zero column and an exact zero singular value + mat< T > z{ 3, 3 }; + std::fill( z.begin(), z.end(), T( 0 ) ); + z[0][0] = T( 3 ); z[2][2] = T( 1 ); + v.push_back( { "zero column 3x3", z, { 3, 1, 0 }, 2 } ); + v.push_back( { "rank deficient wide 3x6", with_singular_values< T >( 3, 6, { 2, 1, 0 }, 0x5736ULL ), {}, 2 } ); + return v; + } + + // the fixtures at T's precision: float and complex inputs are built in double and rounded once, so a + // rank-deficient input carries only the ε/2 rounding noise, well under the p·ε·s_1 cutoff (D-028), instead of + // the Givens-product and matrix-product noise accumulated in float + template< typename T > + std::vector< fixture< T > > fixtures_at() + { + if constexpr ( sizeof( real_t< T > ) >= sizeof( double ) ) return fixtures< T >(); + else + { + using W = std::conditional_t< is_cplx< T >, std::complex< double >, double >; + std::vector< fixture< T > > v; + for ( auto const& fx : fixtures< W >() ) v.push_back( { fx.name, fx.a.template astype< T >(), fx.sigma, fx.rank } ); + return v; + } + } + + template< typename T > + void check_factorization( fixture< T > const& fx ) + { + using R = real_t< T >; + INFO( fx.name ); + mat< T > const& a = fx.a; + std::size_t const m = a.row(), n = a.col(), k = std::min( m, n ), p = std::max( m, n ); + auto const f = feng::svd_factor( a ); + REQUIRE( f.status() == feng::linalg_status::ok ); + REQUIRE( f.sweeps() >= 1 ); + REQUIRE( f.u().row() == m ); REQUIRE( f.u().col() == k ); + REQUIRE( f.v().row() == n ); REQUIRE( f.v().col() == k ); + auto const& s = f.s(); + REQUIRE( s.size() == k ); + for ( std::size_t i = 0; i != k; ++i ) + { + REQUIRE( s[i] >= R( 0 ) ); + if ( i ) REQUIRE( s[i] <= s[i - 1] ); + } + mat< T > sd{ k, k }; + std::fill( sd.begin(), sd.end(), T( 0 ) ); + for ( std::size_t i = 0; i != k; ++i ) sd[i][i] = T( s[i] ); + R const pe = R( p ) * eps< T >(); + R const rec = frob< T >( a - f.u() * sd * ct( f.v() ) ); + R const rec_tol = R( 8 ) * pe * frob( a ); + track( 0, rec, rec_tol ); + REQUIRE( rec <= rec_tol ); + R const ou = frob< T >( ct( f.u() ) * f.u() - eye< T >( k ) ); + R const ov = frob< T >( ct( f.v() ) * f.v() - eye< T >( k ) ); + track( 1, ou, R( 8 ) * pe ); + track( 2, ov, R( 8 ) * pe ); + REQUIRE( ou <= R( 8 ) * pe ); + REQUIRE( ov <= R( 8 ) * pe ); + if ( !fx.sigma.empty() ) + for ( std::size_t i = 0; i != k; ++i ) + { + R const err = std::abs( s[i] - R( fx.sigma[i] ) ); + R const tol = R( 8 ) * pe * R( fx.sigma[0] ); + track( 3, err, tol ); + REQUIRE( err <= tol ); + } + REQUIRE( f.rank() == fx.rank ); + } + + // the four Moore–Penrose identities with tolerance 32·p·ε·κ·scale, κ = s_1/s_r over the kept values + template< typename T > + void check_moore_penrose( mat< T > const& a, mat< T > const& x, std::vector< real_t< T > > const& s, std::size_t r ) + { + using R = real_t< T >; + std::size_t const p = std::max( a.row(), a.col() ); + R const kappa = r ? s[0] / s[r - 1] : R( 1 ); + R const tol = R( 32 ) * R( p ) * eps< T >() * kappa; + mat< T > const ax = a * x, xa = x * a; + R const e1 = frob< T >( ax * a - a ), t1 = tol * frob( a ); + R const e2 = frob< T >( xa * x - x ), t2 = tol * frob( x ); + R const e3 = frob< T >( ct( ax ) - ax ); + R const e4 = frob< T >( ct( xa ) - xa ); + track( 4, e1, t1 ); track( 5, e2, t2 ); track( 6, e3, tol ); track( 7, e4, tol ); + REQUIRE( e1 <= t1 ); + REQUIRE( e2 <= t2 ); + REQUIRE( e3 <= tol ); + REQUIRE( e4 <= tol ); + } + + template< typename T > + bool same( mat< T > const& x, mat< T > const& y ) + { + return x.row() == y.row() && x.col() == y.col() && std::equal( x.begin(), x.end(), y.begin() ); + } + + template< typename T > + void check_pinverse() + { + for ( auto const& fx : fixtures_at< T >() ) + { + INFO( fx.name ); + mat< T > const& a = fx.a; + mat< T > x; + REQUIRE( feng::pinverse( a, x ) == feng::linalg_status::ok ); + REQUIRE( x.row() == a.col() ); REQUIRE( x.col() == a.row() ); + auto const f = feng::svd_factor( a ); + std::size_t const r = f.rank(); + REQUIRE( r == fx.rank ); + check_moore_penrose( a, x, f.s(), r ); + // one implementation behind every spelling + REQUIRE( same( feng::pinverse( a ), x ) ); + REQUIRE( same( feng::pinv( a ), x ) ); + REQUIRE( same( feng::svd_inverse( a ), x ) ); + REQUIRE( same( f.pinverse().value, x ) ); + } + } + + template< typename T > + void check_cutoff() + { + using R = real_t< T >; + // diagonal: 1e-20 ≤ 3ε·1 is zeroed, the rest inverted + mat< T > d{ 3, 3 }; + std::fill( d.begin(), d.end(), T( 0 ) ); + d[0][0] = T( 1 ); d[1][1] = T( 0.5 ); d[2][2] = T( R( 1e-20 ) ); + mat< T > x; + REQUIRE( feng::pinverse( d, x ) == feng::linalg_status::ok ); + mat< T > want{ 3, 3 }; + std::fill( want.begin(), want.end(), T( 0 ) ); + want[0][0] = T( 1 ); want[1][1] = T( 2 ); + REQUIRE( frob< T >( x - want ) <= R( 4 ) * eps< T >() ); + REQUIRE( feng::svd_factor( d ).rank() == 2 ); + // rtol = 0 keeps it: the cutoff is the relative rule, nothing else + REQUIRE( feng::pinverse( d, x, R( 0 ) ) == feng::linalg_status::ok ); + REQUIRE( std::abs( x[2][2] - T( R( 1e20 ) ) ) <= R( 4 ) * eps< T >() * R( 1e20 ) ); + // a rotated 5×3 with singular values 1, 0.5, 1e-20 + auto const a = with_singular_values< T >( 5, 3, { 1, 0.5, 1e-20 }, 0x5737ULL ); + auto const f = feng::svd_factor( a ); + REQUIRE( f.ok() ); + REQUIRE( f.rank() == 2 ); + REQUIRE( feng::pinverse( a, x ) == feng::linalg_status::ok ); + check_moore_penrose( a, x, f.s(), 2 ); + REQUIRE( std::abs( frob( x ) - std::sqrt( R( 5 ) ) ) <= R( 32 ) * R( 5 ) * eps< T >() * R( 2 ) * std::sqrt( R( 5 ) ) ); + } + + template< typename T > + void check_legacy_shapes() + { + using R = real_t< T >; + for ( auto [m, n] : { std::pair< std::size_t, std::size_t >{ 7, 4 }, { 4, 7 }, { 5, 5 } } ) + { + INFO( m << "x" << n ); + std::size_t const k = std::min( m, n ); + auto const a = seeded_random< T >( m, n, 0x5738ULL + m ); + mat< T > u, w, v; + REQUIRE( feng::singular_value_decomposition( a, u, w, v ) == 0 ); + REQUIRE( u.row() == m ); REQUIRE( u.col() == k ); + REQUIRE( w.row() == k ); REQUIRE( w.col() == k ); + REQUIRE( v.row() == n ); REQUIRE( v.col() == k ); + for ( std::size_t i = 0; i != k; ++i ) + for ( std::size_t j = 0; j != k; ++j ) + if ( i != j ) REQUIRE( w[i][j] == T( 0 ) ); + REQUIRE( frob< T >( a - u * w * ct( v ) ) <= R( 8 ) * R( std::max( m, n ) ) * eps< T >() * frob( a ) ); + auto const t = feng::singular_value_decomposition( a ); + REQUIRE( t.has_value() ); + REQUIRE( same( std::get< 0 >( *t ), u ) ); + REQUIRE( same( std::get< 1 >( *t ), w ) ); + REQUIRE( same( std::get< 2 >( *t ), v ) ); + auto const t2 = feng::svd( a ); + REQUIRE( t2.has_value() ); + REQUIRE( same( std::get< 1 >( *t2 ), w ) ); + } + } + + template< typename T > + void check_empty() + { + for ( auto [m, n] : { std::pair< std::size_t, std::size_t >{ 0, 3 }, { 3, 0 }, { 0, 0 } } ) + { + INFO( m << "x" << n ); + mat< T > const a{ m, n }; + auto const f = feng::svd_factor( a ); + REQUIRE( f.status() == feng::linalg_status::ok ); + REQUIRE( f.s().empty() ); + REQUIRE( f.u().row() == m ); REQUIRE( f.u().col() == 0 ); + REQUIRE( f.v().row() == n ); REQUIRE( f.v().col() == 0 ); + REQUIRE( f.rank() == 0 ); + mat< T > x; + REQUIRE( feng::pinverse( a, x ) == feng::linalg_status::ok ); + REQUIRE( x.row() == n ); REQUIRE( x.col() == m ); + mat< T > u, w, v; + REQUIRE( feng::singular_value_decomposition( a, u, w, v ) == 0 ); + REQUIRE( u.row() == m ); REQUIRE( w.row() == 0 ); REQUIRE( v.row() == n ); + } + } + + template< typename T > + void check_nonconvergence() + { + auto const a = seeded_random< T >( 8, 8, 0x5739ULL ); + auto const f = feng::svd_factor( a, 1 ); + REQUIRE( f.status() == feng::linalg_status::not_converged ); + REQUIRE( f.sweeps() == 1 ); + auto const r = f.pinverse(); + REQUIRE( r.status == feng::linalg_status::not_converged ); + REQUIRE( r.value.size() == 0 ); + // legacy: 1 and the outputs untouched + mat< T > u{ 2, 2, T( 7 ) }, w{ 1, 3, T( 8 ) }, v{ 3, 1, T( 9 ) }; + mat< T > const u0 = u, w0 = w, v0 = v; + REQUIRE( feng::singular_value_decomposition( a, u, w, v, 1 ) == 1 ); + REQUIRE( same( u, u0 ) ); REQUIRE( same( w, w0 ) ); REQUIRE( same( v, v0 ) ); + // the default sweep limit converges + auto const g = feng::svd_factor( a ); + REQUIRE( g.ok() ); + REQUIRE( g.sweeps() > 1 ); + // a nonfinite input: status, never NaN in an output + mat< T > bad = a; + bad[3][4] = T( std::numeric_limits< real_t< T > >::quiet_NaN() ); + REQUIRE( feng::svd_factor( bad ).status() == feng::linalg_status::nonfinite ); + mat< T > x{ 2, 2, T( 1 ) }; + mat< T > const x0 = x; + REQUIRE( feng::pinverse( bad, x ) == feng::linalg_status::nonfinite ); + REQUIRE( same( x, x0 ) ); + REQUIRE( feng::pinv( bad ).size() == 0 ); + REQUIRE( feng::svd_inverse( bad ).size() == 0 ); + REQUIRE( !feng::svd( bad ).has_value() ); + REQUIRE( feng::singular_value_decomposition( bad, u, w, v ) == 1 ); + REQUIRE( same( u, u0 ) ); + } +} + +TEST_CASE( "S7-R3 svd factors reconstruct with orthonormal U and V and the known singular values", "[S7][S7-R3]" ) +{ + for ( auto const& fx : s7r3::fixtures< double >() ) s7r3::check_factorization( fx ); + for ( auto const& fx : s7r3::fixtures< std::complex< double > >() ) s7r3::check_factorization( fx ); +} + +TEST_CASE( "S7-R3 svd of tall, wide and rank-deficient inputs", "[S7][S7-R3]" ) +{ + // square, tall, wide, diagonal, zero-column and rank-deficient (tall product and wide) inputs in every element + // type; ε is that of the element's real type, ᴴ the conjugate transpose + auto const run = []< typename T >( char const* name, T* ) + { + double saved[8]; + for ( int i = 0; i != 8; ++i ) { saved[i] = s7r3::worst( i ); s7r3::worst( i ) = 0; } + for ( auto const& fx : s7r3::fixtures_at< T >() ) s7r3::check_factorization( fx ); + s7r3::check_pinverse< T >(); + for ( int i = 0; i != 8; ++i ) + { + if ( std::getenv( "S7_RATIOS" ) ) std::printf( "S7-R3 %s worst %s: %.3g of tolerance\n", name, s7r3::worst_name[i], s7r3::worst( i ) ); + s7r3::worst( i ) = std::max( saved[i], s7r3::worst( i ) ); + } + }; + run( "float", static_cast< float* >( nullptr ) ); + run( "double", static_cast< double* >( nullptr ) ); + run( "complex", static_cast< std::complex< float >* >( nullptr ) ); + run( "complex", static_cast< std::complex< double >* >( nullptr ) ); +} + +TEST_CASE( "S7-R3 pseudoinverse meets the Moore-Penrose identities", "[S7][S7-R3]" ) +{ + s7r3::check_pinverse< double >(); + s7r3::check_pinverse< std::complex< double > >(); +} + +TEST_CASE( "S7-R3 pseudoinverse zeroes singular values below the relative cutoff", "[S7][S7-R3]" ) +{ + s7r3::check_cutoff< double >(); + s7r3::check_cutoff< std::complex< double > >(); +} + +TEST_CASE( "S7-R3 legacy singular_value_decomposition returns thin u, diagonal w and v", "[S7][S7-R3]" ) +{ + s7r3::check_legacy_shapes< double >(); + s7r3::check_legacy_shapes< std::complex< double > >(); +} + +TEST_CASE( "S7-R3 svd and pinverse accept 0xn and mx0 inputs", "[S7][S7-R3]" ) +{ + s7r3::check_empty< double >(); + s7r3::check_empty< std::complex< double > >(); +} + +TEST_CASE( "S7-R3 svd reports nonconvergence", "[S7][S7-R3]" ) +{ + s7r3::check_nonconvergence< double >(); + s7r3::check_nonconvergence< std::complex< double > >(); + if ( std::getenv( "S7_RATIOS" ) ) + for ( int i = 0; i != 8; ++i ) std::printf( "S7-R3 worst %s: %.3g of tolerance\n", s7r3::worst_name[i], s7r3::worst( i ) ); +} diff --git a/tests/cases/s7_r4.hpp b/tests/cases/s7_r4.hpp new file mode 100644 index 0000000..99d5971 --- /dev/null +++ b/tests/cases/s7_r4.hpp @@ -0,0 +1,301 @@ +// S7-R4 (PR-10, PR-2): Cholesky with a status and RREF with independent pivot rows and columns (F13, F15). +// Uses the helpers of tests/cases/s7_r1.hpp (namespace s7). The exact RREF references below were derived with +// python3 fractions.Fraction (Gaussian rationals for the complex fixtures) and pasted as literals. +#include +#include +#include +#include +#include +#include + +namespace s7r4 +{ + using s7::mat; + using s7::real_t; + using cd = std::complex< double >; + + template< typename T > + mat< T > from_list( std::size_t r, std::size_t c, std::vector< T > const& v ) + { + mat< T > m{ r, c }; + for ( std::size_t i = 0; i != r * c; ++i ) m[i / c][i % c] = v[i]; + return m; + } + + template< typename T > + T conj_( T const& x ) + { + if constexpr ( std::is_same_v< T, real_t< T > > ) return x; + else return std::conj( x ); + } + + template< typename T > + real_t< T > frob( mat< T > const& m ) + { + real_t< T > s{ 0 }; + for ( auto const& x : m ) s += std::norm( x ); + return std::sqrt( s ); + } + + template< typename T > + bool any_nan( mat< T > const& m ) + { + for ( auto const& x : m ) + if ( std::isnan( std::real( x ) ) || std::isnan( std::imag( x ) ) ) return true; + return false; + } + + struct rref_case + { + std::size_t m, n; + std::vector< double > a, r; + std::vector< std::size_t > pivots; + }; + + inline std::vector< rref_case > real_cases() + { + return { + // square, full rank + { 3, 3, { 2, 1, 1, 1, 3, 2, 1, 0, 0 }, { 1, 0, 0, 0, 1, 0, 0, 0, 1 }, { 0, 1, 2 } }, + // square, rank 2 + { 3, 3, { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, { 1, 0, -1, 0, 1, 2, 0, 0, 0 }, { 0, 1 } }, + // square 4×4, column 1 = 2·column 0 + { 4, 4, { 2, 4, 1, 3, 1, 2, 0, 1, 3, 6, 1, 4, 0, 0, 2, 5 }, { 1, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 }, { 0, 2, 3 } }, + // tall, column 1 = 2·column 0 + { 4, 3, { 1, 2, 1, 2, 4, 0, 3, 6, 1, 1, 2, 2 }, { 1, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, { 0, 2 } }, + // tall, full column rank + { 3, 2, { 1, 2, 3, 4, 5, 7 }, { 1, 0, 0, 1, 0, 0 }, { 0, 1 } }, + // wide with a zero column 0 and column 2 = 2·column 1, rank 2 + { 3, 5, { 0, 1, 2, 1, 3, 0, 2, 4, 1, 1, 0, 3, 6, 2, 4 }, { 0, 1, 2, 0, -2, 0, 0, 0, 1, 5, 0, 0, 0, 0, 0 }, { 1, 3 } }, + // wide, column 2 = 1.6·column 0 − 0.2·column 1 + { 3, 4, { 2, 1, 3, 1, 1, 3, 1, 2, 3, 4, 4, 5 }, { 1, 0, 1.6, 0, 0, 1, -0.2, 0, 0, 0, 0, 1 }, { 0, 1, 3 } }, + // wide, full row rank + { 2, 3, { 1, 2, 3, 4, 5, 6 }, { 1, 0, -1, 0, 1, 2 }, { 0, 1 } }, + }; + } + + template< typename T > + void check_rref( mat< T > const& a, mat< T > const& ref, std::vector< std::size_t > const& pivots ) + { + auto const [m, n] = a.shape(); + real_t< T > const p = static_cast< real_t< T > >( m > n ? m : n ); + real_t< T > const tol = 64 * p * s7::eps< T >() * ( real_t< T >( 1 ) + s7::norm_inf( a ) ); + auto const res = feng::row_echelon( a ); + REQUIRE( res.status == feng::linalg_status::ok ); + REQUIRE( res.rank == pivots.size() ); + REQUIRE( res.pivot_columns == pivots ); + REQUIRE( res.r.row() == m ); + REQUIRE( res.r.col() == n ); + for ( std::size_t i = 0; i != m; ++i ) + for ( std::size_t j = 0; j != n; ++j ) + CHECK( std::abs( res.r[i][j] - ref[i][j] ) <= tol ); + // pivot entries exactly 1, every other entry of a pivot column exactly 0, rows past the rank exactly 0 + for ( std::size_t k = 0; k != pivots.size(); ++k ) + for ( std::size_t i = 0; i != m; ++i ) + CHECK( res.r[i][pivots[k]] == ( i == k ? T( 1 ) : T( 0 ) ) ); + for ( std::size_t i = pivots.size(); i != m; ++i ) + for ( std::size_t j = 0; j != n; ++j ) + CHECK( res.r[i][j] == T( 0 ) ); + // the legacy spellings forward to row_echelon for every shape + auto const r1 = feng::rref( a ); + auto const r2 = feng::gauss_jordan_elimination( a ); + REQUIRE( r1.has_value() ); + REQUIRE( r2.has_value() ); + CHECK( *r1 == res.r ); + CHECK( *r2 == res.r ); + } + + template< typename T > + void check_rref_real_cases() + { + for ( auto const& c : real_cases() ) + check_rref< T >( s7::from_real< T >( c.m, c.n, c.a ), s7::from_real< T >( c.m, c.n, c.r ), c.pivots ); + } +} // namespace s7r4 + +namespace s7r4 +{ + // the complex fixtures as complex literals, rounded to T (every entry is exact in float) + template< typename T > + mat< T > cplx( std::size_t r, std::size_t c, std::vector< cd > const& v ) + { + return from_list< cd >( r, c, v ).template astype< T >(); + } + + template< typename T > + void check_rref_complex_cases() + { + // complex 3×3 of rank 2: row 2 = row 0 + row 1 + check_rref< T >( cplx< T >( 3, 3, { { 1, 1 }, { 2, 0 }, { 0, 1 }, { 0, 2 }, { 2, -2 }, { 1, 0 }, { 1, 3 }, { 4, -2 }, { 1, 1 } } ), + cplx< T >( 3, 3, { 1, 0, { -0.25, 0.25 }, 0, 1, { 0.25, 0.5 }, 0, 0, 0 } ), { 0, 1 } ); + // complex wide with a zero column and a dependent column + check_rref< T >( cplx< T >( 2, 4, { 0, { 1, 1 }, { 2, 0 }, { 3, -1 }, 0, { 2, 2 }, { 4, 0 }, { 1, 0 } } ), + cplx< T >( 2, 4, { 0, 1, { 1, -1 }, 0, 0, 0, 0, 1 } ), { 1, 3 } ); + } + + template< typename T > + void check_rref_degenerate() + { + // the zero matrix: rank 0, unchanged; a 0×0 input + mat< T > const z{ 2, 3, T( 0 ) }; + auto const rz = feng::row_echelon( z ); + CHECK( rz.rank == 0 ); + CHECK( rz.pivot_columns.empty() ); + CHECK( rz.r == z ); + CHECK( feng::row_echelon( mat< T >{} ).rank == 0 ); + + // nonfinite input: nullopt from the legacy spellings, status nonfinite from row_echelon + mat< T > bad = s7::from_real< T >( 2, 2, { 1, 2, 3, 4 } ); + bad[1][0] = T( std::numeric_limits< real_t< T > >::quiet_NaN() ); + CHECK( feng::row_echelon( bad ).status == feng::linalg_status::nonfinite ); + CHECK_FALSE( feng::rref( bad ).has_value() ); + CHECK_FALSE( feng::gauss_jordan_elimination( bad ).has_value() ); + } +} // namespace s7r4 + +TEST_CASE( "S7-R4 rref matches exact references on every shape", "[S7][S7-R4]" ) +{ + // square, tall, wide and rank-deficient fixtures in float, double, complex and complex + s7r4::check_rref_real_cases< float >(); + s7r4::check_rref_real_cases< double >(); + s7r4::check_rref_real_cases< std::complex< float > >(); + s7r4::check_rref_real_cases< std::complex< double > >(); + s7r4::check_rref_complex_cases< std::complex< float > >(); + s7r4::check_rref_complex_cases< std::complex< double > >(); + s7r4::check_rref_degenerate< float >(); + s7r4::check_rref_degenerate< double >(); + s7r4::check_rref_degenerate< std::complex< float > >(); + s7r4::check_rref_degenerate< std::complex< double > >(); +} + +namespace s7r4 +{ + template< typename T > + void check_cholesky_spd( mat< T > const& a ) + { + std::size_t const n = a.row(); + auto const f = feng::cholesky_factor( a ); + REQUIRE( f.status() == feng::linalg_status::ok ); + REQUIRE( f.ok() ); + mat< T > const& l = f.l(); + REQUIRE( l.row() == n ); + REQUIRE( l.col() == n ); + mat< T > llh{ n, n }; + for ( std::size_t i = 0; i != n; ++i ) + { + CHECK( std::imag( l[i][i] ) == 0 ); + CHECK( std::real( l[i][i] ) > 0 ); + for ( std::size_t j = i + 1; j != n; ++j ) CHECK( l[i][j] == T( 0 ) ); + for ( std::size_t j = 0; j != n; ++j ) + { + T s{ 0 }; + for ( std::size_t k = 0; k != n; ++k ) s += l[i][k] * conj_( l[j][k] ); + llh[i][j] = s; + } + } + mat< T > d{ n, n }; + for ( std::size_t i = 0; i != n; ++i ) + for ( std::size_t j = 0; j != n; ++j ) d[i][j] = llh[i][j] - a[i][j]; + CHECK( frob( d ) <= 8 * real_t< T >( n ) * s7::eps< T >() * frob( a ) ); + + mat< T > out; + CHECK( feng::cholesky_decomposition( a, out ) == 0 ); + CHECK( out == l ); + } + + template< typename T > + void check_cholesky_fails( mat< T > const& a ) + { + auto const f = feng::cholesky_factor( a ); + CHECK( f.status() == feng::linalg_status::not_positive_definite ); + CHECK_FALSE( f.ok() ); + CHECK_FALSE( any_nan( f.l() ) ); + mat< T > out = s7::from_real< T >( 1, 2, { 7, 8 } ); + mat< T > const keep = out; + CHECK( feng::cholesky_decomposition( a, out ) == 1 ); + CHECK( out == keep ); + } + + template< typename T > + void check_cholesky_real_cases() + { + check_cholesky_spd< T >( s7::from_real< T >( 3, 3, { 4, 12, -16, 12, 37, -43, -16, -43, 98 } ) ); + check_cholesky_spd< T >( s7::from_real< T >( 1, 1, { 2 } ) ); + // random SPD: Bᵀ·B + n·I + mat< T > const b = s7::random_square< T >( 6, 77 ); + mat< T > spd{ 6, 6 }; + for ( std::size_t i = 0; i != 6; ++i ) + for ( std::size_t j = 0; j != 6; ++j ) + { + T s = i == j ? T( 6 ) : T( 0 ); + for ( std::size_t k = 0; k != 6; ++k ) s += conj_( b[k][i] ) * b[k][j]; + spd[i][j] = s; + } + check_cholesky_spd< T >( spd ); + + check_cholesky_fails< T >( s7::from_real< T >( 2, 2, { 2, 1, 0, 2 } ) ); // not symmetric + check_cholesky_fails< T >( s7::from_real< T >( 2, 2, { 1, 2, 2, 1 } ) ); // indefinite + check_cholesky_fails< T >( s7::from_real< T >( 2, 2, { -1, 0, 0, -1 } ) ); // negative definite + check_cholesky_fails< T >( s7::from_real< T >( 2, 2, { 1, 1, 1, 1 } ) ); // singular + check_cholesky_fails< T >( s7::from_real< T >( 3, 3, { 1, 2, 3, 2, 4, 6, 3, 6, 9 } ) ); // singular, rank 1 + mat< T > nan_a = s7::from_real< T >( 2, 2, { 4, 1, 1, 3 } ); + nan_a[1][1] = T( std::numeric_limits< real_t< T > >::quiet_NaN() ); + check_cholesky_fails< T >( nan_a ); // nonfinite + mat< T > inf_a = s7::from_real< T >( 2, 2, { 4, 1, 1, 3 } ); + inf_a[0][0] = T( std::numeric_limits< real_t< T > >::infinity() ); + check_cholesky_fails< T >( inf_a ); + } +} // namespace s7r4 + +namespace s7r4 +{ + template< typename T > + void check_cholesky_complex_cases() + { + // Hermitian positive definite + check_cholesky_spd< T >( cplx< T >( 3, 3, { 4, { 1, 2 }, { 0, -1 }, { 1, -2 }, 10, { 2, 1 }, { 0, 1 }, { 2, -1 }, 6 } ) ); + // complex symmetric but not Hermitian, and a diagonal with an imaginary part + check_cholesky_fails< T >( cplx< T >( 2, 2, { 4, { 1, 2 }, { 1, 2 }, 5 } ) ); + check_cholesky_fails< T >( cplx< T >( 2, 2, { { 4, 1 }, 0, 0, 5 } ) ); + } +} // namespace s7r4 + +TEST_CASE( "S7-R4 cholesky factors SPD and HPD inputs", "[S7][S7-R4]" ) +{ + s7r4::check_cholesky_real_cases< float >(); + s7r4::check_cholesky_real_cases< double >(); + s7r4::check_cholesky_real_cases< std::complex< float > >(); + s7r4::check_cholesky_real_cases< std::complex< double > >(); + s7r4::check_cholesky_complex_cases< std::complex< float > >(); + s7r4::check_cholesky_complex_cases< std::complex< double > >(); + // 0×0 is ok and empty + auto const f0 = feng::cholesky_factor( feng::matrix< double >{} ); + CHECK( f0.ok() ); + CHECK( f0.l().size() == 0 ); + auto const f0f = feng::cholesky_factor( feng::matrix< float >{} ); + CHECK( f0f.ok() ); + CHECK( f0f.l().size() == 0 ); +} + +namespace s7r4 +{ + template< typename T > + void check_cholesky_failure_classes() + { + // the four failure classes of S7-R4, each with no NaN in l() and the legacy destination unchanged + check_cholesky_fails< T >( s7::from_real< T >( 3, 3, { 2, 1, 0, 0, 2, 1, 0, 0, 2 } ) ); + check_cholesky_fails< T >( s7::from_real< T >( 3, 3, { 1, 0, 0, 0, -1, 0, 0, 0, 1 } ) ); + check_cholesky_fails< T >( s7::from_real< T >( 3, 3, { 1, 1, 0, 1, 1, 0, 0, 0, 1 } ) ); + mat< T > a = feng::eye< T >( 3, 3 ); + a[2][1] = a[1][2] = T( std::numeric_limits< real_t< T > >::infinity() ); + check_cholesky_fails< T >( a ); + } +} // namespace s7r4 + +TEST_CASE( "S7-R4 cholesky reports a non-SPD input", "[S7][S7-R4]" ) +{ + s7r4::check_cholesky_failure_classes< float >(); + s7r4::check_cholesky_failure_classes< double >(); + s7r4::check_cholesky_failure_classes< std::complex< float > >(); + s7r4::check_cholesky_failure_classes< std::complex< double > >(); +} diff --git a/tests/cases/s7_r5.hpp b/tests/cases/s7_r5.hpp new file mode 100644 index 0000000..74cb1f1 --- /dev/null +++ b/tests/cases/s7_r5.hpp @@ -0,0 +1,332 @@ +// S7-R5 (PR-10, PR-2): qualified expm, cgs and bicgstab (D-029). Uses the helpers of tests/cases/s7_r1.hpp +// (namespace s7) and tests/cases/s7_r4.hpp (namespace s7r4). The scipy references were computed once with +// scipy.linalg.expm (scipy 1.18.1) and pasted as literals; they are not regenerated by the suite. +#include +#include +#include +#include +#include +#include + +namespace s7r5 +{ + using s7::mat; + using s7::real_t; + using cd = std::complex< double >; + + template< typename T > + bool all_finite( mat< T > const& m ) + { + for ( auto const& x : m ) + if ( !std::isfinite( std::real( x ) ) || !std::isfinite( std::imag( x ) ) ) return false; + return true; + } + + template< typename T > + real_t< T > rel_err( mat< T > const& x, mat< T > const& ref ) + { + mat< T > d{ x.row(), x.col() }; + for ( std::size_t i = 0; i != x.row(); ++i ) + for ( std::size_t j = 0; j != x.col(); ++j ) d[i][j] = x[i][j] - ref[i][j]; + return s7r4::frob( d ) / s7r4::frob( ref ); + } + + template< typename T > + void check_expm_exact() + { + // zero → I exactly, also 0×0 + mat< T > const z{ 4, 4, T( 0 ) }; + CHECK( feng::expm( z ) == feng::eye< T >( 4, 4 ) ); + CHECK( feng::expm( mat< T >{} ).size() == 0 ); + // diagonal → diag(exp(a_ii)) exactly, diag(700) finite + mat< T > d{ 3, 3, T( 0 ) }; + d[0][0] = T( 1.5 ); d[1][1] = T( -2.25 ); d[2][2] = T( 700 ); + mat< T > const e = feng::expm( d ); + for ( std::size_t i = 0; i != 3; ++i ) + for ( std::size_t j = 0; j != 3; ++j ) + CHECK( e[i][j] == ( i == j ? std::exp( d[i][i] ) : T( 0 ) ) ); + CHECK( all_finite( e ) ); + mat< T > d700{ 2, 2, T( 0 ) }; + d700[0][0] = d700[1][1] = T( 700 ); + mat< T > const e700 = feng::expm( d700 ); + CHECK( e700[0][0] == std::exp( T( 700 ) ) ); + CHECK( e700[0][1] == T( 0 ) ); + } + + template< typename T > + void check_expm_extreme() + { + // ‖A‖₁ ≈ 1e10, negative definite Jordan block: exp(A) = e^{-1e10}·[[1, 1], [0, 1]] = 0 in double + mat< T > const a = s7::from_real< T >( 2, 2, { -1e10, 1, 0, -1e10 } ); + mat< T > const ea = feng::expm( a ); + REQUIRE( all_finite( ea ) ); + for ( auto const& x : ea ) CHECK( std::abs( x ) <= 1e-300 ); + // nilpotent with ‖A‖₁ = 1e10: exp(A) = I + A + mat< T > const n = s7::from_real< T >( 2, 2, { 0, 1e10, 0, 0 } ); + mat< T > const en = feng::expm( n ); + REQUIRE( all_finite( en ) ); + CHECK( en[0][0] == T( 1 ) ); + CHECK( en[1][1] == T( 1 ) ); + CHECK( en[1][0] == T( 0 ) ); + CHECK( std::abs( en[0][1] - T( 1e10 ) ) <= 1e10 * 64 * s7::eps< T >() ); + // ‖A‖₁ ≈ 1e300, negative definite (−1e300·I plus a small off-diagonal): exp(A) = 0 in double + mat< T > b = s7::from_real< T >( 3, 3, { -1e300, 1, 0, 0, -1e300, 2, 1, 0, -1e300 } ); + mat< T > const eb = feng::expm( b ); + REQUIRE( all_finite( eb ) ); + for ( auto const& x : eb ) CHECK( std::abs( x ) <= 1e-300 ); + } + + template< typename T > + void check_expm_nonfinite() + { + mat< T > a = s7::from_real< T >( 2, 3, { 1, 2, 3, 4, 5, 6 } ); // only the shape matters for the NaN result + mat< T > sq = s7::from_real< T >( 2, 2, { 1, 2, 3, 4 } ); + sq[0][1] = T( std::numeric_limits< real_t< T > >::quiet_NaN() ); + mat< T > const e = feng::expm( sq ); + REQUIRE( e.row() == 2 ); + REQUIRE( e.col() == 2 ); + for ( auto const& x : e ) CHECK( std::isnan( std::real( x ) ) ); + sq[0][1] = T( std::numeric_limits< real_t< T > >::infinity() ); + for ( auto const& x : feng::expm( sq ) ) CHECK( std::isnan( std::real( x ) ) ); + + mat< T > out = a; + CHECK( feng::expm( sq, out ) == feng::linalg_status::nonfinite ); + CHECK( out == a ); + mat< T > const ok_in = s7::from_real< T >( 2, 2, { 0, 1, 0, 0 } ); + CHECK( feng::expm( ok_in, out ) == feng::linalg_status::ok ); + CHECK( out == s7::from_real< T >( 2, 2, { 1, 1, 0, 1 } ) ); + } + + // complex closed forms: zero, a diagonal with complex entries, a nilpotent block, rotation generators + inline void check_expm_complex_closed_form() + { + double const eps = std::numeric_limits< double >::epsilon(); + // zero → I exactly + CHECK( feng::expm( mat< cd >{ 3, 3, cd( 0 ) } ) == feng::eye< cd >( 3, 3 ) ); + // diagonal with complex entries → diag(exp(a_ii)) exactly + mat< cd > d{ 3, 3, cd( 0 ) }; + d[0][0] = cd( 0.5, 2.0 ); d[1][1] = cd( -1.25, -3.0 ); d[2][2] = cd( 0, 3.141592653589793 ); + mat< cd > const ed = feng::expm( d ); + for ( std::size_t i = 0; i != 3; ++i ) + for ( std::size_t j = 0; j != 3; ++j ) CHECK( ed[i][j] == ( i == j ? std::exp( d[i][i] ) : cd( 0 ) ) ); + // nilpotent with a complex entry: exp(A) = I + A + mat< cd > nil{ 2, 2, cd( 0 ) }; + nil[0][1] = cd( 3, -4 ); + mat< cd > const en = feng::expm( nil ); + CHECK( en[0][0] == cd( 1 ) ); + CHECK( en[1][0] == cd( 0 ) ); + CHECK( en[1][1] == cd( 1 ) ); + CHECK( std::abs( en[0][1] - cd( 3, -4 ) ) <= 64 * eps * 5 ); + // rotation generator θ·[[0, −1], [1, 0]] → [[cos θ, −sin θ], [sin θ, cos θ]], and the unitary generator + // iθ·[[0, 1], [1, 0]] → [[cos θ, i sin θ], [i sin θ, cos θ]]; θ = 7.5 takes one squaring + for ( double const th : { 0.75, 7.5 } ) + { + INFO( "theta " << th ); + double const c = std::cos( th ), sn = std::sin( th ); + mat< cd > const rot = s7r4::from_list< cd >( 2, 2, { 0, -th, th, 0 } ); + mat< cd > const want_rot = s7r4::from_list< cd >( 2, 2, { c, -sn, sn, c } ); + CHECK( rel_err( feng::expm( rot ), want_rot ) <= 64 * 2 * eps ); + mat< cd > const uni = s7r4::from_list< cd >( 2, 2, { 0, cd( 0, th ), cd( 0, th ), 0 } ); + mat< cd > const want_uni = s7r4::from_list< cd >( 2, 2, { c, cd( 0, sn ), cd( 0, sn ), c } ); + CHECK( rel_err( feng::expm( uni ), want_uni ) <= 64 * 2 * eps ); + } + } +} // namespace s7r5 + +TEST_CASE( "S7-R5 expm of zero, diagonal and extreme inputs", "[S7][S7-R5]" ) +{ + s7r5::check_expm_exact< double >(); + s7r5::check_expm_exact< std::complex< double > >(); + s7r5::check_expm_extreme< double >(); + s7r5::check_expm_extreme< std::complex< double > >(); + s7r5::check_expm_nonfinite< double >(); + s7r5::check_expm_nonfinite< std::complex< double > >(); + s7r5::check_expm_complex_closed_form(); +} + +TEST_CASE( "S7-R5 expm matches scipy references", "[S7][S7-R5]" ) +{ + using s7r5::cd; + // numpy default_rng(20261002).standard_normal((4, 4))·1.5, ‖A‖₁ = 5.94 (one squaring) + feng::matrix< double > const a = s7::from_real< double >( 4, 4, { + -0.3286716625089818, 1.3936606226964554, 0.9329334716081147, -0.5252792304594791, + -1.4850480371190857, 1.1163261934688191, -1.2310979653076153, -1.3662397422059085, + -4.039856710035755, 0.17777160587229968, -1.3625704897147035, -0.9038952069773634, + 0.08226996372122795, 1.4618974388691317, -0.6853026895932535, 1.0294510878328045 } ); + feng::matrix< double > const ea = s7::from_real< double >( 4, 4, { + -0.24853487600479163, 0.02684645019835008, 0.1877005400720356, -1.4615747395214558, + -0.027256558343341286, 0.615888790885136, -0.3779660471406582, -1.8867428635129848, + -0.6608926016705973, -1.5528185729439463, 0.28883482828891993, 1.0857401605634964, + 0.502285296362307, 3.4658676324552617, -1.522872575272041, 0.8457065877396357 } ); + CHECK( s7r5::rel_err( feng::expm( a ), ea ) <= 1e-12 ); + CHECK( s7r5::rel_err( feng::expm( a.astype< cd >() ), ea.astype< cd >() ) <= 1e-12 ); + + // ‖B‖₁ = 12 (two squarings) + feng::matrix< double > const b = s7::from_real< double >( 4, 4, { + 2.0, -1.0, 4.0, 0.5, 3.0, -4.0, 1.5, 2.0, -2.0, 1.0, 0.0, 6.0, 5.0, -3.0, 2.0, -1.0 } ); + feng::matrix< double > const eb = s7::from_real< double >( 4, 4, { + 39.564648851459076, -16.323279303838085, 50.582129012167115, 51.159244877194794, + 25.995824630946895, -10.660783809409793, 33.15430503812922, 33.46526275298682, + 28.98727302293373, -12.169515854397675, 37.00243940975122, 37.582347827381284, + 31.417721978674933, -13.006598079333658, 40.14491115851765, 40.57335340586671 } ); + CHECK( s7r5::rel_err( feng::expm( b ), eb ) <= 1e-12 ); + + // complex 3×3 + feng::matrix< cd > const c = s7r4::from_list< cd >( 3, 3, { + { 0.5, 0.25 }, { -1.0, 0.5 }, { 0.25, 0 }, { 0, 0.75 }, { -0.5, 0 }, { 1.0, -0.25 }, { 1.5, 0 }, { 0.125, -0.5 }, { -0.25, 1 } } ); + feng::matrix< cd > const ec = s7r4::from_list< cd >( 3, 3, { + { 1.3712706767213854, 0.15597657831225087 }, { -0.9684029456415137, 0.4506778282412952 }, { -0.24284452567538198, 0.3057490979511099 }, + { 0.6926061294227425, 0.8112756454504476 }, { 0.29043359311825423, -0.3757626571722854 }, { 0.5950516957178404, 0.1462719441204775 }, + { 1.5838751698156879, 0.789176073738423 }, { -0.5724612615097182, -0.16102754172923112 }, { 0.43237367668175875, 0.6762943510856989 } } ); + CHECK( s7r5::rel_err( feng::expm( c ), ec ) <= 1e-12 ); +} + +namespace s7r5 +{ + inline double residual( mat< double > const& a, mat< double > const& x, mat< double > const& b ) + { + double s = 0; + for ( std::size_t i = 0; i != a.row(); ++i ) + { + double r = -b[i][0]; + for ( std::size_t j = 0; j != a.col(); ++j ) r += a[i][j] * x[j][0]; + s += r * r; + } + return std::sqrt( s ); + } + + inline double norm2( mat< double > const& b ) { return s7r4::frob( b ); } + + template< typename Solver > + void check_solver( Solver solve ) + { + double const eps = 1e-10; + // SPD 5×5: Bᵀ·B + 5·I + mat< double > const g = s7::random_square< double >( 5, 2026 ); + mat< double > spd{ 5, 5 }; + for ( std::size_t i = 0; i != 5; ++i ) + for ( std::size_t j = 0; j != 5; ++j ) + { + double s = i == j ? 5.0 : 0.0; + for ( std::size_t k = 0; k != 5; ++k ) s += g[k][i] * g[k][j]; + spd[i][j] = s; + } + mat< double > const b = s7::from_real< double >( 5, 1, { 1, -2, 3, 0.5, -1 } ); + mat< double > x; + CHECK( solve( spd, x, b, 100, eps ) == 0 ); + CHECK( residual( spd, x, b ) <= 10 * eps * norm2( b ) ); + + // relative: a b of size 1e8 converges with the same eps + mat< double > const big = b * 1e8; + mat< double > xb; + CHECK( solve( spd, xb, big, 100, eps ) == 0 ); + CHECK( residual( spd, xb, big ) <= 10 * eps * norm2( big ) ); + + // diagonal + mat< double > dg{ 5, 5, 0.0 }; + for ( std::size_t i = 0; i != 5; ++i ) dg[i][i] = double( i + 1 ); + mat< double > xd; + CHECK( solve( dg, xd, b, 100, eps ) == 0 ); + CHECK( residual( dg, xd, b ) <= 10 * eps * norm2( b ) ); + + // zero b: x = 0 and 0 + mat< double > x0 = s7::from_real< double >( 5, 1, { 1, 2, 3, 4, 5 } ); + CHECK( solve( spd, x0, mat< double >{ 5, 1, 0.0 }, 100, eps ) == 0 ); + CHECK( x0 == mat< double >{ 5, 1, 0.0 } ); + + // NaN in b and in A: 1 + mat< double > bn = b; + bn[2][0] = std::numeric_limits< double >::quiet_NaN(); + mat< double > xn; + CHECK( solve( spd, xn, bn, 100, eps ) == 1 ); + mat< double > an = spd; + an[1][3] = std::numeric_limits< double >::infinity(); + CHECK( solve( an, xn, b, 100, eps ) == 1 ); + } + + template< typename Solver > + void check_solver_exhaustion( Solver solve ) + { + // max_loops = 1 on an unconverged 5×5 system: 1, and x holds the (finite) last iterate + mat< double > a{ 5, 5, 0.0 }; + for ( std::size_t i = 0; i != 5; ++i ) + { + a[i][i] = 4.0; + if ( i + 1 != 5 ) a[i][i + 1] = a[i + 1][i] = -1.0; + } + a[0][4] = 0.5; a[4][0] = 0.5; + mat< double > const b = s7::from_real< double >( 5, 1, { 1, -2, 3, 0.5, -1 } ); + mat< double > x; + CHECK( solve( a, x, b, 1, 1e-14 ) == 1 ); + REQUIRE( x.row() == 5 ); + REQUIRE( x.col() == 1 ); + CHECK( all_finite( x ) ); + CHECK( residual( a, x, b ) > 1e-14 * norm2( b ) ); + // more loops converge from the same start + mat< double > y; + CHECK( solve( a, y, b, 100, 1e-12 ) == 0 ); + CHECK( residual( a, y, b ) <= 10 * 1e-12 * norm2( b ) ); + } + + template< typename Solver > + void check_solver_breakdown( Solver solve ) + { + // breakdown: with A = [[0, 1], [1, 0]], b = (2, 0) and the start x₀ = (0, 1) the first residual is + // r = (1, 0) and r̂ᵀ·A·r = 0, the denominator of α: 1, x the finite last iterate x₀, no NaN + mat< double > const a = s7::from_real< double >( 2, 2, { 0, 1, 1, 0 } ); + mat< double > const b = s7::from_real< double >( 2, 1, { 2, 0 } ); + mat< double > const x0 = s7::from_real< double >( 2, 1, { 0, 1 } ); + mat< double > x = x0; + CHECK( solve( a, x, b, 100, 1e-10 ) == 1 ); + REQUIRE( x.row() == 2 ); + REQUIRE( x.col() == 1 ); + CHECK( all_finite( x ) ); + CHECK( x == x0 ); + + // nonfinite A or b: 1, x unchanged and finite + mat< double > const spd = s7::from_real< double >( 3, 3, { 4, 1, 0, 1, 3, 1, 0, 1, 2 } ); + mat< double > const b3 = s7::from_real< double >( 3, 1, { 1, 2, 3 } ); + mat< double > const start = s7::from_real< double >( 3, 1, { 0.5, -1, 2 } ); + for ( int which = 0; which != 4; ++which ) + { + INFO( "nonfinite case " << which ); + mat< double > an = spd, bn = b3; + if ( which == 0 ) an[2][1] = std::numeric_limits< double >::quiet_NaN(); + if ( which == 1 ) an[0][0] = -std::numeric_limits< double >::infinity(); + if ( which == 2 ) bn[1][0] = std::numeric_limits< double >::infinity(); + if ( which == 3 ) bn[0][0] = std::numeric_limits< double >::quiet_NaN(); + mat< double > xs = start; + CHECK( solve( an, xs, bn, 100, 1e-10 ) == 1 ); + CHECK( xs == start ); + CHECK( all_finite( xs ) ); + } + + // a diagonal system with mixed signs and scales converges to the relative tolerance + mat< double > dg{ 5, 5, 0.0 }; + double const diag[5] = { -2, 3, 0.5, -7, 1e3 }; + for ( std::size_t i = 0; i != 5; ++i ) dg[i][i] = diag[i]; + mat< double > const bd = s7::from_real< double >( 5, 1, { 1, -2, 3, 0.5, -1 } ); + mat< double > xd; + CHECK( solve( dg, xd, bd, 100, 1e-10 ) == 0 ); + CHECK( residual( dg, xd, bd ) <= 10 * 1e-10 * norm2( bd ) ); + } +} // namespace s7r5 + +TEST_CASE( "S7-R5 iterative solvers report exhaustion", "[S7][S7-R5]" ) +{ + using M = feng::matrix< double >; + auto cgs = []( M const& a, M& x, M const& b, std::uint_least64_t l, double e ) { return feng::cgs( a, x, b, l, e ); }; + auto cgs2 = []( M const& a, M& x, M const& b, std::uint_least64_t l, double e ) { return feng::conjugate_gradient_squared( a, x, b, l, e ); }; + auto bi = []( M const& a, M& x, M const& b, std::uint_least64_t l, double e ) { return feng::bicgstab( a, x, b, l, e ); }; + auto bi2 = []( M const& a, M& x, M const& b, std::uint_least64_t l, double e ) { return feng::biconjugate_gradient_stabilized_method( a, x, b, l, e ); }; + s7r5::check_solver_exhaustion( cgs ); + s7r5::check_solver_exhaustion( bi ); + s7r5::check_solver( cgs ); + s7r5::check_solver( cgs2 ); + s7r5::check_solver( bi ); + s7r5::check_solver( bi2 ); + s7r5::check_solver_breakdown( cgs ); + s7r5::check_solver_breakdown( cgs2 ); + s7r5::check_solver_breakdown( bi ); + s7r5::check_solver_breakdown( bi2 ); +} diff --git a/tests/cases/s7_r6.hpp b/tests/cases/s7_r6.hpp new file mode 100644 index 0000000..8112534 --- /dev/null +++ b/tests/cases/s7_r6.hpp @@ -0,0 +1,163 @@ +// S7-R6 (PR-10, PR-14): the committed numpy/scipy fixtures in tests/fixtures/s7/ match the S7 APIs within the +// tolerance recorded on each manifest line. The suite runs from the repo root (AGENTS.md), so the paths are +// relative to it. Error measure: ||got - ref||_F / ||ref||_F, or ||got - ref||_F when ref is 0. +// Prints `ORACLE S7 FIXTURES ` for tools/check.sh oracle. +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace s7r6 +{ + inline std::string const dir = "tests/fixtures/s7/"; + + struct line + { + std::string name, op; + double tol = 0; + std::vector< std::string > files; + }; + + template< typename T > + double frob( feng::matrix< T > const& m ) + { + double s = 0; + for ( auto const& x : m ) s += std::norm( x ); + return std::sqrt( s ); + } + + // relative Frobenius error; +inf on a shape mismatch + template< typename T > + double rel_err( feng::matrix< T > const& got, feng::matrix< T > const& ref ) + { + if ( got.row() != ref.row() || got.col() != ref.col() ) return HUGE_VAL; + feng::matrix< T > d{ ref.row(), ref.col() }; + for ( std::size_t i = 0; i != ref.size(); ++i ) d.data()[i] = got.data()[i] - ref.data()[i]; + double const r = frob( ref ); + return r == 0 ? frob( d ) : frob( d ) / r; + } + + template< typename T > + bool load( std::string const& f, feng::matrix< T >& m ) + { + return m.load_npy( dir + f ); + } + + // computes the op on the loaded inputs; returns the error, or -1 (with why set) when a file or the op failed + template< typename T > + double run( line const& c, std::string& why ) + { + using R = s7::real_t< T >; + std::size_t const want = c.op == "lu_solve" ? 3 : 2; + if ( c.files.size() != want ) { why = "expected " + std::to_string( want ) + " files"; return -1; } + // singular values are real ( + bool const real_ref = c.op == "svdvals"; + std::vector< feng::matrix< T > > in( want ); + feng::matrix< R > sref; + for ( std::size_t i = 0; i != want; ++i ) + { + bool const ok = ( real_ref && i + 1 == want ) ? load( c.files[i], sref ) : load( c.files[i], in[i] ); + if ( !ok ) { why = "cannot load " + dir + c.files[i]; return -1; } + } + auto const& a = in[0]; + auto const& ref = in.back(); + if ( c.op == "lu_solve" ) + { + auto x = feng::solve( a, in[1] ); + if ( !x.ok() ) { why = "solve status not ok"; return -1; } + return rel_err( x.value, ref ); + } + if ( c.op == "det" ) + { + feng::matrix< T > d{ 1, 1 }; + d[0][0] = feng::det( a ); + return rel_err( d, ref ); + } + if ( c.op == "inv" ) + { + auto x = feng::try_inverse( a ); + if ( !x.ok() ) { why = "try_inverse status not ok"; return -1; } + return rel_err( x.value, ref ); + } + if ( c.op == "svdvals" ) + { + auto const f = feng::svd_factor( a ); + if ( !f.ok() ) { why = "svd_factor status not ok"; return -1; } + feng::matrix< R > s{ 1, f.s().size() }; + for ( std::size_t i = 0; i != f.s().size(); ++i ) s[0][i] = f.s()[i]; + return rel_err( s, sref ); + } + if ( c.op == "pinv" ) + { + feng::matrix< T > x; + if ( feng::pinverse( a, x, R( -1 ) ) != feng::linalg_status::ok ) { why = "pinverse status not ok"; return -1; } + return rel_err( x, ref ); + } + if ( c.op == "cholesky" ) + { + auto const f = feng::cholesky_factor( a ); + if ( !f.ok() ) { why = "cholesky_factor status not ok"; return -1; } + return rel_err( f.l(), ref ); + } + if ( c.op == "rref" ) + { + auto const r = feng::row_echelon( a ); + if ( r.status != feng::linalg_status::ok ) { why = "row_echelon status not ok"; return -1; } + return rel_err( r.r, ref ); + } + if ( c.op == "expm" ) + { + feng::matrix< T > e; + if ( feng::expm( a, e ) != feng::linalg_status::ok ) { why = "expm status not ok"; return -1; } + return rel_err( e, ref ); + } + why = "unknown op " + c.op; + return -1; + } +} + +TEST_CASE( "S7-R6 committed fixtures match within their tolerances", "[S7][S7-R6][oracle]" ) +{ + std::ifstream in( s7r6::dir + "manifest.txt" ); + REQUIRE( in.good() ); + std::vector< s7r6::line > cases; + std::string text; + while ( std::getline( in, text ) ) + { + if ( text.empty() || text[0] == '#' ) continue; + std::istringstream is( text ); + s7r6::line c; + std::string f; + is >> c.name >> c.op >> c.tol; + INFO( "manifest line: " << text ); + REQUIRE( !is.fail() ); + while ( is >> f ) c.files.push_back( f ); + cases.push_back( c ); + } + REQUIRE( cases.size() > 0 ); + + std::size_t checked = 0; + for ( auto const& c : cases ) + { + // case names are __ (tests/fixtures/s7/make_fixtures.py) + std::string const type = c.name.substr( c.op.size() + 1, c.name.find( '_', c.op.size() + 1 ) - c.op.size() - 1 ); + std::string why; + double err = -1; + if ( type == "f8" ) err = s7r6::run< double >( c, why ); + else if ( type == "c16" ) err = s7r6::run< std::complex< double > >( c, why ); + else why = "unknown element type '" + type + "'"; + INFO( "case " << c.name << " (" << c.op << "): error " << err << ", tol " << c.tol << ( why.empty() ? "" : ", " ) << why ); + CHECK( why.empty() ); + CHECK( err >= 0 ); + CHECK( err <= c.tol ); + if ( why.empty() && err >= 0 && err <= c.tol ) ++checked; + if ( std::getenv( "S7_RATIOS" ) ) std::cout << c.name << " ratio " << err / c.tol << "\n"; + } + CHECK( checked == cases.size() ); + std::cout << "ORACLE S7 FIXTURES " << checked << "\n"; +} diff --git a/tests/cases/s8_fixtures.hpp b/tests/cases/s8_fixtures.hpp new file mode 100644 index 0000000..3539d5f --- /dev/null +++ b/tests/cases/s8_fixtures.hpp @@ -0,0 +1,199 @@ +// S8 oracle fixtures (PR-11, PR-14): the committed numpy/scipy files in tests/fixtures/s8/ match the S8 APIs +// within the absolute tolerance recorded on each manifest line (max |got - ref| over the elements; 0 is exact). +// The suite runs from the repo root (AGENTS.md), so the paths are relative to it. Prints `ORACLE S8 FIXTURES ` +// for tools/check.sh oracle, one line per TEST_CASE (the lane sums them). Ops are dispatched by name in s8fx::run +// (conv) and s8fx::run_fourier (fft2, ifft2, fftshift, ifftshift); the element type is the case-name field after +// the op: f4, f8, i4, c8 or c16. The result type of fft and ifft is checked against D-031 at compile time, and the +// reference file must load as that type, so numpy's result dtype agrees with D-031 too. +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace s8fx +{ + inline std::string const dir = "tests/fixtures/s8/"; + + struct line + { + std::string name, op; + double tol = 0; + std::vector< std::string > files; + }; + + template< typename T > + std::complex< long double > widen( T const& v ) + { + return { static_cast< long double >( std::real( v ) ), static_cast< long double >( std::imag( v ) ) }; + } + + // max |got - ref| (complex modulus for complex elements); +inf on a shape mismatch + template< typename T > + double max_err( feng::matrix< T > const& got, feng::matrix< T > const& ref ) + { + if ( got.row() != ref.row() || got.col() != ref.col() ) return HUGE_VAL; + long double e = 0; + for ( std::size_t i = 0; i != ref.size(); ++i ) + { + long double const d = std::abs( widen( got.data()[i] ) - widen( ref.data()[i] ) ); + if ( !( d <= e ) ) e = d; // NaN propagates + } + return static_cast< double >( e ); + } + + template< typename T > + bool load( std::string const& f, feng::matrix< T >& m ) + { + return m.load_npy( dir + f ); + } + + // loads `want` files into `in`; false with why set when the count or a load fails + template< typename T > + bool load_all( line const& c, std::size_t want, std::vector< feng::matrix< T > >& in, std::string& why ) + { + if ( c.files.size() != want ) { why = "expected " + std::to_string( want ) + " files"; return false; } + in.resize( want ); + for ( std::size_t i = 0; i != want; ++i ) + if ( !load( c.files[i], in[i] ) ) { why = "cannot load " + dir + c.files[i]; return false; } + return true; + } + + // computes the op on the loaded inputs; returns the error, or -1 (with why set) when a file or the op failed + template< typename T > + double run( line const& c, std::string& why ) + { + std::vector< feng::matrix< T > > in; + if ( c.op == "conv_full" || c.op == "conv_same" || c.op == "conv_valid" ) + { + if ( !load_all( c, 3, in, why ) ) return -1; + std::string const mode = c.op.substr( 5 ); + return max_err( feng::conv( in[0], in[1], mode ), in[2] ); + } + why = "unknown op " + c.op; + return -1; + } + + // D-031: float, double and long double give complex of the same real type, complex stays, integers give + // complex + template< typename T > struct fft_result { using type = std::complex< double >; }; + template<> struct fft_result< float > { using type = std::complex< float >; }; + template<> struct fft_result< long double > { using type = std::complex< long double >; }; + template< typename T > struct fft_result< std::complex< T > > { using type = std::complex< T >; }; + + // fft2 / ifft2: files A C, C = numpy.fft.fft2(A) or ifft2(A) of D-031's type; fftshift / ifftshift: files A C of + // A's type + template< typename T > + double run_fourier( line const& c, std::string& why ) + { + using X = typename fft_result< T >::type; + static_assert( std::is_same_v< decltype( feng::fft( std::declval< feng::matrix< T > const& >() ) ), feng::matrix< X > > ); + static_assert( std::is_same_v< decltype( feng::ifft( std::declval< feng::matrix< T > const& >() ) ), feng::matrix< X > > ); + static_assert( std::is_same_v< decltype( feng::fftshift( std::declval< feng::matrix< T > const& >() ) ), feng::matrix< T > > ); + static_assert( std::is_same_v< decltype( feng::ifftshift( std::declval< feng::matrix< T > const& >() ) ), feng::matrix< T > > ); + if ( c.files.size() != 2 ) { why = "expected 2 files"; return -1; } + feng::matrix< T > a; + if ( !load( c.files[0], a ) ) { why = "cannot load " + dir + c.files[0]; return -1; } + if ( c.op == "fft2" || c.op == "ifft2" ) + { + feng::matrix< X > ref; + if ( !load( c.files[1], ref ) ) { why = "cannot load " + dir + c.files[1] + " as D-031's result type"; return -1; } + return max_err( c.op == "fft2" ? feng::fft( a ) : feng::ifft( a ), ref ); + } + if ( c.op == "fftshift" || c.op == "ifftshift" ) + { + feng::matrix< T > ref; + if ( !load( c.files[1], ref ) ) { why = "cannot load " + dir + c.files[1]; return -1; } + return max_err( c.op == "fftshift" ? feng::fftshift( a ) : feng::ifftshift( a ), ref ); + } + why = "unknown op " + c.op; + return -1; + } + + // the manifest lines whose op is one of `ops` + inline std::vector< line > read_manifest( std::vector< std::string > const& ops ) + { + std::ifstream in( dir + "manifest.txt" ); + REQUIRE( in.good() ); + std::vector< line > cases; + std::string text; + while ( std::getline( in, text ) ) + { + if ( text.empty() || text[0] == '#' ) continue; + std::istringstream is( text ); + line c; + std::string f; + is >> c.name >> c.op >> c.tol; + INFO( "manifest line: " << text ); + REQUIRE( !is.fail() ); + while ( is >> f ) c.files.push_back( f ); + for ( auto const& op : ops ) + if ( c.op == op ) cases.push_back( c ); + } + REQUIRE( cases.size() > 0 ); + return cases; + } + + // case names are __ (tests/fixtures/s8/make_fixtures.py) + inline std::string type_of( line const& c ) + { + std::size_t const at = c.op.size() + 1; + return c.name.substr( at, c.name.find( '_', at ) - at ); + } + + // checks every case with `dispatch( case, type, why )` returning the error; prints the ORACLE line + template< typename Dispatch > + void check_cases( std::vector< line > const& cases, Dispatch dispatch ) + { + std::size_t checked = 0; + for ( auto const& c : cases ) + { + std::string why; + double const err = dispatch( c, type_of( c ), why ); + INFO( "case " << c.name << " (" << c.op << "): error " << err << ", tol " << c.tol << ( why.empty() ? "" : ", " ) << why ); + CHECK( why.empty() ); + CHECK( err >= 0 ); + CHECK( err <= c.tol ); + if ( why.empty() && err >= 0 && err <= c.tol ) ++checked; + } + CHECK( checked == cases.size() ); + std::cout << "ORACLE S8 FIXTURES " << checked << "\n"; + } + + inline double fourier( line const& c, std::string const& type, std::string& why ) + { + if ( type == "f4" ) return run_fourier< float >( c, why ); + if ( type == "f8" ) return run_fourier< double >( c, why ); + if ( type == "i4" ) return run_fourier< int >( c, why ); + if ( type == "c8" ) return run_fourier< std::complex< float > >( c, why ); + if ( type == "c16" ) return run_fourier< std::complex< double > >( c, why ); + why = "unknown element type '" + type + "'"; + return -1; + } +} + +TEST_CASE( "S8-R1 conv matches the committed scipy fixtures", "[S8][S8-R1][oracle]" ) +{ + auto const cases = s8fx::read_manifest( { "conv_full", "conv_same", "conv_valid" } ); + s8fx::check_cases( cases, []( s8fx::line const& c, std::string const& type, std::string& why ) -> double + { + if ( type == "f8" ) return s8fx::run< double >( c, why ); + if ( type == "i4" ) return s8fx::run< int >( c, why ); + why = "unknown element type '" + type + "'"; + return -1; + } ); +} + +TEST_CASE( "S8-R2 fft and ifft match the committed numpy fixtures", "[S8][S8-R2][oracle]" ) +{ + s8fx::check_cases( s8fx::read_manifest( { "fft2", "ifft2" } ), s8fx::fourier ); +} + +TEST_CASE( "S8-R3 shifts match the committed numpy fixtures", "[S8][S8-R3][oracle]" ) +{ + s8fx::check_cases( s8fx::read_manifest( { "fftshift", "ifftshift" } ), s8fx::fourier ); +} diff --git a/tests/cases/s8_r1.hpp b/tests/cases/s8_r1.hpp new file mode 100644 index 0000000..64a30c5 --- /dev/null +++ b/tests/cases/s8_r1.hpp @@ -0,0 +1,259 @@ +// S8-R1 (PR-11, F15): conv and conv2 follow scipy.signal.convolve2d: kernel reversed, full (ra+rb-1)x(ca+cb-1), +// same cropped from full at ((rb-1)/2, (cb-1)/2), valid (ra-rb+1)x(ca-cb+1) or from the swapped operands when B +// contains A, otherwise an abort; an unknown mode aborts (D-011); an operand with a zero dimension gives 0x0 for +// full and valid and zeros of A's shape for same (D-030). Literal values below come from scipy 1.18.1. +#include +#include +#include +#include +#include +#include +#include +#include + +#include "./s2_death.hpp" + +namespace s8r1 +{ + template< typename T > + feng::matrix< T > make( std::size_t r, std::size_t c, std::vector< T > const& v ) + { + feng::matrix< T > m{ r, c }; + for ( std::size_t i = 0; i != r * c; ++i ) m.data()[i] = v[i]; + return m; + } + + template< typename T > + bool equal( feng::matrix< T > const& a, feng::matrix< T > const& b ) + { + if ( a.row() != b.row() || a.col() != b.col() ) return false; + for ( std::size_t i = 0; i != a.size(); ++i ) + if ( !( a.data()[i] == b.data()[i] ) ) return false; + return true; + } + + template< typename T > + bool zero_by_zero( feng::matrix< T > const& m ) { return m.row() == 0 && m.col() == 0; } + + // A (3x4) and B (2x3) of the asymmetric case + template< typename T > + feng::matrix< T > A() { return make< T >( 3, 4, { 1, -2, 3, 0, 4, 5, -1, 2, 0, 3, 2, -4 } ); } + template< typename T > + feng::matrix< T > B() { return make< T >( 2, 3, { 1, 0, -1, 2, 1, 3 } ); } + + template< typename Callable > + void require_one_message_death( Callable&& fn, std::string const& expected ) + { + s2_death::outcome const out = s2_death::run( std::forward< Callable >( fn ) ); + INFO( "child stderr: " << out.err ); + REQUIRE( out.signaled ); + REQUIRE( out.signal == SIGABRT ); + REQUIRE( s2_death::line_count( out.err ) == 1 ); + REQUIRE( s2_death::contains( out.err, "contract violation" ) ); + REQUIRE( s2_death::contains( out.err, expected ) ); + REQUIRE_FALSE( s2_death::contains( out.err, "AddressSanitizer" ) ); + REQUIRE_FALSE( s2_death::contains( out.err, "runtime error:" ) ); + } +} + +TEST_CASE( "S8-R1 conv of [1,2] with [3,4] is [3,10,8]", "[S8][S8-R1]" ) +{ + auto const a = s8r1::make< double >( 1, 2, { 1, 2 } ); + auto const b = s8r1::make< double >( 1, 2, { 3, 4 } ); + auto const want = s8r1::make< double >( 1, 3, { 3, 10, 8 } ); + CHECK( s8r1::equal( feng::conv( a, b ), want ) ); + CHECK( s8r1::equal( feng::conv( a, b, "full" ), want ) ); + CHECK( s8r1::equal( feng::conv2( a, b ), want ) ); + CHECK( s8r1::equal( feng::conv2( a, b, std::string{ "full" } ), want ) ); +} + +TEST_CASE( "S8-R1 a 1x1 kernel k gives k*A in every mode", "[S8][S8-R1]" ) +{ + auto const a = s8r1::A< double >(); + auto const k = s8r1::make< double >( 1, 1, { -3 } ); + feng::matrix< double > want = a; + for ( auto& x : want ) x *= -3; + CHECK( s8r1::equal( feng::conv( a, k ), want ) ); + CHECK( s8r1::equal( feng::conv( a, k, "full" ), want ) ); + CHECK( s8r1::equal( feng::conv( a, k, "same" ), want ) ); + CHECK( s8r1::equal( feng::conv( a, k, "valid" ), want ) ); + // the 1x1 operand first: full is commutative, same keeps the 1x1 shape, valid swaps + CHECK( s8r1::equal( feng::conv( k, a ), want ) ); + CHECK( s8r1::equal( feng::conv( k, a, "same" ), s8r1::make< double >( 1, 1, { -15 } ) ) ); // full[1][1] = -3 * A[1][1] + CHECK( s8r1::equal( feng::conv( k, a, "valid" ), want ) ); +} + +TEST_CASE( "S8-R1 asymmetric 3x4 with a 2x3 kernel matches scipy in every mode", "[S8][S8-R1]" ) +{ + auto const a = s8r1::A< double >(); + auto const b = s8r1::B< double >(); + CHECK( s8r1::equal( feng::conv( a, b ), s8r1::make< double >( 4, 6, { 1, -2, 2, 2, -3, 0, 6, 2, 2, -6, 10, -2, 8, 17, 17, 11, -3, 10, 0, 6, 7, 3, 2, -12 } ) ) ); + CHECK( s8r1::equal( feng::conv( a, b, "same" ), s8r1::make< double >( 3, 4, { -2, 2, 2, -3, 2, 2, -6, 10, 17, 17, 11, -3 } ) ) ); + CHECK( s8r1::equal( feng::conv( a, b, "valid" ), s8r1::make< double >( 2, 2, { 2, -6, 17, 11 } ) ) ); + CHECK( s8r1::equal( feng::conv2( a, b, "valid" ), s8r1::make< double >( 2, 2, { 2, -6, 17, 11 } ) ) ); +} + +TEST_CASE( "S8-R1 same mode with even and oversized kernels follows scipy's alignment", "[S8][S8-R1]" ) +{ + auto const a = s8r1::A< double >(); + auto const even = s8r1::make< double >( 2, 2, { 1, 2, 3, 4 } ); + CHECK( s8r1::equal( feng::conv( a, even, "same" ), s8r1::make< double >( 3, 4, { 1, 0, -1, 6, 7, 11, 10, 12, 12, 34, 25, 2 } ) ) ); + feng::matrix< double > over{ 4, 5 }; + for ( std::size_t i = 0; i != over.size(); ++i ) over.data()[i] = double( i + 1 ); + CHECK( s8r1::equal( feng::conv( a, over, "same" ), s8r1::make< double >( 3, 4, { 33, 45, 57, 34, 91, 114, 127, 80, 166, 179, 192, 120 } ) ) ); +} + +TEST_CASE( "S8-R1 valid mode swaps the operands when B contains A", "[S8][S8-R1]" ) +{ + auto const a = s8r1::A< double >(); + feng::matrix< double > over{ 4, 5 }; + for ( std::size_t i = 0; i != over.size(); ++i ) over.data()[i] = double( i + 1 ); + auto const want = s8r1::make< double >( 2, 2, { 114, 127, 179, 192 } ); + CHECK( s8r1::equal( feng::conv( a, over, "valid" ), want ) ); + CHECK( s8r1::equal( feng::conv( over, a, "valid" ), want ) ); +} + +TEST_CASE( "S8-R1 empty operands follow D-030", "[S8][S8-R1]" ) +{ + auto const a = s8r1::A< double >(); + feng::matrix< double > const e00{ 0, 0 }; + feng::matrix< double > const e03{ 0, 3 }; + feng::matrix< double > const e20{ 2, 0 }; + for ( auto const* e : { &e00, &e03, &e20 } ) + { + CHECK( s8r1::zero_by_zero( feng::conv( a, *e ) ) ); + CHECK( s8r1::zero_by_zero( feng::conv( a, *e, "full" ) ) ); + CHECK( s8r1::zero_by_zero( feng::conv( a, *e, "valid" ) ) ); + CHECK( s8r1::equal( feng::conv( a, *e, "same" ), feng::matrix< double >{ 3, 4, 0.0 } ) ); + CHECK( s8r1::zero_by_zero( feng::conv( *e, a ) ) ); + CHECK( s8r1::zero_by_zero( feng::conv( *e, a, "valid" ) ) ); + CHECK( feng::conv( *e, a, "same" ).row() == e->row() ); + CHECK( feng::conv( *e, a, "same" ).col() == e->col() ); + } +} + +TEST_CASE( "S8-R1 int elements are exact", "[S8][S8-R1]" ) +{ + auto const a = s8r1::A< int >(); + auto const b = s8r1::B< int >(); + CHECK( s8r1::equal( feng::conv( a, b ), s8r1::make< int >( 4, 6, { 1, -2, 2, 2, -3, 0, 6, 2, 2, -6, 10, -2, 8, 17, 17, 11, -3, 10, 0, 6, 7, 3, 2, -12 } ) ) ); + CHECK( s8r1::equal( feng::conv( a, b, "same" ), s8r1::make< int >( 3, 4, { -2, 2, 2, -3, 2, 2, -6, 10, 17, 17, 11, -3 } ) ) ); + CHECK( s8r1::equal( feng::conv( a, b, "valid" ), s8r1::make< int >( 2, 2, { 2, -6, 17, 11 } ) ) ); + // complex elements take the same path + using cd = std::complex< double >; + auto const ca = s8r1::make< cd >( 1, 2, { cd{ 1, 1 }, cd{ 2, 0 } } ); + auto const cb = s8r1::make< cd >( 1, 2, { cd{ 3, 0 }, cd{ 0, 4 } } ); + CHECK( s8r1::equal( feng::conv( ca, cb ), s8r1::make< cd >( 1, 3, { cd{ 3, 3 }, cd{ 2, 4 }, cd{ 0, 8 } } ) ) ); +} + +TEST_CASE( "S8-R1 impossible valid shapes and unknown modes abort", "[S8][S8-R1]" ) +{ + auto const a = s8r1::A< double >(); + feng::matrix< double > const wide{ 2, 5, 1.0 }; + feng::matrix< double > const tall{ 4, 3, 1.0 }; + s8r1::require_one_message_death( [&] { auto r = feng::conv( a, wide, "valid" ); (void)r; }, "conv: 'valid' mode" ); + s8r1::require_one_message_death( [&] { auto r = feng::conv( a, tall, "valid" ); (void)r; }, "conv: 'valid' mode" ); + s8r1::require_one_message_death( [&] { auto r = feng::conv( a, a, "Full" ); (void)r; }, "conv: unknown mode 'Full'" ); + s8r1::require_one_message_death( [&] { auto r = feng::conv2( a, a, std::string{ "" } ); (void)r; }, "conv: unknown mode ''" ); +} + +namespace s8r1 +{ + // the oracle: full[i][j] = sum A[p][q] * B[i-p][j-q], accumulated in long double (complex for complex T) + template< typename T > + std::vector< std::complex< long double > > direct_full( feng::matrix< T > const& a, feng::matrix< T > const& b ) + { + std::size_t const R = a.row() + b.row() - 1, C = a.col() + b.col() - 1; + std::vector< std::complex< long double > > out( R * C ); + for ( std::size_t p = 0; p != a.row(); ++p ) + for ( std::size_t q = 0; q != a.col(); ++q ) + for ( std::size_t u = 0; u != b.row(); ++u ) + for ( std::size_t v = 0; v != b.col(); ++v ) + { + std::complex< long double > const x{ static_cast< long double >( std::real( a[p][q] ) ), static_cast< long double >( std::imag( a[p][q] ) ) }; + std::complex< long double > const y{ static_cast< long double >( std::real( b[u][v] ) ), static_cast< long double >( std::imag( b[u][v] ) ) }; + out[( p + u ) * C + q + v] += x * y; + } + return out; + } + + // max |got - full[r0 + i][c0 + j]| over got's elements + template< typename T > + long double crop_err( feng::matrix< T > const& got, std::vector< std::complex< long double > > const& full, std::size_t C, + std::size_t r0, std::size_t c0 ) + { + long double e = 0; + for ( std::size_t i = 0; i != got.row(); ++i ) + for ( std::size_t j = 0; j != got.col(); ++j ) + { + std::complex< long double > const g{ static_cast< long double >( std::real( got[i][j] ) ), static_cast< long double >( std::imag( got[i][j] ) ) }; + long double const d = std::abs( g - full[( r0 + i ) * C + c0 + j] ); + if ( !( d <= e ) ) e = d; + } + return e; + } + + template< typename T > + feng::matrix< T > sample( std::size_t r, std::size_t c, int seed ) + { + feng::matrix< T > m{ r, c }; + for ( std::size_t i = 0; i != r * c; ++i ) + { + int const u = static_cast< int >( ( i * 37 + static_cast< std::size_t >( seed ) * 11 ) % 19 ) - 9; + int const w = static_cast< int >( ( i * 23 + static_cast< std::size_t >( seed ) * 7 ) % 17 ) - 8; + if constexpr ( std::is_integral_v< T > ) m.data()[i] = static_cast< T >( u ); + else if constexpr ( std::is_floating_point_v< T > ) m.data()[i] = static_cast< T >( u ) / T( 7 ); + else m.data()[i] = T{ static_cast< double >( u ) / 7.0, static_cast< double >( w ) / 5.0 }; + } + return m; + } + + template< typename T > + void check_direct( std::size_t ra, std::size_t ca, std::size_t rb, std::size_t cb ) + { + INFO( "A " << ra << "x" << ca << ", B " << rb << "x" << cb ); + auto const a = sample< T >( ra, ca, 1 ); + auto const b = sample< T >( rb, cb, 2 ); + static_assert( std::is_same_v< decltype( feng::conv( a, b ) ), feng::matrix< T > > ); // D-030 + auto const full = direct_full( a, b ); + std::size_t const C = ca + cb - 1; + long double sa = 0, sb = 0; + for ( auto const& x : a ) sa += static_cast< long double >( std::abs( x ) ); + for ( auto const& x : b ) sb += static_cast< long double >( std::abs( x ) ); + long double tol = 0; // integers are exact + if constexpr ( !std::is_integral_v< T > ) + tol = 4.0L * static_cast< long double >( std::numeric_limits< decltype( std::abs( T{} ) ) >::epsilon() ) * sa * sb; + auto const f = feng::conv( a, b ); + REQUIRE( f.row() == ra + rb - 1 ); + REQUIRE( f.col() == C ); + CHECK( crop_err( f, full, C, 0, 0 ) <= tol ); + auto const s = feng::conv( a, b, "same" ); + REQUIRE( s.row() == ra ); + REQUIRE( s.col() == ca ); + CHECK( crop_err( s, full, C, ( rb - 1 ) / 2, ( cb - 1 ) / 2 ) <= tol ); + bool const a_holds = ra >= rb && ca >= cb, b_holds = rb >= ra && cb >= ca; + if ( a_holds || b_holds ) + { + auto const v = feng::conv( a, b, "valid" ); + std::size_t const vr = a_holds ? ra - rb + 1 : rb - ra + 1, vc = a_holds ? ca - cb + 1 : cb - ca + 1; + REQUIRE( v.row() == vr ); + REQUIRE( v.col() == vc ); + CHECK( crop_err( v, full, C, a_holds ? rb - 1 : ra - 1, a_holds ? cb - 1 : ca - 1 ) <= tol ); + } + } + + template< typename T > + void check_direct_all() + { + for ( auto [rb, cb] : { std::pair{ 1, 1 }, std::pair{ 3, 4 }, std::pair{ 2, 2 }, std::pair{ 3, 9 }, std::pair{ 6, 8 } } ) + check_direct< T >( 5, 7, static_cast< std::size_t >( rb ), static_cast< std::size_t >( cb ) ); + } +} + +TEST_CASE( "S8-R1 conv on float, int and complex matches a direct sum", "[S8][S8-R1]" ) +{ + SECTION( "float" ) { s8r1::check_direct_all< float >(); } + SECTION( "int" ) { s8r1::check_direct_all< int >(); } + SECTION( "complex" ) { s8r1::check_direct_all< std::complex< double > >(); } + SECTION( "double" ) { s8r1::check_direct_all< double >(); } +} diff --git a/tests/cases/s8_r2.hpp b/tests/cases/s8_r2.hpp new file mode 100644 index 0000000..c08389e --- /dev/null +++ b/tests/cases/s8_r2.hpp @@ -0,0 +1,335 @@ +// S8-R2 (PR-11, F14): fft and ifft follow numpy.fft.fft2 and ifft2: X[k][l] = sum x[m][n] exp(-2 pi i (km/R + ln/C)), +// ifft uses exp(+...) and divides by R*C; result types per D-031. The oracle is a tests-only O(N^2) direct DFT +// accumulating in long double (D-007). Tolerances: eps of the result's real type, N = R*C, L = ceil(log2(2N)). +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace s8r2 +{ + template< typename T > struct real_of { using type = T; }; + template< typename T > struct real_of< std::complex< T > > { using type = T; }; + template< typename T > using real_of_t = typename real_of< T >::type; + + // L = ceil(log2(2N)) + inline long double big_l( std::size_t n ) + { + long double l = 0; + for ( std::size_t p = 1; p < 2 * n; p *= 2 ) l += 1; + return l; + } + + // the oracle: direct 2-D DFT with sign -1 (fft) or +1 (ifft, without the scale), in long double + template< typename T > + feng::matrix< std::complex< long double > > direct_dft( feng::matrix< T > const& x, int sign ) + { + using cl = std::complex< long double >; + std::size_t const R = x.row(); + std::size_t const C = x.col(); + feng::matrix< cl > out{ R, C }; + long double const two_pi = 2.0L * std::numbers::pi_v< long double >; + // exp(sign 2 pi i j / R) and exp(sign 2 pi i j / C) for every residue j; the phase index is reduced exactly + std::vector< cl > wr( R ), wc( C ); + for ( std::size_t j = 0; j != R; ++j ) + wr[j] = std::polar( 1.0L, static_cast< long double >( sign ) * two_pi * static_cast< long double >( j ) / static_cast< long double >( R ) ); + for ( std::size_t j = 0; j != C; ++j ) + wc[j] = std::polar( 1.0L, static_cast< long double >( sign ) * two_pi * static_cast< long double >( j ) / static_cast< long double >( C ) ); + for ( std::size_t k = 0; k != R; ++k ) + for ( std::size_t l = 0; l != C; ++l ) + { + cl acc{ 0.0L, 0.0L }; + for ( std::size_t m = 0; m != R; ++m ) + { + cl const w_r = wr[( k * m ) % R]; + for ( std::size_t n = 0; n != C; ++n ) + { + cl const v{ static_cast< long double >( std::real( x[m][n] ) ), + static_cast< long double >( std::imag( x[m][n] ) ) }; + acc += v * ( w_r * wc[( l * n ) % C] ); + } + } + out[k][l] = acc; + } + return out; + } + + // deterministic values in [-1, 1] + template< typename T > + feng::matrix< T > sample( std::size_t r, std::size_t c, std::uint64_t seed ) + { + feng::matrix< T > m{ r, c }; + std::uint64_t s = seed * 6364136223846793005ULL + 1442695040888963407ULL; + auto next = [&s]() + { + s = s * 6364136223846793005ULL + 1442695040888963407ULL; + return static_cast< double >( s >> 11 ) / 9007199254740992.0 * 2.0 - 1.0; + }; + using R = real_of_t< T >; + for ( std::size_t i = 0; i != r * c; ++i ) + { + if constexpr ( std::is_integral_v< T > ) + m.data()[i] = static_cast< T >( std::lround( next() * 9.0 ) ); // -9 .. 9 + else if constexpr ( std::is_same_v< T, R > ) + m.data()[i] = static_cast< R >( next() ); + else + { + R const re = static_cast< R >( next() ); + R const im = static_cast< R >( next() ); + m.data()[i] = T{ re, im }; + } + } + return m; + } + + template< typename T > + long double sum_abs( feng::matrix< T > const& x ) + { + long double s = 0; + for ( std::size_t i = 0; i != x.size(); ++i ) s += static_cast< long double >( std::abs( x.data()[i] ) ); + return s; + } + + template< typename T > + long double max_abs( feng::matrix< T > const& x ) + { + long double s = 0; + for ( std::size_t i = 0; i != x.size(); ++i ) + s = std::max( s, static_cast< long double >( std::abs( x.data()[i] ) ) ); + return s; + } + + // max |a - b| elementwise, b in long double + template< typename C > + long double max_diff( feng::matrix< C > const& a, feng::matrix< std::complex< long double > > const& b ) + { + long double d = 0; + for ( std::size_t i = 0; i != a.size(); ++i ) + { + std::complex< long double > const v{ static_cast< long double >( a.data()[i].real() ), + static_cast< long double >( a.data()[i].imag() ) }; + d = std::max( d, std::abs( v - b.data()[i] ) ); + } + return d; + } + + template< typename T > + feng::matrix< std::complex< long double > > widen( feng::matrix< T > const& x ) + { + feng::matrix< std::complex< long double > > out{ x.row(), x.col() }; + for ( std::size_t i = 0; i != x.size(); ++i ) + out.data()[i] = { static_cast< long double >( std::real( x.data()[i] ) ), + static_cast< long double >( std::imag( x.data()[i] ) ) }; + return out; + } + + template< typename T > + void check_against_direct( std::size_t r, std::size_t c ) + { + using R = typename decltype( feng::fft( std::declval< feng::matrix< T > const& >() ) )::value_type::value_type; // D-031 + INFO( r << "x" << c << " " << ( std::is_same_v< T, real_of_t< T > > ? "real" : "complex" ) << " eps " + << std::numeric_limits< R >::epsilon() ); + auto const x = sample< T >( r, c, 17 * r + c ); + long double const n = static_cast< long double >( r * c ); + long double const eps = static_cast< long double >( std::numeric_limits< R >::epsilon() ); + long double const l = big_l( r * c ); + + auto const X = feng::fft( x ); + REQUIRE( X.row() == r ); + REQUIRE( X.col() == c ); + long double const tol_f = 16.0L * l * eps * sum_abs( x ); + long double const df = max_diff( X, direct_dft( x, -1 ) ); + INFO( "fft error " << static_cast< double >( df ) << " tol " << static_cast< double >( tol_f ) ); + CHECK( df <= tol_f ); + + // ifft against the direct inverse DFT divided by R*C + auto const xi = feng::ifft( x ); + auto inv = direct_dft( x, +1 ); + for ( std::size_t i = 0; i != inv.size(); ++i ) inv.data()[i] /= n; + long double const di = max_diff( xi, inv ); + INFO( "ifft error " << static_cast< double >( di ) << " tol " << static_cast< double >( tol_f / n ) ); + CHECK( di <= tol_f / n ); + + // ifft(fft(x)) within 16 L eps max|x| of x + auto const back = feng::ifft( X ); + long double const dr = max_diff( back, widen( x ) ); + long double const tol_r = 16.0L * l * eps * max_abs( x ); + INFO( "roundtrip error " << static_cast< double >( dr ) << " tol " << static_cast< double >( tol_r ) ); + CHECK( dr <= tol_r ); + } + + inline std::pair< std::size_t, std::size_t > const sizes[] = { + { 1, 1 }, { 1, 2 }, { 1, 7 }, { 1, 16 }, { 3, 5 }, { 4, 6 }, { 7, 11 }, { 13, 1 }, + { 31, 17 }, { 8, 8 }, { 16, 32 }, { 64, 64 } }; + + template< typename T > + void check_all_sizes() + { + for ( auto [r, c] : sizes ) check_against_direct< T >( r, c ); + } +} + +TEST_CASE( "S8-R2 fft matches the direct DFT on every listed size", "[S8][S8-R2]" ) +{ + SECTION( "double" ) { s8r2::check_all_sizes< double >(); } + SECTION( "complex" ) { s8r2::check_all_sizes< std::complex< double > >(); } + SECTION( "float" ) { s8r2::check_all_sizes< float >(); } + SECTION( "complex" ) { s8r2::check_all_sizes< std::complex< float > >(); } + SECTION( "long double" ) { s8r2::check_all_sizes< long double >(); } +} + +TEST_CASE( "S8-R2 fft of an off-origin impulse is the closed-form phase ramp", "[S8][S8-R2]" ) +{ + for ( auto [R, C, r0, c0] : { std::array< std::size_t, 4 >{ 8, 8, 3, 5 }, std::array< std::size_t, 4 >{ 7, 11, 2, 9 }, + std::array< std::size_t, 4 >{ 4, 6, 0, 1 }, std::array< std::size_t, 4 >{ 1, 7, 0, 4 } } ) + { + INFO( R << "x" << C << " impulse at " << r0 << "," << c0 ); + feng::matrix< double > x{ R, C }; + std::fill( x.begin(), x.end(), 0.0 ); + x[r0][c0] = 1.0; + auto const X = feng::fft( x ); + long double const tol = 16.0L * s8r2::big_l( R * C ) * std::numeric_limits< double >::epsilon(); + long double const two_pi = 2.0L * std::numbers::pi_v< long double >; + for ( std::size_t k = 0; k != R; ++k ) + for ( std::size_t l = 0; l != C; ++l ) + { + long double const ph = static_cast< long double >( ( k * r0 ) % R ) / static_cast< long double >( R ) + + static_cast< long double >( ( l * c0 ) % C ) / static_cast< long double >( C ); + std::complex< long double > const want = std::polar( 1.0L, -two_pi * ph ); + std::complex< long double > const got{ X[k][l].real(), X[k][l].imag() }; + CHECK( std::abs( got - want ) <= tol ); + } + } +} + +TEST_CASE( "S8-R2 fft of a sinusoid peaks at (a, b) and (-a, -b)", "[S8][S8-R2]" ) +{ + for ( auto [R, C, a, b] : { std::array< std::size_t, 4 >{ 8, 12, 1, 3 }, std::array< std::size_t, 4 >{ 7, 10, 2, 3 }, + std::array< std::size_t, 4 >{ 16, 16, 5, 2 } } ) + { + INFO( R << "x" << C << " frequency " << a << "," << b ); + feng::matrix< double > x{ R, C }; + long double const two_pi = 2.0L * std::numbers::pi_v< long double >; + for ( std::size_t m = 0; m != R; ++m ) + for ( std::size_t n = 0; n != C; ++n ) + { + long double const ph = static_cast< long double >( ( a * m ) % R ) / static_cast< long double >( R ) + + static_cast< long double >( ( b * n ) % C ) / static_cast< long double >( C ); + x[m][n] = static_cast< double >( std::cos( two_pi * ph ) ); + } + auto const X = feng::fft( x ); + long double const tol = 16.0L * s8r2::big_l( R * C ) * std::numeric_limits< double >::epsilon() * s8r2::sum_abs( x ); + std::size_t const ka = ( R - a ) % R; + std::size_t const kb = ( C - b ) % C; + for ( std::size_t k = 0; k != R; ++k ) + for ( std::size_t l = 0; l != C; ++l ) + { + bool const peak = ( k == a && l == b ) || ( k == ka && l == kb ); + long double const want = peak ? static_cast< long double >( R * C ) / 2.0L : 0.0L; + std::complex< long double > const got{ X[k][l].real(), X[k][l].imag() }; + CHECK( std::abs( got - std::complex< long double >{ want, 0.0L } ) <= tol ); + } + } +} + +TEST_CASE( "S8-R2 ifft divides by R*C", "[S8][S8-R2]" ) +{ + for ( auto [R, C] : { std::pair< std::size_t, std::size_t >{ 4, 6 }, std::pair< std::size_t, std::size_t >{ 7, 11 }, + std::pair< std::size_t, std::size_t >{ 8, 8 } } ) + { + INFO( R << "x" << C ); + long double const tol = 16.0L * s8r2::big_l( R * C ) * std::numeric_limits< double >::epsilon() * 3.0L; + // ifft of the constant 3 is 3 at the origin and 0 elsewhere + feng::matrix< double > k{ R, C }; + std::fill( k.begin(), k.end(), 3.0 ); + auto const xi = feng::ifft( k ); + for ( std::size_t i = 0; i != R; ++i ) + for ( std::size_t j = 0; j != C; ++j ) + CHECK( std::abs( xi[i][j] - std::complex< double >{ ( i == 0 && j == 0 ) ? 3.0 : 0.0, 0.0 } ) <= tol ); + // ifft of an origin impulse of height 3*R*C is the constant 3 + feng::matrix< double > d{ R, C }; + std::fill( d.begin(), d.end(), 0.0 ); + d[0][0] = 3.0 * static_cast< double >( R * C ); + auto const xd = feng::ifft( d ); + for ( std::size_t i = 0; i != xd.size(); ++i ) + CHECK( std::abs( xd.data()[i] - std::complex< double >{ 3.0, 0.0 } ) <= tol ); + } +} + +TEST_CASE( "S8-R2 fft and ifft of an empty matrix keep its shape", "[S8][S8-R2]" ) +{ + for ( auto [R, C] : { std::pair< std::size_t, std::size_t >{ 0, 0 }, std::pair< std::size_t, std::size_t >{ 0, 3 }, + std::pair< std::size_t, std::size_t >{ 5, 0 } } ) + { + feng::matrix< double > const e{ R, C }; + auto const X = feng::fft( e ); + auto const x = feng::ifft( e ); + CHECK( X.row() == R ); + CHECK( X.col() == C ); + CHECK( x.row() == R ); + CHECK( x.col() == C ); + feng::matrix< std::complex< float > > const ec{ R, C }; + CHECK( feng::fft( ec ).size() == 0 ); + CHECK( feng::ifft( ec ).size() == 0 ); + } +} + +TEST_CASE( "S8-R2 fft result types follow D-031", "[S8][S8-R2]" ) +{ + using std::declval; + static_assert( std::is_same_v< decltype( feng::fft( declval< feng::matrix< float > const& >() ) ), + feng::matrix< std::complex< float > > > ); + static_assert( std::is_same_v< decltype( feng::fft( declval< feng::matrix< double > const& >() ) ), + feng::matrix< std::complex< double > > > ); + static_assert( std::is_same_v< decltype( feng::fft( declval< feng::matrix< long double > const& >() ) ), + feng::matrix< std::complex< long double > > > ); + static_assert( std::is_same_v< decltype( feng::fft( declval< feng::matrix< int > const& >() ) ), + feng::matrix< std::complex< double > > > ); + static_assert( std::is_same_v< decltype( feng::fft( declval< feng::matrix< std::complex< float > > const& >() ) ), + feng::matrix< std::complex< float > > > ); + static_assert( std::is_same_v< decltype( feng::fft( declval< feng::matrix< std::complex< double > > const& >() ) ), + feng::matrix< std::complex< double > > > ); + static_assert( std::is_same_v< decltype( feng::ifft( declval< feng::matrix< float > const& >() ) ), + feng::matrix< std::complex< float > > > ); + static_assert( std::is_same_v< decltype( feng::ifft( declval< feng::matrix< int > const& >() ) ), + feng::matrix< std::complex< double > > > ); + static_assert( std::is_same_v< decltype( feng::ifft( declval< feng::matrix< long double > const& >() ) ), + feng::matrix< std::complex< long double > > > ); + static_assert( std::is_same_v< decltype( feng::ifft( declval< feng::matrix< std::complex< double > > const& >() ) ), + feng::matrix< std::complex< double > > > ); + static_assert( std::is_same_v< decltype( feng::ifft( declval< feng::matrix< std::complex< float > > const& >() ) ), + feng::matrix< std::complex< float > > > ); + static_assert( std::is_same_v< decltype( feng::ifft( declval< feng::matrix< double > const& >() ) ), + feng::matrix< std::complex< double > > > ); + // values of every D-031 element type against the direct DFT, at the tolerance of the result's real type + for ( auto [r, c] : { std::pair< std::size_t, std::size_t >{ 3, 5 }, std::pair< std::size_t, std::size_t >{ 8, 8 }, + std::pair< std::size_t, std::size_t >{ 7, 11 } } ) + { + s8r2::check_against_direct< float >( r, c ); + s8r2::check_against_direct< long double >( r, c ); + s8r2::check_against_direct< int >( r, c ); + s8r2::check_against_direct< std::complex< float > >( r, c ); + s8r2::check_against_direct< std::complex< double > >( r, c ); + } + // an int input transforms like its double copy + feng::matrix< int > xi{ 3, 5 }; + feng::matrix< double > xd{ 3, 5 }; + for ( std::size_t i = 0; i != xi.size(); ++i ) + { + xi.data()[i] = static_cast< int >( i * 7 % 11 ) - 5; + xd.data()[i] = static_cast< double >( xi.data()[i] ); + } + auto const Xi = feng::fft( xi ); + auto const Xd = feng::fft( xd ); + for ( std::size_t i = 0; i != Xi.size(); ++i ) CHECK( Xi.data()[i] == Xd.data()[i] ); + auto const Ii = feng::ifft( xi ); + auto const Id = feng::ifft( xd ); + for ( std::size_t i = 0; i != Ii.size(); ++i ) CHECK( Ii.data()[i] == Id.data()[i] ); +} diff --git a/tests/cases/s8_r3.hpp b/tests/cases/s8_r3.hpp new file mode 100644 index 0000000..4d00529 --- /dev/null +++ b/tests/cases/s8_r3.hpp @@ -0,0 +1,130 @@ +// S8-R3 (PR-11, F14): fftshift and ifftshift are numpy's rolls by R/2, C/2 and -(R/2), -(C/2) on a matrix of x's +// own type and allocator; no transform is computed. Literal values below come from numpy 2.5.3 on arange(R*C). +#include +#include +#include +#include +#include + +namespace s8r3 +{ + template< typename T > + feng::matrix< T > iota( std::size_t r, std::size_t c ) + { + feng::matrix< T > m{ r, c }; + for ( std::size_t i = 0; i != r * c; ++i ) m.data()[i] = T( static_cast< int >( i ) ); + return m; + } + + template< typename T > + std::vector< T > values( feng::matrix< T > const& m ) + { + return std::vector< T >( m.data(), m.data() + m.size() ); + } + + template< typename T > + bool equal( feng::matrix< T > const& a, feng::matrix< T > const& b ) + { + return a.row() == b.row() && a.col() == b.col() && values( a ) == values( b ); + } + + // the formula: out[(i + dr) mod R][(j + dc) mod C] = x[i][j] + template< typename T > + feng::matrix< T > rolled( feng::matrix< T > const& x, std::ptrdiff_t dr, std::ptrdiff_t dc ) + { + std::ptrdiff_t const R = static_cast< std::ptrdiff_t >( x.row() ); + std::ptrdiff_t const C = static_cast< std::ptrdiff_t >( x.col() ); + feng::matrix< T > out{ x.row(), x.col() }; + for ( std::ptrdiff_t i = 0; i != R; ++i ) + for ( std::ptrdiff_t j = 0; j != C; ++j ) + out[( ( i + dr ) % R + R ) % R][( ( j + dc ) % C + C ) % C] = x[i][j]; + return out; + } + + template< typename T > + void check_size( std::size_t r, std::size_t c ) + { + INFO( r << "x" << c ); + auto const x = iota< T >( r, c ); + auto const s = feng::fftshift( x ); + auto const is = feng::ifftshift( x ); + static_assert( std::is_same_v< std::remove_cvref_t< decltype( s ) >, feng::matrix< T > > ); + static_assert( std::is_same_v< std::remove_cvref_t< decltype( is ) >, feng::matrix< T > > ); + std::ptrdiff_t const hr = static_cast< std::ptrdiff_t >( r / 2 ); + std::ptrdiff_t const hc = static_cast< std::ptrdiff_t >( c / 2 ); + CHECK( equal( s, rolled( x, hr, hc ) ) ); + CHECK( equal( is, rolled( x, -hr, -hc ) ) ); + CHECK( equal( feng::ifftshift( s ), x ) ); + CHECK( equal( feng::fftshift( is ), x ) ); + auto sorted = []( feng::matrix< T > const& m ) + { + auto v = values( m ); + std::sort( v.begin(), v.end(), []( T const& a, T const& b ) { return std::real( a ) < std::real( b ); } ); + return v; + }; + CHECK( sorted( s ) == sorted( x ) ); + CHECK( sorted( is ) == sorted( x ) ); + } + + template< typename T > + feng::matrix< T > make( std::size_t r, std::size_t c, std::vector< int > const& v ) + { + feng::matrix< T > m{ r, c }; + for ( std::size_t i = 0; i != r * c; ++i ) m.data()[i] = T( v[i] ); + return m; + } + + template< typename T > + void check_literals() + { + // numpy.fft.fftshift / ifftshift of numpy.arange(R*C).reshape(R, C) + CHECK( equal( feng::fftshift( iota< T >( 3, 5 ) ), + make< T >( 3, 5, { 13, 14, 10, 11, 12, 3, 4, 0, 1, 2, 8, 9, 5, 6, 7 } ) ) ); + CHECK( equal( feng::ifftshift( iota< T >( 3, 5 ) ), + make< T >( 3, 5, { 7, 8, 9, 5, 6, 12, 13, 14, 10, 11, 2, 3, 4, 0, 1 } ) ) ); + std::vector< int > const s46{ 15, 16, 17, 12, 13, 14, 21, 22, 23, 18, 19, 20, + 3, 4, 5, 0, 1, 2, 9, 10, 11, 6, 7, 8 }; + CHECK( equal( feng::fftshift( iota< T >( 4, 6 ) ), make< T >( 4, 6, s46 ) ) ); + CHECK( equal( feng::ifftshift( iota< T >( 4, 6 ) ), make< T >( 4, 6, s46 ) ) ); + CHECK( equal( feng::fftshift( iota< T >( 5, 5 ) ), + make< T >( 5, 5, { 18, 19, 15, 16, 17, 23, 24, 20, 21, 22, 3, 4, 0, 1, 2, + 8, 9, 5, 6, 7, 13, 14, 10, 11, 12 } ) ) ); + CHECK( equal( feng::ifftshift( iota< T >( 5, 5 ) ), + make< T >( 5, 5, { 12, 13, 14, 10, 11, 17, 18, 19, 15, 16, 22, 23, 24, 20, 21, + 2, 3, 4, 0, 1, 7, 8, 9, 5, 6 } ) ) ); + CHECK( equal( feng::fftshift( iota< T >( 1, 4 ) ), make< T >( 1, 4, { 2, 3, 0, 1 } ) ) ); + CHECK( equal( feng::ifftshift( iota< T >( 1, 4 ) ), make< T >( 1, 4, { 2, 3, 0, 1 } ) ) ); + CHECK( equal( feng::fftshift( iota< T >( 6, 1 ) ), make< T >( 6, 1, { 3, 4, 5, 0, 1, 2 } ) ) ); + CHECK( equal( feng::ifftshift( iota< T >( 6, 1 ) ), make< T >( 6, 1, { 3, 4, 5, 0, 1, 2 } ) ) ); + CHECK( equal( feng::fftshift( iota< T >( 1, 1 ) ), iota< T >( 1, 1 ) ) ); + CHECK( equal( feng::ifftshift( iota< T >( 1, 1 ) ), iota< T >( 1, 1 ) ) ); + } + + template< typename T > + void check_all() + { + check_literals< T >(); + for ( auto [r, c] : { std::pair{ 1, 1 }, std::pair{ 1, 4 }, std::pair{ 3, 5 }, std::pair{ 4, 6 }, + std::pair{ 5, 5 }, std::pair{ 6, 1 } } ) + check_size< T >( static_cast< std::size_t >( r ), static_cast< std::size_t >( c ) ); + for ( auto [r, c] : { std::pair{ 0, 0 }, std::pair{ 0, 3 }, std::pair{ 4, 0 } } ) + { + feng::matrix< T > const e{ static_cast< std::size_t >( r ), static_cast< std::size_t >( c ) }; + auto const s = feng::fftshift( e ); + auto const is = feng::ifftshift( e ); + CHECK( s.row() == e.row() ); + CHECK( s.col() == e.col() ); + CHECK( is.row() == e.row() ); + CHECK( is.col() == e.col() ); + CHECK( s.size() == 0 ); + CHECK( is.size() == 0 ); + } + } +} + +TEST_CASE( "S8-R3 shifts are numpy rolls and invert each other exactly", "[S8][S8-R3]" ) +{ + SECTION( "double" ) { s8r3::check_all< double >(); } + SECTION( "int" ) { s8r3::check_all< int >(); } + SECTION( "complex" ) { s8r3::check_all< std::complex< double > >(); } +} diff --git a/tests/cases/s9_r1.hpp b/tests/cases/s9_r1.hpp new file mode 100644 index 0000000..4834ec1 --- /dev/null +++ b/tests/cases/s9_r1.hpp @@ -0,0 +1,126 @@ +// S9-R1 (PR-12): plain value returns. Factory and shape results are non-const prvalues, so assigning them to an +// existing matrix or move-constructing from them moves the storage instead of copying it (one allocation per +// result, counted with s9_r1::counting_alloc); magic( n ) builds in T and A and keeps magic( n )'s values. +#include +#include +#include +#include +#include +#include + +namespace s9_r1 +{ + struct counts + { + static inline long allocations = 0; + static inline long deallocations = 0; + static void reset() noexcept { allocations = 0; deallocations = 0; } + }; + + template < typename T > + struct counting_alloc + { + using value_type = T; + counting_alloc() noexcept = default; + template < typename U > + counting_alloc( counting_alloc< U > const& ) noexcept {} + T* allocate( std::size_t n ) + { + ++counts::allocations; + return std::allocator< T >{}.allocate( n ); + } + void deallocate( T* p, std::size_t n ) noexcept + { + ++counts::deallocations; + std::allocator< T >{}.deallocate( p, n ); + } + template < typename U > + friend bool operator == ( counting_alloc const&, counting_alloc< U > const& ) noexcept { return true; } + }; + + using cmat = feng::matrix< double, counting_alloc< double > >; + + // decltype of each call is its declared return type; none may be const-qualified. + inline void const_free_returns() noexcept + { + feng::matrix< double > const m{ 3, 3, 1.0 }; + std::mt19937_64 g{ 1 }; + static_assert( !std::is_const_v< decltype( feng::eye< double >( 3 ) ) > ); + static_assert( !std::is_const_v< decltype( feng::zeros< double >( 3, 3 ) ) > ); + static_assert( !std::is_const_v< decltype( feng::ones< double >( 3, 3 ) ) > ); + static_assert( !std::is_const_v< decltype( feng::magic( 4 ) ) > ); + static_assert( !std::is_const_v< decltype( feng::magic< double, counting_alloc< double > >( 4 ) ) > ); + static_assert( !std::is_const_v< decltype( feng::transpose( m ) ) > ); + static_assert( !std::is_const_v< decltype( feng::inverse( m ) ) > ); + static_assert( !std::is_const_v< decltype( feng::diag( m ) ) > ); + static_assert( !std::is_const_v< decltype( feng::tril( m ) ) > ); + static_assert( !std::is_const_v< decltype( feng::triu( m ) ) > ); + static_assert( !std::is_const_v< decltype( feng::fliplr( m ) ) > ); + static_assert( !std::is_const_v< decltype( feng::flipud( m ) ) > ); + static_assert( !std::is_const_v< decltype( feng::rand( 3, 3, g ) ) > ); + static_assert( !std::is_const_v< decltype( m.transpose() ) > ); + static_assert( !std::is_const_v< decltype( m.inverse() ) > ); + static_assert( !std::is_const_v< decltype( m.clone( 0, 2, 0, 2 ) ) > ); + static_assert( !std::is_const_v< decltype( m.astype< float >() ) > ); + } +} + +TEST_CASE( "S9-R1 factory results are moved, not copied", "[S9][S9-R1]" ) +{ + using s9_r1::cmat; + using s9_r1::counts; + s9_r1::const_free_returns(); + s9_r1::counting_alloc< double > const alloc; + + SECTION( "magic> assigned to an existing matrix" ) + { + for ( std::uint_least64_t n : { 3u, 4u, 5u, 6u, 8u, 10u } ) + { + cmat m{ 2, 2 }; + counts::reset(); + m = feng::magic< double, s9_r1::counting_alloc< double > >( n ); + REQUIRE( counts::allocations == 1 ); + REQUIRE( counts::deallocations == 1 ); // the old 2x2 storage only + auto const ref = feng::magic( n ); + REQUIRE( m.row() == n ); + REQUIRE( m.col() == n ); + for ( std::uint_least64_t r = 0; r != n; ++r ) + for ( std::uint_least64_t c = 0; c != n; ++c ) + REQUIRE( m[r][c] == static_cast< double >( ref[r][c] ) ); + } + } + + SECTION( "move construction takes the storage" ) + { + counts::reset(); + auto y = feng::magic< double, s9_r1::counting_alloc< double > >( 5 ); + REQUIRE( counts::allocations == 1 ); + double const* const data = y.data(); + cmat x( std::move( y ) ); + REQUIRE( counts::allocations == 1 ); + REQUIRE( x.data() == data ); + REQUIRE( x[2][2] == 13.0 ); + } + + SECTION( "factory and shape results initialise and assign with one allocation each" ) + { + counts::reset(); + auto z = feng::zeros< double >( alloc, 3, 4 ); + REQUIRE( counts::allocations == 1 ); + auto o = feng::ones< double >( alloc, 4, 3 ); + REQUIRE( counts::allocations == 2 ); + cmat t{ 1, 1 }; + long const before = counts::allocations; + t = z.transpose(); + REQUIRE( counts::allocations == before + 1 ); + t = feng::transpose( o ); + REQUIRE( counts::allocations == before + 2 ); + cmat c( z.clone( 0, 2, 0, 2 ) ); + REQUIRE( counts::allocations == before + 3 ); + REQUIRE( c.row() == 2 ); + REQUIRE( c.col() == 2 ); + REQUIRE( t.row() == 3 ); + REQUIRE( t.col() == 4 ); + REQUIRE( t[0][0] == 1.0 ); + } +} diff --git a/tests/cases/s9_r4.hpp b/tests/cases/s9_r4.hpp new file mode 100644 index 0000000..636ab58 --- /dev/null +++ b/tests/cases/s9_r4.hpp @@ -0,0 +1,148 @@ +// S9-R4 (PR-12, D-032): span access to the storage and rows; std::mdspan and std::submdspan adapters where the +// library has them. In C++20 (macros unset) the gated adapters are not declared, and make_view / +// make_mutable_view plus row_span give the same extents and elements. +#include "./s2_death.hpp" + +#include +#include +#include +#include +#include + +namespace s9_r4 +{ + template < typename M > + concept has_to_mdspan = requires( M& m ) { to_mdspan( m ); }; + + template < typename M > + concept has_submdspan = requires( M& m ) { submdspan( m, { 0, 1 }, { 0, 1 } ); }; + + // a 3x4 matrix holding 10*r + c + inline feng::matrix< double > grid() + { + feng::matrix< double > m{ 3, 4 }; + for ( std::size_t r = 0; r != 3; ++r ) + for ( std::size_t c = 0; c != 4; ++c ) + m[r][c] = static_cast< double >( 10 * r + c ); + return m; + } +} + +TEST_CASE( "S9-R4 span adapters alias the storage and rows", "[S9][S9-R4]" ) +{ + auto m = s9_r4::grid(); + auto const& cm = m; + + static_assert( std::is_same_v< decltype( feng::as_span( m ) ), std::span< double > > ); + static_assert( std::is_same_v< decltype( feng::as_span( cm ) ), std::span< double const > > ); + static_assert( std::is_same_v< decltype( feng::row_span( m, 0 ) ), std::span< double > > ); + static_assert( std::is_same_v< decltype( feng::row_span( cm, 0 ) ), std::span< double const > > ); + + auto const all = feng::as_span( m ); + REQUIRE( all.data() == m.data() ); + REQUIRE( all.size() == m.size() ); + REQUIRE( feng::as_span( cm ).data() == m.data() ); + for ( std::size_t r = 0; r != 3; ++r ) + { + auto const row = feng::row_span( cm, r ); + REQUIRE( row.size() == 4 ); + REQUIRE( row.data() == m.data() + r * 4 ); + for ( std::size_t c = 0; c != 4; ++c ) + REQUIRE( row[c] == m[r][c] ); + } + feng::row_span( m, 1 )[2] = -1.0; + REQUIRE( m[1][2] == -1.0 ); + all[0] = -2.0; + REQUIRE( m[0][0] == -2.0 ); + + feng::matrix< double > empty; + REQUIRE( feng::as_span( empty ).empty() ); + + SECTION( "row_span outside the matrix aborts with one message" ) + { + S2_REQUIRE_DEATH( [&] { auto s = feng::row_span( cm, 3 ); (void)s; }, "row_span: row 3 outside a matrix with 3 rows" ); + } +} + +#if defined( __cpp_lib_mdspan ) +TEST_CASE( "S9-R4 to_mdspan has the matrix extents, layout_right strides and aliases it", "[S9][S9-R4]" ) +{ + auto m = s9_r4::grid(); + auto const& cm = m; + static_assert( s9_r4::has_to_mdspan< feng::matrix< double > > ); + static_assert( std::is_same_v< decltype( feng::to_mdspan( m ) ), std::mdspan< double, std::dextents< std::size_t, 2 > > > ); + static_assert( std::is_same_v< decltype( feng::to_mdspan( cm ) ), std::mdspan< double const, std::dextents< std::size_t, 2 > > > ); + auto const md = feng::to_mdspan( m ); + REQUIRE( md.extent( 0 ) == 3 ); + REQUIRE( md.extent( 1 ) == 4 ); + REQUIRE( md.stride( 0 ) == 4 ); + REQUIRE( md.stride( 1 ) == 1 ); + REQUIRE( md.data_handle() == m.data() ); + for ( std::size_t r = 0; r != 3; ++r ) + for ( std::size_t c = 0; c != 4; ++c ) + REQUIRE( &md[r, c] == &m[r][c] ); + md[2, 3] = -5.0; + REQUIRE( m[2][3] == -5.0 ); + REQUIRE( feng::to_mdspan( cm )[2, 3] == -5.0 ); +} +#endif + +#if defined( __cpp_lib_submdspan ) +TEST_CASE( "S9-R4 submdspan slices the matrix after the view validation", "[S9][S9-R4]" ) +{ + auto m = s9_r4::grid(); + auto const& cm = m; + static_assert( s9_r4::has_submdspan< feng::matrix< double > > ); + auto const s = feng::submdspan( m, { 1, 3 }, { 1, 4 } ); + REQUIRE( s.extent( 0 ) == 2 ); + REQUIRE( s.extent( 1 ) == 3 ); + REQUIRE( s.stride( 0 ) == 4 ); + REQUIRE( s.stride( 1 ) == 1 ); + for ( std::size_t r = 0; r != 2; ++r ) + for ( std::size_t c = 0; c != 3; ++c ) + REQUIRE( &s[r, c] == &m[r + 1][c + 1] ); + auto const cs = feng::submdspan( cm, { 0, 0 }, { 0, 4 } ); + static_assert( std::is_const_v< std::remove_reference_t< decltype( cs[0, 0] ) > > ); + REQUIRE( cs.extent( 0 ) == 0 ); + REQUIRE( cs.extent( 1 ) == 4 ); + + SECTION( "an invalid slice aborts" ) + { + S2_REQUIRE_DEATH( [&] { auto x = feng::submdspan( cm, { 0, 4 }, { 0, 1 } ); (void)x; }, "matrix view: row range [0, 4)" ); + S2_REQUIRE_DEATH( [&] { auto x = feng::submdspan( cm, { 2, 1 }, { 0, 1 } ); (void)x; }, "matrix view: row range [2, 1)" ); + S2_REQUIRE_DEATH( [&] { auto x = feng::submdspan( cm, { 0, 1 }, { 0, 5 } ); (void)x; }, "matrix view: column range [0, 5)" ); + } +} +#endif + +#if !defined( __cpp_lib_mdspan ) || !defined( __cpp_lib_submdspan ) +TEST_CASE( "S9-R4 C++20 fallback: views and row_span give the same extents and elements", "[S9][S9-R4]" ) +{ +#if !defined( __cpp_lib_mdspan ) + static_assert( !s9_r4::has_to_mdspan< feng::matrix< double > > ); +#endif + static_assert( !s9_r4::has_submdspan< feng::matrix< double > > ); + auto m = s9_r4::grid(); + auto const& cm = m; + + auto const v = feng::make_view( cm, { 0, 3 }, { 0, 4 } ); // the whole matrix, as to_mdspan( m ) + REQUIRE( v.row() == 3 ); + REQUIRE( v.col() == 4 ); + for ( std::size_t r = 0; r != 3; ++r ) + { + auto const row = feng::row_span( cm, r ); + for ( std::size_t c = 0; c != 4; ++c ) + { + REQUIRE( v( r, c ) == m[r][c] ); + REQUIRE( row[c] == v( r, c ) ); + } + } + + auto const mv = feng::make_mutable_view( m, { 1, 3 }, { 1, 4 } ); // as submdspan( m, {1, 3}, {1, 4} ) + REQUIRE( mv.row() == 2 ); + REQUIRE( mv.col() == 3 ); + for ( std::size_t r = 0; r != 2; ++r ) + for ( std::size_t c = 0; c != 3; ++c ) + REQUIRE( &mv( r, c ) == &feng::row_span( m, r + 1 )[c + 1] ); +} +#endif diff --git a/tests/cases/s9_r5.hpp b/tests/cases/s9_r5.hpp new file mode 100644 index 0000000..2e8a0b0 --- /dev/null +++ b/tests/cases/s9_r5.hpp @@ -0,0 +1,188 @@ +// S9-R5 (PR-12, D-032): std::expected adapters over linalg_result, the factorization objects and the S5 loaders +// where the library has std::expected. In C++20 the adapters are not declared and the same results stay +// available as linalg_result, the factorization objects' status() and the bool loaders. +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace s9_r5 +{ + template < typename R > + concept has_to_expected = requires( R r ) { to_expected( r ); }; + + template < typename T, typename F > + concept has_load_expected = requires( F f ) { load_expected< T >( std::string{}, f ); }; + + inline std::string temp( std::string const& name ) + { + return ( std::filesystem::temp_directory_path() / ( "feng_s9_r5_" + std::to_string( ::getpid() ) + "_" + name ) ).string(); + } + + inline std::string missing() { return temp( "does_not_exist.npy" ); } + + inline feng::matrix< double > invertible() { return feng::matrix< double >{ 2, 2, { 2.0, 1.0, 1.0, 1.0 } }; } + inline feng::matrix< double > singular() { return feng::matrix< double >{ 2, 2, { 1.0, 2.0, 2.0, 4.0 } }; } +} + +TEST_CASE( "S9-R5 io_status and io_format are always declared", "[S9][S9-R5]" ) +{ + static_assert( std::is_enum_v< feng::io_status > && std::is_enum_v< feng::io_format > ); + REQUIRE( feng::io_status::ok != feng::io_status::failed ); + REQUIRE( feng::io_format::txt != feng::io_format::binary ); + REQUIRE( feng::io_format::binary != feng::io_format::npy ); +} + +#if defined( __cpp_lib_expected ) +TEST_CASE( "S9-R5 to_expected carries value or status", "[S9][S9-R5]" ) +{ + using s9_r5::invertible; + using s9_r5::singular; + using E = std::expected< feng::matrix< double >, feng::linalg_status >; + + SECTION( "linalg_result" ) + { + static_assert( std::is_same_v< decltype( feng::to_expected( feng::try_inverse( invertible() ) ) ), E > ); + static_assert( noexcept( feng::to_expected( std::declval< feng::linalg_result< feng::matrix< double > > >() ) ) ); + auto const ok = feng::to_expected( feng::try_inverse( invertible() ) ); + REQUIRE( ok.has_value() ); + REQUIRE( ( *ok )[0][0] == 1.0 ); + REQUIRE( ( *ok )[0][1] == -1.0 ); + REQUIRE( ( *ok )[1][1] == 2.0 ); + auto const bad = feng::to_expected( feng::try_inverse( singular() ) ); + REQUIRE( !bad.has_value() ); + REQUIRE( bad.error() == feng::linalg_status::singular ); + feng::matrix< double > const b{ 2, 1, { 3.0, 2.0 } }; + static_assert( std::is_same_v< decltype( feng::to_expected( feng::solve( invertible(), b ) ) ), E > ); + auto const s = feng::to_expected( feng::solve( invertible(), b ) ); + REQUIRE( s.has_value() ); + REQUIRE( ( *s )[0][0] == 1.0 ); + REQUIRE( ( *s )[1][0] == 1.0 ); + auto const s_bad = feng::to_expected( feng::solve( singular(), b ) ); + REQUIRE( !s_bad.has_value() ); + REQUIRE( s_bad.error() == feng::linalg_status::singular ); + } + + SECTION( "linalg_result from the factorization members" ) + { + feng::matrix< double > const b{ 2, 1, { 3.0, 2.0 } }; + auto const lu = feng::lu_factor( invertible() ); + auto const lu_singular = feng::lu_factor( singular() ); + static_assert( std::is_same_v< decltype( feng::to_expected( lu.solve( b ) ) ), E > ); + static_assert( std::is_same_v< decltype( feng::to_expected( lu.inverse() ) ), E > ); + auto const x = feng::to_expected( lu.solve( b ) ); + REQUIRE( x.has_value() ); + REQUIRE( ( *x )[0][0] == 1.0 ); + REQUIRE( ( *x )[1][0] == 1.0 ); + REQUIRE( feng::to_expected( lu_singular.solve( b ) ).error_or( feng::linalg_status::ok ) == feng::linalg_status::singular ); + auto const inv = feng::to_expected( lu.inverse() ); + REQUIRE( inv.has_value() ); + REQUIRE( *inv == feng::matrix< double >{ 2, 2, { 1.0, -1.0, -1.0, 2.0 } } ); + REQUIRE( feng::to_expected( lu_singular.inverse() ).error_or( feng::linalg_status::ok ) == feng::linalg_status::singular ); + + auto const svd = feng::svd_factor( invertible() ); + auto const svd_nonfinite = feng::svd_factor( feng::matrix< double >{ 2, 2, { 1.0, std::numeric_limits< double >::quiet_NaN(), 0.0, 1.0 } } ); + static_assert( std::is_same_v< decltype( feng::to_expected( svd.pinverse() ) ), E > ); + auto const p = feng::to_expected( svd.pinverse() ); + REQUIRE( p.has_value() ); + REQUIRE( p->row() == 2 ); + REQUIRE( p->col() == 2 ); + double const expected[2][2] = { { 1.0, -1.0 }, { -1.0, 2.0 } }; + for ( std::size_t r = 0; r != 2; ++r ) + for ( std::size_t c = 0; c != 2; ++c ) + REQUIRE( std::abs( ( *p )[r][c] - expected[r][c] ) < 1.0e-12 ); + auto const p_bad = feng::to_expected( svd_nonfinite.pinverse() ); + REQUIRE( !p_bad.has_value() ); + REQUIRE( p_bad.error() == feng::linalg_status::nonfinite ); + } + + SECTION( "factorization objects" ) + { + auto const lu = feng::to_expected( feng::lu_factor( invertible() ) ); + static_assert( std::is_same_v< std::remove_const_t< decltype( lu ) >, std::expected< feng::lu_factorization< double >, feng::linalg_status > > ); + REQUIRE( lu.has_value() ); + REQUIRE( lu->rank() == 2 ); + auto const lu_bad = feng::to_expected( feng::lu_factor( singular() ) ); + REQUIRE( !lu_bad.has_value() ); + REQUIRE( lu_bad.error() == feng::linalg_status::singular ); + + auto const svd = feng::to_expected( feng::svd_factor( invertible() ) ); + static_assert( std::is_same_v< std::remove_const_t< decltype( svd ) >, std::expected< feng::svd_factorization< double >, feng::linalg_status > > ); + REQUIRE( svd.has_value() ); + auto const svd_bad = feng::to_expected( feng::svd_factor( feng::matrix< double >{ 2, 2, { 1.0, std::numeric_limits< double >::quiet_NaN(), 0.0, 1.0 } } ) ); + REQUIRE( !svd_bad.has_value() ); + REQUIRE( svd_bad.error() == feng::linalg_status::nonfinite ); + + auto const ch = feng::to_expected( feng::cholesky_factor( invertible() ) ); + static_assert( std::is_same_v< std::remove_const_t< decltype( ch ) >, std::expected< feng::cholesky_factorization< double >, feng::linalg_status > > ); + REQUIRE( ch.has_value() ); + auto const ch_bad = feng::to_expected( feng::cholesky_factor( singular() ) ); + REQUIRE( !ch_bad.has_value() ); + REQUIRE( ch_bad.error() == feng::linalg_status::not_positive_definite ); + + auto const re = feng::to_expected( feng::row_echelon( singular() ) ); + static_assert( std::is_same_v< std::remove_const_t< decltype( re ) >, std::expected< feng::rref_result< double >, feng::linalg_status > > ); + REQUIRE( re.has_value() ); + REQUIRE( re->rank == 1 ); + auto const re_bad = feng::to_expected( feng::row_echelon( feng::matrix< double >{ 1, 1, std::numeric_limits< double >::infinity() } ) ); + REQUIRE( !re_bad.has_value() ); + REQUIRE( re_bad.error() == feng::linalg_status::nonfinite ); + } + + SECTION( "load_expected for each format" ) + { + auto const m = invertible(); + std::string const npy = s9_r5::temp( "m.npy" ), txt = s9_r5::temp( "m.txt" ), bin = s9_r5::temp( "m.bin" ); + for ( auto const& path : { npy, txt, bin } ) // fresh files under the run's TMPDIR + std::filesystem::remove( path ); + REQUIRE( m.save_as_npy( npy ) ); + REQUIRE( m.save_as_txt( txt ) ); + REQUIRE( m.save_as_binary( bin ) ); + static_assert( std::is_same_v< decltype( feng::load_expected< double >( npy, feng::io_format::npy ) ), std::expected< feng::matrix< double >, feng::io_status > > ); + static_assert( noexcept( feng::load_expected< double >( npy, feng::io_format::npy ) ) ); + static_assert( s9_r5::has_load_expected< double, feng::io_format > ); + for ( auto const& [path, format] : { std::pair{ npy, feng::io_format::npy }, std::pair{ txt, feng::io_format::txt }, std::pair{ bin, feng::io_format::binary } } ) + { + auto const r = feng::load_expected< double >( path, format ); + REQUIRE( r.has_value() ); + REQUIRE( *r == m ); + auto const bad = feng::load_expected< double >( s9_r5::missing(), format ); + REQUIRE( !bad.has_value() ); + REQUIRE( bad.error() == feng::io_status::failed ); + } + std::filesystem::remove( npy ); + std::filesystem::remove( txt ); + std::filesystem::remove( bin ); + } +} +#else +TEST_CASE( "S9-R5 status results stay available in C++20", "[S9][S9-R5]" ) +{ + static_assert( !s9_r5::has_to_expected< feng::linalg_result< feng::matrix< double > > > ); + static_assert( !s9_r5::has_to_expected< feng::lu_factorization< double > > ); + static_assert( !s9_r5::has_load_expected< double, feng::io_format > ); + + auto const ok = feng::try_inverse( s9_r5::invertible() ); + REQUIRE( ok.ok() ); + REQUIRE( ok.value[1][1] == 2.0 ); + auto const bad = feng::try_inverse( s9_r5::singular() ); + REQUIRE( !bad ); + REQUIRE( bad.status == feng::linalg_status::singular ); + REQUIRE( feng::lu_factor( s9_r5::invertible() ).status() == feng::linalg_status::ok ); + REQUIRE( feng::lu_factor( s9_r5::singular() ).status() == feng::linalg_status::singular ); + + feng::matrix< double > m; + REQUIRE( !m.load_npy( s9_r5::missing() ) ); + std::string const npy = s9_r5::temp( "c20.npy" ); + REQUIRE( s9_r5::invertible().save_as_npy( npy ) ); + REQUIRE( m.load_npy( npy ) ); + REQUIRE( m == s9_r5::invertible() ); + std::filesystem::remove( npy ); +} +#endif diff --git a/tests/cases/s9_r6.hpp b/tests/cases/s9_r6.hpp new file mode 100644 index 0000000..e6230d7 --- /dev/null +++ b/tests/cases/s9_r6.hpp @@ -0,0 +1,181 @@ +// S9-R6 (PR-12): the one elementwise transform helper. matrix_details::transform( m, f ) builds a matrix of m's +// shape whose element type is f's result (or the R of transform< R >) and whose allocator is m's rebound to it; +// the unary family goes through it and keeps its pre-S9 result types and values. +#include "./s3_alloc.hpp" + +#include +#include +#include +#include + +namespace s9_r6 +{ + using alloc = s3_alloc::tracking_allocator< double, true, true, true >; + using amat = feng::matrix< double, alloc >; + template < typename T > + using rebound = feng::matrix< T, s3_alloc::tracking_allocator< T, true, true, true > >; + + inline amat sample( int id, std::size_t r, std::size_t c ) + { + amat m{ alloc{ id }, r, c }; + for ( std::size_t i = 0; i != m.size(); ++i ) + m.data()[i] = 0.25 + 0.5 * static_cast< double >( i % 97 ) - 3.0 * static_cast< double >( i % 5 ); + return m; + } +} + +TEST_CASE( "S9-R6 transform keeps shape, allocator and result type", "[S9][S9-R6]" ) +{ + using s9_r6::amat; + using s9_r6::rebound; + s3_alloc::reset(); + { + auto const m = s9_r6::sample( 7, 3, 5 ); + + SECTION( "the helper: result type from the callable or given" ) + { + auto const t = feng::matrix_details::transform( m, []( double x ) { return static_cast< int >( x * 2.0 ); } ); + static_assert( std::is_same_v< std::remove_const_t< decltype( t ) >, rebound< int > > ); + REQUIRE( t.row() == 3 ); + REQUIRE( t.col() == 5 ); + REQUIRE( t.get_allocator().id == 7 ); + for ( std::size_t i = 0; i != m.size(); ++i ) + REQUIRE( t.data()[i] == static_cast< int >( m.data()[i] * 2.0 ) ); + auto const f = feng::matrix_details::transform< float >( m, []( double x ) { return x + 1.0; } ); + static_assert( std::is_same_v< std::remove_const_t< decltype( f ) >, rebound< float > > ); + REQUIRE( f.get_allocator().id == 7 ); + REQUIRE( f.data()[4] == static_cast< float >( m.data()[4] + 1.0 ) ); + feng::matrix< double > const empty; + REQUIRE( feng::matrix_details::transform( empty, []( double x ) { return x; } ).size() == 0 ); + } + + SECTION( "the unary family keeps its result types and the allocator" ) + { + static_assert( std::is_same_v< decltype( feng::abs( m ) ), amat > ); + static_assert( std::is_same_v< decltype( feng::sqrt( m ) ), amat > ); + static_assert( std::is_same_v< decltype( feng::sqrt( feng::matrix< int >{} ) ), feng::matrix< int > > ); + static_assert( std::is_same_v< decltype( feng::ilogb( m ) ), rebound< int > > ); + static_assert( std::is_same_v< decltype( feng::lrint( m ) ), rebound< long > > ); + static_assert( std::is_same_v< decltype( feng::llrint( m ) ), rebound< long long > > ); + static_assert( std::is_same_v< decltype( feng::lround( m ) ), rebound< long > > ); + static_assert( std::is_same_v< decltype( feng::llround( m ) ), rebound< long long > > ); + auto const a = feng::abs( m ); + auto const l = feng::lround( m ); + auto const g = feng::ilogb( m ); + REQUIRE( a.row() == 3 ); + REQUIRE( a.col() == 5 ); + REQUIRE( a.get_allocator().id == 7 ); + REQUIRE( l.get_allocator().id == 7 ); + REQUIRE( g.get_allocator().id == 7 ); + for ( std::size_t i = 0; i != m.size(); ++i ) + { + REQUIRE( a.data()[i] == std::abs( m.data()[i] ) ); + REQUIRE( l.data()[i] == std::lround( m.data()[i] ) ); + REQUIRE( g.data()[i] == std::ilogb( m.data()[i] ) ); + } + feng::matrix< int > const im{ 1, 3, { 2, 9, 10 } }; + auto const is = feng::sqrt( im ); + REQUIRE( is[0][0] == 1 ); + REQUIRE( is[0][1] == 3 ); + REQUIRE( is[0][2] == 3 ); + } + + SECTION( "a parallel-size input" ) + { + auto const big = s9_r6::sample( 9, 64, 80 ); // 5120 elements, above the parallel threshold + auto const e = feng::exp( big ); + auto const r = feng::llrint( big ); + REQUIRE( e.row() == 64 ); + REQUIRE( e.col() == 80 ); + REQUIRE( e.get_allocator().id == 9 ); + REQUIRE( r.get_allocator().id == 9 ); + for ( std::size_t i = 0; i != big.size(); ++i ) + { + REQUIRE( e.data()[i] == std::exp( big.data()[i] ) ); + REQUIRE( r.data()[i] == std::llrint( big.data()[i] ) ); + } + } + } + s3_alloc::require_balanced(); +} + +// S9-T11: the binary family (matrix-matrix, matrix-scalar, scalar-matrix) and the complex family go through the same +// helper; results keep m's type (binary, conj, proj) or the complex value type (real, imag, abs, arg, norm), and the +// allocator of the matrix operand. +TEST_CASE( "S9-R6 binary and complex families keep their values and types", "[S9][S9-R6]" ) +{ + using s9_r6::amat; + using s9_r6::rebound; + using C = std::complex< double >; + using cmat = rebound< C >; + s3_alloc::reset(); + { + auto const m = s9_r6::sample( 7, 3, 5 ); + auto n = s9_r6::sample( 8, 3, 5 ); + for ( std::size_t i = 0; i != n.size(); ++i ) + n.data()[i] = 1.5 - n.data()[i]; + + SECTION( "binary: matrix-matrix, matrix-scalar and scalar-matrix" ) + { + static_assert( std::is_same_v< decltype( feng::hypot( m, n ) ), amat > ); + static_assert( std::is_same_v< decltype( feng::hypot( m, 2.0 ) ), amat > ); + static_assert( std::is_same_v< decltype( feng::hypot( 2.0, m ) ), amat > ); + static_assert( std::is_same_v< decltype( feng::pow( m, 2 ) ), amat > ); + static_assert( std::is_same_v< decltype( feng::fma( m, n, m ) ), amat > ); + static_assert( std::is_same_v< decltype( feng::ldexp( feng::matrix< int >{}, 2.0 ) ), feng::matrix< int > > ); + auto const mm = feng::atan2( m, n ); + auto const ms = feng::atan2( m, 0.75 ); + auto const sm = feng::atan2( 0.75, m ); + auto const pi = feng::pow( m, 3 ); + auto const f3 = feng::fma( m, n, m ); + REQUIRE( mm.row() == 3 ); + REQUIRE( mm.col() == 5 ); + REQUIRE( mm.get_allocator().id == 7 ); + REQUIRE( ms.get_allocator().id == 7 ); + REQUIRE( sm.get_allocator().id == 7 ); + REQUIRE( f3.get_allocator().id == 7 ); + for ( std::size_t i = 0; i != m.size(); ++i ) + { + REQUIRE( mm.data()[i] == std::atan2( m.data()[i], n.data()[i] ) ); + REQUIRE( ms.data()[i] == std::atan2( m.data()[i], 0.75 ) ); + REQUIRE( sm.data()[i] == std::atan2( 0.75, m.data()[i] ) ); + REQUIRE( pi.data()[i] == std::pow( m.data()[i], 3 ) ); + REQUIRE( f3.data()[i] == std::fma( m.data()[i], n.data()[i], m.data()[i] ) ); + } + } + + SECTION( "complex: real, imag, abs, arg, norm, conj, proj" ) + { + cmat c{ s3_alloc::tracking_allocator< C, true, true, true >{ 5 }, 3, 5 }; + for ( std::size_t i = 0; i != c.size(); ++i ) + c.data()[i] = C{ m.data()[i], n.data()[i] }; + static_assert( std::is_same_v< decltype( feng::real( c ) ), amat > ); + static_assert( std::is_same_v< decltype( feng::imag( c ) ), amat > ); + static_assert( std::is_same_v< decltype( feng::abs( c ) ), amat > ); + static_assert( std::is_same_v< decltype( feng::arg( c ) ), amat > ); + static_assert( std::is_same_v< decltype( feng::norm( c ) ), amat > ); + static_assert( std::is_same_v< decltype( feng::conj( c ) ), cmat > ); + static_assert( std::is_same_v< decltype( feng::proj( c ) ), cmat > ); + static_assert( std::is_same_v< decltype( feng::real( feng::matrix< std::complex< float > >{} ) ), feng::matrix< float > > ); + auto const re = feng::real( c ); + auto const im = feng::imag( c ); + auto const ar = feng::arg( c ); + auto const no = feng::norm( c ); + auto const cj = feng::conj( c ); + REQUIRE( re.row() == 3 ); + REQUIRE( re.col() == 5 ); + REQUIRE( re.get_allocator().id == 5 ); + REQUIRE( no.get_allocator().id == 5 ); + REQUIRE( cj.get_allocator().id == 5 ); + for ( std::size_t i = 0; i != c.size(); ++i ) + { + REQUIRE( re.data()[i] == std::real( c.data()[i] ) ); + REQUIRE( im.data()[i] == std::imag( c.data()[i] ) ); + REQUIRE( ar.data()[i] == std::arg( c.data()[i] ) ); + REQUIRE( no.data()[i] == std::norm( c.data()[i] ) ); + REQUIRE( cj.data()[i] == std::conj( c.data()[i] ) ); + } + } + } + s3_alloc::require_balanced(); +} diff --git a/tests/compile_fail/S3/bool.cc b/tests/compile_fail/S3/bool.cc new file mode 100644 index 0000000..84781d9 --- /dev/null +++ b/tests/compile_fail/S3/bool.cc @@ -0,0 +1,11 @@ +// S3-R3: bool is not a matrix_element (D-012, D-017). +// expect: matrix_element +// expect-gcc: required for the satisfaction of 'matrix_element' +// expect-clang: does not satisfy 'matrix_element' +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix m; + (void)m; +} diff --git a/tests/compile_fail/S3/string.cc b/tests/compile_fail/S3/string.cc new file mode 100644 index 0000000..8b81451 --- /dev/null +++ b/tests/compile_fail/S3/string.cc @@ -0,0 +1,13 @@ +// S3-R3: std::string has a throwing copy constructor, so it is not a matrix_element (D-012). +// expect: matrix_element +// expect-gcc: required for the satisfaction of 'matrix_element' +// expect-clang: does not satisfy 'matrix_element' +#include + +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix m; + (void)m; +} diff --git a/tests/compile_fail/S3/throwing_copy.cc b/tests/compile_fail/S3/throwing_copy.cc new file mode 100644 index 0000000..5ceb768 --- /dev/null +++ b/tests/compile_fail/S3/throwing_copy.cc @@ -0,0 +1,20 @@ +// S3-R3: a type whose copy constructor is not noexcept is not a matrix_element (D-012). +// expect: matrix_element +// expect-gcc: required for the satisfaction of 'matrix_element' +// expect-clang: does not satisfy 'matrix_element' +#include "../../../matrix.hpp" + +struct throwing_copy +{ + throwing_copy() noexcept = default; + throwing_copy( throwing_copy const& ) noexcept( false ) {} + throwing_copy( throwing_copy&& ) noexcept = default; + throwing_copy& operator=( throwing_copy const& ) noexcept = default; + throwing_copy& operator=( throwing_copy&& ) noexcept = default; +}; + +int main() +{ + feng::matrix m; + (void)m; +} diff --git a/tests/compile_fail/S4/make_mutable_view_const_rvalue.cc b/tests/compile_fail/S4/make_mutable_view_const_rvalue.cc new file mode 100644 index 0000000..9d767a5 --- /dev/null +++ b/tests/compile_fail/S4/make_mutable_view_const_rvalue.cc @@ -0,0 +1,18 @@ +// S4-R3: make_mutable_view of a moved const owner is deleted (F06). +// expect: deleted +// expect-gcc: use of deleted function 'feng::mutable_matrix_view feng::make_mutable_view(const matrix&&, +// expect-clang: call to deleted function 'make_mutable_view' +#include "../../../matrix.hpp" + +#include +#include + +int main() +{ + using range = std::pair; + feng::matrix m{ 3, 3 }; + feng::matrix const& cm = m; + (void)cm; + auto v = feng::make_mutable_view( std::move( cm ), { 0, 1 }, { 0, 1 } ); + (void)v; +} diff --git a/tests/compile_fail/S4/make_mutable_view_temporary.cc b/tests/compile_fail/S4/make_mutable_view_temporary.cc new file mode 100644 index 0000000..a914937 --- /dev/null +++ b/tests/compile_fail/S4/make_mutable_view_temporary.cc @@ -0,0 +1,18 @@ +// S4-R3: make_mutable_view of a temporary owner is deleted (F06). +// expect: deleted +// expect-gcc: use of deleted function 'feng::mutable_matrix_view feng::make_mutable_view(matrix&&, +// expect-clang: call to deleted function 'make_mutable_view' +#include "../../../matrix.hpp" + +#include +#include + +int main() +{ + using range = std::pair; + feng::matrix m{ 3, 3 }; + feng::matrix const& cm = m; + (void)cm; + auto v = feng::make_mutable_view( feng::matrix{ 3, 3 }, { 0, 1 }, { 0, 1 } ); + (void)v; +} diff --git a/tests/compile_fail/S4/make_view_const_rvalue.cc b/tests/compile_fail/S4/make_view_const_rvalue.cc new file mode 100644 index 0000000..91d93a1 --- /dev/null +++ b/tests/compile_fail/S4/make_view_const_rvalue.cc @@ -0,0 +1,18 @@ +// S4-R3: make_view of a moved const owner is deleted (F06). +// expect: deleted +// expect-gcc: use of deleted function 'feng::matrix_view feng::make_view(const matrix&&, +// expect-clang: call to deleted function 'make_view' +#include "../../../matrix.hpp" + +#include +#include + +int main() +{ + using range = std::pair; + feng::matrix m{ 3, 3 }; + feng::matrix const& cm = m; + (void)cm; + auto v = feng::make_view( std::move( cm ), { 0, 1 }, { 0, 1 } ); + (void)v; +} diff --git a/tests/compile_fail/S4/make_view_temporary.cc b/tests/compile_fail/S4/make_view_temporary.cc new file mode 100644 index 0000000..7d0b50a --- /dev/null +++ b/tests/compile_fail/S4/make_view_temporary.cc @@ -0,0 +1,11 @@ +// S4-R3: make_view of a temporary owner is a deleted overload (F06). +// expect: deleted +// expect-gcc: use of deleted function 'feng::matrix_view feng::make_view(matrix&& +// expect-clang: call to deleted function 'make_view' +#include "../../../matrix.hpp" + +int main() +{ + auto v = feng::make_view( feng::matrix{ 3, 3 }, { 0, 1 }, { 0, 1 } ); + (void)v; +} diff --git a/tests/compile_fail/S4/matrix_view_const_rvalue.cc b/tests/compile_fail/S4/matrix_view_const_rvalue.cc new file mode 100644 index 0000000..1b8ea80 --- /dev/null +++ b/tests/compile_fail/S4/matrix_view_const_rvalue.cc @@ -0,0 +1,18 @@ +// S4-R3: the matrix_view constructor from a moved const owner is deleted (F06). +// expect: deleted +// expect-gcc: use of deleted function 'feng::matrix_view::matrix_view(const matrix_type&&, +// expect-clang: call to deleted constructor of 'feng::matrix_view>' +#include "../../../matrix.hpp" + +#include +#include + +int main() +{ + using range = std::pair; + feng::matrix m{ 3, 3 }; + feng::matrix const& cm = m; + (void)cm; + feng::matrix_view> v( std::move( cm ), range{ 0, 1 }, range{ 0, 1 } ); + (void)v; +} diff --git a/tests/compile_fail/S4/matrix_view_temporary.cc b/tests/compile_fail/S4/matrix_view_temporary.cc new file mode 100644 index 0000000..833e40c --- /dev/null +++ b/tests/compile_fail/S4/matrix_view_temporary.cc @@ -0,0 +1,18 @@ +// S4-R3: the matrix_view constructor from a temporary owner is deleted (F06). +// expect: deleted +// expect-gcc: use of deleted function 'feng::matrix_view::matrix_view(matrix_type&&, +// expect-clang: call to deleted constructor of 'feng::matrix_view>' +#include "../../../matrix.hpp" + +#include +#include + +int main() +{ + using range = std::pair; + feng::matrix m{ 3, 3 }; + feng::matrix const& cm = m; + (void)cm; + feng::matrix_view> v( feng::matrix{ 3, 3 }, range{ 0, 1 }, range{ 0, 1 } ); + (void)v; +} diff --git a/tests/compile_fail/S4/mutable_matrix_view_const_owner.cc b/tests/compile_fail/S4/mutable_matrix_view_const_owner.cc new file mode 100644 index 0000000..b161de9 --- /dev/null +++ b/tests/compile_fail/S4/mutable_matrix_view_const_owner.cc @@ -0,0 +1,18 @@ +// S4-R3: the mutable_matrix_view constructor from a const owner is deleted (F06). +// expect: deleted +// expect-gcc: use of deleted function 'feng::mutable_matrix_view::mutable_matrix_view(const matrix_type&, +// expect-clang: call to deleted constructor of 'feng::mutable_matrix_view>' +#include "../../../matrix.hpp" + +#include +#include + +int main() +{ + using range = std::pair; + feng::matrix m{ 3, 3 }; + feng::matrix const& cm = m; + (void)cm; + feng::mutable_matrix_view> v( cm, range{ 0, 1 }, range{ 0, 1 } ); + (void)v; +} diff --git a/tests/compile_fail/S4/mutable_matrix_view_const_rvalue.cc b/tests/compile_fail/S4/mutable_matrix_view_const_rvalue.cc new file mode 100644 index 0000000..c31e0d4 --- /dev/null +++ b/tests/compile_fail/S4/mutable_matrix_view_const_rvalue.cc @@ -0,0 +1,18 @@ +// S4-R3: the mutable_matrix_view constructor from a moved const owner is deleted (F06). +// expect: deleted +// expect-gcc: use of deleted function 'feng::mutable_matrix_view::mutable_matrix_view(const matrix_type&&, +// expect-clang: call to deleted constructor of 'feng::mutable_matrix_view>' +#include "../../../matrix.hpp" + +#include +#include + +int main() +{ + using range = std::pair; + feng::matrix m{ 3, 3 }; + feng::matrix const& cm = m; + (void)cm; + feng::mutable_matrix_view> v( std::move( cm ), range{ 0, 1 }, range{ 0, 1 } ); + (void)v; +} diff --git a/tests/compile_fail/S4/mutable_matrix_view_temporary.cc b/tests/compile_fail/S4/mutable_matrix_view_temporary.cc new file mode 100644 index 0000000..9b453e4 --- /dev/null +++ b/tests/compile_fail/S4/mutable_matrix_view_temporary.cc @@ -0,0 +1,18 @@ +// S4-R3: the mutable_matrix_view constructor from a temporary owner is deleted (F06). +// expect: deleted +// expect-gcc: use of deleted function 'feng::mutable_matrix_view::mutable_matrix_view(matrix_type&&, +// expect-clang: call to deleted constructor of 'feng::mutable_matrix_view>' +#include "../../../matrix.hpp" + +#include +#include + +int main() +{ + using range = std::pair; + feng::matrix m{ 3, 3 }; + feng::matrix const& cm = m; + (void)cm; + feng::mutable_matrix_view> v( feng::matrix{ 3, 3 }, range{ 0, 1 }, range{ 0, 1 } ); + (void)v; +} diff --git a/tests/compile_fail/S4/mutable_view_const_owner.cc b/tests/compile_fail/S4/mutable_view_const_owner.cc new file mode 100644 index 0000000..5fa3178 --- /dev/null +++ b/tests/compile_fail/S4/mutable_view_const_owner.cc @@ -0,0 +1,18 @@ +// S4-R3: make_mutable_view of a const owner is deleted (F06). +// expect: deleted +// expect-gcc: use of deleted function 'feng::mutable_matrix_view feng::make_mutable_view(const matrix&, +// expect-clang: call to deleted function 'make_mutable_view' +#include "../../../matrix.hpp" + +#include +#include + +int main() +{ + using range = std::pair; + feng::matrix m{ 3, 3 }; + feng::matrix const& cm = m; + (void)cm; + auto v = feng::make_mutable_view( cm, { 0, 1 }, { 0, 1 } ); + (void)v; +} diff --git a/tests/compile_fail/S7/det_int.cc b/tests/compile_fail/S7/det_int.cc new file mode 100644 index 0000000..0786740 --- /dev/null +++ b/tests/compile_fail/S7/det_int.cc @@ -0,0 +1,12 @@ +// S7-R2, D-027: det of an integral matrix fails the linalg_element constraint (S9-R2, D-034). +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 3, 1 }; + auto d = a.det(); + (void)d; +} diff --git a/tests/compile_fail/S7/inverse_int.cc b/tests/compile_fail/S7/inverse_int.cc new file mode 100644 index 0000000..fbbfbd3 --- /dev/null +++ b/tests/compile_fail/S7/inverse_int.cc @@ -0,0 +1,13 @@ +// S7-R2, D-027: inverse of an integral matrix fails the linalg_element constraint (S9-R2, D-034). +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 2, 2, 1L }; + feng::matrix out; + auto st = feng::inverse( a, out ); + (void)st; +} diff --git a/tests/compile_fail/S7/lu_factor_int.cc b/tests/compile_fail/S7/lu_factor_int.cc new file mode 100644 index 0000000..b9b5daa --- /dev/null +++ b/tests/compile_fail/S7/lu_factor_int.cc @@ -0,0 +1,12 @@ +// S7-R1, D-027: lu_factor of an integral matrix fails the linalg_element constraint (S9-R2, D-034). +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 3, 1 }; + auto f = feng::lu_factor( a ); + (void)f; +} diff --git a/tests/compile_fail/S9/arg_real.cc b/tests/compile_fail/S9/arg_real.cc new file mode 100644 index 0000000..d7bac30 --- /dev/null +++ b/tests/compile_fail/S9/arg_real.cc @@ -0,0 +1,12 @@ +// S9-R2, D-034: arg of a real matrix fails the ComplexMatrix constraint. +// expect: ComplexMatrix +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 2, 2, 1.0 }; + auto c = feng::arg( a ); + (void)c; +} diff --git a/tests/compile_fail/S9/cholesky_decomposition_int.cc b/tests/compile_fail/S9/cholesky_decomposition_int.cc new file mode 100644 index 0000000..1de076b --- /dev/null +++ b/tests/compile_fail/S9/cholesky_decomposition_int.cc @@ -0,0 +1,13 @@ +// S9-R2, D-034: the legacy cholesky_decomposition of an integral matrix fails the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 3, 1 }; + feng::matrix l; + auto st = feng::cholesky_decomposition( a, l ); + (void)st; +} diff --git a/tests/compile_fail/S9/cholesky_int.cc b/tests/compile_fail/S9/cholesky_int.cc new file mode 100644 index 0000000..638449f --- /dev/null +++ b/tests/compile_fail/S9/cholesky_int.cc @@ -0,0 +1,12 @@ +// S9-R2, D-034: cholesky_factor of an integral matrix fails the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 3, 1 }; + auto f = feng::cholesky_factor( a ); + (void)f; +} diff --git a/tests/compile_fail/S9/conj_real.cc b/tests/compile_fail/S9/conj_real.cc new file mode 100644 index 0000000..44c88b7 --- /dev/null +++ b/tests/compile_fail/S9/conj_real.cc @@ -0,0 +1,12 @@ +// S9-R2, D-034: conj of a real matrix fails the ComplexMatrix constraint. +// expect: ComplexMatrix +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 2, 2, 1.0 }; + auto c = feng::conj( a ); + (void)c; +} diff --git a/tests/compile_fail/S9/ctranspose_real.cc b/tests/compile_fail/S9/ctranspose_real.cc new file mode 100644 index 0000000..1608ae7 --- /dev/null +++ b/tests/compile_fail/S9/ctranspose_real.cc @@ -0,0 +1,12 @@ +// S9-R2, D-034: ctranspose of a real matrix fails the ComplexMatrix constraint. +// expect: ComplexMatrix +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 2, 3, 1.0 }; + auto c = feng::ctranspose( a ); + (void)c; +} diff --git a/tests/compile_fail/S9/det_int.cc b/tests/compile_fail/S9/det_int.cc new file mode 100644 index 0000000..9b46de5 --- /dev/null +++ b/tests/compile_fail/S9/det_int.cc @@ -0,0 +1,12 @@ +// S9-R2, D-034: det of an integral matrix fails the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 3, 1 }; + auto d = feng::det( a ); + (void)d; +} diff --git a/tests/compile_fail/S9/expm_int.cc b/tests/compile_fail/S9/expm_int.cc new file mode 100644 index 0000000..792f425 --- /dev/null +++ b/tests/compile_fail/S9/expm_int.cc @@ -0,0 +1,12 @@ +// S9-R2, D-034: expm of an integral matrix fails the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 3, 1 }; + auto e = feng::expm( a ); + (void)e; +} diff --git a/tests/compile_fail/S9/expm_status_int.cc b/tests/compile_fail/S9/expm_status_int.cc new file mode 100644 index 0000000..a033171 --- /dev/null +++ b/tests/compile_fail/S9/expm_status_int.cc @@ -0,0 +1,13 @@ +// S9-R2, D-034: the status overload expm( A, out ) on an integral matrix fails the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 3, 1 }; + feng::matrix out; + auto st = feng::expm( a, out ); + (void)st; +} diff --git a/tests/compile_fail/S9/imag_real.cc b/tests/compile_fail/S9/imag_real.cc new file mode 100644 index 0000000..5dc7d5c --- /dev/null +++ b/tests/compile_fail/S9/imag_real.cc @@ -0,0 +1,12 @@ +// S9-R2, D-034: imag of a real matrix fails the ComplexMatrix constraint. +// expect: ComplexMatrix +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 2, 2, 1.0 }; + auto c = feng::imag( a ); + (void)c; +} diff --git a/tests/compile_fail/S9/inverse_int.cc b/tests/compile_fail/S9/inverse_int.cc new file mode 100644 index 0000000..b252803 --- /dev/null +++ b/tests/compile_fail/S9/inverse_int.cc @@ -0,0 +1,14 @@ +// S9-R2, D-034: inverse of an integral matrix, member and free, fails the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 3, 1 }; + auto m = a.inverse(); + auto f = feng::inverse( a ); + (void)m; + (void)f; +} diff --git a/tests/compile_fail/S9/inverse_status_int.cc b/tests/compile_fail/S9/inverse_status_int.cc new file mode 100644 index 0000000..fc7fd14 --- /dev/null +++ b/tests/compile_fail/S9/inverse_status_int.cc @@ -0,0 +1,13 @@ +// S9-R2, D-034: the status overload inverse( A, out ) on an integral matrix fails the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 2, 2, 1L }; + feng::matrix out; + auto st = feng::inverse( a, out ); + (void)st; +} diff --git a/tests/compile_fail/S9/lu_decomposition_int.cc b/tests/compile_fail/S9/lu_decomposition_int.cc new file mode 100644 index 0000000..ba8b6d8 --- /dev/null +++ b/tests/compile_fail/S9/lu_decomposition_int.cc @@ -0,0 +1,12 @@ +// S9-R2, D-034: the legacy lu_decomposition of an integral matrix fails the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 3, 1 }; + auto f = feng::lu_decomposition( a ); + (void)f; +} diff --git a/tests/compile_fail/S9/lu_factor_int.cc b/tests/compile_fail/S9/lu_factor_int.cc new file mode 100644 index 0000000..24711f3 --- /dev/null +++ b/tests/compile_fail/S9/lu_factor_int.cc @@ -0,0 +1,12 @@ +// S9-R2, D-034: lu_factor of an integral matrix fails the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 3, 1 }; + auto f = feng::lu_factor( a ); + (void)f; +} diff --git a/tests/compile_fail/S9/lu_solver_int.cc b/tests/compile_fail/S9/lu_solver_int.cc new file mode 100644 index 0000000..71b33d9 --- /dev/null +++ b/tests/compile_fail/S9/lu_solver_int.cc @@ -0,0 +1,13 @@ +// S9-R2, D-034: the legacy lu_solver of an integral matrix fails the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 3, 1 }; + feng::matrix const b{ 3, 1, 1 }; + auto x = feng::lu_solver( a, b ); + (void)x; +} diff --git a/tests/compile_fail/S9/nodiscard_inverse_status.cc b/tests/compile_fail/S9/nodiscard_inverse_status.cc new file mode 100644 index 0000000..4820b9d --- /dev/null +++ b/tests/compile_fail/S9/nodiscard_inverse_status.cc @@ -0,0 +1,12 @@ +// S9-R1, D-034: discarding the linalg_status of inverse( A, out ) is diagnosed; under -Werror=unused-result it is an +// error. +// flags: -Werror=unused-result +// expect: nodiscard +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 2, 2, { 2.0, 0.0, 0.0, 2.0 } }; + feng::matrix out; + feng::inverse( a, out ); +} diff --git a/tests/compile_fail/S9/nodiscard_load_npy.cc b/tests/compile_fail/S9/nodiscard_load_npy.cc new file mode 100644 index 0000000..ee3b4f6 --- /dev/null +++ b/tests/compile_fail/S9/nodiscard_load_npy.cc @@ -0,0 +1,10 @@ +// S9-R1, D-034: discarding the bool of a loader is diagnosed; under -Werror=unused-result it is an error. +// flags: -Werror=unused-result +// expect: nodiscard +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix m; + m.load_npy( "./images/64.npy" ); +} diff --git a/tests/compile_fail/S9/nodiscard_lu_factor.cc b/tests/compile_fail/S9/nodiscard_lu_factor.cc new file mode 100644 index 0000000..7bca092 --- /dev/null +++ b/tests/compile_fail/S9/nodiscard_lu_factor.cc @@ -0,0 +1,10 @@ +// S9-R1, D-034: discarding a factorization is diagnosed; under -Werror=unused-result it is an error. +// flags: -Werror=unused-result +// expect: nodiscard +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 2, 2, { 2.0, 0.0, 0.0, 2.0 } }; + feng::lu_factor( a ); +} diff --git a/tests/compile_fail/S9/nodiscard_save.cc b/tests/compile_fail/S9/nodiscard_save.cc new file mode 100644 index 0000000..664e850 --- /dev/null +++ b/tests/compile_fail/S9/nodiscard_save.cc @@ -0,0 +1,10 @@ +// S9-R1, D-034: discarding the bool of a writer is diagnosed; under -Werror=unused-result it is an error. +// flags: -Werror=unused-result +// expect: nodiscard +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const m{ 2, 2, 1.0 }; + m.save_as_npy( "s9_nodiscard_save.npy" ); +} diff --git a/tests/compile_fail/S9/nodiscard_transpose.cc b/tests/compile_fail/S9/nodiscard_transpose.cc new file mode 100644 index 0000000..1e427e0 --- /dev/null +++ b/tests/compile_fail/S9/nodiscard_transpose.cc @@ -0,0 +1,12 @@ +// S9-R1, D-034: discarding a pure function's result, free or member, is diagnosed; under -Werror=unused-result it +// is an error. +// flags: -Werror=unused-result +// expect: nodiscard +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 2, 3, 1.0 }; + feng::transpose( a ); + a.transpose(); +} diff --git a/tests/compile_fail/S9/norm_real.cc b/tests/compile_fail/S9/norm_real.cc new file mode 100644 index 0000000..9929bcc --- /dev/null +++ b/tests/compile_fail/S9/norm_real.cc @@ -0,0 +1,12 @@ +// S9-R2, D-034: norm of a real matrix fails the ComplexMatrix constraint. +// expect: ComplexMatrix +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 2, 2, 1.0 }; + auto c = feng::norm( a ); + (void)c; +} diff --git a/tests/compile_fail/S9/pinv_int.cc b/tests/compile_fail/S9/pinv_int.cc new file mode 100644 index 0000000..61f944e --- /dev/null +++ b/tests/compile_fail/S9/pinv_int.cc @@ -0,0 +1,14 @@ +// S9-R2, D-034: the legacy pinv and svd_inverse of an integral matrix fail the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 2, 1 }; + auto p = feng::pinv( a ); + auto q = feng::svd_inverse( a ); + (void)p; + (void)q; +} diff --git a/tests/compile_fail/S9/pinverse_int.cc b/tests/compile_fail/S9/pinverse_int.cc new file mode 100644 index 0000000..6dfe15a --- /dev/null +++ b/tests/compile_fail/S9/pinverse_int.cc @@ -0,0 +1,15 @@ +// S9-R2, D-034: pinverse of an integral matrix, status and value forms, fails the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 2, 1 }; + feng::matrix out; + auto st = feng::pinverse( a, out ); + auto p = feng::pinverse( a ); + (void)st; + (void)p; +} diff --git a/tests/compile_fail/S9/polar_real.cc b/tests/compile_fail/S9/polar_real.cc new file mode 100644 index 0000000..938f3d9 --- /dev/null +++ b/tests/compile_fail/S9/polar_real.cc @@ -0,0 +1,12 @@ +// S9-R2, D-034: polar of a real matrix fails the ComplexMatrix constraint. +// expect: ComplexMatrix +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 2, 2, 1.0 }; + auto c = feng::polar( a ); + (void)c; +} diff --git a/tests/compile_fail/S9/proj_real.cc b/tests/compile_fail/S9/proj_real.cc new file mode 100644 index 0000000..4a5e7bf --- /dev/null +++ b/tests/compile_fail/S9/proj_real.cc @@ -0,0 +1,12 @@ +// S9-R2, D-034: proj of a real matrix fails the ComplexMatrix constraint. +// expect: ComplexMatrix +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 2, 2, 1.0 }; + auto c = feng::proj( a ); + (void)c; +} diff --git a/tests/compile_fail/S9/real_real.cc b/tests/compile_fail/S9/real_real.cc new file mode 100644 index 0000000..c491a7f --- /dev/null +++ b/tests/compile_fail/S9/real_real.cc @@ -0,0 +1,12 @@ +// S9-R2, D-034: real of a real matrix fails the ComplexMatrix constraint. +// expect: ComplexMatrix +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 2, 2, 1.0 }; + auto c = feng::real( a ); + (void)c; +} diff --git a/tests/compile_fail/S9/row_echelon_int.cc b/tests/compile_fail/S9/row_echelon_int.cc new file mode 100644 index 0000000..cad4fda --- /dev/null +++ b/tests/compile_fail/S9/row_echelon_int.cc @@ -0,0 +1,12 @@ +// S9-R2, D-034: row_echelon of an integral matrix fails the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 4, 1 }; + auto r = feng::row_echelon( a ); + (void)r; +} diff --git a/tests/compile_fail/S9/rref_int.cc b/tests/compile_fail/S9/rref_int.cc new file mode 100644 index 0000000..9f217c1 --- /dev/null +++ b/tests/compile_fail/S9/rref_int.cc @@ -0,0 +1,14 @@ +// S9-R2, D-034: the legacy rref and gauss_jordan_elimination of an integral matrix fail the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 4, 1 }; + auto r = feng::rref( a ); + auto g = feng::gauss_jordan_elimination( a ); + (void)r; + (void)g; +} diff --git a/tests/compile_fail/S9/solve_int.cc b/tests/compile_fail/S9/solve_int.cc new file mode 100644 index 0000000..f831944 --- /dev/null +++ b/tests/compile_fail/S9/solve_int.cc @@ -0,0 +1,13 @@ +// S9-R2, D-034: solve with an integral matrix fails the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 3, 1 }; + feng::matrix const b{ 3, 1, 1 }; + auto x = feng::solve( a, b ); + (void)x; +} diff --git a/tests/compile_fail/S9/svd_factor_int.cc b/tests/compile_fail/S9/svd_factor_int.cc new file mode 100644 index 0000000..f08a884 --- /dev/null +++ b/tests/compile_fail/S9/svd_factor_int.cc @@ -0,0 +1,12 @@ +// S9-R2, D-034: svd_factor of an integral matrix fails the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 2, 1 }; + auto f = feng::svd_factor( a ); + (void)f; +} diff --git a/tests/compile_fail/S9/svd_int.cc b/tests/compile_fail/S9/svd_int.cc new file mode 100644 index 0000000..fc16d0a --- /dev/null +++ b/tests/compile_fail/S9/svd_int.cc @@ -0,0 +1,14 @@ +// S9-R2, D-034: the legacy singular_value_decomposition and svd of an integral matrix fail the linalg_element constraint. +// expect: linalg_element +// expect-gcc: constraints not satisfied +// expect-clang: does not satisfy +#include "../../../matrix.hpp" + +int main() +{ + feng::matrix const a{ 3, 2, 1 }; + auto f = feng::singular_value_decomposition( a ); + auto g = feng::svd( a ); + (void)f; + (void)g; +} diff --git a/tests/config/tu_a.cc b/tests/config/tu_a.cc new file mode 100644 index 0000000..b6a44ce --- /dev/null +++ b/tests/config/tu_a.cc @@ -0,0 +1,37 @@ +// S9-R3, D-033: one half of the two-TU program of `tools/check.sh config`. The lane compiles it with +// -DCONFIG_EXPECT_PARALLEL=0|1 next to the configuration macros under test; main checks feng::parallel_mode in this +// TU and in tu_b.cc and the value tu_b computes with a matrix. Exit 0 only when all three hold. +#include "../../matrix.hpp" + +#include +#include + +#ifndef CONFIG_EXPECT_PARALLEL +#error "compile with -DCONFIG_EXPECT_PARALLEL=0 or 1" +#endif + +std::uint_least64_t tu_b_parallel_mode(); +double tu_b_trace_of_sum(); + +int main() +{ + int rc = 0; + if ( feng::parallel_mode != CONFIG_EXPECT_PARALLEL ) + { + std::printf( "tu_a: parallel_mode %d, expected %d\n", int( feng::parallel_mode ), int( CONFIG_EXPECT_PARALLEL ) ); + rc = 1; + } + if ( tu_b_parallel_mode() != feng::parallel_mode ) + { + std::printf( "tu_b: parallel_mode %d, tu_a %d\n", int( tu_b_parallel_mode() ), int( feng::parallel_mode ) ); + rc = 1; + } + double const t = tu_b_trace_of_sum(); + if ( t != 12.0 ) + { + std::printf( "tu_b: trace %g, expected 12\n", t ); + rc = 1; + } + if ( rc == 0 ) std::printf( "config: ok\n" ); + return rc; +} diff --git a/tests/config/tu_b.cc b/tests/config/tu_b.cc new file mode 100644 index 0000000..7531d27 --- /dev/null +++ b/tests/config/tu_b.cc @@ -0,0 +1,14 @@ +// S9-R3, D-033: the other half of the two-TU program of `tools/check.sh config`; it uses a matrix. +#include "../../matrix.hpp" + +#include + +std::uint_least64_t tu_b_parallel_mode() { return feng::parallel_mode; } + +double tu_b_trace_of_sum() +{ + feng::matrix a{ 3, 3, 1.0 }; + feng::matrix b{ 3, 3, 3.0 }; + feng::matrix const c = a + b; + return c( 0, 0 ) + c( 1, 1 ) + c( 2, 2 ); +} diff --git a/tests/fixtures/s5/bool.npy b/tests/fixtures/s5/bool.npy new file mode 100644 index 0000000000000000000000000000000000000000..9c7669e2acc9658d79f49c1b9d279be66b77524b GIT binary patch literal 134 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1JlVqr_qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= bXCxM+0{I$7Its>`ItsN4WCJcn1`q@Q6h|AV literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/c16_be.npy b/tests/fixtures/s5/c16_be.npy new file mode 100644 index 0000000000000000000000000000000000000000..97c7b36c4805b1ebc0175c1560f60dff36c2b4a6 GIT binary patch literal 192 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZmuzUJuA`uymS0p-l$aNvUzCyxl5k7RDNY57 w7iT0EqyqUGMmh>cnmP)#3giMV`wt9Y-~gc*4)8!2_7|W6P-;I+oeZit01Gl8(f|Me literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/c16_le.npy b/tests/fixtures/s5/c16_le.npy new file mode 100644 index 0000000000000000000000000000000000000000..472b934d8308082f4e2aa12c488f1947bd36fee0 GIT binary patch literal 192 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGMmh>cnmP)#3giMV1~B+w51|!24&gF<*biaII6ws$0O~3r A&;S4c literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/c8_be.npy b/tests/fixtures/s5/c8_be.npy new file mode 100644 index 0000000000000000000000000000000000000000..ec14c7c77b27a9d3729b6c30df2788ef6b93c9f1 GIT binary patch literal 160 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1Zmu#V~qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= wXCxM+0{I$7ItoUbItsN4WCJey1_lNP1_p)$2@DK&KsE^MZvcut0I?Vt03%f(ssI20 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/c8_le.npy b/tests/fixtures/s5/c8_le.npy new file mode 100644 index 0000000000000000000000000000000000000000..0e1994e6528e1e544b73923cf21830362761cafb GIT binary patch literal 160 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWd``qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= xXCxM+0{I$7ItoUbItsN4WCJb+h6Z~E1_lQPhJ*t^wjGcS1P%Kc7#=u)SO6FcAgBNU literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/f2.npy b/tests/fixtures/s5/f2.npy new file mode 100644 index 0000000000000000000000000000000000000000..45be057956273f6fceb62165d531cf99a45e4031 GIT binary patch literal 140 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+r@qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= jXCxM+0{I$7Its>`ItsN4WCJb+yM_Y{P6{pzt_{ZkvxgrU literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/f4_be.npy b/tests/fixtures/s5/f4_be.npy new file mode 100644 index 0000000000000000000000000000000000000000..d31125b5ef19780409d0f9890169bd14537c407a GIT binary patch literal 152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1Zmu8}_qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= rXCxM+0{I$7Its>`ItsN4WCJey0}KoY1Q-|`9Duk5h!+6)7Z?}-Em$D& literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/f4_le.npy b/tests/fixtures/s5/f4_le.npy new file mode 100644 index 0000000000000000000000000000000000000000..edfeb59de617b7ebed775088a4019bcd1d987dc1 GIT binary patch literal 152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+l>qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= tXCxM+0{I$7Its>`ItsN4WCJb+h6DBt3<3uj7#tiJ7+QdM0T5p}001G;AnyPG literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/f8_be.npy b/tests/fixtures/s5/f8_be.npy new file mode 100644 index 0000000000000000000000000000000000000000..8e2a382216b56869c50a33bcbe5ed876658e1e42 GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1Zmu8`^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= uXCxM+0{I$7Its>`ItsN4WCJey9}Hk{fC)l7a6oB+1~9`x1j0Wc1)%}`h99v2 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/f8_le.npy b/tests/fixtures/s5/f8_le.npy new file mode 100644 index 0000000000000000000000000000000000000000..568e6d4d918782ccaacee305d6e0015243f182b6 GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= uXCxM+0{I$7Its>`ItsN4WCJb+F!*5)p_vXqXbuN3-5>y=8APD8)BynF7ay+x literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/f8_v2.npy b/tests/fixtures/s5/f8_v2.npy new file mode 100644 index 0000000000000000000000000000000000000000..92c7c8dc62f577e54783b03ed9d671440ea0e241 GIT binary patch literal 176 zcmbR27wQ`j$;42?z`#(go|0OeT%>NLpl*|9p{}E#o|a!!Qk0k%pI?-c3KDlq%qdO< xiWg@j7Ni3C8b&$_#+o_`wF+bcE}*qQ@WUQLGaZ1?91dW*K>$KCh(Kwn0|2DWA8r5u literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/f8_v3.npy b/tests/fixtures/s5/f8_v3.npy new file mode 100644 index 0000000000000000000000000000000000000000..cc83368b25b8b4764ab11102a409f8af7b7c3e79 GIT binary patch literal 176 zcmbR27wQ`j$;?o~z`#(go|0OeT%>NLpl*|9p{}E#o|a!!Qk0k%pI?-c3KDlq%qdO< xiWg@j7Ni3C8b&$_#+o_`wF+bcE}*qQ@WUQLGaZ1?91dW*K>$KCh(Kwn0|2FWA8!Bv literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/fortran_2x3.npy b/tests/fixtures/s5/fortran_2x3.npy new file mode 100644 index 0000000000000000000000000000000000000000..099bf2ff22ea426440bce3c34c7e4014e2328a05 GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u sBo?Fsxf(_~3dWi`3bhL411<(I_+byG8U!35e5L~sS_I1HfYMS20M%w6ga7~l literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/i1.npy b/tests/fixtures/s5/i1.npy new file mode 100644 index 0000000000000000000000000000000000000000..220534a0ab0fbcda6abc57646d9d6a769426bf80 GIT binary patch literal 134 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1JlWC}~qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= dXCxM+0{I$7Its>`ItsN4WCN~-{|t-{^#CA99qRx9 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/i2_be.npy b/tests/fixtures/s5/i2_be.npy new file mode 100644 index 0000000000000000000000000000000000000000..1b4378599fdd3ccbd384c0c860be808e1b9d570c GIT binary patch literal 140 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZmuaM~qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= kXCxM+0{I$7Its>`ItsN4WCN}ShX4N<7#J8Cb?W~E0K8-$m;e9( literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/i2_le.npy b/tests/fixtures/s5/i2_le.npy new file mode 100644 index 0000000000000000000000000000000000000000..836de4e97bf7b0581d860820fbc52e60ca16d243 GIT binary patch literal 140 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC-`qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= kXCxM+0{I$7Its>`ItsN4WCJdShJXJV7#JCJ82{G;0K1_dmH+?% literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/i4_be.npy b/tests/fixtures/s5/i4_be.npy new file mode 100644 index 0000000000000000000000000000000000000000..9db497eb0f29ee877c918a4bee85641a79b5c986 GIT binary patch literal 152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZmuaG|qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= uXCxM+0{I$7Its>`ItsN4WCJb+1_s9e|Ns93(##Buf(8Ho9dD=y3IG6C+9t99 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/i4_le.npy b/tests/fixtures/s5/i4_le.npy new file mode 100644 index 0000000000000000000000000000000000000000..d958973645b2fbbf28b5f08262a8629a21ab8136 GIT binary patch literal 152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= sXCxM+0{I$7Its>`ItsN4WCJcn1_p+I|NsAI2GRwBj0_FOLA?L<09XkoumAu6 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/i8_be.npy b/tests/fixtures/s5/i8_be.npy new file mode 100644 index 0000000000000000000000000000000000000000..bf601b3ef3eace573f4738b9d0a022a57883808d GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZmuaD{qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= qXCxM+0{I$7Its>`ItsN4WCN}S1_<~M1yC-GVq{`ItsN4WCJb+2x#~Z1yDW%Ba8u}fgGrGJpkGmD`fxx literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/make_npy.py b/tests/fixtures/s5/make_npy.py new file mode 100644 index 0000000..416f17f --- /dev/null +++ b/tests/fixtures/s5/make_npy.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""Generate the S5-R1 NPY fixtures with numpy. Run from the repo root: + python3 tests/fixtures/s5/make_npy.py +The values are fixed so tests/cases/s5_r1.hpp can compare against them.""" +import os +import numpy as np + +here = os.path.dirname(os.path.abspath(__file__)) +base = np.array([[1.5, -2.25, 3.0], [4.125, 5.0, -6.5]]) + + +def save(name, arr, **kw): + np.save(os.path.join(here, name), arr, **kw) + + +save("f8_le.npy", base.astype("f8")) +save("f4_le.npy", base.astype("f4")) +save("c8_le.npy", np.array(cx, dtype="ktaptJ~-mVnYS4gjGQ9-IIG literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/u1.npy b/tests/fixtures/s5/u1.npy new file mode 100644 index 0000000000000000000000000000000000000000..8f31e5600ca66b0c27d04b206ec3d91a7dd1dd6f GIT binary patch literal 134 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1JQ);NLqoAIaUsO_*m=~X4l#&V(cT3DEP6dh= dXCxM+0{I$7Its>`ItsN4WCJb+#{UiMS^y$M9ccgn literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/u2_be.npy b/tests/fixtures/s5/u2_be.npy new file mode 100644 index 0000000000000000000000000000000000000000..5a34a612cecfc1816bfd39cb9d704de99408ebaa GIT binary patch literal 140 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZS8AlLqoAIaUsO_*m=~X4l#&V(cT3DEP6dh= kXCxM+0{I$7Its>`ItsN4WCJb+1_s9e{}~yCO#U+f0JvZtzyJUM literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/u2_le.npy b/tests/fixtures/s5/u2_le.npy new file mode 100644 index 0000000000000000000000000000000000000000..182bda73c6e49cb4a55562128888efe1d52c693d GIT binary patch literal 140 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZQ);BHqoAIaUsO_*m=~X4l#&V(cT3DEP6dh= kXCxM+0{I$7Its>`ItsN4WCJb+21bVe{}~ufgc$w<0JfDLz5oCK literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/u4_be.npy b/tests/fixtures/s5/u4_be.npy new file mode 100644 index 0000000000000000000000000000000000000000..6cae47a122929e680d28417e8a0f6621bfcf2aab GIT binary patch literal 152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZS8AfJqoAIaUsO_*m=~X4l#&V(cT3DEP6dh= oXCxM+0{I$7Its>`ItsN4WCJb+2w?mV1PqJ}3_>Ph75{-^05zB)jsO4v literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/u4_le.npy b/tests/fixtures/s5/u4_le.npy new file mode 100644 index 0000000000000000000000000000000000000000..18b4e1602e7256d92ab64a04925e0f2b7e16ffe1 GIT binary patch literal 152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZQ);5FqoAIaUsO_*m=~X4l#&V(cT3DEP6dh= mXCxM+0{I$7Its>`ItsN4WCJb+AYcUI|3Cm@RfL%cfw%xN>miH) literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/u8_be.npy b/tests/fixtures/s5/u8_be.npy new file mode 100644 index 0000000000000000000000000000000000000000..92c01df268c4703c2dfa872d38b52d414f888e89 GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZS8AcIqoAIaUsO_*m=~X4l#&V(cT3DEP6dh= uXCxM+0{I$7Its>`ItsN4WCJb+RKWNj3V_0lASp&=*YwWSXW##assjL=K`1%^ literal 0 HcmV?d00001 diff --git a/tests/fixtures/s5/u8_le.npy b/tests/fixtures/s5/u8_le.npy new file mode 100644 index 0000000000000000000000000000000000000000..0fa90c57a969c9b0536704ce4060cc8e7dd64d8d GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZQ);2EqoAIaUsO_*m=~X4l#&V(cT3DEP6dh= sXCxM+0{I$7Its>`ItsN4WCJb+C}4!r|DgcH1&O^sySg*oRT(Y~0G~i8H~;_u literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/cholesky_c16_6_A.npy b/tests/fixtures/s7/cholesky_c16_6_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..e4b3b5bfc1a1ed4fc0688a0e5a461368a96e5990 GIT binary patch literal 704 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGW;zOHnmP)#3giN=I_X7+Z~j(tV1R;?`KO~=Vz1fr_$2P`N`1b6i-W?~ zRGlOCVOJiSyEjR{=!ST+UJ5E+l$)Ynraupe4zR8{Pb%!7wykIWZL!B zZP$Jq?$%fOpZ3_JnP;C-m3m!9NEL3M--D;Qe_k;hxaoMQuEpT({{L5*T5mRew&!%8 z@J7XD%l-?0mOfsm{C)one}n0t4}aRfe|r`0=VkZo4-^HS5j}Uw9?gCG(af_~(DeVx zTd4xKujKi|qDTCf?b&_OWp;>t+`o47#*@p$pV_a_y1Ii`{^di#G+%+jiOhY|g484$gh{O+_bU zqjh)L->6985xRJM|CZ?+W`^8bY9Fg7v+_|XZu|Z>&6jw>w{U;!p#v+jPA%H6;qqrf zZ{cBk4R_520zKRJ`xxF;+m;$&pQmV?lw*Fu{siyzwG2!T?cF0x+iR>I?B8?OT>DG# ZrTuzeRJQ!|DA_+t*x}csQZ{(}0RTaEtJnYl literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/cholesky_f8_5_A.npy b/tests/fixtures/s7/cholesky_f8_5_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..cf54235f722ca018aec26c123686750ff895dca2 GIT binary patch literal 328 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%IItr$mItsN4WCO0PpO(E_wMNV#>h`Xg>(*YfU#NM$_~Z49_O|X39D1zx z_g`~L?vOBjX%CaP;4V9pHB-Ss`q7-#a+R0t6QYotuN7+MDEd#6C~CWj{5|XxY!Bjr*TnOyR1tV|4%k3Byt5 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/cholesky_f8_8_A.npy b/tests/fixtures/s7/cholesky_f8_8_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..d69e764c93975e2fe70246dd0e6b8ec692d0ebfc GIT binary patch literal 640 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#iItmt=ItsN4WCN}ZnPFd!7O6New9n4`ccys%hyU|d1-akfZ^t1W*cJ2C zUgFrzHI9>C?{8gNEPLDI&i*rtvKIZUd0-!WO)z(!?gx9AKEs2$`y*`C9K4pf2X0=r z!#+#i@Dr!V)BRD}vX2BruI%6CYn>q#^3neF?suAZHvG0X>Qqiyq4*VUA55Rh@jz>x zNEL@Sa*5feSAEr6a?;=ZI�xm~i~v|5DR})7|Xx{s|va_!p>tg8L8VPMCc#ea!Ek zx$Es$b#OO{-{1W7m3`UFqFs#4}r4((ckvN;sF+(uyBF- U59UsoeK36mH(bP|E~q*H0J%vg(EtDd literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/cholesky_f8_8_L.npy b/tests/fixtures/s7/cholesky_f8_8_L.npy new file mode 100644 index 0000000000000000000000000000000000000000..c7a69d197bac5d3206fc71fa2bc10feebb450930 GIT binary patch literal 640 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#iItmt=ItsN4WCJeqL%ggVQ#c(M$ON8W{lKRH!?*B!M#W}~t4LD(Vt`?nUHY_H&e zhqHI#)tl_LSM1Y1O#i(p?B;%t4UEf^^$+jA`BOCDx8Y^`&%IHvLJnNsU(EmfYUBTF W`@>!tZP(j-ZNF1MUsQ7krvm_S*Qu5O literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/det_c16_6_A.npy b/tests/fixtures/s7/det_c16_6_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..c2b69d072f26bb53ea96cd76773114c513f75bed GIT binary patch literal 704 zcmbV`{V&u39LK-rLY{5XQ1hMIj+}WI7s5xaWR;kaOAp9huFW`KqIu3P)0S$ZWpm9# zQdtS}yy}xiRxXG0n7Hp9?&NewM;c1{7kdBpdcA&mm!6FZ4L`3(WDwa*E-x*CV)~Ix z{{#;f@)IE<}3-naHGb3JP-5MT*5ai1W}5s)z>M@+z8037rOVIeb&Ur5D?5kNLF!ltbgu z8jVAk6bJXJa&r}*aU{C>$Iv%58XPJNSXM9K;83O6ws$)g7|qBw=r&xvkyNp9Z4x

`$HrI)f$ux+DSOdb21y zCJ)YdUfS|oq9IGeck27JfF!%1&dpm1wWf*D{0bHR33^gG=D7s^D@Urf)}~;Ueapiz za0muFg6+16CS)6a7&!4;39kup$;}!C63^FrY`;+0Wks)K$-e`iYJK|1QjSi9M9tt$ F<6oRVPTK$g literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/det_c16_6_D.npy b/tests/fixtures/s7/det_c16_6_D.npy new file mode 100644 index 0000000000000000000000000000000000000000..ce712407b52f11cf39f6e337243ed26199f86d9b GIT binary patch literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 p7iT0EqyqUGhB^v{nmP)#3giN=Pkg?0JN*vqU)_-AX}N{-002>MBU%6e literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/det_f8_3_singular_A.npy b/tests/fixtures/s7/det_f8_3_singular_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..db03d2914e3b94686b1ff5379daaba7e7b215ce9 GIT binary patch literal 200 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%oIts>`ItsN4WCJb+F!*2(p&1+?GzXLxfYKsRS^`STKxqXit>gdzr8^%& literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/det_f8_3_singular_D.npy b/tests/fixtures/s7/det_f8_3_singular_D.npy new file mode 100644 index 0000000000000000000000000000000000000000..a3128431b7d77f5fba7ab1266c3496ae35bf46e0 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= aXCxM+0{I$-ItqrGItsN4WCJb+C;$L5$s26| literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/det_f8_4_A.npy b/tests/fixtures/s7/det_f8_4_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..ae67a0b2cf94e379134fbd24b55be94661aa2255 GIT binary patch literal 256 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItnJ5ItsN4WCN}!ugMFp{C#L2aV4qhl*DQK4a;|bJbdexy}<9xd<&mf z`(>6d{b#@Xf_=m8bcb0--`OX8I{1D2?8E!5@|=B=&%M~6cIC|pn~=BmPqIrVOl`Yn zU-g7-hhgI5{USFXEazmqX3v+UpeNG#-rngAr|qffkMDq?2qZQ9Q;eb=*Z)>HQHiS+nTvGUsf z1!2#-kM$q0Ur}~)gQoi}dzS8uWs%Qs*fZ=aQBM`#Y;U2x_A=|0ll#9E>0J`q@L<0} k!L*2qS4M4rP8;^We~aj<(}1V$YxK zzvQ!KL9E?{{re-lABlZGy??La`Z{N`tM(uGLM-jA&g>W6kSBTR?t6P~8L2I$-1qkH z5vV#Qar2b@DUT(_F1}~%UAN9-`1$sV{f#Y7mM$-k*&jXlq($)dh5ddXiW6;H&+d1$ lT4v(DdDZ@WmJpMLffM&X^KRit`*g?NVd0@gt6o2`2LQl%rGNkc literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/expm_f8_4_A.npy b/tests/fixtures/s7/expm_f8_4_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..2f5c27ea4b174c1edb59e15a1a90ae0d67f7bc79 GIT binary patch literal 256 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItnJ5ItsN4WCJb+hmYN#?;hCyt)RAQUCE>U-zKd*b}{ai{e>GD2}$l} z>>KVZ$?r|sv!B6b&--5*mG&3(+Zp%o+PHs4ulK>lM_%rC5ZsaUG3m~JKffc4`(@wm z-_y>zaQggL_5m()FVvpBZQpU@-yyS*#MF0Q* literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/expm_f8_4_E.npy b/tests/fixtures/s7/expm_f8_4_E.npy new file mode 100644 index 0000000000000000000000000000000000000000..3767e7850c9516373ae7f7e9a82e580c8d8fa915 GIT binary patch literal 256 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItnJ5ItsN4WCJcAGj3_M8&B-roqt!^c|YABAGnpJ>gWf15w4Q+AGBZE zAL#Mfb9?WE{rxhk8C;tl*+&JY+8^TDY;VnXNHFsI>-~md=kC5&db0n|S@+%VES~S@ zEqOWRecfOCuWL^5u}`iuo;L5))BX4O+4y=Vy|4!W D5!+oy literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/expm_f8_6_norm5_A.npy b/tests/fixtures/s7/expm_f8_6_norm5_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..b314a098095b0309da356b9937093a3a99dd4e1d GIT binary patch literal 416 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$dItpf*ItsN4WCO1GYkL$gJr{Op3v*e*m&D^Rt$1n3iK%P{6vFgVHkb%I za7x2c@BJg%AxmW{s;SI`!_8M&w0Dw;9jTx`CCj5G2dm@?q|BapKo^a z%yvfR0}D>PemmWHi+wKpDP z%D&lOKjTmJjUONFSqcL3w^{KV@Jrdv81jnUVW+^1H!E|54m|ab(AT@lbl}vx&V9>k Pp4qSAn`fH6hWh{j%DJTH literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/expm_f8_6_norm5_E.npy b/tests/fixtures/s7/expm_f8_6_norm5_E.npy new file mode 100644 index 0000000000000000000000000000000000000000..2121e6e3fc2af8e6e62db7af7f24ecec7f4e0a9b GIT binary patch literal 416 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$dItpf*ItsN4WCN~?52q-vcH?u9Gzt@CVg9lIR)ubB=*R2(Cp-NWY0CU* zfBvDMr|YS|_Mr)d4c2wf?58rZLli!C^*J&5pWi-UB`p zHPaUyW^zbdyR~rJLAC?dU5&rfB0uie2shIx&ik-GW46x2O7^Gr2ih2x9@2cb-=X>5 zDSge=`&}RIcXs*lWq(+jrqi|MANS8GxF)pi=Cl3J{NGODGW}tHEYr(n+A)>`C4O8& z0=s_Oi^WTHUE1|){}U@s);pgV9nw;(nU}BlYd^`&?ZibPh67(}Z&w^=;&sq*)|=vR P`Oki~W5!Qng4rDa;-0Bn literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/inv_c16_4_A.npy b/tests/fixtures/s7/inv_c16_4_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..548d21e47e2434efd9c48133714b94b1a39d0347 GIT binary patch literal 384 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGCOQfxnmP)#3giMVMFG888j}1DQI9#-+{=4xKf^Pzb#cc*`$Ian!&=w9 z+y6jkt9QKID|-fohK|W+SJ|`7j?_F*a(};ooKF*5*1`Q5C)#`Cb{(^K*mJYtt>4N0 zVw@ooKlbrDJTmF5oSpi5|DAB_$2l1f><=-mOgydi#@-=w>+qa`~LnLaeFR&Jo;jP#Tzo%?>CT{-`Fqo)P7o@`J50vAqTzQtSx1G9@_%|)p47+ literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/inv_c16_4_X.npy b/tests/fixtures/s7/inv_c16_4_X.npy new file mode 100644 index 0000000000000000000000000000000000000000..72997e92da11fcdcea2677559364b458a666f7c6 GIT binary patch literal 384 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGCOQfxnmP)#3giMVz7NYkaiyNO4->L@{&C&H{SUQIeDXNZu|MQK*YVZ7 zYwcJ3bzSRmYtjBsKhEd>=<3)n*6gsHUuA;5V5iR6V1e5G&ysg`S*%&GpKDfk){-aH z_6gU2ipXrZV9&p^rtXyYYI~I=cK5`uCHo7P?af%)vuS_UlI5=zyvp{+vg*3Ys5RK@ zM4kJq`((8}OIdAg=7UxHXLLW~*s-V8-Y@-9Zk@v{dk4D@2iEMlWbdeTVEK<{EA5wR zt7qM7T5mt~;as_I59Zmo`tQ4(acG78)fc*ZRLq_1Wxcu{e+rpmU+lS{q{m^}{$j4$ jc(!FL>>K5bbLKx;v0ov?qp3FLoPEX-(`Bib7VHNAEOnhS literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/inv_f8_5_A.npy b/tests/fixtures/s7/inv_f8_5_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..b48d9c36eb1b0e949c8e067d11d0c01fdf82fba6 GIT binary patch literal 328 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%IItr$mItsN4WCJekR=?gK;&D=pTjrX5Le%={wz6yX zGlZ=b{Id0${hIj6{B~t;_d8ZKx;)GfaQLC<|8CdjC;PqLTL`o*dS^eS)F|zK(9Qi8 zX}env_xVb;#g7onT>H-cgO5d`xynbr0!?1VLi?o;a3aV+`c5I966LOaq ZFos^VzaY=0uAF|to^4x@;&pXSZ)nw_M{jQq}Ds7=ZB!UT_L25{iB~q;t+jtv^7&ajyt%}t$6=UmNh5>)mt^n81MO{p0vX;~h$%d`rnNG!{f zGUEv5v3xF*O)&FGX_+*)IGdD;#Q!`ow@@bDb(ti$M7;Y^TsFaFu?acx-_au`d??kR z4db_|5BKClVCihi@++G{%LsLTPmdWz+H}MBx*TcZ>7-UYTTtqo~DHY>hzpnvraWh`C8nQ~dJ zi)BW&fv=0>O`~Zw{8_hT6#A!+#*v;*JULh1rr7U*5M|?}d}jvK1rG0&1RX?fhgp>e zUGVt`BU|&tfC0kH>np}N?1-=hC>EBW`IgvSJiG!5@5gWVjZx6|A}^AY^9eXb{K1xN zJJNr)GZvd1P*xBVDve%5PA6~D!I}a|^B13l<4dS$-5iLPbbD!2`2Gu#?a}0FAMwyM6EHR literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/inv_f8_8_X.npy b/tests/fixtures/s7/inv_f8_8_X.npy new file mode 100644 index 0000000000000000000000000000000000000000..7c9548ab11af5534958b8c7b474104f743054893 GIT binary patch literal 640 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#iItmt=ItsN4WCO0N%sy5Ncc0yF9Ja`#IQE16^0U`X6HZ;*pQ*81*L~M^ zdyAD`f-fbu?B|^~_x0`*TkMwxd{S>Ycwzr`%V|ww4|eSrS=xD5B6YR>hVqT4nAxxF z7h8SPJN5jE{oxxf&wU|v&3;j|Qqi26yq^wkJ^70*5>9ByKm3EFqVxqeWyKF z(3A{`>udIxSMJ}UTli+b^`U7+3nEw8_i&_7*>&Q~e#^GJ%X{7w>{sHPZvXrDMf-mf zdVVp9-?cw>QS{u|wwLzK8M}0=R4(uL+J9z)tKK8~8ybYzQ3O1|}3 zi^ulqw~wCRe)NHTR@}vamTh_nhe)`cqD?DT86z0eK v6Xf{i!ZseY&%d{R`+Dt1_HqyPxt;|-+0S9NMSj-e7yAoq^^^tYEwl#!y^$kj literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/lu_solve_c16_5x2_A.npy b/tests/fixtures/s7/lu_solve_c16_5x2_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..cda2f1a7085940223541712fe5edb3a50b9e8c74 GIT binary patch literal 528 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGraB6ynmP)#3giN=S6cr6rCy6Uym@_1A*J)Z{g$$lLvDMY*@rw9lTQ(P zxBpX7%ldQAAKORRd@i0DcGCVpF8jAP9ryOn(0Ws>_2R8Pi&`n87XR!0d_Q#e_SL@F zucTCL`gGD8`+|R$8@KFuxPRppn|a(XMH~Vm*#$bc@3b#zIeYQxmG}GCc)6{xD7|C< zq5W&QjLR$g2S?6_#xm`;XQ(=%&dUDY{)1ZWinyE|_DdXvSr$dz-k-70x_a8?H~aUk zh?8Oyeqyh2f!AGR{yqCgCjaY>t`KlAaP8gi@cG*QhKTiF4$j=a|3m+-EK`@)`+NAO znK^|%v6u1aXzUJuYp?Ouf4W2L@%;?KQ{-d{uGlA>ZCn3KXOaDb*rO}vF5b4kB%M<& zyXnRLhI4zqv>v}}zuSHKP0?mC2Z!JzFa9lFwx1#YTV7m6;eM^zq8BExyx5<1K6{Fx z%!BTfIuQb7Rt8U50{U^^IjGu3CWWO(u89!&r z-uy{0`Wv~*I^6cV%CQ~~ytdPRkx{=_zQ!{9=x+A!%;Jab z&$T>0v*7T){fl#6np~^iY`^#L_BMgX%kA&^on&5e>zIAzjS17@7FFy&sM64CSe`vj^>B6ez_Qj_)Wm?Tw+XDdU0c)WE literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/lu_solve_f8_4x2_A.npy b/tests/fixtures/s7/lu_solve_f8_4x2_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..6950d29093943a30ff2d89bbf0743591ba1d21ba GIT binary patch literal 256 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItnJ5ItsN4WCJc96{dyr`M4YyTz<~nwK9Bv0?+h&r+=TcKbC!I@>k7w z`&-^Ba3pp-+wWvH``OPNVTXAiWj0yWz23jU`R=R}8=vpr;$d((bK@KP4|{&a95z00 zzv)SCXzFubhXa*bHyuyGwV&_TKk;evb6+6` E03(NCxc~qF literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/lu_solve_f8_4x2_B.npy b/tests/fixtures/s7/lu_solve_f8_4x2_B.npy new file mode 100644 index 0000000000000000000000000000000000000000..6e70edc41784cb6989dc6afd662fc23644e55f93 GIT binary patch literal 192 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItoUbItsN4WCJdS$Y}913vcglC>L4u!}NfCmCvH|Ln)8;UwAD(`TqV_ o`%U8B&7PM1a{mv8-g7&QPVJAFSK4)G+mZd#UR(`*bpM?_0JVQXZU6uP literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/lu_solve_f8_4x2_X.npy b/tests/fixtures/s7/lu_solve_f8_4x2_X.npy new file mode 100644 index 0000000000000000000000000000000000000000..2d3bbf5b3ddcdb88b066040a398fd1ddf08fb06b GIT binary patch literal 192 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItoUbItsN4WCN}#X~(ko%nt46SGiLA)NhCVsy(GXJD(rhzi5A#xPHW; o{ps`Hsabd)-R~vyN=9|h#{I1e?kyAFv~j=A1BV-YuaDUS0Bv18L;wH) literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/lu_solve_f8_8x3_A.npy b/tests/fixtures/s7/lu_solve_f8_8x3_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..ec4993598b3db8c815819bd37acd651b818bd621 GIT binary patch literal 640 zcmbWr`A^aT7=Uprb&Kh$Sy>+2S8d@z$5tv6*XwFwVc?NimX1h3TuumDGZl4{VAQ_nT@ufu) zatc9C6;R0(f-De8u1L6L9Fc^_|L1gWsg%F&QXyB&U;kJtg`mb!2pRF;ks_M97kx7} zpSLf zHf3lns?ZJeAqZ5jgmNCc`CEOFVc%e2% zeU`n&1KLZ~yZrO!;C`$2aht<~!jL|ub;$$UW%0|bFbhyWF%lY0E)d)HewkF5kYT*# zsr%)?q|N=R0K+sGE(?>zIxAG|-V`aHa6pc1lt_qlVh?d+#UyYcN!f0s+3Y|sWwkW+ zen;N^EWKXtg`oLxvHqM3RRd?HEt)|%k?{wX-xx9Ct&O9u|BP)-=0wXi7fip0VcAM<9u+j~F24CSr+CfN5-O*nj^%~8Azc0Rbjl*9atu2HA literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/lu_solve_f8_8x3_B.npy b/tests/fixtures/s7/lu_solve_f8_8x3_B.npy new file mode 100644 index 0000000000000000000000000000000000000000..00116d12458ad1ac4303591e8de3d8d3b385ce6d GIT binary patch literal 320 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#iIts>`ItsN4WCJdSKMLKor*7=;n{lE^_S-{yo4a4NOZPt5Kg0YU<2ixn z_6&z#3WRRz+~4y2dCgswtM)HUQa5xxK5XytzAeeU_xb)NM{VZZ^!N5lX6qiZop#gS zW2eP3)?;_=1#YDs@ICryzlN#1*UVQ}_cvrrD%v1<$^PGV-vew~FYOOZO^c{|KXZS` zzx$b-)i?I1C`%mM`t-_vDaH5=7e$}%572%0uATRpeM85i?LAdD>@Ni8U0*%xv^~R) T6^~~)t+5x&iT%IS`@wzy?RbLh literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/lu_solve_f8_8x3_X.npy b/tests/fixtures/s7/lu_solve_f8_8x3_X.npy new file mode 100644 index 0000000000000000000000000000000000000000..a8b8082708f868d0eadc3423b688ecc9db9b0389 GIT binary patch literal 320 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#iIts>`ItsN4WCJdbeR_xG)?Tpx^l}H^MBCH$k@82lA30vyzrMue%al70 z_O}+BeCt&IZEtbvdN9k$-}}$&PdUZ*_2T~Tx9tvWYI|W{tG_rjN8{=Krl9Wj$-i&x z4-e^><9Gj?y@7(0!=%f<_H#Pc6)m25Wxr?ov=a;F+_Ps%m?jo>;M@LXw%4U&)_&e! zIqTi^&@ `, files relative +to this directory. Case names are `__`; the element type is the second field of the name. +Ops and their files (all C-order, little-endian ` max(a.shape) * EPS * s[0]] + return float(s[0] / s[-1]) + + +def tol_of(x): + """two significant digits, rounded up, so the manifest text is short and stable.""" + return float("%.1e" % (x * 1.05)) + + +# ---- exact elimination with Fractions (Gaussian rationals for complex) ---- +class Q: + """a + b i with Fraction parts.""" + + __slots__ = ("re", "im") + + def __init__(self, re, im=Fraction(0)): + self.re, self.im = Fraction(re), Fraction(im) + + def __sub__(self, o): + return Q(self.re - o.re, self.im - o.im) + + def __mul__(self, o): + return Q(self.re * o.re - self.im * o.im, self.re * o.im + self.im * o.re) + + def __truediv__(self, o): + d = o.re * o.re + o.im * o.im + return Q((self.re * o.re + self.im * o.im) / d, (self.im * o.re - self.re * o.im) / d) + + def iszero(self): + return self.re == 0 and self.im == 0 + + +def to_q(a): + return [[Q(Fraction(float(np.real(x))), Fraction(float(np.imag(x)))) for x in row] for row in a] + + +def exact_rref(a): + m = to_q(a) + rows, cols = len(m), len(m[0]) + r = 0 + for c in range(cols): + p = next((i for i in range(r, rows) if not m[i][c].iszero()), None) + if p is None: + continue + m[r], m[p] = m[p], m[r] + piv = m[r][c] + m[r] = [x / piv for x in m[r]] + for i in range(rows): + if i != r and not m[i][c].iszero(): + f = m[i][c] + m[i] = [x - f * y for x, y in zip(m[i], m[r])] + r += 1 + if r == rows: + break + out = np.array([[complex(float(x.re), float(x.im)) for x in row] for row in m]) + return out if np.iscomplexobj(a) else out.real.copy() + + +def exact_det(a): + m = to_q(a) + n = len(m) + d = Q(1) + for c in range(n): + p = next((i for i in range(c, n) if not m[i][c].iszero()), None) + if p is None: + return complex(0.0) if np.iscomplexobj(a) else 0.0 + if p != c: + m[c], m[p] = m[p], m[c] + d = Q(0) - d + d = d * m[c][c] + for i in range(c + 1, n): + f = m[i][c] / m[c][c] + m[i] = [x - f * y for x, y in zip(m[i], m[c])] + return complex(float(d.re), float(d.im)) if np.iscomplexobj(a) else float(d.re) + + +# ---- writing ---- +LINES = [] + + +def save(case, role, arr): + arr = np.ascontiguousarray(arr) + arr = arr.astype(" cut] + # every dropped value must sit far below the cutoff so feng and numpy keep the same rank + assert all(x < cut / 10 for x in s[s <= cut]), (tag, s, cut) + assert all(x > cut * 1e6 for x in kept), (tag, s, cut) + x = np.linalg.pinv(a, rtol=p * EPS) + k = float(kept[0] / kept[-1]) + add("pinv_%s_%dx%d%s" % (kind(a), m, n, tag), "pinv", 64 * p * EPS * k * k, [("A", a), ("X", x)]) + + +def cholesky(seed, n, complex_): + b = cplx(seed, n, n) if complex_ else real(seed, n, n) + a = b @ b.conj().T + n * np.eye(n) + a = (a + a.conj().T) / 2 # exactly Hermitian + add("cholesky_%s_%d" % (kind(a), n), "cholesky", 64 * n * EPS * cond(a), [("A", a), ("L", np.linalg.cholesky(a))]) + + +def rref(a, tag=""): + m, n = a.shape + r = exact_rref(a) + pivots = [next(j for j in range(n) if r[i, j] != 0) for i in range(m) if np.any(r[i] != 0)] + k = cond(a[:, pivots]) + add("rref_%s_%dx%d%s" % (kind(a), m, n, tag), "rref", 64 * max(m, n) * EPS * k, [("A", a), ("R", r)]) + + +def expm(a, tag=""): + n = a.shape[0] + nrm = max(1.0, float(np.linalg.norm(a, 1))) + add("expm_%s_%d%s" % (kind(a), n, tag), "expm", 256 * n * EPS * nrm, [("A", a), ("E", scipy.linalg.expm(a))]) + + +def main(): + for f in os.listdir(HERE): + if f.endswith(".npy"): + os.remove(os.path.join(HERE, f)) + + lu_solve(11, well(real(11, 4, 4)), 2) + lu_solve(12, real(12, 8, 8), 3) + lu_solve(13, well(cplx(13, 5, 5)), 2) + + det(real(21, 4, 4)) + det(cplx(23, 6, 6)) + det(np.array([[1.0, 2, 3], [4, 5, 6], [7, 8, 9]]), exact=True) + + inv(well(real(31, 5, 5))) + inv(real(32, 8, 8)) + inv(well(cplx(33, 4, 4))) + + svdvals(real(42, 8, 5)) + svdvals(real(43, 4, 7)) + svdvals(low_rank(44, 6, 6, 3), "_rank3") + svdvals(cplx(45, 7, 4)) + svdvals(cplx(46, 3, 6)) + + pinv(real(51, 6, 4)) + pinv(real(52, 3, 5)) + pinv(low_rank(53, 5, 5, 3), "_rank3") + pinv(cplx(54, 5, 3)) + pinv(low_rank(55, 4, 6, 2, complex_=True), "_rank2") + + cholesky(61, 5, False) + cholesky(62, 8, False) + cholesky(63, 6, True) + + rref(np.array([[2.0, 1, 1], [1, 3, 2], [1, 0, 0]])) + rref(low_rank(71, 4, 4, 2), "_rank2") + rref(low_rank(72, 5, 3, 2), "_rank2") + rref(low_rank(73, 3, 5, 3)) + rref(low_rank(74, 3, 4, 2, complex_=True), "_rank2") + + expm(real(81, 4, 4)) + expm(5 * real(82, 6, 6), "_norm5") + expm(cplx(83, 4, 4)) + + header = [ + "# S7-R6 oracle fixtures, written by tests/fixtures/s7/make_fixtures.py; do not edit by hand.", + "# python %s, numpy %s, scipy %s" % (sys.version.split()[0], np.__version__, scipy.__version__), + "# ", + ] + with open(os.path.join(HERE, "manifest.txt"), "w", newline="\n") as f: + f.write("\n".join(header + LINES) + "\n") + + +if __name__ == "__main__": + main() diff --git a/tests/fixtures/s7/manifest.txt b/tests/fixtures/s7/manifest.txt new file mode 100644 index 0000000..8ab7183 --- /dev/null +++ b/tests/fixtures/s7/manifest.txt @@ -0,0 +1,33 @@ +# S7-R6 oracle fixtures, written by tests/fixtures/s7/make_fixtures.py; do not edit by hand. +# python 3.14.7, numpy 2.5.3, scipy 1.18.1 +# +lu_solve_f8_4x2 lu_solve 1.1e-13 lu_solve_f8_4x2_A.npy lu_solve_f8_4x2_B.npy lu_solve_f8_4x2_X.npy +lu_solve_f8_8x3 lu_solve 1.6e-12 lu_solve_f8_8x3_A.npy lu_solve_f8_8x3_B.npy lu_solve_f8_8x3_X.npy +lu_solve_c16_5x2 lu_solve 1.6e-13 lu_solve_c16_5x2_A.npy lu_solve_c16_5x2_B.npy lu_solve_c16_5x2_X.npy +det_f8_4 det 5e-13 det_f8_4_A.npy det_f8_4_D.npy +det_c16_6 det 1.2e-12 det_c16_6_A.npy det_c16_6_D.npy +det_f8_3_singular det 4.5e-14 det_f8_3_singular_A.npy det_f8_3_singular_D.npy +inv_f8_5 inv 1.4e-13 inv_f8_5_A.npy inv_f8_5_X.npy +inv_f8_8 inv 7.6e-13 inv_f8_8_A.npy inv_f8_8_X.npy +inv_c16_4 inv 1.3e-13 inv_c16_4_A.npy inv_c16_4_X.npy +svdvals_f8_8x5 svdvals 6e-14 svdvals_f8_8x5_A.npy svdvals_f8_8x5_S.npy +svdvals_f8_4x7 svdvals 5.2e-14 svdvals_f8_4x7_A.npy svdvals_f8_4x7_S.npy +svdvals_f8_6x6_rank3 svdvals 4.5e-14 svdvals_f8_6x6_rank3_A.npy svdvals_f8_6x6_rank3_S.npy +svdvals_c16_7x4 svdvals 5.2e-14 svdvals_c16_7x4_A.npy svdvals_c16_7x4_S.npy +svdvals_c16_3x6 svdvals 4.5e-14 svdvals_c16_3x6_A.npy svdvals_c16_3x6_S.npy +pinv_f8_6x4 pinv 4.6e-12 pinv_f8_6x4_A.npy pinv_f8_6x4_X.npy +pinv_f8_3x5 pinv 2e-11 pinv_f8_3x5_A.npy pinv_f8_3x5_X.npy +pinv_f8_5x5_rank3 pinv 9.9e-12 pinv_f8_5x5_rank3_A.npy pinv_f8_5x5_rank3_X.npy +pinv_c16_5x3 pinv 5.2e-13 pinv_c16_5x3_A.npy pinv_c16_5x3_X.npy +pinv_c16_4x6_rank2 pinv 5.9e-13 pinv_c16_4x6_rank2_A.npy pinv_c16_4x6_rank2_X.npy +cholesky_f8_5 cholesky 1.5e-13 cholesky_f8_5_A.npy cholesky_f8_5_L.npy +cholesky_f8_8 cholesky 2.5e-13 cholesky_f8_8_A.npy cholesky_f8_8_L.npy +cholesky_c16_6 cholesky 2.2e-13 cholesky_c16_6_A.npy cholesky_c16_6_L.npy +rref_f8_3x3 rref 1.3e-12 rref_f8_3x3_A.npy rref_f8_3x3_R.npy +rref_f8_4x4_rank2 rref 1e-13 rref_f8_4x4_rank2_A.npy rref_f8_4x4_rank2_R.npy +rref_f8_5x3_rank2 rref 1.7e-13 rref_f8_5x3_rank2_A.npy rref_f8_5x3_rank2_R.npy +rref_f8_3x5 rref 4.2e-13 rref_f8_3x5_A.npy rref_f8_3x5_R.npy +rref_c16_3x4_rank2 rref 3e-13 rref_c16_3x4_rank2_A.npy rref_c16_3x4_rank2_R.npy +expm_f8_4 expm 5.9e-13 expm_f8_4_A.npy expm_f8_4_E.npy +expm_f8_6_norm5 expm 7.3e-12 expm_f8_6_norm5_A.npy expm_f8_6_norm5_E.npy +expm_c16_4 expm 7e-13 expm_c16_4_A.npy expm_c16_4_E.npy diff --git a/tests/fixtures/s7/pinv_c16_4x6_rank2_A.npy b/tests/fixtures/s7/pinv_c16_4x6_rank2_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..2cc95e0a45520a113ed4f0a5d619654061e5277d GIT binary patch literal 512 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGCOQgcnmP)#3giMV1~3pf0HHtFLud{M2(57dl@Ak_fXW*{X&7Gx%7>|g z=w@JmnXhmFA_7y-0p&9|K*W`xw8#MnA7-8iR2;?^fbtoj>R|3xfQsuu#bM^d)XPA{ uO`z(OpnRCQYEVART$p|tsCt0jm$SPcKk*IHc6SKXhKBjQEaHdoTIR?nEXN%nj{ald{Q%SSh! z3j3m~d&NWJ~Tgnd9J{|@biby zU2a|WD=hOf?<-~R|M&7meMEbd{d%Z>gszk`g;e#}Ptcp&%WB)azoT_MXR%DBeO>Qc z(SKVS?5)A>Xxm?Xa^u|U+W!3kixw=3X(_bdw(y{IS(bgU?5e%Q=a@~FkDu7jVNpL>b@cgu6-HC-<&R(N zKUQv#x!m)${X4_JYfX3RbYQ>Z`7P^_~Y4={WBuZ&N{+#cmIP- T2h{?LhxP`Jcek0Rz1j<^q#;@AujC``4q# z2kh@{JSsA^dZ+#NSvyoe@on3$CGc*8U*Bc>72dHM9IfWshi60=N_k!0|JW(RamLXz z`#%|1o;b{Ne!oNe?4}EjoA!$^tzz4J_R;=d8zq3YUi-*dHTp&^B4%+Wsl(CU2x~Y}vp3fY$t0k)!(;d|gqnX4(1u z9yR4T(I|?_U~lMhco-< UEUi_4>bc9FA+1- v->yBnU?1_)-obF6{haHo_SfwxP;O{=vENM3r1ZjvU*%_ literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/pinv_f8_3x5_X.npy b/tests/fixtures/s7/pinv_f8_3x5_X.npy new file mode 100644 index 0000000000000000000000000000000000000000..f5146563b9eebe37fbfecd371bfd407b0377a69b GIT binary patch literal 248 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%IIts>`ItsN4WCN}v+Pd}JYVYk!ye*WUE@X1}l@)E`{D|p5(xz6WA3@jl z-&&f^8201K{yU3*g>pH4v;XldJL^x%14{W^e%eGmX-vI!5Tvn_A literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/pinv_f8_5x5_rank3_A.npy b/tests/fixtures/s7/pinv_f8_5x5_rank3_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..4942104e0cc10e1ec3038b84e60d42205a0bdb5e GIT binary patch literal 328 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%IItr$mItsN4WCJb+6d>RLUPZl;(iaB2ZclO`HMBhtmuY0MpNK i0Kyl5(jrh=0!kY~X_!1rJ%Ee+eZ6j){eo0m-J(s)_XmLOo4w!Vs(R6EhpqM(#2uei zoo%=OC|Js9{Bz>|6;pz|G?#AKpFX!macc73{V&*OS$DB-wqG(sKI5H6oxO^{?)|eo YXYX%uR&k#DYuWxiyDWkZKUlXP026kBmjD0& literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/pinv_f8_6x4_A.npy b/tests/fixtures/s7/pinv_f8_6x4_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..7ef48f7931755219ca920f5d00bc12259b8aa468 GIT binary patch literal 320 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$dItnJ5ItsN4WCN~gb0i*q33y|#5!10@!}=ThOGK||S;XJpZ@25U)w}}_ z_Y17~(x@Hy$iAiF_>>aGNBdtqyYZBD;$!=WXv4pIkKeWbvGJSXB(@{_57@QWP1-qO z|DCC4Vi^oy*(+$pP4S%n$lheHT@|awv;8(Zp5Fa>@4mgoEq~A4D<}3F*-NsT7r(c! zs(O5vr4(px(4T6d*T?qfc@^Kh6!*qHVdWDIi#t)uoKYbQQ3SoPFi&-}-7E%!b4b)F{}*KW9FFQhRiGB$0Oeb@DD5gVaL_6NEe SU;gpyC!#ngdG1^uyG_ r^uzcZ4iFYhTd^V<>WSwA E0Fs6xkN^Mx literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/rref_f8_3x3_A.npy b/tests/fixtures/s7/rref_f8_3x3_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..85a4d78b9ac4910127df4a6169b4025fca6041ba GIT binary patch literal 200 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= qXCxM+0{I%oIts>`ItsN4WCJb+2ylQ`ItsN4WCJb+F!*2(p>dIfN{c{g6$b_|;DFK)9s>hR{KI~T P2u#1k0jLTn&2az#N|+^{ literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/rref_f8_3x5_R.npy b/tests/fixtures/s7/rref_f8_3x5_R.npy new file mode 100644 index 0000000000000000000000000000000000000000..423003e269b046e4c2c9f2cc340f6db2cb69d0cb GIT binary patch literal 248 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%oItr$mItsN4WCJb+F!*2(p%J8r^+sXqjfeL`<)Ji89-$th?lM&V0fsqH Q{q{FI=ica?`vNWx020kE1ONa4 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/rref_f8_4x4_rank2_A.npy b/tests/fixtures/s7/rref_f8_4x4_rank2_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..6c96e41eee96badfc79e5c6841f42fbcc1d4403e GIT binary patch literal 256 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItnJ5ItsN4WCJb+FyJ@4 F>;b$tA?N@A literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/rref_f8_4x4_rank2_R.npy b/tests/fixtures/s7/rref_f8_4x4_rank2_R.npy new file mode 100644 index 0000000000000000000000000000000000000000..89f12ad125022bfc095dd0c1904b87da940c3fde GIT binary patch literal 256 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItnJ5ItsN4WCJb+F!*2(p%G*#7(CsN5CO4Z>VDX-UIhZ5F!Z6SC(Hu? DE7>R5 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/rref_f8_5x3_rank2_A.npy b/tests/fixtures/s7/rref_f8_5x3_rank2_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..3aea65f2dbe17f2b05fa2a24f316f2234784ec26 GIT binary patch literal 248 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%IIts>`ItsN4WCJb+Fc5Ko&>!p}w9)|x%>bokptOPmM4SW4htV=naS1eW N4X8K+loo)i0|1k{B`^R0 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/rref_f8_5x3_rank2_R.npy b/tests/fixtures/s7/rref_f8_5x3_rank2_R.npy new file mode 100644 index 0000000000000000000000000000000000000000..57e1dbce273ac9b9e501b9d69fa4e6831bd60ae1 GIT binary patch literal 248 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%IIts>`ItsN4WCJb+F!*2(p&1xfuL6Nz`(b>Td?*+^MH5F=MG6l9Z#E|> literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/svdvals_c16_3x6_A.npy b/tests/fixtures/s7/svdvals_c16_3x6_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..08ad68f7df331295612ed84121589ffc4035fe1f GIT binary patch literal 416 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG#ySdSnmP)#3giMV7VQU%v%kKy4>)>Y&OxO!`)j@$Ms4SNy}x32?75vz z*Y+!Hvee3#xx3#aM?m4kiTnE-_SoN!+voKQ5dQlRy62eu00JvP#eU z{acptL|IRGwg1JD4aYBJJlP*2_UP8kt_$`IjB5;LZN6xqwSV*4?~5MVGu&!;I`81p z{S7l77;eg+ZqKmjhr{>vN%kfOt*%L)zi(gQKcn1S^Sb@E9KE+cB45}~5Pdy?t?12u zhMXhYYQ9D9cgd)7;e2=3KJQz?)}8ks*$agDRj-?eEHP T`LNaP<^DhN&VQF#-?0Y(>q)l1 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/svdvals_c16_3x6_S.npy b/tests/fixtures/s7/svdvals_c16_3x6_S.npy new file mode 100644 index 0000000000000000000000000000000000000000..3341744aab55e1b4e9f28d3c621213ccadf65e1b GIT binary patch literal 152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= uXCxM+0{I%oI+{8PwF(pfF7cc>Q^PaZ9OMGZUABJuYrpf|EqA}ipX>o`Q(Z`g6jUac!8!OZaGeg^HgNhfsH+Dp}y{F(dn z`ThpZB*&%qAK0s1zkF%Q@rU*<`nnd!K0UVY+0*H||MHXl49*Q(?tYqLKjoE#MA(g6 z_6Z8njhi1lw9nbD$SQRGru{toCFZT4AK5d!+<$di{&M>p`;Y0|bv$8T^VQr#sr;?| zgJ;`s<)$CsU*Wp&)pfHA`w#3q@-peoRePzIB`f`Rzuj-L$#KQqfD85w%eLj8IMuXY zqV>y(7l)tPf4i1`ct7Xk{W1Rhi;_3qv7hj@_Ww7o7yBJ{9!zK0zSh3$RtPIo>w9~% zg(8Y ze6-n=@ZA2!dmn{WitmBuuPfU)?}EKdhNM+Z(mVTgpE&)}{=eBD^7d!rlBsv>@2pgL zv9spQ{)+b%>;5#~vOg#A*8Fksi~S+(dX?7`p4gX!nVxfddeffcon6LWwd?y2EJ~ey zSNHAy3!I!)hJFY3Z<)5*Ibp#~`v-TXKVoLOu-`!1K$g4t$o_x3J}i&?^4xwxNT*Wn hjtl$mywN|fr}z5)Dc^q^h^sx@UlM)y#`=sa`vJ=D7YYCX literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/svdvals_c16_7x4_S.npy b/tests/fixtures/s7/svdvals_c16_7x4_S.npy new file mode 100644 index 0000000000000000000000000000000000000000..78a9b804e01606223034392dd6237cefdc6c0aac GIT binary patch literal 160 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yI+{8PwF(pfu9MFvSO4zkaQN2NSf&4i(ZOl^&shKBU-rz!4mG`=@9hEG Cb1O~& literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/svdvals_f8_4x7_A.npy b/tests/fixtures/s7/svdvals_f8_4x7_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..7bec5446172b0b195c4d66b6f25bf13c51083671 GIT binary patch literal 352 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItu2RItsN4WCN~}bq*K0>Mq+e1giZxVf1$Y2mafau5N#~KWnq##uZzi z+DE)7+s){I%%0E5=}Ydshx`BOn1#m5KCn-TIo}#;e{X-q^PNwEYmV$^*s$YYrqIs) z3^^wc-B*5V&-GMd?<JqbE)4~zaQQ&aY*Te!QS`# z9o~7)(J#HR-$!8a+Rm46>`k6EELUCn)Lvl8nQb9|HrhAj+otI4n7e<=@shb;YTnpi z$o*UHeCxQq%!M`UK83%r-?qs*#ry7i`xfTfV`r|t-GAlkty2w|FZSPY-yTxE{ee9I D4FsId literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/svdvals_f8_4x7_S.npy b/tests/fixtures/s7/svdvals_f8_4x7_S.npy new file mode 100644 index 0000000000000000000000000000000000000000..dfd1c2428d05a8d86b3e9bcfcb7dd773a6b98c22 GIT binary patch literal 160 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yI+{8PwF(pfF3(kYyw&@d96ks+t7bo9a0o4N3Eo@!*}kcJW__^NBYOan C=_uv^ literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/svdvals_f8_6x6_rank3_A.npy b/tests/fixtures/s7/svdvals_f8_6x6_rank3_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..eeb41deb658ea33d4f15b8764d8a3e9e904ddedb GIT binary patch literal 416 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$dItpf*ItsN4WCJb+FyL^2&^iYov<#GHfYK^Z`on&Rya1Fh-~iz>Kxq-E zxCE5{!5$*7geI;8m50bOFu>Hq#9{Wq?AL&*hsne2H-w7A+{FL|F#AjoKm=4Apfr>Z OGZ)>TFmpLz>Kp*L$}^k* literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/svdvals_f8_6x6_rank3_S.npy b/tests/fixtures/s7/svdvals_f8_6x6_rank3_S.npy new file mode 100644 index 0000000000000000000000000000000000000000..f6e586ecdcc2e5a9151dbc193e41df1365f65122 GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$dI+{8PwF(pfu4B@sZSTK1I^-DlvaVcf>9Ew~^JisO0|%`zrx5O|k8S4V T-EY6xcg;qYZ~a@n%jawWimEZh literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/svdvals_f8_8x5_A.npy b/tests/fixtures/s7/svdvals_f8_8x5_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..84774c3e259158417e2212f6c7441c4ab26c2a29 GIT binary patch literal 448 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#iItr$mItsN4WCJdbZLXi|Iv&~!+}U6g?7e?~%eL7YTs}XucVXaWk9NLk zuOPj7(*GYX_g~qVxO+yyJNpS@@4cJ1J+N1K{dUCzzen~y>hV>7&OF~gfhTAUbNbHx z6<_RkZRotP|H-;;Nx6X6_9)-TkvGwrTu3j zTxP$Le`l}KcBl5K#!Gv)f!zl#}gdod#>6803poA AYybcN literal 0 HcmV?d00001 diff --git a/tests/fixtures/s7/svdvals_f8_8x5_S.npy b/tests/fixtures/s7/svdvals_f8_8x5_S.npy new file mode 100644 index 0000000000000000000000000000000000000000..d69b19be61d36034c14f4c0ce42dadb1b4695f48 GIT binary patch literal 168 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%II+{8PwF(pfF7EFN*ZG$+I~?zMlz%eykG=gr*3ZG&Pt2 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_f8_1x6_1x3_B.npy b/tests/fixtures/s8/conv_f8_1x6_1x3_B.npy new file mode 100644 index 0000000000000000000000000000000000000000..6056f8cb2582bd93ff58c3c78807dbf8122ddf30 GIT binary patch literal 152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= wXCxM+0{I$-Its>`ItsN4WCJdTaGAiQ)NS?;6sFcJx%t$-=jBXMd&UR$0BxZrV*mgE literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_f8_5x1_2x1_A.npy b/tests/fixtures/s8/conv_f8_5x1_2x1_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..189a1f82ca0eec37927d2a54428ebb6d9c422320 GIT binary patch literal 168 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%IItqrGItsN4WCN~6$;);a_P(}v*dVg^SH(>GCGD~=zZu`QXJF0Rw$;4I O{*3<0iuDKG#4 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_f8_5x1_2x1_B.npy b/tests/fixtures/s8/conv_f8_5x1_2x1_B.npy new file mode 100644 index 0000000000000000000000000000000000000000..ae38dc97e63646ad28afc6beca79af6773e79226 GIT binary patch literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= oXCxM+0{I$7ItqrGItsN4WCO0CrHTESTF>@JZMayYr1f+^08-r})Bpeg literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_f8_7x9_10x11_A.npy b/tests/fixtures/s8/conv_f8_7x9_10x11_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..590e4ec3911e56928a514d444ea2ba03702263f6 GIT binary patch literal 632 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%|ItrGWItsN4WCO00+mkh~CB3mvxu$lb_TR<*Z3{c99IPJMFJMo*!Wehl z{=udV!iB-N_E)4%=lsC?+J4XC!ea`HpWBNZdRP52`Qm=Q9V{8`9`Ec!+NN$!S@>{& ziF`?yUGHQ27NM1sW~*PfZ>XBl?Z0U4{(|N^wI1y6_8(ieVO_k-OZ$vnTMkaSdC1-% zoR#m8$eI01UU*G^F!R#>GZj{1w~8;@Z#j8j%A_fm_cxr_E-Ay)Z*P$Bw z?*!l3&ybwUb@kX&`!8K;6E6fj*uP=BnjDL~vsr>>6yFUly zZ{EMF!9Pquj_B^7u@WKx{|ha zzsr@I|8GrvXzx&6JICwD+xJLu|9Eqa}9P zlnX1mXx)lxgTpr-QP0ksQ6wZHLeN{Sx?=V(?DN~_^UJ4%e%YIGMT?L|$e>2EI8iCo zuP9W<7<(#>LXAmENll5o9-fpE&HAsqMY1`p!_J9|OlBRrwVf@EVrOSVqhwP4Cwk49 zNAH~@j)U}BNY9Iz2S^|~wl=ZTz_GRZE$iq4aEY~19mjb{AFs~(&`gbaT{|PhG6`0u z>hT;dZ-Q$KcQ(UIfoYZ@7yodPgS;clyaRUQH|yY`Ck!^?GqFD%OCe&osUG_bldT z`SOeCYN*U1osk&U!mDf1B8%Hn^rB7W$`W6K`BSH$0r4sXELs(u;;w?5i}KbqQH9Pw zNJiTp&*E0SS@v#DH;9>9QlW=n?coZ3(G|z+=d2^cE;OFkG=V`u z29F0n>4S8mQ4_)7CNKsa{L*H{c*i_%%r8uiV^tUUUJgpM)$m_ghfjd8YCb$XM2HXf?8**FP=0I>~aARX9~ zWp(4xQmoW(^y1NlXfKjVnoEbUdcde<+fasNpE{CHyb5miOa<JFkg%9^g{mA&t z$R|wDg<_Xq*ULC7XyreA@)}W&eHQP&GnrPyn<&H0p{aRrVeU5d{v$=w#>S$mc>zv* zD#&%~UciX+BweS9CG04g=ZvYAP**=B_%>gI6oc*Odq34daGR$Ads_`ckD8!ijS?aX zJ!_l`+Mpv*|12|04U8vr_w9Sb$Y}Im*d?^#)XrJ=(sm_q`z^^c#awWeygSdjE(g7O z!Q5D=9CiJYq>05cbdEZ>QO*%S-yQ!7UhOJICpZbxipGGO`(uLkmI920JI{Kl%b*=E ztq^dRur3bUW$IDbFYX=T-%vqw{|Da3;uLV9XU!_#YzUe)-)M3iGEWu8eYBjiWi$u^(|r5P_Y}BlUvse7J^|8L+LztDr$9cg o+x2zB3by7IIzIPbhY{APUrJ(CXcbsV%6%jQuCKB>D!d2&1<=#^bN~PV literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_f8_7x9_1x1_A.npy b/tests/fixtures/s8/conv_f8_7x9_1x1_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..590e4ec3911e56928a514d444ea2ba03702263f6 GIT binary patch literal 632 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%|ItrGWItsN4WCO00+mkh~CB3mvxu$lb_TR<*Z3{c99IPJMFJMo*!Wehl z{=udV!iB-N_E)4%=lsC?+J4XC!ea`HpWBNZdRP52`Qm=Q9V{8`9`Ec!+NN$!S@>{& ziF`?yUGHQ27NM1sW~*PfZ>XBl?Z0U4{(|N^wI1y6_8(ieVO_k-OZ$vnTMkaSdC1-% zoR#m8$eI01UU*G^F!R#>GZj{1w~8;@Z#j8j%A_fm_cxr_E-Ay)Z*P$Bw z?*!l3&ybwUb@kX&`!8K;6E6fj*uP=BnjDL~vsr>>6yFUly zZ{EMF!9Pquj_B^7u@WKx{|ha zzsr@I|8GrvXzx&6JICwD+x{& ziF`?yUGHQ27NM1sW~*PfZ>XBl?Z0U4{(|N^wI1y6_8(ieVO_k-OZ$vnTMkaSdC1-% zoR#m8$eI01UU*G^F!R#>GZj{1w~8;@Z#j8j%A_fm_cxr_E-Ay)Z*P$Bw z?*!l3&ybwUb@kX&`!8K;6E6fj*uP=BnjDL~vsr>>6yFUly zZ{EMF!9Pquj_B^7u@WKx{|ha zzsr@I|8GrvXzx&6JICwD+x|=Ay{& ziF`?yUGHQ27NM1sW~*PfZ>XBl?Z0U4{(|N^wI1y6_8(ieVO_k-OZ$vnTMkaSdC1-% zoR#m8$eI01UU*G^F!R#>GZj{1w~8;@Z#j8j%A_fm_cxr_E-Ay)Z*P$Bw z?*!l3&ybwUb@kX&`!8K;6E6fj*uP=BnjDL~vsr>>6yFUly zZ{EMF!9Pquj_B^7u@WKx{|ha zzsr@I|8GrvXzx&6JICwD+x3{!JS;c{{7T*gruuQ|yfQ zru`ER_vqy8d~46PXG8T%+2{Lt78W0!7<6sFSYdtv|L({8V|FaHO1W}vKht9YjZ0;3 z_HS78J7GoYMSHg88v}xANY8 zjxCRjt{gbBe@4K4Pb=P6`@PyH?>fBm)&5UbCwNS^z2BelbxL%<>rMLu+Fy(gG92DN zVa|_tf7iXRule%(QP1yZ`!^IbwR5~Yy}ymei+kPfclH`>uimYxe7yh6`A9dTiP!eO UYc2MaoAKV>$VF24(xVsl08FC2NdN!< literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_f8_7x9_3x5_A.npy b/tests/fixtures/s8/conv_f8_7x9_3x5_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..590e4ec3911e56928a514d444ea2ba03702263f6 GIT binary patch literal 632 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%|ItrGWItsN4WCO00+mkh~CB3mvxu$lb_TR<*Z3{c99IPJMFJMo*!Wehl z{=udV!iB-N_E)4%=lsC?+J4XC!ea`HpWBNZdRP52`Qm=Q9V{8`9`Ec!+NN$!S@>{& ziF`?yUGHQ27NM1sW~*PfZ>XBl?Z0U4{(|N^wI1y6_8(ieVO_k-OZ$vnTMkaSdC1-% zoR#m8$eI01UU*G^F!R#>GZj{1w~8;@Z#j8j%A_fm_cxr_E-Ay)Z*P$Bw z?*!l3&ybwUb@kX&`!8K;6E6fj*uP=BnjDL~vsr>>6yFUly zZ{EMF!9Pquj_B^7u@WKx{|ha zzsr@I|8GrvXzx&6JICwD+x{& ziF`?yUGHQ27NM1sW~*PfZ>XBl?Z0U4{(|N^wI1y6_8(ieVO_k-OZ$vnTMkaSdC1-% zoR#m8$eI01UU*G^F!R#>GZj{1w~8;@Z#j8j%A_fm_cxr_E-Ay)Z*P$Bw z?*!l3&ybwUb@kX&`!8K;6E6fj*uP=BnjDL~vsr>>6yFUly zZ{EMF!9Pquj_B^7u@WKx{|ha zzsr@I|8GrvXzx&6JICwD+x`ItsN4WCJdqCCb%F8!p=4DA~VRxa7V4ssBPw`sZKnZ@A~i#az~H z@1WHx@~i38{xp{NZVN9w+P`ey>SWo@7xn_e-dsAnw%A*^OsL4)dB%Rr_XiEGmACgB VtUJ8!$mTuvD;~Wq7JGTu9ssUmO)&rf literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_full_f8_1x6_1x3_C.npy b/tests/fixtures/s8/conv_full_f8_1x6_1x3_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..3ad593427f4875f44aa4c9291653f13794465a1f GIT binary patch literal 192 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-Itmt=ItsN4WCN}PrXNd;o~*Ywtoi6|?DN2W+YGI+g+ll2x5(slbo?~c5=^=g01Bte4(>bLg;0G6abo&W#< literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_full_f8_5x1_2x1_C.npy b/tests/fixtures/s8/conv_full_f8_5x1_2x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..19e316d398a2a77c6354d4637f442889aeb79f33 GIT binary patch literal 176 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_full_f8_7x9_1x1_C.npy b/tests/fixtures/s8/conv_full_f8_7x9_1x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..061337b0b51f2004026eab5ea7890a667ab5639e GIT binary patch literal 632 zcmbWr>q}Ds7=Upl3c46F6a@oMqAp`p9};U#-xkZ3c|%c(;bpezAkmnTrA|t$PlXM2 z&X!Tj%M_NR>I5Wa7Kh{Xdn!Auh&o2nF%q9SJlF z+)ZgTHa8C}@7fV#VRu# zJO9YjE22TCUg4{Zi%@N+c(3#xoEsZQzA|px{?}SI?N00-DLR$A5ByTsX2Do?}NA+36bi>4qZnPJa2U0YhYhVWG!_ zJf+g$N?}m;*11_6p@$!KQ*_d*1EWInR@M_4$hfhmtmRnIFWuJizcc73=aQ!SX)ufG lxlMI#+MOWcy#VS zv?22v5FN^boFoq8G%$RjjSfN_ARD;tXmcGPFx+ql7aWlN3q8L*AI*oH^h2L)AQTcx z=sA2@wv_%3jlNsJqBCf8fkc`w<$d{?M4H3@&-e54Wc+6?`Jg>>ta4(NBj7@2cq0a1tRUM?Sa8L=r=Dn-#4CdxcWuwRu|w52 z5;ID&7BZr`nm$5^nu7XR|Yzp!~_DmHHM2`I)@wzPO3)K#0i zdZ-K7&6GEJ;=5s#Q}nnuZ3fEUm*(bCT3}jqr$Dx1fq7PW*5yto-aRbNHHk;@%_r9$ z2%lQPw3tvH88v`<-K!*-*N@qz)kw}jr9yKz8yf$ zy{?$XL_>DTw)%d|2;bv7tLs9I==xU2bF=VA@ki{Vwi(zMu`PtbnZ)$Hh8MQBd6DpI z6(K8{i<>?6;5D8Z>Ppf(1OXFBtX=*o@I@y&HKEK3$yE?8QuX?_Ver-{)Yrl)QA;{H z;CQ41$D_c9=X*zhGApS}E}8{lbahn4h6xBe3`Mv5d`K+gyK5%===ZBb`bkz$k%hBD zvIjQvBM)-Men-mJEjo2>vlQSf?`ra@JjVCa9iEerpcpq}Uw` z`WawdJ(78&-4E>5)oNO%7V_$-V()81U^Lnbn@T6)gU&A+WMw7{q72<^T^>ezG}#?t z@Zo^Q)=G$XA@g?7yFajp@p!K{LYi#H6I%jPkB!(-unCiT&pUy*t9^QH+zG87GtZIm zHzqbIvhSHD(OOcRlDqvrTq5c9mO2wEB3C^x4cgGzsFH)L3eM@6BZ;`_&!BA>@W@ix*Orn9m*9Td3=V qluMgL54?IzF}#wnaPm9gW}Q46S~`zCv6ZKx(Tp`u4=RJzM)(&~(X~DR literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_full_f8_7x9_3x12_C.npy b/tests/fixtures/s8/conv_full_f8_7x9_3x12_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..ba796be6d238d5b194c5f7788d132ce02a5a9e53 GIT binary patch literal 1568 zcmbV~`#+S49)QO*E=AI3TAWhGW?f1ew3S8Qw{#{x)#7wSMRqonRm{#Hmuxs)*u}{; z6}oGU&`E7=F5kB?Hjz?>m}tzH8Q0Cskjtc;_FvfNx99o!$@|uSx9>h3N*pE0HZ+WP zh->=|!`3}~v#kTeHawae!(~T>L~}#K{^~6DQC`^R&O5^9gnf29R|keO)82uR%=o)3 z(jRGyw>vZn;t;V|^}QOqf37vHZhQ={0x!C_ah{^d6#td(mPRyL?f7Myem#)t&dWnu zIhKfuULNc-YyKkirQ#)Q+y zktsO4pT4ynqqjtrX1TqF?=9DEEOb}n%{K9u&*kHILg}|z@0U&(wa@Z7&^840?<(mp z`BDft=+e?&U__cEMm>pN$H%xI7gp%|EFiLt_U=py7{&vBJh!lQhJ^gL0_TB_NBEIz zs11FfLuP)pyldHpS=bxvP$Be@f_aMHZhN%?4_$9dxN=wmr=4VGGg1Xq1Z7-^w^4%E z4-2!(iX@;~(O#Q7sX+(2nc~G=9dh%u;f~RTO(3MKiEJ4$AnshL|6%+2EG*9*&@gH! zq9#Pb zcNR?}8l2V_y+&o-8V^o^2!hi}mU(P@k3Z3j3jXUkf?nbr)6)E2v^1MJ4wqlz?5cAu zQRZ5(IA3TH*L@dq5^wdplr12C3!iMWmUY9)1h*Q!BUx%Go`KndgLm{V*a=*OWKcJ0S0$RM%1w7{!so3e_e9l{`Qc3 z&qc)H8|(Si(<-oNewy|qc>sxqUn9i{=Ru^`TVTI|MiiGYNA<$TF;FT<>Nz%sfk7+p ze6)A~>r=yn=^G4)*le{@BN#yEfu7t#T`2@z{dAJORsbPhgj1g92Ml`L)!pmW2Ljix zEfqfRAtvh8iKzo~_)y|iHdXm2oCvGpghxz5Y+vMKFEb;8MM*Z~doLl^sf%=Nl*Ytb z$4)zopaGbA=UT3%%Aq}=Il<{I!n+Ih>Bg0Fu-vxhlPa$Rf;`R@_IPTcth;_Nm^P2O zrFZ7Zay8^3RPXr8$(!u)JwYXF+UC) zJFJr3#6uYOWc&Wb0TM80HpIwgKH}KX$>!Kc|9; z?Cau0YBy4PuZ9jUjliauIA`uhDY`T0mr9F%#|fg`ZpF!Q?4s(27e4puWSvZla7UpKOBs|KP{TvEJyhv2;T${$CC3UEKG^)J{pjXzbK;a}>z z4cl2yN)N7T!aCDykue@Z=ss07c*$oLHi=jU&;6;S$J%|h6{W^R+6b2uZaDzgeDZ8Q z)KkcyZfa&my#VtwmKDpD?chF<8C#yNfv&i3lKzy*a7oC;#211$DB8OGz_vOCbT)R~ YIsLc`eSL1`cvs9~WdJdJvqORZ2dPXG!~g&Q literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_full_f8_7x9_3x5_C.npy b/tests/fixtures/s8/conv_full_f8_7x9_3x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..61b21550cf2d1729a7628e22602869cd169ff481 GIT binary patch literal 1064 zcmbWr{WBW|7{GDP>TT1FJKZX6oqF0 zXqmU6oLarEUWSr~cJUrWQ7=(Ry%Uvq32D=8-M_H!Z{JUrx1Sf)UsL0%MjSbW5gE)P ze@Y@dhn^%;NaWBfEH;b&)1@n{5XOJ*P7jM@y!FV-^l--8w{)VA?2aF$km5=IH~n&p zLNkHe7@m@QF>`KBU~LL5u4`=$Kbv>TlZ&PyFi7Y3+i?gTuG@`O2~3#CKUjA+x*FpA zY`A%5G8`f(T4uDKVfN4W@`oH&z?%?N(|$^bLrF;`wRW@c@ZP1=zwR$U7|pMuTri6< zr4G!2w0bnjU{(32^O5{jx$T^f9LFuFs~fQ`D)|y-ChDq}E4PsS ziA@Rlashnt4y7`taseuYlS(0b5C~sNHa;$_0P91vrP|g}gk{>e?yV7cncv@?+}{Dg zv;el+U4{ncdcLNDT{yrEkkn<#;g|4TEqdE36ff$RPt_G6dG5kf#XsAq&b@f9$7CAC z$|o}0iAlJqdhEDq-;O!f5wW2jN^l@irg}FTF>Sv^F!hIC%!cPWamlmTKsc5+XVZnE z@k8o#k_@T6`jUr^Z@~AwamM!|1;nSmsQ;r=j=Y*@cY=%bIv%MC_9yPjK`QU2j$0~# z%PB8VpBaVO=0{6x(Kz<#9cWm-kPqs-uY-;zWy2zgW-#a>Ma^+0esEfaZ8pwCD{&(* zw~JT&Jr;p?O^+*nGYYKhI+OtWRXj1ww0uw_0hbtVRycVX1jG`e6=xH|Qyl~iK7GJ4 zJ}fO1O0mqq)aXd`A`;wn9FDyd!d{|wB)PMYk1e$eoSb5_K_T&d{57~7ubyZr@&m>^s()s!poOJeLr-}%f;Gc8QvGQg5cB#w{p({= z6!kZCYrRl{E+=!Tg}DN{l#~^A%nnrMtb0|R-@-aqy=~7EPl4xZoqAZO6>as6npBM? zP;}0uisCwh+MHbeZfifBz1n9Q`0XU_#m7oBs!D;KpepMa+J=bgSWk-EEByP literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_full_f8_7x9_4x3_C.npy b/tests/fixtures/s8/conv_full_f8_7x9_4x3_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..8bcafc3e8a82bfdd0cb8c0f151141ad4aec3ab22 GIT binary patch literal 1008 zcmbWr{ZrEg9Ki8moOB9Y+DO8fqo@JRli9pDdHA3Z>IGss5)oYE8JResfHNfzjAX(n z$XY=npr^tFJY78d0c6P2fKnLOgf9@&ZJ+Y z|IfA%-tx?4-XIL-laFX;=D}ePcYW^zJ)UG4U z3x@8q7OSjg9R5iAxwOcLQSa}4dN6thZ>vL3NfOo}ad+vHnvfM7zR^=Bu*_lgx@M?b z)QR4~CYNWWO1zx!aU|vAImqFpWOd^!cv_McvY*=a8LkuN3{}hN!$QB1(Tz<<8$=|gWnQ10)oH7~NmI?T_HUGr? z%QdWx94ft1qsA+Wjo-_T$U*srS5s$&2L6ubvUn~822eVj3f~=ri0bsHtX2aix%wro z4;V2gJySj0s>e4UadnyxP1t8{Iy5<~gCfonH9ncd#Hxcn%7!k?CiBXqp3`vHEAQd8 zY7*6(dmVKd5Ae_g^X-9&SqPRbo=>CRLAk80iZ7nTRD~yhYnKIl+X-)qOa&R#Q?i99 zJy19;+hCDE!S_A^8qy3~^LwOaWH*STpIoo}sUMS5RV?$}aom^j0Zr0O@rRhN`-WFm z;dHg_7C##^3hy?g{B@6nsWQi3#-rA-zeBRnIiQ57z>%js+H38c3%e@0S2ZYc7_Z6k z?*`*>uIP2sH1?nAEL7D^ zfJU27=Tz2mAof^G2LlZd`Lpn|71Ib%ot)QZ3kaBUJ}}1i{1b17^S7_i6i|@NcfTpC z$L!_MJbL>DqRY?Gz1$_kwlW>qTM<<>g0zI90Wm! zx(VV35XH^KK?jEx1x=q9`~?2s;d$RnxVz+Zt+ra-$e9Ck=o(?VnYcykDm$~zvu-O+ znn@7(ancC2z8vhOA?xXOupiPV%bqQjik@xR|4#lj%d(s?<_$l>1w6wjYtvAN1<1oY z>mRWD*9-WU-of8keF-!V^eec5Gw>mXIi6`xogojwHTez($d~vYM#xjpBWq8cGlsvg z=7@ZZPv9Je>2*AgwciUA$ty4qxAYl|vNj%sC-OShckAwYuS?ed5D|RRH523?to!x> Da6q!r literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_full_i4_6x8_3x5_C.npy b/tests/fixtures/s8/conv_full_i4_6x8_3x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..db53ff4c1fa0d9c7ce03a6079dab0b217e12feaa GIT binary patch literal 512 zcmbV|!7D^j6o>C*N+t^>*-$shrCy3=F*Fualif^XLCHclb46kFo@``cXE`!fHa3)* zSjl$EAAqtzN=Q)@W1%ece5d9w@apUPoqOusckXs}Jd>M@s8zLQ@-CS1&9FAf`MB}4 znJxG$zFnFw_<1Miqjn*1oCkAu*-`f7p@!c~eAztZ9?WurNS!q$p*VSz%COA!&?C`*oLl*&$s65d!MbW^BT z1x54)T@)r^P*)L^m6c)=CP5}rM4DQPXim>B=pSg|;rYF1X3w0Dw>xsA;$UT*)oh)% zYh3SG%q}+e?)s4J7`wJHb}ANasBVnaxEe2yp7318y}D?VOPyO7a!g@3&oOQ0KYxJ) z%d$TCz8}UPaUveWm3R-lAt&SexEyE0MtDN~0DeF!^$2$GS};&bt-1B=BKZLRiZxds zF2Gx$lN^r?+#%~f0qBBR>LlC&eXtL{Fs8Y*=4L#HJ(vc~r?qdxqZnDgaTwZR5oQ>B zfrAi*0cw5EhVRrB_%qgCFXLjo8Rp?8^%;0i_CRZ^g)(@_xYlCGQ&?x$rFUTlc2c+E zZ>zqqv((-qkV+koFY!DJR>=Cc_R)mT!Z74RK^*shpYcp*5u!eib;jC*?raC_g*(6^L-@y;526x2)FM-kR}-y3Z6h$-sbrtEJLP`-4Ydgip!b*aZK|vfz#710&ixt1|ULhMWaCo^VygPH9 zEvEBjrcUZCxeIGABQ4`?UyPQb^ha;^tJ3F=eqwjQ!H11KIOOiY=wZ+3i~jp`UNOe1 zQtE@r`wcXZ+n@pvBWDxPKPhnd&Ye4;3Z6h$-sbrtEJLP`-4Ydgip!b*aZK|vfz#710&ixt1|ULhMWaCo^VygPH9 zEvEBjrcUZCxeIGABQ4`?UyPQb^ha;^tJ3F=eqwjQ!H11KIOOiY=wZ+3i~jp`UNOe1 zQtE@r`wcXZ+n@pvBWDxPKPhnd&Ye4;3Z6h$-sbrtEJLP`-4Ydgip!b*aZK|vfz#710&ixt1|ULhMWaCo^VygPH9 zEvEBjrcUZCxeIGABQ4`?UyPQb^ha;^tJ3F=eqwjQ!H11KIOOiY=wZ+3i~jp`UNOe1 zQtE@r`wcXZ+n@pvBWDxPKPhnd&Ye4;3Z6h$-sbrtEJLP`-4Ydgip!b*aZK|vfz#710&ixt1|ULhMWaCo^VygPH9 zEvEBjrcUZCxeIGABQ4`?UyPQb^ha;^tJ3F=eqwjQ!H11KIOOiY=wZ+3i~jp`UNOe1 zQtE@r`wcXZ+n@pvBWDxPKPhnd&Ye4;llQt4Bk95yH!=Mo)fb8mBtE(d4wlw>%|S;!FTgE Sdh~dG{uwpFO?HAb_`Vm`FUt}D literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_same_f8_1x6_1x3_C.npy b/tests/fixtures/s8/conv_same_f8_1x6_1x3_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..c6f1a277a8ec14dcc80ef928f92110592f0f1056 GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-Itpf*ItsN4WCJe4nvdSbJ`e1-&Cm*4D0I($i%d>e$IM&%zx3YloN4xC X|GzC?rWQ0m+Rv2v?#P>4ul54~<;*vR literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_same_f8_5x1_2x1_C.npy b/tests/fixtures/s8/conv_same_f8_5x1_2x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..39d0f4fe31b713cae183abe325c662eca4f98c14 GIT binary patch literal 168 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%IItqrGItsN4WCJemx1W-xi9X)n&{iuee(ceHs~0|V!=7H=|7PKbuvCLf O`}O`62hMnK%^m=H6f(8| literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_same_f8_7x9_10x11_C.npy b/tests/fixtures/s8/conv_same_f8_7x9_10x11_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..5f9f8004d9d65102e215dea245cfd3442aa8815f GIT binary patch literal 632 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%|ItrGWItsN4WCJeqQ(gzQuX|$uefcKw6)tQC=I)Nzb&F5>fbgH6j%(xY z?0>jOTk)>FutP&u@h{WG?e?zRRw4Of-}Vb1xnor;b;Z8RWc!BImoM4dzx*5jnM=Z< zV5M))^EC(e&+tz6&e<<`;FOATiu8OI2VRM~?>iXy9SoSKp8R3=(SB|Ia+Z+x7yCCT z2{y3teA&qfbm#;fwzvR*Wu5Q0eO4sEM+-hd2yI1qf{$SJ1 zQ2u@2?XNCXTh;lS^T4BnG085AIS=?6R4rMwm-Rqs2%qd+&F}VUH{y1GiD!0j**r7; z)oBKYWOYvStgE~S_65Yf@Lu!K-t1uP6S>o@4vmd*ixnO*I#|!>n&>$HvAwZU=k7C~ zzS+-7I$j*8rWVt+@cGsfTh`F@3tm)l!e8|^I~ jEY0n{Dd@oM#Ah$J^`E`U?$giXR9PIf7@KwMoj4o-Nq7+U literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_same_f8_7x9_1x1_C.npy b/tests/fixtures/s8/conv_same_f8_7x9_1x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..061337b0b51f2004026eab5ea7890a667ab5639e GIT binary patch literal 632 zcmbWr>q}Ds7=Upl3c46F6a@oMqAp`p9};U#-xkZ3c|%c(;bpezAkmnTrA|t$PlXM2 z&X!Tj%M_NR>I5Wa7Kh{Xdn!Auh&o2nF%q9SJlF z+)ZgTHa8C}@7fV#VRu# zJO9YjE22TCUg4{Zi%@N+c(3#xoEsZQzA|px{?}SI?N00-DLR$A5ByTsX2Do?}NA+36bi>4qZnPJa2U0YhYhVWG!_ zJf+g$N?}m;*11_6p@$!KQ*_d*1EWInR@M_4$hfhmtmRnIFWuJizcc73=aQ!SX)ufG lxl%{Bf0Lx)e4V~4 z`xEMWb)RqCyZ_zf!V@eFZ|x5}P7Bv9*tFmJV3zlqwg>ws++pqwR$8=wXWNNODhki- zb#f)R9!!60ziju+;H%O5?LUexcG-6C<^IxN|BR(>-rv9M_j7&2Z}07EZnWL2gN?^KVVd`^sVs$`*#){;R|*>*uO6*H~UELmHh_qS6xp3e9ivIH`5L^jnn&A zE|%jc{_tgg)w$IQTG!v$+a#2Drj{}}EUSnTdt7|YKEXOx?|G__&{`DfRJ; zdtdf{`O+_OlH;0v8&~@4bgsAd0;y_Vq1O-X7pz--aDMIi{SAC`61dFY?62U^SNyc{ zmA%D^kl^)OzSukb{X0n^c)ESgEV0}V$`|YpAHH3-wEmgB`>xFS#Z5={^9f$qwC>l% z{kOHa9;l`mJg{dn+4ADwjR*GY-kwjqWBO#j+0vTiGe@57 nzgAu8ku87DehSCRl~-pS*HbFM z1D#hU&-CQ|Z2vB^Y@ev?r~UuM7TtTzz;M9!ZlA28_d|QPo|`_4B_Hjtc@}8F)_v9f zwPvq>^w}5o!U=1IFRprMA0B6X>cUi>0|Hff$18MK?Jq0jT+Qy!?9j&(=3O2Ba({GS zv3060x5JZdQ;i;9-nIV-gI4ybR~!x-7VOP(nclm9<)p`-6gD#)u-V<0pwCg{p5Fge^j>@3$PcG|wa(t0yn#xMK3MeA-0 z7QeJ#k;~y;9?Ny$RIpmk;dIsmiNy}LKB?W_zh&KmzW*_=?VsE$_ExTXzd!b5fZ+~? z5BuK~#h#f{amIems`r*|4D}yu9^yO> zZVa{Dt33G+=zg5Rs`iY}L0j*thD^+3`)}_oSN>vpVt*n0M5W=q{r2x>YIg9g{bMhs zzWLvW$(QXV4EB{g`tf)Fo18BvO4t9g|FnDWp_eSe2Y6og9!WgR=)iPeEv50*XZr(* ki*Hpi-Pq4?XJN`q!OZZkTAF5dikvE-b6a&%0|KerG2 zc~vfm4vmWao+kfYp%)dT`5Bmd7ay{H+{?|S*Saf3Cop1KF z-H$e}Jos_{zIE59B+utOu=~#(zQ*q#?caPn9&kHf@qUkzu^gTGhoi(1)rVvJtxH@lFfv1;8zd%KFB$pz{!?9a+A zl+*6`WM9y2a(0XVDf=Bq_eqN$yJNrle8LabvuErt?rpa7a5-(ihtp6_?&Bx>8@<~O z^Ax|{Kh?(Q**EPg`vWI+Tnb2fVsCzwO(j+TgZ+l-(S?02Pxl9G)~K=hy~AGVL(low z5+CRJrk$Y129W7=!a7axsF@XP}{S3x0=brz&us=nzPe?=U zt^L-_yWFSeoZBDrV5aDU&tLa1xI1a{zGt`h-?}k9vGdoZ{p&8YcSlCQx3Bme%hh`I o&Hg*5w;gAG^wXZBW76}TSugE5bUI&_RD88xJ^iikn&8j-0hSChe*gdg literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_same_f8_7x9_4x3_C.npy b/tests/fixtures/s8/conv_same_f8_7x9_4x3_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..30e8633cea26406d720e0205f97acf838ff562c4 GIT binary patch literal 632 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%|ItrGWItsN4WCJdSdzrVNzj?U-!u$p4vV70%-yF;R##pgy|25;sL0kLZ z?~heAI_H%1bpP@j{?TjFf7xf~&OW}{_w)X13r?&r`1WG|s$cgm9_u@_zbRgG&HvRW z>>a}ld>=Jkv`_lDR7y@||Nb_Srz&T5zu7O-qfwq{vE81BJ!V7l#IN?v-`6w9N4>OP zvvAjyz60;=-+gG&>f@biA9qodE&a_4`?T^4{w6x#?A<1(gwCF`WxxN;kUu?rpZ1%~ zaI5_&_SHVSbx;3Sxli^Ik3PLO`S!rReotrk%illtuZy`jyJPi@{a=#e4HFeF?0053 zDAlWU$3ALRacsq&H~TYH>@t6ydB4A@xctVoJ+Jl)?~8waPy5sUQ(t$wKfLzbzCYxH zNLu#I{TYj$Y_4oSvi}C#Nr~rWoA*N4trv5$4RbuLkk z`9MgG{G#~od;3pk9qPTh{>lCa-+#(!zPxX*KBH~kV&}X2FPymatYLr8e%BQRR}B9@ owwK$ev-R7}%l13iy00000 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_same_i4_6x8_1x1_C.npy b/tests/fixtures/s8/conv_same_i4_6x8_1x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..07079d61615a18ce060418c0be0815a3b93846bc GIT binary patch literal 320 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$dItmt=ItsN4WCN}z|Ns9NWnf_V1f)UigFvhWPWa|O(cOcsi wNCT<=H9)!xNKXRdwLr`b literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_same_i4_6x8_3x5_C.npy b/tests/fixtures/s8/conv_same_i4_6x8_3x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..774e5c142ed812c3722327e9ffccc818473a3e15 GIT binary patch literal 320 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$dItmt=ItsN4WCN}n|NsAIW?*2L2*f{u?6p7~4P;*c(uqJU4rKoY((i%T z5r~%o@f09_0mQyQxiBF92*gu?I2VW;fmjKMCjjOBfV2$|Zv(P~lc+ uCJApQ)*%l`lW-webzfb3!*<_6;TK)xxEt_0Gr z|NZ~J6-a*sVm}}j1@g6lat4eH3|oQpaiEwdkTwDG`+>A55Pt@$5d_kTK)E;|y%C6; zfLIL3e+HDR0J8r8@i8Eu3y8CTY%iex4?wmw5T^q1Lm)l`#M(ghOM&uNfb9Q3_Iw}) E01!`pdjJ3c literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_valid_f8_1x6_1x3_C.npy b/tests/fixtures/s8/conv_valid_f8_1x6_1x3_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..a3c5443da494705090aac26ed78a6c4960a98d6a GIT binary patch literal 160 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItnJ5ItsN4WCN~kGql1M3f;5cB9qhAG4s~`FTFQBXPQ0P|8L8esRhlC G_5%RpzbE!+SA literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_valid_f8_7x9_10x11_C.npy b/tests/fixtures/s8/conv_valid_f8_7x9_10x11_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..d0c75b0b7c5bc04827452f754da5288e50d81ab1 GIT binary patch literal 224 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yIts>`ItsN4WCN~S%`A2IYM$92YPuQ9zwf*K)un2yI)8H>*b@-l1q&L7`{CU%3u^ Vn3ukYq5t>(Te_hOvs-x%008A{OMd_W literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_valid_f8_7x9_1x1_C.npy b/tests/fixtures/s8/conv_valid_f8_7x9_1x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..061337b0b51f2004026eab5ea7890a667ab5639e GIT binary patch literal 632 zcmbWr>q}Ds7=Upl3c46F6a@oMqAp`p9};U#-xkZ3c|%c(;bpezAkmnTrA|t$PlXM2 z&X!Tj%M_NR>I5Wa7Kh{Xdn!Auh&o2nF%q9SJlF z+)ZgTHa8C}@7fV#VRu# zJO9YjE22TCUg4{Zi%@N+c(3#xoEsZQzA|px{?}SI?N00-DLR$A5ByTsX2Do?}NA+36bi>4qZnPJa2U0YhYhVWG!_ zJf+g$N?}m;*11_6p@$!KQ*_d*1EWInR@M_4$hfhmtmRnIFWuJizcc73=aQ!SX)ufG lxll=Q1 zZ(nnx?Us-6NBdcm&hI@a_G!Pt`&F0IKVP#y^3AkEP2=?bm5b##ia&hWUv+M^g4Xpn z_BIJ6o~flw4$CT{#2y!4vu9J0me2`(w%^xz1Go6b5BnK*OkfDtO4u*(_O|f9#PjyE zs(p8+G2h$IH1pST7M=(D)3qjPbTZzvmkF{j-+tobex|0>$20DI+5hEBzr;z7YxZqi z>95nd-r5VKs(FQ8KeXRsMM&`aEnn;%{{Ee$5Io&JXO>v*2jvU)hY#N_TU!6j-hEf* z{Nkn~`}qVfY+Cp0;(jKREieAvcwoQo?fJwzrcd^pEv-pDbL83nYt@w=+4AS?r*N!X Sd3Dy2{WWTT-wK_*wI2ZNT($H7 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_valid_f8_7x9_3x5_C.npy b/tests/fixtures/s8/conv_valid_f8_7x9_3x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..5aabd16afdd2ade1cde59a4e65399bbaf4914ea1 GIT binary patch literal 328 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%IItr$mItsN4WCJd-o|uOlCV#YVDEQ8txaXDqf@fUIZf%&qU;T0T^d<} cr0zRf%y8h4nqXo8|3mv3j9<<@|94?O0PP-#O8@`> literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_valid_f8_7x9_4x3_C.npy b/tests/fixtures/s8/conv_valid_f8_7x9_4x3_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..53203bfad04a649edab7bb0804d78a7887f7203c GIT binary patch literal 352 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItu2RItsN4WCO0W@(cbZI^XQwCZ~kXp0j1Y|ILs;J$;|{o6K;l{V4X; zKD%{K|5v$B_7aaiy*K&xz`lM@XZXwCKlVE_9F*!+x?>-;syMb{&zt?3Dt4K_&b;5> zR9t@J+MZYYh4;ljzo-3a|EaG#-5*|iZr>mBK_o5v=6?4FI+AA|JhS&X`mU--WX1lo zNB1tynelLc;bnsy?jzsqZ4X?qW;lA)zDy+g=5_6#Ar1?}Hm*l${8Wd;`&gG-=Mv?Z4*&p% CN|+!3 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_valid_i4_6x8_1x1_C.npy b/tests/fixtures/s8/conv_valid_i4_6x8_1x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..07079d61615a18ce060418c0be0815a3b93846bc GIT binary patch literal 320 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$dItmt=ItsN4WCN}z|Ns9NWnf_V1f)UigFvhWV0oDamNK(RO=-U4Kw o0%9Q`TMvl81KC|bJPC-`0x>s89gsc%#Ku52kow;Oq*;I%0Kv;qbpQYW literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/conv_valid_i4_6x8_3x5_C.npy b/tests/fixtures/s8/conv_valid_i4_6x8_3x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..9fbc9d8dc1f6522301cf4ab88314c3919c362b1c GIT binary patch literal 192 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItnJ5ItsN4WCO1E|NsAY1ma~tJOzlSGB7aY0&yb{D*-VN5O)B@w*j#W Qkevv`EI_;ph;IYM0CN~O`ItsN4WCO0(|Nj5q`v3p`k3f7Jh&>q?7)%%$7~+8RMj&niVlJR~ K7LfJ=iUR;SA~1&l literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_c16_1x1_C.npy b/tests/fixtures/s8/fft2_c16_1x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..4f8c50727317ace24920f6d9d7975bf152268833 GIT binary patch literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 p7iT0EqyqUGhB^v{nmP)#3giMV1-Hx;?$RCh9TO9G|M-1wKLA5lBiH}{ literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_c16_1x7_C.npy b/tests/fixtures/s8/fft2_c16_1x7_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..ace34613ea3d49377e52312e2658f34cefb0ec85 GIT binary patch literal 240 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 Mo)_BUs6)m70pSR1>;M1& literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_c16_3x5_C.npy b/tests/fixtures/s8/fft2_c16_3x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..4f2a5004ba244982eaacf435fd45d14bfdd30e3f GIT binary patch literal 368 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG#ySe7nmP)#3giN=n|IXqN3Qv^Uz5Y$BzHNJ!@k*Es~-#gvR}bx?A*u9 zb>QBMOX~JJzu7x&J@aAvlwbQ-RNvGuD7|cNtK^?Q`OmlgZ;Cml)EM8lZ#z1rInC?W z{<3J#eb*=c-Txuz^Ccek*Y-R)muJoFd2GK_d2&!&-Ru4DEdMPFk$GT$ZFyM69nRnT zcLXyF<4&qQa2&|+{oZ`LTj&5o zPHhUuKBxVO<1CxGuI4iAVc4*0n SFYIi3W*?CJacR5EX?p-*o0L8P literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_c16_4x6_C.npy b/tests/fixtures/s8/fft2_c16_4x6_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..5bee6343574c66bea06d656f341d0703236a75d9 GIT binary patch literal 512 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGCOQgcnmP)#3giMVo3;M2k?E`s3xh?si!8ha1N09;fF& z+g~{Mk!Nq$yZyYoUQJn%$Leq)?nA&nzKixw?n{2JKK;pl!LmnH#R0eWN3HB&iFIH) z@Wg)p>*o3F2U?jU-aXy>YCnT$&??>H#rrFZ)fo+R|LuR!+7f9mAnhQN>^ptO-GBBK zY5CRjiWwcg+)ZOB^`G{O#UD?^pY$KXP{Wwg0nUXye=oY8zkMbG7m{2TfygNOIq{X`#yx`wCs= z1uIp4*(RSk_7hB&PgpB=aR1C^jVaQra zS$wqj@{5;Vv*hM}i|V_d4){wr2&EL==g4O`aLGR5?lzUj_H0>`?k(;Yb4c>ktB+qI PF zw+AH^`+g>xlZE7j|66WR3JgFyJzhk$D1p~TO-^0uudVzOn$o)zxEi8 zJtt{_-Gx%5y+e-kV`3Z8207;AB@rZ?LZKv%N!vkGg#r2@9t@5)bW?;SJ>sBnhSwCX z=|=)oT-y7hat0H+6E>%&%wm`9_B*Y2oWnJbgjk=en8tZ?u0?nh1-^PI`dchjV9hs- zvo^Gt!w&ZMD-G0P{90B|TjD+n=;!K~Dh#xtdtM*6dK(pL$w>}9nUd$ zkH5BP7LdW45?x7A@k%hf@f7pDy$Y;XQ=j`{F@-%eovm6|FT+^Aewiy}mLTathlO94 zEf9KIyspL!U`BPZQ%b23sE)EDydzkEQNLPozMKw}cqQ`zmrvs8sJK7L{ySckLs0R! zv4~eS#AoDI(jX&cU88mGFW|_By!`ZmKHQ8oRPxzf15Sn1*_Zjrajvr2veV!P%;9Ev z#Pb9tINIi)oib<$!o44EoIp+C+s(HrQlGW32F>!~5lo>$(KU6WKr)C#Dr_YJ3mCDm z|CMnA4HibL6OK9P1K;ds?X%iTIGxOVG=G!?6%I!W+8q|K4L*|Tnw5WTmc_gFTNQ&tiY+o9`n>`67*iA95snSA!8)hMWHf; z)9&?R(g8id4>AtQ(I3E0l`ITsh?Kzor*6)giWMNVY-iflNFC6(H+U4v*J@bPG#qH%s|IFFGRkP?bja9#pX{YM zj|C=gV_c7uV?x1Dwo-_xrnNRAf(>m~yX}Ny2WEr!bo!qpnAi?6Z`Kn8UYOpi$R{fo|otPqdrD^C884xFO zq%s>3_L6}cFVhKdiZ85*IH(F6Y@g7KM<#FqdS!5{r3~9-(n79xCxSi9mUOdr4UoMq z_;vVhEl^lU6)Sg6VU0hoayu-EV8xTM{+3Au)aouF)A3fYM?S>s{;Ca$38VZl?h+1v zoqVe0_81fTa>pyr8G%wG15utA5r)LMHfMB^K>QkSTKv5MTzYAcV$VU~i}ctW^TU%^ aPVXKhnnH%`JmMGTED<{Nn19M*lHk9Jdu8_k literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_c16_8x8_C.npy b/tests/fixtures/s8/fft2_c16_8x8_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..160d46ba6e750247052be2987c270019a68317f1 GIT binary patch literal 1152 zcmbV{`#02i9EZ^~VyL+=jO)xEOk>IAL~3@;a`5-VipkbFO3_mpinmRwq~ZIfh@d6Mpn*$D>aPKk{}jC$<;QiJi^< zzcZw_@?2v%YXpP(yeEFj1i$HhHh+&E=<)xWER0{GPdanJ0a`^7& z2L3Vy>iZ~G)Wcb#HPBQ(s>j5geKVE^ax{eKMUatujhe8^U~l5@p#k$G&XMw#L(`E$fF$S6gUhmq(4Aqm6YhT{Uls{ zK|Zs+&KO$7C&SYcS>WYUR{h+|0yfSlQO=cZ zjh2Z|{Ax^E%BDfSlWpPWwYtDhk9#63w#0+H&C;-57A&0JZ4l!^1OFovR$7-u!Zh{l z`m6;V-^2s=`4R(o?9lF$ju^Nvo~>0%*U{3|?))ny22h&WaH?>`1fPBtq)G_YffIg2 zx3lLf;*uhGMl!vEwnsUqRVk)Xkxkr)U6naDAxG5<>?RQwt6QYKcCd4h*NUInVKG;A zuq5*>3TWVWBx45bD(z_4uh9~%35Cro(p6&YkC?tU{q~T=4?{dl128Eb&^I%jMUmwB zyH~;KsA8J@cajHO)e+mnespgHckS*X^$DGr8s!^v% ze%PL)MlZ%DZp~2E2tN^H@|Bzl-zx5#9R(8nxL02Oz)(qiB8bJc|%Gq%#lE(Rd( z>u*o-UPQ2D9d}8~!u{>TMT+xEWLVzh$-2IQ^lo!moX;4z=*hXB*H5WX6hcT2wbxH$GeIq4S_SZAocMDVuIFNC1Nh4-G_^(#&+6cqnbG~Ymf#q z*J#m302Leu6~GI%!8b(VHESMZT*gcw=RJCf=6XuKkG`kl<_ulaYXW`f8VU+k^}Inn j_|bgvYfCKqsiZak&&^mIW9Ki8UM(Gk|*eSEmkPp{U>DDftd%slUnz}i4KOF1o`rP??$gx&TRLpEK zMh(R-R#w6fYALz*>ob(3>?G~hmPweAwrX~DTTA~!&)=Sp2DSD?vTg%&fhpmPrb6Dr z#j>1&mvd5Sl$A$H*;4ku6S;Z$$7f8= z-~;gr#^|%Kl@AmAn6sqc=_J9cGMvx(jnOn3nq-wW&4(j6yjV=d>Iqc+3PmgHfdh`0 zFlz6DKqdlpgF%oMEP}?CCR)_#MI49Uk^}YTxaw&&N+#6csdC}|z+vpRD$rH)S=lQe zz;f{pMOsRLP$}9X$e3iDHV)IfdIrRq1rXr!r3F(a2)H+bvb9u<@tGwZECb(%a?yQx zZP5Qq5Ed^BTgmXoTks?b~84qKs_ivDzf&s-o;#SJLM9x}WY z?oBnP86o?WlXAXxQqxdJUR2fEZu1(<&Z$tQq%OpjbbbYX^%Lqd5^=9?gs5Db;heM$ z!~ODcbcvETx?K4A{5sUM9mm0YkLg5N1&C%`L^9;Ae06yz{GcPGoUZAI=Z#hixk@k_fx_6Mshd@F96EG&-QF8NUam>UQ@$kslN0bY( in0aXv?(sE|ne7ju^2!pr&G+I(ZWelMl6b?|82$y?h!V^I literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_f4_13x1_C.npy b/tests/fixtures/s8/fft2_f4_13x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..655009dd9358da1dfe486ec191b04e5c90c48363 GIT binary patch literal 232 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 NNuJg6ts;3B{|Ddf&qe?M literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_f4_1x16_C.npy b/tests/fixtures/s8/fft2_f4_1x16_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..a6f04f5ff4bf7565be595081c11babc5ad1fd0dc GIT binary patch literal 256 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWd``qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItqqnnmP)#3giN=xl1e^7=S=g!O}s0;WPWv0_OwSvrFxl?Ps@tVt99d zjfUrbv8@aDsEfSZ=aePt5X#%Q%UWmq{oZu`RyckSGY_t+`l?AZ^c?V;lPdgdw} z*q*z>-qiW$em4&815Y=9+P84kr2Wfh#O}39k9UZzO5K;*zYC^rKa_6=m4~T^>30AC DQutVt literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_f4_7x11_C.npy b/tests/fixtures/s8/fft2_f4_7x11_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..54583d244e83b870b567d5903d2daad024a52877 GIT binary patch literal 744 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWd``qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%|Itqq{nmP)#3giN={Ar8!GXTN4sf!N$n9#n*AXLM__WkAk{u&7fo=uM4 zFQ>BiK$p*#1FP+C@4pi`#Q{nmfQman)!9S!*+b2-iPIKz@X>m<-}n1Vhf>Qe4(F~E z+I{DYK5(*qi@liRar@tbPxgNle(4~vhReZev4n$tgsuH|!@CDEx^CF_nTXlnF?Lz9?O?&%w;j?xH8t0W+2*2e#bjc98DdWM?WnY5&tbC-)aP z2p*81DekZ#TiO0)LCAralPetrk{&p$F=%%1VeYnn=ViOUO-$KQ;XsUoL5sA*JLB~Z zcGlAmxD=jsX!2aO|DXHP16~ud9R4Qr9AIZFKhW?|_kjKiy91B^{IK`EnYUl#`cen3 zyzB#|eVzx5dXL%X{>a~NEx6e}WfSk-Czp*5v@m5H2-#rb@OOHp!zMFRyNBKKwor3n z{Jl{515ou2Q2n-0^X#GKJ3#G&*}os^9tWuV9H8LOTe-1$XcL3^Nd#L{pK*IwT xKCtkDg`Yh%JP$zQ%^n)wcF^$O2aOL{{OpIu7cBk`K;sh@zx$!_?EsDc0{|1UJt6=A literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_f8_13x1_C.npy b/tests/fixtures/s8/fft2_f8_13x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..e1e67f1e044c9c77a5b309280c6fd3c9fecfdd4e GIT binary patch literal 336 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhQ>MyhMGDGwF+bcuBmtRYqpB8IWRy0SJSzrEjb_eU)AvATYlx+{!jh8 z1CFfzVSn9irpyB+CI_RO$G5&sytn_&#IQvwVITHyddfXP{?LE>cCMdEYQK066zmV; zTgUy@{)W2P_L2u5_n-Qs|NC##FZ)n)emjSiFVOk+XzK0J%tN=&9?d-m(A;Ye0PbFf A&Hw-a literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_f8_16x32_C.npy b/tests/fixtures/s8/fft2_f8_16x32_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..acbbda047e05187105bfa8af5ae16648470fdd2b GIT binary patch literal 8320 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_f8_1x16_C.npy b/tests/fixtures/s8/fft2_f8_1x16_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..d38078b728b3a77049eddf1b4b52b12f94aeca1a GIT binary patch literal 384 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhB^v{W|}$*wF+bcuDuJ3wTq?;9bkY0-Lr-)X?d6S_r#uhsPq5Aej&&C zH>K|jIrwoE&x);JKcIH6xJ&59ul+L**ZI`%ySo40Cz1E((w^9-?YB{Eo63BE;a0NM zeUT6Qxo*Gi)?z$ipKxNu(WK3<>=QnQJS$#$%3f}sZQ57u2m3#){2CGU;K6=_#>8vg sdvDmo?3abgzk|r5>)#JEpABL@y8R9?_sxd5&mPVF4lw_9L;PnC04jx=X#fBK literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_f8_1x1_C.npy b/tests/fixtures/s8/fft2_f8_1x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..6b8ef8f8f8afc7b316d3918089776e9118c870c4 GIT binary patch literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 j7iT0EqyqUGhB^v{nmP)#3giMV0iEC`y^Qzv3{U_7Ee;+| literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_f8_1x2_C.npy b/tests/fixtures/s8/fft2_f8_1x2_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..152066ba33f656ad95c156d3c3aee5d5159597ae GIT binary patch literal 160 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 t7iT0EqyqUGhB^vHnmP)#3giN=3$+_Flpf#O&j1Ai`)w~My*&Zv0|17&B=7(L literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_f8_1x7_C.npy b/tests/fixtures/s8/fft2_f8_1x7_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..fedc06a029dd48c6e3f62094c4c427753445b5c5 GIT binary patch literal 240 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhB^x7nmP)#3giMVsWZh*^^+d#XMh4D3zru$i@w^I-zuHBQJTS_BF=Jk qz3^`PYmqm+yNWLEudOS5TRq{0eV5eiLnlwZMCaS1skcWn?*IUu*-^9r literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_f8_31x17_C.npy b/tests/fixtures/s8/fft2_f8_31x17_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..154459d10c7ef53019d99ae9d6f843e73f8e000d GIT binary patch literal 8560 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_f8_3x5_C.npy b/tests/fixtures/s8/fft2_f8_3x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..3a99e3ed62fb0f7571309822cd91d7bd0ef8efa3 GIT binary patch literal 368 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG#ySe7nmP)#3giMVE7sENTN%IX8K5BJx`=PR#83M?$v5x+y8O4_pv86f z#;fD|h36UPJe~gmoxdMV{eHFy-Id#~Jl_9!S^85|8FmLb=~n)^{8#L6ZIC+@)A4ox z0ile2s}Ha^IIcD<+it<-pud}Ora=84dxxvb()+9z+I#i}Bpse|c)yw1eCyayhLGn}z+-|HBx z^Xiv9SF+YbCic(v9dm2CX1*1Ns-Lh2O@99wRQ>xMUVS=wc;UVMOp!}HzwwA3u=i%@ zEPbQaN`{fsx2dr#=ykBjh;A$_gulA2GvkHG^|6(tb<5qS} z@PPfZHHO@}#=q^0KQ5a-vHQvX6YtU^3vI61E67zZD8F^a9_D_f&us0S#4+cJ4h^0)qA#Me@v5I(FHGQxckxM?WIulqr2Y$&Hnvp?n8I~el-8t Iqxs(f07vY?nE(I) literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_f8_64x64_C.npy b/tests/fixtures/s8/fft2_f8_64x64_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..d7462db5e4f9c14271cf90d16e0c005537238873 GIT binary patch literal 65664 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_f8_7x11_C.npy b/tests/fixtures/s8/fft2_f8_7x11_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..f413390249ebac172d9b7539d297f37299f84aa0 GIT binary patch literal 1360 zcmbW0c~A^c9LHO#-P%@L3e|3HC2P$xwlSf@d%BF$A?s+yq_u6Ni`GQ75y~j6B}Rx@ zsf++Th83+;w#~L-#$Ufb{r>yC-+S+OeLtnq%cElB85Y?VdBP-x zQl2IB7YGC85AWY88+MFfJSd*ERr1%>Mm!&Bcf6kTbWg8WL{ONPW0*OQ<7UT>5 z(`*^}4Arp!TWs;qJcxaBDVKxW!=pdnK0J|LtTOjBl&%q_|ZJqJhElM;*O4zmfypwD?tWv*k8uP|Y9nD&`B@4F)r37mnnUv|W<;mAa188l_ z^WG(f_bB;@j%S|E#!~r8#;!~z&A49WTbw!$2lMYWoU_)Emga)0iXm6ZOV^$;e;Om4 zAnz4a??FRK1{d^nTVbhAJ*<`7g2lcfxwfgx#{*vj!XN ze^yiw=IKBktlO>aMTNC~+SN-qC%-jN*1A2R!sLk?o;g zKQ143r~7J*$3+vnNMHZdI44}G7B~=eHF{`?Wv+T8rsJdfXmOA|;HGKX+o5mX3 zIj0IX?J6JD2(qxyiPAfFz8c{pe(>nSgo`h$HYGga0YW59=eKeggW#pnnSH zgTVX{&;teYhx>v1!(hK~{}||l0R14KF9P&Ofj%K`P~TAhkPiy@p@1&}_=9|6z%K@T HL;lHccp80k literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_f8_8x8_C.npy b/tests/fixtures/s8/fft2_f8_8x8_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..018f3f11c0495566cb82fb1f78ec87022849b91a GIT binary patch literal 1152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG7CH(RnmP)#3giN=fZf|Y`@eEHFhIddCZ9WJQ$-JWxNUqBc1ievrC$5g zT!p{(8u>iST5C?(*V&%vY-D`6f59=oRUB$h?AOjtlb*hw{Qyiqn*4q={rl0(cbFtt z-TOjH$YEAazmH8V_W}3j9_ftRf9zdabv7DD{xO?8%EfMI9dNJeehF#pn?5Z{zj%e@@!B9e6@eF=CgCvWxwrB z*u#3SaDUv-=fC%cf|$U8Nfx{0C3*x8{9sG*Vt@W;|AILZ%6a?P4s=aBAZMz|cYty8 z$%{Ws-`RJ3e<^osJHr7H&6kflT$m47G_L+*^nlG_eau(MYp(zIv#$SJ=(}9lVaw+$ z_PQc}_6O^qdmb*reqhUg+cm5QxetWA&wN_(NX)_EM~aJbn2JMw`8EADw}05f;xEe7 zNyF;g2aKG;9FJk2wlanb%&lC~)sZ)F^oa|BwJ z39>lA^rOiiK-0e;&HMvs{&zrg-vKoDJD~Z`9?k#g;p2c7e*4kF*8wg551_@5JzD(P xqs6a1TKqepr4RJ*{y@(^4ruvnKLAjm=@tM0 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_i4_1x7_C.npy b/tests/fixtures/s8/fft2_i4_1x7_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..8c822bdbb3d4f3ced3219e92ee94cf6dcc4ef113 GIT binary patch literal 240 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhB^x7nmP)#3giMV1~9O2fY1yK3XA?H&6=Tp;H7&lZ@Y@Xf#d_{C;Z&0 m;BZUx_tV8w)E!*pF6hQ9DjtYtQZrc~u7%D=SAPJ_JO=>6nMSh! literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_i4_4x6_C.npy b/tests/fixtures/s8/fft2_i4_4x6_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..5b481a581e70f727579be89aaab1018c86fbb9ea GIT binary patch literal 512 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGCOQgcnmP)#3giMV1~9NZ0HI-|v4e=SZ})0@4F`yb%z=PUl`9VDJ3;sj zjt3y(2W%iTTplV8*AL}eI6%~W*#98J?`~A4vP0@2r|X+z%?^0&JATKxMe{(zr1Q?K zb*xbJW)4hX4I)r^We1I)HX#qLJ2*_Z8Zj}*+tOjS?OqL^MJ7=7S`J|4a6;lhgQb(+ zof9fhah(IJd?H>{ZIeC#QDO}hKd=YFhs#67;rb6i1xs6kp8 zMr^|jdB|X!I%>ukHjQLz%B8D9B@Eh!ZH2e<==buQ-|d|9JKyi8E@F3hRJ;XK$P^1w zRqB*{fgeZUpCa)Vh&X}+x%r3llXLdx=BKLu90Vt4sa1>n>a^rM)#6&XRm711~OerI(rsa61!8Q(EK&Vr%+7hb1i`D43ZojKeT1O>VSTvvS*PV4&8 zb7Lg9eRGLhw0kI8Yu7uCmhd4VX|#efJH$en*d#i7*2Tq#(!fgivQ8V`8XI;5oHd zp|n-UA5$n9vGVUCYt?UUh~ zVkNA7RAHy?11!{y*6&S}fn`$qkaQ#fMHVkwd*Z_&l0Op^Iu?Z1$`!(zSzict*wbXz zctI!Eg=f4a0ow)EmC`|fOr4pBT5bep@XU?%Y&m$A8|GrydBd&~+Tmr!NZ8qMSgs2@Ebf+P2~Up literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fft2_i4_8x8_C.npy b/tests/fixtures/s8/fft2_i4_8x8_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..19f7c2fa9f1f181228b2450b7d7a0214fc7cbd66 GIT binary patch literal 1152 zcmbVLZAg<*6uu%PL=AG9A7!mrd#6qF-E4EOI*x5g^NSRM7G;^vl9uLPHKfFm5K1EA zBzP(CMolMF=44PR(G_x?mM&Sr7=tK8j2i>2YKZkps`&`a*&U3w`M@kMm zqDV8j#8o@RDmN#nxZEn<$mvvEO})F(U0HXs-d*in0}3i#qI0z_o~^v#Ts>>Eb*k)C zovKy!zp;^o13=D%Oj{bH-+~fu25$ruI}Iq;Gi2Oj#lpc`4&Y5Nt{yL5c#<$SSdnm!vXUv6w>)k~23J;bxYv@cM52tE_($7rNkZ@ROR-^WFV%Ecw zC@2ok!H6=>%eV)yBQewPWp6x`<*l?$1~rri^qzpd-TB^`I6L}U7xcfH6!k^{Nb=F-lB6jT3`+ z>0XFH`y`~-zudKaTMyk)L08G!BC2a$zZqZDwiNs*x1)@gQeI|+&)`Vb!V}>G^FFH| znf{RJ9~phf=tpKg?EH|KFNFIe8k957?g!+0WPiZycbzBS^Q_MIUw=^kx_&k5FJ%1B W`V$!cBI9q?|H%9gWd0BPzwj^m#F#Vy literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fftshift_c16_1x1_C.npy b/tests/fixtures/s8/fftshift_c16_1x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..f72c79ce9a1115ea1b1df75951031c391c397956 GIT binary patch literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 p7iT0EqyqUGhB^v{nmP)#3giN=GgAyEl?Pq1XS;k(&1vIHdjL=CBVPaj literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fftshift_c16_1x4_C.npy b/tests/fixtures/s8/fftshift_c16_1x4_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..fb66318f75aa95412ae359305aae2eadc3c9841a GIT binary patch literal 192 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhB^u+nmP)#3giN=IZrdK{_ME3KQ4FKq?1`s?JrEbtk}Hd-u{Tu6w^j^Tl5`@>lG?5wrF5>M6JP0|3&;KuG`q literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fftshift_c16_3x5_C.npy b/tests/fixtures/s8/fftshift_c16_3x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..b88f741a4cfb0cab7f6c809d7961c1e08a299361 GIT binary patch literal 368 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG#ySe7nmP)#3giN=3Cx|=?%y}q8~h8@b9CEoAC(yPRh9Sgeg(FmwwZqp z+Lu_wG3~XuyZ^~|mzfcXulEORIQwxe=d1lK}icu=AFE-J{o;kvksQ&)6fgn#t<4eMQNu=nrCt_sdM* Tlfo$V*8V`u=d(gf=k5mp70Z_- literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fftshift_c16_4x6_C.npy b/tests/fixtures/s8/fftshift_c16_4x6_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..1c6414cd433d219100a0f139c17f12912344ece3 GIT binary patch literal 512 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGCOQgcnmP)#3giMVhdu9P_q8mr*V9P%FZuP-zUqU)-=i|m?Eie5R`kf} z&VHU7^Q=7^?%C)3TdMUe>6!h4yohz}W~c18<&+#b7x&J-;m!7pRiWGM*Bn{T@BjUU z{g2gob{km_*h{T^^-t31wY^S}3RjHm8~b+)wy$!^eZ0SCK8Xx+xIxzcRB38us>zG-G;g1^dnCZMAP=*euF1=h6NP zGj9|LZoas`K;;s5d+1&J52tsm`Y(55f5!vm`{t~#_jg=XU(mPxpuI+*NZYFYPxmuy zU3o`(;#2!28!SG_oW8JMBv3KvMZ$yqHTQD=HDunj-%;kZcxlKfdzY?>9q}1g_8&;P zU|tx#&z`~6Y(-EXn7eewsXgZr6ocl>Ener!MEUxnc68K>+|aMnIN RH1(bRk65R0^C|E40{}k@>Y4xm literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fftshift_c16_5x5_C.npy b/tests/fixtures/s8/fftshift_c16_5x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..eca6d3d45953635ff65f0eca4ec9f73581fbec85 GIT binary patch literal 528 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGraB6ynmP)#3giN=9qWA8XGfm4zp|~cP37am{VQHcybJAlZC^3x#kBV> z@Ahw4YW&pu>z@4?i?$rw9DL7S;*o?PTg+YiZ(Z(hpQ}B!_nBlJGvHwOHs<>&g8LZ?EQ9?0mjo=lqg6oSGNx zS=P>evxn!ly-lok#^Pr$?K#eJvMow@u|J_j!S{jp0sAXUUan5gzi7`=vznvw=q39u z&dN#kX^;13bS|5)_`PKoj+dj`R$_UG*O?^oz7>-#k0 z?f#6M&x_3Kui5wP>a>#oe98XY+d|z{pWf_WktmgF|M}$p2U(xweVx|Wt8~RXM!mjk W|0N-=cK3$o_KWy)ZynqAdOrYy@la_1 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fftshift_f8_1x1_C.npy b/tests/fixtures/s8/fftshift_f8_1x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..24e92d0c12064572b6f1dec3355ab3455c62e0e9 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= gXCxM+0{I$-ItqrGItsN4WCN}ahr==anJ4!H07CB{Q~&?~ literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fftshift_f8_1x4_C.npy b/tests/fixtures/s8/fftshift_f8_1x4_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..54982181ce7a82f759696aae54984e16fd70a475 GIT binary patch literal 160 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItnJ5ItsN4WCJb(BVMNEkKfxXh}s4lKip&g#p&~_b@LzCFWB>rbw|uC FdjPMxD`)@! literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fftshift_f8_3x5_C.npy b/tests/fixtures/s8/fftshift_f8_3x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..2c25db183ef25e78bcd5ee2bcf8daa28dd8f703a GIT binary patch literal 248 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%oItr$mItsN4WCN~-Jztb=+3vMpSM>YPKlx|&HEU|)T7|FdPb!(*cc9?W z{u46!dko*dvS0EohDG$lo&6jvpCWggKCwTxR@Hv5+oSy(dKKKYypQg$XqHQG-}u;G u>Z8&uVb90=6^bn%H#^(}J literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fftshift_f8_4x6_C.npy b/tests/fixtures/s8/fftshift_f8_4x6_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..2d65e4ecacf5c693135f0860a259fe0fcb6ab856 GIT binary patch literal 320 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItpf*ItsN4WCN}bjt)s2&ra?Coc?4M9OO<)G!#{CP{RxYe^d%yq1miOP4{h!(EBq_!fhFr66@{+tEw&213 z6?r^K88fcgt88Jr`&jVBevMBPqn8<6u;)wLwW^}<(f(WhD)0E_J+d!I>=2jSyWhS< zqs;Pg+e>?fCE?+$FV5~i7Jlx`zh!Ut%ZQ!-*L3pk{TXckSQY{CdIR|GoVSW`w$b{d9T%gOnK4*7%eA&2CS&;lA~H zf5BIM_U+m`?Q`bdDSIdVdjE@cD}sA_Zrc|~#0Ro>->{#M^r&|=|DOFWnR^n#Zob@~ zap2dhcHf8g2Cp^?GX6MgUm+{~>-Uz|_73@)Z(lerwEwbmqY-b^`~B}46I|I=KHvXg cMKi1J{|ojW25FJ3hi~rRkof*m{LH)i0W33%4*&oF literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fftshift_f8_6x1_C.npy b/tests/fixtures/s8/fftshift_f8_6x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..c9392af03330ce440ff2ba59d71816bd63cb5f94 GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$dItqrGItsN4WCJdq*Xv~pn4jCT-8o=?OW>V-RJg{TuOZLu+pc=c+IhU$ WFVIw?&RBPIe-?kr%I%fU_5%Q$LNZ$b literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fftshift_i4_1x1_C.npy b/tests/fixtures/s8/fftshift_i4_1x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..2a7deea00721a7e39fb741ccdd1bb12658945e9d GIT binary patch literal 132 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I$-ItqrGItsN4WCO0h|Ns970M3dZW&i*H literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fftshift_i4_1x4_C.npy b/tests/fixtures/s8/fftshift_i4_1x4_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..f5129a8912f60cbfddd820ea672dae9cac72cef6 GIT binary patch literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= lXCxM+0{I$-ItnJ5ItsN4WCJdC1_p+||NsC04#bQ=HULtRBV_;p literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fftshift_i4_3x5_C.npy b/tests/fixtures/s8/fftshift_i4_3x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..7cef88baace652cbb0180074e5de4b8873b0bcbd GIT binary patch literal 188 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%oItr$mItsN4WCO0B|NsAIXJBAp0^+|w_HQ5t0g(6~AZ7-NgT&aNYFL3Z J2>%0$0RR%KJ5~Sy literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fftshift_i4_4x6_C.npy b/tests/fixtures/s8/fftshift_i4_4x6_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..58e062e9b024c73cab86ed17daabf5dde4ea0d59 GIT binary patch literal 224 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItpf*ItsN4WCN~0|NsC02gDo<3=AOr8_NF+q<;Z13s4LM7=f4-h(UZ9 Y8z>Fd#|#wv31oxxg3N=dVFQW-0Cl!QX#fBK literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fftshift_i4_5x5_C.npy b/tests/fixtures/s8/fftshift_i4_5x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..f31a02fcfd30f8769591098e6ae125a6106cad2a GIT binary patch literal 228 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%IItr$mItsN4WCJb^1_p-T|NsAI1=4Il{13_oi8Dgwn1D1pl+O&LL27>j c)qMwIP9Xa)kOpCp_zxfr!XP~?KnyYm0Fhlkb^rhX literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fftshift_i4_6x1_C.npy b/tests/fixtures/s8/fftshift_i4_6x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..a984b40eda8847cfd43fa86d0d44e2369a406296 GIT binary patch literal 152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= oXCxM+0{I$dItqrGItsN4WCN}r|NsC01H_yR3=H3)G?4la0KvQ~VE_OC literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_c16_1x1_A.npy b/tests/fixtures/s8/fourier_c16_1x1_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..4f8c50727317ace24920f6d9d7975bf152268833 GIT binary patch literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 p7iT0EqyqUGhB^v{nmP)#3giMV1-Hx;?$RCh9TO9G|M-1wKLA5lBiH}{ literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_c16_1x7_A.npy b/tests/fixtures/s8/fourier_c16_1x7_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..17fa2d19df19b15d08a5d7d7406c44e2c40e3974 GIT binary patch literal 240 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhB^x7nmP)#3giMV1Mg);2V#%d-?`8?t#9!a`#byJE>df_YA^EIMmi_w z#r_5Pu906nciGE0+OE6r@WNi^EaU3`6?gZS8Ts>PynMcYPLt`CwH6ompJ11;{Kat6 o-e-QqH1-L1_BR||xopO&)Al*$r}s`+etG``r&URPJKx&_09#5|RR910 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_c16_31x17_A.npy b/tests/fixtures/s8/fourier_c16_31x17_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..d26c9cc86803c939c769eba816b780386429dda2 GIT binary patch literal 8560 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 SV9lKU`vZ2zooHKl)gA!0zn4S+ literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_c16_4x6_A.npy b/tests/fixtures/s8/fourier_c16_4x6_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..589e397cdd94b09a1ebe5ceac2e80d223f6f366f GIT binary patch literal 512 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGCOQgcnmP)#3giN=G`_h`VpfmsL%wKOXm7l2?=U-QCYR36{d|)qTb1v9 zZO_ABBAxO2_Wli8_oeU8IB0*Sh*fLDk(>J!cAA)q)St6wnDfkhPgK7BBIVptHIG;O zm&x-j6z_Rp&o(_tS%1&_{by>`S{EF7X1^%b{(s)rm;3E*u9K8H{$xMHti`%E=GpcK z*yXvH&3Ej#*m>dYeAV0b8Jk%AbG$CwD@b0>lrny1|EOyBlcekK?fZhs1s z#@4ju&+Qkq%wtd$IAs5(tZ&iLw@>y5bj`Ro>)Ceu3fIGDPa2=If3Wh|yD#Az_h0#> za5>TBx%~shXg%SXH|-k=x%c&}ZM09C%FfXJ>%snrd+T>+WxU(JLcD0U%C&p;1+#oI zOT=H>O9&OYTXo#puW;)1kD56f?KjlhwY{9T&E9B*jbrDb2lg9W7DtAkJHG!%<|DJ9 zpResdxIA|$OS^0TA;EOzqO=S9r!1=FT+@GV{|fbn*NTs??{`>lpWe~G)Lunp#x%X_ PkN3Y3H*+*lxVIkwq59pd literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_c16_7x11_A.npy b/tests/fixtures/s8/fourier_c16_7x11_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..e4e3233441781959747f7dbf5a592d66c8fccf4b GIT binary patch literal 1360 zcmbV?{XY{39LIMOa;l{#3Ac(6x|D0qIbWsMl4oAWD{`~tp-C4uJ>2PG<@xdC;nhxZ zdLW_lloa>+&Jj~#wdD}mX2Ui!Cnx2h>;8rN{PupoKY3pMo<4zJ5Ka@~&BMZ3p-giJ zlDT83?Otf~#0Xw58mR6<=^xU>AuNayDk0t*8 zgblAyp|z8gCQ>3n-}#e%RW}s3SJG?OOJFWCBdMPz1}#H&a>XSX5-So*MzZ_Cr|!US z`mvqJuWHlXbmB3*=;~`E==5Pxb>lbNcBw)0=!%q1;ahB;qxsT#BiKgHHp>4_0fr+_ z)HzjRAViw!yp8HcLb>+L>6iCFCwGi>mrt_wN*;8_Q5M^-_sb zcMJ)p(-I`yViriO%P^G%c5E^yV&ZZM2$ez!3@|+$)AI~!4T9+P(TkpQ0J@*<& z4U9t4tzMX8wr$K${R@J-O5U-Kh#~iZJ$3a;F;Wu~AGOYjF?VL&hK=tOz#WQPxOr(D zXc3lm`EMJ6XA|T~{)Y<LW){qV?l7@9&7D&*c)Y3`6QU>f%{_nvFg=pY*O7GK! z9(4ZsUWHti2acV)_tH2rpee%!wcRJt=hV#PP{C8M_fFDP=e9zLnTy8eyeD9{uJWt$ zmQmRC)9W7k{x&Rkwa8d#7DFoU#g%Wv`+*Z^+Tm*<1dr8`0lq{vxZR{u9Ngs4%QOyj zxGlre6=yxk3@OrtRyOi?cagg4M#sf9a?ri47aOE8jztlR*hmgrRSicTL8jiM*iG>Ik4%*$&+S7AQi_uWc;W^p`CqqP4ysrEbnr* z$`ix(4KD1(n-f5+cyPev>^Sh6D*Y)u!0LNs? zP(;?}|1>Rfp?whhMrx80i+EW7u|6v^UW801eN#ra411IJ&4;l@p>5{=`Rn_$=J)&IcIF6s;O?l@lfEcjHzN@YmsJ2@_ Je0n2+e*q8ms2%_S literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_c16_8x8_A.npy b/tests/fixtures/s8/fourier_c16_8x8_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..c8d0f1345df351e4ac6580787627418986489f30 GIT binary patch literal 1152 zcmbV{|3A|S9LIN$bH!4+x-YJa+eJ~KFXHwPd#`W49r@A;eeoqGN@(-AI7?C~=$9;$6acQ?sELmnSzQ!Apbs9WLTZQD$hVG9zm_N-t%fpluZ zEmo}<`zrW0F*G6EY&c!Xa$s#uA=~-yo^Tbm*J*77VMdcv3vBL&vFNlhglr1yJ?`eVQhcj8cfdO>I;G+ARhw&Q4f}@-hg0Iio_;P9oieD0| z{8&9e>LOOWFnk20gu_NAnsHE!#ti+Stp(EhUB4BV9D`gjscr%qAW2uoRo&`g*0|u z#@NqFbXquPc3}A!u1;u@W;?XvwDV%bQ?eQoB4p-M_f;S*jCyhVnG%~y*H&A+Q$w2V zXcbve0{VL|9Wx5#kQDuSlQfRN^*hFGn2-8E?;EffCRJnMgjcJ;Uyii)s{QjON;EyW zv4glG1{ImH?j%!(`#rhe(Ky4f$)M)co$Vtak_LrNk^3PlSztcDeFSO5T>B-DA)qI_ zkUSbSNcqGr{`j3IP^YIKIvTG9a;C>N-B|+5w3Q9S%tPQ_FNljj@*J%50%trj+re_w zfh{=MhwjbWy|?%@BTc{ROkRx+kEx%R`Gt04)Raqpmg`cF2hZ!WK!vjgM*dgVDA9dp z=GzAwdN48N(6Gll4ba<|jJIFc!+yt$XUu%$ILavx2wE?O+<>hi*B7N&@C(l%!sQL- zPRDJoyR3wriuVqNd*8q^PNZe8yBe%_3+HzRz6N8y%V2E)!Vn5hwo4C;@kiA+FF}{C(rFl u&aShYohY{Vk{YzF|P8CP`x literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_c8_8x8_A.npy b/tests/fixtures/s8/fourier_c8_8x8_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..917c7cba69ab1671ce836adc6825bda7354a690b GIT binary patch literal 640 zcmbWr>r0bi7=ZCf;t7^ec=#ass?Bv=zFI50^$W(uioLj`{C=6dye> zeISzdGOgHSpMmb(9ndOXM|bg4n3LYe-e2zYdW!`dszP#cej59^He4bjFt}Dlo|UNJ zmU99^SA3~o^*vb6SVgX`lU+E@kT}6X+n70wIG1~%Nl`-l%0IB?z#!s0ok(6})sT?& zY%(FA;f3wZp!L{`YvUX{kT``Gic*Mj;U>6dbI79S4Ss6X2j|*3a=!30SOoIn`Vu9y zM>gQYMLYUmZGaw$hW=^nM^UE?m*4K8$3!u7Jhl=F7t6pf>a=;yZzkPDNW1j4P$vkZ zrTQ(r;~I}$nlxfkWw5t}E7*K#7p)u$gpi>XOzZFC)!|h%;)uQ^S4@K3nnYiHS!E@x7x@c$(-u?! literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_f4_13x1_A.npy b/tests/fixtures/s8/fourier_f4_13x1_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..258f834bbf65481c0eb0d764c4691002d0746821 GIT binary patch literal 180 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+l>qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-#ySdynmP)#3giN=wZCQU<@TEHm7C3H@32J8&Qa6dZmPi&J5_P3{ja|# b*&Brl?_adR$1Y`6o!z{(N9`8Kp5F@q4mUA> literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_f4_16x32_A.npy b/tests/fixtures/s8/fourier_f4_16x32_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..8af4c61a075ecf537527698c14a79a07636f18bd GIT binary patch literal 2176 zcmbW1`9IZp8^+5P$&$*REXhe6TOrQ(zR83MB_TW&vZTm4_OV1oQfZf-%Ct;Pk$SW; z>YUGAB$AYj9&4ed(S$^4N-{nF!gKw0y-z0Fyxh;rJ8<%(sPHrDga^tb5$pP8Cz z+67VRX;|;R1)q=Xk(A0Tq}LBlFtre6vP|2A)y~?WV(x)a-qT_AOKJS{`KUzfvlcJa zJOD3~I2t%EMHPLyU?h?w?crG{44Xw?P2A|tm=N0e6U_X*12ul$K$7-U+IwXY$QR3T zVZJt?ZzK&tF`H<>Ta7G2`UI(Mg^&^&#Y`@2#3z0k7?n1%V{pyql$7%EFJk8e!VS8PpVh4B|$1zz5N6TAZ$n%2ht>=HC*~ndi)ISn%;s zZV%fUl!B_ATX1FYWNPcIl+3J~i&0v$@b9=g5TJ>aKRprqF6;oqt!-Rhb2mgN^)MUl z^*GQiMH$(Jl66PK%xJs;PT1PAH_cwGJm(PCDbHctEylWAAFvY+8u(?zJiIIA##Z~h27^QPSb5`5W@XC52f6npgXcd9 zT>j;P6+fBLCdZk0ZHX0Ly=KIP+?j&ISHEzj{8y~wWF+plw2JvqLU^BEH?ux_d ze%vtoRO^ZMUOJp=>hIvO>Su}Ycr+D{)v}=)edO=E24d+2f>G&i&Sql{{CUz8|JrLr zUklDNMbw2f-6V3b7fQa($e=$mVo)J$0WJs`cDxn1iM#XJ3)deEApKM;ls!2i(J@#< zUWNsbCO5*#o&5kAsmSuR`S7b-p`_>V%Uebk3Yf8TA>mwCRP)ScGmmu&qVEwL8uO>8 z^ZYTQKm!ZgwV$eRCnX^%_zw?+DcIHKdAjK`6=rHlmzm9YQ}GcgGiR zGvM^aGm`cx)@0vT4xR69iQmOT{ic&pyIG0q`QDJ;nv7GEU%`s2lUZ!lD3k5$V%4XV z1l@)8?1lLwalf$#D#qBegZ94U6IKAWVoyoP<-1HeI+C{A^|1y;4X!I#nI?&>ScGF4 z3p+0qSE^~^z010kd_xa;H`B>!!$z`Ki6Z?SgUrb~TjHbD#75o(QU9|{98Phii*W)B zC|gcPa{AbsvREuoE@6T9cf*)wH)MVP*BKAPsCD0Mc$_XvWD|g$@50%w59RPFH3Da; zSkhpTy2NhnOIA~wN`9XkShewPD9K!eKA!{VwM9QmmrFpe&>x{}*qi+E^G5`TZjN%W@ptBL4uT!Z}zZzmWnPyfL&zELbKJKs7E?apmK=c-i&|443Cf z-f6g^v+WYR?d^dMqmh`U$V2VkZYDf9%C<>M33N=$nO?mIq&M=&T4YJefy-!r;!;Xl zlFfWY+GHbpj$2jvl}+$UKq1_Lq7@%9x&H>z#>aZplx|_aOEotW7 z<^_;;wk~FrJK+0)B`m33#F8|%vAD@hQgNj~3;}_(>X9)d=g&r))OyFz-Z;8dYYBOG z_0YrW6#R5q9&}nhGuH_tng}a_%wiLAY4OLgNo%P0Q6jadFX#SKv=HC*r=a$?or~Qo t091X9>7(jeH1OqoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItqqnnmP)#3giN=244odlyA1S_fE*z*Uc5S6MA}YZ_TL>w$mHf>|fRM n@0UqS+&|A^hs{1_|NXlg>gqoAIaUsO_*m=~X4l#&V(cT3DEP6dh= fXCxM+0{I$-ItoUbItsN4WCO0PTo-NIGTiI|L2VwY literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_f4_3x5_A.npy b/tests/fixtures/s8/fourier_f4_3x5_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..c7f98864f6bfa9924946794e63f07c80534fc9e9 GIT binary patch literal 188 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+l>qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%oItr$mItsN4WCO0LXMgSEN$9rASr)pV%ksRP>h;R~w(+;^rcGFAx60+< jKDAi6{pT-Vv~yE3uxGq@Z{GshTXy{W-`fRlVz&nXNoqI# literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_f4_7x11_A.npy b/tests/fixtures/s8/fourier_f4_7x11_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..689d5ebddaacc5dacfc5ef455ea516014fdfe869 GIT binary patch literal 436 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+l>qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%|Itqq{nmP)#3giN=KLwBX&PcJh-DvXP?wz&Dex}=sb~n77_gb9vuxIpV zvfuD%x80pJiTkTha_#@l?Y;l6I-9*+mjC`cZ{qfw*O}X=&U$Renqy?&%b#m+dd=LP zT`A69=R@GsTqx<^1boQ$)^tRV+ z%eFU>QMK#eJPd5dfR?#&+d%nmyC)55&>&v?aQZy$eh-(&gs z{Vp4R+R25w?a$8Ev`<`i!!BRh-CnO{*B-4!*8AiA&G*%>QQXfXQn$ZXU2eZib&b6; z-*G$j&o=g)*FNvN`)KK2r?->#P1>5ZZ~Cp*`;zwjvW-)Vuq!_J%5E|9^?jyK0&L&x ktlTHs6JVdu7`OlCK^FTX8QJ@ddwTX=tvkAJQ(>eX02pqq>i_@% literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_f8_13x1_A.npy b/tests/fixtures/s8/fourier_f8_13x1_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..f65d312c3a1ff17b4020638a7689130f1ccea86d GIT binary patch literal 232 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-#ySdynmP)#3giN=J*<^Ce+s^}|5N+s^mNbb_Dm`*xslr*?O!m7Ym;=r z4*Q1ZUv2*QytZe%T6VOf|B-#ugw+rHL!a5_)ug(HwZF0dv#`s)&-L#9lnUN7fx2h= e6BcOfcpiDuK5U8g!g|kF_EsljIiG)ivmXHG0aCO8 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_f8_16x32_A.npy b/tests/fixtures/s8/fourier_f8_16x32_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..a5c326a70c63aae31459e1ff2c1c4e3e0228e488 GIT binary patch literal 4224 zcmbVP`9BnD)SpVyaw)ly7NdBrA*mE~Cdm?MWI|f*w2&l8Dv6>+Qc0rC5^dzF?AdY# zgULGfVGJ{7j2R3nx{~Fc_g{FQU(V+_Kb_Ax=ewLI4hQ#<56vN5CS21x<$m#`pVoGw zmbr(imOfF-lw|9S$0lwUqxnkjj40Jch+6)$U{PHg)SI#=41e`u(l+jD84p><{U z=hs$9N}zgKYSPdxH`Zk=v>(^aIS`XHj|Zx{-wxg1BmtuY2bmpVBM@z$lw7!e7>M{= zkRTbr=g-xtZ{2uEaQ8OUtPTeTwTWj_%R%nqR7wBaGVoue*Bou#heXn^+8JUm4BSd7 z&!_ZbMC?EXebW$ReYR-Z)j0%7k-l!10{hYG`_m{+2M@T)Z4zW0cAOt~L#7dyq-|6__fuNe1)>QGdFb~!W9QL@SP4d&0+hR-q$ z=uklV(^$?$Mmeqi>Ju^KC+W^jNUVdFbq#bcpJpH|$z8U@q6`DZgT5}@)`Z%!a{^h@ z3?RNSm{nkNftyw`Ih{8Irlwo0`quUXixBpsWsh`T3+_;6Xeq$BZtrw>tr)|Dv&+m@ z(ttJbAdEz9$JF-np8gm9-6^sDq>x8iPo3;n==l z#{ku_!*sgg9f){~&m`(qU`*ickt2FM43bHAU3glAIU!A(c5Ozffo93bXw^>)^{s4 zEO(Qj?_K`x4X;X3S>elg%3u8#y-xPEc85PRdR26 z2d0nR-v5Wvjm5JAe|uhRg(aGY*Y!*KASCCY?ZuHdFq%CbnE9Ly=I1!m{uaIX_Q3DQ zJoR=YDl~MtBnmN=dZfEzVI_Ww@d$f2DaK=ZXFN(i4q;mr=jOk1TwqK*vs|9ri>@82 zF-M4P5U^Uy&Y2^Dcs(2UP1pqSN5~_T;}QsJP>YwZ6r%kzO}7=^0+~eq|ESHc|`k+}?;op)ID%kM)4B z)kr$6t^kcB%L2zaVu*h1XiXcCfIn+NqGb~oxp9@jLAl*XKEFR_r6wCg9vYaA)8m1h z?2&g{F&}>|<}7v6kmASU$k+Ww4VY9FDZG*@!WOI21hOp~BF$-+bZ@8_(-T_M#-v_W8fFtnjOoBaP)9SEX$q`tOMJO7KXRt z(+%EiI|nfmC>oVT^k+a;s;dh+I*eq?D$ZC?4UGLsSnl*6AC!7V$Pp7fG|Fu+>J|vm zh$61qLC?p@#*5^iH~46-e&O8SrvsqxyY~9t7ktdnTAChsT!gf=6}74p#lYROG>jCN z3i*QdX>M!|km64tyyBmPHWZ!8D8qILuT!b{72OBKlz$Dcyl%yoPTOx0TK$-s__ABk znu+P-^_Nb2ccOnUdo=l)2&VjkrnVOfu|-Aiv~pxC$i;LpRP;;n=_BKhHTj>7 zO~;x*_fL36IAa96=EOO2&U1jZ>`dpVcF4bF87N@i6$%P8=&V<73Y0p0bC^(*C*J zNT0XsDW+a9(=2}^LF&wy=l(10aO6$N=hkE{7SBH(y=oZ~69ws?)*h7rIo~ZIb*u*l zG&;I(#ty-O`MUFOGsPG<_t6iZkbZPsD5yv=Zowsi{F7UPd0-?<;y$^^gx42xa3`{|x) zZ3VCG+>X4kKBT!k2u=AO1?C^#-$`E}#epX)>0p`=SyLxw3qSNgM%lga%^&%oaf0S) zSkDBmLQ|Z`K!mluF~=6n6{Gh$p6W;EE}$ydZ(5Mr52WnyoPQLCA*kv`i2lzm6brEN<$5a%k?7u2+Z`7>+hhu*!ApHg(| z`MFBG{6~*8KSBa0)IMjpNqvET{io;EMFNyLu;wKHZ43C6S{ghqidW^~he zG?L#o02Z`fpV-DBh~MkMyl&G74%PR%DX}9stAZ* znn-sU+0uQf?FLeiVs6>r|}1)pCGrCt;VWh%r)D z^m4H=SGxcGWgAJ&Ft%rIvxzSoe51cStaRo={tmBky&4vh6#eUX{@Fk&^eNhDAl*xL zwS4OPB4EsUd52nE3#PS^=SHPIAmW&x?2CJ1&~6wrpAF(6tuXDHL^lH_4Ek3}0!M&g zGIYqXD**gr1@XmFo+Ze^IO{PD=)Sy&zuz!{I@wnc|Dzaf&dkxF8H-?PZLt0d1{cVV z%94Y>87O#ibk*5AtuV!wG2!g%1J-cBrc?PfptR=HN>}~})ZTP4Q?6ryN#o874X61K z@Z`Xzt>-29fDprJU)}*Zw_8VVjI*RV>Zn|Fz6&!1&Q)V?v!s3O{F&jy#+b_UeGRoN zq{RkA=l3;%Ojf9_msLAF4^tBr$W@_@mdxrqPY00s;cv$%XFm!_@^J`Ce1fife24fyVhCw zj{rC7Lj3sM5+IO*s=xN;Bhz4IekiRSXy>}Vx=r)2Eg5fK682(AsPgWyQZY7(I-ug!; zQ#-)daX_C{iCE4jc^pEre)A-toqlJfQBmPtY68K%*++ z$)hec=vI;99#z3bx-Lb(N=JgDPa@=A&>6@e)NNIFYDd?VOV52A>)Lf8TG*V-(^)tS;gP}_VPz>K9{Nex(k(_VJ2WC<(--iJ z^pUB(ES;0$zBz3}o!D9Flx%C)3zJKNzdQWL$GumL3Y;1Tft7 zDdUwmniC6@SMkdj-owc9I+fVnmo0G*yW@vh`k~IUz7N>XS-0O6i#Z-3kv0VVAhJl$IZV6y*~#)^{?@F{mJ+x$QXmcCQ8oXlbLqKjm% ze;S00c8#->%{?$&M4w!BbO_|Ang7P5HNx#vq%Oj{E*LW6sMiPwApbyG#k;>c;pW$h zEvyzX`c{o!Z_%U!DZ%W`*rGQW@JcR_Y}*VrZzyJGCR-q2+X90)#cnYAF{*6%s0gmd z-71cW7oz7I>kE%vn^1=6{6?3`fv!gSuatWoK!|=+z{-h4*P)(m4s&~en&-7|r-2Zr zTEg}mpUZ*BWI7|PxF45n{O7O&@H+{T+IDNJu8ISeg^SBX2@mMIWCQQ+?85NH`Z@)F@gO5vFmGu? z1=!P>2Y`C&7BNb@QFWyc$a{Ea>LDV~i$i{G!6Xw`}7LF#g@R0#-Js%D{U zhNOPwnVzBJJD9tF>!h0WKI9ILw}14{0}aa$H7f?Y;5uzqpJF9MlbvPy0S|hxKG^cr z592|Qx7N=({Js~TSAN*=yMc!Jnext#WHFXej1wYedeJ#I&HtJ05U9*hl)n?W5dWW6 z6y&;vUixs65%<^Gu zVv!(iT7s&-?51`}#3(mGi!k*sg<4Ly{6ldYB%Mvzs6(gW=pvh+3mE12OLpSv@!uje z+NZpkExn`SZ~Bz}^N@{`d-Ueg2gLAxh?5nW&&BxQWcuZ)eh6bsv-C-PTs^4MZ8+A7 UmSyc8i?qea5`VrQAmxw$1xFa&Q2+n{ literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_f8_1x16_A.npy b/tests/fixtures/s8/fourier_f8_1x16_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..a793c5eb331dde1a65cf4249e9ceeb36e8ab5030 GIT binary patch literal 256 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItqqnnmP)#3giMVg%#D;=Plo4-xpn3nD+g#y+@Tu4R*|X zxS#L#!iz0o@AtR#&;Onz{&@eHw3S+~t>5ggS$aZgh1T8uE}sgno2k6B=kr*pb@uK9 zdj~y*=}XfN?f2WUIN`~%H~SUT{9Oy#UhaRxvDY~2=A-?;b}-%$<9WP)Qm=R6#%YiC F0|0_wVBr7& literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_f8_1x1_A.npy b/tests/fixtures/s8/fourier_f8_1x1_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..c5d4a5dc4a779ecf83aa3820c982803e46b84bf9 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= fXCxM+0{I$-ItqrGItsN4WCJb%o!}dKoWnuDQH_f{iMp(#<{l0a{Qb7XSbN literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_f8_1x7_A.npy b/tests/fixtures/s8/fourier_f8_1x7_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..693780cf9e7884c7c88f44769f7f5578bfecad7a GIT binary patch literal 184 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-Itu2RItsN4WCJd}h>*S?zaHBEaBqLL?d|3Lr_vqg`=-Cz->^ve)*|zV e`wiw8Obf`lzF$nJSwKwpxjlo#^~4#j=j;L8EjStg literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_f8_31x17_A.npy b/tests/fixtures/s8/fourier_f8_31x17_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..263c772c746d0a95997a0a1e2029f5a7cd9f8bb2 GIT binary patch literal 4344 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_f8_3x5_A.npy b/tests/fixtures/s8/fourier_f8_3x5_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..1c9c25eb984bbd0006c15257469aa66203abbd9d GIT binary patch literal 248 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%oItr$mItsN4WCN~M4UhV^$-lDKBooBD@ z?{Y8U;^Td3-*+lxma@jn{XMrjJsRDf+5cL?`czctxxLE!UyU{X7wkFSeinMU~E_v!u<0j=&EYmeG%2pzK){eQ~-fbjd5 S+a}-J|7TOc0^!5A_X7Z3-+sCP literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_f8_64x64_A.npy b/tests/fixtures/s8/fourier_f8_64x64_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..0542eb857dfd4facedc3a7654f73f55d24928428 GIT binary patch literal 32896 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_f8_8x8_A.npy b/tests/fixtures/s8/fourier_f8_8x8_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..f22762f6dc3df07f91145c6fe9e83978668bf2a4 GIT binary patch literal 640 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#iItmt=ItsN4WCO00%0-zSX?N_OxL>?|tmV3W#sBo4+9OBzJKVUTzv=N= z`xw9dzttJv@83{pXLaqv0egd@e;eJF9^1d+u$P12na%sBMJ}ytxb)b5h5yaKcl*xU z^UOXi9}dvLAl&ujZ-gq-5;$35JC;q|_Xl_9tG8>O4M z?{9j!zacD_WBJ#a`*qykzi)f>*nWog-^pJU@7S;4dcywv^RxYVZ@-_|D@0B&~jxjgv`CNF#_H2G^Z?d2?XlB=={Zn{%Eq(gn)qWnE!}GV^ zy0t$>{pz>Y)z9`%xY+&X^YtzE9j0I2?Am#9zskp+%p*_U+rLSF%B)lR!hT=wfg(|n zC-xoHi5Ef_T(oCc-(vdDZq|O2-A_~cmc7`&Axem-nZmX+__4l?_lWrc2PSi8GWUd> zhvUI$k~0T$bn_&erLJpTZ`F2@b&GAfPvgyWo6ZyF{dk=vnrF*+m#A-aBHL;;BYU#{ zM*Zf!FBxOVmvo4`#6?-qJgi_@% literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/fourier_i4_8x8_A.npy b/tests/fixtures/s8/fourier_i4_8x8_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..00ed9b70116e482c578e25bc0a55c91aefd7d6cb GIT binary patch literal 384 zcmbV`y$ZrW5QH!O>r-5J3MnmuSlcN!7FH58q98^tVk16<59`0@d_`Wsg<*Gxnc2Ng zX5;BRGADC(tE^ahH?VHF?Yr2zb?%Qo-7j*#%HT)ouE@j}oAi)r?+MRh z^Y$F-#@%mhj@ds@tl98iYTf?cg|llze_gg0T79*3?}| z`i#g40TEjlcTaaqXLA?#+cy7iL)FsB!)9~c!`{->W^*noD}6#hT2AbQK%l_?-(dDH z4Z8VE&K!@6eNdfcEy4<73Vo?40ySS88W(*Z3CiX>ep2td!I7ZGQT9{uxFAs<;p!3y zdrdEd#Auab497v$BLi;0wk)=0;Tr>!|BaE78w@~&h6CkWFUH8o$Ner3$y{{rUz0*P{d(L9n?*S{iW{=#G>*rBrQ-YpEVrUmPir; zMs)n}C@*P6nGUjgf8J%CNk{$((d_4^$QZ|^Icg>s0Xp_S<1HQS;JXF!@|o+=P`=%( z;9OG-t_2^MdGdsY)h_lI6-{X1=$~Wx=Wi9TK6F&;2+c(EKE+d0uE{VM$nx-7U=}z= z#4mL>n?Sg?z0CISWN1t1wkONP03XMaw0ssB546PJnba-9&;9k`SB}PjY%d-(;>m=m zVe^?iu{-|^#-h1gI&<>fXe_k&F|nueDjLv|%In{!!|J#6V1FMA z7|~LGqB5HZ#k^*^Z7FoHKCHQv<3mS&-7POq{)>X`>&wl=cacc(c(dQ}r2`J6Q4+_x z%5X%dUVSC348+)7d(!fXL3~8)C4;{QTombGinY3j6PG5ebB)|VdRqr+t92%{TMN(c za7+e^&X%t~m(x&&_-wR3Di0)iqvA4H0)Sz5O=ZJ14$J&MP2V=6LYG^%nn{-vhL^Z% zY9C7n4mQE6(}EXqt*nP$hmXmGk?iJdQ2t(WIHcPR_=`B5v}}DL zMr`8L#q?C@rFJ@wv~RwjWkT(^L?8%<@CvJoQ!)R@sd}H6abV<{rC7)qg=blvOj4dw zV9yrC?BF>+>^F(^eNaq7mw~UiYn-fSFk)~Li|u3)ZsR%nwDd|g9&Kt_ z^ll)*3v$I?L3Sid%sB<30|&fl%0Dw)bZ)1-4Yg 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

?P{NZik>0!2ZzVi_8+- zd-iiJo3y**;1qku2dCQ~w(QvdcVd;6fb9CB_rqx(bePP_Q*FZ2GC#Y?)ndsggcyZL-)eb@f|d*_IJ3cRw$ zJ~G9A?JSL}`z5ByaCxd&@V+*Cws?bI{roHKz4tj9#>FhzFZ9EruzT_z zdlTz5vNva+voDxsd+Ywox%-9POvEN#F554#_GpsOpN;l65?}n&ynVu+{q)W~seGIF zANsGQ?%6ieKBiZq?v}_o`>N%WeY8z4*dMy}Rx|PC*8N+&UOmeAy3F3l`tO_7Pq#mkwU(vA5w-KR)ftM*E#k zN5Um{9opab?)2Nwlb7$W(=f^0n}2kF|3h=rwNl&cf1Oy9bu4$IeSzBauDkZT?0I8^ zS@KTouy?w3PVu1b2KzO~4)s2N7rB4g_ah7na$ENAOnu?8!{);NTj4)nzHVQ*fA^Ei zr)yp|s4jP3m|yKJ|AasIx!XZNg literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_c16_7x11_C.npy b/tests/fixtures/s8/ifft2_c16_7x11_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..66fd5524fcf25bd7d14cc8cfc62da3aa8e185e33 GIT binary patch literal 1360 zcmbV>`8yN{7=V$EsU{t$C)boRIabTWMC!9qOjOnt;X#g;#58i`%9S%$GO}Y)V@5NK zbBwb-R~qTS7AqY*sz+=n5<6PE`!DSK+xxz`ryNe&I7>@~N=1^reS*ChWMdN9#7obR ztV1IE1~D!(JOkZ>7~VerjpLpH!9M@?;9orHKL7eYst!p{Pg92!P5Pg#UtXQ!`0!~W zkXGt-%5r<5{q?L^B_kV_jTSG70^6`NY^b`-va$Z!LMR%e=QD3-{)QQ#`X48Y%#Yoyu&WJgd zg%=~*ou2Q>0-C~MvrBas@T1n=r77DokpCcez_dLHwU<~~3G@&UKZ5q-(*@|~XfI`Z zzZS@|qsqd)CFoKbuO6@}1)U`a4Q#O#FWdIlKAYsBV1XB-w>u38_QjF;a+OG+oHK9c zhvSoNrq*4KEl9WLdcV{v#ekmv&C0=ipiMhy@ontDDtcF)b9y7ZGEeCx*7J}#tv2i7 zR)F#W6>W*+R*3dW2p6h8z=iVzQKB#D5c_FI$=9u=u=4GiAm4%uHxmyGV@?^&vmWN_ z%cTNiOn&IOMk&-Z$3~VjGvTU$Kw!F;fFW&E?#B#Qm?}(G*E3+FAXk4ldCVUqA5hxH zy$Xm$YtDVm!#uI_RLeN~?N~3=b%uTIWyj)j=i~IYs zQg@ZXp9?aCvz%twa>gq0s7)QL{d>YdBJ5yRn}pPAdeHj#GA%k2zo+!*``-if}~W(pSXE11rNr z_3!5!kZ8GfQ{VC(SkO(QDjAhP`O28}d?g26ZLK~ZHM@Z+avG|3J#3(6+{ar=2d`BMe!f~U3KulXx&e{=8Ayt zzmKv$rxyBVGOFj<1t8U$8@t245lqj*Ql5-AtdV!=NxRO+*zHbdPSZP4p;)5zt+570 zAsSz$w=`hvOqKCtCL5M}hmOrBbfC?HBNDDr4J!Y(jdtaO2tw5F1%;}nV_6qhD68Ft zl&W*(aX;sP)cqGzgE0;G(p%edqL2+A2UZcM&c~snni4QaWw=N7u0X3 z_Hpo2U}0b74|h<5bMH-rW;W7&;?J90F99V#SC@p%2}pYOE;oSQgoMA`dPetnVbg}y z45|hn6*hg+oi1cTe(tN-WLh1hWjsZO$hia4^xERU1l2-lv6TPkzgV9{acE3Y0g#NTziP)Zi!p^hZKu#{A|a@!xI`8(7J9DqACc8xX?BGt@3Wg6>#T(x>W>S=2>jih+ivE&$XgocOMVN0&;h)ixx zV;Un9nS<{%gZVhdKC$hCCDBUTuB1FFn`)D%+4f)9_qX?bc|QJ3-@w(Y(pLRIi(;ij zvT1uLG|xyUH<~Sl7M;XSWrrt*C9$Je|BZv;2`Q{kJ>^t*GV7D?^01|N&}}JMl>dvt zMmgOvl3I%jqWXoxpzVXXq@f zM5}blTE-q3$bC(T`)dePYv;Bc%C5m|hVzBtKik3RRj5Viogy3~JCgr0Z-feFj&*PC zRe07nU|r)c2Rj$(DAlhJMeAH&l(*Diclu}W?$l-EMEE>B|F;xFOcXh7X}6%ad*CJa zX$Ok>OXN9!G+=A`YSUU~BXo}X15{Dz;fU>^{bQG~v>ZYBq6fC$$nDPfRt9B zT^okP@Qr^<{K31sA z&jtIr?Si`wO~BC(wkLY>;MQJ(*JF^2)zn5Cg=0N@pAY5wptBs_?k_to=BRAGyK*1?HZArgLTN3M7a zVH0;)-`t=GLP#&$t^|o-;`a#cv%!9dV*~r-H(5QR6$RZsR6WXd`n+HWVw5&+%s~U{|PJN|iDn z&OGs(j|mV!f;VdH-12a#Mw4@;qzOthB*_KiZFtwZeP1dlP#aCr%?RqiH0kWhb4xj1 z?;h@A@S9-q%Fj39G2z$sdP`X{$g$mC@J&NK zbPDT?JOAOL(Vd;@WgUUb@s=mYnyVpyz3Kdh%nIyy6Db}s&j9@svHa$f1nALB^43Qe z0VDFLmgIRGEDw)6E!rv2iuFZV_SP<3&Kq_mO4DJR4QS4J5qQu)`1E_@S`5%%v0H8~ z22EbB>rGl2o(txe&mO-74OAkK%)152V(Rzt`+7iXM$dZVmI!2yJK-KD!n*xqack#u z;of^;aD}oG1g)$uFQ@(pPCqxrh{n4>t=-%xE6Rm)-oI45Hg<(W8B>YlE0s7IB#C|P zD8=csq4y)t-+&0?4mRn24{Riq8@K1nkZe5o!L~(?qUjxP9aM6Va=+pf@oS;po+#Pi z+YC$W_(_wQYDhJ_s(CP}2F7KD?Qa}4ciYgO~B_-D|ekcGi* literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_c8_8x8_C.npy b/tests/fixtures/s8/ifft2_c8_8x8_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..47423be48d0be36925655bcee98f7e52b2dbe784 GIT binary patch literal 640 zcmbWr>r0bi7=ZD&gQZI>huO3tFS>fwrfBZN+@4z`!6w;I$wnf&BN{m;Wx0ZK!+AqqSx&h@tNZ7PdHO2dmRA+#Rp_=pR4!%ZVN$k+{qF=jy!Xcy z%r2tpR|r~I4;)^vrXuwWcFC^WQr{=wO381G9EyZNmy3vyg!pmo{GL=Om-0( zv)ia?egt~>@VNY`Dcru=hZngeSoD1c#w#0Gd}%kzg*q~z?I&?-3e?wilA}ru*zXu% z)@}tqhlCeO`$?l^7qvL{k;IW#kbLL}t*H;*{!+Oq+!z2VJY7fGXp>g81 z=q9q!oyxBH=X9pd2;o9IX=*l+>5NkNNgE;5uN+@)43Q+a7vxLZFz4KHjLH}%)t%oU z-}wo1ON`2>%r(ViLzC@+CKV@oI^fN48K!12eDNt46?w?;;b9*tS`HvA*p$BUtyJ)* z15{U4=#y@Ng^zkP7rAIc>|H4A4kk=sVC>m6f1Y)0T7@fO(!nzDeY6y#H#0GM?-DW0 z>A?TVI36gh1hHF4=d@$=aW(@+!5&`5q@Zh?nQ!OYnWHFpmH+6z%i literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_f4_16x32_C.npy b/tests/fixtures/s8/ifft2_f4_16x32_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..fc2ac53ae5a6678367eaf889a11754f406c1faf7 GIT binary patch literal 4224 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_f8_13x1_C.npy b/tests/fixtures/s8/ifft2_f8_13x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..4dc1121c65a4ced2c7c312de294e893356caf18c GIT binary patch literal 336 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhQ>MyhMGDGwF+bct{nm!mGpE^+cQAHs)|>k_X@Y}XK(aT&6eC_|NehW zvP0}Xds!Lz7k)v<_RBvAi2rqd(f&B5v&S8zx7a_?nkcK@eb`<{O8Fi0)r(ryxq4W2nso#%g9=d(|(cEK?=HC4P$xVHN literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_f8_16x32_C.npy b/tests/fixtures/s8/ifft2_f8_16x32_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..4cec68cb5983ffdb34d266273fee49944aa7fcb0 GIT binary patch literal 8320 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_f8_1x1_C.npy b/tests/fixtures/s8/ifft2_f8_1x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..6b8ef8f8f8afc7b316d3918089776e9118c870c4 GIT binary patch literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 j7iT0EqyqUGhB^v{nmP)#3giMV0iEC`y^Qzv3{U_7Ee;+| literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_f8_1x2_C.npy b/tests/fixtures/s8/ifft2_f8_1x2_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..732f29a754d29474cd95ba5c185acbee1e64fed5 GIT binary patch literal 160 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 t7iT0EqyqUGhB^vHnmP)#3giN=3$+_Flpdej&j1Ai`)w~Mz1;!l0|110B+vi= literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_f8_1x7_C.npy b/tests/fixtures/s8/ifft2_f8_1x7_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..09a17690cf52b8e91c2db752a9bc47960a8ab1af GIT binary patch literal 240 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_f8_3x5_C.npy b/tests/fixtures/s8/ifft2_f8_3x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..67377d0265437e442ac2e3206c0e8085b7cdbcc1 GIT binary patch literal 368 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG#ySe7nmP)#3giN=t?HMSPS)OI&j1BKB);&TIkL-sq1;C6khKT)H^}&x za5i`DS2c`Gn!kG^I^P~my?tMYy|kA4()}!(CS2-Uc4Gh3<5742q)xThab1~l>-#o) zC9_4rHNs+e`wKc&h*rM_6*Sy-&Z6i?cX?!q5IRLmi={4b|nfw*}EUj SJbN^Gdo=y%=G&v$zaId4*N@o% literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_f8_4x6_C.npy b/tests/fixtures/s8/ifft2_f8_4x6_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..c0897c52dd4594ad60e2c30c33e1fe7fee609f55 GIT binary patch literal 512 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGCOQgcnmP)#3giN=LtbZOTolgOGeE(f??=tP_O0CS9Cvl_V(a2o?&rA_kX-)!ynQGbxZ0q}+wVfvZ-2$@`|a!5Q}#2TF--ChJY#?5 z&Z-UUI!o-0_y3wIabmZ8W8*Y9PHRqaTf9G0`C13Iy`xdEKNtdmw_G?Vw z_$-@SYVT59#xAF{Y5#l2PWeMAv-c<1nU=p<+Pj}aBGQzw65Rc0^82e$^`pBVZhr*CetR_cp}Qa5 NfBRwnH-h+oKLBHe%t!zL literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_f8_64x64_C.npy b/tests/fixtures/s8/ifft2_f8_64x64_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..fe185a3ce8909627f6c029be9d181db6c4527212 GIT binary patch literal 65664 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 M{CXzyiMSj79}HahxBvhE literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_f8_7x11_C.npy b/tests/fixtures/s8/ifft2_f8_7x11_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..46d69b7cc166203de89304aacba43019ee5482a5 GIT binary patch literal 1360 zcmbW0eJm7k7{|3<=nd6Q<!;+UFn@cM>N2NQv>*dgG)gd{x%Xzs& zoI8hN^HNUdWlwHpDk>!swVKt^3t?Fjy=?vU{Mqy0^Lu{3-}CnUsDkzfhD6S>IBk)_ z=Eq86#O!S>wr332gYC*<3q<0R;%MO^k(eL*H{ctcAc_5RE{Ts$jQ!&~ZFOaFxz4UE z8S6i7kvQ+i!p<%OSo|~9#Tf6yPL=Rt_t+VsZ!M?vr+!s1JQ4SvO&0TRNcc7M=u~{~$$M8c6&C+?Or6H?WAXG z)TAE##7mqWd*p%_WHN>=bHH=8WAW#6r8x3(XT7Pe6dDQ@E7bBjxajhj{b53h+UF@NZ#L1`vG^2Tkum!GcU0mK(_|jjl;i8=!nA^iSu5WPV8W0A&7jKXiXc q_DlDVL?1x(L-j@V2SlHgH>z){f650Cet_^r`J;Ry;g|AF`G?;q<)P95 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_f8_8x8_C.npy b/tests/fixtures/s8/ifft2_f8_8x8_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..0a38d26af74a75b5f29aa8773fedabd2c7803c8b GIT binary patch literal 1152 zcmbW0eJm7k7{~84uTdElUE@levqO}23{M%tklwtsteLwTh09%0iS3FyH!tUmbay!6 z&TCxGTgpRA%O5l`EY(ae*fO;;wouy2AHP3){`>u&eV;wg^Zh)<;b+4lVyKifN(O_& z6~+q~UUY_cyxS25i_YNj1y=-dDHr$x4);$YFfK{R-S`(K#9iiYydBvrI-AL&XVU*S zOd@(ZgA14Hfbuu$^+MkcaoaH{Q2QnFbsKoP+$~9_mxCkURGoA4B4+u#%oR~ZsCp6J zVC0aE%?cjvUW*3*>4`k42cka8%ro-m_B;Z4QemifW+NQGokvTUQi6XDOM56rfhw~d z$1Z(K#^v;-wb1Z>*j{3ya29ny*SqV+l8P*_T)ee@aJUG?@>Rc%QyLIEDSatD)u5xO zCCe7=$c`GA7#c~$!Jvi2Eu&)W`Wh|e&9wl{rF7DCv=!>4d}(oQGroUjW?RuygN-~3 zFTQU%-0wDwe-PY`A6Osd)?QT*v96t1TusF6q^#KeeF6xx&uprab;Di*^*%RoFS_Zu z|9sWc3dZJ?Tp6bb^6vC;<(vx8b>s!q#ui}NZg0KHR0$L_KM!Z5Dd4bSWd7LJBG`7~ z=_GwuE6Ui9ZLRXOuwuXoF`QMRN@-=6(yInZX`hXU{X?X-4UVjM6rgx{-sVlG3N4xQ zGh+X0V2c`8-KW*ia(>BrJfITvTh>y;G;N^!J_gQ~N;vI0JR4=HLESH(CjI_KIJ21e z>Do*O99`k~+eJP{e)_m;(^T1}|K~jX976^)a6ou9hv8I%(F&KHKWMd3@8BE3ydlNu z(bC{3svkC3`*0E#o}k9)q-UCW&8D8n1EB|^J~=-U{gM46F&`51llwtpf8>6V@SpSt l3ICAr7YYAKK1hCm;0p-;Nj^z_Nxn(`k+=`?evr5?_zeqc0bT$A literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_i4_1x7_C.npy b/tests/fixtures/s8/ifft2_i4_1x7_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..d822b5427d48a51d0853766f7406797175161984 GIT binary patch literal 240 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhB^x7nmP)#3giN=+uL%1h}(ey3UZll-|PPVYyZJA#oEV3kL)$_-Zre4 o{cJCLsHcSG^RNBWl^SYqP5!)pBU8Z2+ZX?$^U>AYqnWoK0IYpilmGw# literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_i4_4x6_C.npy b/tests/fixtures/s8/ifft2_i4_4x6_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..8b25862526f04177918778b62e8fb27810434aa2 GIT binary patch literal 512 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGCOQgcnmP)#3giN=RbcR8KLZqmg27As9Xj`7inJc=Uk#SOv|rTOw|lky zkNqKF`S1H7>Pq*Z$=jppw}%+}!9D;ivD99xFr((&!<+VZ)-ubxUV3ZarrG-9I@81b z0f$9c*;nj=>VIp$0c`Ind){EKi|bi$+28;6TS~k1+kP%f>unG3e6X)r9%v@e{2HqM zA=DlQhCly7;QanoJ`pdfw%y(j@!zBUGR6~Jmsehbs`~`hS89VMzaLFMy8F@X-;d@# QbocK^^WT1LRR8Y>0E5!h3jhEB literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_i4_7x11_C.npy b/tests/fixtures/s8/ifft2_i4_7x11_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..fa6ba75d49e7fbff85c5bc2701ba24f84946dd41 GIT binary patch literal 1360 zcmbW0c~Hz@7{@D3BS%xNRKxz-n2j=K$vVO_^bJ4K5Q2DzE>*Yl_6zvq44_kFJKCuP$%ugyD5O(IPcyg*rm zl;tg9cuS>x0ne4;1% z2^St-nWi+L$v<0qDsb`c-cGn67QA#c&&7C)_ZMDgXi=hHq2#cg=ylscYppS2!gpuk z$+~*rZM`!=bm zyl+6mNOPP(nU8XhJub1C?YJn_tb*g#foWNX5@*{y1-Jd$zF*JlV3>)KFW7)kr@0va zZiWHteLt3ITJD0^yw>7zasBW%M|?Rlvjr`j%H&n5Mw}U?-|6d61L-#wsn^-uhg2qc zx^bWfe!555*nMq-yPiEYw$^(1mMGyrYSqD(ucz6lq&{#rjkYmMPK6A76l7Ou=>0)2`qqVeN8VB+o$p17ykosu-K>7pHKb;SqAJqe$Kal;< u{Q=o8lKoSCAkhyJeNp`((I@2%iN1m8pYlQZ0m2s&{wSZ6UnG1}{_!`#be^jK literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifft2_i4_8x8_C.npy b/tests/fixtures/s8/ifft2_i4_8x8_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..a583406fef958eaccad68ec628db1a9103786d1d GIT binary patch literal 1152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG7CH(RnmP)#3giMV1~7PF51|3x+C@)FC|QN`YyEh z0f)misQ5()-SEJ^L&CuLjrLaiK-(Yn=ib81eE>D*mi?qY$H})vUhZE}`^e~T={o!O zj^4#HU*FsRT5C$=U7m~kCwoqqd8GOJ{$sp?#%hm`*cWz`wmtBCV*jgih39;kPy6L2 z=rL&Kp4q?Y=DwM4Kb+rx>BQw8o!IM8|DW40TUq`rhws;Zm#+{0TW;UI|3HF69Gg$_ zeyg>%mk)J6*sn2J!MKxs^M0wM17|j@KL>XgEdD25{?zkn=_UI<#Jf$V8-}T||!`s60IW_M^ow)Zg&<-;b6)p!#9qcLY6s*`uX@^z^wOE&bY~rEm1~ QZ;zHg?9uWMdj7Hp05b98IsgCw literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_c16_1x1_C.npy b/tests/fixtures/s8/ifftshift_c16_1x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..f72c79ce9a1115ea1b1df75951031c391c397956 GIT binary patch literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 p7iT0EqyqUGhB^v{nmP)#3giN=GgAyEl?Pq1XS;k(&1vIHdjL=CBVPaj literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_c16_1x4_C.npy b/tests/fixtures/s8/ifftshift_c16_1x4_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..fb66318f75aa95412ae359305aae2eadc3c9841a GIT binary patch literal 192 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhB^u+nmP)#3giN=IZrdK{_ME3KQ4FKq?1`s?JrEbtk}Hd-u{Tu6w^j^Tl5`@>lG?5wrF5>M6JP0|3&;KuG`q literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_c16_3x5_C.npy b/tests/fixtures/s8/ifftshift_c16_3x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..acf8b4c1dbd4a178c9906167fdd52fbdad0b38b1 GIT binary patch literal 368 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG#ySe7nmP)#3giMVndy5{7^U9YABg#UR%q$m{SW5($(ifD+`l1D;{(6P z+5G|gc3(R_f6xAX=_><2K6$r)PyOSM6L#LRuY2@5Gjhix`x$#=Rx??hwy!9875zc% z@csiQTav6ickI{D3M(}5eZPMKbEmcY_YL+2{{rYQNa4EvzMVuk8D#cYWaud1tS%Fjw$+?)Lo* zi~luyO5L~D@jLy1mFxcgJ#0xDhGsAJ`&cdVH;sH`f1-6-{9XS$`x^pz9MmqawP!JX Tv+a2CQ+uA*{|@r{zO)Ab_$Zeo literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_c16_4x6_C.npy b/tests/fixtures/s8/ifftshift_c16_4x6_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..1c6414cd433d219100a0f139c17f12912344ece3 GIT binary patch literal 512 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGCOQgcnmP)#3giMVhdu9P_q8mr*V9P%FZuP-zUqU)-=i|m?Eie5R`kf} z&VHU7^Q=7^?%C)3TdMUe>6!h4yohz}W~c18<&+#b7x&J-;m!7pRiWGM*Bn{T@BjUU z{g2gob{km_*h{T^^-t31wY^S}3RjHm8~b+)wy$!^eZ0SCK8Xx+xIxzcRB38us>zG-G;g1^dnCZMAP=*euF1=h6NP zGj9|LZoas`K;;s5d+1&J52tsm`Y(55f5!vm`{t~#_jg=XU(mPxpuI+*NZYFYPxmuy zU3o`(;#2!28!SG_oW8JMBv3KvMZ$yqHTQD=HDunj-%;kZcxlKfdzY?>9q}1g_8&;P zU|tx#&z`~6Y(-EXn7eewsXgZr6ocl>Ener!MEUxnc68K>+|aMnIN RH1(bRk65R0^C|E40{}k@>Y4xm literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_c16_5x5_C.npy b/tests/fixtures/s8/ifftshift_c16_5x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..b28ef40e75b7ffc4020230061cb33bbd407bc6d1 GIT binary patch literal 528 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGraB6ynmP)#3giN=AN_kA%J;w8Uvet_wdH}U`&ZnV%q!UcX#bgRpT)`n z59~D-*KG;)K5H*@@WIZV7oP0D@qA4Xhx`M3CARd{&1)a*=X>&vAzbOz{$0~{XYQH# z#@=U=b&#X^b$hpYg<{WkJ+|Mm&Ubxw}}oS{RdKug)X(8+|Tg#YL3Ou=lgZeFPX!sdBL7# z?d&&ucy8O<#A;_Oe)iJ-hIjm?Qt!+54s#~Tw@FO1zp~`z>g4>3_AE84IVz7{vhU)o zoK&Cocz;IcvRQi>p4kU@|4dhw+^|1ym--yX8ISinG^T%>vFF@=r;<{ORgYfoS4jF6 z*PnKFzenV1Hq|{B_s__ExH4k-#W|A{RU^IOtZMQZ$AKKrSL%j literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_c16_6x1_C.npy b/tests/fixtures/s8/ifftshift_c16_6x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..be00df7fb811841818b8c81fce5abd510d235b85 GIT binary patch literal 224 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGW;zOnnmP)#3giN=C%*)i$Tz;W4>)_`PKoj+dj`R$_UG*O?^oz7>-#k0 z?f#6M&x_3Kui5wP>a>#oe98XY+d|z{pWf_WktmgF|M}$p2U(xweVx|Wt8~RXM!mjk W|0N-=cK3$o_KWy)ZynqAdOrYy@la_1 literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_f8_1x1_C.npy b/tests/fixtures/s8/ifftshift_f8_1x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..24e92d0c12064572b6f1dec3355ab3455c62e0e9 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= gXCxM+0{I$-ItqrGItsN4WCN}ahr==anJ4!H07CB{Q~&?~ literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_f8_1x4_C.npy b/tests/fixtures/s8/ifftshift_f8_1x4_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..54982181ce7a82f759696aae54984e16fd70a475 GIT binary patch literal 160 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItnJ5ItsN4WCJb(BVMNEkKfxXh}s4lKip&g#p&~_b@LzCFWB>rbw|uC FdjPMxD`)@! literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_f8_3x5_C.npy b/tests/fixtures/s8/ifftshift_f8_3x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..0414aa38952cc91f9099dada5223646d7f5c7068 GIT binary patch literal 248 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%oItr$mItsN4WCJdX9Lu2G^-t}kJ}S);_I$iwq1f_qv(wG}+XNa+yJTMO zpP?Cb=&k#K{U>Dd_ZYr^W#6#pi_$IIz4q&hejoZL|IEH-O>JDO@Rj{ZC6oIO6g=8r u(JYtXzVWgBl5a6Aq95+;=V191x!d%K{js&G_IuqP?cdO=;I8F;bUy&OuUU-% literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_f8_4x6_C.npy b/tests/fixtures/s8/ifftshift_f8_4x6_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..2d65e4ecacf5c693135f0860a259fe0fcb6ab856 GIT binary patch literal 320 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItpf*ItsN4WCN}bjt)s2&ra?Coc?4M9OO<)G!#{CP{RxYe^d%yq1miOP4{h!(EBq_!fhFr66@{+tEw&213 z6?r^K88fcgt88Jr`&jVBevMBPqn8<6u;)wLwW^}<(f(WhD)0E_J+d!I>=2jSyWhS< zqs;Pg+e>?fCE?+$FV5~i7Jlx`zh!Ut%ZQ!-*L3pk{ziUizWn1}t|A!ULth)a% z*n1eHMY0~gx!*^ZT}IaQiv5f&&n2I=KHEQ|A-ncn-F5pFCchp(TJUWD1KIs-yc16B zH@iLAhWpm*{TB?ruB}M9Yyalv*9#8+@9keOBh>xtr_1{vq{NuE#-H3jA?Z=?YW_X@ z3%=^JZ`a;wpELJP**odi`(LbE5!~Bz+rB^|K9IfphP^|+=GzyJ3+-Jp_aubfe7Qg4 cz^_;Bz7OpUUTqd+{BhR4LRR|M?=7$G0q6va4*&oF literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_f8_6x1_C.npy b/tests/fixtures/s8/ifftshift_f8_6x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..c9392af03330ce440ff2ba59d71816bd63cb5f94 GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$dItqrGItsN4WCJdq*Xv~pn4jCT-8o=?OW>V-RJg{TuOZLu+pc=c+IhU$ WFVIw?&RBPIe-?kr%I%fU_5%Q$LNZ$b literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_i4_1x1_C.npy b/tests/fixtures/s8/ifftshift_i4_1x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..2a7deea00721a7e39fb741ccdd1bb12658945e9d GIT binary patch literal 132 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I$-ItqrGItsN4WCO0h|Ns970M3dZW&i*H literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_i4_1x4_C.npy b/tests/fixtures/s8/ifftshift_i4_1x4_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..f5129a8912f60cbfddd820ea672dae9cac72cef6 GIT binary patch literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= lXCxM+0{I$-ItnJ5ItsN4WCJdC1_p+||NsC04#bQ=HULtRBV_;p literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_i4_3x5_C.npy b/tests/fixtures/s8/ifftshift_i4_3x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..19ab05230deb18ef6f560dcacd2bd8d98852d38b GIT binary patch literal 188 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%oItr$mItsN4WCN~$|NsAIV_;xl0%BGu{tXoS3B>GB@xMSeNDKr(;(vgc H87Kz;_LVzU literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_i4_4x6_C.npy b/tests/fixtures/s8/ifftshift_i4_4x6_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..58e062e9b024c73cab86ed17daabf5dde4ea0d59 GIT binary patch literal 224 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItpf*ItsN4WCN~0|NsC02gDo<3=AOr8_NF+q<;Z13s4LM7=f4-h(UZ9 Y8z>Fd#|#wv31oxxg3N=dVFQW-0Cl!QX#fBK literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_i4_5x5_C.npy b/tests/fixtures/s8/ifftshift_i4_5x5_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..2ac1268e492e8ab056aa1a9eb3e7129362d41b67 GIT binary patch literal 228 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%IItr$mItsN4WCJb^1_p*7|Ns97VJ0A(1&IFv*&y-XK$;auvjH(k4#WnD eGXgP4?k5nlL;1`=8YB9C`24Mj14L^1O literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/ifftshift_i4_6x1_C.npy b/tests/fixtures/s8/ifftshift_i4_6x1_C.npy new file mode 100644 index 0000000000000000000000000000000000000000..a984b40eda8847cfd43fa86d0d44e2369a406296 GIT binary patch literal 152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= oXCxM+0{I$dItqrGItsN4WCN}r|NsC01H_yR3=H3)G?4la0KvQ~VE_OC literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/make_fixtures.py b/tests/fixtures/s8/make_fixtures.py new file mode 100644 index 0000000..5fc8734 --- /dev/null +++ b/tests/fixtures/s8/make_fixtures.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +"""S8 (PR-11, PR-14): writes the committed numpy/scipy oracle fixtures for tests/cases/s8_fixtures.hpp. + +Run from anywhere: `python3 tests/fixtures/s8/make_fixtures.py` (numpy 2.5.3, scipy 1.18.1). Every input comes +from a fixed seed, so a second run reproduces every .npy file and manifest.txt byte for byte with the same python, +numpy and scipy versions, which the manifest header records. + +manifest.txt: comment lines start with '#'; every other line is ` `, files relative to +this directory. Case names are `__`; the element type is the field after the op and names the +input's dtype: `f4` (= rb and ca >= cb) or (rb >= ra and cb >= ca): + modes.append("valid") + tol = 0.0 if kind == "i4" else 4 * EPS * float(np.abs(a).sum()) * float(np.abs(b).sum()) + pair = "%s_%s_%s%s" % (kind, shape(a), shape(b), tag) + inputs = [save("conv_" + pair, "A", a, kind), save("conv_" + pair, "B", b, kind)] # shared by the modes + for mode in modes: + c = scipy.signal.convolve2d(a, b, mode) + case = "conv_%s_%s" % (mode, pair) + add(case, "conv_" + mode, tol, kind, [("C", c)], inputs) + + +def conv_cases(): + a = real(101, 7, 9) # asymmetric A + conv(a, real(102, 1, 1), "f8") # 1x1 + conv(a, real(103, 3, 5), "f8") # odd, asymmetric + conv(a, real(104, 2, 4), "f8") # even + conv(a, real(105, 4, 3), "f8") # even rows, odd columns + conv(a, real(106, 3, 12), "f8") # oversized in columns only: no valid + conv(a, real(107, 10, 11), "f8") # oversized in both: valid swaps + conv(real(108, 1, 6), real(109, 1, 3), "f8") + conv(real(110, 5, 1), real(111, 2, 1), "f8") + ai = ints(121, 6, 8) + conv(ai, ints(122, 1, 1), "i4") + conv(ai, ints(123, 3, 5), "i4") + conv(ai, ints(124, 2, 4), "i4") + conv(ai, ints(125, 9, 10), "i4") + + +# ---- fft2, ifft2 (S8-R2) and the shifts (S8-R3) ---- +FFT_SIZES = [(1, 1), (1, 2), (1, 7), (1, 16), (3, 5), (4, 6), (7, 11), (13, 1), (31, 17), (8, 8), (16, 32), (64, 64)] +SHIFT_SIZES = [(1, 1), (1, 4), (3, 5), (4, 6), (5, 5), (6, 1)] +RESULT_KIND = {"f4": "c8", "c8": "c8", "f8": "c16", "i4": "c16", "c16": "c16"} # D-031, numpy's own dtypes + + +def sample(kind, seed, m, n): + if kind == "i4": + return ints(seed, m, n) + if kind in ("f4", "f8"): + return real(seed, m, n).astype(DTYPES[kind]) + return (real(seed, m, n) + 1j * real(seed + 500, m, n)).astype(DTYPES[kind]) + + +def big_l(n): + """L = ceil(log2(2N))""" + l, p = 0, 1 + while p < 2 * n: + p, l = p * 2, l + 1 + return l + + +def fourier(kind, m, n, seed): + a = sample(kind, seed, m, n) + out = RESULT_KIND[kind] + eps = float(np.finfo(np.float32 if out == "c8" else np.float64).eps) + tol = 16 * big_l(m * n) * eps * float(np.abs(a.astype(np.complex128)).sum()) + tag = "%s_%dx%d" % (kind, m, n) + inputs = [save("fourier_" + tag, "A", a, kind)] # shared by fft2 and ifft2 + f, i = np.fft.fft2(a), np.fft.ifft2(a) + assert f.dtype == np.dtype(DTYPES[out]) and i.dtype == np.dtype(DTYPES[out]), (kind, f.dtype, i.dtype) + add("fft2_" + tag, "fft2", tol, out, [("C", f)], inputs) + add("ifft2_" + tag, "ifft2", tol / (m * n), out, [("C", i)], inputs) + + +def fourier_cases(): + for k, (m, n) in enumerate(FFT_SIZES): + fourier("f8", m, n, 201 + k) + for k, (m, n) in enumerate([(1, 1), (1, 7), (3, 5), (4, 6), (7, 11), (8, 8), (31, 17)]): + fourier("c16", m, n, 231 + k) + for k, (m, n) in enumerate([(1, 2), (1, 16), (3, 5), (7, 11), (13, 1), (16, 32)]): + fourier("f4", m, n, 251 + k) + for k, (m, n) in enumerate([(3, 5), (8, 8)]): + fourier("c8", m, n, 271 + k) + for k, (m, n) in enumerate([(1, 7), (4, 6), (7, 11), (8, 8)]): + fourier("i4", m, n, 281 + k) + + +def shift_cases(): + for j, kind in enumerate(["f8", "i4", "c16"]): + for k, (m, n) in enumerate(SHIFT_SIZES): + a = sample(kind, 301 + 10 * j + k, m, n) + tag = "%s_%dx%d" % (kind, m, n) + inputs = [save("shift_" + tag, "A", a, kind)] + add("fftshift_" + tag, "fftshift", 0.0, kind, [("C", np.fft.fftshift(a))], inputs) + add("ifftshift_" + tag, "ifftshift", 0.0, kind, [("C", np.fft.ifftshift(a))], inputs) + + +def main(): + for f in os.listdir(HERE): + if f.endswith(".npy"): + os.remove(os.path.join(HERE, f)) + + conv_cases() + fourier_cases() + shift_cases() + + header = [ + "# S8 oracle fixtures, written by tests/fixtures/s8/make_fixtures.py; do not edit by hand.", + "# python %s, numpy %s, scipy %s" % (sys.version.split()[0], np.__version__, scipy.__version__), + "# ", + ] + with open(os.path.join(HERE, "manifest.txt"), "w", newline="\n") as f: + f.write("\n".join(header + LINES) + "\n") + + +if __name__ == "__main__": + main() diff --git a/tests/fixtures/s8/manifest.txt b/tests/fixtures/s8/manifest.txt new file mode 100644 index 0000000..9be12f9 --- /dev/null +++ b/tests/fixtures/s8/manifest.txt @@ -0,0 +1,136 @@ +# S8 oracle fixtures, written by tests/fixtures/s8/make_fixtures.py; do not edit by hand. +# python 3.14.7, numpy 2.5.3, scipy 1.18.1 +# +conv_full_f8_7x9_1x1 conv_full 2e-14 conv_f8_7x9_1x1_A.npy conv_f8_7x9_1x1_B.npy conv_full_f8_7x9_1x1_C.npy +conv_same_f8_7x9_1x1 conv_same 2e-14 conv_f8_7x9_1x1_A.npy conv_f8_7x9_1x1_B.npy conv_same_f8_7x9_1x1_C.npy +conv_valid_f8_7x9_1x1 conv_valid 2e-14 conv_f8_7x9_1x1_A.npy conv_f8_7x9_1x1_B.npy conv_valid_f8_7x9_1x1_C.npy +conv_full_f8_7x9_3x5 conv_full 2.1e-13 conv_f8_7x9_3x5_A.npy conv_f8_7x9_3x5_B.npy conv_full_f8_7x9_3x5_C.npy +conv_same_f8_7x9_3x5 conv_same 2.1e-13 conv_f8_7x9_3x5_A.npy conv_f8_7x9_3x5_B.npy conv_same_f8_7x9_3x5_C.npy +conv_valid_f8_7x9_3x5 conv_valid 2.1e-13 conv_f8_7x9_3x5_A.npy conv_f8_7x9_3x5_B.npy conv_valid_f8_7x9_3x5_C.npy +conv_full_f8_7x9_2x4 conv_full 1.1e-13 conv_f8_7x9_2x4_A.npy conv_f8_7x9_2x4_B.npy conv_full_f8_7x9_2x4_C.npy +conv_same_f8_7x9_2x4 conv_same 1.1e-13 conv_f8_7x9_2x4_A.npy conv_f8_7x9_2x4_B.npy conv_same_f8_7x9_2x4_C.npy +conv_valid_f8_7x9_2x4 conv_valid 1.1e-13 conv_f8_7x9_2x4_A.npy conv_f8_7x9_2x4_B.npy conv_valid_f8_7x9_2x4_C.npy +conv_full_f8_7x9_4x3 conv_full 1.5e-13 conv_f8_7x9_4x3_A.npy conv_f8_7x9_4x3_B.npy conv_full_f8_7x9_4x3_C.npy +conv_same_f8_7x9_4x3 conv_same 1.5e-13 conv_f8_7x9_4x3_A.npy conv_f8_7x9_4x3_B.npy conv_same_f8_7x9_4x3_C.npy +conv_valid_f8_7x9_4x3 conv_valid 1.5e-13 conv_f8_7x9_4x3_A.npy conv_f8_7x9_4x3_B.npy conv_valid_f8_7x9_4x3_C.npy +conv_full_f8_7x9_3x12 conv_full 6.5e-13 conv_f8_7x9_3x12_A.npy conv_f8_7x9_3x12_B.npy conv_full_f8_7x9_3x12_C.npy +conv_same_f8_7x9_3x12 conv_same 6.5e-13 conv_f8_7x9_3x12_A.npy conv_f8_7x9_3x12_B.npy conv_same_f8_7x9_3x12_C.npy +conv_full_f8_7x9_10x11 conv_full 1.6e-12 conv_f8_7x9_10x11_A.npy conv_f8_7x9_10x11_B.npy conv_full_f8_7x9_10x11_C.npy +conv_same_f8_7x9_10x11 conv_same 1.6e-12 conv_f8_7x9_10x11_A.npy conv_f8_7x9_10x11_B.npy conv_same_f8_7x9_10x11_C.npy +conv_valid_f8_7x9_10x11 conv_valid 1.6e-12 conv_f8_7x9_10x11_A.npy conv_f8_7x9_10x11_B.npy conv_valid_f8_7x9_10x11_C.npy +conv_full_f8_1x6_1x3 conv_full 4e-15 conv_f8_1x6_1x3_A.npy conv_f8_1x6_1x3_B.npy conv_full_f8_1x6_1x3_C.npy +conv_same_f8_1x6_1x3 conv_same 4e-15 conv_f8_1x6_1x3_A.npy conv_f8_1x6_1x3_B.npy conv_same_f8_1x6_1x3_C.npy +conv_valid_f8_1x6_1x3 conv_valid 4e-15 conv_f8_1x6_1x3_A.npy conv_f8_1x6_1x3_B.npy conv_valid_f8_1x6_1x3_C.npy +conv_full_f8_5x1_2x1 conv_full 2.3e-15 conv_f8_5x1_2x1_A.npy conv_f8_5x1_2x1_B.npy conv_full_f8_5x1_2x1_C.npy +conv_same_f8_5x1_2x1 conv_same 2.3e-15 conv_f8_5x1_2x1_A.npy conv_f8_5x1_2x1_B.npy conv_same_f8_5x1_2x1_C.npy +conv_valid_f8_5x1_2x1 conv_valid 2.3e-15 conv_f8_5x1_2x1_A.npy conv_f8_5x1_2x1_B.npy conv_valid_f8_5x1_2x1_C.npy +conv_full_i4_6x8_1x1 conv_full 0.0 conv_i4_6x8_1x1_A.npy conv_i4_6x8_1x1_B.npy conv_full_i4_6x8_1x1_C.npy +conv_same_i4_6x8_1x1 conv_same 0.0 conv_i4_6x8_1x1_A.npy conv_i4_6x8_1x1_B.npy conv_same_i4_6x8_1x1_C.npy +conv_valid_i4_6x8_1x1 conv_valid 0.0 conv_i4_6x8_1x1_A.npy conv_i4_6x8_1x1_B.npy conv_valid_i4_6x8_1x1_C.npy +conv_full_i4_6x8_3x5 conv_full 0.0 conv_i4_6x8_3x5_A.npy conv_i4_6x8_3x5_B.npy conv_full_i4_6x8_3x5_C.npy +conv_same_i4_6x8_3x5 conv_same 0.0 conv_i4_6x8_3x5_A.npy conv_i4_6x8_3x5_B.npy conv_same_i4_6x8_3x5_C.npy +conv_valid_i4_6x8_3x5 conv_valid 0.0 conv_i4_6x8_3x5_A.npy conv_i4_6x8_3x5_B.npy conv_valid_i4_6x8_3x5_C.npy +conv_full_i4_6x8_2x4 conv_full 0.0 conv_i4_6x8_2x4_A.npy conv_i4_6x8_2x4_B.npy conv_full_i4_6x8_2x4_C.npy +conv_same_i4_6x8_2x4 conv_same 0.0 conv_i4_6x8_2x4_A.npy conv_i4_6x8_2x4_B.npy conv_same_i4_6x8_2x4_C.npy +conv_valid_i4_6x8_2x4 conv_valid 0.0 conv_i4_6x8_2x4_A.npy conv_i4_6x8_2x4_B.npy conv_valid_i4_6x8_2x4_C.npy +conv_full_i4_6x8_9x10 conv_full 0.0 conv_i4_6x8_9x10_A.npy conv_i4_6x8_9x10_B.npy conv_full_i4_6x8_9x10_C.npy +conv_same_i4_6x8_9x10 conv_same 0.0 conv_i4_6x8_9x10_A.npy conv_i4_6x8_9x10_B.npy conv_same_i4_6x8_9x10_C.npy +conv_valid_i4_6x8_9x10 conv_valid 0.0 conv_i4_6x8_9x10_A.npy conv_i4_6x8_9x10_B.npy conv_valid_i4_6x8_9x10_C.npy +fft2_f8_1x1 fft2 3.7e-15 fourier_f8_1x1_A.npy fft2_f8_1x1_C.npy +ifft2_f8_1x1 ifft2 3.7e-15 fourier_f8_1x1_A.npy ifft2_f8_1x1_C.npy +fft2_f8_1x2 fft2 3.1e-15 fourier_f8_1x2_A.npy fft2_f8_1x2_C.npy +ifft2_f8_1x2 ifft2 1.6e-15 fourier_f8_1x2_A.npy ifft2_f8_1x2_C.npy +fft2_f8_1x7 fft2 5.3e-14 fourier_f8_1x7_A.npy fft2_f8_1x7_C.npy +ifft2_f8_1x7 ifft2 7.6e-15 fourier_f8_1x7_A.npy ifft2_f8_1x7_C.npy +fft2_f8_1x16 fft2 1.8e-13 fourier_f8_1x16_A.npy fft2_f8_1x16_C.npy +ifft2_f8_1x16 ifft2 1.1e-14 fourier_f8_1x16_A.npy ifft2_f8_1x16_C.npy +fft2_f8_3x5 fft2 1.5e-13 fourier_f8_3x5_A.npy fft2_f8_3x5_C.npy +ifft2_f8_3x5 ifft2 1e-14 fourier_f8_3x5_A.npy ifft2_f8_3x5_C.npy +fft2_f8_4x6 fft2 2.9e-13 fourier_f8_4x6_A.npy fft2_f8_4x6_C.npy +ifft2_f8_4x6 ifft2 1.2e-14 fourier_f8_4x6_A.npy ifft2_f8_4x6_C.npy +fft2_f8_7x11 fft2 1.1e-12 fourier_f8_7x11_A.npy fft2_f8_7x11_C.npy +ifft2_f8_7x11 ifft2 1.4e-14 fourier_f8_7x11_A.npy ifft2_f8_7x11_C.npy +fft2_f8_13x1 fft2 1.5e-13 fourier_f8_13x1_A.npy fft2_f8_13x1_C.npy +ifft2_f8_13x1 ifft2 1.2e-14 fourier_f8_13x1_A.npy ifft2_f8_13x1_C.npy +fft2_f8_31x17 fft2 1.1e-11 fourier_f8_31x17_A.npy fft2_f8_31x17_C.npy +ifft2_f8_31x17 ifft2 2e-14 fourier_f8_31x17_A.npy ifft2_f8_31x17_C.npy +fft2_f8_8x8 fft2 9.4e-13 fourier_f8_8x8_A.npy fft2_f8_8x8_C.npy +ifft2_f8_8x8 ifft2 1.5e-14 fourier_f8_8x8_A.npy ifft2_f8_8x8_C.npy +fft2_f8_16x32 fft2 9.5e-12 fourier_f8_16x32_A.npy fft2_f8_16x32_C.npy +ifft2_f8_16x32 ifft2 1.9e-14 fourier_f8_16x32_A.npy ifft2_f8_16x32_C.npy +fft2_f8_64x64 fft2 1e-10 fourier_f8_64x64_A.npy fft2_f8_64x64_C.npy +ifft2_f8_64x64 ifft2 2.4e-14 fourier_f8_64x64_A.npy ifft2_f8_64x64_C.npy +fft2_c16_1x1 fft2 9.7e-16 fourier_c16_1x1_A.npy fft2_c16_1x1_C.npy +ifft2_c16_1x1 ifft2 9.7e-16 fourier_c16_1x1_A.npy ifft2_c16_1x1_C.npy +fft2_c16_1x7 fft2 7e-14 fourier_c16_1x7_A.npy fft2_c16_1x7_C.npy +ifft2_c16_1x7 ifft2 1e-14 fourier_c16_1x7_A.npy ifft2_c16_1x7_C.npy +fft2_c16_3x5 fft2 1.9e-13 fourier_c16_3x5_A.npy fft2_c16_3x5_C.npy +ifft2_c16_3x5 ifft2 1.3e-14 fourier_c16_3x5_A.npy ifft2_c16_3x5_C.npy +fft2_c16_4x6 fft2 3.8e-13 fourier_c16_4x6_A.npy fft2_c16_4x6_C.npy +ifft2_c16_4x6 ifft2 1.6e-14 fourier_c16_4x6_A.npy ifft2_c16_4x6_C.npy +fft2_c16_7x11 fft2 1.9e-12 fourier_c16_7x11_A.npy fft2_c16_7x11_C.npy +ifft2_c16_7x11 ifft2 2.5e-14 fourier_c16_7x11_A.npy ifft2_c16_7x11_C.npy +fft2_c16_8x8 fft2 1.2e-12 fourier_c16_8x8_A.npy fft2_c16_8x8_C.npy +ifft2_c16_8x8 ifft2 1.9e-14 fourier_c16_8x8_A.npy ifft2_c16_8x8_C.npy +fft2_c16_31x17 fft2 1.6e-11 fourier_c16_31x17_A.npy fft2_c16_31x17_C.npy +ifft2_c16_31x17 ifft2 3.1e-14 fourier_c16_31x17_A.npy ifft2_c16_31x17_C.npy +fft2_f4_1x2 fft2 3.5e-06 fourier_f4_1x2_A.npy fft2_f4_1x2_C.npy +ifft2_f4_1x2 ifft2 1.8e-06 fourier_f4_1x2_A.npy ifft2_f4_1x2_C.npy +fft2_f4_1x16 fft2 7.6e-05 fourier_f4_1x16_A.npy fft2_f4_1x16_C.npy +ifft2_f4_1x16 ifft2 4.8e-06 fourier_f4_1x16_A.npy ifft2_f4_1x16_C.npy +fft2_f4_3x5 fft2 7.7e-05 fourier_f4_3x5_A.npy fft2_f4_3x5_C.npy +ifft2_f4_3x5 ifft2 5.1e-06 fourier_f4_3x5_A.npy ifft2_f4_3x5_C.npy +fft2_f4_7x11 fft2 0.00065 fourier_f4_7x11_A.npy fft2_f4_7x11_C.npy +ifft2_f4_7x11 ifft2 8.4e-06 fourier_f4_7x11_A.npy ifft2_f4_7x11_C.npy +fft2_f4_13x1 fft2 5e-05 fourier_f4_13x1_A.npy fft2_f4_13x1_C.npy +ifft2_f4_13x1 ifft2 3.9e-06 fourier_f4_13x1_A.npy ifft2_f4_13x1_C.npy +fft2_f4_16x32 fft2 0.0053 fourier_f4_16x32_A.npy fft2_f4_16x32_C.npy +ifft2_f4_16x32 ifft2 1e-05 fourier_f4_16x32_A.npy ifft2_f4_16x32_C.npy +fft2_c8_3x5 fft2 0.00011 fourier_c8_3x5_A.npy fft2_c8_3x5_C.npy +ifft2_c8_3x5 ifft2 7.2e-06 fourier_c8_3x5_A.npy ifft2_c8_3x5_C.npy +fft2_c8_8x8 fft2 0.00065 fourier_c8_8x8_A.npy fft2_c8_8x8_C.npy +ifft2_c8_8x8 ifft2 1e-05 fourier_c8_8x8_A.npy ifft2_c8_8x8_C.npy +fft2_i4_1x7 fft2 5.4e-13 fourier_i4_1x7_A.npy fft2_i4_1x7_C.npy +ifft2_i4_1x7 ifft2 7.7e-14 fourier_i4_1x7_A.npy ifft2_i4_1x7_C.npy +fft2_i4_4x6 fft2 2.4e-12 fourier_i4_4x6_A.npy fft2_i4_4x6_C.npy +ifft2_i4_4x6 ifft2 1e-13 fourier_i4_4x6_A.npy ifft2_i4_4x6_C.npy +fft2_i4_7x11 fft2 1e-11 fourier_i4_7x11_A.npy fft2_i4_7x11_C.npy +ifft2_i4_7x11 ifft2 1.4e-13 fourier_i4_7x11_A.npy ifft2_i4_7x11_C.npy +fft2_i4_8x8 fft2 7.7e-12 fourier_i4_8x8_A.npy fft2_i4_8x8_C.npy +ifft2_i4_8x8 ifft2 1.2e-13 fourier_i4_8x8_A.npy ifft2_i4_8x8_C.npy +fftshift_f8_1x1 fftshift 0.0 shift_f8_1x1_A.npy fftshift_f8_1x1_C.npy +ifftshift_f8_1x1 ifftshift 0.0 shift_f8_1x1_A.npy ifftshift_f8_1x1_C.npy +fftshift_f8_1x4 fftshift 0.0 shift_f8_1x4_A.npy fftshift_f8_1x4_C.npy +ifftshift_f8_1x4 ifftshift 0.0 shift_f8_1x4_A.npy ifftshift_f8_1x4_C.npy +fftshift_f8_3x5 fftshift 0.0 shift_f8_3x5_A.npy fftshift_f8_3x5_C.npy +ifftshift_f8_3x5 ifftshift 0.0 shift_f8_3x5_A.npy ifftshift_f8_3x5_C.npy +fftshift_f8_4x6 fftshift 0.0 shift_f8_4x6_A.npy fftshift_f8_4x6_C.npy +ifftshift_f8_4x6 ifftshift 0.0 shift_f8_4x6_A.npy ifftshift_f8_4x6_C.npy +fftshift_f8_5x5 fftshift 0.0 shift_f8_5x5_A.npy fftshift_f8_5x5_C.npy +ifftshift_f8_5x5 ifftshift 0.0 shift_f8_5x5_A.npy ifftshift_f8_5x5_C.npy +fftshift_f8_6x1 fftshift 0.0 shift_f8_6x1_A.npy fftshift_f8_6x1_C.npy +ifftshift_f8_6x1 ifftshift 0.0 shift_f8_6x1_A.npy ifftshift_f8_6x1_C.npy +fftshift_i4_1x1 fftshift 0.0 shift_i4_1x1_A.npy fftshift_i4_1x1_C.npy +ifftshift_i4_1x1 ifftshift 0.0 shift_i4_1x1_A.npy ifftshift_i4_1x1_C.npy +fftshift_i4_1x4 fftshift 0.0 shift_i4_1x4_A.npy fftshift_i4_1x4_C.npy +ifftshift_i4_1x4 ifftshift 0.0 shift_i4_1x4_A.npy ifftshift_i4_1x4_C.npy +fftshift_i4_3x5 fftshift 0.0 shift_i4_3x5_A.npy fftshift_i4_3x5_C.npy +ifftshift_i4_3x5 ifftshift 0.0 shift_i4_3x5_A.npy ifftshift_i4_3x5_C.npy +fftshift_i4_4x6 fftshift 0.0 shift_i4_4x6_A.npy fftshift_i4_4x6_C.npy +ifftshift_i4_4x6 ifftshift 0.0 shift_i4_4x6_A.npy ifftshift_i4_4x6_C.npy +fftshift_i4_5x5 fftshift 0.0 shift_i4_5x5_A.npy fftshift_i4_5x5_C.npy +ifftshift_i4_5x5 ifftshift 0.0 shift_i4_5x5_A.npy ifftshift_i4_5x5_C.npy +fftshift_i4_6x1 fftshift 0.0 shift_i4_6x1_A.npy fftshift_i4_6x1_C.npy +ifftshift_i4_6x1 ifftshift 0.0 shift_i4_6x1_A.npy ifftshift_i4_6x1_C.npy +fftshift_c16_1x1 fftshift 0.0 shift_c16_1x1_A.npy fftshift_c16_1x1_C.npy +ifftshift_c16_1x1 ifftshift 0.0 shift_c16_1x1_A.npy ifftshift_c16_1x1_C.npy +fftshift_c16_1x4 fftshift 0.0 shift_c16_1x4_A.npy fftshift_c16_1x4_C.npy +ifftshift_c16_1x4 ifftshift 0.0 shift_c16_1x4_A.npy ifftshift_c16_1x4_C.npy +fftshift_c16_3x5 fftshift 0.0 shift_c16_3x5_A.npy fftshift_c16_3x5_C.npy +ifftshift_c16_3x5 ifftshift 0.0 shift_c16_3x5_A.npy ifftshift_c16_3x5_C.npy +fftshift_c16_4x6 fftshift 0.0 shift_c16_4x6_A.npy fftshift_c16_4x6_C.npy +ifftshift_c16_4x6 ifftshift 0.0 shift_c16_4x6_A.npy ifftshift_c16_4x6_C.npy +fftshift_c16_5x5 fftshift 0.0 shift_c16_5x5_A.npy fftshift_c16_5x5_C.npy +ifftshift_c16_5x5 ifftshift 0.0 shift_c16_5x5_A.npy ifftshift_c16_5x5_C.npy +fftshift_c16_6x1 fftshift 0.0 shift_c16_6x1_A.npy fftshift_c16_6x1_C.npy +ifftshift_c16_6x1 ifftshift 0.0 shift_c16_6x1_A.npy ifftshift_c16_6x1_C.npy diff --git a/tests/fixtures/s8/shift_c16_1x1_A.npy b/tests/fixtures/s8/shift_c16_1x1_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..f72c79ce9a1115ea1b1df75951031c391c397956 GIT binary patch literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 p7iT0EqyqUGhB^v{nmP)#3giN=GgAyEl?Pq1XS;k(&1vIHdjL=CBVPaj literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/shift_c16_1x4_A.npy b/tests/fixtures/s8/shift_c16_1x4_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..e8310a63892d4910f4a98dfaab9961a4c8e84976 GIT binary patch literal 192 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhB^u+nmP)#3giMViQMA0hqX8MH>fu*5wYvu@6del*NyxY`)|Z-J-vF$ p?fr9}W?KE(ac6&A?y^ZIv!2>tn08sQdC9%~5ycZl(i2YD0|4SDKuG`q literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/shift_c16_3x5_A.npy b/tests/fixtures/s8/shift_c16_3x5_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..c9a7ff69cd5bbd6cb162eee157a8c1f2c7854cce GIT binary patch literal 368 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG#ySe7nmP)#3giN=6Rp$Y@A}`_-w?>-pmuq!J&Wm^ZO4nB+Vi~rcaYcj zrG4M@t}mP+@9Y&8<_aFq-M*h;@xNwIsr&Xiey1O>a^2s*hb>9N(Co#2AFDW;K)5Y5R(jSJ5BD4)2$lz9)rI>aG2On9pa0md@S( zV4k0xx!%kD8}c+h@OzxyAFyxtwd3>m?BAEZGVtS*cl%2$;+XbY+};1=yUWao#Mk=+ zHk|#qmh;tqu~%DIOX^|MGhcEo2~ z*?%DEf_Y)|K6?gNvlT_=>+CQMm50vklv%cQnaaDam-}Zy{8i68htM)(L z&#-mn9qEZr?U!t@_#|`s!hR9kuM;%yzTI!JvVHOgse}8OZg>1?Q+{ke<6ni~>KUi( zPjJ>gJT{f}6uaPuke_Wz0G&(gd6&OYOiSFL^ROZ!)eV$#bVKCr)Ybm?_&&+GPI z>?io$PrSar>4M#*|16L8FUX5n*KT&oep^n-k#lkH>>J)}&sY_@-G0rH_5A+dU)cXx zooBa^^?<$9%2)p+jb7V3?0F}Ds)nDc1=g_$>s z1UFyYU!Zb{yFK)-y-tw|SB&f%`*#bruX4(LyuWAVZ6jZXNBjR(iTud8|ImKL#w*u# RlV0v$aAxc37dKbi0|3kn>Y4xm literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/shift_c16_5x5_A.npy b/tests/fixtures/s8/shift_c16_5x5_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..960d67c2c16a63bb834a9432eac58c7f18501ac0 GIT binary patch literal 528 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGraB6ynmP)#3giN=0Pmma%90!Q=j~FT<2d8-euu{NZ!`9s+wW9TYO(6k z%l$XJ<2RLhU$%FcGf}=xVw(MxB`;Se=U=pEsaefYdGwNf7iZ<9`n1RUGdh>e+RO0F zo@-v>W#*+X_v}w{*qJauPqN;-M`|_WM0AkNBhro`z%%tcwn!w zxNb|R_gQ?Iya2(rc8wg1-T{`R@rQ+uCD z)f%gG>ww{)4 i(Ocf_?~t2d@W%J8J&#KFv$u7R_a8_t7P{1Waz6kKSnxpr literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/shift_c16_6x1_A.npy b/tests/fixtures/s8/shift_c16_6x1_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..b30978f6a80ce696ea35b7852735eb44cb942eb4 GIT binary patch literal 224 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGW;zOnnmP)#3giN=b8icESABZ3e?_8Hs{QAa`yXU|lJ|95W3SQ`?-=#^ zuKkyUxZ2$tp4%_t&%JeQ+w1*LehDm*Z+vYZaQ4KV66Hzv+vo}X(j*pl05*>VNhuR literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/shift_f8_1x1_A.npy b/tests/fixtures/s8/shift_f8_1x1_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..24e92d0c12064572b6f1dec3355ab3455c62e0e9 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= gXCxM+0{I$-ItqrGItsN4WCN}ahr==anJ4!H07CB{Q~&?~ literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/shift_f8_1x4_A.npy b/tests/fixtures/s8/shift_f8_1x4_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..e08219afeb2bb3f5f15f71e55e0bef8f4c535cbb GIT binary patch literal 160 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$-ItnJ5ItsN4WCN}*PM=?`oBzOm!Jc=lJ7R9x8yN92Er0ypUP070*!bZd FdjRj6D`)@! literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/shift_f8_3x5_A.npy b/tests/fixtures/s8/shift_f8_3x5_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..1b10a0d11707e827032a50f828758dfb568eedc7 GIT binary patch literal 248 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%oItr$mItsN4WCN~aYgO&{x;@&zp;y6O%lqj5ie|Y4_l=M3mwbz15&dvy zKL^XF$laz-?6(Otn0Cp$+&@Dz>d;&F1N$vHYpAhpraxZMw4mSopa!|CYVo zFC%vTU(?CE``dOs_1He;{r)A%Ykyz(cX@xA*|e``+TQG+P&-Xv2H(d09~>Q$IG&x_ z|HrY>)2;I1eiN}WO`G^@_Dx=rH^de^*uNr=Cn;mbHG7pUYsAE!_T08Fkcbat@4jI_A?Z=?YW_X@3%=^JZ`a;w zpELJP**odi`wd=g7G(T!*1kek`q%F*uk9W3HQ&B)Txjo-xhEm)=F9yV2Y$V3_kC#p zVMQ~m?*9w+9tLTVtcP#z-;ntJQvA%j`@ihmXv7=!e*e411Xs3|&-bq|`StkGf@k|5 z$nIz3op55mk1o56tmzf|8C#x9K5Kope?~)g?Yp|`_6uf&x_|w2dH;iy7}M7Hll#qX cPqyK{^?LsWgRg5VQtsNnx%u^i!~c8x0Tk_v4*&oF literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/shift_f8_6x1_A.npy b/tests/fixtures/s8/shift_f8_6x1_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..1fb5de78bee2dc86d28ea28c16074778821bbcb2 GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$dItqrGItsN4WCO0YtKPD99&h#wG?l0`*4^Bn#h1e@Jzqnf*#iKe`Z8Mp literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/shift_i4_1x1_A.npy b/tests/fixtures/s8/shift_i4_1x1_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..2a7deea00721a7e39fb741ccdd1bb12658945e9d GIT binary patch literal 132 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I$-ItqrGItsN4WCO0h|Ns970M3dZW&i*H literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/shift_i4_1x4_A.npy b/tests/fixtures/s8/shift_i4_1x4_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..bf7f96dc634037a00d98ad3ae91765173019111a GIT binary patch literal 144 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= lXCxM+0{I$-ItnJ5ItsN4WCO16|NsAIWME)m2jag#HULsGBV_;p literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/shift_i4_3x5_A.npy b/tests/fixtures/s8/shift_i4_3x5_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..dbff31f44212eb579be80a66d4f4db411c04ee6a GIT binary patch literal 188 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I%oItr$mItsN4WCN~0|NsAIW?*3W4WvQfClIp&`5^oc$Yul5Oi=t6$Oowd JsbL3-0RYp)J5~Sy literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/shift_i4_4x6_A.npy b/tests/fixtures/s8/shift_i4_4x6_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..db3f0028747ea22a27f6b07dd5147f0b5df577e1 GIT binary patch literal 224 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I#yItpf*ItsN4WCO1MK)}Jkz`zW|EI`Z%#H>hcHX!>aP|a^3{sUDDV*i8k TK{QA|2*C9I1&aLwVvu4iGK&uoIsihh*^M` g1BibB#XuM&&j!T*fNYTXZy*iQ528WpLGmDR09>R$b^rhX literal 0 HcmV?d00001 diff --git a/tests/fixtures/s8/shift_i4_6x1_A.npy b/tests/fixtures/s8/shift_i4_6x1_A.npy new file mode 100644 index 0000000000000000000000000000000000000000..ca4dfc56b6e3a3102e609de459c31e2839ebe230 GIT binary patch literal 152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWC%^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= oXCxM+0{I$dItqrGItsN4WCO16|NsBzWME(bQvZJd@gFE30KwENVE_OC literal 0 HcmV?d00001 diff --git a/tests/fuzz/corpus/binary/double_2x3.bin b/tests/fuzz/corpus/binary/double_2x3.bin new file mode 100644 index 0000000000000000000000000000000000000000..fecdae53d9012319c09f41eebdc6b72ba631adfd GIT binary patch literal 64 fcmZQ#fB3uYE>ix F*#KpP2*m&Z literal 0 HcmV?d00001 diff --git a/tests/fuzz/corpus/bmp/rgb24_trailing_1x1.bmp b/tests/fuzz/corpus/bmp/rgb24_trailing_1x1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ad5be6261a279b3c54029aaf2fbeabb3dce6e39b GIT binary patch literal 61 lcmZ?rwPk<+Ga#h_#Eft(0hV9^lET~&3>E+r>cnmP)#3giMV1~B+w51|!24&gF<*biaII6ws$0O~3r A&;S4c literal 0 HcmV?d00001 diff --git a/tests/fuzz/corpus/npy/f8_be.npy b/tests/fuzz/corpus/npy/f8_be.npy new file mode 100644 index 0000000000000000000000000000000000000000..8e2a382216b56869c50a33bcbe5ed876658e1e42 GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1Zmu8`^qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= uXCxM+0{I$7Its>`ItsN4WCJey9}Hk{fC)l7a6oB+1~9`x1j0Wc1)%}`h99v2 literal 0 HcmV?d00001 diff --git a/tests/fuzz/corpus/npy/f8_le.npy b/tests/fuzz/corpus/npy/f8_le.npy new file mode 100644 index 0000000000000000000000000000000000000000..568e6d4d918782ccaacee305d6e0015243f182b6 GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= uXCxM+0{I$7Its>`ItsN4WCJb+F!*5)p_vXqXbuN3-5>y=8APD8)BynF7ay+x literal 0 HcmV?d00001 diff --git a/tests/fuzz/corpus/npy/f8_le_truncated.npy b/tests/fuzz/corpus/npy/f8_le_truncated.npy new file mode 100644 index 0000000000000000000000000000000000000000..6696718a8c0497e5cf39ee986ac47ad0783e979f GIT binary patch literal 171 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= sXCxM+0{I$7Its>`ItsN4WCJb+F!*5)p_vXqXbuN3-5>y=8AL!d07v>B)c^nh literal 0 HcmV?d00001 diff --git a/tests/fuzz/corpus/npy/fortran_2x3.npy b/tests/fuzz/corpus/npy/fortran_2x3.npy new file mode 100644 index 0000000000000000000000000000000000000000..099bf2ff22ea426440bce3c34c7e4014e2328a05 GIT binary patch literal 176 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(4=E~51qv5u sBo?Fsxf(_~3dWi`3bhL411<(I_+byG8U!35e5L~sS_I1HfYMS20M%w6ga7~l literal 0 HcmV?d00001 diff --git a/tests/fuzz/corpus/npy/i4_be.npy b/tests/fuzz/corpus/npy/i4_be.npy new file mode 100644 index 0000000000000000000000000000000000000000..9db497eb0f29ee877c918a4bee85641a79b5c986 GIT binary patch literal 152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZmuaG|qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= uXCxM+0{I$7Its>`ItsN4WCJb+1_s9e|Ns93(##Buf(8Ho9dD=y3IG6C+9t99 literal 0 HcmV?d00001 diff --git a/tests/fuzz/corpus/npy/one_d.npy b/tests/fuzz/corpus/npy/one_d.npy new file mode 100644 index 0000000000000000000000000000000000000000..aa15719ae6a277f5e232bba74e53454d1e5399e9 GIT binary patch literal 152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= gXCxM+0{I%oI+{8PwF(pfE(S1Aae&Y!P}`ItsN4WCJb+#{UiMS^y$M9ccgn literal 0 HcmV?d00001 diff --git a/tests/fuzz/corpus/text/complex_2x2.txt b/tests/fuzz/corpus/text/complex_2x2.txt new file mode 100644 index 0000000..c5692ba --- /dev/null +++ b/tests/fuzz/corpus/text/complex_2x2.txt @@ -0,0 +1,2 @@ +(1,2) (3,-4) +(0.5,0) 7 diff --git a/tests/fuzz/corpus/text/float_exp.txt b/tests/fuzz/corpus/text/float_exp.txt new file mode 100644 index 0000000..3708f89 --- /dev/null +++ b/tests/fuzz/corpus/text/float_exp.txt @@ -0,0 +1,2 @@ +1.5e3 -2.25e-2 ++3 0x10 diff --git a/tests/fuzz/corpus/text/int_tabs_crlf.txt b/tests/fuzz/corpus/text/int_tabs_crlf.txt new file mode 100644 index 0000000..78ed961 --- /dev/null +++ b/tests/fuzz/corpus/text/int_tabs_crlf.txt @@ -0,0 +1,2 @@ +1 -2 +3 4 diff --git a/tests/fuzz/corpus/text/ragged.txt b/tests/fuzz/corpus/text/ragged.txt new file mode 100644 index 0000000..0177db2 --- /dev/null +++ b/tests/fuzz/corpus/text/ragged.txt @@ -0,0 +1,2 @@ +1 2 +3 diff --git a/tests/fuzz/corpus/text/real_2x3.txt b/tests/fuzz/corpus/text/real_2x3.txt new file mode 100644 index 0000000..b36d133 --- /dev/null +++ b/tests/fuzz/corpus/text/real_2x3.txt @@ -0,0 +1,2 @@ +1 2 3 +4 5 6 diff --git a/tests/fuzz/corpus/text/unclosed.txt b/tests/fuzz/corpus/text/unclosed.txt new file mode 100644 index 0000000..efd6400 --- /dev/null +++ b/tests/fuzz/corpus/text/unclosed.txt @@ -0,0 +1 @@ +(1,2 3 diff --git a/tests/fuzz/fuzz_binary.cc b/tests/fuzz/fuzz_binary.cc new file mode 100644 index 0000000..8ce98d4 --- /dev/null +++ b/tests/fuzz/fuzz_binary.cc @@ -0,0 +1,10 @@ +// S5-R5 (PR-7): libFuzzer harness for matrix_details::parse_binary; traps when a failed parse changed a pre-filled +// destination or a successful one is inconsistent (checks in fuzz_checks.hpp). No file I/O. +#include "../../matrix.hpp" +#include "fuzz_checks.hpp" + +extern "C" int LLVMFuzzerTestOneInput( std::uint8_t const* data, std::size_t size ) +{ + if ( fuzz_checks::check_binary( data, size ) != nullptr ) __builtin_trap(); + return 0; +} diff --git a/tests/fuzz/fuzz_bmp.cc b/tests/fuzz/fuzz_bmp.cc new file mode 100644 index 0000000..694a30d --- /dev/null +++ b/tests/fuzz/fuzz_bmp.cc @@ -0,0 +1,10 @@ +// S5-R5 (PR-7): libFuzzer harness for matrix_details::parse_bmp; traps when a failed parse changed a pre-filled +// destination or a successful one is inconsistent (checks in fuzz_checks.hpp). No file I/O. +#include "../../matrix.hpp" +#include "fuzz_checks.hpp" + +extern "C" int LLVMFuzzerTestOneInput( std::uint8_t const* data, std::size_t size ) +{ + if ( fuzz_checks::check_bmp( data, size ) != nullptr ) __builtin_trap(); + return 0; +} diff --git a/tests/fuzz/fuzz_checks.hpp b/tests/fuzz/fuzz_checks.hpp new file mode 100644 index 0000000..00f9728 --- /dev/null +++ b/tests/fuzz/fuzz_checks.hpp @@ -0,0 +1,136 @@ +// S5-R5 (PR-7): the checks shared by the libFuzzer harnesses (which trap on a failure) and the +// `[S5][S5-R5]` replay test (which REQUIREs them). Each check parses a byte buffer with the matrix_details +// parsers into pre-filled destinations of several element types and returns nullptr when every parse was +// transactional and consistent, otherwise a static message. No file I/O. +#ifndef FENG_TESTS_FUZZ_CHECKS_HPP_INCLUDED +#define FENG_TESTS_FUZZ_CHECKS_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace fuzz_checks +{ + enum class format { npy, text, binary }; + + template < typename T > + struct is_complex : std::false_type {}; + template < typename F > + struct is_complex< std::complex< F > > : std::true_type {}; + + template < typename T > + T fill_value( std::size_t i ) noexcept + { + if constexpr ( is_complex< T >::value ) + { + using F = typename T::value_type; + return T{ static_cast< F >( i ) + F( 0.5 ), -static_cast< F >( i ) }; + } + else + return static_cast< T >( i * 7 + 3 ); + } + + template < typename T > + feng::matrix< T > prefilled( std::size_t r, std::size_t c ) + { + feng::matrix< T > m( r, c ); + for ( std::size_t i = 0; i != m.size(); ++i ) m.data()[i] = fill_value< T >( i ); + return m; + } + + template < typename T > + bool consistent( feng::matrix< T > const& m ) noexcept + { + if ( m.size() != m.row() * m.col() ) return false; + if ( m.size() != 0 && m.data() == nullptr ) return false; + return true; + } + + // Compares element values, not bytes: long double (and std::complex) carry padding bytes whose + // contents are unspecified after an element assignment, so a byte comparison would be unreliable. The + // pre-filled values are finite, so operator== is exact. + template < typename T > + bool unchanged( feng::matrix< T > const& m, std::size_t r, std::size_t c, std::vector< T > const& before ) noexcept + { + if ( m.row() != r || m.col() != c || m.size() != r * c || m.size() != before.size() ) return false; + if ( before.empty() ) return true; + if ( m.data() == nullptr ) return false; + for ( std::size_t i = 0; i != before.size(); ++i ) + if ( !( m.data()[i] == before[i] ) ) return false; + return true; + } + + template < format F, typename T > + char const* check_shape( std::uint8_t const* data, std::size_t size, std::size_t r, std::size_t c ) + { + feng::matrix< T > m = prefilled< T >( r, c ); + std::vector< T > const before( m.data(), m.data() + m.size() ); + bool ok = false; + if constexpr ( F == format::npy ) + ok = feng::matrix_details::parse_npy< T >( data, size, m ); + else if constexpr ( F == format::text ) + ok = feng::matrix_details::parse_text< T >( reinterpret_cast< char const* >( data ), size, m ); + else + ok = feng::matrix_details::parse_binary< T >( data, size, m ); + if ( ok ) + return consistent( m ) ? nullptr : "a successful parse left an inconsistent matrix (size != rows*cols or null data)"; + return unchanged( m, r, c, before ) ? nullptr : "a failed parse changed the destination"; + } + + template < format F, typename T > + char const* check_type( std::uint8_t const* data, std::size_t size ) + { + if ( char const* e = check_shape< F, T >( data, size, 2, 3 ) ) return e; + return check_shape< F, T >( data, size, 0, 0 ); + } + + template < format F > + char const* check_all( std::uint8_t const* data, std::size_t size ) + { + char const* results[] = { + check_type< F, double >( data, size ), + check_type< F, float >( data, size ), + check_type< F, std::uint8_t >( data, size ), + check_type< F, std::int8_t >( data, size ), + check_type< F, std::int16_t >( data, size ), + check_type< F, std::uint16_t >( data, size ), + check_type< F, std::int32_t >( data, size ), + check_type< F, std::uint32_t >( data, size ), + check_type< F, std::int64_t >( data, size ), + check_type< F, std::uint64_t >( data, size ), + check_type< F, std::complex< float > >( data, size ), + check_type< F, std::complex< double > >( data, size ), + }; + for ( char const* e : results ) + if ( e ) return e; + // The native binary format also supports long double and std::complex (is_binary_element_v); + // the npy and text parsers do not take them. + if constexpr ( F == format::binary ) + { + if ( char const* e = check_type< F, long double >( data, size ) ) return e; + if ( char const* e = check_type< F, std::complex< long double > >( data, size ) ) return e; + } + return nullptr; + } + + inline char const* check_npy( std::uint8_t const* data, std::size_t size ) { return check_all< format::npy >( data, size ); } + inline char const* check_text( std::uint8_t const* data, std::size_t size ) { return check_all< format::text >( data, size ); } + inline char const* check_binary( std::uint8_t const* data, std::size_t size ) { return check_all< format::binary >( data, size ); } + + // BMP has one result type: three equally shaped, non-empty, consistent channels on success. + inline char const* check_bmp( std::uint8_t const* data, std::size_t size ) + { + auto const ans = feng::matrix_details::parse_bmp( data, size ); + if ( !ans ) return nullptr; + auto const& [r, g, b] = *ans; + if ( !consistent( r ) || !consistent( g ) || !consistent( b ) ) return "a successful BMP parse left an inconsistent channel"; + if ( r.row() != g.row() || r.row() != b.row() || r.col() != g.col() || r.col() != b.col() ) + return "a successful BMP parse returned channels of different shapes"; + if ( r.size() == 0 ) return "a successful BMP parse returned empty channels"; + return nullptr; + } +} + +#endif diff --git a/tests/fuzz/fuzz_npy.cc b/tests/fuzz/fuzz_npy.cc new file mode 100644 index 0000000..b811b45 --- /dev/null +++ b/tests/fuzz/fuzz_npy.cc @@ -0,0 +1,10 @@ +// S5-R5 (PR-7): libFuzzer harness for matrix_details::parse_npy; traps when a failed parse changed a pre-filled +// destination or a successful one is inconsistent (checks in fuzz_checks.hpp). No file I/O. +#include "../../matrix.hpp" +#include "fuzz_checks.hpp" + +extern "C" int LLVMFuzzerTestOneInput( std::uint8_t const* data, std::size_t size ) +{ + if ( fuzz_checks::check_npy( data, size ) != nullptr ) __builtin_trap(); + return 0; +} diff --git a/tests/fuzz/fuzz_text.cc b/tests/fuzz/fuzz_text.cc new file mode 100644 index 0000000..1823eef --- /dev/null +++ b/tests/fuzz/fuzz_text.cc @@ -0,0 +1,10 @@ +// S5-R5 (PR-7): libFuzzer harness for matrix_details::parse_text; traps when a failed parse changed a pre-filled +// destination or a successful one is inconsistent (checks in fuzz_checks.hpp). No file I/O. +#include "../../matrix.hpp" +#include "fuzz_checks.hpp" + +extern "C" int LLVMFuzzerTestOneInput( std::uint8_t const* data, std::size_t size ) +{ + if ( fuzz_checks::check_text( data, size ) != nullptr ) __builtin_trap(); + return 0; +} diff --git a/tests/fuzz/make_corpus.py b/tests/fuzz/make_corpus.py new file mode 100644 index 0000000..5de93e9 --- /dev/null +++ b/tests/fuzz/make_corpus.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# S5-R5: writes the seed corpora under tests/fuzz/corpus// (run from the repo root). NPY seeds are copies of +# tests/fixtures/s5/*.npy plus a truncated one; the others are built here. Valid and near-valid inputs, all small. +import os +import shutil +import struct + +ROOT = os.path.join("tests", "fuzz", "corpus") + + +def put(target, name, data): + d = os.path.join(ROOT, target) + os.makedirs(d, exist_ok=True) + with open(os.path.join(d, name), "wb") as f: + f.write(data) + + +# NPY +for fx in ["f8_le", "f8_be", "c16_le", "i4_be", "fortran_2x3", "one_d", "u1", "structured"]: + os.makedirs(os.path.join(ROOT, "npy"), exist_ok=True) + shutil.copy(os.path.join("tests", "fixtures", "s5", fx + ".npy"), os.path.join(ROOT, "npy", fx + ".npy")) +with open(os.path.join("tests", "fixtures", "s5", "f8_le.npy"), "rb") as f: + put("npy", "f8_le_truncated.npy", f.read()[:-5]) + + +# BMP: 14-byte file header + 40-byte info header, BI_RGB. +def bmp(width, height, bpp, trailing=b""): + rows = abs(height) + stride = (width * bpp + 31) // 32 * 4 + pixels = bytearray() + for r in range(rows): + row = bytearray() + for c in range(width): + px = bytes([(r * 40 + c) & 255, (c * 60) & 255, (r * 90 + 7) & 255]) + row += px + (b"\xff" if bpp == 32 else b"") + row += b"\0" * (stride - len(row)) + pixels += row + offset = 54 + header = b"BM" + struct.pack("): det (PartialPivLU), solve, inverse, singular values (JacobiSVD), pinv (JacobiSVD with +// feng's cutoff max(m, n)·ε·s_1) and Cholesky (LLT). Built and run by `tools/check.sh oracle S7` +// (-std=c++20 -O2 -isystem /usr/include/eigen3). Prints the Eigen version, one line per failing comparison and +// `EIGEN PASS comparisons`; exit status 0 only when every comparison passes. +#include "../../matrix.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + template< typename T > struct real_of { using type = T; }; + template< typename T > struct real_of< std::complex< T > > { using type = T; }; + template< typename T > using real_t = typename real_of< T >::type; + template< typename T > using emat = Eigen::Matrix< T, Eigen::Dynamic, Eigen::Dynamic >; + + double const eps = std::numeric_limits< double >::epsilon(); + std::size_t passed = 0, failed = 0; + + void check( bool ok, std::string const& what, double err, double tol ) + { + if ( std::getenv( "S7_RATIOS" ) ) std::cout << what << " ratio " << err / tol << "\n"; + if ( ok ) { ++passed; return; } + ++failed; + std::cout << "EIGEN FAIL " << what << ": error " << err << " > tol " << tol << "\n"; + } + + template< typename T > + T conj_( T const& x ) + { + if constexpr ( std::is_same_v< T, double > ) return x; + else return std::conj( x ); + } + + template< typename T > + T draw( std::mt19937_64& g ) + { + std::uniform_real_distribution< double > u( -1.0, 1.0 ); + if constexpr ( std::is_same_v< T, double > ) return u( g ); + else { double const re = u( g ); return T( re, u( g ) ); } + } + + template< typename T > + feng::matrix< T > random( std::size_t m, std::size_t n, std::mt19937_64& g ) + { + feng::matrix< T > a{ m, n }; + for ( std::size_t r = 0; r != m; ++r ) + for ( std::size_t c = 0; c != n; ++c ) a[r][c] = draw< T >( g ); + return a; + } + + template< typename T > + emat< T > to_eigen( feng::matrix< T > const& a ) + { + emat< T > e( a.row(), a.col() ); + for ( std::size_t r = 0; r != a.row(); ++r ) + for ( std::size_t c = 0; c != a.col(); ++c ) e( r, c ) = a[r][c]; + return e; + } + + // ||f - e||_F / ||e||_F; +inf on a shape mismatch + template< typename T > + double rel( feng::matrix< T > const& f, emat< T > const& e ) + { + if ( f.row() != std::size_t( e.rows() ) || f.col() != std::size_t( e.cols() ) ) return HUGE_VAL; + double d = 0; + for ( std::size_t r = 0; r != f.row(); ++r ) + for ( std::size_t c = 0; c != f.col(); ++c ) d += std::norm( f[r][c] - e( r, c ) ); + return std::sqrt( d ) / e.norm(); + } + + template< typename T > + emat< T > eigen_pinv( emat< T > const& a ) + { + Eigen::JacobiSVD< emat< T >, Eigen::ComputeThinU | Eigen::ComputeThinV > svd( a ); + auto const& s = svd.singularValues(); + double const cut = double( std::max( a.rows(), a.cols() ) ) * eps * ( s.size() ? s( 0 ) : 0.0 ); + Eigen::Matrix< T, Eigen::Dynamic, 1 > inv_s( s.size() ); + for ( Eigen::Index i = 0; i != s.size(); ++i ) inv_s( i ) = s( i ) > cut ? T( 1.0 / s( i ) ) : T( 0 ); + return svd.matrixV() * inv_s.asDiagonal() * svd.matrixU().adjoint(); + } + + // κ over the singular values above the pinv cutoff + template< typename T > + double kappa( emat< T > const& a ) + { + Eigen::JacobiSVD< emat< T > > svd( a ); + auto const& s = svd.singularValues(); + double const cut = double( std::max( a.rows(), a.cols() ) ) * eps * s( 0 ); + double lo = s( 0 ); + for ( Eigen::Index i = 0; i != s.size(); ++i ) if ( s( i ) > cut ) lo = s( i ); + return s( 0 ) / lo; + } + + template< typename T > + void svd_and_pinv( feng::matrix< T > const& a, std::string const& tag ) + { + std::size_t const p = std::max( a.row(), a.col() ); + emat< T > const e = to_eigen( a ); + Eigen::JacobiSVD< emat< T > > esvd( e ); + auto const& es = esvd.singularValues(); + auto const f = feng::svd_factor( a ); + double err = HUGE_VAL; + if ( f.ok() && f.s().size() == std::size_t( es.size() ) ) + { + err = 0; + for ( std::size_t i = 0; i != f.s().size(); ++i ) err = std::max( err, std::abs( f.s()[i] - es( i ) ) ); + } + double tol = 32.0 * p * eps * es( 0 ); + check( err <= tol, "svdvals " + tag, err, tol ); + + double const k = kappa( e ); + feng::matrix< T > x; + err = feng::pinverse( a, x ) == feng::linalg_status::ok ? rel( x, eigen_pinv( e ) ) : HUGE_VAL; + tol = 64.0 * p * eps * k * k; + check( err <= tol, "pinv " + tag, err, tol ); + } + + template< typename T > + void square( std::size_t n, std::mt19937_64& g, std::string const& tname ) + { + std::string const tag = tname + " " + std::to_string( n ) + "x" + std::to_string( n ); + auto const a = random< T >( n, n, g ); + emat< T > const e = to_eigen( a ); + double const k = kappa( e ); + double const tol = 64.0 * n * eps * k; + + Eigen::PartialPivLU< emat< T > > lu( e ); + T const de = lu.determinant(); + T const df = feng::det( a ); + double err = std::abs( df - de ) / std::abs( de ); + check( err <= tol, "det " + tag, err, tol ); + + auto const b = random< T >( n, 3, g ); + auto const xs = feng::solve( a, b ); + err = xs.ok() ? rel( xs.value, emat< T >( lu.solve( to_eigen( b ) ) ) ) : HUGE_VAL; + check( err <= tol, "solve " + tag, err, tol ); + + auto const xi = feng::try_inverse( a ); + err = xi.ok() ? rel( xi.value, emat< T >( lu.inverse() ) ) : HUGE_VAL; + check( err <= tol, "inverse " + tag, err, tol ); + + // Hermitian positive definite: A·Aᴴ + n·I, symmetrized exactly + feng::matrix< T > h{ n, n }; + for ( std::size_t r = 0; r != n; ++r ) + for ( std::size_t c = 0; c != n; ++c ) + { + T s = r == c ? T( double( n ) ) : T( 0 ); + for ( std::size_t j = 0; j != n; ++j ) s += a[r][j] * conj_( a[c][j] ); + h[r][c] = s; + } + for ( std::size_t r = 0; r != n; ++r ) + { + h[r][r] = T( std::real( h[r][r] ) ); + for ( std::size_t c = 0; c != r; ++c ) h[r][c] = conj_( h[c][r] ); + } + emat< T > const eh = to_eigen( h ); + Eigen::LLT< emat< T > > llt( eh ); + auto const ch = feng::cholesky_factor( h ); + double const tolc = 64.0 * n * eps * kappa( eh ); + err = ( ch.ok() && llt.info() == Eigen::Success ) ? rel( ch.l(), emat< T >( llt.matrixL() ) ) : HUGE_VAL; + check( err <= tolc, "cholesky " + tag, err, tolc ); + + svd_and_pinv( a, tag ); + } + + template< typename T > + void run( std::string const& tname, std::uint64_t seed ) + { + std::mt19937_64 g{ seed }; + for ( std::size_t n : { 1, 4, 8 } ) square< T >( n, g, tname ); + svd_and_pinv( random< T >( 6, 3, g ), tname + " 6x3" ); + svd_and_pinv( random< T >( 3, 7, g ), tname + " 3x7" ); + // rank 2, 5x5: a 5x2 times a 2x5 product + auto const b = random< T >( 5, 2, g ); + auto const c = random< T >( 2, 5, g ); + svd_and_pinv( feng::matrix< T >( b * c ), tname + " 5x5 rank 2" ); + } +} + +int main() +{ + std::cout << "Eigen " << EIGEN_WORLD_VERSION << "." << EIGEN_MAJOR_VERSION << "." << EIGEN_MINOR_VERSION; +#ifdef EIGEN_VERSION_STRING + std::cout << " (" << EIGEN_VERSION_STRING << ")"; +#endif + std::cout << "\n"; + run< double >( "double", 20261001 ); + run< std::complex< double > >( "complex", 20261002 ); + if ( failed != 0 ) + { + std::cout << "EIGEN FAIL " << failed << " of " << ( passed + failed ) << " comparisons\n"; + return 1; + } + std::cout << "EIGEN PASS " << passed << " comparisons\n"; + return 0; +} diff --git a/tests/test.cc b/tests/test.cc index 8dca80d..8de2cee 100644 --- a/tests/test.cc +++ b/tests/test.cc @@ -1,7 +1,7 @@ #include "../matrix.hpp" #define CATCH_CONFIG_MAIN -#include "./catch.hpp" +#include #include "./cases/abs.hpp" #include "./cases/acosh.hpp" @@ -61,5 +61,44 @@ #include "./cases/trunc.hpp" #include "./cases/view_bracket.hpp" #include "./cases/zeros.hpp" +#include "./cases/s1_f16.hpp" +#include "./cases/s2_r1.hpp" +#include "./cases/s2_r2.hpp" +#include "./cases/s2_r3.hpp" +#include "./cases/s2_r4.hpp" +#include "./cases/s3_r1.hpp" +#include "./cases/s3_r4.hpp" +#include "./cases/s3_r2.hpp" +#include "./cases/s3_r3.hpp" +#include "./cases/s3_r5.hpp" +#include "./cases/s4_r1.hpp" +#include "./cases/s4_r2.hpp" +#include "./cases/s4_r3.hpp" +#include "./cases/s4_r4.hpp" +#include "./cases/s4_r5.hpp" +#include "./cases/s5_r1.hpp" +#include "./cases/s5_r2.hpp" +#include "./cases/s5_r3.hpp" +#include "./cases/s5_r4.hpp" +#include "./cases/s5_r5.hpp" +#include "./cases/s6_r1.hpp" +#include "./cases/s6_r3.hpp" +#include "./cases/s6_r4.hpp" +#include "./cases/s6_r5.hpp" +#include "./cases/s7_r1.hpp" +#include "./cases/s7_r2.hpp" +#include "./cases/s7_r3.hpp" +#include "./cases/s7_r4.hpp" +#include "./cases/s7_r5.hpp" +#include "./cases/s7_r6.hpp" +#include "./cases/s8_r1.hpp" +#include "./cases/s8_r2.hpp" +#include "./cases/s8_r3.hpp" +#include "./cases/s8_fixtures.hpp" +#include "./cases/s9_r1.hpp" +#include "./cases/s9_r4.hpp" +#include "./cases/s9_r5.hpp" +#include "./cases/s9_r6.hpp" +#include "./cases/s10_r3.hpp" #include "./cases/opencv.hpp" From d558f99e679bb0e85fc8b56bd8f1c6694f8a6381 Mon Sep 17 00:00:00 2001 From: Feng Date: Sat, 3 Oct 2026 11:50:37 +0000 Subject: [PATCH 2/7] include shipped catch.hpp --- tests/test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test.cc b/tests/test.cc index 8de2cee..fbfe18d 100644 --- a/tests/test.cc +++ b/tests/test.cc @@ -1,7 +1,7 @@ #include "../matrix.hpp" #define CATCH_CONFIG_MAIN -#include +#include "./catch.hpp" #include "./cases/abs.hpp" #include "./cases/acosh.hpp" From c29a493dd0f0104acacc9dcb55f42d762c749f7c Mon Sep 17 00:00:00 2001 From: Feng Date: Sat, 3 Oct 2026 12:14:57 +0000 Subject: [PATCH 3/7] fix env problems --- Makefile | 41 +- build/.gitkeep | 0 images/0000_gauss_jordan_elimination.bmp | Bin 24630 -> 24630 bytes images/0000_global_save_as_bmp.bmp | Bin 3145782 -> 3145782 bytes images/0000_mandelbrot.bmp | Bin 3145782 -> 3145782 bytes images/0001_conv_sobel.bmp | Bin 0 -> 786486 bytes images/0001_gauss_jordan_elimination.bmp | Bin 24630 -> 24630 bytes images/0001_lu_decomposition.bmp | Bin 786486 -> 786486 bytes images/0001_mandelbrot.bmp | Bin 3145782 -> 3145782 bytes images/0001_plus_equal.bmp | Bin 786486 -> 786486 bytes images/0001_singular_value_decomposition.bmp | Bin 786486 -> 786486 bytes images/0002_lu_decomposition.bmp | Bin 786486 -> 786486 bytes images/0002_singular_value_decomposition.bmp | Bin 786486 -> 786486 bytes images/0003_lu_decomposition.bmp | Bin 786486 -> 786486 bytes .../0003_singular_value_decomposition_1.bmp | Bin 786486 -> 786486 bytes .../0003_singular_value_decomposition_128.bmp | Bin 786486 -> 786486 bytes .../0003_singular_value_decomposition_16.bmp | Bin 786486 -> 786486 bytes .../0003_singular_value_decomposition_2.bmp | Bin 786486 -> 786486 bytes .../0003_singular_value_decomposition_256.bmp | Bin 786486 -> 786486 bytes .../0003_singular_value_decomposition_32.bmp | Bin 786486 -> 786486 bytes .../0003_singular_value_decomposition_4.bmp | Bin 786486 -> 786486 bytes .../0003_singular_value_decomposition_64.bmp | Bin 786486 -> 786486 bytes .../0003_singular_value_decomposition_8.bmp | Bin 786486 -> 786486 bytes images/0004_lu_decomposition.bmp | Bin 786486 -> 786486 bytes .../1_0001_singular_value_decomposition.bmp | Bin 786486 -> 786486 bytes .../1_0002_singular_value_decomposition.bmp | Bin 786486 -> 786486 bytes .../1_0003_singular_value_decomposition_1.bmp | Bin 786486 -> 786486 bytes ..._0003_singular_value_decomposition_128.bmp | Bin 786486 -> 786486 bytes ...1_0003_singular_value_decomposition_16.bmp | Bin 786486 -> 786486 bytes .../1_0003_singular_value_decomposition_2.bmp | Bin 786486 -> 786486 bytes ..._0003_singular_value_decomposition_256.bmp | Bin 786486 -> 786486 bytes ...1_0003_singular_value_decomposition_32.bmp | Bin 786486 -> 786486 bytes .../1_0003_singular_value_decomposition_4.bmp | Bin 786486 -> 786486 bytes ...1_0003_singular_value_decomposition_64.bmp | Bin 786486 -> 786486 bytes .../1_0003_singular_value_decomposition_8.bmp | Bin 786486 -> 786486 bytes tests/test.cc | 2 +- .../__pycache__/const_returns.cpython-314.pyc | Bin 0 -> 14325 bytes tools/bench/fft.cc | 78 ++ tools/bench_compare.py | 222 +++++ tools/check.sh | 933 ++++++++++++++++++ tools/const_returns.py | 244 +++++ 41 files changed, 1504 insertions(+), 16 deletions(-) create mode 100644 build/.gitkeep create mode 100644 images/0001_conv_sobel.bmp create mode 100644 tools/__pycache__/const_returns.cpython-314.pyc create mode 100644 tools/bench/fft.cc create mode 100644 tools/bench_compare.py create mode 100755 tools/check.sh create mode 100644 tools/const_returns.py diff --git a/Makefile b/Makefile index 3469911..debe25d 100644 --- a/Makefile +++ b/Makefile @@ -1,41 +1,52 @@ OPENCV := 0 ifeq ($(OPENCV), 1) - OPENCVOP = -DOPENCV `pkg-config --cflags opencv4` -Wno-deprecated-enum-enum-conversion + OPENCVOP = -DFENG_MATRIX_OPENCV `pkg-config --cflags opencv4` -Wno-deprecated-enum-enum-conversion OPENCVLOP = `pkg-config --libs opencv4` else OPENCVOP = OPENCVLOP = endif -OP = -DPARALLEL +# Portable -O2 by default; FAST=1 restores the old -Ofast -march=native set (D-006). +FAST ?= 0 +OP = -DFENG_MATRIX_PARALLEL CXX = g++ -CXXFLAGS = -std=c++20 -Wall -Wextra -fmax-errors=1 -Ofast -flto=auto -funroll-all-loops -pipe -march=native $(OP) $(OPENCVOP) -LFLAGS = -Ofast $(OPENCVLOP) -pthread -lstdc++fs -Wl,--gc-sections -flto +ifeq ($(FAST), 1) +CXXFLAGS = -std=c++20 -Wall -Wextra -fmax-errors=1 -Ofast -flto=auto -funroll-all-loops -pipe -march=native $(OP) -isystem tests -pthread $(OPENCVOP) +LFLAGS = -Ofast $(OPENCVLOP) -pthread -lstdc++fs -Wl,--gc-sections -flto=auto +else +CXXFLAGS = -std=c++20 -O2 -Wall -Wextra $(OP) -isystem tests -pthread $(OPENCVOP) +LFLAGS = -O2 $(OPENCVLOP) -pthread +endif LINK = $(CXX) ####### Output directory -OBJECTS_DIR = . -BIN_DIR = . -LIB_DIR = . -LOG_DIR = . +BUILD_DIR ?= build +OBJECTS_DIR = $(BUILD_DIR) +BIN_DIR = $(BUILD_DIR) all: test example -test: tests/test.cc ./matrix.hpp +test: $(BIN_DIR)/test_test +example: $(BIN_DIR)/test_example + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +$(BIN_DIR)/test_test: tests/test.cc ./matrix.hpp $(wildcard tests/cases/*.hpp) | $(BUILD_DIR) $(CXX) -c $(CXXFLAGS) -o $(OBJECTS_DIR)/test.o tests/test.cc $(LINK) -o $(BIN_DIR)/test_test $(OBJECTS_DIR)/test.o $(LFLAGS) -example: examples/example.cc ./matrix.hpp +$(BIN_DIR)/test_example: examples/example.cc ./matrix.hpp $(wildcard examples/cases/*.hpp) | $(BUILD_DIR) $(CXX) -c $(CXXFLAGS) -o $(OBJECTS_DIR)/example.o examples/example.cc $(LINK) -o $(BIN_DIR)/test_example $(OBJECTS_DIR)/example.o $(LFLAGS) -.PHONY: clean clean_obj clean_test clean_example +.PHONY: all test example clean clean_obj clean_test clean_example clean: clean_obj clean_test clean_example clean_obj: - rm ./*.o + rm -f $(OBJECTS_DIR)/test.o $(OBJECTS_DIR)/example.o clean_test: - rm ./test_test + rm -f $(BIN_DIR)/test_test clean_example: - rm ./test_example - + rm -f $(BIN_DIR)/test_example diff --git a/build/.gitkeep b/build/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/images/0000_gauss_jordan_elimination.bmp b/images/0000_gauss_jordan_elimination.bmp index d735e7dd6dc197d57c799e3ba0c2d26f3ba4bd8b..0522e3fcbbbb31836b5dcf9e3559bdd6b2995d51 100644 GIT binary patch literal 24630 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 literal 24630 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 diff --git a/images/0000_global_save_as_bmp.bmp b/images/0000_global_save_as_bmp.bmp index 94a09647e8c1c7fb92da0805411986d396cb1731..5348acfb7b396db036e8f8a2944b31f431df4494 100644 GIT binary patch delta 34213 zcmafcd3=*q_P5!dtZmY?Y15`l(xy$DE=163wPPz|6$MMH2#5j^u!q%AtX_Yd+Z&vVYX=k8~J zHZ`;=HsN1Of`a=udSeyMfmIdFjh>;+ErSO!OMZ;MxlQA1s_PZJsBo~*2+7*qSSRtg zpL=BM=K8-NoHzyZcetBb)$Ghc!3IMyYuJ)n#@dz}h6)_Z@@zXx|5&HzDc|?_udWM5 zbF0dRW(KgT-`$N(bv^-J)hpMj*@1@2Oe6Oe%pjg_^O#Ov8btw-W1AN2T1aB-)Cwclv002jV)SoNJ)ZBJOx*LQmS zu4B%Zl|kZYX>*&>%L8kBTxIO?-=#gSjQ-ch0!w>rWWK-ln9c$zl85=GDZoyL#>Qh` zx}1~`ovUU3CD$>($)2R^kcIW4Nf@X6CHdPGSvT>jx1uhN2=$y@a(s zobO?+KjfL3m()qjuQI2z)`1EWzrCSHwZ0o>T_=8 zy2Z-xe7wgSoh?tLmWOn2xf{NjwKT<K=-!7+_En%4Qu@ClG@F$e&Sk?Vm zvztR#m$CNu(+XL6mDG!MoXZCjUum(-wrD`}L8FzuP^Ojm*n4|;MNg(jIH@Q6nceVx z%;(YDnE&IPUaaj=$;8@D<^i>;E9y{mqMnuaFwtQ9{)lcTbRR3X>CCKhsaN8U-qd5B zV84+DQZ(-uG@{M7eCQ}-mUS8zYg_8{vDW|QxtRaXY%72G+HlW1QAb|hd?{<0Y%XMt zOH6Low%J+Ad@HgYGWTPz*P!`=9!3LmZprmFR~?y>tyNW+skHn#1Q z!Ni|T=rQXuA~i$=s|OtZwZ|l8`8e6#>_6CxmDgjV2_7sbEH+lYF-y<-KW>)zQ@S2D zF=gQTYkem=b%9mo#ChdDwEfkB{tk3`SxD)yv$nIDn8Ev!BzDQ5l=zbuie_{&5~Vag zxUFa-?JRp2BIS2Wg)$N6X_KD$zS2tUcz&LikLf6C*73%d@>3MV%9yS_nZPWY(o1;9 zS49gunf)V#m-(9%=`66_-ix&!QCOMf1+$fP3{&d)@Xv}C3F>K4>crj`%rZIK%3Avy zF}pOx0KR(}qD9_1rKng>99{OFSxzd8S=*3U2YdZywZxA< zS!9dARUI;nV=eER#4%e7DgAay zP8UmJXN0Ft5NsD(3V<`OIQ0cqyBLy4n^U9o2C??Bv0mmY^jKN>eQMK+QwIRtx~3pU zLCnd3&E;`rSSWTna{yc7!C;TU?NBy12kp~VZ>~|L7Y+Rx{#1>F zwfD#|apjwY6>YH-Bk1r}Fw2lMY_i#jrr<=UjVsp_9=)uEnb8&i@5Rn>GhcQVI%CsF zEc9`VjbB>Qt$|JibO|!ahYp;BMb>n7V7S)I%9HUEc*$+%#(9Npoozu7I%N1W)b{0O z7YiNMrt?eFiMeP;W=9w+jWUMQUShs~V9?4}xlP0!&V7T@#ABZ>+;dr5o#=v1I_>Go zuu<>8=J70?XCokC;3)fCw7NloV`mcfbbTj+H~=AB;4JRiDvtZtB#EU@!Mz?ks&JRU z-_#wy;RjD4Zyi*K-HtA861#0PCbC3O$0GEymY#7K=Udh3%s1R(;^+H@`~OyVVBT0( z*jrG!>;;{4{qxMM{UNi7RcSqTey~^J?}C}P1T%EmMVjP|p2D&)73!$C2wO@r`p|ii zanXe?26Pf%?I?5!sCNVtTOf00^kM#Y^UQpqxzP8=S8WSo@JdxEuljdHyOEpP!CJnF zb+hsjdWkRnZvl2LhN27MzRZ6#R?jSN$4Cr2TVgF=TP42oUj^x5NMckFBXQgZaXbe1 z5dkheQQ%e94deSy6#OSV@vLo%)C(J#u09zF@{!;G>uamp#Fu_tAa`w|5`)r<1&*j3 zym427Prst|%>ongHbkY_^>*L1coulToyyuj*6Z2)d-6=Ys-@r&jO&{Pla*|LiKQ3j z5MmbbKBf)l>%wKkv3zKTxu~Vpy94b&5}TlN@kUmFa}`beMM=kLpU4|GZ=O{^C%rgv zyCUx-5V;{5{ouqf_h6y?HXLCfTgc}>R^aG7oAkW>(Sig8HPAQGKq2!7;&7*}O$lGS zkZ@hJ^sU_aV8PtW22ZfPo0S(C`iAc#?3G|jui#@kFSGcua~h_r^~`?)2fOoLLPwXt zQOhD%F$-+g^n&;zS(*PE=vkezS<3>Oi|yB8d8JiFo5T^;LmsZ6 zp`<}}3vo~uVmtX30tY&5;%Nm1v;K&TUei*Lks(WZy7U>slAVQeAwNtQo-48OKAVa6 za27lPDmN8ni36Va2Ma`n`{N5LIww++Up2R@O_)coXhTTZ&@r%}Dl|}!h<@M|H)NK( zlJxv)9j(yF-62lfW?F)mNC4^08h*Fp;`w_n1l?~HKw3@48FuxB{D->m zMMrfrD_@ZR88uD>1_XaqhC9~{-^L3%@|SD0(;qX%>aQ@dxpOrM%(=)Tv5sF{Zsy#a zKQox8sOOe@@*h{s_1mHhikrK$4Rp{el&D}|JygvTIZy^2q%Kt_;;dhrhQEF?nOH|} z2yz9d@&jRJBiDN^G5ZDgeVRWSV@Si>?aguS+nZl=*_$zhbCLn4j&xg`#@{8T)6hcd zJBGvE(jQv0t~Gy-XlpUl>`8`FNR5MVaE=U53S>`~alB!(%rVW! zp!I61Bbp^F)GA-|QuC7ICRX-t7VhphQ}sM)YW`!Q1sQodA%mrQF|lZ8NH=T~YdLGx zv(S5~W}Y%G9}5ax$(U2anB(Qn2$MkhSeH*8Hs7sTCg%Ue4g$YgB{u9^ay-YT_JkWmz7_o)${YWVsi4kzP4Y{IGTGwUVhTpKU( zOG)9;7EKE!pA=S@l9nkqb~YU)0KwyB36_$#E!b9jwR+a^5M(Q8g6M@Qzkg>hgyDqj z1tW)om@sFZ~|0#VOkjLB7wjKV@4B%veqc>#q&4@Qr-*W zVxPiRSVcn40Kz^+_s2+*frSC24}&eh9b|DZ0eMvpc4>T}nLF2buM@+$kabM;t_(L0 zZI;k+_E(?-xIs2yxeIaAaz3dVS80-icP#Nf&;?W2P=fF0FAOr}1oXU&l@}^YoBeB{ z2#^F=o27-tvc`$qJvRUmYvEn7FwYn3Mz%Hov99zd{s*F zWJaC`yAH=}-FPoa;{*@$C0Y2YnWaWo45140>LAs8(wxJZqebbKSox(CiG8&{7J?DP zbYc9fuk{`d(_mG%ddHJsf+_kU1qbazS}V^O;mtGf);5h%w826T>qkRefeq;AyS%rD z-*905C>Fr0&6cNXh|oA@*`xLF@BiYxGx}nrc!6^P`hEcO{r#2Rs_rkza_Hsl+1_(n z-6MBv(&DeM#j>)pxK!5GQw38J#5tZ)##53`!P>N1I`Dl!^I@P&?D$VPR{mSE z_j&_CXmA?c>K%=@#dvGREK2-bjQ19~QP%K_dM2}YH6vM|hY8g0gT8n1e2j@Jl-^Gb z;x)$91$4}IC9EqAlX7ibaX#-;1G)*@o)~j7En}WN&Vnp=XUB#H0t8DSOF-92l%NGf21*TR>T?#LR~aB%XXc@2yVceK0~EA@?8AM_wxqRBdma($_mjCjWM&_CQ1C6xAOX5_7Y*z1&+Z-^CUKLjzVH{hAE`rNtMLFH?+Hl6kz7Xyqg6q zm??(VrnIv3F=%web9tkTtaVggBbzuGTHJ_d^6u;Wzr^o;H17ed_ECAii>E<;=iPY^ z8CiRtatv#?89cn|_OAc;vZ|}|^!)A{^M-W-6>>a8D})5S!j9>(G0P2ln2H9g?Yto` z?CH+kMznr26jsst^IO6%R$u;NU9Lr7tY(&UT`#ft`E#%0VYsKn0R+p)J63rV?KI8L z)e5Y&UEjPHXXY=>&b??L4)A7=77`R~rodlgU@M#JG0E2d_9ICq{??@2BLZ+;7eH(r z%v(*Goo}kgYYsZUM;PgirZj`(A;+v*S>Q2HEx%g@9fphzp(d4{zcV!VBSCcn*e*t2 z;RvBQ>O=@E*E+0{%)Y2@Y!G`b;_dGICx7%F7`i|h*0y$)Nmrgl-<9;#al{p zKQYiqu~sL#3_TT=b!KrjH}2H)ZH2jF-&EFGn4^Vgu?YJ$)GtQjpXzg8 z5lFNL!%c)&slaJ?msZpHG;H8mXf3oAI#2o8Oua0I`X5h(HuJmZrLH8fmA#q)FuP(9 zYhaUUZI)pC%z*XX@~PR({5{nY)OncTzxu(`M2Nk`VT8G=VSy*quAmC?lYgq!%?_+m zK;A8J1N3Xp3sF$8=^^*Z5V9SK!MmKi(~qvTf8HI(f#K+j(Q|Y``^IeL7`zriTU-is zjn##XQt2>|d(N}LxBF|ySZlMLS(YblI3@l9`d#;a6y!z6yy zTb`@6bij~M4viUCAA3C$M`8N6stPimz;v+(V}bRBLF#X`iADC>KRj<42y1kZj_&sR zybi8hy=7 zB5tlOaA&a~PNYMozhLs*Y-GnxUO>0yRI^%KS#NZB`Wt!KPdSj}N>9^o$x-~SC~ku# zwfMc99V*q5*(vh@n=4diSL+~^DiUozZR#;T6K=ZH_yoVunQ31_;k+4;{R>} z(({dhoHipS${}BSG*gU^%JC_4Qdq+}dn(Q*+&CxJV*;J`=U`j`ie;~7{SGQkbd~G* zmcco%>r{=;Sa|JyIaU?TdN&9#(GMN?!*gJk=lo#At)tTWm0Zxg0HmXB&bzCoY}&+CFH!LMHAuY=(>ro{W*INHA|Ab zK1}xe?DxbY>aLluZO=xl5W$LB|IM)Q@=vn2ivPnAfvcJgn}Fr8<%8_A@K}=oQI(cw zXN^)x(53R2(1ois;i8&i8D9D z0_|g1vWqzj6RrG#1=+d?5*;#RK_H|-O?7=>wt@~dArY3YhYb$d%XMNNruBh2+1Emv z+>)CPjM2a-WuXOz`2rrERxb<8hay@Yqc!nSlV$q28{diP!&;MNLbdU*xKQb))f}Y{3p~#156S95}k3W3COXF>FFPs&L zrslQZY5AjfWM3&d?G0Z~0*_VQ=U5mv(NyD#YM+v0;}6|TmXna3cK(zD#EFPxB&z^w4%R}rr?xG#O|E9zY8WgTf1Uz zO83aUZ<#H(X@z+bessc=ul{GZyQiwv*;im+tow&LGgt@F+WdNo#Bbl_9;T543R+dY zqI6jx6J{e}K=%EXi;4Y6f*Iih#9su~$J^P2_IN#?yvZG_RRyOc@a8S<_mtEvIPiY( zZjAH^ia0rnpoqIz`F=O-GrQ5dj_bfw)pKysOTGg=YiGT+3Y{TOdb|Az4 z#r&%w7B#9o>B6+apI+?#Uag+_L`*un@t7x#SvEqWtU7A)F!O&PA#GpgZc-Cr3h$5u zg#ts2jbs;qa7EEa{qM(pZ8>K)2X9ICu!~<|7f);ob3;fq;0CJX5aVna2{ILgiL;O% z`aJ_oY%o2)d73*}qw4s6GaoU_eNMFJX9xqr(mhvH&HN?aLJDla0O&QCkad!tXFlcb z0d@6jJpjoV1^34QY<@BpF#m%VE9?Iad>@%(-30;)@>v9m!Zi9YIL57PP8$q@nIj@F z;1)J9ImXFHU=^%=F|_{pfo>dlXlpbeNL?pcj9M!e((~~w6HmO&eRUM1oM<=}+~FdE1S!-=qiPB@UI-~&OHC#HmltuEpSqJF@{15Z1*#JYPLn&k*Po`C6wAEvM8iRZKO3@ge`V@S%5W9^;;KOEmx=Y5*}>PWIzMmt>2KW$K@Wdj;Ec_M;9r^$lSy641MQru-niJff;vH zW{v&-0UnqHo%-TO5Z(s&&YA1UBkkF0tUl;-Kbqm*$6H?XYi53!Hja!MdIKZX zBao~PJXxe~UII%+=mtB+?`&p+LIrafKX5K{rf8`T9WoO-fhpEe7;^tTk*_?25T19CrP zby#e7^D=lLbJu4+s!%;SQp*=^$lMWTkg#d1l&v&lzb?-#??RQH_dlYSR-`V-H1X6q znX?4pzG8sj=(h6I$(c|bFs5NP;5oPPj3+Yxdf7uLY~$ds&VMKq%59@(so2S|k5-bu z9M>m}W>p?mOVO}Qi&8Oym-#cN>bgQ!Gq}%j@dJ|Q-jVsGk#-IC;o59i`9{U*+3=CM zXyE3|$%4l|f=5E(8P{cE0b*kNkRuqbwzb*%;MG{ixkZ_`C{(A8HSxM0ng15!(U+tH zKeuFN-X(fa2{klxC^2c}nTda(W9IpW%&!G2=HNug)@@60^W|T3y=Yj`+JU2~^)uH9 z_`?>%n&|HbCigyTh0Xn_RKgmKkgdqI#g2aCx}%X$KYmYS+*|p@L9HGzTm7StSA`DUQw=(3c7i4r|MXhS+zB3&tXF zj$lgS9H*K~nDbfdRuXOYB*RFL&`w^~?84a!Kp1zr0|qJ(iP>7I0tEp$-8G&#S&RM* zKz~T~M$DO83?72|Ab6GO(TNts=?PH>L46W09q*d(Cs>}#utdCU)lqnHgv?f2>oOQg zj_t*qyF9S599BXQx+5FILxHvadYgkr3kHwFHWRBD>H4c;0Do?z3rB{`;Gi&rxCtr5 zxi3x;7o?JU4cy1QfY-Rh1BG@Gl+%hquDUNcqD%qdv1*hVa0DJ;b{0O6Bf3^uwu@?S!3aI9WG~t1M{BC{!A$M9@M{abuGo5wsk^Z>=yWzhJR~ zjE&d**ID{MD0JEVCI>31hVdu8DosE%ZJHeZM5^RY48Vkn2G=*k*K4Je|*c(@n6FQ17t&;j`Ndvup#${bHTvoJ|zE z!YUP|2oNddZ4;b}6^eIxZG*Fr?m2>iP*aitdSMs_7~iJHob!psmOsAdaA59^(1)bf z9XIUUH1XeubW-R7g{?}vkZc$5pyFp#w#0w0l>5AeBf8?RMrxPhES6$1^`>PIusWfQ z?9F#^)irY8worVK!3N!>I{WkfJUTb4bRO0lvj4j<7k{f)2*b;U4o zYkU|3qgKyasxy|dwz~@v4sgT;{b?+^ON(KX);X2NZ$~#MD8@%zunTz;;oM%Vuyg%y z8RAlD1=u*OMO@&E+=FOwi4cSKxTIo&_HgN3C*XJjq+ttlO5x#cN3-EH7o5U;vFWP} zaqCd)h;1ew+h2j@39a`fDD0?DVb=fe;fyGgVM{U>(gSJ3l?Avki(|5yKeCND^SkAb@~g0|UpuCTEO`G>ULu z8bsV;INP!eP{L_lpSXxYJo`jOErIPrqpg^gGT9!7Fv+9YkX)C>W2zl+#GZX9V~l_k zF);F$DvwinnKK_Zyl5_n*E z#wps_2Lvq+-d?7IL;SZfyj!Os_D}SveVo=y=1eSmy80Uy8W45hf)){HrHZzBd_oT8ay#|vp%{x3#~4t-b~VA!JAK+!i4hVVtlwWU#47Cc~w#iL}84;9p~waakVLphmt*#}Jj4_s`0htWdRX zC}*FzGdzlS_yu=Hxt5nEWc&)pfGz{t@rCq*xNqek9{JRk&1@}i;I{K2ikpx6G5uxO z9G5AV63ZMHM5ZzFUlQ1gqXC9C-v3DYYXWv6hiv~Pw z4_I*E^@kcWswv&AQZ+YN`K%Yy2Wq3RvK2+lA%uB&!ou-OT0UZ0`riasTOu@}qCt(X zVE&Wvs*QRo{Za9{HS(IemCh`)aHz_bzGcZ~{=nGu2L;#;VY>}ZG{UkvBo+bczr)4g ztAjKxcD#t|8#N;RegV8&1`azS;W&ajh0R$C`8ji7ctZDeq34Y?v$Ib?#LB!aJp2d9 znma~ls@!rz`YVF!A^9P6Y6MnLYz&1~vtyGGkeAsr9q~^Xz%K{}Sxi5|3gLeiifnvN zdXso_lGL@)lvj|>V^Z*t1H3?i!Nz$9HJ!)AravbjPm$TKPC&9i9YltBW%^U{8wwpI zC3+_5kq{^-(}0dho_jv+UKvDAb2O=;L9SEcNV^|m7XpcFZ0ikJU`5}hIaI1+ho0g6 z&ZPAa^LVP+@}C-tw8eQ|cNYSr`;579Mby`BW~u&h;tX--9T9^ z6w8BYFovT$r@$YK&H&-ao%5o&-A=_Mp}V_)XgDesY29s972VnISUlwWnyPk z2<^Tvn082j8`Y}aTl({xo=7IWJ__ z)AAn0X*Q_rqg1%E$RsA>X+>Zt(obLl@}?bDs`kBm7r$7Lwoak=8*eE}n~S*rf>bOM z81BfL49!R+V3A8fU}dJKAu0thnAR7wAWmh%vhMo`0o7k0L-fAIng)FxudxYU%<>@{ z!pgqaq;bWo{1quJR*IeTlsU} zZ@g^hII#eS#Icj9WJlVax`b>_Fd*yskwbXI@#t4UY$_3hHwwmYR&Ic=d`>Pb*xzhP zh4BN#DC!g8Y++*(BJn*bCVp;RYKC|_lOnTyvau*o0iQ2oFc2;6Nzt>wo!A>czM7gS z02?D@a1q~!5!zOR`S^Z*>YGZ{k&hndD;B2SBY1Bnf)K(MBeY8%=-F@pd^J0@TBX|g z)~)=SxvB4KiLW3C;>;)0m1QhaeQ>hLEiDu;1jA)z5Iv66r#_)nyv?h}rM|0By|yri z+n!4OOiVh7-*7XcZ+!pgF#1Y7gDHs2ZTp9&#t7!u(#26KTbs%ePQX8^PF0IH>*Oni zY>0e+MJf!zU~Nn1LlKe4KPgH5-oWwZCxNs>mg*6|47a|(bfq2jt2 zBU|eG;WsU}rbB!^Ylr4lrvnT6)E4;-tbHWOl``b$q_d@^R@iJ}_3%U+4M>I9pAPAK zKCEGg21Qu=Y(y*c|3Rw{uGg6O;foHxenoMI1LDOg$F(t_(wi*282_&wqlITQ_#G}S z$WM?{_Tmgf^t+mGI^r0uRGm5fI?p`j=%*trj_vj~tX7cU>3H%Y6wO}BoEvh=Kmgt+ z%9=#Jhr~nM9UmBExc1#hM1!=ACW%wuW~7gYM%c}K(I$eioVC=*f$c5NP%b3VxSX~#Jj2P2Xu zR#=ncW~2su+vKPd;0R@B4d)YK>zyg+Eqac2^jde5_ zINZtzj)7Nwy0EoGOp(2!I=z*L20BVrsslTQ@Dq1C9@L^qn3l;()jMjr41=RpGO;@l z`1>gq>Ym#iPZ5VBWpIcyO$y|^&!kL!niJV(A`*Rp;tBFX*a}rDw?kO0B`pEQGfG7FzW|fldpVBBiKAD+ zJ(9YJ9RcQVrbu)G3;YLS^t&mJdMr(o!}Lb6LuKM6Kc$ep@^p&I)Vx&#lkz>@&6Hck zoyStLA?1EzImGbi1W_meW)U2bBybB2M7Y=2986iF58j?JL_}zG!U}bhOuz!3)G7GE zu}4A}y)`>i-dC$yLSFv(-jrkN2zHIYhIBUweF!54b|xX^XD%#k8I@4yUf-ND9XIis zlmn8g?fs!#w;`oS0}ofqWVK>Ef9sW$1}!g{mr^FFo_*BF$1g~ESHnwZpmV(JY5cOn zz3K|qamYSMX0+wCxB;wXcJgd_z#8h}p+S6V)~ju?R}}D;x|Ak8o3K5zO+XWctODD%FpjtX2KPeCzyvdQU6WGS z$@)%;vKiUQdc)dy*l0g7BC6v-V=-$#Yt!L`mS%ps$0A-yl1RXvUrvCx-^IJzi#p5B)7U zL9JNG>n+ttj~bf0n$H{9lb~MtuzLwLKZ8=+YK? zRrU&AmA!;N*|28{k*+U!C$PrHG~;Lxkm^>5wFr|x>`Fg65Ush%HVB~_2(0%F%h3n7 zQc~CNIO;0jMhSugDhO}2FC;&xP_18@&yF-EJ5()iDf$+v4!Yy#$%2=!W`K8dw0 z)+20)>@d+Lf~(z#9ePWGoIKKv6NoY*%cdk#c*Svyf*{aQm3)RcYwU)GJMb+;RtYrU z!^sls@g|PmvL};2RVbe4NqZ@1)AyqlMsqpKyFTPtJyUVCpW2n`{gv> z_i*yVDn&ZKwl+ClLp=&J1Y!saB4VOPFi3n$#$i484Np#o4A~WM5CP7lR-4KCBra`e zj}K8KNMjZpPq?(H6bLkfCp8*ga%VDXsq{XQUxot=cPmW~n!|c3N&b$kG}!d(DH2Bm zWk){GG(U`~^N1|L<{~j8||_`lo@H6eN#P zsg}R!25|T}@OVG~+JAOK#Bp08uhiBCPcfv zS*2pnCh&a@`@=eZjmdt=_(rk)prjdgo3gT$t^1oH4WSMhI=1!03@puflZM}*wf_vh ze@a5@r|IV?{j|}~d-U@r{j5RUcX24`Zmr@i-nb%ZxtbTxOPZ`wH1fAt(lfdh#WM)t z>7*jP_8P?tPg{`PyRJ`KGV?X5ZG8T-NsE-cIFR&;R`CY^;>o0YfIcdTF1^7?59`!} z)X8zI)s&RL8ara}*9O>=KlUe$RIMoPM~#=$&sFr3OFyafW1;rcg2vB@1N5rleUVY3 zNLr)hrQataCTGqsBoPioxM<6Da3A~I6Av44C?u}eDdzKk9!;MDWpq zKY8gZ^jr2);@djKbpBf?@p=F~pV*>Sy~l6n|7=XW9rN8lFeQ&C4%Di?J-LuKjZa*m z<|Pjb{Cg8&cK8Xq4suheC>Qm$4Ix$PuWyDe-dz$6lLwi&`1Dm(M6o zJg8+u3KEkQ<^1u2#A{S4SOWRl9*H4D)yxNPL!60_s{MWSCN{A$30+B`{|BCj)-Y6; z_^DQP;kOQcjU_Qh&4*sFO_EfP4%P6J%EYHN3`R-Vj^W;9p+2z|_Ll`YI#zKy2@=TR zOq^u9Vk=2Oo?|IEOMXfCM@c!#<-zl*AZ5Xk=+p2lfGTSRy zZ;ikyo=Q!KyQcAn1Unx;&Ndx{clo(yqOBaaE!{#04udX_;4nUZjBS8cxqBr4=rP-) zY8Lwg`AGvvA8)@t%}TLnNCWoUmMX8$=b87}(lE*ch^Nv%wqG&#rM7cg)y7wg*rUBr zZ{aUD*?z(LqJ-gMx6P(f+|T=YY{w*V*B2Do42apW+py3~Hq)!c1ocw_ZT`~6@`b1Ql)olyz|Osjc*&)oRQj{KH(}X{h#BD)rx1? zBNyVIg{s7$r3GwfOSsgKZ!AgDaz%Lu7Bwxh{IK%QELvf5+^4NwpLo=R!%8D>;Sg~-fP|)I6 zDM@xd^E2x$YSoEPC-YB^TAx>ta5dt%^&5?BMT~|{A+rV6CLD>({|O8f&HJoxh)@Kq z!UKi4W`97b3rYwN<~vQixz+lnXvj=&ajN|z#}UnY2J%L08@fRmB9UZhEuXj2x&xz< ziIvwf6yA~0%#?7hblXk@x(64ANy2_ojR1UqqxGapb@b!E@tfvZ4LagB@@A^_eXVNs z-?LdL6}NlnelL6sxiPrsZ&XzxhrFPU?!iWz$2ZF+9;u+aDW6F+-kmK zj`cAGu3AWF#bd0r5d8F8Ism_{tdG^LxRQ_RWBme$MJYYne?L-%MQBi<26K!oKukCt z{~up%t(CgG_78@|cUv7)^;)+TQx0z=7{3j3q!)u6)cjx0I zH(6n!n%F-^dc$wMN27Wh68Tn*btZ!43@{=iFAT4VF0Vcvhl7j~xJ-OdTigk(miOY& zqc`IkCDoSKuj9M7#sxGhO4i_ENhofKRyobakF1Crq2{IY;z-(^0oyKquznvy1tu8x z%zXUQaR(*UyRc1GO^MTDp@iS)d3_)bTOjZa#-scQwv8}WA$}^JKQSt9-yhxxdw%V^ znBBU;aoAO@@8=^)@?)4_A31|5uIm?fty;BxV{iUiRoqKT#WVcoJL4{?S^Hagf<8ih zHSlSSERS2MQhj}V9*?~#?iyW}*MWi1m;G18{TG=VtN?2!7k~0WsnoF{7IaM12N|i3 zLpDVq2l0qQf401=q5hmd&`d) z|7|FaHMHFV>iARQLq4%!tOtEyxlyCoz~_HxStCK4hjLG;t3%(hT#{7(JhhY$-(mSi z9eu$jo`Y@!>pnbul+qT60~?cCEZ85dFT>d<5{x=C-U3(S5UG6-8@}-s%ew*wizl2F zhwGV+lVz{R*!ldIEXy^jch;7y=)Kqy=Hp$cVY0PfqcXEG{cvChpSP?r%FJ|qD-J42 z>}THGAcKe;Iaq;RY}-1VgN=_{UMEa?%<=dv4n=bOpuQ6}J)H*rohk#Lcdz9~jUtOr zA7Obz!^-YO-USvWER%X1pfHJ|{LxTsh2|>Daw98z1@_~Mn`3YzEKY|{p8R)#+tK5T zlcAW*ya_FKfeGkw$&*!;kGt0LouoQ`cp|^Q%<_?%1so~X;G?J-=69mhV0{6NRKp6` zVBw%C_#TbN&^$m0d|!IILGB z$6Wz8uich}wVbC9HkwO$={U3Aw4!1R7_9J{u?|N+f(x(D0Pw;~`k0qWs(-wc#gA8- z2WwcvF6@QUQZr75wslx&p}%9v_V$^7LWkVcAv^t;%qC3?t#npxPDy4BRS06CaLhMq z%xy*;wn3u#3J9ceH>wj^#&aqwulXsq&8Wo-?2H(8<1O|C*6^fK-&AMfHK*}P2Wog_ z(y_VARGA!m;jmF>kgh;M5~G%#Jg-a*XSpszji36q*msQ@o#qOpR(!O@1=r?5gbE=o z8E55tu?C&$o!3hE)B9pKtJob=OugiU<=Qu6k&E=>suVf1RwOakuAyhWpGJA3Kq4*_ zBEinsJ$gPN6pNebjwjSU=DP}KJ%O`1$V%fA=Ea`SsP=5F;{FA(pKF-<2`!|kZ=gpv zoW#{Ten#wjMpiaBmXb0Z-2Zg!m&WGBp>&S4z%dd`VR`((6R}Tdf)1FPC!SU4`Iu3$ zCyXNE8hODsX13r49y6HGC<>dN@zMQae^M(l_^|3&*xi5p99Dyj zhRs5m-1S$1f#$EL(W7t%S$!_RnpeVljjSs(uS<>n5Z93iqPGrx~{*ND7o$XoBkz-S#!;|tvZ zTXl1LOtMb(^n*J7`stV@st6q7pOMAK8$XEwr@%$r0D_ZhP@D(_XNq~p8OKcuj2=od zlw|ZJPlAJgy(b27hJfX0=P=e{3-pBIS7KQr}a7 z387>K0XKlW9yoyD7)ESHj9vxNXcGVRk(fb}sEGu+It2(*42>La)?$UV9?ont>xbI4 z%)i?Te-|0=aiGIvA&U7lW|?`8KjunZga&Oxw9l%Z(0>PN6x}GgLXIuOK%TL-3RBIC z0VpU!2lVN&1`_(jz^g!|nZQP@B0O!)oycMDFFcyYn4zrk8sktz0m>Fp9$Omop;9^i z0en=#2J51HcYtq7RHCTNFjCe2xH4Gr zP)N3G9Z5)}x51ej=Z+cHh57ITp#&*G0e1XNFsi#{5xLBo4FXsLL2!HQ*? z5DAp*Lp>eBQMhSA7g(BE7TQ$!Dm?Knj4uUqAgUO_$%|2hgD{w|=(gRF2`zjZoDMyX znvhNtSyN{s1)@Y0NK+kR!WQdm$od7344HyMv5s_J&}Mp|6X!dQid6qJL}`4B`5li@ zrf^*mj)FWkiW4L&t>6IWaJW4v7cJD71!3<84~kX2Y5KQHbKmVM_;cqzsYzsQ!YhF}Jk2N;{IZ-D2&-8~*EkO+gzIdGyHzdj9M-Q;H(8D5udefib2uKRLut^c+#~ z)Pc@+{Wl=yF>j6O>FCg*Rtd6+x?^uzVp=J0ciL_z0Z`WofP^YIo2Q!)B_h_rQJ9}$ zTZO@!yg8fj)yux|thTtPNG2dO6QDpRr!b{5kywX)AO+?Ho@!JJ?> z(d}Tu?;EA`XW36i+;|tit^tz+UhFU(2iVHqy& z{N9+YYi@w4uVaR8BOy|aoADT3q~UbKMBy`de&dqh+(FVU%Ric`XO^BeL__wpL9aZd z@dd3gz%SwHdj4Cx@pf&5DhJii{){R)mMFw|Bds$#c}&3B)GP%0H=FUIIXykN2S)(; z3SPXyBC#cx5c}N(Z|Q92jAxDcW*xukkTI!yLx|Qfjzx`wv9%b9N;G|lx4&h4H1ebx zPYz;iQ2YSdea!NNr4EBWn=Y#|*})YFWJgSoF|CxM@Q^b(DBaE4-!UR6>~Yk|ZGn$~ zHGW~$vOq7pjRIVVRP>Bxqu_8*c1fNpPK4Ax^9z1hLb9J4sNhA*6_5+#$Naw>AdiI#4z8E)Hv3dl$suG z#K^Me8wbHVQ@|(AH-0A3yvhwlq!Hww(qJ~2)b(O8sU`A{!&_LCik^+jMca3z0zR7Jh^X6P7Q`;c zIFMW&UK0rTSPutCTT}V~d42jb5ZK##0ugim|H4AG_g>>m(W&1_-Pi&iO1gDp5*cUw zBom;T_&EcDVjeUizKe>l`xfNLt`~lw%E+RGb`v2^gf3^!w+vYUf;kcPFeSSX{;%Gs zbmm6J06CD}zsdMYcRCxp(J94ES}pv)BKTWoiU!!30KaI}P2?dZe&_7vu?L1)CtM`=*| z6O1dO_{J{y*f8VdPGO+Cb}CARgUWxRec4A7wNkW?%1xG`7~BEf+K1_FYhoV^2R0iV z&%x{QjRfIqocI(3?EM!Eo#QKLy&9!81hfYI2`$#9Oz%#Njv}j$eW}pG^;~L!Wx3LA z&IAbMGm`hEAFs zF@l>9flZH5V?To^9QxH+)^x-G)6kO9Hhi%(N!v~^UsN)Dik)usQx#f)r>Gxt5;MfkI z2>*k`O2ghL2`b=(u_*sas-c>YjTj6sxRT4cgJ3BLj*k+=`8Wli^bkBbpVlKbwWkev zL|YT>d~c)SeX&lv(eemdbVGwyx?m2cF0w?<8A^gKH%{IoAOI>zy9*%5mvX0A%P80* zeIxDZ{I{uw6b0e}uj2M;hHVOE&7J)7=?1QrNAh{Ya5@!nF>TiPS$RebbTm0)q;m*5 zIj*FW2maLgsBa=CH%-A6g(w9GVsB{eY{_=2HE?~h#4Ud_OoQjXQt`<0COZN{Rym**&>F0oN>L{yW0bpeDC`%e>tJX9rj214E8;ag`4Pi2QMz|W z=)!VAvh$yCjto+L)4cmVqBZkizVK7SFW_dbB&5bT~Z+Z&}7RV+;tIRjwhIg^X2uC-t0~$8lVpF;1-7 zt`x#i16UcILYoHx>dCn4%iLCWDII~}&-aPZv*Z8GG4Qm%7$!$)i;9v&+rv?MIZLJR z@=;eB;+3k02dVi}y$rdU&VfL=)TMxg#1xx?`Rh>Epd*`U5Gv$|LtHb+xa?_zMh3A_ zB;&EhzHTd5WEKU|2rbP$sogI+}dsAwMxm|)Qi1#ynvb3{MT5b4zQ#4^Tu zCU%*y`T434q!X|qiA5yQ58ki8R{)c=wvVb-jFAyG>(cpE+x1hTjgnG>MnQ;Qxk>+= z{2aIRZuvREU%85&heba@tVVuGWeENAz-9VrSgEdAte@5$Y~*ENZ4bZ`cTJOij#}|K z-x<{ZDlbH|MzSQhAcB@3k(F}%G>T)EPmAygh*lYb>LOBkuIA9~h`)>}FtaTu5ij5G zDgBetws(sHBmWmXGjBza!aK(55x^Mkmq4P*Ct!oBGScV=VYud~H@{d-mu7p7{-4pE zgVi8cS8>4xDWJjcK>hRb_~9lI0E0l!4FHrlJc&c6sD(#)bEV!Y)*sE!PNGGi zE+l>EpI0l^@&}36#cI%<#Hc)rRz6F8{a{V3n&y4(SH!3yHdu>(K~p)QcvaTa6vXaqd`7ewy8I_ zoY(CYLr2Pmuwh^^iblTEQ7zCgFeD4AA_lAWm&g*#Jsy5XdM_0q8pAQ;JJrh7#r(6w zx>*Xv7kt!bx@Y8@4TLocfgD*DdS0|shv?n#jBN>*OmcJBrm4=U7?A47Q@OgODCO4v zGQzGz$(0j_A&mys>TXx7PJg+Cx31TD#V~^-LZpgK=m?d?EH@ZD;Xr#6dt|BZFX~yz zaQ#;w%*o_!OLZx7?>pw{WZ{7nSWzTO*wazf(#2X%LZ1>}H+gKP?wJUGm8^ZYdL*|@ z(9M=q8W#OC&)KPMX z_=4Aa>l*bei`qM)kR=P=bLCm)W%_kXvLKf{IiyjobLv zv)Z7{qKwfI%A>6d7QWOzt5)2=gWqToKTE6#04xl+J8tz3VgP95_%_MrFnZOEnwfYD zTQJ^oTxKX0C(xoVP_PbWNSPa)F;u5iaO1Oi5PG`^sZyc!5ZXkA^*vjoe1-9GW6L#% zai)H$9fBM(KbF(T%Az4DODE4UkkUtmqj~!|}zA*#USG0%N zAo*KQB0y@_a&6~)A+cbFZfTcBJBip(Z$NyghCXV;LJakNLJPFJ;b#6oJCLe1dm?KQ zA|7=!G#4qPTe2jru4i657O?EykKfX#lunsKkuKP9KAd7@;x99y{Wa zIGe03)R-&?sz$0#Zt4|CnxFduth>Hhu}IN=%Hfp@*WEQko1~8RpQDC&f4JV|<%bdg zTk`2%B1sCBXJ|_|l;k361jSMKu!pn>(M5$+hI*|4rQ4hQdWc4Y_lDmyO#84}F^umT zp?{7!&7TD2TW#xeQDI5%K$)B`bgAI&EASqoKZNv)oSf z{uMPUTQbddD%rwsEz+V)R(M=63iU-?h8N|Fx2w{5d1hoEp@X>X03jT49FDknjcWe% zM4sc&qR?8H2-u;odZ7*^q`|w8Ptp2j4ysfcw5XayAk2Tk11}4%i3kV`nB*C>T$G+N z@Y$!N$D#H`5fguuo;0k$Q$&+?!A3;?9EB+HXk|sWA*3YZi1cXp2H-@QqktXdYD6I$ z_DcigIFb84k$S4Bm8)_7hTrSCcDwX~7_ybb{mia!Hqo1C)2t)cI2t1a(Ux47+Eo&A zF+1P3zGobbQm78DT`axWNpLz5^a%%Q>s6pPl8 z>lFdEjFph@IZZ+)D7AqJK#)ScR)&yoWy@0$4kB+SVbdX_{Jat}$Jnl1Od`UCY&gZN zVDeB#Z3JHPEo~ZHJ#Dwp2Sb!94^JG}l_IaYRl3I*0h&TU$Fb#G*W_TvZjcs-@iC!# zx!vPR2ao9~A;~$)GJV=I+BRfwRDtC&InpbCf@_s4&BmliD>~mw5z01hh?Q1#p^Ren z^E>O)T9i>N@m-JLO5`z^QxS+@Ij>nI=nYhG%Xj!Y!W^t(Q@so2%Bf-%#vczueykD2 zqM8qD3Q<6`O_LxFGpcjTh8;oKXB3D-s+TpnP<&dc#f2be%PPdAiGr4h6YGeczG!0{ z*py4J!xR(wW)4-ABdLY8&c-K9%HO9l15`7tZL{XzsI1zi!PnZvJ%QZ-gWhxbNC3=< zb@R|#O%6J9wCno{ajT)ilb7T3Z1g&c4zUH!g4@h4`JAC|1VQUSJ$-<{V23NJkEnnw zUZeJtjW3_6$?6;-RN5U)PzYN`o#gPDwiVv4WfQ_weq$OGx5PDQHov^ z+I_uF;}nd;&If|n!+?f~1YbaCe7iu;P&x}?5Ix~Xd80;?A{dO4CYcDDGnLauF=?BO z(1r96uuNkDk9b&e=tQ7z2*_bq%PKWJy7g>iR0x5EsV_Je`k{%3dTXv044`_y_zHrv zJ{N}8?IsUk%uRI?Ur?;MLA2Nv0+vrY&Iy3kK7vCnj-z9E2ll3!zl$xsXPLl^==1AP*!{g+fLe?!7-FpR(@ zDx|tG9Km)1YSbJbcEB6Is^uwHDv*w5tW z5-IWL1r7lhe}*@<4J!k#zTN7e%#c79F>`)5Hp0b6v6kQ58YXk0J0MOV;v6%s=WEuh zA;p37Uxb*A5C}3HLbIS=f+*JOa`jBLdU_)6`jd|!-lyt=0*DIqm8p=@ZsRYkR^OxH zXByNGi<<*xyPFYIjKcQFL0q7|)4uU@NFtl|sjsjJllh&T@Xo{!dkg(Zd>^X1~! zoizmMMWH9uC<9C|n3vu(FMZ{L_75SXg)e$Y{j&jD32Jtrs4Ouoqyvf{GVyuC)Wf@? zdSOciLmtlVP$Q$Bj(H4RrA!wuIbNdLLZmHBGVE62^8GkyU(ClkxK2H=JLct3B0%pf zBrwZie2(?aA~j@RQ2aNwBEv!e;DJmv3Ic>V=!z>r!WxQM`@))gr58409A)K?`rZgwL6 z2ss53|L{ju&p$K|F^bTkusKK#LTn!lWmm#aGw~NC@+QAg9iv&Gju5k?j-#z_&=$Nr z2>t6C|0nTXM^sQGy0J$X&@ z%UC4#?p9ZZ-$Gy!-+W-nlQR)VT8emsXKzzs@d$QgKoTu!r}4lIDu@Xft+nFY87yRk z$vt8cz~PVy|Ii~nRrqQM>;l*`NTfZZMjTUDSr!mEsueO78Duv=A~Fu7EKwM~9V$eq z5~j9^DJqCO&^dC*Nnwowiiwptb~lEOIEpk^(FG*^CkzeEA`d9hW|#~K8Iks|tgnL~ z)zTz^)_LVUG(HuWFZiC$LoT}KJLNWkcCCV?`z5Fa$HNc6&Ps%sp(Hz1&>vetm(BoX z|59GFQ^^f0z%W7(UQ@0!g$eo~^;j8-pQAFwQsw&~`kE3Ft^6Q}2MqexFRbl16y_3j z(TB}cJ|R=av))3=Vsr526!x$4uq?_S@o2-xHReoELU5IlwOX8f5Y5@SoD?XS_hLF` z)hS#pk}BSMY;SToceQmQ(Kq}dP|$!gH4VVD632uYJ3KU&n*tO8Ga8bY$n3Y zq7jvRuJ=!<2qvlb}i9l7yOK=`wB6sSTFLF4!y z!ei8i&Nk%!0vG`UR`ikr+Zx#}=t};j)mhQZt_#KVD_Kx%w?iTq|gHha(QC#ze#?8mH zVoqUb_eM}C()U{ynwMKoS>~vm5N-iM!7#G0=P=kxQJLs3dOt1V#T1bE-Jhe)6nvhKusF3F zB;t*zN7Mi-FV50$SUBNv?J~<)y7g)!qY;#GtB!s~%wgQgL5m^@RAd~VYlai)0iAxs z|J(L7;5q_ef(9)CgLtVROM4?|WymhzjI1JXs=f$#z%{U`eP9(M|8(&H9sbRSqZ;Ai z0bIUe%T@%`d=KVQPzSVqvx6q^a!FwG4_L!&R0sCtK-WuyPE(w`?i<_Yy?*UT27oT{ z14iCg34g+2Nz(`zfdYml_+vWpK;B*aIGzVEX z4zryHWcy?XO<%g#0`Hmzh$g(sIu~f`bCpAq&AtRJX1HFnR9sptQ`)UeseGGR8SQ4K-{-ln*DP3{_wV3Rik{GcdBA-J*f zkBSbk`tu21*1SR4i}@z#J%RUC2IgLsXyBW_Dr(Yp;V^}P!v$h+-bnxbo&KAxXlhoP zFXq?YGrKqObF$X^U1ru?m}6mWgN+7uew9wk+ukl3tYe+Enl-#$o0U)f+s|5`E;_|J z`e^N}eS)HjRgQN|`d1DWg&J_!NgU9tNz{;|aziS|dgNjHi27(rDokU(K6(!;uP~We z?LMQKtv42F`Hqc6b3$Enh1eXD*s#uZrkSjMkYx(1@621k+T(RTCUvNf9z%6yj+wQW zxeY97oyyGjt}dDx!K-t+=D9`t=Y$$m!O^cn23DDtb%FU(rx> zkFLd9S7heaNkxB~7=91PO=&e|e#6{-Y%7|4Dhq3_)lCig`MFDe&r8egHne@H zn@sI}nkGk#YM9X|R&R(O6ENwzv9^~n_vHu82DY<3OUs|Tvgl4REu&b6J+CIjz!z%C zP-=;v7>pAPd!ZEWSTyfT*Yfq}3vUsFIh3`la!awzwBS)dJl>Ibi0)uKv0K`` zlS4cLbt*Hfcn`z5=I6rj9=6f^O1xO*ZnM3~$00nHn~J4rV%E2_e5@@=F^<(In(LUm z-Mfl4?{W2{Np`UFKkC8n#6S6Am@`@JYHT(CnluCZ@mY<5zx;9GYOLcfZ}{_X7Y-3? ztSaKojQ2APym@Eg(~u~VuoBH2*pR*~Sg0ow2}jLy1=j(h2u9k0-f7Kj#HU&d zZ(dS3NW2}$%EuR$&>I6AKDEHW8dhY0YeV5vp^6390bx2m_u;~qLT}Afr+8b&*2fvO zeD3{);|1%A7O=)H7+=_%&mCU4wri0JdeMu0|B}Yc8*VOK+4XH-dRt1<0qLFX#n1X& zEpNEKaJXRIH`pgJ$B|glY_3GF9`QT8$mJB<79=?;It-0ER1$I_z%QrAgwU#g3Q-CI;2xR1Ovz=-wkl$=Akf%Dx20|U% zN^AvQyS8Af)XFYzdo!$0C-Z-!(y|$&uu>N;FSuUf-&8vr?BB??v&vk(gZ=m|PNn&a zy4Eh%;g4V0(^%UND!iUuATM)K33I=q87EdG$BKMiRR24xd`VNn`d?jO=EElz+%IOk zC-v9ID*I?H($c$aB*G z-B-yk%)DQA!3>!*1S~pPh@rbJsbE(Gm4%JC3cDyZ$)n}NqYGa9=PT=1Ia-FTt7X

zJVKOem>Jxk>ypL*V1-&ROh4MmmTIB0D^83q^ zSgy1OhU7X}o6lgO)u?6lC6Jr7+w-4|V9*pnW|kJXbrR7UbXt~pf0UW|x20(LqwDh@ zH?l@+?hiC!WkJSriNYegncw?dzPx^zwIR>Ttf#Pm+BRT$`QE|+)CTgGM{uAe?%{XL zqfrU5B?$*&1N|J-QMq`G?j>+$1;;pylLkxqRRE+t%r+Zji zsX@y}SL93EdKClFb9CT{!r?it?TI0m%7V3@*1v;>JdJcMTT|y=^dlEz()OSRhiMcJ zQ*n~LS)6~h7*4@>9<9zyaCWlth8%lS+Xbg6X;s=B%y$6|dDENUBV5fGkcvI-p<19V z=#)Zbu#lb#Viyr(vR6s64-JDJL>sgGbe5BKY>$VYd@#Yr+LmdcLaoJGq;jnOZX9Uu z+VVd$Qg210JW{#Zvw0YiTNF}<&@2e|+v0^XgQKh@?!v99 z7F>Dl={&5Ca67R?dNFq|T?wn*>czSLk{ajHS9!M^SzD&Jlc5_mf+a)2OepzA=r&WJ zOIy>87QXG{ylcYDDrD-xo;22x1zoD`RyBtHyjM`$__nu+8f<_02jM+x8;;++!51-` z#Hq(r7r@;Zg$*?}zg$jV+z`fmi2Uc`xb|FYu+cc>~n+ z*Ifz{+-aX=$EJEFJGD0U36a1>}Q8wQe*zwQ(&q6snzmV3-fZt>vJ5{sBy|>dhuU- zoi$wVg)Y)Qr@G$UOy)M>1dJU04&nP+@CDdN+y4U10>#TJL3|F1XCB$gA~{iqgeS=kC!(OzNj!>m9fT$)CH_j zr%Yq*pQQyC>W$t6(MX9&@1}Mo=KAfdzCv>H=Bq^=ncf&NsvdEuxwhq6SmnF63Rq3r zZmi=cn}M}{q;mwKvB=AMnYFzAGH<7W8sDx_-9J*xo4novSnUq)-zIu`i-KFDy=z7H z;uNf77>+`yH!uRTy=ES-^sbC}36;B2!3mQo4E+EaqAn86)@8-OB`;Wq_=ez_hVzdjc#iD)0I90qf${P%o3eJ*!VSXRyqRSxM4^@zs?x)kch%1`CH zgqFc$=b@y`ul}LzZWsw@&L8I(!df;@tjm2Y%zYMd_laYh6mfVIawE>8IpMc-sB`sjl361Y@iou)jyhelsa8SS;Gy2f__)qM~7Y z3OziVGgTDrE|wW2l&tQUdE)7uiSjG4n1UrO9Mcl3Q9b>roo9cOGh9Or6B;RpOo1W^ zrH4c587Zn2FSbiBWCaw0<_e_)TU5*Yyq$BoOy=&T-XW098OvNm>|{MRqKbb$L*@rb67d?ZOtV812^b2@mHeLxrF=O8h;{y2n#2pBW!0 zo$SyfDDZR~s_J}z6qpaa2F~u!DbScL_UKFEqc6!xz66K&!h5~(tnKy0GFJYc7k1z_ zNdF1e ze}W#HeVQBR*|&*i{^rd&D-6`w!Ln$Vt93;%uuh7Bpt)D+W$inoWDTYK4%i59UZ1m6 z6n+G!GtR+9D(yurZZj#M@yJCTthryTfxmfW&SFstzCBdDK{9hYS2fYeSH-RAIX8;c z6nYU5FQ9GaH1g*7oZnQ6t9T$OXSdi5A*#h8syfLR=o@FS2c}uf{8b|=6ji|iO{#0= zi!|@j*)6qMbk`zj=+-YtaUpLaI5=46wMql)9Fb$-`_6k7bmg=D|KMZo83Xlqup3sD z6C>^Z6}x2j&li{8@V`ov$C{>@uRh^f6{;2gnUV5Dwu4Xn7@vpqOpob2yWq_+DczkKV4Se!me9Pjte_UtwA z;whf7dez3~^LcZ(`Tj_a6{QZXbTUhoy>-TK9l>`y` zH~?)(<1qQ(k_m&)Wa6XQXI08oMLg}R?9Ey#^#+a1Kq@6uyq_JfRm|e0hqD8sf%`)Q z9H-KrFxx7r5Siyk9dKY5AjkQjKTrKgfZ0iIxO1j1KCfjl^dSr z9ZRym)r1Q8gYB~muTGO69uU}Q7Pcb`o&!ryY~7pZW#0$gXGk{UL-MivZ^XP`T{h+z zbVV{9PGMd+DVr3r$cGT(Xn(z7hPgyHb=UrJh5m$`h z2`C4GYyFs16xpmF$J);r3Lq;Ty`=8=|4vE^q?$@3rp@n?J%0sG0e}A#J3oIs>raKc zLUmIQ)|Qo(!zzb3)!g$)^UEfugMK7jcLBlUUrf&E0s% z;jGui=!BSfI83Gtl_Hmqem5&tshV)BhEF||m4m8o&$=a8m6x@yvQ9xPFn(rWgswEu z&4AIF?SKa;O;fxxFW{L7Hm$}v!g15k*Dm= zdRPqB;1F|2b!dNy_HpP2Wa=JAPic_cjadUj1B64ihYl=tG01S3W@{}D!_H?QlH!+TU9QmF-bZ=E#Td6r46#@ng!R5^U*e;OAvv21)bnN{K10~|_p`};9vqVmAC2@dA& z?l!Z^8Y{-5M{Fvqy&@%=oqjYYilyF?tl^0ZvmO#7GeR&YFG#N@-*eqW7!xBC*|YHG zYTsk;8Cb1BDC_nN1o+Ol&Aek))+WK|F$G#kO6DBqAEAaD<0nI3)_fqw!y6ySdQ_=; zW$oqMF*R$KLV4%S{L^V!T-}1@YNmxm=Fja}BZZXm!AG*0wU5tQMSC26H&-0=VcKd^ zGp@>kDn2i10IMBpGV@&nv)&OCG!osIP5z%!R{vW}byNEn#o=E3-^F`py(JfC9e=8e zL*-fX*RH;SmaH|bF*h0J?!VOEQ56eSZezZd?6S7W0Kxg=WL{8pNU zzvG}u6YU^|XlJ*Jn!?H_MZ>2;s~h|v@ir!oiQv*XufWC+nzJBGgIszBxinIT28s?{ z%tbWFmj}TXZV*_R*^D}qcFV!6wK6-_`a{+jR{3pol{|D0b;E4J!w^~#wShROdP3@B zMFdz(1#5ph5y6t_SvYau)T`NycZ?eT-mjUEy}>5-rY2%Sda>H&nM*?e2zejp}2s;|0mG)8n^)8%c7c$2&_a6qp77oA%)0HRR z)1LWKnA2v#iCGOrqi8w?(C`J`)WcUs>lqF6N=!OmhE(yXTm{VMwAlEbeVJ>+On8XN z7*@U{buz;u^K&eUPxaK~H)s@g$W0!GLousLRP!oWS|5Wq6tWuU=N|^R`Jc>Oz+2a4 zZWYX>f*8Is*8XbN7|0!LG2$=!T0Tx`rqnA7;xmEJUBOVk&e`F2rm527J>^wkEcwFS?vpjE4aTV^Q>Na1+mmS{D$Gp z_P{Qqi}?0r^-pG&k)C2>yB;tZ*;S9gi13xg#<0d+vG9kars!Dhn+YbCb!TRks98L{RAFJ<|3FOjq21B4Hw8h5$ zsII)5!^B$GrI9!X?8mUi$0nR&WUeea__(HX=KL^u0p{-h`K=-i_H zO|0Y6Oq@+L1J?5vKWn`yp$ZU^0j$}Z;$>}t0vD^Cn~4pu$KG2g0fE#M7|Goc@$4wi zyhqf+saJ# zP3V9FSi1r`>ec5`wQR*-vF5-?L~R$0vc$5+PuzO`pWMs@QSD}`_UOMynXny9Asps0Y5 z?I~Defx^b00l#00H8V#~Bffzeb({(!(61gyNB_K#P>m?nb_@uN6m@2no*lfZ0Pwm2 zIe|s{$t4FFjDk3kfp;O*wd2`@YQ#M3fHxh3Wt?y(BS+86PG(>gmmZF2uA zZ+$)kz9l@rfahbve73R-nA~mCGwxQrm!joe$Ih zUA=)PJ)W^zPesmR`>jkz@T7LFswT4ZPAg8Bp4pRy1Q4PK-F88|IMW1NkGt>7=%!M> zvx^UUAY+7vxFA+io|@IR2$2-21;hjJ!6`whiINZG_;~hR8P}*3bNO9&XIw7|BFcg4 zCx@#S;3u2}eOc>E3D_xAvuJ#?((Yix_XALoe|yGs$cL5b5FMdT2g_l1yp%9hI_qF) zcv$0pr;E8YNzk}56+=>b>lmUcWsORx?uE+YMMF!tKPuyO^b1>#@LbrwKfrPGXy1$^ z1+^L>71)zAMbm?|LuavgYXHh~z~3Vewudz<9X8fJ2d>h~x@U|PU5+MAZHfR_qU%G$ z=E;SsEQD~>*t%$~@s0NCy?4OL;5>lf4M#PWm??uG7NS`sNOWl=Yp_oN5fd{-n zS1k2D*bNw$G>$kw(GrLT*1SDI%PxEZ4>FkA_+{tP`$aNkohz*iFeJiVTZY{Op+pO* z{Uaq|`35kw42zh=MRaHS_%1~|?CDLxq6jJ$r();5WmpBa-5FBd9ty124YSJ9zZr^gHDGXbh>PjiS1a2hBL#rxtdj@$&~; ziLqG^7hlYQ^dXU@(8vHvl`v5Xmh0+J?W2$4JxhH z#h7m~al<4Q6U^PDreeiTkwzL`cZAi!M8Z(SZI;JZ5;G7GfCcY`x&spo@j>_-$Sv8A z@XGDAQPTp)^l-}C3VJhZ8Qiqkau%MwH2qQ`3@=dGLsS+=zy!0Gf+$iP;j-NZoWHvb z@N3Yj?|dw|Hz^*#BAt=;E3dn_oZ(GJV?f`Lfnd;gafnx9Z#g^pH&uzprNxQ zmO&aVKQ=twZ{TH>=@^re>(c)=wG?+xH#7WDU}Rp zp$HcoN@oF`n5R$QEed9G#87|JD3uPH@0HG0h@1}%H9PjtM1YD3`3kii;Xno+NMc||Ut-aT!* zN^jI&5^cD|q+>DJiey5{0BKN#ki+E6wB06sl>U+!(`jPc%?bn@0I)FgG3K;(lO8YQEtgmfEavsRcvd!6W7{$Y&o$tKb8x_4_QWRZQ)ce} z)%`Kn;7PZ+rT7T_w8QDqI1UTHbf5dIi8Z$4=qcTX|MKE>?p0dVi%T;3(T(mQxJl6L zM%?QopTW*6o^+pv4m8&dAuGV01E#xw#Y!<28rTXCe6|B?-CQf2$I%bC_o}13mn6kp zl9q6Z({@Q>46FH4lgJuJX$^tt?ow9ttR6VgC+q;#6arF(IN^8LD?{9iG>ZNFz%ciC zZIktN;0hK&n3o3-6z$v3?b51#|Mg>DeY5**4Xdn3#0FV_C~L2B_fP2Ef2d@7`6?hYYmWob`}}R`4!-#I)YTf*j`dfz^c)Gh0`&=EjB$J(W^`4)O#ttC zU1chyjrHDitZNEv`>#v=Dfo94{oOtFoL2SUV^8rBWvM6B#0bs(6MELr{M4&8ij92n zrK#&g1$a*S*HCvVl=P}N{6=Gcm}x7}78-g3AE{6M8B@C*<1_4>Yqv&q`rA!BO__S5 zLU9+bR;8X)OQ6o`Oo-f3oi4Pb9w5(_p~qe3mZ66MsRC9CZw|Py?g7WrGS^Lrtc+?4 z{S62w{@(0bqf(vdc$!CVb6tUxO^!jM^H*ZO*Tw)0Gii;hyIQq(>vjC4b*`tCis}6N zmt7asBoTllYC`Ip0CI(e20 z0{fqDEPUD&*ZW%48kWW@XSj3-`4jk{_0AMKJ9I6=XBHa^9d(H81V$BI~ErwLq)-@a-{te@|DfhL`y8w52!`^u-Pr;96B?*B_=W6|S=; z1S23wUxok1|7*%^8r6{x9^^a!Oqr(@FQ|Xqe=-H~r+zoKvHwWQe@%p6K)`${yu!Nz z80wn#l#jy?f!i;~>N)f}fJQZy$q>H#Qtr^GUf*^D|6qSgjh0PNBleekKTvvr;hpQC%t*o$RPrjSiHmwrp8({FKY%8xqLx@WRj^T|Z`7#C}b zS^1IlQf9qWUBRqBD1m{@Qb3oJEe{X#6`;Az#vC9`KY#}N`qY$r6ufwJib<{d{QU>` zb$6v0^`rs01Q4Kkq}?nKQVa+w0}l*I*=7=(*I%2GDZN0rUdNy8m$DZ#7}`<0{?xg6 zV|fa8ihoE7g4^Q~&4D=(`_1JD?l=7hY?xJ%2Zj3aWHZ~fUq_f%fDQd|h;}vQq->!b z1+Cvb0f*Fy(+)eImz;8g8ro4k|InTCxI#JoEzp|*;@&m_N#f%`uBEGE7q8593uGQ9P;lLk2a7F3b?8N5$Z-by}Zig*a@t(n)o zmW=rXIsy3HpYqTF@vt=+{P7T@cs>CaF-E`yo4GRih*DW!%}2B(|Ew1m-U2pki=Cv^ z)IE(BFx;PV@o|q+HMr-M#!gQKNUWxwn7K^Qu{)o^Iys-6fZcXq@|&3CyOL?C-cBv9 zOvY5ea=jm4<*nE8r&H$n=EOig=2wj-3MBDO{U&R2p z#Q?++JAJZMBWAo$RI*p4eBo(6*_!;Zip&ypY6ecvY3H0jsT8xHnvndc>djaCZuu=4 zH;kZ5BWBeXL$=eFtK-uPn#1JZg%!n zDI2EoN4Rs7UW0QMIM%dxT#2kc6Yx)%!MH4N2LdD0);M29pO-q#><(iT#QjpWGq6W( z;Zqu&ILvyE$iX?*4X~n)H*GfN>uJ!j8MB;P<)M4nrM{Gc!0*}u)_!HY4P~_a&somn z2E|H#w81$<(Na8xT2oD}@jDZh$_cmf0VAFN1u|fubF@Y^yswfkyVbc^+fv+{2utb5 zM?abLRVR}`awG{P?MZf(Vhr#1LDCmm)!*lS z=J}r`8I|z5(93N}*sl61yvmECiB`cp|MCSEo- z3FybSM)LQbNV-SO8s5NC>zo52?kh?K=<`b;8|tPf&C;sgdt*4)Jd#wdVXcFcpjy@@ zK`^!T$SLJBM<;E8m>80Dt4_6lM}NL(MAAw%FYcGLP@{UJ#>C$qko1UJd3iSPKRD@> zqNSu9O)2?L5;T$0oTPl6>cjna^1F+Y-h(hrP6Du87}4d%D6A{lV0G|0)}(EaP-+w{ zJCnFVtGe@M760;l;&crk_I=_fIt;>3{EA-_v(>C_Kjh}9jzpYOZFE=;{V(k-&=`Ss8sLlx|y$kC$SlRU3w>g^TS?C%vCGz8OG>scm%7zKJgZv2OSM;vQxb>n|G7`6O^m5xzpNuz_b!Jb&9S0^ZAb3FpE#dsD`W+9e6btz)qZ|+EvCJK}>J-~}<86-B zYAkZ9yp(=?^pi8v_Jj-Q?ml|8EdiRU?>t1C^$Y|@c>o7O=iLBV z!98W;wZBFIw@qF>@^AO02m|81wt`~GIdZTBr{)Xm6BZa+iklPAxF_jnF8xfWpIZ7E zP1OuZpxxLn0dSCA8?NH7RVE-3T2h`s!~P#4$Vs?Qr`o;w8vasI!ebb|7C>1%iQk)Em?FQ?3RLE@qq95%2t;OXAa8m50!6XwcZ4H zdMM0S*@$38dtsuBmu!l+DHUGcZ%h0sNT-$5o~Pq6s_px7#_+bg;&Xu(9Ty*OiC9Gr5RI z>(VUvfuD#+53WNG${vaTM4_0^6CRDfR3|a+N26qEl48;PJsAkee&W2*8p1 zaQ{It>Rf@DDtjOvaOPkG+Nc3_)PMz{a=-&rp(iNf3`RH{lIOpo%mLsw#}ci%JN_<( zYV)ch_E~kjQ}tY9GCxxt4;MYArhumA95J32!5AkP;_XYEKydFh0!OhT)`8H8omY*B z|4!d@UR}T&A4|;!DpT$Yx#Z=({IjS%;PS@vLAS)8(zlddhiNJ69{-a@HRoP4?{;-O znXCEnBsJW4RT4-4E!NZIowN7QDW2hzRPiU(9Ib>etNv0u!0_a9gtu}W4MJbmem!&` z1Xh5vph;}M3DVQ|i~Ut4sXssbW*?91hN5uo>eOB$sNwzvp=RM?ZF>Z+7!jWF^+|#s zGCmPpir|Uq_mURlIU~VJxCITX=^tm~KnWoqS%#eC#3`_G~7ycAbO7?@#9G^q<4FLA)aRJpKwl9F^h^^pBCgo{L1 zM3}GHZ2&ljoQ+t~qH3V)Hsa!%C@3H~oxNe$x%S$9!GgE|EVdwG%j1$uNWwr>%FBRJ z>~zCt;e_5>zZBTK_O13hxlAx(gKG{iB~;O!1pJZVCo`o!Q-~5yR!FlH=%geG z7|tG0g~(H>27Wk?x6ZaNGO%HPyVpqU#n6Zd8HQ#W#R0Ife*tuQ-Bf!3gt_q>Btj9W ztE|y^X)_`mN#jww(uN@)V7Bpf6YWiK_WA84TGeBdqxiHj)Y;O3_UUTHRQ}2kdp+c4 zZ~Fl)j^SIlzQ4UtgL4A60gHWDwyLKdj^+1VVgEwS+%1~z!h0l8Pa^LCLKW3geb%`^ zHY= z6%y0t(^lZYk2@g;su0?&c^7*3tI_s326pvdnp4zy1Z)~~@ZEPU(pUv~>f;{h#IrGV zJC*jC7=d5o$lZA|j<)5IIP|YQP8mhfZUp4QW3G;aB!7{YydM`S_U(z?+Aof z1<1sg8Vy`{a~5tC&Wn3QbOi|#8g#gUGby@Q2ujq6fg_wdv_vgmRu>0EDY`6>FdpV# z0jz^)SC9txvnIs>nkr~IJ~9HVg?Nv^nqkR8{Cx_Dtk5`@jfv}{xQ~A`CT@}-AIJo` zgrT5lF`}(lF%)80HZ*RR7`y=pU)wNvggvl6mw>CuGIjxhs2K>hEUAclT@Y4CghB-mg={9*nR(n19@4`IKW`hNI(VmYYRm55Yadw*#0nJH!zenbjjJFiwyiH~dngL|r=VC9Lp zF9YnQxYz~U#h0;f85hlg_mEy<$z$N@8m#pc?7@Q{#eQZaz{@1odaoAYlv?ERhSq4g z%-H*tir4snS+Tnns=y<0+&(+@uvX?DAaZO_*{6l10?Wa$V{&Y4kYN*f;?P_2p<#vq zE?21*G0vq>t&Xc zDGCQH3V`?=7f<^KSxyQ+Xa0d$>OcNu+pJj4SN?2^)<(~t5}jbVBsrF@>>z&r<&ECN zz~$+gfoo!WBj^Rd+8hOk=Sp#a*Pk*XB@vc7zzOI<4Y385G24s{b~!GRZ%Kwvt9CkW z)sn>GIVWt^U=ylY>uwvcxS!f?6OZbj&Y#0tQ{qTaV&k@7ZE>*Had6IN9khL{RK2@< zFhAdJ+n~Ua-1@$4Ar=kh>4&13Li|t}DCBA6di+a=!10TDI)2$6n@z9|lUW;#nYbWg z6E__p{|&r+tIY@^Ek~N2ca+La9up4LX9eNwTez_@q#OunOg1xjt#DL;1)dp;# zcAMM6^RjKo0T5(?{jnUtF02Kh^(Y;IYX@2jr>K?}CfTkt5+(v*#FT#e%QQB~C zU$6zu0uCBTH~Ks9ih4akLwZrr&c&ln#XM%jz(8fFD)k?f7&6ux_O z`aeck7(LVuJ~|L{XNb5By%qQy9HkW55+|$A0?>wUCY0zq=EY15(e4P-N+%QD4r@;W zwiCWtJ0D#ab5Dqt2WiWoAe2b?#(dvp>-fk?F%ROP6IqScDhNjrt{zaH%iNUg%*;oR ziGkpI{K05Ge_RZbiiraC@27V%>N;{viQNHBi*Ejn9vXACLNo9FsPqK(#3m!IA^wE` zN~6hY;0JDtaTtPww;r`2d_YGlIt#o*Ycf{gxMT~GY{bl#S|yf-+mY9sgQO$a(j>*q zaM}7lb)Ke9l_PK(%FU2s%uXmv6H%40`l}NFRXwEg@qXSIWC;o8aGEA~gx~geUHrC$ zcf`b`o6uk_%{CH@5nv=HW`Gb@!Gs((K3aJ-_NAZy#1K6`GmT! zmVJ2G>QTJP&mFdw>v-u~R(N$+I1H|)%CUgdRuwey)*aTTp?U$T#2RyA&LO&M1@MG~ zU-=STei3#nG(YH*gnRVAiO6@oA1aaC2M=2R18~0#*krw1si@>Twpbe=1Y}Nsa@^KH z-vtP&a_l%M{g>bl)ezkv?jb;81nQ`@Z!CtgCKjsUty(o(ac7c7b71$Nv;_9kh`2aj z`;7HC^h|bEAL!P|bJQaog$-7}WTzfGiwt9@ zAGX#+64uU6>n-7W0n@_iPAzPkrgHL=_gkkK1T3p^EUe`XR`{lfDLRrVk(Iw~bnuho zt#w_=f~P^j3Jp9#vjv<|xGY5_BJp5r0GI=9LU;f~Ng%4IB#ae!sLore z7z84GOX)dIt}e3t71;J`q>jYd;YByTpp6UT5tpA4|Gq-(XnH(-q zxy5o^0PuzH6=o}%N*1_pF?6`*YYFC%gWO2=&*eg_wtp7RT_7d{t01oH;norpLl!0S zg;;(NtR*T!%ypS{;fD|vV5BY>3|seF8be+7aJ1!G1VW@C!(1=H1`_)fUX;G%cz~=V zA5U#F;sAfIwFH9Q=-rhTIosCaN?6bz1lr{mTxJe2wjNAdEZKTkiDhJi5995NEc;~= z0!$(s3Yf~fS|VW}L8JPo%42ffoCA91ShTB6t$#NCOCvFhHdr7U3Ty6xFD_;v6 z#6Hb(t3uJnYo=QY^iq>hA6Y=6r{tsxJeUh#$kldnrvQc=K+IcfEN~f72cR{nsw9mJ z(>#gx5*GMG%L3+q0eABJD-kfZjj}8V4oxo_no;4?0b3jC8^dMIG_zQGw|*5V8-P;onlx=kwqcyl#w+tDSCVEds$!ZQN~ z=z3YGtms7f$h2WJ4Y(cATkn@(t=QULvhzhT;X**nY9lT<`fo8pV>7`D_RWW@_SnNF zJ)hLgQY?=OH3m9+_)u-H)(eMKDZDVS0Vz%_*a~M^=e5w0#MK-))1hR_95uYgV=4I` z93gckMVDQ~v2|h+OphK$r?@fJ{ut5~MKE+)O1m(m%l`7Y-+nXhLs^TV0fd_eW;mH* zw64HANDbT3nU%;!_k?3Ew1skCd1BuVly<`y6>Wo!`#|!hI__Pgyl(^nK(u; zAz0$;qv3I;RTE}dKNf0n^DN{se5^79cE%zncAyVwEquF@9c<}#Zxmm6EE-NzAr|D( z7%FB*fXs2URs;tE9v_)Gj!_L1>Y5sOP0K!r2E;S6comA{77bz41=tsp3&b>G5~zIO zfmC>asrVBr2(Yp@qbG-o1Eq#b0pa2T^$Vlc6E04DlglH@kMgfZA5#kNbK1^mqzRQ9 z(v=g0t?XIt%P~ri!e=8pPYF*fHil@N9gXY7+Rh_pEbP%<&qwr4u%rtX@a_?YoK7(E zs1Br))fX-av8ZO{EV{Si7XBEE2C6W!O<@M&zyKb3D0L1?3xSlto6)%EwhG#%Ujdu4 zbbhoEsG;R4HBtyq#9C@!0TQqF=2)P#Ef~Mj8PW0qOO_*USHV7je+Is?aq+`~ox}09 z-DyQWaB=-9gj!l3bUFgB!rsBeAtOizZ1tXF zqv3%guV*l}hqN@d0z{Xx3xSa~#$yTekA@3_?$Y(WE&4rVs3X4y1p+0vefuCa^ybeE z23Ay_iJ-*wxCPagZnCfopTia`nuQf#(LWlhA&no|p(MaX@6amP6cJ$zu=4VgJ)=Jg z6XRM5-7NvI5QovE=qUo-PMR(bE}#eaSH;mELe-aPARTC!asRYfSZD0mS?uPM-e`EN zg30$ByxbKHI1Rb}D2JMsy>m|rgy96W@Q=c;O4{2ITl57*D?bt&{R#H!pT<$F{pNH# zYp??Gb@>*+zniZ%K;V%dmh>wAsW$qX&=Ro#1l*Uk?vH{-P0454?{;c=-r1<900}3B z05*4!ViM0f9rc}Pjg6;&71dzE==izyb^U)eCZ8QducS}TFAA)+BPZd{L(9Sv5EOh-+JydB5H)Sxud}e!bO@0?pP9+i6ZsmBDFWeH11SdH|Dahd zUgO#yDLIJg!Siu1jIK5V`awP^wzC+(-obm!f5U^c(7ax%2BI69N&(z1<)2|T4n3;T zvbqiM1r6D1ZZHYgCuxRQ84K5O# zKm?74^UBO`o^Ae1)VhIoJ0^34BK+FI2Te8qCSHd*py4Bsb4~dF+6sW~q;zl0ast?} zu@lW5Mrm~?aJbp^|N_W-v zlGAgb6twWo*Eo6nDHC#TMUqUDH{wFu0Lj@w`v*DmkX`I3(IT@QP#4$fhQfve_C#!2 z(Bh(r6?zMtZP(kQjL_v-bWT9@ z-%&V+rUmX@#2|R=bEfWL5|W}E*LO*@Q_sq0WX+O=7_tvhNs%L{^&pD(x8Ztc`n=F6 z5G~4U(GXnr+nLo{7B&-I06+}N1#O)PIV}a>_W3!*G-6>|$Qm5&8{qsIu@`&hNFB~c zB>pn+>nE8KHL4%KdxejD!1N>F;nXfFFPV_IxYn0!1YETucx?GppvpRPlkGtVK6Fw_ zw&6YrcY*&U*gg-BF})AZ0dYZw-o9uBGAhtzPRP(|y0KdEy_?a7L{L0>H(tNzLoT$pZsidMi>DiJ#wK@Q+J$OGX1PbQEKq@h^PWo!A2o~9THSg;l~^F|FK{=_*8;3p|3;k14X2Ir0)xwm;^(mYMf_IS{ZNg)^mzw35D7Cp z{wre-BgPrAU(f}8AypiKdcXzHhya=L*#D%A(Y(FReXxWG`j(^+ur|1IA zefUI37v%Ljj3)#K%4#77Q#@zQ!?R|E=c0W()M}|E8;$1#59sieeYG~A0Xhg@VnFT` z;9}ZZDDjqTC1cX)_V@buA|1bHweeEwf#i-s)uqzM6WTCNVxg3iR*ouQr_;Snah^0> zkK_N|CC1-HjU_0a7HYXvDS%x7Hqb&tdFdW$;E=@t*Qwl9_}sfAABS~X1_C??j|X8# zKo9|pfJAR_rUpoKus&d@P%GAQ)q93%f<7%oPoGsV@a=5|scz{7vfIm-6nAVk z{AA+m*BEdg06blyT*T|8hL`oKMKfY}%_@UUB}k++w3KX?@kWDBnU%!q2PGsE{LvgZ zgF8yVA!xaKp<%RO2~a8SQSlA44>% zkCLeI&wU1$O11p)M1HQDVWBwwu`lWC9Qcv}00(E0_C6CnBH;AS*6r#?C)_QvdM9&qG;@kIvOUC^7Tb>ZkF;(?Lu9!o31LY)a}OzF09% z?A7M4kYe}3=wP=(&pV!i{;9k*;`uZvN3a*C04RdbtsuyKnF82ekzJZD*jdLQywRDU zps%4|FkiY!|B5jZxxHCuKghKDFHFes*b`T(#hski{kY7s^&0&&mB?ZCpI-XQ zbzO=fWej=I;I}OZ9MgEa8Z%f+W3eIMxm=I98GWsTzHtF;;&HQ!^iN1frzixo08Jka z&!RwR?8?;xJk^EXOWCl)v=lpJojJTpUv$A)AfciQnq7bq~$qCdcw--_yP`q2T?_cT^85+LU=n<=z*QHb(N`Y=} zXuZILgv;YJP7v90@^QG~(Ps{x??`1EAJANW3yzsLKz+L%Jq_sm4Bs^Q7I~Z!o1}T>yh{aV&kcdwsEV4QH)|tA` zLUhP1OF0K6x;xbtHt|ln8Vk#mbrb>||H|a9#CxO`fP~WGKQT_HlVkuxj{qDupc$rvUs9~}U{6T3llk!319Y*{GQ#qBQEYIeUxsgwuDd?oOJ@zeTyycul8BP4 zq;D;VYV#g}0*LgVu@OzfU;`E@e~v+1KiBfd({zbp5}FU01YD#egVi3*7nijSe33m^ z8$>h04?U4R3-|b;$7RtiUux}agAV9|&@KT9Y)e5x_jf5lD1YQ{ZH`=}5EGbIz=ZG> z4N6>qWkNLdKWSlXhD8HtfFB{6j^7Z?x89bHs{RW;!K$E9OJ9?aPar{to4Tm((;zFP z`$k0|qhk#sOg>Q^y#8J7f27(33G&|#S>rml3%8@}Wji&}Qa6O6Y1r0V49k_64Axm^1HwyvX)pi_d zs@|MDX0Ur{7zC+EpWS+^hHUl%z(8M=T>O&9nebgeiQ}g649zj*RHIVXrd$AkDd{#! z`BBQwS|8A~k*zz1S%=18L~ywCntL^0MV3I5$clrJ59py3(iJ#Y1k!G{3atZZ)olp# zz?u_je&F~Sq=&jWRx>)1k2u3cj2e)>0IG*FLFirRN{ByDf<+|11CEK3PBp)eEQEHT7|R42P| zFL~Q4HCJBDi$oW?SjE8$-?;>rGV(qkoYY`NRvfuS4z?>BSHMLG(|ryO+KlV|l@+Dl zuA*jrN$F>%Q247G!e2;tNO0I@7d|=Brqa52Tz3ud@p7+_8U~*yg*2?AlAlX37Gxvq zDROfPoQWu2JJMB|52=|fbSqcr00avQ~$J>j3+sTj&TG#ccq{>KZ-Lp_R$Zx2>J zj7HLn{zDg{{P3$fola ztUU7OL34dLTxuSb0^Vn>s+x+e`L|*c6geNLLHfKazwovC-cavFzXX#SVp2m)2)`Gu zTN*rIiJ&y&GCQ_R5OGU^MeaVV9u(pxz8t!Mnlwpdb`uVzuH2|6rPLGPi9dsDx37iL zq#Z)e-sVNP=bv%lU)fEe#!VKexi0L)zySM40B!3HaOD5Fi$;g$hCceahMEFI)mr-e zC%$>xr7V^>HKlndPTJo$sppD81txcX>5ThV7AOniw)2DJ; z3GuOqgehnSUvRs+P>FO5mH=(4DykGtaXI0ElMU~IbF~47D^I9a->+{0Ic_h|B;f0* zz{C3AX*>E!N!#X)eUlM!$n#z&-jR}LwNV(otz15pImW!&uA3utzM|- zKeQ@vDHQGy+@N?bW4(+XlA|ry|KU;`Xu#Ex1s3fe$fz2^*ke+F=7=PhF8$GsLN-wc z4Jq13%Ou#b4b-tFxi~I=W~j%7|Q)@Ivsxq7@bC{PxH{RU+I@(jTO0X{+}G`~mJUqVh@x=+Vy{U*3~H#qQ3 ziCDUuE1i&%FHQ=0~8aiIZ)YUzMW&KPQ ze6vMIRAIr7)Q;aOp9^xMLloIF=ny4t07b&g3?a2}V!*vI4UV#ucGY~eG_o=H>QvB= z0ayEj=5FMxh17>vxGMwS3S4sZf1y)z*FM#P|CafDVy{IkK@*+|%Dz1%%5}kidayDrmAhdaKGIp2(Sm zorb&A$j8TR_%#j}+|rY4vGApTC~-*xjYrAwE6fgFeWye>@anZt z`>;x>q@;oeuafW=k13_;i6F$-3vJcL!K*CD1~ri0B)yT?AWCgu2kW4{(kGnw`)?}$ z#*m6iyHZ1qhG9W>@aT(7yzNybz^x#BMKnCjmIK4lGx&za6~4X6wC4fd?g`-`?m>wh zkj-bHi(GhK`LU>QtJKRdH6%m1iJ=}`rzhnHA*C~%h za2HyaG!PNgPeYQG(YM&eHShX|f#Onjr*gqT6n&A(k||ilI+jot&^kCi{4F{|%kexV zmP@IMv7jW6MCvC0?Wwh&J2 zJ3I%(2L-T%H%k~Z%KXQXoDcgn%mOq>kQ=1Ihi$R#TW~zL{;AVS>Aw~MzVRgxAOBuT zVBWB*VQWnFYWdzD6&JIIz#;Zw!t)P8!0-L~|C>+0t6N+xANpPu;$qqo+4n?zOQnL_qp=`3a*^98pMgX3U zKBAx<{ZC?OZY4;qCcq_FGi!0}=kReSGAB>O)&2G|H$YXS$5Fy7UNJ?n1}m%;`VA^tI)OeA5`d*5;3M>%?ivUnMJ_mM1m7n08jP3F`qIAm@bs8|Fx9w0gr_ z00a;-14xz;-`ex9LN=TWzZYn?JfMiMFone(W`wTJl+FrqrwD4J1cav9fjcB?)hLN!V$SziZXo|lE>PLHBl6lj8M_{kPcOeYsi+G?Jvt+HGbaEJ{ z&<<`f0$x;@XxTKeJ=#bu#{K?<=6U4ffLD$%$)e5egDp_)RLB;HZ;RMA-Mw$gMb+S= z7br_j-Q?py({yPA%;Bz#MTQAsdyw;FlY8Bi(AWWI3~vtkDK<+J*|8?CuVkl$y;ZHV zlK@)z6nK6Av6QGy)y+uXDv!Nf9u<=MMk=KMyAk?G_IHuPDsU`1b5rG$+e9yfqesZ8 zDyo+btjIEiN%S4dwg+C0MxbXD4le%#IqFRV=JJ1(?oun(F@cs)D{+vl_=X~6Z2ZHG zc+c8G^QN1o4+yn;9Q7LpIC*=5(BJ>Jd`Q2S8+4Wluo|mgExd<8frGq2rNC8$(}1Dj4XRdpL56}H2JFTt0{da-(>2*Pi$wGyxdb>~ zw;iM$WG-;)9w^NLyNXddJex&)fV;7vAq^a&-N)z+92x`GR;<94G{B26AS+IQ)6BqW z-@P(yn*|;If*m{)GC~Sfp9bn1O$Mp20GbTU;!yR#Be#EZux*wy`GQ4L^5zR_umOJH z-H5=E6INDmH3?ib4BWo}HVAw=Wx+-r{>`#7T@ZJI?onI~bYm=Nga4ih qP!BvXlXJ7Uz%P#Nz%&cIcoFDG(0D1DhGduqV1>`g2J{(=DhB`{{$D2m diff --git a/images/0000_mandelbrot.bmp b/images/0000_mandelbrot.bmp index f82f53dbe4f703af498f1de8af90c34a0be963ab..66ac13d88511dc2ef69fb5c42719d65d918fd3c9 100644 GIT binary patch delta 523 zcmW;IKWGzS7{_rg@7;U3q)mFUy-RG`OT7C3UvLnIAQd7UO4e{wL8XX83#E`HgB=cT z31SC*g<6v#os2d`=_3Tx6ogn14B{Usv`FdTBpF?TzsvA}@AJIy4Da^JM`;^YJjn}r z%#%IkEgz&=yWJ*fiZ6tM@|P?QDuijt(&ie8a=Dz#d*=yl@%-;=nUbR36zZz3OL4TTw7s%H{c z903gvD delta 522 zcmW;IJ!lhg7{_rg|GR(k&#M>PyTqow#8c~ggM$zVqKFU_GMY?<3{pC@P^x6K!@*4v zJLq3%nnXyaMoUrpI0Ro2YNsZ_7bvuG=-{M|F5>STKJa~>XM47m-YQ$Lvp@OjPp64SMGwv!4Cmiu*7sbUaeNM*(~SW@AnHK%H=YBUl-3*`Cwbb7_QXX z7WRQ;AHw(O*Sff?x}8ptI-Ss;+pOR3)6?rwOY_Rz34|d+JAr8C3mXgua`#oFE?eaw zr-uj8-=^$T>WcpJv11kTw@XKh*!VQP9%^QK?i+(<~GU)O!(pqY4yeY^jxw zU%Jmqr4oH^#y_fFxvT9s9EA~DZIAXAWpgSrNski7EMxV0os^VO8DY6xj_zcQHRhGP zW{qX!)isUFWZsO)*YCspaWeFYb`#nyE?X1&o@~ZArz?5md7ag2HTwC@SoU$0ZQHW< zL`zU(&$xVp6^ljsnl|70ymD7;6B3YwV=x9O7>5a3u`T^3Ycv|PF=y$DK;h}SuFK7t n(5SmVZ$TO|a2#wn0Vm-UOu}h6183nJoQDf=k@n}O%FF)%+Puz! diff --git a/images/0001_conv_sobel.bmp b/images/0001_conv_sobel.bmp new file mode 100644 index 0000000000000000000000000000000000000000..20cf46ac440ec3c05ecd4db1e48f11f7f2cbbe55 GIT binary patch literal 786486 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 z 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

@NTrVR$f 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

~xSr@6p3oWiI}zR}XY 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 hq@bk|< z=Tq<4v15&B4%@eHKXKxO_R5tj&Cqa^Crz3(W5$dhe)wVM&Yhop@(E$}!Vd(K-@ku9 zWGwvX(W9eBj|Rf=VH!G#qW1m(bi1G*+ooH$^>fOF^0(X3MZ1HQ@Q zEn;Kx{Q2|2P7)fUoK*`AvZ0)CAR8tN0c=6dWaGw-kcSMbnKNfHq50KUUr`nX4f=TQ z4muzkXv2mLtc*p-gj&3blt;sv7)=czfA-mD)Z`aKLu5WQc`jIySKUNUE=fxJh?`+ z$FY=X;6oe8kSJPn~ZXbY<(qC-L-$SQ^?8%P%u z(cw)N@T^Pp<${acpd5;$Hj*ffb?XKvJ7N&wvH5hA2P`ZW@OV9~Lr5M$UwqyHUhi&2g*} zS)TDa#sXP8Ow)Ez_9$P_7%cP|5{CStC0jw%6%GrE27Lg6G}!@3qcdbq!Id3H78r$c zDxOesMZz<4Y90kk0i?sWGB1-XW@6pCb+bSlpAFQf27p+p(v7_iHtxzhxtedEg@A*f z1`I)A@5qTAl1Gjlfd*Jagg1UvX)!Vlcsi(vg{|SOx8C~TgAZV6P(dWrB{gg--iv$m z=%L|YFBvagx^(;YZ6x=-f||=7BtcBTmLWB$X&1A`=GQ3_d=sh4H3HVhPi59&BUr%!Tvi7PCrpoC zJS|+^5Gk0a3Neu>&L^skvpPsw8J#|;#ZjAt9E^0pe(cyWBZ`zk;V;Tj^J1j{`jm&{ z+=?c8dl$AbAvq_&HyKAD*(X>gEWcR25RGP5XcYM72291osx21NAmdA>GJ>&ia2f}! z;sZ29rD^SXhY}G~_o-6}f;L##LJ8EAbpaE$pim4wfGx-Rqd9w9CQ-OTVdJX~YJ=M9TMp(cPA8W?Vw$!mAO zVj!TbI!qQ6sG=@gY_vzfjg@c^LK54-A{MO3U1kM&gvWNT33dmz;Xk#UXE>p+i+{C6 z9YX^jnb82%IT12N$s=w^1oJVvIX4VQbM}@1zUf(%$^d5zL|U*|l!)k3WUyTq9rOb) z93ZCbyTrv7jMzBO(4~tjmngVAG9h=@=2>Xq%(F)qTSE0mWIZzBQ+;D%AZogVjJeT0 zQU*R{m#EPpA+Ln1H1N&b2TA0-e*JpKjvc)PgVgy1OF?n+h%*oty;CJsj1Q6fvs53Q8zSx{`qHB1xmuFj$y3C@rz;m_U&z;09RYY z*AFfXbINMjvSr)0ZPA`{FTc;8Ju}>`TeqxEc1TsTCGpYkk3aq}VWq69!8dtS4>NNP zC$U%;)}bq!X%NZ=Do9}{3s{B!XpfeZdE*m@nJ16jC6Ush3!meSrb6f9sxDjV zFRr*{NRtP&HxuJB_vrG-8;OwdL(TowUZzye1e~`~5uX981c3pJ9xYR?ut3FDq0si> zOD{@m(9y<8rQY46x&cz~0*yWlc{4FW7hqv%BQ~79#)vQ!GIt@+W$WQqmsGOLT<%#|8#9w-~v z-JYBIn3!={m5*V`9?A#b(11$5s{@t;WQ4b5(0L4i*6vE%oxY)g4>z<_qa+J?0;Ct) zxlN$}noDS?amzUqo+Smq1p>khW6LK6wkAeFEn;FU15qG%&sd_skKU5eE7Pz^z&D4~ zTXd4dU*2(Hn+l{oq~6$p)uMH7x|nMSvK zYqYem*Q$YO$&dj%%psf$N*Vy900*T>)F8t{W|b~F&WhkA#Ca48k4!|gO13b4wMnE7_5y8_w7oycgSj%Ez-&9-0<>L&F3<(1071WC3C$ zB&HD}9;St^kq8!H3@J+x*+CPA(&dR_EcCF3Y-HwwKnm75%V^HFTwt<+ku7sCYtTo$VifRO@bCNv(=7^DFl0%{Zv zlM|XAFd61MMk)pXgP~r)+FV@1q|J#g6-Go{M&}K}G)+ULaw_v^Zj?=rrUnUf!>LZJ z%7nCd^Opg>(Up34CB@!nr66@~x4T)RGp9-daD~Xp9G=#0)ASpxaHPC@M^*&PO}PM( z3x>>LMGeoD4xot$NVR>{S3{tuO+vva1_h=l`-307CLl)Mh&y-gY;?sC|0$S&>SqGx z!#Pc36%SxJnlA}_Q$iu>jCGn6L#nIsS{CJ4p4W z5RvdN9VobxHFWr7PD0JlV#*02O$|ai+Pat+k*0x|=yDkXHwZPTm4j-Lwl5ES6YvML z{L6F*;4P@(kL}i;@1MQ}+3#$owmRQAOn^VUVtyZa)AYvjA?p)Dycd^n%!B}`J5WtUq zyjdV@IV9xGMa`X+5=UqQa;L^~=%LLmHFGN&*kTM{Hu&b?bO#WW!zgb?B z2smpnGyy}qOwuF&=3svzDGl2fS~VN^cxy6H1ra`j#bSdbY^}6FT@dM#5kXR1L`RyW zN&rnSY8MgcY3fa7tybV9D>aHYIyebT z2s4M6hF(Je5Y(!ftvsyeL9Z@>HVUYf6j4)i+->xxVIZ}UAHfnqF-kbpq6}REybYT) zC-`RKK#rvfDMC6_*CqM<`3^!5aO{DPLk=o}+QMn(pn`i)^~#`1`(kF!-XQeSVUll3 zU{phR6HqbI2AaQc@R8ZLgwl-=q=4mCX)>1lg1oz5!Tj_lY8uoG;rxRkhI9VmTy*RL z4TIU(-ev~h^l%bH7N|r35G+UzUUx`hayU$Z1m=|SpH(h6z?`3yalPbm$edh8Sd_Mg zJCKo|B$;A32vZa8^a_VjJQ_s?$qvLYI!$UaMMf|Y2vx}32%G9^e(=p4Am9SHrQs)# zEfAQ{aCc#;#pOcQMEt z*KpK~Wgt_+1aoc@{9g(4GjmX2mnAE??%E`lX1zcFvXw%~-&*h7V*Q z&q|vg3PM0xgIwDh;ihOaqyR<;Q-ER6=7B4;r^$(ou<4kL5O9*&iiJ?>LVlFcE+79_ z2)@a;YuC=|?7}u*U*-XbrB|#QiCTD?2rBkZI;+vzCXtrt7=C#*e6G!+91kC-6h4#IB_x2sHrl!UVn#)_^ zqeqXPK7DG>y?5^(pTgut%ttwYAZMF4;hsHv`cs)Q@qe}8o4n?mLVI9%3wh!V2KvVt)VreIG6HkPcMwRtUTlL(H2jIX~h8c`Ma;{P^+d z)2Eq>pn$4TzVk42$W;0LRgYJ_X&Bx?Z9Rwdz(0uSKgnS_a6Y?nSx zj0!%7yonTthF~bNbj*PQ4`^UA-c86cpDQ}T=S_r9TN0tR+E_CFl>olUtFi7dw0y`w z5;HY0Gx&I8Qa#WfCcyNjpcj$~2*yeoJEmZPE+t(TZ{VV){%D8=_VP@jwzk%93W}0J zA&@RPA_@XzGCBW{NJ9lD1&XZ;+F%!4m7jCtqD^V|R|xnfugHT)!NFu8Ko9*SILd(< z?Ge)J7zJA}26{OHAl`yhm0<2)URX}*4}`TL!`xlSghtftDHW+*_V)t|}%-@4!i3f#*_2ADueTbIg%YzPSi z3gqYtRZ#$%r10-w@Y4lPk4$I^4P}AQrmu_Kl8quA1QCoc{x-l(ghz&CSnxm#~)EX05V2P=hP&hXl5xFu7wb|x-r zxvLdCFa#+R4OWz09_f?tpw}IYBtC2M?a`>O97LCZy?M^a<$IySyE5^q3LYt=Kx)*kQs92AVlu_j|5?B z3$Q^{HTWj4kU~Hy0HCT1RT$bp!R3+Mdx4s7)zTmSqClva*!!f6f3f1Yj*1bo5*_;B zRh)SSHT)fx>R#cdPxtR%po)it%v}scT!@uB?;3t>bnf+NXYo#SXmtiLj zZRsy5gu|l)p_kcSy?Utz>I1BtUc7$+L5_G+@c!kE2~dQJ3G{55ylF@%w1*O(a*B@X z@#M*qn>TNAPDr1`Sct8N8SCN0hu-E8UX}ANRlO{ux))xQpd%@~dYgbOFr_P3uFz~4 zdA*ekzUd(nFg3s%{sSixtOFpfhOQpb(sD!r8j3LVoJ^wZjssy;ydA*$bcTBzufl+i| z=(4Ly1pBxgksA3Y0QL?amtKGUby?0VNFKC9dLF60AU*TW*;G21<2pnw0 zOu%Y{w1oCtLik`HLgF^^gMtL}y&yZ}q+&Cn>7v6BVy^gcCxJN$E68)A?F?f${Kst< z9buAI6hiy#_!*P}Aq8`0YYq}B{{IyJssKN0v0gZU*BUJiCrO~8m&7BXHXO(UGUcpY zBTj(BBsys)UOc$Sk)ND$LP_Y6Hw}jF+6Kx+O$L$ZGB@M0(v`6^$O&1IJbzUS_-2ll zfKq84ER_^XmgWfomgXCdyP^aEh=Cm~F+b(VY0yEG2N|IYGVXbOrVn-|uIv;Z)h@K> znVMX{VK2^*)ZkNLvA0Sn;G2x?RWcf`5Ic{&$yrv$WmVPSn>^gG2p}mtR+mQDSm=cS z!~jTH?gK|DfGVKK`5{MCfQSW!(3le!d8jL_tT@pi*Jg{WY~ATV-kX}ofs^kfcE~9uiY;WIPyA$T(-hi7C08QWB7`a8(`nX6}>@aCAaE zM+-{;3os#!JkE|I$tR}@YhSnS;#A`Jslam7dvZ-mvaux=?kmfS;aE}Z8OoXYVb`S)G)UeFH`V= zp(7u}td|fJU~#u8fC~=D4=cdJ4G*0FokBH4Ep>wqL*|@t9>x~hzS!{3oSX;b&vv6B zpa=kVBf3Csv(TVGpK}wi4IzfH1Wv34BKTJ$^D}d2dyKWy`LkGuaBH+)4LGB*pkS<1 zH{3#0vG52M4G=k{`(guL3Ty$c?d=uAnZr#u6cec=xXOzki~UanmwI*r)n4nj*WTa#JokOw*L~gB^WFQ^`CZevX2DqnXC5{EU&S*o z{+<8(SHXYJyz@A#pxA#3{{FwbFDMv2di1JQt6qKe)#smo{^y^6e*5jWzx(dHnKNg; z_uhL$hYnr1aN#r0JoD98Uw!h)Ctkn&^2;Mej99T^#qs0EhYuhA&O7gTyJpRrEnBwq z>(|f9l`B{N^2;wDfBf+`-+Uv;jvYIeE?v5H>(<|Y|Gj_z{x7}sk}QV~9XfXG*#G{1 z?A*C?^XAR(zyJQ>!-oe97$D>K-+wQc2*3XNtLHg$=E%EX!GdR>eRkTkX@dt3{@{ZT zCQX_Ivt8a!nKA_`NMLy3g%?JS965gc_|>adyFP#Z{MTQ9UG`bCW_|YAXG%MG@F0*U zPoA7Td$!asZ&Q4a$SA()kNs=+;h)$?ATG6V#=#WM&QaTuT=8l#fufC1*#KD_gl1R z@!^Lb$|yAfD5rVz=9p>Nu%X3hQNHYGu~-&TF?vAWc;k)QDs}tz?ez|giV`!QE?Tq* zV+M)_Tro9LP=$){42fD)fk|D{CZAe7aNs~3Vp3a;p_rrvp6dpG_wLye*yzvI)VxrVuB+Ej{w1jr%V5rsWg=K5kt`$lPgd$fn>r>c#1U8K+ zQ{#a5np806h3YijTjiJzvuP?di}of?oM?W=rjfPn$3AQEOGwXV^F;97%ugeVPiFIY0)vLdrZA|OgDH=Unrnxx8n7I}~v4qO%E!Gh=+Tw~1 zYQ02uQHZXo*F^JwumMveT8Pg+;8^m_+6YFl>6kB|wespxs7U3@>h3tHx0p zz|x@z1{<{=tAfBOrZVYMUzAVY2+?0KgmPUAP)!L6K@=WPN=_ho$ijKZMp5vXQwmWO z4)JV@Xcq7>Rj^#cV)QgfmpxRc1P!7<(EwAXXU7DU0*@|LfFZbzu4}7Y@KBm#-LRk< zCu&g$GEq^bN|kG`y;jK_l2D06EdW>}EX9*T$bA`e=Dl@c9Tah1ifh3L^iJ#YbfpeY0t%0Hw17*FV zgjL>o=bc)HAI<|IjRDV=N%We(A8O>Q&KE%x!rOU>HISnVPVNCJdFYX&!A4b!2B8zg zdLzTSZI_V-a7cSbgIrmSEdbEA&ML=| zXXKS&9+`|35&@~Mu*dOKqy#rGO7PGEsR5JJYhn<}5J~D;C?t@d+QrADyvFM0n{U4G z!V8IC?O@=ai#i9bw98y*DCJX&a+Gfu{d+<7+GLkX^;C60`jqydGA}U-;zte|4UWb3 zYMEZEizONg4-s^kxoMC?MjssBX4w(V`8HejEE28Hg^Ij)eaYRhgaK9 zekzreDwHOgdDIAnvq4>;MlXmBerQ#y7qqQS$b^C8NhF=3au8r%BUpsgcmUN#6Xc5i zLZVWyX-R_=C9*gIx~NzIRpFp?BrycfupyJ|v5j_z1!0iAdBK=~M2LJ{#XlcGfBp5BW%kk}oav)lOd|fpQM>g#jmSknkv>|$r5P+qNDZ6#R;mcwDDo-sI5c%! zkMxBAj6a3Yp*6-?AGs?SCQB$(i*Hzzg8{WD5M5|TJ{qwKk2uWP2zT$^J#uB02EjRk zd@vq)%GQR)22;%a=Uiz zw7?79As8{0sqaETYoof9K$)1K0kT29kwLx#3%A!@cOAfv9;ea`H{2kI)DJ%Rp!-9% z%;f2fnG+rdX3+{~$OcETF2Z9#;Fq_4{rbkr6niZ)w?SjNi4Z)XR}&w2-~lD`c5HAV z@db2v83$*F^}Zv}2^IMgggVEB7ku7#6ElJYZci8%w{G29X~@K$LqSY43%`{RY9$QB zj0D=l5?XYPo^$Vk8F+Z6S>|cbstydYF|+1EE?nUaNOPgZl44Rc(;=)@QjwK3NrcAS zT!?@K!OggC;#qg323`c8(gyZqR2@znHzdTkQ~^Kog(Xza7O*4FYJt;G$2Vw$uw8`8 zGE7nqRj3y#v*RsbB(1F$csLX^+I3r;0`U0aDI4)Oat&6I#Yuda)pqp34qub0!WI51f^UKfz+ZFo`y-hnIHs;uOjSO00W$6 z98qFwnYjbcv=SRIX2Al_YLwatlRB0_wFUs!K?RsC!>O}Wq$r7U2JuoI1VeVaEFfBa zXF*_)2ZohEX81I`Ovc@mVof^dWIH4wuk}a}$Iyv`TL`VxapsUSov5T1&@y^cK1Io> z^@_zamb}Qlii{9aFD6040Br#8S<;9HKhgn%1R&LmWPB12)%l1)ks?lzfF&`FiH}Is z4T)J(l#0|M5hb9M-gFjwE^3^9NsR{)?6Q}70po<~0u?8^tWtaJ0tOG1AT`dtHNvEY zB`Z0z2->0DXvYAwcy=AO1lU_hKE zx`1sXcEuG}`0=MhhYljBQ7AmC3xRCS6@I1Cwr$(0)RbZ4;)^flQ<|9#3uw0D*7|$+hA41q5<#>jc?6s2q z705ypSu0&c3zChJjqnh#k#x_16L?^w3S^SEi|E3P>QG94g@uJq)ks%TE6JM)6Rlze zR4;8?sS(IA%Z4Q$r9gR2ka2)eM?_YLjr4|F<715z9PQ|3nC44N-@>?x1(;-F2;msY zw+u)_3Ldt7sz>VeLTYVgGVUJ?f<*4+0COVf9RN^)%~kl16bwPfb=DPhX`+ngJw~dE zn49jSi%6`|6l!NOU^{IE;V1BGv}lmHC?;<8NY~Uw80ZB?cEEtq7Z#!8+U+f2>oSDU zs+2@A!_&NwOA4hamMv4M6hw{$R9nn*bYDB{WoV*#3#5@zMR0O;*o-z9oB?T415B=Z zB&LrDi86IoQdBEJ$yoK)Gr|P{Ko%repF0h4#B|X{4yk1phgixX6;*>uknm8Tbh2|r zNwq*nAlE586OWG+MF1kOP2fpgSQ7QH9tjp05c=_2WPJLF`1M^A0T7Bd_!}NnM0k3^ z5=l!Uu=%!Kx~%WAm=1l{TB@WT!j0iZe#BLa$$ z>a6jl$)xClXeh-bFEt=aftVVQt(2~MJ}hYH^d z1cw%}^r>H-ttS!@sE(Edgv!rET8349#EC>YjtUNA-sue(V<@Z!VU-35QXJS+>RF?; zk}zCXG9ROGJ(8LNO$}h;7-u3RJc5J==WLc@5zc?4u#y`X3hNmu1v-(5P3p@pzuZdF zg;_@cvjygy-S)~Zg^SA3bi3%yL%X??PB{)RhG)z4PEpuE1<`653E@OQQqeC{V-S2~ zW>p6OW+WBIbbA;m@aTtZh?cDpl3~Xoer#kQ$`_x9Ak%9K!3H7Wiaol*d2|h{;<#v_ zsD%lrLZcaoXQQW6B5*2>hhb1W0AlCId4Mv%8PNHg8| zY|x+qDkP$r&Ye5s(~lVLkU4M^OJ=?Wpx2#)b3yGMRzgKrZkPpx7Q+OfWokDvX!n}) zKr5X!gkeksQKO-tH?>eQ{rD+{I5mYKIAX90rxCM&#~M8s{m3H=b9No`5c<^!y{Qgk zYV>0Q@OA-aC4StZvvhmY1+P7n=H8xTONw)URu)h>x{n7M<{P?T7e49Mq2sqBUI(ha z+l~iz0lcpj7=xUx(Jfi}{l_r@36_+jzf@szDUCLY7A=Y>N)TU<%qC9g#B1$C9a~1b zjwlmv>e6>+C9fcKP6K9#SJ;SEXf5+>E|BI^gj>cLye37BQx}EPj55usD>TE$XkH^A zBo(D$hL{neHGXzOyNsw{L6|2G;wN%L>b3`6x=b4oDi9mS6Gfm}DHM5Ik9Is~mVJBj z6^Z0%QdpDjkH3nA+#pC{I(U2d;mpEvFrt!<1CWzwre0kXqIT-C7e5xeZm&5qHLQuu z6k@NH@Tdh&QV|o?6sUJ>7Jl={laNtRa!YVuf6h)0JNMC$pm#&e46eisu6NXQPQnl(3 zXri4FWP>+=wTZ4obIWH9VyTOhQ;WCCv0lBJ!gzJur`m*DqY6JcQI5sF4UsfFqREI` zi>-mg3!jQfSHy-!TU;cGNH_H&4I3x|(npq|0v-c`Wb1P`qPcj`4vR6SJ0Sro*Y(SK z9TCklcz8AxMxGJ`ae5jY2s0^&rE1-w2USqA(v+;@#(^+gWReOI4YxTI?4>st;55AC zZB7}oUBIJ-o?&)9qE(3aT5mh%$p)E_Yo%c!9#tDOP`!4FNhCX32tZGjNhHLqG>sOV zODLa=aiYt#>V?S?$FtVkm75AFv_OBwbkPev)g2MMw$g*?F+j{%X^p19reDw^0?IfAIj z5DCtlogE}WT_nM=U4dCxkSqp?6^kR9E;A_CRVR@G9V^Zecm7ClXzDvM1=nT23!#qf z3+{>Szz2?(qz->>dn+(kKVpDwWP+FLYUa9oGvA$$c{a&lu8%E!wRGMneh~K~emH#>|(E zW`Vq+A`cfub0T2F2;i1ZsK`*@kcN>!D((hhW2c;iW#K`OkcoC>Y8*)$Hy;TlJI269 zon^2YMv+nBqP1&?W~S9!Rak5sRAGFOY#B4O-b72GBi`z} z)L3P|6bm*~V0+8Q_=$294iB~slioiEMNu$EoavA3UXbO8_V}VzPUSIzz2|;0*}jSw^vRCA%vahR_;e35H8} z>jefJXGDT1?QlqeBoP^F4Ylj0k6^=(Pt~h-k#XXgw6u?;wVt#fM-*n9-N;wF2$o?3 zo^UIjCPGC>64`}S@c5@_d`6{s004L_=@d;0W%|4YReuRj?eNQPz1HN0Tz81a`0I%K zQhmpgxm>f>6n7(Lp}uW>v3!9blN&_^R%;AAD?dBG7|hW1tn^TX*>Gc&$+y&OOL7|M&-i zEJ9K4hgF0hGN3W@NYxnjto7y*V6@;3PNkU++TeJ~*Aelh28@}j3p46+#N-ABLft{2 zikw`a0uO*6j{I!p;f#>lsKRWSV*Qx!?*fhte_UXRe)+f2O3Lwr1I!w&G$wBiN;OL_ zY{Z_o&QM2&!kM|nOrGz7N1|$Ho6brP=Py-ItSzAO=X^Vm!Wp4V(Wp+>NEeKnHETL$ z#N_tGW6GQ$Dn%3k6ianVv)I7m1WQWB20mSs+Sx%`GWu1@bv+e9e3H;p8I7C03=<&& zvUHJiI%vJ?7~pHbhOGeZ2@X91qs4tj~r1i?VYQiCKume_?c zw2Zn;HxD5vz-yd@1K9&$i>6>zWNdItfYn04KC+U)R75jI zOv$kzxo}AQI1yYrijo4Q=1~es0d|EZl|Ttn6Bq)`DQzfFH00B{wo0w1tQ84#jvVW) z!907EOCxM_ZsMF3JHbf{B$4%3)y7H$3jh|@G^t1g(}zi1=@HebOP8$?$924kk5bG_ z?FEwQkf>x)3wxByjVSx#5~fKC6}gjVP%1()S9FolS->j&71&~lXx^u2l+K;A2P@NG z^m@%|)N3)jcExrVor7+q`2g$Feaut1d3VKKv}yHLnP^cC_d~MUeHV*6xyiTKk5L@b z-M9v6gx3TEv$M%wFYw^D6Bv1!q4hq(fYc0`nM37;q&V?uzQ3q~X0R#=;6Y>I_K!CQj90=&z*okXs8? z3+n(XU%osA+KXFAIIwbzFy-6gJOVymr@D}U0eO8#Gc1Cr*J}Wz_7-oBW-Fyo2-$0s z)Xc{f8WR=__}$N_&b;s$nv~kJx2A?gkhGW<*aat4s1~1`OpuiZ1kZYhe745?rQp)q zq!xNJO15w(A32YZ28oyk(?uZj?ZzDfjhS)tg-L4U+xjJxEAE8^Ck!Gd7=0lmN$rB) zabvznoljB@@DeG+Mi_`zX)qWZYcv9E<*;bWjEqns`;?C{aF_`d+w28GDR_%f#WSZ> zom?&&D;rfov~D{^23}nRuQiAzB%cDTiF^r@-ZBuqkOEX;b;NB9NWc&k_hUX@yC|Ik z6>ANu0VZ8lK!R|cRcZV|a2r{WHLzhGU4&;DnIYUzV@YINRBFV;hm+EbIhE=W58)fS z={e*8O!>Ma0!V07snRq;77>&~NVK6qj3JX^J;>O32dbp(R0}K&tb{|q-HxjN4-3dXknlVrp@T^FTT_|E|lOZWsCrbj{#uFl04i(JL7mV zK8-x`v7``p1)5Ihrd6*s*VhUy(_cZ9hFdLT>8cZ4Em-x|bx>2dM%(CcLJv)El$AIpj5dhUG7G{ueO9L2y0efEPmn~@2 zQz-+-gKFn7-UmmluW3w7AQw|v&OwcscP*xvXR8vV@ft+6u8?kAlEjwDwMAS#?D{?waa z#*l_XQ|Scc-a`5JLMSqEdPj|-`D3wAwC76%4LAi*;d~@c*_|EiR~+4Uwjvyjf;-9y zQu%06EC3E%@f|=idaY{JRj*#%Hw!JGb5NC_C@rwwrxvpabXmqQ;lJdPOO)U*9lTvd zZl|I0Go6d7W?bNj0G_{M=#e#U@c6c*^q@_A)fFU@mU6r{9ehJCywFoaid!OwR@Hjy zn+}Cjor)|_pe~~bFztiW3oa@<(bAQAt*2qjgp~^({15>B=&Tz>bI0l}!@PD2LcNk^ z86=oDII`oA3lYD`a)W11ZTFFhV=S%^mD?)%ad4O>GlG2Jjes>^%ji%b2BYLY!4W1S zEs4V%^0mTvnpxy%0acqI2ZX+Op@Z(wbk+-gVwX5(Qv~ZVAeZbe;>181e*=pR7ju5` zuE0ix;1&>!bR^{T2b&3VT{B{ucmgp{JFc8cCR*<><5L|(i>CY3$ZM0=Fd)a^XpQSy z3SZQ19%OOLiSG8hYWawlX)vYrSY*qn(Cn z5+}M$0H||CBC=6}Xvha1jXI(<-DJFCD&KI~Wepvo0$SH4;<349HU3cHP-&{x4&5Xe zTa+9d_bE;+Q?iGTcu^)@rL?4WIKe=R?1D(Dr$nWEssKZebVN5{CM06xlXeL!6i7%$ znqviN+D8DcD+&pk1&KuBs1b4t1Xne+V+NVdmmKky5g{+NK(lBM9s;nKAbl#7Pl3S+ zBo=sRmI!jmt|>-LryLWx2NNH1x#+7O*Y7@%> z35{T51Nq|c7N-Vrdbpp|dib>!EksV(-nxgE&QLfb1Z19E5i_7VRUtHqybGmf-vS7I z#6YY8k2Ps4Nhr!JOC++Ed2Pz*SyPxi7$PuOUctn?raf*CHt_nw%>CA6i$l$+WSQ6c z%OR~)wKWbS-)o?Ca6rx&{nVoh1X|30)S`V{&UFE;)L$-&BIG!FJXpF08AGR`XgWp1 z&L;6KCKVE;VbQRptp))kUB;Ux@^iwYB|(e{-VmPOe-LgkwFY=6vd*H*UhwQP6ui+1 z=)ZSRsTzF~(I9J(=_2sXAZLe%Ahu(^R;^l4X^o@7M7szTeS&SM$DrN$v;8X|)9R>t5l!gT1U za-8WPFb0zY%SA#CKEPNCGeIDDnh2G!^n*GjxH&W(4k;hOU=5PZ2%}TJ7w#+EdJ(g* zlH%5U*~@vXK|A-*DOpUq>I@V?LP-4b>K!#YJFK*kW8+7cEim1#aDL*!p3{d3V8G0> z9V1NAFo0wkz_xe|u)HP?`Ib2^WFY`;%3&lpt^!6+(THJ2rzsl2rtDG<{di`-X2-~z zSqvcGb_$W&N-{P_dgs}=i4UP=#tKg3VB#dgyt#t`bqZ80v5Bm?tXLvq&>9f{QwvFG zoWi+~E668H#v-+t(y54yy|zmWWEVs$@lPaR2qm4qfNG;cC#~_ zmW~H1!U<*=quRwc#UA*SQCNAMHWP}B&9kekQq5y#sE=jYSbtK+A-iGw<{*a15~nc zMGI`CM!j=h2#W{OLP$IINK%0*^^6Fn_ivLutlJnJA5pW;7fs4h@!8fr#Zr7bLr|6tV}An!85fN2x1 z_$OvG2oKQ`V!b9Fg?lCdT_7jS$Ym<=fOC_gr-)_9;`kLsBDBz-m6}KS?B2la>G)&c|2{{fmPz@=NRvHkmt+4=d9MZO;h(~qkrA)7JLhxc5 zG`=CJXjCKxo{c{x&=`Akph$-Y%WPMj!j)i1snJHX5R9QShA`Ne8)hr{AtAW}34$-;vm4o%r5MUh`3FlTj%V?6>f z35f@&TEkai5RKAw+zo(bfv3xfFFVXQrx5LMvKSM!Sm|LSDH?pN+Tv)?xEwt&NMWzA zbfVE}WXm+*LB`^9IpC=sa_5%@>4l8CBL!^OHhmU=M;n;*5sPrqdY>9ABco~`NdzI4 zDgidkC|}i5+itORp4o-%X%I5?NJZZI5(g(vya0ocbQ2^ak3(t^pUyQ-eAS5zl4hxe zNN6L4umW`P3LR0h-xl24`^D91fIafYqoXn#q?J_tk!z z@ndIvHwvH zr*P%Ul|^<|f?A!#QAwIg*d($sjn@f$zoX89fc^wF|E;xlG^FxBK&w$wOaVU zqWp^{GiwbSH~X0mE}=mNALq10Cvh7%X^GQPtQi5G?KtAK$<0Paz(=;@R(Hhokd(Yh zo#YG%0|Zr1A=GX$t=CpH0tP(A`p8I{M86S9%_@DF!h?=j?@%LtLv0uV_F7Ei2>|jH z7`JC}W=9GSBTPu9L+@x3U=u{ZoIkf!CK@DGAQU}oncCT`n>|}{_b3;Ii$<9eDNT(k z)fW?r8GOP|4^AIEc#T-JV-laHRZR1u;|3NL8ADZMa!6JptZ}Knk<&5=z)u(`zr8+fS=7Z^6$j$VZTpbF~^uf>|qi(tmKGsul4FdkN7 z&)~a`5Y>^Q_|6W)BAUhd6@n7$y(2TwZ^$`^f;2L!Ejhm zngfe6Wn>Xj(12odvue9^NFx>CT{kRHd5ugfbyI)U>#b;73&Y79LGEdU@ayIoU2_EA!KDrhS^)1QI2+4BNWNWXss5F(Lo%r1qs0B+DA|P^ThAMPQ69sWlnjrK7 zrhJqtL1eE5R3mMeATadHZ7OW2Cg%JjdB&nZXgw<*$%u0I&eQmbpA^nL_gskpFx2*< zS|KPBN=$_Sqpkdnz1zYcc&|ZLrrT6@>MQ_&4RQwf34k7~ft=|H1+bVxIfmeOMVIj@ zDMi6!z*V7qaRg!h2E>2^EDk645n!xd$z-3~W{ypPI^vF6i+QP|#4Ry~bwS}yH?yo{ zBk!Wye)H=VT?A)}$WALxOIp&QJwIw$Y$X$xk$&JovIYDZ3|DlSr2WN2E*KCZyVRPC zWW!6eZrXK_%ZcfQ|ELOb>orbMUd!UGis13ZN;}l+Zo#L%zd(r3-0MOp4i%BTF6avw z%7KK15)WH2(F6=IW^kUfkD{crlKhZt(DX$s@o!SJNiTH46c|GjjZBNtB|F|I(-jXZ znUc=RNMrOs25Q8fdVO(&#{`K9yrvU|A@zMqMUYdM9?`ISel(JzL(^Ok4`9rVow6)L z$AT2%H8Q=`L>Qca00^Z(#qt&)jT`&2#uY>44i;?Wcnt|-AtVFh2(Sh`KBicngJI13 zh)cL4xPZ1`l^QW@vF%#vwIFz~l1t#nv%`pD@xZ95+A}0bW)xJ!G*Bmkj&M<1a;!K^ zm9KEJw^+&6>j*M+L=dClqNK{ip4VvPEr2<{u%Y{C6piV@i7_*-c6_P`V5AA?L2SAp zti~ydd$SJYz+eVVG<_0@fGmP;WkkCg*)?n~I`t&-41g>mcxK?(klkzP@E{Q;twbiY zf*TXd^aUieke|9h5m=El_RF}&TuIc^HK||FQ_)xDk4AS2qnHhZo0$QV)yON4nZgftHxG*|IANz zS+EKcPw>K+I2O_CfkQPCr|y6UB)8Fh!F2}`7;JRs?2CX}*rNkN3DWT&_KQHK!7Gfp zup~iL)WUOcA~*n~t~A|Ljvzcr5ax+(J|>iVV^CR#Xw)v9;MVwk#xf%#d$D50Dpjh) zOF1gTVVGPOliH00S$rc!X$gl*t)YZ+FxM)|(8=oB+l2#YYTX2ZnDGCPkxj10yM+s>nnb2N^9kZt$p@AW^ER`5#uX;G~HO^Qe!3zUoSOB1HCTTHqpgOWduM)69pW>j)GGk5ym_!Q$G-5Ua zFETIUz+8eV2wr?2*@#=hhFN^R<|KA`#uCiT1b9Z|4h_ln z>bs4qfEGoXE~bDY&=nrs*(xec1dL;79I7#iBCxdpNXq2)9&Y@+MH$Z4nvExkD&|-jdis{Tnk!RT<7l(@)bF)eL3a3wTOtdo5 z`K@mEJ~mIHZApQrMm|RIlk!`v8_&MnqwQw@{r2#z>cwxXJfmp~}>E zMs4&Moqr_H&1@H3YY?ax8U!tI0#6=ZquQso8!G`dOYNSSjHGmhD2^dGNPg~6qwC1w zd!VN2)S@qL`~*}No%=|!x}ysCwM;Fj5S#(}ra-P*Oj;b6p7Uc1Dm*MRCV2KQD%~eD zRc8dZLAC$Jme~Bu8UCr=+|{T4WZ)|g^LMlAir-bN@oT)%fEgP;6;Qq2%BviG@yu!E zr5_JE(KuV+H;Eoz*yXRT5HfsckfX;lS64gvA;GDHI`{wVT`jzhKj)9vaw*wI)+5$Y z<;Ndy<#p=$I->-Y3eMzxO2ZCxQbCKVK@wSiy+$wT+Grxw=+@d4>xj21B|JT{T?a{5 z73PjYbXj*y1Yg1x;W<7uNKy~;B9ZON6biS*W0?l=NrodPvAOY}ZK+KkoP3Xvq%bBd zT8|_W)1=tQkhLkcR-;A@r;OCpZV(I?t2gy{2AYnc+m+T;7eqN0T*?O z5xG$j#HR|Bh1kS_S?vbF3utXuh|xof0|IG`R8dA4LR;kGFbV;NCW%uMIRIg>U*nlm z%$OK70Gt}I=_wdKWs(>p3FhfjB#?nw5DH+a)y1HEq-4cvCA7{3TGARe%Pk!QIa{I9 zGIj^2anmLpL8h=A-q_+qMItaLSCAkMoLVN4!L;3jFqno3g3TH>iy0hB#BZU}GFQyI zfP73Az|5xWQHy&IH?CTV77QrLdh?4|=3pK*6aYNn&=nDUih1j4o@XMEkEt5@SW*kB zq0%o>7UFrW&e)d=`ui1I~tO6I#2R;9K^D6P}R~2P%sRTkE|>CJrgbEsB8!LxCs)o4PEJohraq zn!F^f*ND;@t@N|MV)+kKbHvn>T()B(^gsICI_^1iLKL0?N-UP>8T> z*-bu{&?Tm2?9^Wb6yny=pD>&h-ue`2PBcH-K&Tzgrt{7_k3G81RB1cEHaG`y;!A|! zzM%l`6krZ`X2M)o7IBd2Q>lHV3t&s9UlfHOrstkkA33>?fblFNi?&89?ZumzlKS6# zHaIe>R}hv(2|i-)vWwt>TZsfgk+&8|VLjRKRbV7_gsD!YIQO>+AfeR-Z6!z>!BDTf zIr3=F{F=3hL3}XW^U$rxT-QV!3Nmx2VGU`i$oawmrPBqu5Ux9>kId9!JSl|UtS400 zwaF1Cg4q#DU8u$_NLKo4pak>lAfrRq&03h}WB!K+#UU*wfEE^KxFB@g5D!DJ*9$3J zp+;*YQa%9M>LT^Zt48WIiRzU_fh>r|%%I?Ail@auOrqo4coL-YjS`PF*IpC6(J+Y; z(VM(>Da{<23)+?*TA4Hqs=VTx5f9mERqxm--Yh_o6cEyzXwG_My6%K>;Tp1lGa_K25WaPcpQ;}z6s?l`?0_>zftoS&WomCo7T_Z5G z5Ra~@+CbsNTirB(2yqr@hs9Ppj6@(C+-d~T;=wNu?ooLW$1~jotQNg<&j6%C%!PYM z69h2Mc@c!rx;bRFzT-PV6vZo84R`gLBl+QiNAVV3CnIZOgKWe-e?~?edwGIrOaM;U z^a87zXhsySYkDMz@h3km$e)if_%LL4{0{~yNQ2M>4ikr@IdQvWhDY7)nBKEUa&+5 zklOL!@3|l$nSrok3HhChy^_L#O}iPLxzd`uN){T0fezr9eV z>9(HcbHh&?Mv~NR)Kl$a%Q~pJNVb?!Kx+WKAU1}tWKe}-^6rOy>h=IX8ZCli)#$5@ zn3|3j9Wi}i=S!IajfoO;hZq>3nI#p3M51!JC>QX?&0q>bBxV+DZ_%issPJq$bcECm z2uOOzGz2$gY}1HgBe!1AGEA~G^}whXMG7QNZ@C{-93!lHtzLZrz%tK9l3kl+D~+Lw zc!l_0Xiye-ivfAD!Lg;I0l_hkQtNX(83faZUNy?jI$$GpjnHut!iGw{HD4-1Am3v( zg0m6pB|@*a8jTH@*@fVsO60B7?zjXAS8N#xD)O*J()fuvl~KpZiR5tN1%|Q5bJUAi zcz6iwP>~*KguF_2N5qpUoSeYpZ1U;|LiC+;;zS|NQ1J;BE!Z%^79&@j#`2N$FO@XzM}+e#CoB8 z_o$-Tu1xow3IV`t9nlv@BqgB50*j$Vvg&k`>foeaZU&MCZ~}u>?!-0?o&Sc&UTZYS zGBEfUwpFKO?enP-BO!Xg4*-JGCyQfBbj98U2g#7~e^szDS2&xG47FcWXi|63NJ1{+ zpH#F=mQbn?GH`F;BiCt${&mDTLX*P!N0R9B3s1Rn<=oY?aasI-3}NzA!A{LD*o+-u z3y|#Cl9$hrG%MqTI%2W-;=t)2VXDHn03fx4o%-Fz_XwWv9C5h#jmZB>^Vw&g?H45J z1aVf1={FYD;Shnc_yUC=9Abc0eI%DI@Ixd!ZcvI4Abm3uM^QFv1f$km(ZKL&zHR{H zNaagj=%9_DqRuV=fcH=-q55vcX>7OKwt#Kn8 zdrcF?npW~KHL9Qi9GVU*#dm&Dg11`3Knyr(8Baw?A+q@bIA=75Ef691Hr!hfrcIWn z)pd6l%)yu#7I+ZiCd9)UcLMxM3Q9wxq@sCPi3g)$Ftyd`11-$-uPBR|nA&X&tXtaiILDdl#!Ilm) z$xh({;!`3)u*C7$f~t9@?+ZB3Y{HK~@Zj_;5&bY#gDIK;hes=IG5BZ)(z9jw!9Y?t zhQ!fIAi-l+qHzNyn+2A_ZJd%|NTIngUc5=IYo0X@hg_Gn%BVmb!i*p344?(dXBxbe zQM+!XjP@E>#Ucbq(KKCuwTaQ`f(QFTf-AO@fxg3!VI&|>%XCE8oWGD`Kxv}6u1wV- zSqgpRLV}Zlx>Sl!J>}Q}ie{xN7E?Y$hQz}?q_dKzN$sKt*nj~f@<2t-9d~6Kswrtb zmYia!KrHI`9vY>P7ht`hA8ZQWKLAYf=+(S04HC^eD|%}5G> zPvfY_7P)WJBIBHj>HH3;u#_Qika!%IDC%LQpb}3kadq_8vES;@6iMWT_ zb2#G1w*xoo;(pxq6Li zv_NaSFJ-1lQN-g;-Wh=(o-c)u6a@ptItsNL+l*TIN)SzI+5p4X4JvifgD>g6)J_x5 z34mP?(jnOk#X1r@nzH*xSp zOTHg@A&b#-ov^tmwWCy11mt7kP3E9etm%1of zy;7*%6+Q-)IJQKkWRsK2-e}KaPN)PgR1Jnn$BFwliwzMzRm2mh0>cDvSSTF%I-8a91z%JVt_&7y76a>EL52aXT??uWrWp^7AC1) zbE+I$*qu01GjsL2$a%0qz#J7?k;$MH?DN*f|HWfo*T`raH?oU{aJoDvleqQ;6QlWr19{a)l;A zl|rs;rw|?sgTeIRX%+*|a*5U&ENQNb6pM4m76M(NA~C52W5z^q{jv)`bc++Rmx$qD z#&t!>r537#LCK2EF^zEG4V?v)?14JM5Q@Nbjewm{2m}LB+NT|vBVF*3DF$9quz4Z7 zHKxNeaWW;eYlM?RvEq{oCV{79sR7Vg3*7FSbmaixBsILOm`E5nW^9L0BdigeE4qtz z7m9FTe5${oqR3w5OT-D?LkjCB9e2fU+STRG*}b=0Xu*-=w-;3aFM=#yxGOfx3eimt z%yK=<4ohVFn#J(l(z^#X$gI_QG zGFS_gu11K^h*eeoJ{Mc*AHfz3D9Wz&7Rv%3I#TXFSM8UR0o+q$SdK zztpLh!04Yu6=fNr>ar`8APxy(z)wKB!-x!s!d1%a1ouLAYP8Xf1Uc!50WsHx*8{yq z-dpBI6`59RROew5Ne=j6#+BE^U`W}N-sur?424=CWDe%eB$BJ)#i2L$y`8!nx7v!rBpDyHD@TO*EKB2$Yv7MnP^q_)7U>5(od5N06FzIjA1FXe1fXo`Wt ziGE3Bdu$d(l*TArw~|jf9NC?*#ekJMr3(UD53nrO=ob!ie1j@D10)#nk%M9MGI{U- zhL*SBLZkZZceN$sNZ z2xcO&46E|$Cj6L3ms?^CgMlt+(Gov#U4t}56Yb(BXbgug;tESFT^FR2ODJ5KBa2-m zZ1y6i<3>f%vl5g~Z^{A86rc+#*aX*NqroE70-hF=obf>miXs3pLDgS8coxT}$k(r+ z(jX+mCM>jG@Dr`Ur_@HnMU5a!bLTsV!%hm7(uR@L1ts9e?9jIUq8BGr%PZAnq*wI(7#d{eLW%obz# zRBuMcX!BK^Fa!>7kk!byrFJxni9NuaBmPEdrnrQO!?0U%oI5)Z&c`^jF9Lp{@{77% zE?TBu9J+IMuZvh6@sS6s=ln<$)41^;e{rC5dHs#UMz_%N3JWJZG*1c*7DO zMvGI%M!T4~HgPS-lS9gb&593?8(n_O>b;;(RH#A=EXJg&-Sgv9O#fcgapqYls8rz@ z0DcRzX#R^Z+npCSD&N6p4OX>)v8$AYB3x2I5DS>2O5qMBgM$!nK@EFS`=-FkU{;qF zkhic5$2|`XxI(uuxbBKs_Hg78n@{l|6!nrKJ|WRf&BY1!JZ$7Mq(y94C0F;ZHYy>< zgwjksaYCX&W(`6VdF{K5_(UiLp53)-I$`6=&u)$YqmkJWid0Z!jmV}W-^aNbSK$og zXAO92@ssJ09b-ZBlZO%TAci-`d) zLHr6QT6txJ$6MmTgYt7zD!9W!J5y)9P6SQ$W(pL>3F+)K4bE3wMefme|f-fF9KuAo#3bEGa(A$$A?MYm#d zU3W*=NQ<3=QaGda3)_V1{B=NZbs)uIKGdZ&7zDINd}#HQMKd&N(FG5~qB2weG9V&9gNO3eSKUf9isq>G4lA>X%|UJWI63 zUuGG#Ei9eo>b^gKsvTp>@z%4ORZX-Whm4U`x;J)$5U@}dh~P_s>jck`-D69~;&?3f zV9Q@-nXNKi)EAD#Sop1Ffbm|RLTgyu{6mf?zHPg=22FOU=|@JKQ>^30N8B=ID3mOM z03d~%b9e-2^1ez4Xqn;iBZ(`XS-NixH0f}0kO6>d2LbR74hIlas^-r+rKk9(7W3r< zM3g&fqv49qYKMsD8WZ$tchHRa)_Vg5JjRwaR4dESTA%$cknC}OR>z&4_koN z6=HA*k`vyjjvB!hlX5hO8sX6kbElQ4;97XI9$Mt+k>Iw-NRYP3js#@#Fgm$2Y|QM z+v}lNz?hO5@vKHwOX|8r;+RcUAV=>UOqx!gSTei_H?vWKmDmPAy?}XPY(zwmLo8J{lE*Tj>PPMYd`1 z(atnXA6Av4Q{rGu9Q<%LSMUQ#oZnuB;7vgB@cxF<&sS8wOl8P)j4PBQVdlnH6m!V zWkl;!iy`q=F8p|QaH!N??KX+XG|TvRQA}k*;?JObm3}+~_dv25O}9o6zZ(ppqIWJ1{tYnX%DNIth`2 zwg3_*h}7x=PnibO76H}5VUTA0O*ALu_Iku=bC*b#vRFeQ!~?w9!JF?9ju;)GQk{y& zhFG&eMfl|WN;66<(ObX(AS6@6%4BhBYdU%Rl-PvLLq!mh6K~OL2SJLWG>f^nxBQd8 znO~KH0l8r@i9)#vG0{{CGbGLk;x}1Vst%KO88Nv4^Qqy&m0`z;CMw5#@gL7Q&X9uv zBhQGak%y>@s`2JQ+hN8)nN8CKq3e*S5rGzKqQQyHie{O!)q}=jQ|8DK>G0)0R(hrx z&oYv(P*OmsBK)fk102TTWr23XK>xbN(!my1XPu0Cp)^dgDMkvRunK_+U9RYbAOPzi zu7EeWu4{pejsx)ch6nX>W@`w;YbT3O(TSHb*LsQqn|)ywE!OLAP9jS18mpiZ55edcx3OL$SRb7D)Cf-QL^dWEbA_CYlHe5L zwWf$bs}ySiVVel8vE6~IcgRGM*@4h>*hQt{P#_GL!GpGH3S~-7-o^)hLLxAY)?&|c zIhc5hLI{a;JxC5q43N~3Kskz~5aJg>KtWuzOOSjuP`-=oLU#AzTA*;KTy&zj=uDRg z!#L4RpPC5plq|Ss3@ZegV5>{(b&weRG|e`dQmIm<7^Yi&_qvW405HJTiWe`=Y2D4b ztLGki&9s4pUw>`ngt{-9W5UV8pH(WdzR-gbpk-?U@=`0!6bl7FD7m*R{-S z@-aRPr}1~!tu-8ylWQ63EcSxo8zRB=%SOKi$u2KQ7RV?OdOgFbe3An@`V>$)g@7c5 zHaUa5^~<(rU$U%JwJ#~AgFn-QU-J1LhM`g{gf1%C7YdPag(9^ZJrdP=s$i0?i_BCV zZ9Z~>Kx>eFr|<$r7HEMp(WjX7=MViNGut&+95)Z{B1jH1L`bU!L`I`Wgi51W!$kvb zKJz1zf#nH;)3&KMjHHsgU+WFBzR*&Bxzcerfy(4FCd)1i7(HEZMUQkaK|o?nlq|dQ znE;QXX9JFBYzQkj*bK*HiOHkLQWa(#3@&OmS&G+cVOGHFh|=UuBQUI4Zg>nhCG%1< zs2s-YBdW$5etc@iO%UG@!~#xYrRnpqi$3X4OhFJ%e!@by^@-JVU3vT#9?l(06>|VLZapxfv#hW>t>l}T@%VO zb=jyj0%8n)$kk{sR*{S`K~(A@dX*!N6xv~A-_6YSbwz!VE~7sB46QKyn372la((d0~r;dZB7Jw_>^|HQRBl z5E7Z09TBLJmNK;trtY&)b6z#6%Rckeo6U*?vSX+x&{@SHRee6 z>|q#VR#?>zRhS@&ROeGi10FB{usC#!A^?IJ9q&j{GDnj&HUj(!U44k9R z-DBy}r2(_XMGegfuGjF;fGn0NiYgogTC5fagj~#ov-#9d*)+*^*rxklzUQAx78Vxz z7c$~-MIH!be&%I>K6Rt(9+=%Z6woLXAz7nRHb^;K(E*VG=OO%A$ykMomue_^_$4I& zZXaFZz%QKw{TEe!sIiP0GJJnWpgrlhlNSKM>{(v7?kLKAEVpsp6>Bt$$9m1Q&8gpx zbQuPC+{WAJ=3F#ege4LY6mE@l?t!uGt?wF;>xH9`Jwj!#Q1n9>&KEcN77OK5Uom8d zKlX)Ei{W=nID@1>3)}H!2X*<*ST=dgD}v!bfUx z8$-8lxlKB;QLo!nvXP5QDbt~&DY_4&nf2v`H3`l@oGgZjhRv7z4G+kj8h+;|8#C)v zvREkcww)}gm-bAb;M!+i(l7T6l<5lX{ZkP=vj}1X%oa1waD*~|X!ofrM#C(SuAvZ0 zK&3f2h!!UX)*2F}TQ|KTd(Ac#D;FeYNB4c?3gP+4KmnaxNd@+cbLSLw+6>X zDKexS9zX7{aH|(q0!gJlWu8Q9ayb&Kkr9Um#{$U3=P(#EedgCp@h;c33B&x2+C|nT z!PSB^#4;jj=|w+zYliiQ@wbH54zw6^HDm|$QYT4X}P(`byC*}KSG8do;r(-j*vSG@|+CcyMp zWcZ=d7jCAZL4vN_BpU&t9F+7=QKGSZgm|Gq(;<{b2o9}|6O}2zo~G!jxgeg%AVSh~ zabUwgr2$}Ad}Jg6&@6eiz(-Ph>lOldi7W$BA`cNj^;V;C5|7d>%M74&;JL9!ln5$< zIrB~}N;4Gn1FeVJO@vme0*$Pa)xZV@RY)W+wCJJ`9z}8%dnO@GgwqQM1tb6+Q4weG zPd7OaRj_@}-kLZ+t+u#JU1MJ8!4t$K+B9ri3G%QS#A;+A z=nY7+QB?lkdNJgdm3 zj!4xh7Cfb?(Lv1CEO6bS;Fj1o6m?mn3lc%@IM6H)DU1y8I>=@*pcl>*D@F6#X$X%h z=$}*w5_rf#H4g5SCgu_CyVQ%opOsg728g_yD!+xQZ@rZE;l|E9(O zTA)%g23ToW&Fz62m4-C7;5vD$im`VfBbyH~ygc4b$JQyBueXgi0*fCWwgxkNCk7@%!csMKMhK};A|rkhky zjTyUOFsJIp099}{i#14Obj9%yXn|rCjtAY*Xg5gWYk`(gEZ%e$FkloYsc6u8fj6Av zfqRSK1OZYOFmzdKEU*j=R6|H(I*1L|eX9F*i76Mr7)BjPhF8b65?UKoX8~fZ0ndXV zQDnWv2=OU|JVfD0BYSbcKpK_GNS|0G1`8aiCYLv&%dtx~dLbj;;75+~g>}(D*#$q? zT7xluNGD?Yh1@M4=cjhw>mcCXUbsgEz@5MH&5pNBk1ugWx2b+qcQo)WSs1A}l*!Qe z9vV%DT8yn+PBf9-dm};B;yX?JHo_jA8t?<`X4`$Qd33S}rF^9M(Z=sQ6ynhF_Xw^F z53A?2ZtR&Ghu0%_=>Ul1U;@BDI_3XLKnvHxIZu;937E771~T))%razWiofbgl zeT7gv??np@z-(+KWucJs3eLleII#G@3uTwOJdBdrG}L;HM%k^QUgR4+%qWoB$=k${ zv7)$;2)MFRSQvok0H(&L05CmshaU$kx=f#*5*woAwMz>y%&~RQYn*7k2$b)1U~#}G z+%smx_mL8mBaSMN03qKnlGtNbQj4PnYzHvoASw0$z@&iIdy6zNEwh)6&^)(M9l~9K zhl!D#Eett!$74~UOh%__=K@vZ5OsEe2eTT*bQ|C`7|hB?q~Z#HlU-_6vl=>axZp>b z3iL&TEz5{5Jj*2oPm@vm92Q<6#}*9JgHSMALw-~Wp+b#3(NpO^P28<&IpUZ zy8TwqJXtXfBLq${0d}ql3kI&DK(kM8wu=d;O3l=zmtLBG;e$h-%qT45w@w5G#qRQ@ zfbB=yO5k1Gm~%37H|Ltm1gvzO@iI4tri0jq3L9jmiMGJzQ@_u%2vdW0POb~qAX7lF z8L5^zcNn;&7ExW;|%3#xXZ0W4jw*%d~@+GJL2cGH5Kt8ZF=(3V~c{c;@h8szoC3 z^otiM87kvomOa>xvCA5bR)x~g zXwn6t4JC+-A61J_Y)tWvyD|XN(TTPIz%t_)nS|Y^LgIxt%HdO5t6SPs1`@Zr8W_gIvm}in;pXNgB9db zxaWt^YsZ}0QQ=5+N`j#;-il*PED*#+Uk!XS!nvZT+G~aQRZZ0#z-wm9&@h8K!zC{< zxHk~Zxyj#5w@(R-N|ouln+GE%i0>)NH1(2Vo7B}PvQQ+agCg$rQ)*Cl_{a4UbIkVg&*!nKvpB z?WR&FeN>B#3M2rv(2Td{*A<43Pb90InTzZOOdJ*AoM|}G_&=O-Vb21bi_8yg#{-rG z;iZzY0!(_iXzom(!da+Xs?!3PjVC`xi@iFCG*wH@B3xl3Qo90=x-eskmB7}Bf!zrX z18em*E-JXZFr&z`(mZRTlZZtaIxjE)p-?nzE=Yojfgvfn_=bnPz(}D|AkC(k(qApG zMJ-GQ8>$dcn|!2qaO#vFogAd|JhI1J16hl9jrQNmZC}6v6Kt zq)hY0zR<;-F+-)E*%8iBq$1bd19(6}NRZPH(mW)RF4zdh7Hl{~lnFnsJja)Ghk&V0 zd^Zy0jSvdUNQg$WZ&KN-fmlHHxgk zm5X@r*0(3O{fym_p4*})R+|3^eK>`LFgh~Ny?}V2r4+UWo*RJjCNf^Cc3P&9zs_rTs!Cu z2FUe@7-UBv!IO%0SCl!`zSLb-0B3=6OG?4o%G)t#-zaZ43h9joS%8Eb+&l-FinRtmddCe&S~P4p zsl7!AmT;(*o-wSCFr$%3E)9Q&`&XFk%gWWMZ+8t68)9Py}I7{k%Z$3?ycb53;;^@AQD1i zq4okvHe3!H@XJC2lqL=unZHUg!$2g39Db*Oad45ktkG^}sGIh>WB1*`Ta~gVo=HCz zqfsr?!l2BuTVEL9Q5}TdYBA0gjsagQw8<_Nsh1_H)cw440ISQ_jimD>2)D}g>_8OR z0-oca34y1TeA}nW=i~r54&=4W7ZVZ8I0F4F2D4r;OuR`12|}<6iS5oW_$i93WPy(O zPG^=0lE6p{b%?^GPKn7(B;rV}I}`+`F%i(^A~`u|e6#yMPxrFounq)a_zk>q7DfnU zgR$3s|CgH&ewZwRV@rLhuC7)x=R#+oio@vV*C+Xseah@znH1W@*H$9r3nsq52#*?L zyOgm=JMeBX8Z+XHl>mTdy}BR_eHJR3p{@YvFalFBklKr5&W*H1UP_F0`c!K>>ZpY+ z2%r(PFhRg`S)d0O`kMY&3`5H#sMo|`8QDooxzoj1Auzrykhmgh79>YJ)#2AT;94%8 z8jIRfX@mz2A|?ygNQd-6N+ksEKAjcW)($9+;MFeYkJKZzRW%xW?4vb zx1Oq~8c12gZTwMX5b-NPcO+;R+|Z`QEMu213%207tSJWZ!C0p3q7T-ap`t}{on;&d z-U4Z=7d!03MB?^(Y8()4(BYn;<_;P_n+qXX=A%l<$h(l(R4gKZY@$^ZVe2pOR!TVV z#S~g{4FQ5I9%K_6?|NL-NE}XTQhKQ6#cvBc9%&ztnvw!`t*y}20l<0^O|;#IgZQPR zMo@)}6wt{R6M86YYv6!6%rcHws*gV|)Y=_{VNl7eu@WZ204W&Vl)D$3&Jt#dAPo++ zg98c)Mw6l}v-0x7L%pUD?(+g4b5KU! z^*9`$jSucy1~GT|!VS%cW+;bg?%=0n$(og7)BfAGQ6~)Knh`*Ox8a3>?)dMX(E%?F zu3PNGs$Sv;eyh0FoRj5DQ*nVAgq_x+08~Qid}n7q+a? zlG?lDw;=lCA5TIFrtI>ReJTec;%`F1AB!k3!sD&ro&Rl=JHo)Esn>S|kz3}c8|6rf zNwmKUKv-G*T7@BUcO30Lwx}bVA>#~>IkqbqYmC5_m>`TyL1cCa$rg{K=4Ijpi>&2n z8NaR@>yR{di3x+QnIhUO1B56+5{%NsD0IbqAAuljUx@s5MqQw)F*UMb0W@*4CJV!n z<{*!Jfdd5JCCyf&T(bTFVDQ2awpyEC>1z-=K#N>(?OGUYDj#yv0YRdiEJUc|KtbLS zKqtBo8{Dkn&Qo2iWlRuq0Za^M5bG?^sz{E*+K1fn?!^%=bWFbbDDR>Y9t$vps;|A~ z0-%HhAL~UzPskJ(?(04WEzO7nb_unwQIbM+^!6muikD{|C21h%BKswm;?x`usBPjr* ztnooBv2Q#H&jL=}fdko8T*lQ!jxYhH_=yHb%8ewH$^w|Uj};#INHJ_DT*Ocv%}Ti& z6Pkq!CHbp*P~QI)Oa*CV+L zOqqNnwyyQ8@BnU$+JVAippktktCyYX-pbi@V?RTi7Yz%kvNi!`q}LWo2w zO+c~CAOa9}apIMykfFWGq?;6o_`pXWsf&DBV;@|IhY3Ko(i%0bDAqv0M^BYrMJzym z7|KWr5@C%ODG1(5f@VwONf^4uQ+#k!Qnq81_yxd`jF5q4haVZK>2+QH3ZR{#0cU2t z9CY2MAv)c3@Z$q5bwFj7){#ed{M6&DtL51w7f!8uU%yM=&pvuzU}Ql6SOjf#F&8+s zm>`Q8JO%bqiJ?c77)KbH@=h6yPVK{pKEg4%b0K&< zcSp3iBzUOWg&tl6+#vHeS%GnZ?Pg_qsPz15XT}|U%{3Rslc$0=%hXz?*n(s1cm#I7 zj5e{E6izZ+;HoU-;v+IhmMN2z@;f#oEmNX0V)!ysBt_v_roha?*(25Q?Ld$ORxpex z0VI{f$N-~^OaZQ?kCVOup{0Uii-6%ktO!?aD`3uYfj{J(K*5Xkn1-EE}DgG*uK8zA>(yA>0X*l4ws1*KQ;Q0c62D zF@Y@&6cQIyf=p2`LDB(AhzaOH-|PaEN7PStav`b~_^53ywpZ6vS1y=RK4R!7E)wg( zC3qDSBzJ_4L5VACXL5OYL@>3IP@#vzaFzllLn)C|6kyV1vP0;=3uJ}!)b{=oAsAJY zAR@U>^SuOVqhSFk!pJoM^#ykvfl-{QEGpqCE2luP9(6e~=|I5(erj80fhi;CnI?w5 z3q~+mIc%|BLGj8OO6VH_D1^s_0zq59gNvPpk@tF5l`#sK={8m2NV$Oz1T6+|pb4@e zt~5rHQi>US#ove&iN(sKr(mn^M96}6a9>5_LWIe<_CXI^QXP5-D>fx73}}lvpn#yD zAcee_gG579A}*Lk22sgQUkzaS8*Ns4jl_NnBa4Y%ExB?ieVJ3ZCjyp`sAA;es4tDHx$gWA&_x(@!7(hv+#d zqNt9hQTF=T#2p{~NJ3FXh4us!+A<>VzIr%I`y+)fII>V0lf`;$1dGZtA|7Jsnp36M z1-6Ty!rKlgUjh6YLnKL@L1xMrJ&a(l;LS%mjH?dnHXDxc(s1M{3wG&EVCp!XsInlnU2|Du7Yu!8v5_5Y)b)y@TM39-NDy>M6HKg;HK!0j zaaRoVxb4SEd5PVN;By@DY-8}Aa$1J7qD2Qe?iTiT~g5$C7Mp|{TOidlyfvjqVi?EGO+9+|t!jy6oNm=6_`IU0*K$oCoG-yDhLCSiK zpvjStT4n(YvJfgT<>C>`%EXbnxDR<;wH_PU7Z0E)2|IW$RB@5Hp!3~UtDLZ~?H$yR5NZhdrTS=jl*#s7&*|1=l z8XzS4slMzqtiDDAz)~ChWZ$6zn3fR)gc)NX81Z*D5DELTGw}r@Dql&kQEbSgrmmU7 z2gq}0Qh*^6C3=FZRul=jDA%oi=)zh9VG}oNMfd3Fh3Z@ z;3K9fpQJ>{GBK1j`Qlv`Hc~cs#4G^NVU)a-(lub&mxV5djMxAu7^U~fb~<3J*Mmro zOY#Mp;Z8aE#bL=R77G@mna&j|7O|s_iVh#Uj6(2=$4cI14JeLy7oOpQ4_G06_8Ig7 zsg`k{moP^I#ll-9c9SnWF2)B*MAj6byN*K!0xDEm!kM8 z=Os1i8{ejcE63@rx>A zpfGww`*4@fVXqN*QhHO zz9=E0vY;YH9<6Xv6$ye-7zqjhZ>7L2ti%i%F`x?MUCYRQ7O0%jCLMc;su)y3(G4j# zA#74eP@L?0@r%6>6THZQAe|6Ed#q!Y=7<>%N=dpY4|xQR>Puf>k`kwzOgWeG#kF~4 z0Xi`PVV3QMIcsj(2w~$x{6brZ8szGKKh(sbgA_gA; zpb%K-Gv=$Z(kL7~mmt=m7I9-7Ns!h%U@hiRHC0M50GFeRYX@k!&!cPZFv@`hlCFW~ z5re{$ms}bLx*mlX5hQ%zjG3}faU+BHO94aBL+`DNPVhDVt^_j6Sld5Zs}kqPq|PxlqK?c}K)1fU{90<}Tl>js;InsN7OodInbI#mk>k(0KAU5=P6DjV5U zCISv6B%#%g?hfaWVG~wnkVNcd3YaDx;}nu{styn^(q4*9(!s00fX@geXHaP%Y*Mq& zIFCNMprYnxUy-;Yr;zmhLKYB!q46O{PB9rudIyn+j}p@X_ugr3+;xq8dhf!y#|>=!xN)Woe?SXOH{Rh1nCJPX)gB-U(L&c zWg1O`Fs{vs3{x6J$4P{76(kytzIHKA&t14CRC32C654Mjm}6@ z*pO#|EZF6dG}*LXWeFgg?z>3`fN6b=BL=W5nRukki}tY{R@6WYBvg*BRg-WbCr6-` z1jgcd3YZG#Bm9{~W~mv7sv~Tv!Gt!7!OR5_R1r-1 zL|AzEOs>64%`~^&1;%np+@xj;gq^g(!Yr(q6*D_RLPhJ<0vfiY^5|odM8KQoMr#$gWwoMPOzG8e8c;9iSD^CPYqd ze%f_%c3KuB!ia>N8jhqD8k*WsRzFg5`BI)VQy8*yaHYfk96+^fp>ljV>ymNrRFq&t2o!ZIDr#e>lw6HB+f#DXOL3ii3?=V zp=u{Vr~x7|nEwGW8o=#fO&^fiIVb0jI>aqvnlm;yUhr$x@s(9j4PO?FaKFTL*{Nisg{ z#VeF{hlaz%5VR4tvV*4xEn`-*xZ^4NdP-7)K}%Kk6#$GPx7ZkB$^|6{tT@F_lSQkP zAX0Zz5>Td|W1*x(s~M#LM!Ohg~e>6IC(KmOf-d)Z?nK7E&!{85Koo4CNIL% z1%BRP4dy_jd&FNbBJ>ndDI|L1EmhG|?&t+Km6#(FSt;R$nVA1Y z2!t8Y%g&{8)Wv=t@zEI-hI*fF&}y@Qm}8VMbR5^NX`rWqaUSN5*qTRPq>1M16xkt( zuLIPBw$N~zz<9xk4KdJ_qCx}ICIV^FjLaI!Ec=y1T?{pMgo^((jR>$q5L`rZaKVae z^uS>*#G@k=#Tr?YIEQ2j0#hBO!@lg)MKIDqJP|6TQKD+RaA(Ygk7aN+R+PzEoqaI+ zUf=?AU~VInG^vbq4TJnDik<}{NRg8f34+~>aF~~oKV*|cchr<+F<2}|HtqFPfoQLy zL(&kD6bshyu3_;9OL-ThEy{sQPW5bx$F(qkV57jK2^=eB=cyo>f&dpb+Txp_281DC zrO%f&#+G@iaPH$X*o_tM&2%)@jP^=Rm4Pg4b`W07ScfgF=_$TQy>b{Iq>?Nkvyu@9 za*8T)R}pOCl%!C8#K33FcaE~r7PWInf6;*?S3x1b9ZssdM0jzn(*?kq?bJZCQ!VW* z9@VFcqQXfL;*Z5nVpfuRA_*KT0VObqi4!x4q%LF1cLZWCU%^u$EA^}@s{)-t5>NFn z!9i*6psiA3_6{y#Lr=1n$%|7{2Ezp)5i$!L%LLB|9OR-M95SUqaCqm=8e#M0-D#8N z2v6`V5Ix(ulRHN74kFU$oeQc(YUqq>@4%O!vI;;tE_}pB+>@z{@Ijp5<&?F?1S5P= z>}EH(B0TEB2Ae%m<}1`7G8nyks!g~Zv^ z7rH~c8*+?h_mI8``U8r`ctctMHQ3T)!i5_|oyTF24Hagfd0w`#JT4}Hj9MI{80s%3g37{|%Oe*B4EO9>sDFpkJ zN3t-KCgn6x_;uDL@0wbKJOU7%q$UPXXbSD1zB&j*eHm9B#{=}X1xmtDJMlOw>&kQn zMG2I>;!!e*GsrU7RRN5`11~fLfF1-^u>?s@2{>!M;HWQ1k&S)JcUtkm8 zsF~Q{)?o>HM8~eFvGYpkyAYh+N?BNFW$;Z5r|= z22u=C%D|2i3s`KD5{A+^6}Z4L;dMu^DKrc2xbAxpa7=#y-uEqLZ748hHODbQp~a@z zIaOA{Ya(m(qr?$jb5SZ8iC@4Z6b)SmqkH6&GB9 zjlEEHhb*urB;sbVOr>TqBLKk@T|=uuVLdzS+v0HOi@)H-j8|k|>2ZmM;G3INSF&CP z2Yf&W0kl*R>A+T}NJ`}LLUXj|e+ormDc;qb?*}z;fiGZ`1N}2f>7a zuCfzD+*2J@6N^YNmY3MYzBSwlPZqjZ z#d$<9Oi(y?cyZG%;6s3Y2F0fI3IRrpB5pnsJzeb73P90Pa{BKMZ3iakeCzjvkABzD zgoQFwA?eG4jOce?(zPVva!2oj)N$8FT}((Z2YRZ^GP=T<^7wH>3_OxzN`(eYA&Gp+ zCY)K5EX`ri1;M8P_=eB2b5KNeI_cG1;KiDLVBu8m1XB!LQ>W550(ghH5`#rW*m1(d zDCzi(g%pE`3%e+obSSWf(sVG9g1w54s2IqjjjAjKcHAVeAmT!VU@`sxRm|dqA>+D( zidrl@oQE4$$VKn~p?+{s5q5;lDVyTN4SOxtUrGZ#hkT$J+4d?TcdUU|aeX7O5ve%p z$28xbz^Hb}X0Pmg_hHH|2!q{vJuW-Jprr*Ggot5?!>y-4c5Phh5k%6G7J~}{>O~Ui zi3{5$2oNAhUkn8dQZE$ZizxvM+wXhi-2f7vFnkMOA2QZ3i+jSe3rRGBZ<-cE#sYrb zggn^wBgqn^g1SSCT-YuW0)pg1CEcpP2&vcdL_ijl-!4MeFTf@sfh8AOK+F(Gh~O!4 zT}BXellxi>GAe>&e7WzMQgKHO;DBA)6M0;_BUu|+#vBEL8;?c?1Z2t`v7#R0ks1wm zZ0jSN{l;#wz7VY%X>&y+wwi(k>SPOFSPBz5MsFP;g@m6*fMBfT*9BVkJ%|M<_~>OI zVvb5t9*c zo5d>2h%HDWLIaUIC38r2Q~ZoOgg^!rEs!!zgGKdzu}h}5 zumeWvW1y@RMd=k9&R7Hr^z+i}f`=rAkSWP>%>|nPaUlkFyc-`7GdKH!171dGtnHeb zJG$grNLc5-)PSITh{r6aTw@d?o|0BrCmhN9VEdeYU8|$LfS#G#B2fb zm|0-=Vv{>Zam(aFg&eaXCMm9+EvXhF5n=Yd+|*pAhJXcTFGaOjc0?6QblW4#j6ZRL zf)V{CWx-HInr1G51?(U)7t{q$xRw+bw)dNf6kzT?wkVU877$4r0S;%X2t65D&mcfh z!XZR0086aEVk73m6FEVrF4|L5OH*Ki5h{9UB7&^-G`oE9P~@18y2@(YI0mQ!=CEu% zgJjKD4`YZf!Iu70VCBO=^UfpVNenR8IIckiF_TXE*4U!)5lZsmUUgI6{69F{}4I5~?*!3C6ppK=70uouckihQGd$zCLx> zfB;fi<63Oc5EU{*32uT>VmTrq&@dkYq+1hVLdSc6xDPIgga(H2;uXEAI^wCIJN7CW zFM`2X_w^1(PWVt2qy?&JOW2AEEX^P*$*fW3j=cCXK18nZH5An)TwH4_y{5+zk11hu zglx{hprfLcM-RHdHwRY=KzRhOBA7{UEntfD(LgVMNwmx(FWp4M3=|Bme-}JN$ z`1Bu89?2b~rUrfRQL#vqB5Fp>rJ;l;Q#L6c1mw;I&`dE6sDk*f(Il?icuQ5hOIa0Y zh%RslWD^b&6gE7Cr@25%Iv8=muNQdL-EeeM;e4YJt@MpZ0RxS3>_rSKErX{tA&Dfg zLO(J4HUtgWI&u^wiPK8_3Q37+CcTocypmI_kgrbn?vX*jksg;9WTYbu3QSwLpc?{~ zl%nVEZqg+X!H0LqNU(1`$mx-fG)MaM4HMWjiMS@(zWWFa zVt!4Mg2YvUu!CJZsIDA-t+H3}8p;9};#2}*D5yuG<<2s}2n?`5leO)(K-GO_VJl?` zx`~+rZg3kVa8?LLgsSlqJwdY5N=flcFck|cOkM^BPbZm`0?srG)K_@wqFAcng&Q(u z%A|lE3Q-9(cCCY;ZHTK2a1>goqzND{^giPrfss>%W>aiHa3L=cF(Uo++;+nkyy~UI z{?H6$Qx%gDm3{9z&V_u*0<2(8qs%N&FbD*@0O%1k#Ks-J7(!Llmt}vYC8}E9Mu&W- zN*h7y5o{q0E1-F#)^5s$=Ge=N)25>`TEfjnCp3!TLYvSHo$k+zj+zwSk2dtsI3P^~ z7Jyot!n04f7^F_rfG_IJ4#nV9OA<7pI+1!ruQ;+2q=tf&t#nAX-87L%hh!Jdmf|GT zW{{I4cG+t*$V-H>a1&uzRz$=gOKDnvS!p2SCJC}qt-a`yF`@dl z&W6Xycgpzr<}NiJP;EnFD8WxN=VX$2XId5yfAzex!!aW zR}s>=*EQv#hsq~4nrSa&Sf;ep(gJ9^z+oW?!&A!|GyvD~SfmzMA&~^uGEq5B!;nGk z;5di34k0L{m*J`CrWoW^(5w^^SOibRM4~%ba~Ob(l?ZQAh+kB4H1ZxeNKQdPf$c)h8sAM8 z@JbsptEu%qp%a^o*fCHz?FfqS3XK&!LfaZlQys@AQotq+aqS$a97Ka~Ar(ggV_c7@ z4xN+{f;kG(76OICEQ65|vM&bjC@m={kyg-A4588otY)pUDx3;J$wo2Di+5(l3@ai} ztkOC$^FrmM4-inZDP?L5G5G5OvB6wmDyZq??j!pIliE{bmW?*Mwgd{bI`)-0HEU;#QJ4`8)1!fJLg`_CnwYiXl zje9<_4-loH32F^1C5Do8m~vD4=9hSwwM7M`FC;a!*c8AGH!Tpkz*NMH@Cc-)jISh~ znk-7=54bWyEojgUorrb7aop0FPR?vHNZCa0rc#PfBy`f*(p=E6w8cj{ZzEz54ei`G zaMMZK;anZ@jT~ueI%Xnp1Rq^*0TCl;c1c9D&;gYqK{)B_2@Iudmz5@tP-(jt^`J8r zkQ9ZsQ6kQk?%Phyq#yt^_5wJaBpSA}BRrzvtVIoI5rhGYJRC^}m*iqEcS>Uo3z&lj z0B4qQ>c=BFB?y)=PVq$LSPm|M!9fXd4{jD9o9x61F}ozog82v;zMx9>jW7`!PtY(9 zz~#3ii!t9ye>kR@<^@yMgH8(Un0E@QSwL!wT5|^n%lsFae!rgtfe#bCU^GjRToYv4 z?vAKzVZnWjLOEo!ST34c7hsOWA%Bvk@6|AkSt>XjELtZHYz1}p`m2{TOw+N75pkqDyoBMwJC|lrkK_< z(^)1~N~Sll-9bo3&~UPHA!X+}WmSDQiKJ+$9I4cF%*tBT3^l{T2@201MAT`OoxD^!@%Hye308f?+6R21@}qQHbpnnHpq%88I) zu|X2TmSHJ<$U7dA9RaA3jwaL;7>B(GngrPqp}t@TZyn&dzz1rhE*e=ir3QKNvuT7$ z%^I9{+=Y@9K*TZ`K?Wi(JWL~KsOzJ%rSK2{bfC737q0nb5b+p?iNwhfn{pQ|hIO?`R-9;_Myf ziX~-RfX1dRpyrM~Yb&v_FCGGpE$Ae7i6FBJ%jmN(>}nPm3VMM&da4lOXM&#w?r0EudAAX^^1@j3 z!z)a-%)8*lhFUx-fqdO}Z0^*pp!$opaHOLt0Gn2-0-+YUZql>RR%K^eH~sRXkyY8@ zoaXe;y2y)asSBlY66Taa69n0)jWRjjOID9#g%(yC+XY4hw7gRvxwF81Y@w(;3X%l@ zn|%=mX<GlHiTz6%guDrzu+jsQ9kdbf24; zFHPVDhOQM$z^daBFCx@y@+I{);sA)aRxBqX{VOB8B&Luh}OH zrIDK9A`GF*)Ym}XX*&ya-k>i${J|(TiJO*JR@Rt;AuuH+U@ILMRKx1 zt1i?@?xJ-;44tyHJSeOexlW{Uv?AXiiOP8Hg;Lh)Q3>-HB?v81tULxTX{eAQ7NTdh!XFr>18E6MXTK)U>6GxFH-IC;T9bRzqX5V&fD zA(E$Zq|bbb4W#_a#dkUd1)vKm#~nb3Fh}BaU%;wtFYZYba{xum+1HOQ6r%X~g}M4F zlly{nA$Q4AOCOb0gs961=|(CkOHGMgpd6GOEilId;qk7Z>gzs6NgP#xEIdG2ZyC7w zvPLZ!0ShR|y8x%1$C|?8I)w|MFX7>1dqJ{C!|Uy0Q`gu3yyitZ?2^y z7`iP&yL|K_iFz5-*rihbD>3w3^J-7p3uLbG${m);f;+@YQEZVBDPUY3eN(W7W{ORx zv&Ju_qCb=z8Wuyz0z4N0K&-dD;H{@EBC&vs6Dz9A7YlruTsY=PX}$wlzgvT)DlQnb zQBY@cY6%BtNRa9jQs$}Hln*jypIxFAp``4k!&Yiu&F%s+e7PXY$^>D02uUd@Iy6iu zh$)8=fG2qF0FMcl$xglKuw~Mh7q~PFJGSUPW*KplHAxB25Rs8`3mZhf4M|Oq5^tcR z1l5%|?y*4q3?NfpQe=l}>A|{#YZ=KGjjb_3fQb*-;@!`EMxGK7E`FtNJtX-uk`%%a zak6q4F!V*`54bYIPj(z0;Ovp`q%T^vP$naup{Bid+c@hF_VrjG!0wBphqMtD?Bv_xakI$MH*1szK62nfHLg0xCm%UBEz z<@2IusRZM!Ng2E%XCGF4k$qVR*w}T`9c5w}N;I5?ST7PSBWqQF3Bd<4{d8gMiWyfy zYCB)ygE^qKm@XM2Vpd-{YGi6DFnfXHCPg(&?V>^w5f$&&2vudpEH!$I3993Q;xQsw z9RwWu>|2SESC2rIM~i*b1=rkSWk04}U3u7a9;#9S3-Ylcv1nOUf*hf@NyNvKb0$c;W_wdIuY2 zXPJrsS!`BP2Bl$;w5p5Na2P-aiswR86Eq+|OoYnhrzi32ES7PbS>L9Kq-zZ*PN!LF z;Ke>)A^{rqYG++$uLNNTG%f%xJljcu>QHqTw&-H$IscO*^d@e8_{DH4VWOZi)jMXTyxg%qa#DNrc5+Y-6on5N(Y&5YN~VSg zdSIzgi6O2kt_84?FE3)2vW=F}!-SE2WUxi0xRA9f2tx`+4|tWsyL14nI-roIe3eq^ znYCBwRV10R1Q%&k*s3^#0CBo=3y+e7JvVh3=O0Hkf zMwtvO!pjj2k(x+Iy_@=lmRg2vEYMz>$$}g`Wl#WG81h6+i;+bAZn3&+L+zk3l@c<> zjqjllW`WM2lSWPzJ%XhBve1$;!Zg?X(h%FZlei1=VwYNrpBI&K@`Q#3vXFvnb@Yg9 z2+#x~CNLju5wPH?ihy~cKQvHYECbU<)5=X6A}@Rf4taqpJVodNAPPY$;e!~~2%Z-o z=?3Ts0t`vCm3BFuD!rQ`bU|8eM<)oVf;b73u&vZMloOaB!a6p|n%KaieUO24lOq%& z7YUc}+|<81g;T`ll(?DijNm7%yi-no0S*dVK%sWx!Eh&Pv8fuI0Z8{m1$kS91VE2i zP)dC&W(med@`4!F=vJo)lSt%x$4d0Ze`!ih9i_k*d`KdYE#t^U1E!Q2G=0QlBl{Mc zCX%&M;>xXyl~`$Tl6|R3&;&7YKqOQ;8KcC^teClGO|np8M~!$#OKt0;cY)DKcg$Sz z$Vih~=H29#DTovco|pq|m}?^Z5t|0rs+bIOtggYWCn*{bdfM3$6onw;oUff}4$A}_ zGTM`3AU5bY(&h#MDG&zZtl>F5p{VkuT7D4lky)H!U)B~7$xh?=029FoTScgfzEf7l zS!UmKpr4MfWCym6nskYG#wicx2+vC*2A^GO<&Grw;-Ol25sF-^6dK?^G|&K6o{g$FxD1W5jO!_hOua4 zjR5F?fa!yFU<86w%gV#6)9yq>mWXo{-Ts(jk(FN487_^7g%nCTwNri{hFWCy}_$%r1Z41AUqLdpv09d{@!W;*{C}oy4MQkCb(&@V=JNiy|U{nYLjzkN| z7T&qQ79$Byuoo2}Y<5#`>)k;HrlccBV@M*A!MqT*c-$AWGMT@^wijY{>3UQYkI1#2 zAW^i#2DADRA@v$egv6bbmAo9!xRABlX;RDZ$9H)j`K9f2fc7S&?mH_IH!CSaKfF*m z_R~p6Yx-a#nIFKv75(pL31ZII>nE+xhdPtLWkP#Pl`FCS^j7aG1yi zXxc#v>W)_$P^c2543yacmDD2)BngiAc|uHYd-i2qh$%RdRQQaVb8^+4TwV8-Q?9*iVf~cdEf?}Y88hrBAM3(t2 z#T`aGH6>LL0w6_H3F=MVfxQT(Z6RPI<~R&sl)z-?BiS*MFjfGv01=YFN=VR9IPcPQ zM_mLjYvCaPY!yiJsxma_u=2@WJYE3Q{_<5R0*;?Xq`-WcwbCv?nQ`11-*SPNo7}mH zR9M7_e{onA*b8B6BnuPa5kUGz+dWl8EvDSex~Xw=y58YYHxV3YTf-(Jy46J1hH%!f zTzChvF^AP6sl>jg%g9L*L;}VEh&92{)wQFwDWEg}G>jmOa@0TbuZ`Vh|NfltvMWEpSl9tigGalOSRRZPwUc)!+N(hj!!Ch|8M~BKy_8iG z2^+{J7rF%YwMX&=tYwA>ckUciG)L?oh; zJF~0-*@fyGF6>*Q3aIE8Ldi0&cW~Z%RL4ozXiP&$0aPJlfug7LERz&kq$SA(772r} zLxbQGE2nFKQ(fEHR3IX(-w6(?09>0J2O>u~nm~jB#R8sKmiAIPj)(wmSl|)yR}_|Q zB>O~Cgfv!oj7Uc#h}L%fWsW#J%R<)TG*f)kp2_whL%9kicpU0XvR5fEM81Oc@518v<^cJ6fI;YXQlML4=~kKw2lT&fak29r0Q5D=SZQR-QH`u; zheQ*39V*~j0K2r9UI4hKdWsI{9lqqn9dIOzPB0OeutDKkp(V(dDu_)mY6nueb0h#% zcY#9cXuL|+{4xkwa~d~NMMw%99;}gRkSerO1h~Mip|I4vuo9l)!BTV*j7L181Spi7 zL3z=6L}E&WaHPe3muu@B}PtjFVP{FmceHUyj5~lz>K@o#n6&EvhMj?jE%1g}DPnq1O-o}Ap zml_m|^zj@+EVhNdVW67YLQf=O8SsVbog#_BN`>~ao+Ih-j#T7GUO~qN4od;B6ptNA ziGe#|OA}V)(S6`ZP(}P$2U|yLSxD1*E~II@jW9<)FMCyrBcU2VUbb6cy(}mL1#9L) z##m=frz;%JsHW63U3%-j(IziWjYvh5n!RGy6j&#IRkIfe(n+peP*;`#%Y~36#T0B+ z94mxJd;5kZFnd`5h<7Vxs@=L1LMyqp zSZixTMKR;xKpTBe$432&|71tYQw*JmCp1bUBa*!Yfqe;TY86y>!Dv;oL$Bzh>JVyb zZx(23N8H5FBZ43ujZDE%68sP4jBT8(6((5MQSxI6!&7%;!MHo_BbfA|YV_+AVwvCfRDJOlz`>e zw*bhv5S~n7Wv@q&G2=)*D6EGnZYY5nVHR*L3=8zBPIvCr1xiUVMD8O6utuk#0qxbE zWY+o9yB6loQQUp9qs_G;74@=_?xCU-#P6MSFsM@2kT~k(BBj;%?wc2me=a~l`sgYd z)Y{tL9b1r@7ZvAPYSN^-=)sgQ1jZwta7q|mFrppY)RGEhcv(+^99bzFn)yaUb_g2R zR7=VVYTO8nk`Pa&VOA+Ip&(g6Y$qb2T1i+~5Exd7M3sfjk&V1VTjMYVc?h`nly}=D z4rH2UnU{uQkc^NlaxH!*RY8hP808CGx}yCh99%#T6U!ST9+SLeHtgF7?tE z1_4-jR1WBgs)Hh%YAsY3Xd^uD9_e@OkNGq~MF_7()=FqES@xj;3IamREtVQsG&02& zkM&a4LGr=`{pd#$4DljymVw<5DI94=EpIOP=!=75(HyypS->vR1in}x>BbGvy!*+T zDRy`v6x!1)FfyDfNvoAyLy~lhS!sM^%@*ze3pat;LVmG@o*<7L4Kk9hh+>u_1l(be zYSBFcNFs=d^R_kW%cDZT&D^(#b^baA-balyVag^pgv;@j+2=MpSUH z*oEODY=SR-rtrrxKneht7YncjrU)e~9vn6Tlpe^6#e!&^#sQ+xrU@wEj59!M7PA0< z#j@AWID8@J^E^^EFZ?q<5@Cb|AY-xsK+U!6>N^6iK#C=6lSSl^6rKk3B5rzz_!A8l z)KTz?4v?B1!m@VLBQc|#PG=t`T1iKvQ^G49PW3E8nxqI%^JPtQ1TPC5gCTzEU%=8O zRTI1>^$Wj&!mpmyiQJhuG)im+f$$Vvc-FYXLj7*9q9&PbQPjdsBUk{C=K^9d7mR2vh8PQ^QeJ2&DhmWJHcT*aEapYN;_)jSy;Tvwy1-af)4(FNCoSfWR?;z{`cv zX6lJVv;rgGJi%%RtXu4hC5&V=02D2s7kI)(x}^p88kW0K95vv4R4hStO@>a-<}1ruGyG zCn=90p{I|GJnM}B((Mr_RGE_0swoL1=@J(_ntFo7J!Vl90`5zP!mEoM)l~2tG35@0 zq%B-fZ25XYLtS)}0C3y%bgg%sgc&D~DkvklOI85RODu$p*!bmmUJV zv`0d-Pj@@5c|_}w3Jn; z-6<5Kw328oR%lre2QA5h9cp^3Q77~Mev5)zftS`KKYB3kC$tG=~X5>Ziw8$g1Rt^iKKt%~r!eu*& zBQlQJ1`3ev@(xDL3lo+xR(IRQ4yTS54$|PY*J#kNvS8T)Uu`6))=Vjlk2V59$~F>k ze@);^vNEbG2{`mjMm1st2<$K@S|vtu>G&vMLxchgPq7MBWfhSN1(i54H#LOksVW$q z#*mxF1il%F+EWRDPk$t6rP<+~cV0XNFF}(UgaxCH%GCHq755Dqi{S$b_@GlSVDcyh zi_sXQVt_d#0ya=^Rmw6|S?ORkS!PX+kUD48JGRr^Yl^s5d#{k#Jd{ ziL{0mhJ#+_0y!-f2EfT(i5LoIu9slo!yV-TtluBfC`VAqf<;*&iC56`RJ0ma;h>Fp zAc8i_&`<~$YN0f&DKT1hP6jenbZir_yZ{JQS*RBmv>k4VKUfe?V8$Y@c6x*f3^7^Z zG#AQYK2o7H@n=@!2phsW*BSs?W(Z=Aegunx>U1{^LGgPxs!R|yk_D_FV2v1nAX$_U zGt8;6UXUF{5*v|+nADW4c7v@T`SMh2_@{c5*u;duoD-cSVm;cxN|~_8UQD2+#<8bX z3Q3#{VxS;3phPH7GA=tpp;NS>m*(RrWD&F7KCAxbeI>0#Wo@>JgKM}Nd*3` zy#yvfaYBO^%jg4}va=rMW{@zL6_4rQ0?twcf-R5%zAU8RT7i%pEOY~sf|r#3ijD60 z+9GjR=vc(3CIL#L%!7gSMT^49YrTtCKzxm?7j^d^Li4Tu@pNvpl zurNp=m4-~wE-hezM%II{(u^8*t>rxCj7qxog8N!n@UoyG#D|P%VZuwJXt>t%VzXWb zf;E~>`LwxzR8N82G!c9sHxvX$uEoZe3xJa;vS61?o%)=XwKjwSPVHGHePW<&;_=Q7 zU+xeM@03$`6a%+e=nspO%~23_xzi@3TQ$*)S+jt^8hrN3!b)8%7we&I;pslX5hoHV z9}BVryTL(bF%T7jiOFU+>878$5>Q%LNXDhyEmIu?(*kI3xEOp!C*$%Z2(oZTL4k~Z zMq4inNDcadOI`vH6)$d@q|hK5EVwCIB~t< z-7i8aLM>^j1Thvq_$F)VyJG=$5*XUUiU1l(ie*r8O0NKA8Av57Lcv((CQJZA;s_eE z0B6AuL=cvpUZdfZP5cIsyvQ=Y&|p?PMvugyVX=zK)OQmgYm=s-1X(~o)mLH{_5zku z0K(Z~0K!%zoC4fNc`=L0Vn#y9*u|GMrW?$a6?!mqPOuAx400hmts!h6!%#zcVYsM7 zC{0k45s7GgT_X}NiCZriAwd9baDcFT_mm^x4x2DQ|Ex)^UsG?lY} zWu{!S?8N2pDF99#dZpuna3PPzw2@Xj0x1)9B46Yh@){fKUZ)l$IT9tcM0` zR?>&WncUGvHc?cx4AOit$Xeu((R6g%OF==3V@!!qrYxh7jBJ#Vm0l(+G>i;{vC^@h zuqZ1FCWvS$ywhxgCMjW%Un`+xm(+BSr^bhVlnWR|q82pPY(+mf$i+>&$Rib1Up)&e z-aS=o3Ca|2c~mJLSwJ&LMq}ytib}4b;SR(I0vE!TRKo}lm4-ETEFiX05HQ3luG5GX zz&9H)${LIkHgV8RVz5QbN-u0B1C$A+^qhKxGe`vxTc|y{q7!=Sn01u1yv1f)f5bIKiQ!T})MNzfU?gmp(X>5SY(rA?eLiK?!LxtH|WL2{#+Fls>c z;{7^zFb$X9iKZRunab8v&P^q#V4xJN8>Zw+S_Bc~@WW zo`RQ6(K;0n1PS_83b);)(l8-_;&f9aW{t2-G>c`#HFuOB2=1UI3jjwjkKhb&cf_fJ z5=0>pcKBB>*2Lxye2FKy;1uB{u4iSf5Y`Ju-{Gfaj;3}=AJZJ{Neblk3k0x;1)`zQ z9{H}#sU8>1d7hLF&*_ZxX`OBYCVo0`X;1x2c9^w=%zBqNzm;AXuGz8OQ{qD<#Dims zqTEW%CSN*~W!`NesyIVT^kYL!%p~1Atzb~IvtT1}AgvNNK3LN}lHyKBIQ|i+w2^cW zUKFD6Sc*1Qvdn^O@1AN^HgzBH?P8D)bE*UuvnEqaV8K&4S}#tygF>{hHL9>$*dhU} z;C%$FEneznqm^2QqMD)5HXjwpd?DJ#7G)C&l_Tu%fuW}&L2sqkn{LX^8j@%|kL-)z z0*VR;l(U6d9Va~BdPMGJ!09Q~fu4$sgt}@L;w%`wrlt~i69_P*45Wr}L(?uK=T~pq zE)m!0q}?cnAVnn|!OK^bDTY&oNMNhn46>uRf&rRcq#j-410K5BksxBg7V?skFIC~l z5ck#37ST!^KY{FpnT|aiIfvK_BE=O6{TDxO;1zZC=VQ5xoG1|o8YoRkX><3ro_w9| z*hgdCiaaXlTDMXLk zSjJciL&hlpvlIqVH8L*(GeiW&zP!Y5qdP2^T*Qs91k*I+(IXZl2n`o3OMx}zhLtTM z0W3meJH6`Ft%z~$iV8L4; zz}ZLV1lPtc?@9?V1XCdSDlYq`xohs2;*n)w8VXi&EfF3)wOxejXfH<+r@9g(M*;>c z7lKqEdqwD)efKd+Wm&)k7jAlFmmcv^0J6X?_Q{c8OsJr#f?a)wqD39n^OwGnN|GofO(aveb*!an zaE1dh^bR`d6If6aA`rPStrVILyW`c2gQ@`h$|o35bB);eX|8DpLrPI#NW|C|lE8GX zp0yrVQBDbr5}A6JwHhHCxYQ_XL=3O2BqrjQtT`?c_DMAS&0i?7Z63 z!~DSL!$koQq{yC$KQ`PGGSNb+mGIQ_Cov8g(3)}Fro!Y>M7eRuBYkqYT zwBvy6K*tD4w2Wl2BQWs7N*Gp#E%l=h#tKu6&~veg#Mpv492~iMq!CTi*e$0&m@&e{i65Kq9k$At^puEg|Ntu015PIV;( zpgH7)dLzF>6kYX{OmQt>a)f(~+iMwV(!Az{!s$?XpkWG1(PmG5K00TK)Nk=kwZp0TwpJE4C1+p01c6>l(KIhf>CJj zY8V!AW+m&7JAc#PT|O2gT@g>sK_rie(f?i*ny|- zN|0fh7Fi%L0o%x$cME`{E1`iTybx_saq7H{C)AI^BO7qYuPo%wo#275`qq2r0&aND z6qa&o8k%uTDVD6^V6spNi?t+#m4Lc}o#dK6B4Hn5Vq6GYk5RAMd zw)ma)#Re1B#7uY*z+R5XI6;OFB!vwh2KkkU(>0qyWm9)>r*Kw6Ko+vL5>b7{P(cL( zwbU4at)c9t#cbzV;w%e8O-+CA{U{_Dr9>?oEdV|w34#D*WS2&BP}f1mC`_mg=0HP> z=@(~3@o)_{%Sej8Q#-W8C7s1S@48IyTc%$$5fQ*`@?vz7aZud(8_xw}vA>LCBY?JEUo6aS9T*Q42D}rycgSogg3yd{Q~m!LI>I1_j17oFynbQ-*OdAP?DL8QE;)R}4}n*Hn%dM=3a4W0=x6 zz7U=f>%|Xw0fXIQ0cb1f@WqbUz^-{Ip_?jbja|MhfI^Np9)#Kb5mbHOIG{o1*0o1go*hO^+oqPY0%Babj- zjUC3>BwW^DS_7BT`h`}UwAXrJlPq21Kt%`KC-P2i0`O7yNs49h>qoe`ug#_Gju)N5 z6Bk54M?(oopsGFLpt2%IEiX>x#Sy)tFg{w$zIFyOZBY)oK}n>bl(uMLis2Yc-lZ>E zk-&{plE?!7`02w1F~m#@NSs~5laxEefrc1%3=%`am_G9&HjCX95&*HwM_AEw677x` zvp~?-3gN{OGMG4;*O(BM$)b6wN<7g@j;~hA#WiaL(91Ph1|YR}6K&kY1bamSbCLT! z2R>+wWAxb(nry;K1+fvBP!*~mtp}h-&_HZ)8YPOtATR1GHDODZM^v;CV`Zu-)WsbF zi|wF8X|7$zH-gWUk8T14!IYIunK&rHohjg>H9EqOb<;Z>j3;3*NRaUv;7w#`2-rlh zz$9W*aZ!TcJ7>D7QXth$AO)=Lj2pOyo%?`=Ih3GnK&Yl1b)`oz5e#z57vQ>Hn`lXe z761+?fzzX?WI@wVh4Rp07uM5N4FV`a0US_i6nq$=gyN_F7Sl#S z(mB4yZx`pasYSF%2R-D~Z1})twHAhqjm?BwA`#;*UeSIR#HFvJ$V{ zl!aWVtEQ$lwnLR`9Hu|uB?5S2fGU}Z=B8^J3m=i-7|={TVPxMdGa#Vt8xVL^3ISvX zbJZ6lgk|Ac*dEE(@CA_z*66=o9LZXZ0OgJpgbi2~l$r=Nh@=Reo3b$SBqC*i(_uh? zi@dPlKb6mmHRz-hfo~`fLqwGkeUPaHQCDojrjYthv6R&T55g1@Zo>A6YeFqx_|9~EKW+|ftkXSsd{2ywG4D(Y4nSw=I!dKVsRz{E_$&O}fkrG%J` zRw4`{sjRW6K#dacqNUQy5mx@uI9$?SZZ_}a3lLJHQp!P!ov$pWA(R{-7W)V#Df7az z6yU7T8k~}_k+}k0Wk8YZM8fv^g-AZMma>KI-t-Iw1S2RMqcg^H?j3YH}8ZOKZzuwa^%0-&pGQd-#ZC`ftHUUR|CBvWRUW*N@n!2$UKN)x%M z3huk(v?f$8WNn!S@zO^uXf%20cNZ4xAPQznJu4|)qjR+YS)eUWj~L57g(o{^2UFlB zN0RkUSacJQJ`yhC#*1Yt%e#yylJ4W35%^0`*PAjV+PvT$X*DdRA~l>6E5|b{^*boI z)62#gSr&}2MFK7(&zNqdPBfKtrK2ynq(gRGxbL?S@iSttn~VTNsGL#;B12Dj6byMM zBXrdQ@koujGHX<^VyW>DA6q0FmEtR39bml^wI{VvF91qHgkTqS0IZ>${J5 zNHMfQs?$k5-NRMOoJ&RGK9)jHvf$D+F!WJsSiv=!vY-S(fnY2!4rHb0OQw`o@Z7ne z-kj1;!;u#NikylX;&|(+`$XPNB@mL4=c7_02{y9E6yD-9x@Jwu@I-y7imati5D-c5 zA`DE9SOyWSticdr@XH$MmKwPRl-58zGf}PKz#ZfnQ8L)V3Sq=!mval!O4FHNnIK~v zxKMrP1dP)40BI5d1d(()ze=ovY|0KM7U)DBN;HJ6ul>BwFL=G{KKce#lMZ$5a!0bd zSX(Is8i-Io;<=5KA5d;OWGV_H#Eqc2@4QFDDXKFqkpwTj#RL)!8O1^p_hrYRlO8Y$ zrVD{Nni4nGs5d@a$)LKEEC^UH3z8yWAHm24;RT=t1c5R*g(mO1-np@AHnWt*xk4jI zO(gwVoe00W*#g9vM%3sgxKf z4?z|@?xC@&b;5K&qA-l}#Xef%07|Pq8bDR==r{}9q;JF%y{*x?Buh(ba3BC&7`mVz zKnT8TlERUm0xU9Xc^|c^OkEI9fc)`vcd4ptK@>*Q1~EYi0V662d9exhzg2r|K8Ozz zleOw+)~s1e&pG#MMj9@CddmyT`p#DXkJN=+K%O1cVj0bd4>1u5wP!YIvRwcQYFC{L zXYgV#Qptseq`F`+FA_;Ku#sHI1#=kW2(YBpGS)oR&Qz56GsTFf0-z*1kt705ri9cu zz+~^pFY&&!Fzcw#j=lO6k^*L&FtRCN;wC{hsT0KmIDG?)$gSrTwcON1z*nYz^gt4v z^e#0}Kn(JP-5pVZ7pbTm%PiOqdDs$3V}huvAV!f;+l6O~EMz1*eu=*!!lr0F5-r{E ze^G4o>k(?HtvrRBj}HGBYrEVL&mD*nQs07`Txjp3cQzp}HiQAS^s!pjsth#wat8`G z4N#$?qVGhWqKB7^Aj6l68|auUR5kTT*nrZWpk@&M7%OXlKzN>K{|dk|ybUxNfrT}= zAq?ert=~mRkQH4O7%_=thm%5;l-Ov6qB9~pkr=$ z76uJBwj5J^BXGeu*K`I&4I=_&CBKwkIl4)^#ABII$-5L#PGA6m6%ZJa9d@aXr)o_` zz|IKEE5L+RR2w3L!6aaQ*})uO zT2JvLsbz>~i|UBM0`8QC3oMc&O3My&uoqRNjO{FAm)Jn( zuSTFczW~?i+|gC+(gGAogZnwf7ThMtgid84<}gOz~a4=Pd;zU1pgYIxU%EEp43QYzYLQfM??3?&1C_|p)nQF7s_4yquP)F4mU zC@^>O)hz5&2F6)4N~~uDP$G1u)%yyh#L`K!96PC~mzY7sDK&8BS1+ulR0PLM*39zg z4C!pC-GD4F21$*SQAH1) z2Z8{2k)}H^hb{6*U*R;b>?nYbfRZDFW|>OKREM(3AoUNAW;OP>s zLQE0yjVX`Rh%d;vHeq>3<9@oaK&UpVveayJ2WTwYrB6i&Rb6IYG)fRpw2)^(UcLpW z8cCO$jZA4G)yG4~NEzBN7l51mvg4(9un5%1BSH2hMX~WyQD#CY-d5HtR*=<#Y{pNOCQ5ns23K5k}(5>b@}40=$v}n!f1FV5JHo zxiAa@7q}`@a*eJSB6l>i9r(&2YfWc5n?A}UDd>6Og8&4qtlEJZ2-{wFct=a60lTM= zv>gJnwm?V8Y@EA zeTiUDZ2ta?>cdh2$*2wtJRKw*oI7$qr_(Rz+pLwG?FD1q^=3h0;EYDrD1X9cfbb*tKG zbKVh&2s^vda7BkC*+d)ZlLCT3D$&4wxi|^C$eP8JK}(V{sKSAWv8nfMcQPb9G)h0> zwn)4{L6p24V% zYGEiTu8)KQ;5MR-V1y@5Xb>VL5RB143CWrWsiL_cU$M~~cN{WNMfGJ3I(doN1@LvK z*fcM3vyuyb@flVCk%$3=iqav8U=)lIBTfSm$JmjzLO?)8eDo5nAw{SOpx7i%9!)(> zCoiTX1$nH~9rkKcL^Ye(=al}-Q7(e#9SuZiFD4)c1%{QtupPW;V7*+uhL= zEW-y%E?{n>;NfFzS&XhG7c!_F4v=N+)o7&8yVyKM2DDTrX^I3acJak8JOv;~d)XAZ z1@N?nFW!Bm{E+c39$E0qGE#YDBw5_AL-1kHSqoeUtQ=~giG;+IqEjt>;YCP|0Kq5| z4hYgA3OK}{dee44O(Q%pK^}dl87&62qnbX|_@3%JVfb%w^@9mbBXpFIeZnOS9*qV! z6`jP1#1J83T3-4^g~?hNdc3He_-%1VFPlmzN{1palsHGu%itLb)QG6 z>ArpyvB2pX?LF1b6i=GYX$Ax}vzG#MZH+E-A4$}d9l}W79rH{;1(l{229!FoZ>6Hp zE|ZShSV`H$snadco3xb9*q*jPj~oGtx-!UVvqRTQ#Q1RGC3t~R5+^_}ArU=7N?nx( zKLH{PkEG1FgQZ7uRA`Z)qM|TjOi)yU19>^Jz$PU5b-<=7>Pwt$!D`coBgK+3jx|7G zB_l9uTXJo7NR8@Bfn^pXtD4e522?jMxTd1oFgh{L2rda&*kTqB+-N>1?Df&N2@BMY zO(4KZzO<1wEv#h3Q-Z<^B71QXfgx-%LX|A*cg=`$3|cHsDPyl)4C1p)MeCz?B_m3p zU=S3BAT}x}gQkyUWq~Bnz;=lX2Gxxi8~HW;MTMm5un7|~c z5RM!mRYW%x9ZF^#ltc@2b4RFb0!J{|B~Gb%w-+FQb>Y9d<3fuW0rZFNQ9^1=!3c%c zPzyKd8)eg3L{j`@)_qyas}`d(?eKT?9>d?r0EcYIzm_C31~y1h`Mc#B4idu#qF| zQc8srhJ4K|3xq)iJ@svfW!=q}3*x2))KNUTLw3GI@TjbK4nub2#hqfAiTWDsbU878j#}b|@_~Yq`-t003^7@=FHK_whJvR6K=V#pfMc-+m#GACTX%R# zhkgoxE(u#k)~lmw4gq~4(whP%AQFcU9Eb)eU?NlDRWb(A4<<+g zG#bSqBl?<-VFgMPz#7w`*R@&@0oo;OngCT6=!SeLr^biB`=WBBXF>3GX@5?|;OHwg z%4556fHpJ@&Dc>3mj&7L=gB6nSagLRdS_n_oNF_!I z39DJN9)C2u1?-3%+IBGx95EOQWD1@_6|lW31r6ZyZW-e?Dm{-95jpQFA|6GQwJ`(= z_%H$o@8a|)4whL16P;)olCvXbIB@D^yP}X0>MCsSoRW7^!_v0E`8U?fKIAOpq#WpBtlsOT#$%iBf!;KoYH56BX%&{;g$eu z2NB}G7$jvesnpT5!dy@?$Y>~y>Atq|h-%RpkNCBW!jmZ+!y-MUi+$9vkhH}|^O`Jc zLq8*ETU}`)=xeQNmurY&lw&-Df+44FQee6yP0GU#S>v5e7;0O0=oMwKfDmDV1q^B{ zjv%S8ee@%MF~PXvGHbXfmb!R~OZu092)jolNcn6hgAmgfNUm7aqX8-&G)8qLqamIe z2LeuGwH0adZAc7=p#=0_^KxM&*RqqZ;9bB00*<&+fTuzO77i#OHLf)dCX+1TDJuY_ zDK>=@HterFPR(WY94ZZDa}!@&Y&2k&87-V7(TjZ{*Gq0u8jbMnG&w9Vp=meHJ{X z0m@Ts;SmT;0lOG6p@gkQ)AQ8JYupAb8jB`Vm`bg%JZf;puK*QU*y$kl-MU3+pAQc{LOq zoZFR83wsx~ExOMr!5?e%CSf7_c2OQ!K~-xzTarhEKp2Dqy!a#KPK@@7hqTf+Qs5Ek z^U*m|EzlJd5Ei_T@aek)(sCgxyC@!hvc|F#DMgY9rvR~045o<+nlfU>Ll$hOHh$%h zqXA(%k6d6Q{SmXdBSIXeCvN&ENcTm>qt>8H@|6+B+DMML5RVbVf_!bX%lMEuh-eh2 zt|`2DplX2>m=eGB#7!jDAd)!Q6u)m4Fd=}vm?;*E0cYsBE-(`f3tm7Z9!B5;d?hdx zAc?)kgo<;D|8kTI7pgB{AqkjYR96>v7#F;(Ek-KT0w9Kn6Cco?7b1XN2m?CTs%)cf zLQ(YQNRGDXT;=e{wUs86q>vwZWPv}Z;vLhpiJ~ZvzHq1bpzw9Uc{neMUqsB$3z)VHzbc)|5|zLP8-r>?;x-^HC%|;-M3%NNmJa0>!>c z*(f9k`(6NHk6eHV_`u}LUd2^+2CZZS6e2Va357P4xy8dtbwWYe34Dx zk~lJbR0J#*xTA>_mo;MO5nsk1gZ>qOV*qNQhmY9E1hj_?^t}%dQL35}j0-<1jF2n>OO5Fp z5R?^Z1S`ORPatULXwnjB1b&x8fPkv?0_mA@~B62kgpgP z*b5MCg(TF#g&w3fMu5I!gjq>JfQGOj3u#)3-u@wgG!dJ$O3E+H8kXSmt{Lf%EtFhk z$&r}#HDtg-lym_xl!e8NdxQp*$^jb*DIrI85pZF61VG3Ok6saf3*46}!DNIGkw=tv zAtNQQ*nJXVV4;C6%!-ge`HsUo0Rb{n*+^38xp)}Ydd#{HI+`hWOb`Zwfk;M9p(dIv zg z_4f6gSu;hmPHr1)LUS*k*slt%%qHLQZ4f9DG&e%Nl+MPoMH=WT#H<5KnBQy_sIAV z21<}wVF=#4m{m}ZFsN#T5yHZN3?^t*0ay=4;7AP(sH<{7Kv{j2AW-0Cfm3r_sF>j0 zmpIu`B;ZigkY}07$q3H+PSF{6lWQ<~2aCP>f;+qtJZ?*q_^6ARL8p|8E)3hj0xrI6 z2d|a^g_RF7@h5lRPcM{D`2fTERWc?b37}y06&>0-IXlfsAS0K6c5$2TNc6mXAFDU6LVW^;d zVM4p0XIM)ay~XUNEJ!5@Wrt15O#ewA%mpS{vP^zu!LnWAgpx9W%R7o95}YB=tZ0=U zs?hMLjS9pF7vk5Rpz}@%sXZLnvBguWwqap}n zxLTXDP{jec=suxxiep+*??_GUM9UGf0g5~7idsr0W*lb6GI|AP#e#snsvsUK^>S<> z2!@9a?*v2`q)BB-jm{M{1lI zfXSw?xe%(+gJ51Fmm`U@%Wo9)&0>Nn1xl+C6oQP9MAc-9l8_g^xQ3Vu%0o{qkfX>= ze12_lrq$Xeg^CE00D=yNav7U|n17bAI81aH0aMVJ+Fx2%_ z+G2Io(U4au#^nnW^N!--wnxsu?(0N=Xj_#PKk0xfgA#XGlC=bdEjy9pgO-FFJ9L!- zXf*C@(E?TqLoWCaB3>wilPVR`I~MzoV(AeAt`iw2aXMYEIbJ#s>mwyVV2tvJ%)&>^ z%px{GR7C%33X#iH0Q!*y^w81v(g)(Ne6Ham4331M9OR4m>l&TKor1CtXTG@R6mv*{ z3)oUEjPh*)+Qg7JV-y!+CZ`Uf?#t9(u6d+6t;7dXD@BkgtY1|S^< zChryt17a|7b`_^exhXqyO3X4PYtlEE)W~)stXnN6m3Sg4)fB4n;UjqQ1OYS)FZSAH zzUXch7m`{F*x#Q~dS#;0?!X_lY(YqnntCRJo4(AVmWn7ATCzZ9;R#zEt%#mlEH)cq z>nts7$4GZzr2(ZT1}@;~i%$cK2vcU;VJ@{^(2$4`=BFNY*L0df3SJgV(;ZBMfUhm= ztC~bQNYtk?f()AwmaZK0xtBX?N;Iy%kt%gl5k-c0TF;UH+KjN zc*!qy!h8tmVtlqzyV3K;bZ1c-XvE9cv4LK(X?$oj@!+TRDoAS7h$%m9X&=S1ON~6m zY9J5|G7F0AOAt835EPgRH`V9VM$3>BNoc8jB?SskF)_+OsR`G zZW&g92!IGl`P~;bF(hJMW^p*f)_v!8(hW}m10Q>x-dzX){xX#dsu(eZNF6;@L@5K4 z*s>-DwGcT3_=13yvNLLdOkkY>+~f|DV3e2j?8sNBq~41=Hw6z~_dx_JC<)a{?pR|A zPt*>Qau)_iiej07DVTAlj^~kpq-$6j0t7c%0JV<55NXmV_d)6%Gi`Lx!XFT6Z66sI zEu7H_N!anVLhr)_!$r=%j;2n6bU=0CNGV7yuRDYdQjMwE!K+Pl84Hw(H9d|SzBm&x zky9#is(b{z79+*lh#b8T359&I(lxMxl3wGH zVz4hflO_=$=@iB(7dSB2zbLK!ZSU6!O~H=9P>u*VRkAN6k=x5J*+dvW zED(G#K)@sO!hxCESWh#pw1#d_X=~^k{ik@o@HTYFCR2Rkg52)*)6|-S-sQ0w5RYM_#Z|41f|mJGdbdnrSH5tF=j%NIL$pX)g?o9=-?~z6sSD zyVy||vPQmC-vWMhw~XZFkrY^FR$hX~A{vfbRF|08s27N>7+kZ=h`i8`|Qkze3Yaup{#S^#L&Qpzrn$1?en1q%o%VMIt@XUq;Dz=t*M0A3;Sf?yn3 zqfF{$SqceHUFng%a4?Kp{L6+*eVBRy&dlF>8F2g@cr}lCL?U#X~sr3q5hdL{bXPj$XD)Ba`ly z!vP?s8-lrSuVn<5DKXG(mQ^3+oKf2g6QvY0&=?e$1xUz(a(Jr8t@mycv8?)nfxJlY z-)SG}2Rv)whHNNA!^yq|l$4ft zpK_9YXdr;>2oYg)0;d>`%`(ChDxrOBp&oi!gOd*JrtujOq@GpWvZJp4JdQzf&7fZ3 z#UuRmQEL+-y{s!C=A=iZ0WMlMNwftJAPCUlU9`kg=ep*Ofaq>(fKSI^s3FYX*2f&CtcNqpTyrOPD|Ls|T&taSV8J&pIT|j) zCKvL;H$^BCYCt4nt1K6u;7O zpD9pN9+6`dQ+fdiBF7o10!OD4Sd^15vyc%C!6&U;utti7Dr47}O|6rTEuBnzc!C0NwUJ5zE8 zII8OfvB96ZN|3ZFw_w1CmfXSL9mAa+BowMDAdHkDfKDC(5UEswOev@~QCVh{Pa)*O z0(V-HELh_m6z3Lo0kS(@Iz?v*hE0>*JCx+B```doFRlTizU-hDW#-5QO;8#Yp=k_i zNwCl&_u)W=Fwm(Ija8G%(NCdDN(Dto>MceUn#s6jKI$(kwVpPoHgYFTtmzB&wVhyc z1S`ulg-3=0FUeP}Eq5-wiwECSO+OL@(?^d3BnyTDq2q`Vz>3x~TdbFz?)IZDFWAUF zVe}JiaYw!a;|S~;-w?N#HE4+68Q63H0<64aovO*vR8>LXH_90lt$5Jhdbz-v z{-XiCLM?s4xcgFuHU(f@y<~^>Y*I6|CX!sZ32?MQNPE#~{5Sudqxahx|MgUd_5)P)4pBCMr{Nh(N^UE^3ny@I;k%?u3 zV57hUAb7F{2luH8lxT$uTBNeNOs!=^kgX8_V7=22ieU@!AqbulI;oKhyWFSP+8i<5 z_p;ZqUrPebc3mtUlZzhmnA+P*E_jqUc?@vpbEKk&6o5#A7t~J9q{F+tlwbQRjV)*o zY9Gm$Fn|w9!X+aLBWv*xE)e;}fNmRzWQrZNQ+=_)-yLCymd?YU*iv2F#b5!!#0SgJ zM&Upz7fvPXrM_$`mhJ!`=r9CG7`CWO~<>ZK&IZxYDA)hc%1V^Na4vHt%DT|r6V;(gpc6K0^K%7yRVv{ z5HRR8K95`&0qz)HMu31tC^mr!m=Q4}JjC2LxmbgeF$ZLzLBky>3tKhep!?VqiFnLq zeFrOMEl^yMn6}g#A5hV^9(HVWXu)1;EIhI4P?5V3Ft!T=&k3(lg=v}&?5j%81RV zh$k#tsYZearuC`-E}%8DSk@XiBQGey)1ktND|hVlT}Ee-DfmlPR46TFYuFL0<101O zw%)a;7^LRFslmlh|0yIU5H6BJqcB-i?C_2@1c#(h7&1a4Y#~GnWR%k@f}_7QHMJ+p zEIZIT{PSfy&@|)!5%%{Jq{4|4Q1W%676bGg46wa2@d5;P1Rzs7z&;{L1WKH;5}v%!YtRO zgW!q0zE(AnU<I>0Cj4#7`^WfThX@F`OZBJi-nA z+-E@w$n2$>t|224FCpOu955;}^WuyF96IcgtQ|ZWtZdq-#b9M)zxat3XY4f&l#E0O z09>3}iQXCQE)~Il5+F{i$kEpo)-zySu}x7T7e8Op(nA+X0={_)2g0r#W*Grt1OmF-M>Qq&IHT5_(g*qjF6yMd za#0=4f+`rUYB(tocQ}%umC|R>ddhEfnox?!j=F$FS@ECdh#!(-qh>h3pzU;9?P#OX zBO@s&hYL>O+!>w=1yXcb=NAc;SdJWN1cj3nVoMny00bA5Y*cGta>|7>KQs`AH7r#N z8%Z>!MSkP~2t{&g(uUSMW@`s>0U6xWq;!>-Yaa(=q#_P<%)ymwi?ti*=#{M5w9H3| zYkVKs!3tjlcqBN!q5E}+?ipK-H1yUV9N1{qkRYj9?<3mV4s+n6sB14Nd%+5f;#6f4 zO8vk^84%JgHc1DGqc9BeV&W(YK{iBq;a#$l;>8vlRnQ|Du|}E*k!406%_Jb=;S^Bx zS#_X9CH$m-eqz&H;z4o)3$|cK39j*q#(Ecz>RTf;zWWqF zFf5A%>`ubIb2To7r^;56KB5f);--1gfPM0=`i!fZ8WDCeD@ST$0U|Lja`H&Z3_h76 zgZ7c8f1r20UV)>^ZZhS++A4U9g#l7sz%{cP%7sP%h#FapnUW1v0SiE7eGCvuDW3`D zqrR|`1yy5~7cAlee}rcZ%sEwJDJu|D*vpZ;6rDKO4hQs86eTd-5ZD?hfx_4Yi*$4` zYS9^H5whHE7r6$3u$AOaQz$e+(2}xn?4$;I#1=N~0+-5?HL|P?H3j_2ZMQPzI$e9ilg;kR)lUso4Q&_XY{@kqZE{zeQG8cv3VgUK1fY9Ep|sR@+He?fT)N;!6Yta2B85K z?yOfAYVTT7oMHlnA+YKK0TZ)%BqX~$k}L!?nv{vW`-(0d0XUh<#TN=e`pHEA>~k#u z_wm^RVGDp@nvCjbJc*X5DxW$knYu_5O1ua|DOscNOc8%Kxuf%p%Nk2<0kSZ_B0-qz zGQLEs1TK8^;#wHKDUfu2l@EFb4XSg2b<$_bbdxx7Ku%Z@VU~ddNg>fCbHS;Y7o-x5 z_V=~UpDBY;??Vpu6HD#o)l?)?E0n;uYA;7gTNG`RMrV>*RFUwR`%P^~Vq@xP-P_uAq zROw~weVbqeA+@BHqM{!B3es@#L&Uai?5iCqlN3@Z4cVbZ!f-P=avyCJ#TKF3Ycc8e zZsy7kCPJlzb^*MSiB@*xloHC(-`ApPEW0B}AC*ALG?Pv5)-a-QMh_#ZY$dGN1eg1Y z?yPI*$W-o3xv5xE6SHq;vi8^J#bX30oPYV{g}3m5r{XG;D)_Qypd0HN#1Z#Y);jE1aX`>o`a5Je3P|MFv{!MUE&BYdVW8&?Tr!O;e~8WrG8naY4Q; zpdrN5M$2r(d6i|~M^E)QRl!Mg)#C(8v#>0M_G(q4=aGI9iCx~Qy59Fly?_7^`su!a z^}e!-M1jOimWjD~8EVd@z@aS!1Y1ail|T?MgyqGr$cuq;5=QSNsBZ`4j#HTe%^gN) zppVjQzHlleFsib6*zqFTl;3dSSpf7CTpC=2 z$_FJ*1?)waU?NTB6B1;A$u)PDd1@u-TwpANDxzxGXcv#na%2s%aVk6L*^7!A0ho9t zN9BXC!gVH5U*`d=kf}SUB~HO`ft;orSw=$XNX?{xhGIz#bc~x#I9Ap!r>%-b#ud_YJxwLi&HI8Dbp!b8&WfTYkj{UUO?kax{xBdGC;T8hvqml8@-`}nz_ z(rYe~C~~F<3;81B%0yT!PCO|&&X^7`@e6~a8c9JL{rG;r2@ZquQc6L;Z6g601eFJQ8j52&K`(O3{);V+CkR=0Zpyg|_VY zRR!!465iS(Jl}mf7~_l=kD>ws&BOs^Rb?XSmw%WmpEU*ns96K1qWC|mTc8TOi$o%X zNST~cP>!S%Pc5T;VC921D0UQ$Z9G248YVDje2mq9vO&oD`^tHdIzx z1V;Fv4KDExOoEII`cd;LiWYOMbrWrDl#7ldICKLh+-YrQMP-3LlAVo|hk%%gSn8uk z+FT5-1z*vz(RQ?PziX%>xiqO0n=H!^mOoXs%5JdkKs9MI{Wn z$B~#hqRb!yl$hBU3~ULeDI+#R!G(NTV8?x$#;mU4PVJZy3@LLHhVZ0>1Yc~1nyh^k zp7Xh(AXN8#L_(IiQ&TAUG9pv=C8d<8==L&#O4AmhBytwI(cYYnHxOPyt zmmST;omoZzSr)b}gi8;KpBH-KY)Q`*OIsNkVqjSd16Bv=ME6Obcj|E*hsCr7dVZbY zjy304KYdsrD%V|jj+8- z0U>K8wMc3#lb~e#iI`;ZNb%?l`?g@LclNDBJim>Q5!qSBIJV0d!pLR~1Yqc0c(jy& zr%{Hxb2#H@Ox8f>bZQ8?DGbb!sRRKFY7Eg$0>F00t>lyoLxh@v%cugWja`GwM$z(X z$f~t#!FVb)`pg0rSiYgtWfi**feX~Q@=05a1lAyZyueOTQO)q2+fMIGq zBw1ER7C=NXymO7Bn9rI-Zhl? z$P{k#=v%h#W>e3alDdpeYDVIqzlgBxw6K+^4m8}b#;HsrYdQwG*0y#T3V6aVd~`b5 zWZW9U=+HuPO$0wLSnOKw2t$wSI3MX1%u(82PSuwj(O!@mH)P)$LhTTuO*mr8O=9Jv z)>8-p5HWbtNkBPcgWYrh1;Wq}j^OM-tEQeZ0t6zApTJQXEhY@}!b+H!1=7(__KKMZ zg9U~XgbX`w0zo0%(E>>^Rir|~6TBf}Y7j{`xfTo;vSV49ykPDn;HHL*6bteksg9J< z1_7X<$|97JN2;I};z0vaMy7z!z*Rw!K*<6dB?3ll#5A*#dZ3>>ih>dXQ%8C$3*f6V z*AfJXdNC^tY5*=pM?y?cHE8q2j*Y5}-eQo8U6|k&jL z1cGG5HR;p8=m9;gq#YEIaj~hOy2#O~48u(U^Fj>3L_Z<%A|orY)Fk2vdQ#RzA}3Qm zqL%j84lscO{ozHZz7%MAJt!o#XujCxBN>4|BxxhjAiqL#-yOZLe{Cc|bPs6O2#jLL z8hTJtFByT6QjrulDF8RQ-4((*+UXcYaFf-f&n5{zhVlot-DzQZ9&0R>ZH zW0`A_kaq=@sbC~aKM5e-x)2E&K@5@m$cvC{;Zf1mSJjljDP3OTr=_+C$=@(Ev-~7N zC*9#%BmkGS;$n*n(TWW@iI2S40XymTQg><$4PD?R7c@X2R3V}6U_j)1ggEJ&_t)zsDcvd( z6_mI?4)CLprxv>jSbjy0PG~P|PQ?s!Y(ZUJEM>tJWYR7c4`q`BOOxdAN_HbG%Z;tUzo zP+qJlF*0B*JEC$THN^-TJGMK6B~T>P0s=)e;6Orsq|B(RDAwprsmYz}b4@XTgWD(} zBSH!a30es;bajAH9XH7%>4p#e=Z;eyils7Qu#xJ%Ue@vwVUsWX5h_U}r|=XKvT@`W zW?G7tNN6Y+E)K&MD;Y`Du%hx+g=NTaB&7aOo5IY1v0l1HtCLMPqXedZfM#Kt+5)nL)jMH5O2=c&9ZM zNSu!jz=8)gop%Cchexs?PGH(%B@1CIR3QNbHz28e&fx?^g2a%92-`=BXP9Cy7k+J^ zpX$qzILWM0>tIZ6{5zO0T#kAj?cqc(I8VP^xAz7k=}x zmtSF|nWWntD0vDfBgT4|yXg??K9JGFwfm4413F1rHAST(0IQ(f0a@hmR4;ds98fHH z3CWKk&*u~J>TBqbAZQ!?m&?T;0qaoc$(?!-WWKbCnCY<8;N~S$WYc$I(~r?lHQBdJ zyf4x z0s+^Q)>AQSG3jU-UmLY2Mj6eJ(Mn25=wuCB1!V{3!d4(+B~v0VLDwSjY6h{xpe+_~ z1gL1BVL3%Gb}V3o7uL9j3>f7lWubym`YfAw2%u6_2z7NjMO8aV8Gob?Ey<}97xH_` z7Z6yG0w^G(Wg?r>JH*0Y%#LKNNnAA**VijJxL^UnSd$SL1qN85%7T4P*>?eIk*lD7 zCn*Z8j!-fkm=ZiX2|!s@6P{8-Z(U=(m;p;Uz4}hUsYj}5G)P$=@veo1fjM;BPynWu zbf995ifUwMMW!S|)va-#vXLMyj2m)CNYclkv#ajg_gxn|sS4OrPmQi3Gt!u?t=)tawNQXA}XdxjUQxC7YPN-sna-E6cGZ7C^qbJt-f3+jp^fj#jluY zjuJ?gO&HoP1uxB|Q;Y!ZDM9u{%d(%y@J&(N2O1E>AVI(qaDO4wjJ8n#axoepU<;D_ zC=uu}B7JhF#1bd|gi$q7(Y3_6#uHeH(=Y#~iS2et(;wD^hR9LNF5Ku}@TsrnvXVe) z8S*H1k%$~Q)e$~ALhVF|sJsYUap7Z`cMWJ*Ah~x92oTw+3#@E3#fZOmqd^s1OAXB| zQ*M0V8kl+$6sl~Cxe0c2(0XN3L@&j~USu#qklD2uRN1go1;A<_D#{M^6SIzn61Zea ze)X@^`-j|&La*ua;ubHh9N3?$%yv)IhPTLCE#Lkp%B6Yh>ymBirc8NLZSmi81m+k z>y}A}rnwI{`k+|IDOm#o8qVQKQd%!MNkGt>&R`4aaC#IEIih2PLR8im1bS9HG@R61fEdg=2a;AF^|F=h zQz~!#T4I=42k;g!B{A4`7$M0szNpilq0>E1;aHL_QF{TR?j-3EjYpkm7vzBq zTMGohtSO^X;wKc;z%mjcBasLmE6`s1tDP>>JNQozH3EG6bS41Rfe&K9kas{q21NQ@ zCu&lUa9($7dERC1#kEEw2xuc08m=D=EJWPO#z4h{$`8Q8si$-78cwWvw#VRUQj8tx5aoej0DUzRyf+hgd@hyAaSw} zM)%%LdIqHHT*6>T6a`;Gu5#B|} zsYF!4sIrlg1D_GU4oO4|E(n^G!AKukBN$(27_kO=1{2LBjNnynkMPI$7XaF&5dJ&= z{y$&N{}#YgT?8*?u<(cvuF>98^;HGE4^>LVk!zT14bdVt!FOmUk0_)Oj1Qd0J%X=y z+(AF_3jq0*k6BOv7SHJq!0D%V8If^b=q)40C9D98b*jmc^@{77Za@!o%(^2fKRmj| zaGioV(sAJ?&EW#~Agodp!`BT-S>rxcK_oYw#PpG@DHWyFp%ehr>gb&)DW$%qkQNhU z0wM;DR?pf(*@#afW-o*0g5xEv0E==Y^;7~OQH6+6LiDhP7xpOt{Xrg32vQdS*&5$6 zFcy13YTOtBj6+x!O6DUOfq-rz!jhtL1WnkYC4&-Y2Tw#O2BU-*TezbM4vK0gB!na# zysM5%DX|xDluRP$P*8N&2q|*9guo~#NU#WrDP?j&sVJ(b5JvyH5D6LpR)dgbl@b-* zMkgyty+P)Yo0b7kb`Ss#A#H?t%H1wr zj6yF?;R6|vS^!}S+;>vaN*%^zs=+mpew3qSr;D6@|Bko}(WZrT`+TpdKVUR7E++js=|htL&tS_(%i-1XhI7_d60J#KB8P z&}vRq6N^A<3YbUoVnl>CQVh5eIyuS?SH06u!8qKKbl3C&P`DuyqsLx0ee_OGq^1|> zxn6*YfVs2Yui{9~DHoJPniyqzl6|=V9|&fL`^W$#Yp@7tenmkE*@;#mJXIiDXsiKB zoY_YMJ?@2yrn#hWgJcQVR~SJ`Uocpa1?gx>%M{B~c-m!fgWXsZl4gg121hg4ikWPZ zM;m{2qw^gBX|U)ENV zNAMC5+;&uRp-=Id9VcOHbRRZS0CXZU(j*#&i-x5MER$tK6@cxkU`*&uy5Xg-X*g@p zaGw##dnaows}9ja1}ha2P>icG#nXx8NC1R_D#C$%$|*J_Ae(3gPh!F&<21n|dSZAv zc1nsD_x&(Mtf-JXCc6V%4 zYm{>>JZdRfQGrM!Yj9$Y9`5r(rf{2mYjDgp%E`rccuL$m9I)DX($U&erYN(l$%vJ7 zo`{(gvg>;gs9{SanMH3$N~cX9DZgUTNfIPTTV%uc;ld;D z#w8AT0pF-)A2=Pz;f#3FaT>8pp*0j_1V9Q917SqsbSg-u=$`Wf3v!VKFdwb!5{TH4EP>r%D~4;AiHm)KBW>q0#;?!5eF~?^1w7BXc2>& zH`x)kax=?FqT#+aga#7oV(S%%l*vf9S}A7yu}i~hVP-9mEF=X3TO&Zph$`+WqGTn4 zt73yj)#T1hM^}LfPbo3YHwZj5EVz&?#E1Y=Ngt^+lyjFVn9G9E=GtH1!HOVLB*!HE zD{Gx7B+K+8QPT24RaSO1lmwab`w5_u4o)G-uf0+xk4i&BXo3qo;goCc=$O#}8D~gc zCOe$LIvZJo2Eoy)Fd^)ICD5ua&`*RGQy4dq4dE#aarQ`@@Ki6dLolJ?JBt#~G}d@e z2Gx{;xoHS0ig954iHi0TcF~d-UM%(&F&#gq0W+g>jL9olt|$M6lQRlc2&0Mv!oW1ALT2e=)1vqBXpf z2J5g(?yyA<9JAO0l|m}mLfc}>U|>m2oF3_VVF;dUbqBb2!%+zwhvW-DO@}1>I-@F+ zQZg$S6_hw+L}(YHY8(rs?4$5BwMK@OWw_xmLk9$=O!N~V4!ROo5%1VyKH9EWHfktE zkpje&15t&6%(g3sfCb}SMhc`Uq;I5>I~z5f0IXzC?%-82@!*57-6TFZ?7XUd6qFok zHw=-QUwYM)1r-z(ad2YiMWq}Ud6cGQuJKLcT2BKS0hGkKmyENcI?^FiuKE3ZK62EU zJW_s7#jGp6Q+~gM&_Ml5_xRPk7Dz-1yh~XOs!uU2P(B_Jz%`Uev3S_Am`cNwsw19W zhK4Ob@UCrTA!S+EC>Z_1HAPhvFN22c2oD*E8+Fw_A`w4Rc9AKghc(~;kXE}79}2*c z0KjgeuO4)XWr4ZR7Yh#6P(>#Th?pSp1o8$MR5;P?8-BnhsY@K=j5*aq3oHR0J#n5NB$pYUQr=R7(3m&m$ipNyiLRUQAw- z)T~jgyx>)ts*ygj7@m$0*y5uBAzF~aR^bGo@in!OG_oxoDLUy^TtUi(2xw}7;ecIa z-#OU@6(WE{RXOyIHEK$=>~)iI6tc{TLfZ;h7Sh)jO5oj4AXf5HLFu!}KCIN2B%-TQ zx~7nLq7C6gSQw#fHmWSegNFVhfSQzFi9m^fP#A(~7jeMAWE{x_FCBJRAi3-dQZPK) zm2CP7AQHfmQw^&qMuV0o7XU#OyCe=z6(ppX;HL&eqL7T!f6IU=Y#T+$mv#UJk))Z@ z=h`yJcqe_L;)MviBWu@Y3Y$1U6Xc?xZW1L>bIrJykF0qFZ9=C&; z!2wz5HBg8ghDxs@6aY$=Ap_&`B{hztghEsEi%!aCjR6WewNL_WB4Awue8#ypN^De6 zAB{^IND!z*te~{iLj!6qNm-+b$eIP@&M{c;6GlS8BTU5aeo~`(iLg#*frvTGAdkYy zr2{tHsJ%OUQ5f$u2Mx>!5K#dAu8W;xfg^G}Q8k#?u;f&k$UgfzU4*_7D7|#Jw#8w) zDZms-cLyNakamfaUk6C;ut<kktd z6bZCJ1OyfYtaIH_O8QSI{CGkt4HO9Q;w5ql1~C<3PeW3S0^YJ&&|GQ*=)- za5htQh+hWx)Vh zkR;t=CAQc{A|oCHiwhSz+EW9F9oH_r#EHy=g{CoU83cHdj(mMY523;efC|U2T`KMo z45=j|VVxM5m6xcbjJo(nWZ0L8HPn(Ak|4eigD-%qopxY{7z)xyVA3(<#}hw%X-VxP zBT6VKY4XJa0dyY}q5_v)?BJ3M73Yqs^QBTKO^OW~;klpO;V{noqZ10OYFrC|7&3@~ z?1PRPi$`pdReedZpkAH|MuCX7U1pq(LRCZzH)!Bv`rsr}DxO&fb#_!=BXBK2>(v@c zGLj&+2-tS@(hdl38MI|&mjxOM$jT%tuGLx$jvp)#D_c}br`Rq|H>HOEq7^fpfrFlM zipDxM>cE4O$_*UBaHL*94t%|-Sx6095-~kO@pxebh6IOj0bILL11m`c!Euw`3WMgz zm+VLcEK*Z1ZA)jUnXm2ojt(y~c`OBd6BG$1FfC^doLyAvHRS?pfe` z%M|g{`|cn!_3H}-vjj@Y7zG41MJniNPt~!ER+IE}!+?+lJguS3#+-l!BVWOQ#WI5l zAo67stHlFV#+@of?veX2#7X_4`kqo+@Ak5+BAx=oO*P^j)DjeoIC*4Bc;<-N0a)#C z8MGlLLDy3gJ8xR5wW1fWOc5xgGB%hy1I19~zNnDK=3qT zU+Rif+Jqye^gn!*vi`zA^>yvSE+={R>9Cih6tc+zJ#0r*+g050KnJ--9)%!!5FA_ z^UHlz5T`?|$*8jUMz5srmoW$^pKAzECzSAV(;CAI6O2nxsD+|v0W?5B|KH7bLU=2>~vEslO}}r0w!0;2sIg9U!2zfnq%o5C*LP0-lIdf|v%1zOxW&qRqHM zSPw3GA~4ZvC>T;L@(3sevIRpJXG9Z;0ov*a8Di^QQef$$@XR>7pb8wLL_tN%yS(7Z zCYHhk2>4^50Kt5RsuGCV_U0gu3WtGUi8`+2T_DbJ5MEZ_5d|?9iDx8)l z@;cNSt<9zcWlfwFMPT?tF7VcR?(m-ob0j0P#_`Fii9Vnx@o}xPO5-~PvGS4F>OluQ z>xq(%6M!Q6QvkU@Kx2xQ9k|&-HeJvV;znSJDn(-7prK&eRw4ASu7|lC1w;0!kX@qX zT}PNZ#6);hNF*{6i7y4ol$O->$V|+cMF6F2(4d@_x6vrn^3+vdXlDo;L-21Hi2+a` z^};0*>b{KvV9+jkSwrqbZeT&!)RQB#*7ztGOt2VMZUWy0c&*X?@=^=L(~|B`Ip0|r zl!%q`wZIVZNcvXts7w%0YZMiaW$=+8z$u<8`v^(G&M8!JK$RW)J=J2$2OKE5C_DHg zfGv=;Mc8103Ht&7mq%W%t@Pi2qx_b+LrWFfmktyn+OTyK6gU8RmBJQ6zII0)C6BTdebA!&rV;UFhQu07mQs} z(*npzxWt2#>gc{m%w>efaJ&T?O#mj!Q3)l}?9S3Kr?)WC>5V4=NJo&emIwmq7v*zb z-{F;DY%#M`8Aa900*XhV$f*)@L~_}5zau@r#NSOA0z{9i4)2(Tkhq7#bj*6&h2+3Y z87P46Fu$ZD7lXrjn0B#7cAV>2Fl(GDg#jfOU;!*gU=b~SCKT$TPw6V*qD5A6r@wH_ zJ8hAgWFh8`5*U9#qm6)-9k_U*`Q(>zr+PvsRA$kDHNNN@n=A-G@Q@TgeWMjtirFr6 z#EX)fDPi6Ogst37903DRYoL(hfn{7s4QMduIstTtL4(6nvj&)yldua_=|$oN0r9J; zGKt(uEzh-3<%?9fr?TR7fg8%fyJAU4iA@a#p(l7%zy~%>B~`Wt@~Woqj03EbuaaqY z@bz!j$5o`jK3(dWzulNb04pALXcLQHXGnZgugEfMk&oKfLH@e(8xLD=(6gj*pa~V8G(| zt&Vb`3|vrGEA3*$d`aBQ4iZ7lc$bbJaD?Yw7)avK+W}JIENBYj$xARcIzO`pE(%Gw zh!4wx7dEcaVX5f_*Uc=!P#cTA*w?ToEO2Bh!pGy`CMBfXI#l9xmD($f5?I3{3>8r@ zFmVT|Y@|O@5QaNa76~+Dn(8W32NlZ%Z1f1voaG2y6MWs_b@)gP9 z+%fs7I`g^D0@lPP9# z0NGq)ti&}PI^i4Rdd)7`AuVLEz1e30gP!VNZHS-FOO|N_x+*-^GLz4rqMMVH&h}x-|ka&^1DL@Cr z<38wo=RzBBQBjG&Rz?H|XadtRvKG0GmWd~|mlSv@9;aM*VUCx*YD7bTBYp;jfy~Gw zHFcDuNQkgF4H^LxVINfotHmZNzMxGo0YzGgj{_~}sFuT|s4&oX0UFpWQ`Vrv2UrQi zz#`qE0va#A)Fj|yHqmr3jWtGP4NP8Cie(kFgSc>ZG07_Zi1iK;S+IJbQedEJ1 zPnDkjLqoFK%FAAhB?1}wqB<`i5(9zNl^oe3DgcVvfYwCxiZzS50HZY);{bvoxirCU zU;)!Ey{r+K5{Wjn%a0N&OLm;N^tCOd$r>Oy8o|eWk+LR|Ed1T1t2RG62vOsSusHhAD6ea2SW0}U+eX_<$Hr-^zLDU70ngxZ70CGw*jRsB$HEQwV zoauZ^e;9P%Pc(AT@2oLmJ1^Q6wpOxFNHLZs$O{zdk~>6$dYe-clr=GDRsgI4gn+9E zcvZmyji8B`q6`41+lu9;0x2aL@CzbAa%2?pZmbB&0`V}er)2FPNl8sq5JOa0aV;45 z3rt9yf>-NtN4UVKKompnLY0y70l_kbCWF=(vLNy+koje!+(|tR=R(n`7TPNT0aR`i z!FD((jRI+HVdJD|sk*FzudF6QQ`<7i4{Et{CuO;_>~H%>M+|;)5z;UWVpCK6_l^er z-lZ;zXeBOjMCCkn6O$cyz~#PUDkdwau6Kt@sFeQ2@D4>pzz_to@HDb2dtp0RSQe5d z@&yO5prhUf5QI%$MRD2@q({D~d89f_flHM|!V!&1CGeRMC6gxHgleT)lXrT8{GcFo z*sHj3mY1iN(LL(Hh;IQz%u3<8sh6c`I|U#k;zT4C)IyK<ZjGnT zex?KEWLZ(vNOm$+O^yUZ3P8%21=JW1xeyY@vX3yf+u}Zu(Mf4+r-#tChD=G59dbe6 z2!%&5XO@)lf*#OzGN(2g0W$oGN*3hB8of_61Plbmz04za=pKo-(S%YXAz?UJsJ%11 zo6>P!MQofmI;AFl8~=E^yHw?wCJMs?5X56cK#1``B#Gm2l6$E8(i_?XiAi_;tXj3I zbwA(xNtO#S1Ka|^=rZx6t79!Bp~{`qKtzJX&kL-qbZw0wt%tC>Ba9T7#YV{rn@w0r zmJ1cMUKZlfBNnTdYqDvx?TxL^{iXq(e1^=qUy}NOwZ(WXlCp^o{JOg)X2Z%13Q*(m~E0%IQBTqauvR zMTwPz+##eFg>eA(lqPF=*RrrtMrH@em9q8J47)hOBC-HL!Q0!&(Yl7tdy4mLFx5+87P;_Ma99( zyWp*N4R)Bzk;bBv%8C}$GF22mHOLW%UFZvuAUS=Opym~-7C?BtgR6SMp~yAqKzJd^ zRM>K~1)jEBfFZ60&o7*lCWU6xM($JuZZuQB7r&Eqj3gqous4jzQ*~DY1F+NCZR$c52L~c5sX&5xR^o zz>1L6NJmrvB)PiV3rQMe;#4e)El^yh*wl$yL-mzUM>FoDV$lI0J0*5vDkx zuw-H7CS=I3P!WtNbu^A$z#Lr#V}ZW2g+W6Q>x?k=l5d1P?2tN1O1d7C;au$x<&^2~`WL zl<~nYz*S!hFleK?&C~HKd2yjV^&Q^QdU<+niY$A)?VvsuG z;w`$epo3_*`-TEySP6V)kh-ft|czu@vmvXc0943c(Q|oj5M#<6cG;A@Ge^SdH0Ay zS}!nSt}Ygs9pdL#vcfQnMe6{d9M-sot$_V7V#@m^4fi-wKIzj#3TaeHR^LenhPunLNeK^jX@g^Fy_mzN5P8O_|G255|dl0^n$E-;G` zn6_(M4ah0<*hgSMp@-7w3wDrJU}z>b`3e;{ss>3D(?^(y5Drpc%|=oqHoc}_&|7Q< zr4-yzFOL9Xu_~C?dRc0&6A)nPO07z)M5Ubt4-nDP1X3&>V(5;h^QDF$pry7Io6^Wg z^__|tp;uN~BVf4`J^jV3_LP^E$vBabHI%>z8HvDwI#DwmpixNZMRNTxf_V5QFJ8!w za@1=y%3|kLO!F=&%>@T7$vfB$8c9hLRmdifKAKGoij7hUj3_bdCW(`~yetEh(pbit zkBUM?$ti=db-|aHPLu)!Y`0RhBC&u7=mjE)3Hp>B9l;}a><~Ih$&@B@4Qi9Z@KP5R zq$ywKN4yU>8r zG1tgA?-a%zg%E)H3LDOF>&u8xg~zWH)YnIX%mo6FQ1lR+qEKe*aX@Lf))yRMg_T-H z;^KFo)Bp+xY{Wh61v^T!k5o#mao`U?!D|8d+b$^qTf?cWJ(UP0ggk~b$UY?pmp*mV zBT#rJ^}J|&O+mK>!<2K24k8GA(Kl^EM(jmMr%E+-hhL59-CqW29WPXfI8ZYL)39Q8 zfg!q50JM(m+wL68j>sWEmuP|j6x1ID0L3*&qzu|3gcyy|@BNU=qqfy+N+2W$W8R(K z1&=3`jYqAmH+{=+UktDUa=&KiGT^H?CJ0 z8a>XLv=q)L;ieyCU{}tyHBVg3=r?c zQz7^gfIrGGW$d!dont@Upg(fqj{Y+OI{C6`qc~AWQmP3VI9tG)06^i91(Z>SlKn9wRJhn&~_k0wmz^pND zaDWb|k!UTJEYRo}0^D3EGk%ixxg82LxD&0k1IMn9#*$F(~ZC6NvzX z{@Cl9pivv@igozIk-Y-&kyMgIUn_(}49zLmI*#a3B-*0r!jLj<;4LA=3NerlF0_xB zW$ljjs7{mdldMTrf);Q=T15yYVyJuw7MLCxWW<~ofiVJkFp63A<)|FIOHe0*2m<6$ zRLCG&F62l{#ms^liNPddpZZCePQq4HzM-%t3|#W9MEVM(<0!DC+{Zx31Hnp^W8W?o zR6!ldpcowEWu!x;ARYkX3|~N#GODnKx9~wug;R7+WhxlgFp(o`V({|FJEC#{NxbFS zb_n3JyqX%AFDcoX(E>KMkleAyO$$V*+!_iAk%2YAU<=lnP!`KYx!K1kkSdn@aoHDC#bc$)>M1WqJf)UoS!pPw?D%#5x6wZ=_Xh=it(WO1 zz;y)7B@QmT)K^~+d@<`{)r5>krh{nZ>kje64ajIfDkp2-TyzkcA(F&Z1P3frAW{!K z+%rCq(4Y~a35B1+W1LNCI@~g7)<{9XlpLwd6ft{>E&NKhp#T&Ak2X%H#1pW11$Gn? z56oR)xQy^hFdPYtdXqJ6B4CNI0C_lzO3Iufk3yINVkU8H(k0@C2Jjau$%Vg2psHey;Bw>i0Xmdd~piMu4mUt?V>Ig=@@HYj_6zQ;Fj(Eo< zA7#p~DxeB{Bn1}l=*^>14nP(J&%4 znH(+V*G)*;QXjz?ED)=~?E7oIH&MBJ1$+K5M90@e|p;*9%{QGI!-EO*w(#n}c# z9??&ExntDgfGGx2VIWwKWXgWS8dicqLb+=k5rW7~rW~b=6}yxV2S6O|=jY z>Vgy@r63+Ss4^Zp?IQ^;IW;MtS}-dfNE#)Wqp!6ao-<2)@WklxRBUjAp+}mRYX=)o zDG3b`Hhl(>LIainI+i;Gs+2Bp=FlZ%lp|)y9V)~ZITD!RNOJ@xyzBLq7sA|fE9>;lp$+9?Q$|EJGkfL?%W6y*W0UFroT<#1oJz-Lg&ln+&CmtuKWN-KdZ zJBe5c0e+oz87Jn(mNj-!KS{|^reLJ}AZkXy%}QNqui8md?oDbJZlW0%R97K{AzwuV znl#;)vQq(Rwb(Z@>09rP$tWW!I2iD2oS6%P7Y|f<>?JTtt4uUXgy?NR2^C;21i6KL$U}0sq|E+x>d>qS(94BFQtMW@gZ@v*O}Hk zgG>pAgo>HwTf-n`@|fTt6-T7QN^AkRHW!iun~lOlW=aJs*El9$*E(7Z=`CeQJxHPp z146)trkA3VQ$ad!gq12-!wXHv^8oiJ4G1RZ7zcBeT4-FGzQsIX$AZaFeeT zh_gZI+$$++2|ZbmBk5~-8t8(0tF9%Bc%_dv zm~K({rr?I?kk z0z(FQX#staSm|Gff5lZDv)Fbkfu;(i#!U*zf{(z|Yq~}*w3KUlE^(&#k{3eS4qIu8 zT#ynGk{&@?;ZYuq?4~1}&eCE;Mm!Wo1<3_3JW^e)%06J-w-W4{RK9dZ1sibq>E#y{ zTO2sKlQ=Kbh6-s$Cq~&(644SCU1s)?5j5k-1+|nEa60>0Oi{_H`>I9^RR@7N)hu8^ zA?hj&8VEyXZIJ@wUOJsnaE-%opoe0kc&^;4x|I>xDU+9mk^<2XgR*+69Kr^sEFdEc%g}%uh=%}wx1nf|-49VAE|^{k{&OX4CC0CKvC zJU+?`_>9BN0vy)GJc0}OAO(wSkXi{ztzm&iQ&S6ss$sF$Q?7M^JIcoqIjv6Q?e(G+HtKZe6Rl*3_(&%S4r|m;1A3&t^fks}Caq8f z>09q9@iz1 zXz$#scA)d4iVGX1KywsD6A>8-@9O|{5*wvbP)!PL?!d^q2(xT2JOL|I(m}4x1%O+I ziYO{tD{V0XxI-0L$P|VM;QuEVjI_>TMq~;o>p5isL@G<=6i7|!KUouuP~l^ZI4$PX zMr;ABipWcX?t|AZ+7+-Wt`KI5H8{@{Y++&$(|_)0yGU&JQCT5C{2@uY;bW9Y)=)Dd zkwM7>ChO)1?g7UmC4iL%@s}Ea$Wdtw7r~Ha;wf=C;;@@)p>}v8osKtBBP{yD`HiY* zWYyG?vcnBtsEvMCP1ve0<6X2?@ONbss*sAyXwLq?AxQ@M|t81ixNjanoM;O2HOcGiZyL5kqyL&7h|6 z2&y23um#2$s{5F10!_@yAzCf~>!WL`2sCvOwt#)K3sJ=&S%K*+>9a3#TjaUqX_X0;{8V&~sPSDvxplGjVq%D-+yK?)fM+)fVGH+Ad|RkPGm=t0{Nj6^060N%NJ@ z7ZXxXFv+IiMPemWG+cz%AdjfradN<6!E*r?T`x!>0mMogB{sy++G39lf8-)R3@JdrzmDn15Mt=R`z$x-3j%5JXiClxxh@pqR6xawoPmK@pLse0rArTjh zNL+ZrFkz904-k0(tRH-dzBPq+6*O;UG>3OGjYv zk#DC0ZHUZ*gglh2R~Nnzj|&3=JqRp=0Ab2#AOd8>dQgxcf+HQmYYlO8pn?)5as1Ow z+RZO25~M18Q4BZr8dmEvLji{&nMSdrfyb|AKh zLE(M09+);t8N5^%u-5Bw_T41l;#4&Q#Z7A9sdV58b|o;>Jf%yT$O86-L_eAJh&9Gl zRz?u^)YlxT*+?+K@CLdpo3_(0!;%_#!d z2xss*QOKQ!o3PqVD;ZlN#1?g7kht~5hfO&WWCvPJikTcG*N}v;uUfpAR+dSJaraeL zBEi_PiaYX-GhT{?zjyT}n$cfwZ!+N(OwrKXQc2}U8X z0)I%5EHtARtfXwQ^kFL(g659c7}1zi%h6QhP)>^((bQU%O^QLaFdw#-2|#v!CPF+K zC@N43-j4~k6L3VLx*8e5WX+xRiVGQds!W;`WfQ!%@Wmjd@<^{qK}7&!neF`g zXn~JJQg&t;JfWnH5O5PvU?-2lqcD8Q-FkfRRNW02iyATy?0C5cp1#%5-NvRRq zOGJrHPakE(mr_EE1@8bvUAXCPS-_b=Q1fmj?{=Ywm2gvH?W`R3YDsX(5fx$L4kiGW z$nc`dqGgKrj6>M8g001}ww`Kf3Q$8;f)2R5@|Gky;y*cG_5g7;pveaRfib5$)nB^hIbYWU)hO7z?`Z$ zN?T(N!km4{VuewKdq6fR@KF5BN>&M2P(E9L>6ahUBqQ_70Ft=xEI>dxXjq^erUr&s z17r!BQ1*(v!yC!cbovFg;NTj;tdt1va(B%yO6v>fAHn+;z=D}#y{N=21&V4Eiq=M; zsS!s~#sn+jfW7FdJLKZTsO1TW3Fs!coCjC~rY0gI3WOMz>2&saQ7@*nv+V5EO^(yH zXfexRLP&*50mP=$NjkF*Y1++AoQH~LX_2~Rq-h&A!hRo?3e zvdNAX(-&eRgYx1X0^FgEY2|4{$|I@4L;$`5L|{$90@x8i7;bW*(4<>n1dS%ZvIVD`^gjzCUQwcayHN_%y zs);So@JJ$fB}jLm1Px!`7?CvstG?!PpCp3FMmJSiIjqDJWyMKIdMYrckkgAt6~uPR zDI-8(HJ-?eXvl&T>polsn!K#UA9=asLhBjLN(oql+o+PMmAr!jEToAwLI*u1Kq~f) zZ&j9&uzAF7q1wwSJ6NRpSb+e-7QZP26Aa|nG9ZvFTRNM;0U5x;uhdvWV+$y}W`PeC zq9@*UpEBtV%ppPCDS=vB>?;7h!XJvEmsvA7oU-siQ!9ZVD`-2LFc$!*>4}v#Itlx( zQAm13{Iu6DI!wEO>^NtFa3>eP255PzWjiSY9W_upX!9aS_Q?Vl&Ry(?(CO3yTfkzy z?-BgMpInFshH|vb9VKwen3rad+x(fX(VU?xpUF%Ci`eZS`8_G#n7`> zs+Yy$bS)UXRS_D6w5l)bR9!B-SipsNw1722@-4#_Cw6sFFL+8yNK#f`tRQ{D_R$5d z3Yaf!7mUVdMBUjDwtC6Jq;Q{8c09sH<>o?muvJPpgUB)}1b>uM9qUD+K&l2kv=OHP zA$_~R$hEI>Y~l%m2|&Q~7M|FW(8*4NYgm|2Dwv~gjMb`8k_7?LfZW+dH`G*+QUeYE zIWpqifgURQDgkJfPQpr{2nWdWX=j%8VJN`H9N)Z%xrR3NoQ$_3$dwugo_AmY(|t!gevM|H%;g~GAM0y_q)bTkVpy04c(rIj4%?Xf~o@+Yj`2fgiARDBSHXbOsx$K z3{-mc<%KfC-%Mdgw2CMfi3l4?^$z3>3vyZ2qrelMKFBXMcZ+} zMzBE7w+Yu?f~UILR$jhO3AM4MZ~zyKIQtdI?@owE_rUJjY2PC>qt!B%yHf|cy3ZZx z(8K=<%uo*ee5>RZ42aNRD zY!$O2Dvg(TG`_|p8ahI==yW=-zfcaU3Pe3bC>U9YQ?iVZ7iHCt)Rk0{5vN2S71u}B zG@wv5JE>Pq@X9m+XU$E~I>kXx3gVYbGXmQD%28~x#v;-3OH0K7Pp!%(jGiu0hOfd~NkA>+IV zNhAz9<3Uy3xx+(+lZ%g1Fajh?gas_|^wzb`>U)qiw(yQW#6-++CW%U*^+*;l2|f`} z7aLJaV3xs(L|EfKSRgEN;c0M*z_tS$=u}olUJ}6#1Vap>>g6L=0EZ%Bo%-S-SbXKw z?@+~afw3$Lqr5hewR-7arPt0-;?$`R<|I_`E^L$|RkVgOXlnNb32G|D9VKzVmMn2o zWva-y4n=0tL_KsCOFS}SN8JT%1h^)5WDOu92e|CW9Z6Su!8o6@;5e<6!uDdB2J#CN zAmEaBFNO2J0`pIR;GnV!#JDO*L0_vBqDs~sn0th8R?_D*P?`?#FrlSR5hBs%(6e1^ zA|ZO<1(A*5G7}k*wd}m(KTL#Vy|0I)*i_ZphAAylK`}VES}ZW>h{P1u zgb0h@S1sa0yXYr8t#JVyV;$uLCI)6z${icgm|m$n`{Kt6LIgdJFw?bfJ-QN6IpPip ztrV>W7p+F%6t{J;M6>`rl|YV;Ml8Te3QDZLw#(FgB~u6@CR9a~GK0L}gMh6Bqremz zyD-W)KmgOnM{T91#0^St&>a{8M#0lJt?m3t2~ov6PAZW0)R=z#V8L%PzKq-Di0h>z z_!U2TIDXqjdGNDABRs*YtWcE$P=Y5i;#VvU2t(vlW!?b-XCc9gXkdkAWMBKUCUS(4 z6crV@^_&WccQFf<9nDU5T<9-pLW4LP7EX^e**TmMD@6!BP!lwF1j#;pq~j!628hIUs9?YaE7cSU=9?hU=3U|zD6ypInB%BxVF+HF zFz1vJJz@)R=o0%*wgUE2Rv0n`WC|vIP>7a>%U6(MGu9!=rn-=E2#{#O2p+cb(l26Q zS<1pDPyi(CKEebV;NpTQ*@;3Q3?>5A;!M;BsJ6(<~AB|2w&LrO<-EUJGAK}=#U4V%{yYqzVJ{N zA&HXv*y1KHJgS;1*yy9`a|&C5DLMqGF-(9U419xt1mT7lpca+;mZ1~b1f2*Kj@k=` zZlkXIEZZx6TU1R_paU|eNWz*I78%(BKu$x@w+w^NI}*wlnN3?+AefbKMh3ZL0EChk!#Sw78O+=VSu1zIC6fI zj_`m((+Ckca?~V)w2MJmNS|xkg*Ms&1DzSvJz*5KF-K87G6LA4B&H9>QU-QposvjL z$yD5QqnT3R$aX{pj`i@gMpV*7B&?>Z=;Zv&sT?UMd>EG(Jz<#w6M08yROzrv!zqu& z>f#-e><~I5fDyv3Ix1@;g)#R5D+a*}TL6@STtf_P_>~$joSH{!>Z#UK4$+FAUKxCB z5;5pmO!@>sprjyLGYC)x4|a%IFEF#rM|i5Q#z9NVBradlimvQK-d{T0L_Bt2>nSff zQ6!Wcyb3{Elvv@ETe~Gn>Z=C{5%PG-mv|V2zjtBCNTt+8Wg!DV3#2b3z_PCpYASN2 zk){RUG7A(-3fdn&Xbf{MkPs}OPMML=GO)8C7aS0(l>|ZjBJ4}F#6_t0SG++2JJ(Ffh(J*)>;1-S zQgBDr$*dTNp6*~Dykwl%S}z6?;U>kV{)Qlq!Sa{UMUhboo z+UXrZ5=PV&8?)F&P89-CK>=a2&*(9^x+6BVaO5V8w1RgN%_EsAjVh2kA04@gBzUEx z`o07iF|zYW`6{AZG$4<*;2|~kf($7(J{Z(W;&hl70MJm~Q%trV!jh6H{(w$AI!@hf zfwmkKU2MqDo$Nf)s!~Qj6qN;KkS16Nz6d)fxG!cO2^v#g$jlD5E3{q^4-VUcio_60 zp@haRR2)iN$i*a53olT!>0qM~^pWcL=>OD!SV_lIr)W=sKvj@m$7y*de8hH#_yCa3 zSf;f#AO)Z{q=|-5MR%yK7cz^%V%L;G7TTOO?!+l1&|Enq=u z^a91uEbtd9Kr|_NA&VedoxSlVW0$w60%9k;h8D|kPgEf zbOKXWmKBs`8^t3?DL`A*1O^B0VCV%~%N(kajii%q#P%*!W04Ci!U2&`9*-m|5?Ulk zfiY-gdx-}bL}(*|$)zkR(gdHEd{^|uxoki#enemkLsY7g_LDy zxaeY*l>nY51y-Y_Yf4DiMIttSEs&{1w4r4zxUYS5ihRW&aqeWqnwU9~EH6gC!w*p< zk46<*N==*V7iuYNdPwfP`=hFgb72dO6({M?>^xdbb|eUJWX3d_X-2RqK^@_duL&Lj zSsneaz*sgKM4q-28GEfv=%^N%1!G@l9Ct}H?e)h9D(zrQjx?bAV(^28mB{SSYnex4 zlL*E3T|+QhOiyvAA{;3(=oE#z3c#3lQ$?gnd~_mI-H|DVNSQ}JdJrEXV@OF~cBrV1 zuD5MFpU*(B68E@+D*7RqX4HVB9!iL$pfKT7uc-(ZUW~XFp^dzwhbl-!FoKja*Or-& zW}?8105K>TcPwLq7=WhGKI$*MgH9%wlvUhl;8%eVi6h=MsqZ5^LPL=9Vpap91iH#v zpMu3MIxh^@O0P6@9;ylrN!n$0bU5cuf)pM(>8fdh$utMs*|ZVEr3p0F!oe{EfRNWs zG!0|1!d@VV1W!OGHo+*a{*?<~3a0_BaUoe;B2ePzQ9FpieK)lp0^7@G+z^cPi4sk~ zWO>1vf{}WWh=D4)uj_5G7=L(lL7%CvbiDeBz`W2w_9_aoQc5}?7dnQ7pa55=Rycd9 zsjP)3unu_A!7Jaq90p`TtZZ@NRAZDYrIQ$i7YPKwg-&RITHf8%I7*{NtYMdwRZVsX z02zQ#IZ%)ejjzW=GSL7+E9?R|R#+@NYDN#$PExkO!8NDWGXh?P zP8)>>G@UDD9FwC+i~!NXAF9HD666I*xa6h2XbDxz&=~k6og>Y~rkmc$HNj^}`y(fY zKo|}fg(|i%>*XWzi`Gkm3So<6Z5I;C*-OntWsAjE23L>nWk1MrH2Z~1@!R5VlS=%sMcJ7QKgUy0qBW-hY3FDCLtLFF06!)!s$Md_}Z8SE$^(N zWa9CzF34c=DvEFVI$Am!nf8JRR-W1-K}7OMNDMk3icsHCX^y<(hMtu>R_jwzFJ{W@ zsYI~awS1lHumXV{-k1UlIpRye^u$1fv%H)+#Z2fx3U=MCWWE{zjSSkZQWy(!x+F05 z6(Lh>5-2b24_i+C#H5r=X)$F@B<*#_OMyrM3%VHX8T0~zX0+bSQY?WP4es+!_lzDX zh+L-_n*2&%%5o&ynwKfkN=!hc!SNiiRZZ?7vfkMv!1a_g1)~dSzG)=}Hqp{b78pbr z2Dt!&M69Q!xD8CD7e9zx_$b;qV~sn_VqEeSgCKa+8VIwGS6rA`l0`f^BW30D&j4IV zR)LI8kCcWH6}M83cAHR+SG zXh{c0x<3568;~JR429CML7r^cHjw(Qgq{`Y(G`L_~2^c|XRDnNJ$XruU zD&$u_0i!(uBoypR-$9OmcnT}_YZI5#$ScI0H0U zgTft3f{=NXp{pomOzlyYR$JLjZ*UbP1$Z0^ew$D#GI$ndL5KqU{bBDz1EVNz7`Y ztS)$AoE>FS6pO_V2Nr+;NiU&NIW>ibUHE1rry9`EX@rT*8D74c%eN#yd6|8(6OZpo zjCdCzFli%@z1xTw{mH-=0R^%}f|enMkG?=DF2n%k5d@^EcJgAGM{L(MfJI~Va^Wyw znOe|N8imGEBV`7G^;$s7tA%<2P-*z08^$i2J@PJ1_*+biSm2sdcZ?^~!GwY&=|DXA zSYwy_Faa#0>NUYLjsqGHP;%7HTFFNAHjkXN9rBY$7qr6H0bHVi{6g3=RR*uZ`8SYg zY?p2&8Zz~B3g{eJ-4`C&^hg*E?VeIpy(};-!?J%1q?9m$C%@ukS!?TMMbYoV@Kr)= zY;x@-R057a;Ic*^k$nrCjm-!pw$hI|K4P*FW4%b^$O|14q=6!R+X)3a3CuN8`A)$j zWphVHEVvLk#K<5Sr&m%o)xMY2pEOLECx`JdLcQxJr(*#u+=_-Mjbdf4h9{E?k+TV8!BApR&PHH=jh z-HIN(5C`q4UOLELVR#B>G5gXge&FbO=NV9F3U+j^?E-Le1A;s7hnrSaM{#0^U6L{{ z=&g-(m2pEy`LJ5d{>%hqoz4!a7qIqsK_c)?kPd;q3v;nUbc+_NvBjj29ra)VkqjgGf`udMq~e5& z#MuHg@pC6-Xt?Pq>EjEA>>vOrJW^a0lo!`>WE|8yvX5pAI>|#ujX1K{8i@d$=n07= zvWz*lW2qDj6vUv<*r+-NpYG5COwoMolAzd-kPCr%WP!ZYlpT}EM(McEi_}nvR^tZH zlt$RHW`}WEsFY-_R01w=PiX)sBLSN>WWvsfn)MNvBecsCbpkpiNUGD#t)VPv~b z6b$czcLXJ;-gP<^WrSpjkUUyO$DH|rquC)KHifo;Qu#=Zgv~X1#3=dtFK1FtcWAX< zHa@h5my%g*7qc+Y(MpNiB!XhoI@5;{cl0lDh5*-m=_3h zN-t&aKoGFJP=YUm0I;On9Vo#TLx_yxdZb2Zqx2AFR-uJKs~JHsLxFK-tx+aVUDI9+ z#C+{(CrJ6CqJCl4O^0(AcIhLh5o9PM_=ru|DyXIsw~5lAYlYRXH4IBIG@4NF%U;YeRVUcWPCJE*0vMx2TP`Q66>U5PNt z1b;oHgPiuotopKGpI=Xj51k}0$Oc$*mi#gcZBKDq?vUg{*rW+uDuv9(G2Tj_Fp3;# z_#-bR&?9DtmCTA?z|r0omK8;^I!-R~9Y(pQXmFq>4ufE5I@ckD=}*gTO?>?xL`qoj^&`HFh*puNm7Jl z8Ej$bshEY~zA9jgr;KAP7+uIzY}hDfe)}oIXfSR%I!cWrVVm8erQtF{QBmbcIs{w* zgiF={gsMT{zS{BYB{lu!0;KXaR`@a-T+U%==p zB)?|3LyB1=^|VyVOtCCZ0W)qmY7H?UE!pAGV%G2$I8rbjAnC^h`lpTbV}bWoko1d}Nch2u7P|BHs&O<)?mo)rFozNgoN-0yyYIjMAR83-U~< zJ`t0bAgxhQ5t=n*UkVm*Ek}4#cv0oZBSDIRyc^0QbYZ1ogfQfn{wuCR&}Wm;M)`t; zR>MRyB9fPeMRmBj7L}OoA~G5W+8Wb#g%FzBMY1& z50C`_b|E1Q`*IO2=vac>RX7F}OQyn51wetC3m4d8rOHMj-GS-|Bd;n&23f{=;w&`>c#!HBp&Apoq;N8F z6V+85GM34R)X*`Y@oPbVC>)Qx+eIM>j+G8|4wg`2+;+-FphQIgMXN?k8RAOD3wpR? zFHEQk~*D8M2fMkK2oj+Ekrr;=d^aWGQ| zj;OdG2>8YtplpP&WnMsbtsKmHF(O*h)E{ll4w0CoP)%S>2f<(w1tz>KvrqZiF&#uD zc!zKS=mO9gPu63XcYHA7;iEJHGZQV~S_?>x7vNYRP795>er{GZ=x=t&XOG zF;5AX)U3e-HFbv+;Dct@q)r;z)?OQhgenTb8ZI$z7xMGPwNOn%_hF?d?rS3d9bkIm zBby@O7jr0~pGx!*Wfs3vpO0|%?v5(U6t$3_3k&RWJT40!61dL?FQ`oaP(9{~jf zC=q<%5N$WP(+g^i7|9DAaG_*Y(iVVIR5|)S0w@Hq82E%pRHmU{SKzF>VU7vwx?(A0 z#3MYcU|;8o1T65Bk@J8Qcu7D~4}1_jhgt=&)K*UCPandeH35?|=0-n$oA&oLUja@4xrKaku z%22`(#83$7Sm|_Xm%MmXYwHz}P2fYuQ;zfxWgvCqOTSGi`y{BYT zVC0ClxF8Xfgs|{p7h&|9fYY7y=?OmAXbbdkNx%T%3n9HbLNtt&5}hP2JgG4PF?x#? zE|gCIE~t%)U^QWp1)24;L*iorg(p+ylw<`XD(6+XaLOQ^S5{%ezn^*>r>zH(W1eer z$~C7Tb;PBFjL;%Cv7&>$T;MON@dG&I6^m1a^KKan)^j8^_^29dn!;(BBSQfn6arC8 zQ!Zkl3}PTeek;>EytoD>U7rKx{KtB%Si46(^YR74)rV2gXgX3ZKYbHpPrEISnlSUHfhQKGUsNFp|h z5F0%*dh{k0WeQbz;hokY2I%0=kx{ONl+%V-N~QIzGbC$zftY|-b<9DS3yC#-WTxX70Nv^2 zqxZScIIMvVZ(YzyzM$twhSGG~BREhSha8O{9xFYSxZjQlk!IJSnnLgtk^r)!9PX1u z8iKT{=A0=`xvM55SzwksH~rG$so+UjG6Fg;cCgMNTne^OIY^?Dy<8J^rhJKjZSMph z8Vbq_u3F=#A(7Zf&%GNKM4n#=sFd(5W<+Y3%pjoHPhr%^GN=+jetq?DM?6g7M#I7% zPhD`KYShUH1Bi~$1qQAj7dhz2g-4FBjtXL9zn^Xdz%QVL;U+uuP+fcr!$5>o3xE?7 zvIb-^vqK&+(2GRF4PqjM9t))20wv}Js&)}Bs4@a2_BEFU#(_J=t9WFDR0=IApdp@d z19^2xJi%h4B3cZ2T8d*jt}kM?dn8V|=tNHS7pQ&IDbyP*VAKe@TWa#MfPxW8lqN6s zVz@OOZtb#{{sB?B+E(+2@)1RSOqnm3%a zoA`;0hSK1m;6j6t4w*6zVfp}AjR06rD-#vfL24}#nZie48ebx$0I((?%lZys#Gta; zgyu*P4fHtGQV4CUSaz8aR$?7Y%p)L>CW|F546P(7QbWqXXeh{3`UaV0X(NNANvQma znHSvSS9Y>iXbI{aM%2zqC02rBW0on>hnZ5te8CtN?u$_5LNa%}^Md>`wM_d6SYW)k zPWi!tX%=wmkSSCbAXR0JECv-f3m_BV#9TQr3Rj60VR50RfVE6&JZek=B^?2%rn3Q0Ke5C(*;!EMn} z7%k6&M?&>oK!oT4d@F5KWm)j0Mp!Clso5neoCLUl^_O~ahXcZ32hQx9EcoL>fm~Z@ z1bE7wD&T;09DOlLDFr|uy!a)Uc)}@nu3^rpuvJseU+jNh;0oib}e6IV;?ysu5g@6A1%!$ zvd^q*VUrqurEI%o#cvYPb|V9-f)TT2&;T!DQxXjbF*jXkJuv!+&vvP)#<$lyvpSJ? znnv6CB{Cq@#Xf2}XER7@Pm3{@3xXyRCD8jc2d^M33z+LxyplWJke!nToQ0}V(8ftP zxNkcONsuFgz#|bLM8c*OD1?^ck(2;53y%`<4pLeQ4I#Nd)K$Y$He(TIpv@G;V2$W8 zC>PriTMt^PPi4)r7yvF!y2P@Za3H_Rp{&9~RCiF6;*ldjQ9t=Y1CmTBnRI>>`_r(2 zpg5j`Y4(BFeT~DQpY?dr8Z1jznG}vYswgkW2+wx`Tf`%Nc1W2L@Y{eu zGnrZdZN6-G!MiY6P`HK^@4|)y0w|E`FeM9hp`~7O7aqi{l#T@^3#SgrN+2ZZAgYS1 z6tjv>E0hD~b(}0r4MGZkJh8xfAqg8qo-$>NsMyCqsZ~uyR5g9-j+**X01YLUCg!V) z$pS0K1Jw}>y`}21Fe7|V#AM$HAW)~1ELEU{QXpcQofl4Npd4|J%=)Ny!UiMQ%{8ph z>Bx+A?t_#g9K#K24=DeRMZ?lDlyDA|qh9tS3@t@A37RQ>oYCwwki4g90;acOBdxyv zi;6+o&LH&@HkB5VfcXNfYj*hZFXveni8Wr%_B;|F??Tn!8pMK%%L_fHPOQP%J4o5I zQ986*NCe7nKIkVl+9(AcJ(2=gSYuQ00wXwnaS|S1PKo4^a!L*Cv<~0+m61dg$f4KK znj`o1lzNdA9|cJY#BYsGM_MOmqD`U^UQeN%)0CJsjw6Sg6rPUZhENFzxj z0hgNqQ8=6tpYfOe1zSggcbRVJI>MNYv$s<=j#U3`fRXaLmmvbJ5{DH-X*OqH^i&ZwiV!Cr$y z#f>?^Ge|~6E7{b8eMebl>41jnBnA^7{i>`-Xrr!yC%J|R(z>RxssmLXDS(D`fqOUr zVI^?NReh`w1I^*xh@>iF03!>!Ol(r~NaDO`WZoH3Zmfnwv%}fd%fXBb08$2vy;M*- zY?_ZkwFO;uF+%EVTM$?nYArj_T41~MU2{Qlc^3>Qh_l*}1vujXFu{m0;)E*hNrXTV z7J$Rw9EStrBo4Q+NX4z^QBB3EQZO+$RY$qSEL1p0A#wJLnO(fYMB?NZ=k*|L$+C}t zg$8cmjFK@zdu6KbG*(`W2$d?z3v*P5P@5?##RvhHDLEA_wxF@37;!=FRLTOCWm(KT z!qX#a?2e4sXG%ZD3KFeM_$&*Hg#=aC3IPghM6S>rLDFLE(n>P5%gKtb{(P(oXyc!KVd^fjIdy+47^6n{Y z<;9EiT{F&+(kqh@qr}n_xdxXD0$}X2EF@)~XL`t*jPx?1;-nh&{USwGbRr;BWR{M_ z6hl+H2}Us+Nen6z!Yh!Yk@z9zw{+I{^^Hb&(C{yRxS-WO5^zq{ku@hhY06!KP?DGS zS8lEel<{G}QDB++VFv@jB|>wM1t@XqLWF8WlMOXD%`y?PYy=C*dDUKiEdUGSJQ`cd z!5}qYlMC+X7z0`6kOoQ_8E{p~npD>iC@(W8#kSFj zMLGJ?Ej&%c4%bALbh{5j9zmp776XLHC?!m^tr0J57Gxwm(*zj^JBh<>ekoz=O&rSR zOfDoVHMmH0I!N(EYy?xY>pr=os9i8o6s5#8HB}BEdm%ibW=GLQi#Ebex^<9vQWEbz zvL+H$mW~z>syn=p2zj|s2zIjOUGHOTwAaOq;3xe7jsq1O=$IUdl3Hk0*CfSynQCOk zB|#<;7O6f!G!#W*0d$H&rLP1|Uw&Hgf}8b@xbWwc5V@lq zq(&hGAbsh;!L{NNHz2U%2S9+Mw5Mzm2WJVLD>eqD30O&KPrqh_5;U|MrII+Y6_qg5 zg6vBh%i z*CAn$U!h8z3r2Y7#d?uIRfODG3`4?#81BQ+wPYc{oq}Si3nkDC0yYu!K0uiA2=UZsN#Nb{kMDt+5xB-PSWFboR8Si`1ZI)w8gHCamsI0}KHKo$lBFrQdS zR(L?xFVbWR1F?dAxLNEYOmN0t#}#D~kG2w+1nmN~)aV1Mi`-4>0b3b)3C~7pk}o!0 zh(}j+xlV#Ga-)&(;6UmNL z5^xli6j??Fq5*TAE*SI0Ue?^P%)2!Si(=6Pi3o!!Sn^A%-Lf(-yg_wFTI#)}FHO%*>JYF;;kY z&6Hdq<0hw2vJA%@D*UJIR7%)P$=%O2p8AN-%<2om#Rzz1N){l=b=j$iINelMI!1M2 zqKI7JGq@xrJNVn~DZ&e0p9)5AY6Qs&!(NVLr?sUiNHmZ*c(ntWb?DW?1Sf{$JkdaP z>x~2JMT_?AQ*UZO2Lzzkluy_WoFWu9FYKc+J3e}M=2S1`(6;PrG+E10hpL6P@=@E` zrNlHJZeYX*WcIG~(y`K!7<6!!4w^Z?c|l$mYb6|0N@W$8b_OZXWMPg#G6eiKLILQc zhIPm!?C33$ic9Ncfh~ku;{(LIpL%TP1r)YBR6|l;-mw%cvc>^W%Z^#sXpB7C%7ta@ zxK9Z!mOeaTNN*h#c!wc;R9U4^OG~N+N@JS%u^rQ32plw!Ad0AT^hf@Hyr!vrZPnU3())yYueIP8$-BZ)JAky8(nadPVLObUnq zYc7llLaNH_pso`&q^c{ZLNWkW>g?!9b*QeKZQ;;HKIMN$?E%QOm1e2At0=LpkNp ztt1PdEmJHdKn8al0N4>ck0WemwOHSC6;2)bMHqMB$qO`;PgI^tMAoDRb4rshFk%sW zT3%qNV>4Svx%{+K}^bUU{;?3N@?^sv!u^53ouPtbsr(J%R4YpP6}Kg z73I`f?CVWsGN#F_q_ACHL>O-&FCC8rKs~tD0)o^mj+y`j4u%jDYxIH(66X;#bez2o zoWOBJR3LXa@=@aSjow-*aaBNCFY&OWYq(P&#B&JI4mg8;F6h7Q`iozQK){RSVud2Q zMg|`>5m@L0xj-$s!^B7Aq)2K_*ohAixPybHri6qPZqiX)%I3!qeWZLw28ChAO79k6 z9fct;B#Q;gpb+#>QG_QWwIEZpQAk>hLAc=$*_0PpED(?CYoDZs7iJk$4(bg|$6Cbj z)Ovn>G%t8j6x;3fl6dZ<&ob(AY8iypQN45u7uZV);jB8a_0)ZJl$SW2W@$3-jMyS& zPJx3bW{?_rFnz2;hA6ihAPW|vy934AY(tZ}Z8DWVbq z?JPn(F<%_Xpvoo|NfxBcDyY;9VhH#Wp;+u_Pp~K!7wTde3ozl=xCa{Jcmh@K6t{9{ zG|(xo2C+pL9NCWZH7o{d8ODXAK_nI_P4d;Av zY6PG?Wzf`GfKa<57|=Ol`H0no+OI$kiZ-eaYf9{+E&Wih1$4JKiMjZZoV+;K>FD|e zP=p8$(DU;5V$*~j!&I7T%9Pj$g~`IWu=SJx3>2f2yyzWu5eyvsb`2}~gV;89UtX%i zg#`d1kL+ktv01=^IVd(==7L!+*wm_$f;RqxOTNktDAt$~07ov2Fuq6--7}}m4x9lv zv1Kr6QY^DZT?7D01op^KaHxhS4E42&Be`mdM^q@svT`#>2e47t0tS(;7Y|j@aTasL zucNiSP-RCXM2`{mWfP3n>oVJ6t~zX5jH@W&8pt+cz6vVM?RFzOj9YJRA^4HHk*)P$4w>B9dwcaimmRJucY)T z(X$KXNST3xu^L%akg;80w!_K-0q_od+}0E*q`BB3{uVgi7!5W`1bV#a0u87uQOm~; zb@Eb5O^2d_0SGBTGa0#QR0)z_-j$O0>w4Gh6O(kZHkTKzR(cEACyz!dc3G^}kSPT! zgvLaM{!mda)RzktQ7otuB?Z-$?xTSkDG(+w-cMDyO_?ngBvJv#`9#>#p}K+*rxByv z3Tg}ew3jNnsn9^<1%~d+*8(z*s50e9{17JNbj&}?G~GmqLXAQNVoEyhC?7Q3w-Sby zsWR6Ym436v8Yc|~ zA&-Xw7Loug_WPb-6mc}N=Hd|*ahM~3D2jM^ftZf=6vqHAB;|&NmgH1ANGN9W%R43; zb396cYWaUrR94D>CzvDn@9)u{52*p8w&K^8&WCEm1;5~eut=zl^z}0F!C|zac<%T? z{x}YH6x1X7uatDySsmH5Jk_QDRuW0M%M>i=!K0V64Z&y4g-r2{3kpmb;DaspvOoYy ztG>{WOu2R#CYUCU^|}%QPMgd+94fR6I%zT5=xf^ILhfp0fg<`LLd9Lve;O!esH%~V zoQhwNP_ok8fq)Jbq4lK31**7Xv7|ICb}65Y%z6pQO9^1YsYccsa^yv8xQQ($uPsEC z`gs9R%qD`oyi-)@B{GPuvWA_H+=+*__^47ET-~LwF9eTHTqu@Egy$xqlY*lW269Ss z3?Qiq132o48wL|RrC@=2b-^j)C}%r`@Ke3~h=iQ5)hWi4Tto{EA8|wQ8dFFbmUk&ID|aqH zjZ2<_TJ?qMnp6@oJQoIfXdEqxJm#-!F$;;-F{L!V%6a7Fn5tzQejrA5X@!HIayS8M zH>Ob3M)uK9MFgqobhLi)RLclYFfN=P6;XtixfZ8xk~@#Q#AF;m6*hsuiV&jf@~LxRl0A?YI_?*xYmtWa|}3*aS*cz;qHe7YrJ(BPX*0>%c5dD}lg~y?)m+ zVuT?D;_1FZV7L_6p$00Rn9B~fs>Y`3sG3X#DK7-DOcRlJYhY!Wu|<19N+m4D852QA zsE+K|q`d-GbdOZT#OG0*nn+Ph95fJ#OtDlJii->y#7!P+TPYwR3pPp_({K+geDTW# z5O|R_WY7klZbHm1fD2Nzew6d|4Sz7rAsG}PA|N`8FY-%;B%)t1O{Gi$plN4Yzz8KY zP!5zsO9vI_WF^)|R9aU`*>*$~gLZaKvxXfTF^X~6T8Ub;ONmuez$B4zF>_}-0TdW< z@Jo&`+|(%yT8Xr_>&NHwNvVjlux;^_eY=1r82axRuTtRkbpYn-VprqCQHppnrKZG6 z9lf~oh|`ZX43vf2@VKD*1m%{X7B}4af(6MFcF(avQ`LaKox*c)p}A;2!loz zyhtBm1f$J$vF$z@AM#>HR0P>0*oqC9jCjPgPM55?pszi32c*)WA)3x&3oKSCjv#{} z;8Jwod^MMN^qs~ebec%Fx=%?!L{$W!DJ-LzdRCdxPq!+rmDHZvz>PuaD2;QeGzFmj zg$;APkJ|_%1r6f6fNv#`R0!|D0jaD7i5NhDw(5H)N7idP*CN4lYm|m@kA&0_lt(8? zlmI?m)p~MwLS{rpa3-EI5;JaSdEvo9s47TClr{TOW`s?d`YwR&-qjH@s*lx3=vsRM zR)NG!@en|9H5#*WR5hh>A#6CqM9dIFHi}BA@DRwh_(lLUEdU2e2}wmrjZs2Hd4Yz( zWSNj82uySphKfjyO);2XeEF#K0H+uxIH%&2n)@)37ka1>*NsR|^@T8aR2^|5Enf-* z4TOZ40Mxx9r915PQEGJ68lA3{tifsoKx4%2*Bkh290dZGcIFZ0kmS@xa6yK)BePn& zBXV&njd!_#6v@HZMx_LZ*Z?Iv0ZSZuocd^&5;G1%FGKc1bB5HcyV5KItV60GN;_pp|w9&=nLDe)Zh-B_R?*ps00PpO};*82Ql-gvU;E3 z*Xe34cYmG`iQ+nI0fccQKwcIzjvI^{Rg7SflogaO*h`bQK{$(8D`n^Y?EM`%O5Trn`QZ-%3h&6ka2FPB_sv!Nh1~(9fabN;R9aUN6 zB*{?5)?3QE15rT(ix&8P8&TX8d7jaec7?sF4>_05>hrVuAxe7afue01yTl-k2>8Q z<%Y1?VT+P^$8GpnDNc^KW>$ED#{`lk9x_f;i4{{A0!%l(a06UIGGaoNsH(mi!PE3% z5JY0I3-S)e6y9RwG2G$6smTIlF~Ac_n6D_pAO&Ve!(AjC>0DS@%#L?u5~_DzY$Wo) zguF*#D+N>$o;tGQTLUhEi)?B@6%?e|WFrcxmynQYK2KNN-+67^;*DOG0H({<)91#|zV#mhLS1O`dQWJo$)w+^*KxuG(wVOMtBU&H5 z>kDK9j_qtW4o2q+q+Y!N9wDZTEJf1W}Hf!GQOnvAqZ`#G?8~x%p4JLF*up~YC~QC z;y!7zfE^2b5g;-y;4EgLA`g1A>882h&k#CGNGTO9rB{R$6Y!j}*{&nNMQS{?%&a6( zJc?F>0N)yQft8QaWS{gwiLBv+JFtbk!od~;nKBVCP7ro4>@EGApI_QCK z9RNfHC=x^>lwMP~k6KEsH~F%XS(SyABey9=ShNEH1h3k;k0%lol?4{_qHxxzxT(QD z8VJ&8psPqrhm8-5`E}HE2hXiX0D6dr-XWtj{t1wO3}&(Z5;oe%!Z8)dI)zzer~zGL zg?LCJmI?ztA_NO6DhFf$tA$+)KvYcmZtE$WrL2xHx1ATcV-&Ub$a>rZh^ORErwBlS zl)$@2a7TptTBmTT%6RTKOoY@jBFA%8aO|On9`PtbVKa*=K#-bIZW%3N4HUGLeF5uk z@BEr^Fqbd6piz_%i_!!o5UQudLF9sAU*n5YEubn+)d9YbtO*qwv`F>&Vh6VsOSvsT zWA^z)EtM6IZ|nNn&prkRy|P{+1d1>UK>RK&!$vlh4_2PK#$NUHEf0%qbU}eJ-?UP~Ho0RT2*#y4!W=|o)>CYh3(%BI8CnFkFz0M^9N+Q1RQ2lTw2!4K&@<5fi8%3#BxMIP|M?bs>Ol5_tbGgH35c#MI_@m{mp_8tW z#73<_|2gGL$ElYbg-1O7q7DRM2oDp;wZBKG8ul7G27zmP(Tr}`uDDbehSm#=Usy3k zW}V?R8gSg9R}zO?f6+iNeX5;_8>hA_E{GJu@FExP$X9GSiz#T!9Wex8#1m(l!>m!_ zf_TE2HLwT?0q7yncodSNDj0>HXB#?mtLhQ%;Ur8VJ zmF$6qp(-mgA~LG%{@(utY(tj-F#trK)TLx(NeoKJB{_5D3@e@DMKJaS1Jn*T$(s@g zDxzm`a3NbI2x6kr)P+!qaVm#laiGndHAolSjD6`KCsir2FV_U1(J-zw(sGR;x_L^Da(f-r3f7-lX7s>sVn8jvtQ(F^tv`GC;s9L`OA5g9h7Q}dB_7pN?sc|gFh|HChAR5g_ z{RIcZRH5TgLzaA16~-gQ7#h`SNnw;6`Xnbi4%GW}@dh-*3O_0tWm zxik;4Qo+dpp}YuC zHaNrVbS%Y9K=Co<5-T)7X*)Hc}c@6Z5HBl4@l~6H+>`qBB2}=`C&~h{#y_V(Q$l?1~e>AxDO z0wThNO0^pSm~Om@5V(uT&k=kMm)T2~}=`8q;A&81h67sX#!P;N=gL?97Ho44MTQ zv*bpulnDo3N{}K;MjBFW7ckcpZDlZ_I|a6ez*p z3p}+gdLhePWqO&;ql}a}y@AePBjxN=07g&AfCk)$33w&R@qo`lC59c*Xj0*V*iyxG z6P5f20?MH=fMbW53q?Y}XVehD3&rw7yNpvbYM^RSRj3!yrr460fNX78-T@07%dtv^ z11k}*BSR9>r;=ASBt?^wb(Ku*8pJ|eh}qhjv5zTSBNV@CS0EX1X2ybR@qw>%Z82E3 z7O4<0Vdy@rLEBnvTuROx)*?kqJ5-P*b`(VwD29W%LP0~-R+O_0!KG@80?mc#YL}9Z zHa+JdKMBjnTDS%~v>B$9$iOcT#w!QoKH9|;>)Jv(NFiDep{i!-h$B?(i9TivG0rKD z3&Pe!Cc@AZW-A%2u^^cM$FC3~TNg?l%QYjVaR3njqTT$v8c?8y&W#~j6M2*$i0laP zmnZJH2hX=7K$sgF$)BA$1!N)zl)G=nmr*L~v`F%VxC$aaxN!yerU0I2#~& zrRfyb*E!io-(qYWU?EPGaxi)ZnhS1h*U+sb4IKU{Z4F2v+?x!PuWdcWtV|dqWOOty zl`!b^g)W9L9$kp$J}nW6F;In%O-KI9lp$=n1YXbzQl){8n2i*~p_(CR6MT91f&<~1 zVhfWD!zXVy30_zeUF#QGMH7t~>T}?F@5?X%mVvfGt=;fi{S4 zx?l^kHJ%*_sJZ0K{B?>{As<*_mRyyfA04Wd;2?y0 zWXZ|~flX33-F-l5F{x^E?FJbynTRG;3UqA%LUopGEhGpe4d5QyR(14j3}eyYNH>dy zb-fa>tXT{;MvYP>Bz%EO`&-m1GGWDbE5$NNt9bX1f=GysusH3k5X%{feE^CkbKH5! zkQI9bH`jvrp29I)w;UrC$LgRE$0L3SVVeTYt5*&R@ib4s0)ph$w-vQ>61WMgU-X*Q zHLT^V1xa+USzwkPpfrLh+DGu5M?b0yIPzCMb~K$Oh|ovTJVGw#q+8`124qND3ok%$ zQxgeDdwPxFy<1ccUL1y6P^=Q5>G*c%ZpBlE_Gh zr;iv!x_p?GotH8lP5F=M_BE+uP=|ul(!#0>j{ujaeHl2qo6qon7v!oIF%cwfVfC!t z!HG@@3WNZO!qe9rAJu|Wk8EDXV-}bavsu_RfToWOP#t^pjndN6!!kRv5lBpREK9-~ z$1G2U6-~lw7n$B6?NMuCo z(|MjXuXV-ru6!4=k{*tXM29>Xnhcddc+fDiK2?{ePLCJ?J|y)WzQ`EV7_^F7bSS}S z5QqU#aE`4I3A=+?$f%Sl$y|fW+@58;0n*R~G_NAsMk`?Piyd9B@BAR3uMy31eG4#A zzpGa?hC}LtBUCibtYj=pn-vSrMnt0=;Goa3({k=$1a9s~l~K!=mB4tro8%#s zgn_U1=!&U=9nk=1dX_^FH409URO%zO%TrzAC=(0tmuH7nk!Qp`L#Um=uOEs2-~zq5Z_ZMkrr2%20Za-q6Em2yy3u^ ziBLdg0tC5oFw0!3_R(~UN?0;f9iikTi1Bhzk(i2wxl`HmJX%;9zAT6@D-%r@5FPZa zWbUK^xw@r2QaBGcQrgHx8BE;N(O!75*XU@i!L__A&Gw=e$23{C4XUZ%k?(ZP!> zifDFcQBP6RA9fjq1HT_zY*mhGzIyN+D|)I}yG+UW@d5r~!iOfE`K_yDr*U)b6c*B7 z>g~G&6P6O88lWkBt)OSqB`l6p6Pt3bKq5&f6EdohA(;_yCv%&NZ-7Y4fSpvZqbUD)(uN`v?N6>wFYr;LuVi)q1fJc1QL{PY}cVkdf2*~7#RuK48kuAnfM>)m-(|EO5-Memb zQ)D0M2TDx9crL^@I2EZtUI~EZqNs$4k#=zD*;{Fz2w|l?eWj8Z{HuizuxqpQqcl_@ zGMccU9BaW-nN}on9f3Y+^AG?Ms$sg%BMQe9Y`yba)bIiQP5`p9mf(UIOh_t40VFM< zsVyNl?C?OX3MgDKgpQhe5Mqe4>4;_uSG3nNRj|t(2ojSI%weToy$mkJDxC2OsL^Oi z0=ih2u^J`N3@D^ZPF$2t8ILjw7ZZ#UQw|%%2zYs;AX>?~@{zerw4s2OGng5&1YeAXYqdz*eL09@73?bq;MAy7@TCfoC8JX87>P9(I)d6g62V%CHrYL zUCtU=a2o^?ZOS~s6d>3L$jJaBl?;EFD3E_j(q2HN1cORdxVC$?7sw5CrkkS8p-m^a zqc`ah8&RptU>EF2;TBSz1Q2cGlcDk@Y-x9lM4wiYOy>l(Ydxu|5fLOrd&t$xuI1?? zeyuLD__%OWn=nSc0U{2daIX$RbYdgWL{#byqkt_*_@=;M;R9qa`o|$^XW&3^$L}^y z>B#kMLM=w>BPSdeMsj`vunX`#dTt&DPh3kh`PX{QY%6d0<1vJgeqISh^A}6tNq=lNgIKR zyf8ul;y_y$!x=2v&=NjU2y2Cpi9w1r8@GT~xAn-U9Z-_wC%6bEaxISt0~au4p;q)r zWGgZpY;ufFH6;m5gXzbM@42=FhnE-z4JH-V3O(`w9aJr+BXp3kypb|-0W?IrX+Vyr z3Q5Y=M5<_K#GtvFR9*6fr{8eo07`tR8A5gjp&e{z=pp!O(HJGuu zuSum!Z`M+mJL>hwa&RCRfY7-wwgSpc`3xVeI>?3m-3K+5V%f`uZADRhMhP}GmS{z` z4-n^eUuQbold-P}FtNkR2P|w{&Ja_bJ4T|wHwz09ZC(u=<4KxMNC;>=imR*3*9U94 zBL`nMpg}nx@=G;LNX0M)o{|k?3nIu_7f^y`u_&g{XBH&Y3o(`pPhB9alYs6Jfj-^G zDe>z#82X4%1c4NW`U3c}@>Eu8hcF80Tv(ccw$qMv?VGMGXBFn4Qa2%Bifxgc)FPmc zGZ8-Am53q|5X6^^0i8^=08%6)TZ~x6a@<}t6rj&9X{MvViYm31 zb$&oXHBl9I&8Rd9K_#)VGj(i~OoTuZ`G zrCWC#&IO@fZZEhWf_ycQ2^W=>`+~4*$|ra`g$QzU-|GHbphD>jl9o17oS6xL7+9Rr z*uGHq3tdAD3h8$bDY@h81Hgq+wMRBSzM)?c}m6|?!?~&p(Sgf##8xNQ6bc>G;nDX;t_`z z(Z3~Z8CpX$8jy*!A?A@gSO&Y(6or^;7_sSSYndfOX)t^YN)ni^apb@wxEe(RwPP@} zX_$Zkiyg|>9qvoUS`c6ttiz^*GE}<5AyvUQT-#0T(Rxrf*>TWeT|h|yyyDOpVK~{E z?k1JmGW3GrGST(a;#lR_;3Jx>Zm^d0`;Vsr(l#Lh%0kLCog;Ar@0=3|x>z&=Xi|S! zMTU&dW?_upQJNJc20-xzrw}sm6xuq%g(IKMvA;PA>!PPPvi|~E70~n1cSB&3r~)A` zD>aJ7x?S@M7rxH?uZ$@Ui#Dt$_qZwpd7gYnTn8Wx=NJ^aIJw;Y*IMIT2wNskQfA5oUlBTG%w#0H}(90xxtxNl{i&HFyE#!bmZp0SAhOOoreN(7dx^#cEqQ!%)l? zwYpvq4SM2>W&pJ)hh3OLha_RTebxcVikd>b)JS+1@=iq{fOfT~c~bWMJ^_NQKFdxr0aCJ1<^2RboD9OVApKmRnFRkm%2kvDzd+5UU;%rYw060X5+R4=rJ6__#_E&D$a&AEW6-W zgd_oJK-EH&j3x-E3+ZIT|J}qRGXXL-Wd(4_$V6oNFJa?7;)+CQM-UBw_yrWu5Fu^< zD=Ktb8qxAcf%FW}Chib6Ow1(`@BG|Cc%f}MTg#$C3Cjn(fXrIm7f`_#F&Qsa42q$@p#iNgKpbe)o!jp-oTH8%1S&p%g zleI*HgJx_~CTTfKSnmJ?3&{`Z!V;k-MJrSZ8((hn78D#Ld6Cl8VqHI3Ai@Qy!-~ynZQmETkQJ#*FWV(^Ha3D^(7{cyf~(X>1!6?)LJ=pW zkqQ=UXVeZoW+FrUDiE#k2QL#)0*j+nyx1eGNL_9!1iVrOk(kDN3K>honXsWMl#ene zGs(L{NCrnJ+>~rBu+SU3CVR&W$tciG;=z%+w4tJ$OvE90ea%lawVM(!z{@qCx1q?e zzazjzK;u>^XLmqOf;GcDN#ut{b{5`e^H&MHyh;BBYyh&dYx2WikVwV=!o z*Y*eos|Bb_d~HSi92m$TRT}?i-NM>p{4Bud{aX<&pduHJG^W^;P4~jGEu5LSJl!JQg^z~0uR+vKB$(v@zA11R`M?U=^uQ&=M5{dr=1S|R$qLYF zX;_h>QHx5MfJ}%@0svtJsL>Iu%zaMoqX?vHI$?X~x3q*HzN%S- z@@_gVYz_XMt^cPR5b0RQg6+vzH(M?!GnN=`rIcaENc+wb8 zww*bF@E_GV9Rc5WDCFujq1vq!$cSi9$*cppTGb1!8qhHI!q`nAMQSlX+A`D~Vj4zi z(6A7V$pM2b9fp8XwKkLt*ioYdPJ$Ye(}q666KMKcD22mt%4@3=|R+}4u+Cs z7nJGUDnd!XnM7%Bs*a-)t!%}Vq%(tecMQgtCV~$bB}RK5!4|VhmYA%F*1wswT{3od zO*v$y5E6wF!Spv@Q%H`dISWYu7n(v4?`DbVdy^cTFtr1MbfrQFAP>9H^hYu1BZYmKmFlACN5kkZC z&OGu_0;q9K*nq`|MNJgPatO6FkbMjAWza8awrpu@D_mIJLej8=8g&7XC9fQGD8xv~ za2uV4dP1T}t60%DGLnW5%6xPUI4iJ}RQHd$~%z$%)H{2a`{WeO93r~mK{qwu#^ zAj{KJe~@)YUpq=k(vE{B`EV)$$-W)Uql|(?*}hQjn+qJ}C_(-N32OLQnh{BfyMwIktpehdIH8SYK#A`uioL{?9&Ny%o{7yY(Y!-Ije1#MqZ=>q z=O=FZXn<_>o#mt?thxSCmI6T}Z6Mfjz|L)yEH_O}au%XP*M_OlbaEmso&ea+QTVk4 z_duoGBxa;BbYd+`!B*9vhNOk)P(@PJWrC}VI00@6*cxxH4H{M`kdLt1leFB?(P~j0 zuW;cP6+b8X3g8z!)Z5F@Y8KFtsAL}nM5}dCrtw9VB%1I=@Y0|bOL)}w>cV|<$=EgL z%j82R(1#uw4FNApxJfZEdgk}27bMVf2AR}js(jFX04)W|mYhY7)U}p%lVe8Qv zV%DmVVFk+Mr5a6!TFe-^Aq7w{)B>_18w){$5-iFV;LJ}D;cK}3$qz>WG|P%I6r2@s zBpu`gC?yUoh*{XdnrLoX0tiyF=@5^el7ruBOmx8wEXNQ;HkS=@Y`+UhTbT}`vc-aO zWJoqN?_f`;rbxgso}8T3!382k(L8Kz5U7!`+z5#pmC~r4XOcWrI{i>IYt!THEx%9;6z@EYuzfMX9V;N8i=Tzbl zl~g??HdT;^p7jx@kOZ2MlEJ%5O#!BggypFET94zMBw(mUVsM9tlMvSpH=PRs+GEtp zG#zCKB3*YNnQXX+IC@9I8iXt%2^>wW+ zYz>pGbEC*e&2-nI4%-R|?nTKwx)nQU#wUKZW3k1^^;Atwc-ml!Gx&T*%T^ zGEtGR?$AT>LxKDuKr@n&ykjsr;A2si4r&l7-x!cmK82{l4k9chC0qpJL^|2a$6U$d zk>gPd3=N}yd6EI^(2ua9G$O&5m{g%aOo?N4&(P7f+{MK|>8Do~Lh(t8P1{B`w7bBT~2+D8&HCW+5GahcWwlk`0 z%50V(4xg@VJ$@hH6^KK(U+HDyqX9}5%7yOJ@A^xzlBZH-vR_(adO%LF08#{b0ofzM5Si@BLl89iD0%Fd0y-d=#Sqlx4z6i{1xe4+oSBWdFLPSupCln? zf&`TKAd;;!XJ}yDM+5wsKoIu{z#|}w&*TLJ3pBh`3MI8*3NM6VPhr1WjA>h4$((^y zETm<~Pa%>~f}KS^jv2rwD&(pHKH8ZQr~;TUl%(d8t%I=+Qg0woh{DBZm=3h2>k%6$ zF`LL;$wMfur0do0-Az8JB@NS+(3TP_#*s{ftx}zWL<=cFzuOS07i$U;L0h>73!Lp@ z;**M-ey&k3sBK1Ggdq*@EXc%^40#Y~5Wb2>1EA-TWRxab0gVA5xe^#~-~cW-N0y}x zBXvDuISz~q`RiGNVfgM#h8WbSSQ{>I*pZi1!K;tlfd;rx&rbklq?j%^8v$UMV)>FW z^hu@mN_M1$J%unmIGuup4!tV@T%MxUc!T+glzcTXo;zs-`jwAe&f$I>i)H zgt-Jr!e&HZsC07~)qgEv$ z;=u@B=o3nv2NbJMc|yqO6g2tS5KaEs05lLP9}7{1!C0m!RhzCfvX>Y}Y%u_*RX3Tr zXj0V?a5hrQqekyoOI^^GgGC7#4pIdLV4dh-#XSs$q^h}-oHd03ota_ITHSY&0i(I( zVI&fzs_Q+~DYBA)?x1HH5Rgmw2v3@~7ic0-a6AjiS(3Wmbh-I}{lD(h2@Su}~|JY1^k&F3vjMIO-uocT5!oFsi@l_nCfO+o$+KlqZc zYxwgn#e#*&Acc~_(g{TE05Q>)?e|#u$ju`xNQuTMTzV3q9|o(Urr46`P*K*h?`S2B zz$#^;5JgEqlKR~-jE$2J-Z>8zn7OkM%&n*^;bwKfMhX_l5@HyqYA;f>x(F`h10}uB zhwX8m5`e_`l^Ox!9dQ~Q5yY=*GNW5s1(8gk;kq(GK@yBeT}HyzhT;?fcD0!0U2p^F z$GdXeF%0Pn7YC<$L9ti{m;S;R%#{FD&8r;SkX4i~ZEc8l(PdKX7L3pR!q{C zw#`usmt$6{!w6)o!U_3E)ajYO!Ibww~;Z;~0WgB6wf%?$`jpiZZozioo|#s=9_kc%x{NgpWrCh+{d_ zOA^=8(~mCntiEF!T2g#BH9O<72k|I4`%8^Nv>w>;ZbfL=*5yw;meb?D^eM`88{p1aq$htF0zr4kpJGXZQ^gu0oXM$+8G0m+ z^f-whmxQvYTIkjkrHQZQsfAP7A0ez9m@^iS&}0gALED8O%JFLze!V15;fA$sp-)FB z!P2y(AKOZ7i!`hRH~j>vQY;gX612an!R{jw+Cy1{^w6g|3?kRN$U@l^ysTKiARPtL z@YGr=@=d`@hJLz%jJ_6Jwh(L2f`blH6kvf#3F?BIPLZK@5Z^%v_}T#r>XkNHG}mzKm=pmnXn zaJV1!VoIuv4yQP>6{3v0!pGld*L4N5mxbJC*W3v>3Iq#Oq2a#&h?zV^fV>`e-Xk`i zAU8hr~%a_x=*`Xy%;4dZqt_Zbm+i_A%EJ&@#zZqkov zpmSd)@HZZngbD;KfZc`mw2<8g^4Mg&u3?S^%V8YO*aU*sARA7S#R;o~`@ThHo<`!` zqH>TGu^EgkX}d{;YV>lGGULcddD4}eAab8k;GMT;DQb}tXP`G5Iu;OZu=LE8x%>7b z0waFNQxubNK^QoNg971C)pi6In%5Q}J#%SKR#>~POYhrc4Il=(g`2fh^#cV7X=E{xj(yjrxK;x8k*j@t1itM+ zXK54Vs6{61!bDqu+|VZ~55AZ9tt zN*GYsp;7?q1!=o)3HK!ic6@on_T}t8o6^pT@=FnO;Js#n+AtNt(28=SbDI=)tf*&| zAbIx2;FM&6^SjAw5G$aPQe8k5TTdl?EYSs zP@v%0XZw`qBb^AYUns$fesxhSNsyd7%$6bZTg%BD!h*<|4%G-!@-|GTFnm&!48&we zIVK`e$p)i9Ay#2TB!n9MYiS8Xm38|c?K*%tio??{_ADKV25=RF{iy4Eieb`I7n6Zr z;@1}pL2v?gp)@CIgSDr3L0Gq#1Or$)v7(Q1wwA$m8Ko)8T;;egF*n(xrM+w!&;Yd` zl&okK9bvq^fR?;t7MqmM20PODXf3Qk%oKI0Q48n`fWQsHUX3@>c=^K?h9qh^iAml7 z#c~Oo2vb$En?8!^Bk_xGUEPXJL8yo@KUw8jvP!za2awl zWLXQU#3^B0A=w~T`3eCeSkMPycMOA}fb^s3%$2I0f)Z#$Bddt*Sk4_%*KvR{-a8>- z7L^LOmX_fsUJ%BJrveh548h1tStb1|S!ZcL5%>%+tpY1S_#S-%1jSkp8q7)pB&tC7 z#iSE`Dnl!3b~nLArGQ18XjB0NodrPMtqM;;q%I%f;0~bhrFMnj+Hw@)<*AQWGzD~R z9OAHj6adPe;+>WZCT17dfjl}-Ko2U`+Xmc^q9dMyThVFRDUoCMe z2NRWP$7ir2OBgbG#BwIOLmqSjJ^pMk%hCeEx=)jqhP-!}TUS0DBNcH%@&KI|PSq%F z*IExFkhc*yhZ8_rB_W1S?cxJg8jer$5l!pS1M1POKs&V(Xx<%5w@XUKO_)H*AL8wg zIW0HS2-q=9+M3HkNO6d=7VDE01iT>6BJ`j!YLO~8vxEq$wpmCUgK#iKoA^jhBFL6S zusB8OBl*`v^dPK!kPAuj6HP^`_Q+alp-fe?EIq}1bTJuwcPj833$n0)V(q3!yd-0~ zb`aB7a!ON(rO95-G>mIhYEpZ8$|q^~qltwg1sWEWi3ld@diJgm>B(O=(Ldw^Q)ya5 z&Kki@y+90F!1Zi2%eyEltX65Xnc_=!PXE6 zDB#b80dTVv0u!S35g@Qaq91tWAQ`l>Usew~GGi2wu`cAbfWZ(3n(25{uL00N0fW*t z%p@tY0{00?Me6mF6uoIi{-Axl6PuhNU|sYXqbS${6Y_qV)&VoKBILOw00S&;B<=_T> z9zmQ+;r@=S2PHs4$Tc_7auo>>A$E-9KMKL13;jzW$_Ii7(xw3|0~w;?JS@maXfqr= zWg_&%mosXh;aviPqlRPXWoQ^C2z~*`4?%%D6D8njuxJ*J$KObWCm5_u2)+K&PLSAmd8iq;)dDRi%Wubyd7|2-2 zaw*DSP+P)C$Y@*W`D#YSRuma5nnLX`#H09p^C z>jhN^$efj+wE;y!q>gkg3~=s&cWa>)>_AYOg}ii=x!4IATIjS@Jy=6L7T6z*U^9(+%HU0?}KL|yvM zqKaa1n#i?v(S#kdL{>F_=E-7`6EUC_&OS=gh9bVm;;2qVB&q@BEaAQi$w%$RyXjjV zOBYag3xy85n0!>~&`z`#Vk6$^8L-k&5%!^^J1~e`aU`jW9gR?9q);-46)9=~P>`JI z%v>5kK#(coD>>JaGZtu==evdvrhvnIP>E=92;K?AkN^G`Nq&m=NUPcrco$*#HJ(EB zqfA5qpr2A91_xoau;l=5f48Cw?+)rrz%0&&dP;zmxgg>#Fx1Ncu{9W}Qu#0n>k4H6 z)tTi!kgcwK!I=zR#Md2uaL~@;;|L(4$<27MP$0h&K}<`?kN{wUL6P;5?T-wMF;c2v z=Z7ZZ2;f@YeKgUk_@)yWx|KBIb=W~s8Nw5CFTn+JY@x5(+#5~WTA;R z9JeF`f8~%d1=^QO()1jOMOe;O1zY)0tWeAW+8EJ;P8V`!AYMSwgK7bq0gx;XqG2UN zFaKW4NA8C*aj3|;7KOik-vEr5F1VMz6G1P?)+)q|DT76Zp;{2_skZV9h81lGBGa&u zsF$;ks`HV~$rX|!Xat48S@IAgSKnSG@4iq@Uob=vHwK6Uo@+@WOH8K0JhcWGsR)uv zFw8|8kbMO2R&f)jY=k)&$!U?O8DVa&tQ4!qk%BnVQZJ~9!S5$n6VO6fu(ahowK~9+ zK!gwwQ~6{jED|M&VvwprEvn#MYLq{d1ci^ffIm2Ma(l>dA*}>P-*Lds{B4L5)ADjq zK0}6&gw?{el;9&7FbeGebMmm3%|*$WvJjL+VE#Vhltd{R!jfbr;y7KX*SqrB4yeTx zgsx171CcwfsD+l)%l0)aY~?Q|9$A??_9i*Y3g9GRo0C!pV;@yb7@)H>t}RXBZjy~7 zs{&CY0m3j*1clI;#xWT9ywp!&#Gw07AX{}o%uSh;lI`0PUM>J7HvyGq7LXwiLmbht z_K~(eMGZh$hfGxGqul}HD9|7_Xjy@xd?*J6mZl|@G6&^|Y?cht|^PqHT!C738- z5`}<~^3jaQGF?+hj7VfFf~R1&qPmp8OsuN}Ug16*43mIaDpE4ru?slEGYy9{U`p7# zpWMCFNQ$s!>^vFQB&w$zEhtmT{*0^pw0uW&r&+aV90K5wg6EU^k$Y1(E5MQ5xHqXA zZ_}|OD9xq-K=1<4zTmehwKxcAf4?&MXG^#Si$k3*BYQ_C*KTSq%URd;6%LER28W^>njoVtp%;Ky9E)uc?RF$kcP3;6?!-c=dBt*SdhrAlyq*Yy@FR-cK z#RM!B$qkQI0kU18=dMJEV?uRw)hY(sTys6jfk{E5gmS>LiaW#$rWYP|Zn7trEr+~{HO8^;b7UK}$0nCm_|*r|52Iw+uuSqWo@KRe9lx;UaS z4X!|@1W!^_onwP{`8y#vYzU&eiBmv`Nr0u>0@woC(->+D29C&9!H#z$m2V0t$Ux)M zfFSa*x^uPr1gT^r;h-W(s>O7wFp-KV8PS6Kx<|)_|wwm^qw%6vSYw z`zSZm=v?@7SGJ5tdmPXNQKS521!V9d#U08A8a5GuP{9qrvJ3$ZE=xcFBch29PlYI) z;ALI|av_UaOB-qdItQr>tB@k0azBYtARgr=4cw8mcgmz9bG-m%u9p?LvGKg!pM7mQ zetVIa?SND`vyY6Iu`_uM8=DBSDEee&uJqbIoYn0Y!pdJj-@a8RVgJ0}mkS~!3EBX@ zLm=@mD65c%y~9sBu7P7whA%h^vcLF8r^d4aTZ47oF`gNks}Pz5ktv*ta=?p6F9@rG zs#Pin8A27yxPuRa)pQ7@Jpkf@Y*0^ZT7%x~S^UQU*Ip=~DDkzPrOD4t211kc&qip* zN2~Aq@nP?n6-r{%t}g)Pj)0`A zkIXfTa!eG^ikNaAn__B1M5s{**(1OUB{4lBGcY0u1z==W^7oWQSj7^U60Q9OAb@os zv=9{;CR;5D5W5d9q``j0p$$eSl=P(Sg0!NZ1_(@W<+B1WMlMA&=SXfhN ziNL%=9#-y4$sO9mf`u$+35S2|P`H5dR7(QTT*$Q+sDVZs+6r`LDHG#LfTuraD^o45 z0f;H$6rVPz$P~!Xs_!gVpe~qe4SC93d^p2I8WutUaD+7yJJ2Qry93<(T8kH3hW6zD z7*V6~G7%6OY9~vf#N;9V7ybVIj3z=ZBO(vOunBFM2n#V(ngt(wiu?_eCwPHRW1yxt zg%F;!B_;s?YJB}-MRF z?PdxpLG79dMU$rid=gyXbpmlsB;F}XTj^Ozs)a(Z11_y0jt1lv2(qh+FOGzjpi-C+ zCsGWc3iqw$j-{1m!zD%2NmqK#@L(5^9-L30L0U9#EiR-|$=K0U?VFBzbvHWjZXq{` z*3tw_E%rVF;FBZ=G}^PSE-=h;DiTfYya*Z$7Cy)#=HH=R4*ecwo6v3wF@2-5L2D_*S2h>+1^wlny2uaa zlp{|fWM4FcCW8SEWY-3$#UVjH65|yDf(|?K;UTz#OeRS`AfgbP7cfBtXHO{>jQSMy zwmuW5i&I zSy815@s26k8tkbIu>&y)V}Z0FfI+=mC|NMbZd72wcv)K$wd5$NdQJ- z3cq6dCgmM#5+(*9^H1M^Kqn3abQ$i*!;6hrS{m*v936<0k2|KzTz@HFrT)I7iBMxL znm1Uzgn?7b0niI#Ftk^S&~ZgT;4mTx_yN{*?&N3+2lc{)C)fq*Bijvj_%M49K(di4 zsq13elYHq9A|0>*geuTbV23hY$r6KY2SRa$@Oe56`F_|0YqjjyK7F}=mF@=gOVHga>mp}GJ(*n{~IQi*r z?ZdqxYydUVGwLlz!1g91-2sSUK+wE2&%>23VU5O7X-vtA)khv-kjdDxbrX45&O0{g zK^p7``w9^p<}?Wz;?XsVRXCl{vzST%0v=JIIYO3`iSr47=B9x&}hA}hoIOVhdOkW?o2*}JrPk$kkj(u@Hqe{&zCR1Hk$|Gqx^ zdjWx|j`3m;=|s}*X>}?BEQs7kH>h=)%;6)xr#4Djf-~+IDQVe>oQSNVmFOQbBq{;L z(vsw*1Ytb_1ku<&lz^sZb*@mTw~(&@RAC|aVXjP}V$)6ZnP`(TUIGxXqIxxmm_$e$ z!~he3!&dTCAp(f(_zW2%{($1CZh|CaOb3PKWNSske8jafnW2CDrXqHv5kQm_eV@W9 zvcPUdFEqgcL6jf}z8nLTBMwOrEyN7dD$9VW_2mL~~axmCQ zPfR(xsn-m{g>K@{v;kEh>uA@Km$N&15Hk8m5EqW?yqY|8u_bCH!f@D@9^4SYtNpyg z%)w$f_B*bPsa)F>#Eywe1H_TIWIT?DS(MZB7meYXl zP=%Dp4;t(do9qJ4N)}mP_$uKT2?V<;zVxQV1O%g|8 zU}(fCw=76Bu_hb)mxRKV1aY!iu=5K}%H4ZQdg-1dZM*@^~mY6lJl%1Rn) zR3HfnPOjR`U=;hN13F!R4g-jahCwF*;JYuNxpI>>x{(V3ps9-+*mLDcb;9Ds09hFz z8mwqZr7EH#fEet8@*$=uY3p<c(5Kyq~pBuJ3Z0@%SV2io ziGT~Hvt{L71Q02Qt?^`!G&_&7r_hyvvIUOxAcIpDXE2l0p}4cIHu3Yd)}tbTz~6Ez zvZ4Wa$AX*sm(KF$?_5EZFVHT;#P(JU$mVH?R>X_!ZN2!vNaNw48YTjgxwL`JFC=dzB^a~KBo8&JHPMCSo}+AIn%$*8nB*QkQkw%-N)Y4B<^tAD7YcDlWNd<0GUk%HS=e_| z4$3jn9W4w2jpNIOeL-~&C*VZ^W0bR(S-6unV6mo=jptontk^IYdKtMA)^}8=cFjxD z5J08zUIud#-A8`#SE;>~@U&Ps$U#;fX(EH&6xlUVfl+)NfmXTk88XwY8i5hnULmh2 z`q!!mg%Vwf>}63YDbtYvK7>SCV8=4Vgi^I=*`xc0$ys71+TA$0LX-vp$jDy;uJLXM zp%5&{!AFJ1g=q9gI_{+_Vh(jNw|;(zg=P@?P>F z03fK^DpYN^qF6$*I-*79T697-N`srsnLOD85S6-yY+$7>^(vDa$V9gMUymza;qD+^ z%c$Mb!qSXpl&aNj>r!H9OwSZe=f3Wv9M|Z>wRg!Qg6C^e?GHc`VWL23D@3~?LXh;P zr$lg*gvbfbl?E1%6e76X6vbZ1L!Zi-9(WYoDBMEuG#Il~3MJ-XijsXak{FzI0p+9J zL|0m!PS_wc{DdNiole!H#Z=pN(L^&!)}%mywpr4kRdbmqgslinAShGj=rh+I0dtMO zwXBY1soGoRgFBj0tc0loT!6Jt$<6L5bIS45aw7O{V!wkGmSN~ZYw&rzm@Yjg>}!H# zkS=qldKBpT#@NoFM?CORAnFy8mgHluZwEB2M=Y%hEtJ{5F9MV}6>YdlEucfFez(6=sWgyIoNP)n zieMNQ0zO#u6b9X~ibBi%V-wV{@%T8GtEN6f-WF{ahC7a?`BhgT%5R2+f0hLSuNYsx~g%6bELqG0YMOJQF zNHhe|1LbH98c=@PKobwAB?A;f`E=-`4&{wRt1=u%hRV44F5O=t-y?{la6 zrZj-BQ}Tb9Ly4~%sTLs6Eu&Dc%%!S9j8rdhtP!t=6I|=A4H7ef#?~uWloPC{vv%QIL z8!0`2$PmcxD@4xnkqpF4G(hGQ%K>ada)2=9keNQ!^{&xjuuf-vKobhET0sBO168wh zPvt{4YQ(klRN|y{M7KY&1S1*KxP6kP7jLR$*dM zUxg6l+H@6x#VI$OaR)BRC|0T#RVLWo#5lk?Gl)ds8&X9dzOZAAI0^?QQ9;$xv_UNZ z2d@E^(C<7}G7tk74WP{N5{9@I*}Co+j5q=|7u51WabJ1V1p$xfTnYL#-tAU8SHn_{ z^0}@HL~B^+!JL`PN+A+Ms1jI^UO$@2tHRZTONBOwb>FQS#t{ak23k_$T2*QZ7H1O#V`m11 z$OQi0sg8gZMG01s9{i~bwmy0Z>tv`gA*p2W%B=$cA;}yO$V{t>kfn%;HGeDDP<8T9SfSjfD`?Y|(u`r(Jk2?w*0W`B08sEig~Q5gk8u2@=K8-bB1l}wAH~Xd-&_|`A_je}QZL+5ks48EIt=9lO!@Gf z+Rci!8>_@7d`#4gTA1=ZqDH&0!Fa)}eSym%i0OOx5sW0;1(>`gEX~ktF>Hrr;Rpqa1rf$cNJV})%K3p03hdC8 zo8~(93+uuvW=RPP*zryz99y>T`%gm%<$}CvhWu;?G}#tr*3XVC$^_u)CMj|Z2oRok zJqURK0)#Cn%C-1{m~OwYE(ZWOAyJ(kOaqYj(N|``nq`>tEjD$Kj>a6ciHHboxa%A&#!V4gser%2BNCLl7ddC^1Nh zq}A&srnVvgUguqzA`2oJuqetqBPArpg*cd4MG0!*Grj_-8mAUjH4*w2?;rIkjmK(R zh3h++__sYgiqjTWvl}lfDgr)>6IPc|pxRCJ7hwXnHNX}Zyq!@%lJ=~QHLc1Aa6ubz zzz73$QF>a>TGr(`PEe9`QTb9*g$8k_Ez*i~&XmLygtm=`j761}35#ZRC9`YDQ>LYj zgp4~zdbJijaY_bULJ4^H2xVeQo4~**ny#0IPyo>aZfb3fX(ENAgDKgqNJW>CtBG<( zw=@h!ko$@Si{uT^8g@Fv_#xfOU!~M50;eSd!RZs`(gV0HKpgHTZA;767$e!Z1Uyos zpJkv&T56OGQR#72Sk4kAO42*tt4=b=k|70x4x0?eikgwA1eZ3oOAOkE8BAC3cZWj2 zYs)549*8Vwe(f($i9yUaK`;_VjYFs;qz%EM3O7Nj3Yj30l6AL6W`OR{n3tv1Li`f5 zy8dzpVl0A65e-Q>I5Q}afWZO;(Xgz11E8wi9GMJ&q0@rQ`?9~%S=+$h8Xywm z=h){S{9MVn)(8regNdrPr`TLPVr9I`h223K;#-{^0UR$PTMok7P%qe;k-`ux#|ilr zDT3E5BjM&9Tp;E1(gTP)Ruqj6^%^{hlk4JwECqp*_!_W%0f1QzZGZ@p@{uIiWhEL> zNnR!>Ru{EHjOxH;kMmNCcUlsGGOa>^;58CH>>A+o=Bb4c%_j|$k}jYb320$8TFWr& zx-UMRn+`gyhZj%X)D-E5JS?G?K_NF_>O}P#qjnl*>3Re#@Pr|?5Mj%qiBV{I(lyKU zdYt&(*EP;*Hc$&mRU?xVA%VpOZIG5tR=2&?R!iIGc{-45J^Ao$M<%U` zL6wTYKqM(K6lfEFawSnc0$x3(P1HzK6if3;@V8pD8cb1evk-eE17xWh>@MW2Mhb)# z{VNl>WMCnydpFV@Oi9TfM*X&cR^?klfkYxm?c?h+{H^FIx^<SKKj@I2S_&Hd_M>Mi@uBv5-R^@J^sm2+7tjE!3l0RU*o&!7@(`Aur45jYU4Y3mXC+9_TtOs&4gpC*6#xVp9!VE* zC}s#y(yc;i4aj?0i1o=tp8_14YKKEQ(a4sdRZ0*_Kw~fk9PAscF0H4X!Kg8HkclM9 zA2g2X`UuoAHw+h?&@6XwLDHsBi>E{`2QP%OP^uG9IqncS7L>!^^a$MK00aRg@4oJU z0IVP^c|Aqm5Ce!_vz)>SNgOCC$FCFojgvqF7tj`g;ecA&VCN5{Yas@%YLPX*gk9T| zZQ|NQEjj|-xMST+PfC8|*B6TN(S2N#gixj^!BcXdnQ0~E zvvJ~Ai@AgZbL>lode!KoN2s)%)>e*ubS_R&14j4lFF^2!KPV?R_6SFEGZ>^+hYSrE z08jVfBP*H95ERr4H(MK)m2W!y-M0$3VCyLcVL~2I^5sHxmIjv{!BcgSpPN3)99bH~ zO)C-!zalWE?ON3s_XWzXq_eCA5E{dg71bg`TeEuQV1U824JE4+woY+!fSas5)lg_6 zFwsOH7-G;Gisi%5BUd9i1|AI)ic?um%a|@7I;4DhmKY_=-=oeUGEosh`tP^+#hzE- zh4T;|5lY6t`NS9Y9pU;DtsMOL)4$y2z0#mwK@cG~@g+`?WT@B|?q zV%CrP!d~Mh>TxX&#kxb!sPx5wbg5d}o}$KgCI3X$wYgZ}+m1}i)aLGhP9tkBL>o+X zX`7ci`)LGY)UGrsnGRJ8sgbTApx10(N)yB#(lQvZuC*8>bpiI_z#+A~4g#!%rDuvm z2%~_fRg|NV=>eqTi(@3*z~vjFnDhV*BGH4GvaZg87?M1dYu z0$ovsr#^c3RO3@7t)f8MBA`iPAd-MpAn(E|lC+g5AKH?nRV~bjtRhbV5lxMvu?2vW zBBBl8OX@X1rM}45_>lK^LuY1P!~Gykfu@5C3oHVFLOBv>WVrzuZfX|_J`zy1S@6M~ zk-7%FwE(GTLp|;$nVHVSq%ABiSQe)6>!}?47{xt&3t&ESK&vvDn?DLDToq1t?gPsA z0_9MPg!#GUz;6E%(EcTYYwan2UjbB&aVu&HUl~}ag&;tjNQaoGP!)&glQBTt6wtn- z3dyTcG8#eQxb`v^K6COrd-=)|Em*2?Dk-@)J{CG@x+phC~At1wDV9qB^2t zEVdY}L1U4QeTwxFYs5evMiN82fQl?3AH62N6SCEn0~u^0oikIBM%F>dLXbRl7B(@i zQVCEaa3H|FmFcBwys$4R^<#qR>b0kctQ?{uKR+(1PD)Z$j!XbcEvB#+3!zV+at~98 zjo>XuAvltoLR3dN#3m)=3a&u%ND*VJ; z8uQN5{olFz=l1~j$h(xpcWt`4PW#f9q5qkiA5=-D!#we*3*A%=Z8G!GPNX_eTMk?t z8;tIVMrkyRSppCr(Jlzy1|eax6jsj00|Zu5HBmWE#r|!KX4fW6z>a_~kGzm>Hvx`R znF$FY-9jRuOiD7*FB%Hz!1Qj!Sq26ss1n;1oo#HIjfSS+U0BLMK{I z=C%N8mk(Yj!CWn12`{`;EON;St)|GzM->qp-mPV8CRYhqokVfXW7!~MWScJR+SY}P zrL9ZAk~A2G1iKBiEn{Z3@H z$d=pa5!Gox>)Kmo;=aD4JvuQ`IhX}YO>MkK3ZXQ0sX|ts0#ly04DD%v)@IAbsFbs5 zA{pXEN_KyGpbAZABDIJq0Y>MJ7Y86+cYy3dUDSn2Y3OTneWZ|5Rl7Dsx16cSU@1`u z(OOA>}wBqWQnad&_z zphd+&2mNRYEHExsCI_k7ETEw%@%gz-z>7*pN`Rxu#wUp2Bn?2}Um zyHE&)gLl+@AXge^V0r-Ovmohz~e@{k*WVHqzrBXLxe&p_TPG8D~78-#1(WJfS37-fP>gZQc6 zdESLW*czOj(n(8Skf?u@jsj(7xo{vP0sX>*IDp96(#W#0#uri$Dh}{DhBB=vLoel% zinWaQ-JcqLl#;4FWudf9Ow35gg8f*c#x&(6Fqn;ATrwU4r)r>~O%ZJCvp$QG+Sk z7Ke~50SD|;n&76WQDRs~jc)oV4V`5oC0K#X@FPlvn z0iNz#3r!gJQUdVdslR9sBdQjGV0_WzT`XEr;FX5_QRyWVM_8MWFm4os?s4DM*pQ~lyT_w%fR%RRY&J?$9FY9S zTezr^(6bkq>sKJZ&-ni+_qz}xWTicsfUNfhAwauk2D4!dh>P36skjw`}oonKGJ|YB$ty&6{I!=Qt?PM07}VR2q?`$@RS?< zs8RW-u^dv2BndSdCT)J=sjW~Bz)`s1x#8el)q^DT^Miq3WCB@ei zWJz++z#}jXqZ#?LZeLO?p*jI22|%t$Cm3;fK(2=MPs(B7>1WT?h+68zs2VL6D-EZh%?2ho}0BXQnqR zx=)$zz*cUgC9JiGkb%HTSgf%TZAekx-Sq1#c5qF@^yBF6#7>!P!SBr6OwOc;Desu| z6+lc>awA2B6c%W)F-?X%2;qTFY0PS<>ih6N`Hi^9|DICYkgkL2M4 zGZnN5VUrXU0(?ojmWGjvl9)#3Yycr+`j;nPg%~{jLGm&I zrjH^(o`=w?70m)Qm!n=!brw!xh)p7t6#)~W73Gg#(_Ld9NBI@lEu;te72A;m{m9C8 z01$bK%!ric+7hk>v?YlHMyCkilb?y$cLpGP2)JoC@q(Tmgnnd84E_)=4*gIv+p#%9 zRTIepr?jmLK%`sMOAlBxpvDx$>YCj!&_T&T#Tfts(gTzdjI^{d%vs6g;ezTkg0Pf^ z1wJn&%R$lrn&D~a(8BJ^)60deT#rLZG}EQ+`-^?w_e5zx9>_)l%{!>A>!y?_1gdrx ziOE?S#&{}cQV|EskQCN6tpc3!dMQClkT*cHR^-)E(r8Kp z2#%C5d9~{`6yr#%B2iH)Vpialr<6>j9AAZO$`s|@>goa=M}@iR6)qs*2?9E+7{j-f zLn_v#C3oPaD4It)*;or@aPWxm%+d?g0vs274+u6d>#9!IXap$I-KJi2zTJawo~F zg$9r!poCFlB;@%U!|@avkdcFE?lU?|Xn*usQL5$=wzlG0W*H_`Xm8ubI{tXNmmKAp zB?!YyV8BHJ3k2e3m|$;${qKOS^rLrrQ=GFsD=I+n z>Spqi9Y~TDN6EOOr2uAg7hkt;TNjgsU^bHOW;x!g8Y#Q>>z_a6P{bEWM5!`Ma4(-D zzoLdjkwVV!BH&!%s~|nl%NXW?*)S1cmbG%@0n%H-ziRdm7<|7_Ux>A|o3<6-=mo?c z0|>71GOgOgs%!1S3v5ZL1r!-<7#`G@MZu7uo7N>643-m(1l;SXIJ&NpW-*Txtd4f$ z6^pQVU}hhcKy_hLleHrO+Np0A$@t2;m#C^HT zh)F=@2ZMPJ3AzNF=(58q6TX!-o~_7`iU?r3gJ+hn102#xPpt`J02p-)NYt9O?7W7# zi3AkUR~ClxoBo4Iy+uJ8i5wl+w+3@-=~jmjc&&h!Y`)S55)6z1Mpop&%s*u1YRRI` zg%wpIVA_;IMzVS_E8eVCleeO1uqgyn7z}J9rKz`A%ewA^fe;+h%e%csK;{8nIs5wN zf-`-=bWU@g*K@38aElTJz4(!G8tHFxQCYrLaIB)kxhbT?2APNjiP}e$q%7*A`vBvR z^vnh!56hiEu8G3ffN?1>9LOH>C4p!r8m6}xDSZ{~T6sG8BM<{1SqoiGzxovkycmNC zHAB}jWi~a%jr>3qabPB*$bw)V)$XVuJ%MmMDQhQ8XB8ju#*>(58wjD2FW7z8lenJj zXFVl=5S_%HVin5jik{-x(xUN?2mtFUmg8qu5lZ=ZafOjGqME8vux{$Hc6s9+-HBo$ zf>O|0Najf@2<{`gq|aRvhgnnNB6lPxcm2T_*c7s2XAj$=CES;h<-m{^%!HP{ecwk~ zz>g>}Tw{+DC}L3(m_ihfEDemMAw;zzrLugj;ya`NNf1l0jr%mxd8niFvzBX?vjpCf zp6)y;CSTdPAh)i$3Jt?lKn_B@I@N0$^hKSvfOE{}E=zD}{xr)eT4XojrzRd$rTaRk z2yB1{%m`i(d!jV0%e1V8D;cpF+jA*7tYR>3k%LC5wRJ*5S*^xk$4e$*HZd_nP2xJs zA;E%+whim22DQc;-~phA>kk}j zQ3Z%M!J(b zS_-3TgJK$kCF)R(6RgN5R^(?Ss0f7MR(y~;+en9#O&15TQd3R(kLkJxlIh;J10Jw0 zhTV~}o9$~NaHji$OF^digbkCLi7}{_{o&v3fw0aFt?lMJ}N-<2|2GT0<0Ld7V z!i)m3iV`#4ZjKNQpK$D_Mvx4xl3@m$pg6RUl!CH46|@eKZNB1Q1~4WR)_r99fBy2* zy78)5345fy*bejoXbo0b4r%^#Fc(BIMelXT5zhbEVfwSj3+o__f^QEuB&By(o|WY)J~*#QCXi93IhnIi!M>CL?#9CppYe{R7qwb%yR-Q z0An-3bsNJV@zk|Ubp}D)x86dHirwGAw%qUMk zl{1fuCOQvD6RQkNcT4mwNu$;cjB#ITm}D@;1u$KhRz@m9G&Un2=5-`~)1TcTG4tqb z27`fcbn%bI3%Q;kI|mQ4l0eqR%aka7zw+rtDeL#1Wmp*B_ zxj-68bmozWOc8=HmP4jt@}-)Q+^dxVgA;#G6iu?IgWs-~&rJcCNvtAqVQFM8H!`pm z#IdT?Dn4=%X1q9`E0}@bWFd$}C2N=skptB)i4_i1>?apNMDvZnazJ8?&E#tUiqOs~ z(lHD?-J8!Xp?nC35W9@@-6x#-HB64kVPJ~_j98)QMJ#hYRcP`>og(5BEFoENMZsgb zK|5M*XFv9k8oL570+42Tk&*@{zM2CvU{MiMo&KM}Af-hpIFkUg%%_I&Cv_=Z0)~HD)<%xgHAYY>Z4{X;EN}RXF zN36LLmmT(GjU{b?)kR<=j|5bA22)K6IN@QIR=nT|1(+2r2dCzy3lSPr($Io|k$^*) z+6Z{weXHm;{@YAH@)(RUO~Vn+BNuFGIokP8#{6Btk41_LHkc_(KEVN2XkbuQlhL?T z1fju}l;!9vceI!y3*Epf_Hk3h!2@F;*%$?-y2SxiEqJ5^KA(@K;W#Hp3}|}rSQj}u zInt=0$MT-1sL<{d9ZOi3jTuR?NZ%N{481s9K>=eG-%6-}Ou=<9rX>w4im6K*4+aBM z0c_Fm&k~-pnTZ?-8?NZfNKn}zY)rEA$Q_+fYNl`$OYl6^;Pi;QH~}6`7TL81g%H06 zTgB$<8WNP6%V3l;Mj3)66i5UzH%mx?wE%Dv=NLc^hU@b#tVJDZ+l0rZDxDXI*G%}l=f=Sx!sIO0Yilx4+DKvHMPfHB!pADM@p7Ac69GCRo71ZqmzBEk!f&;sjk?QtAs!R@c6C zDvMTSE;tq>&JII!E6Re|z#zSkHYQVQ zdOV|T`ejLib_$&bx0Vo%;xv^AIbh~k#Tb(o#WqNoccerz|B#Qvam7*0tmh+8K$$AB zHgU!lO`)MzEpraxEN3u;f?JM(1*C`$0J)ddsUSO%LDEbRtAU7YA>Pv&aO>J~3f4&1 zCQ6@Ac*b%bae}pwZ?53T^c6$aTq+lhg}%mM33W!25|bhbE)-p~8epK+UcAezj$w?s zb}P)N2%@JliG6e*?drmXtP#N0X$BM0gm_ z2K(hl{}&lEezO%*YC62qlLgzL*I7s_R^vD^%ovX>K`dN_%_Cs!ynfF&OBURsY(>KW zhdb3U0G!BYKf1$1GI;?2lCUZolSos}$iWGH#e`Ed<>uRp9IYiC16Z9Fgg}DS_<`*m zv0z3ls@-H5bJZioqX+|fh+&SVc;I$i(F*{;lpbU2ehg_kp6MuGJFll`v7fR{)nP@< zXxgSoO+c$i7N4xHw~#m~I7r|Q(TTzmR>ZKzXt~GZ;U8`Zs}(qDkXs6n=-8IyC>;-7rf3?# z^Q2*_PZsiG7%(i2VN>)Rvls?u)h#A*O_Zq!04N!EFt2Yry6_%mxEe?f0TL}NBqNr_ z2_dqy6M{k`kR1)rkZU0JU-z{sUS+lj7ny=Oo~raNpvZ9gj*muWFcPtf8d~piA+8mT zA+GU`iw5f(Xi*@V1`@cMg%HklI+!Lbam=upA6VqBSa@4NZnQ6$<)dhCX4ulfHn+pyx3v~uN9CC;T=#hUNq^BuB*YIQ2kNLWXiMooH`|Qds zAQ{FIq~S(wK@c2b2}1YDp~`3>apPU9H3&r`91-n4$kBWG!E`)(x2;E)?rsM|w}5VkkPi(%-CRe;Tvsju(=gH32OiYZMF(SOjz`54yWT?nG{ zK&3?+OiDf6mTe(CTU6L`7l-*Vskl(8dBTGVB8n!6iTE)L$>52=u?RsXid3tkg$v$x zQ&`UA5WI;af|oSq>%PP_1h&+jn2Osir$tMFPxN6bW0jwY!WfcLaX6F?V%3X8lmpr| zBKqRQo~2(|g{v*3M#zUcV8EcJvf@w6;Yu%p5SeTY21}rbQu6i47}Qxv5bKfyGuD!t z^!0g816UxkiD@arX0!47gLQ+7Hjl=3L;O7dqgiRBMO%VK$9tNesLJsOkvo1|ht?DJq zgi`Q7L+6g}XN=%fCax5eHd??yBMwP#Ieg;K0k0<0ZqB^`(2j!g5>^UCF&JRY%|}JY zX2xrV*r<(=N$=tTKSN`}}(761K82(QCR6v?? zunLgkYa_l{$k!D3g+j9SRV@gmnJ6eAbr|os#S$CS9Z2gEF6Xc@Y(g=l5>S2A2SY1U zJ{U&0{s2Sg6UibI3?5K!25Vo^ArtuoVM6+nLreFRn&6oWhW$$lHYO%#e%`>OwrR7l z79KpZv=*UG2_!osi6RxDw$g6Pbr1+-~==WglcG)QMv)#DZ$+wtL(nlf%Uq2OIvAC>8A{@ThasT-aoh)x&RfwdM}+s8&-X zW-7IVC{?V4r)dJiN1kCMPpb})R2)2l6OMuX=0jg|UHI>X{Sc)3xL3{E1-uA~6XakO zJe7z~35p{fGHEFk3=*Q4Q}2SjRzo8~n1v<&q!Dq|6gGennX)z@e^LZPA&|p6hu{He z{6h;DBMqeXFw~Fw7udkLYOEkhsD2Vk51k$xGQt{L=+{mO!1SYD&Z5 z5ZebQfJ*%eU!_>xBZ|{kf#^060)t`lF@IDGNPRDb&m?SyNx@lKf8#)%$49gXg*_Tc z(^CPV)eYpLOl*e>z~cZ&Fe zDHqCeg+gO&I3@0#Jq!TYTo}ME3&Gh*0iwdWooCF|{apL~2RsDgN(A=>XKCKRvrPF@ zF3jLsrBKHz3_t=&aRnZr1s+ZWdS@yw71N3?YI>wJo*Ow6SJI({#2pw2t>^gV5M2rZ zMY1**LWFPv*djXzDxy4GL7~wR+P2Gkc+*%+=WtnD4qd{kN)y$Ex<)Lg!EcHVU@#|m z7mcyOwvcr#syDijwFvGjSbQosy92A}*m%}RnVd9ulPn~z#W75RwrTgd%XlmLfyhyx z;)wDR2Rt!B^dPY6jYqgo zIKoCIkkBe;hGYy%iQ_zJMR@^3CNc3(1%*jFFFZ>}0b&~i2AK~ih}RKag;vIwW6 ztc7AE$T4F%uE31yvyi8K)uN!MZkh`WiagS#36qHgOlU>;UCT>d5XD3!nwYd;FmP^n(uE18d0NavWII*~J(?XuYgKFJL z7L;XFeS>w&g5p#lllIPXthKZh3+WH$V#Cinuu)A|!yL{XV`_A6da6>~htNWV5Z?~s zRId>MX0Tr{q#%?FYq6Y^ZBKaMTjzNf1Y%JR(HrGxyleddD)BMH+geHqGL^xQ1A{ui zR7dq|LxEO&x!71~1eHfJG6gvnBCh+wk_I!(pgZ|ig>yb3g{7lMP$QMnYfzVtvseOoTF(;2Kt zaemP5tf(U7q9^Drnj12`dx~M>)usp-fM2-@LRlgTVim(QNdq)4d0`bf)}<^e)GSMI zJ~@PC9u!Fghq|SXq?W1U8WJzq&?q=aB4f#PIeO&XDkh3x`+!Hs@X=1k5}GH9=w&?b zQ5g>gz|0J`BwHumcmRHq-w7E22@K{VRDv@Y%uJ&HpH8P1l{kT{Yf%#L1MQ@Y zNeqCYU?EWpYDXgchIYo*y)ln8k&SV%!8RoN3^0;y$8^(Alz!xQ+K)XQOxI!(5tJ1s z`ARR+&AdCd0Y)!oz)x-5il`=m0j4k&Fs{Lt4k;0^@Q;gx-QBT2l%J`-=o+#4|_( z&qDSHq7j+Xq=tYsdWLk&B6-bbi&xO(RZ6oe2PxZ+Io+Vs__3(>S(X)0|KU0{Qqyc=&8mXtQ7FpLn^H3dI-b6?jrm>E>D zhHo&BSwaJ0c=we&-q2!4HbCOA8Nd=%v|Ok|j=p->EJW_V6r+@h*t4$6m9mOZDOOa! zeAs=m${jF{1)x^%5Ka+)5cCm^MjCH*ao7oR$<|oNAuA5w_t(KL1-+ zd_wcNRWu5lrbT6{a1at6qL3H{5mcp6x|!)1@ZaD0k=$BjC8a;U+tB^%GQ$WeM_E|q z`Fygs90hbnvQ>3N1cO!Z>AuZH1jS_)cSJxtw7}bcVdE$bEwsv?Omyc2J2G9AgTc(; z>x?5beVLrN666g}wRh@g8_F8P7>HssV=M}u3+YqJe&M45l=4^LmF1hM7tb z?(#^cFq>#3W^^R8aH9XXsfVjjOq$}UlQWPChx%q2>?sAkjPcGqDn;Ue5lF9-DPSDP zU}9QV!_=|$5V~s)MC37{%;iIa`OSsXpk}C%GEAppClKJBXS6SK5zZ5n zY^Qo&l0_#X4pe=R%NHJ766@lY5XK`GhpGc(dJ!@)z~O?ROI|#H3c0Y5|1#PzDZnov z(j4y%(7x~~1wz2aA!Nk>VAYo#c|g9L2`W3Wu?kfJ@`VJSz$i4-@yT*h0>I`dQ}^8z zhcQUNU~0;8xROE%sxSU=jcry}Xc>dO?HxE#S@?ts3?SXqwZu0CGlqeUWKeYjg1V3J z)PX_nV4w&L&M`(yr;!eC12W4Px!5W65PvQ)o;ntlg-kKeihywlP1zW77BU!VIFyv- zV#A^cHyvqKQ9jm{f{L@cMcevC-~qYl1DwxV=w(9W(1p-irKRImWf@shq6_(`psW}J z=jc-Pbd-w=cbu0zvb2A2(l4dH4)hCvLiSfP9#e)UlG2YqW(lrR{3AL)S?CX%k6I?K zb@g89^CY99oq^SbER4rF$$XTo;*}{9)PhXir#k|548vqZBRPtoc378c5V|Q2x#h?b z29pmk!)$_rAl4%nS_+?lK?p1XKsY;^Va~2z8Yv;%M?NZ0BoTLPRc0w+2N)UIbnG~K zveN|%X_DL`NZSf6D2QZM*I-5P)V9Ju)KMDV`zAtDYFbnWHXS5cYpVWOX=7EGiUKW| zG?Jz`x4Qo!CaHNuL{yuOH@?DyAds`7D&`I5F$H*~Ax)mdA>)jHxz<^NhlUrHwhW0LZ?=UMmGU+Ei5&|${ zX>O7N!$ge*U3@4hjTcT}n88PtD<;K>qWUm~Mixb~y!0z1J6y>fx5`Q)Od@?7rR|KS zA|q4qb_(y}R1^%a4B^)zv^dt{I(Jl%+5`YX0JLRmaW$Jkt4{);oodTo1hRA;o39YX zv>3KwLEB!&+nN{xS=1x4qF2`Li+~UU32teC>&$$}Iq*o|HL;{@b*zGfB{d>YX}x{5h`INi+}49NJnB1I?7RjC|(x`1l2G>tf-q23*mSJ$y!`wBXF7`1gHRT z2&dt{ldb7KvVm&yjt3UCQ#fyEdL1YDTos!xM|bow8v(BDJ~V>aZh{uQ%R6st8WiD4 zJg^xzV$YEkFmsvKyV^t`gjOM$J5sYKOvT$yNBoaQYN|Mp+y~wP5@WiD ze3iW4(0p@Y0HiUDr(pQo3T)K(jTy|MDh0{L2%^@IsYU2BjEz;HJW|MzGnJ$Oc0_at z)EnhOEOn=H(Zc6Qh8#J1ulQ*6;%Aw%iXyU`U;`LDdpF&?64oLZ0A`7BCRX8OW{Hc> zIZm?~Mo2!W8ObP1{y7%ZXO z@Q(yET2$5=pf1f?3Lq{HE~ zrZS`YQO;h&=YrEkb_n-Je?Es?YXU(3R8Y{egcKyDxZISBwR93w&}9|Nxu!k_U(S8;4TIxx_8LU zqP3(naI|PUUJA`D1}3Eow%5eG%Yv->o8IL|AuY~wCSpuM`PZJz!x8|HiJ7$md0E|7 z0*R6b)zS{VsKAUzN=E0=QH3Q6Q=tWN3DSbfynzQjgr&LBro;BkqkNPS@TRK{(x4RW z>RJ%U@n%K%Me`1rZo{IgK#d594YUAIpSPT_L=@k24o4A193fE062xK^DWC#8tIHA9 z))mJ@(^1NkM1h&g00U+`GvLQlO!}9!rXWpc!y$MuJM(k0y9W z_-8Om*+fX7ex>PUEsv0cB|(_Vco?jUe9p&!4I0CyaGIz3k~og#R<~7=z;uqq9#quP zJ>=+u%6uXheGFRrCP)-lrwUDldih_}@q+_*=@rtn95(bETL1?5fsQ!TTYP!ficEtA8#&c^HYnxJ&ia6mG*V?6n2!RkauAPm6J2n--OO>pdaN-qPnR_{{w^2l@w z5>Bhp7OYF?IwTMCwtw0UgGH6Ryfl18Cp9=#B{@u_vMmyOb{z8YDsg!UtGa=H0-<%G zVhkVEf|@nP9_p)*|0y#aPqVTR26`v476Fg76g7jXcP$R#X5me0-mzqLHuilAHo3@- zUKDp>p8`*3Aic`f-i^c_iu{8gdq;ABBxSu+b9U2sALXuxvkF?YN9&Rs3=%VdOl3il z+N?(mP^VJh@+AcOb+TebASv#`7==Jb{OD$I%FbYxK!tUcys+ri2T)`;l&Or+iwz5* z*Smt1tif6zGsp{daqchO!wmO zHdE5`6)I$H|Mf^Zx;?;-zP)Q@(6Sbp;1rWNsn6%O04cKy9$Yh+z6zD6UP=$!8jgNK zFL{ZKL;Xta%R;^gQMOqZX^?n13qK2}=cpzFxP{HoE)irR+*cL9>YK>DJPaPaSPpUn9J+{T zX&%r(v>LmNfU?uvkgoyqIR?CQ8V+G5jj{s6M25kJvsQG@Q_#40$qs1@5iNu~pU zXKApt5&Ym^E5kf}p^~pztWGAKP#>nXT~ceMh?KPsRN&LQ;39(rhO(kE_UOee1QiV- zNVcwC$_8p&1Ze_~RjGLjfJd10y-%8$#DH%E;7I{aQ?zlLksx}tZ$_|T03imG6)+ju zI$2H-%%ct%*A`N7;K|Eed}{j?*BJ!Cq?!d4+obn`%_>Au>8yfAg6hxS8Cz;rVGJh< z)Tv+P!)%1GknAw&BhfJ+f*i3e3$$Z{uVkvLYY5629@$|4y=K{BuqTMY05h1qo1i)p zag9NyuIQL52uQl(;c(C*M1~Csm^4=?9?-z`D0Qu503af?Y{}A~Y6a4?x;lcBcBUu_ z#ex=P9tNxPE&fHLy8$MuN_B*MwTVW6MTQ)!`eeoIpvIX*A{4Q@Dlk`e+NpHd z2?I=m4KGjK#3x{;@Qj<{TV0tuz=LhNxuEKzc&4v~>|C0RXwr8Q6Hs>0ngR?(+(eyB zLFGm@s95X{NHfv~A(X-7CKD4HhFD4g-@#s?xi(B4F_IUOA#oF67_=J2Vv6A22`9d8 zpn)qWPA0ehz?K3ssypfqs|+Tl3;0PB8=9>Kf@;BuhzdMVTyNApxJ6h+haa7XKQ%@P z`iNVQ5T&L>U_1o~Hu5>mqUzTY>Q}=jP-jA}Lj~vflb;vqjr2$yq_LtP4&jhk4o>#4 zUBYh_r6ih<>JH~NojBL5v<^2N%bhJ%1aaAzvH+W_Dm}GRfQKlH+BX5#g&bwk;#&H? z6rdUggPGGu44_mN>X9GRK4#oBQLi(X4`o4Ol_Cq3uJO8wWI>dJ?9jy+WTmyju@|T* zgvdq8CR&uK0M@L?Eh#1xp>h_D%JgD6qG%2nNFDr8!4xe5JlV0F-iR3;%u_bwTV$FJ ziTkRR57n9?C7%nMi*hg*bv~M?rJF(s4P06A8+hbk3?0KCS-1s(vPL_HC<+n$<4<1! zJVLJv6lwLwSeiv;fiZmvtA6I<0eA4JEeLCtyrirsXn}2I&Lp0agPrsZ#eyi*kscvz z#!EVero`&HA7zE*PeEA7hRy(JA&Q{=))OTl#O8elMN971S$Ns1HH zrplE9pEzYcT8GmXN1Pes79x%woA$%+#n#YYzl z@tcKUwh+rn!Bf+%r3s({HVuS=8Xza=JQQ(XK#6Dz3PEwv%N^vXFOF3&SS5!v^(AGc zd#G;y*X_OQvA!$$W4fnGgBHHHiG~v|0-9kw|ZSJv`(0S%2PaQd&AVDnd0 z#3~%Ca1q;(hRF@4JJVWJcGPzVnNXpN#Aw%2#9;|Ef`K<|n8iP0^1s9d4FOVN?TBB1B|4>sr*cg=7JwUBFy72_b8_n_{{P zp(&yNN8=2@He@nqn>adKrvT$wKSj_V`GB!qD5`iulwP7@=U9Km1)WvG`*+@9!yF0dIN znRw=2q1-VNNJ|Sy5$`zkQB0_`K1&b?pEghgilDDb*)^>ZB|%h}!eCmBm;j*5qBa5^ zlQXERxWhX6*Mtn#PJRqNoEQf6^$RQsUz8MLUc|$(SV5)NU149{zQoM z*;QEWLP-K6K6{9XVI!G}vF#mtmk4rKi5Nzy-lCw~v@SoZCM`m}U=jm(14e5iD;UaL z1o3&m7Xh%bs*Ph?j^bmvds)qVj z7ZX<$=WXQ=9-_$2`3?+xOE`wqsDVKy#&`hpnz@fwv8eJ??6B$cIB|hAxrj+zP_+wX zYZ#~!7gN4E2Gh2!d*^3ugy;qn0%PJ!M>j@=M(WSpv}Py7+B%V7Eu`TJLb|{#j-=;sIMXSm^9Fd3d)N8hF~CP@S_uC( zH6tOkB5M(s?e%Jj2#z8FVCxc)m~Nu3 zs}ixQ8*?V_)jZiLqGTb01BO|E8K(3^lQ;}?$G_?DYZ^#H0>B!So~h&+RgV(ZeG6$6 zl2zSqvayE7nK<#Efa!h;P^K>UoOV1zVwkC^xd{ccRnQ_+HwDoXq+oR;(GI7+L>G0W z%G?wXOQc7;g?zNSOqGcW^<7)_sUvIIsvMsLp#m#XTweMB!<(fyn#JLAp_E09RMg~y zL3W(%kcJbqBV2)aiXV|lZ*`nYigpt54kVQYWY;!mUoIr2`Dpvj@Y*$mCdz`xU?2)e z($T)~=KnK`yG(H(Ex08afhZzVtNDR>1vP(NeqJm@oxnSg%C!vF4xuer314a#C@h zb+~o`m5R`X-0{&Kfd|&I9GT>+_o5mJJm8US3osIUF)GW!AQu(tBU+r0r22vUj6YUKn#M8YSc*+`T7iaR^$^% zkjBQy(Or%AvXJhf;y9ngu5qs6%Nl-JD+f-{mLc$Tflgp!LZ?vt@bJQc5S*U7FMMj1_Rh~bV~74jOvybamBZm zEaV8AqEnujw3fksxX=&%fMW^c)i@Xw*Ub>9W}%gZPDpN3q#!toTq_DLCASxHs31G! zqmKHF%0+ZRDR+?o`8+@o7SaJk7ELMX$ugCWN~EtKjKOLAKwoConlxB8R#VE8_e_Be zBqL$qcAS$BY)oojE|j5FToXbrsFq;Eq$7p8HxcEFK*PYmp>$wSn*wV41`|;Uc-0mx zt%qPt3G=Fy1@C3BUM)2NMKD0CVH*-i0h1K8G~39ZL?iq} zL?B&QSF*fe*T5v*)i|`ca+>mE0%POEc+mtg#k!K>Ky`!-H%(Nh%z$ti358M@j`Y4J z3nCGOd(UIw8E^k(X+6`TEJ{-`tt$)8r-BZ{a&W45$3OoLN_W!#8BeimT;l7TdaQS_ zY2lqE@Pl4y3c}=~0b|=DH!awMp!i`Y=42;!MI&p00VnW6>1M9g0-L#GIju>*!Xa-^ zf#E$R0TSc|r$o?&7Frm94Y{xl)d1r|J1=$Q?#l?0W#`%yriDT78Wg=e)zhTUZ;t2) zw|&@ZSqNhQFuZ^;+vfa`A|nxO?u7QYT@>+;BGmwOE#h*N?C1!atlf7c2F7ya!MTGGa*6iDGFDjr~YqMTrB?BqVSjW@t+X zW(ty{`Zb1>ROUTS5TA8<8=0iEDC+oAOLWtYNiRb9iBcSAMR~Cq6}%j1v0%SqN0PxTN#=gL&?g`_ToMhjV&?u-Yg`*>h% zFZUVGsTej|>j~bCR3I#>5M*s30MKZp7M+U}g;Tn&h*j))w+hls)F1RW2;sz!6j+F{ z4KU1(sZziIKGE6O>YQV+mSsD@wk$Z)HAtLhiq(A`u(~lEMJ9Wb`)Ue2so4>{_(bk( z!x*Mm8YBu@S4@VHzNb7aBWj`o&zAO-6WsBUZkHuh%;KerLdso72F9lG2PlUll@<~Su zo>EiC6Q8eeVp02%+EE2=GM=ZD)d&2iPaMSnG6fHHBG`#cln6!i7UTJijR8}bI#}b~ zXExlzKrhBtP~2$yBL`YApcc$kclr;pcn1GkfyPTvZZwez^$kD%u&7-?QhL5$r@h-& zbY^W?XCNp`fGr&@2vi_Z&O*^(h;Dzbn)qYQZoFme+V74Vn^l93)6AezQ2 zaSQPP@OqByY{b6%fJ!D>SRGMz2PSM6V+`YMC46paf3*?KbkVFx6kn`hbI5no^j1Nm zRwiYJ!Jh90oat{a!s3<$x@-L$6~aobYGP)^t}$ZL9T}M-vUaDC1r&`gl%=oT1S5Q+&hxl-moecx9ntf6o~-8d_8i; zc2t9hyQImDN>4>AgesK7QLVsrHg2(7$y7Nj4dsaqgB6G>*7@;fypf2-820QwGU7u4 zJ#}0(T^y@xn1s+STu2j{gsZ7K5ZZl^CUJDJwD>9=S{<0TVV0)OEV3-XMkao=1)SiM z)sc@G0WHlH)?#2k{@`2{5KbK9UEob#Vv19LmJ#xCD5fdC+OWNknj}iCrRU4SwL_X! zD2@j=dshPNK_F9dIic*L<;2&F+$87ddl%qG`g|=1gsqd>V*5yhMFMh`>eoLAv(adFtIBZJ7HgvK*s=hk3@39Ugekx@r?w zOvnTN`p+k{VPyXMh=#1uQy(QldQI>ONgfM86GFb5R149jUTA5tMn6-3(`>5NCl-WI}&&) zAEl?vA%x9^6f`mKhGD}sDi93;RP^!;d+-u? z)f87NTVQxeKf#IK>y8VGB75RdsHh5>}zk&)?-0tTor(yKKeMI*8C)D#CHs>WQ) zsX`}k9f_=>6^rG({G|X?*sQ{qo{I09m-JZHB9_F>h0RS0ddeG|??N|2 zvNF_CAW=J()40^W3ly;mh6TYnccD%vL8ILVHjJrLVfhUtBO&J>9}(q?1Gk_^;tGmY zcuDT=K+fR`oHTX~U5HDWkQ*J*sYx2gPP+qg(1I>s$Mj)ehz#H>&EFI)$RoQXek^Uv z1`-c`0>ZiMob#1{?{R(+(&FrTwp#zHhyVBcy#d$j)&E+Kvoy4R)7IbQh#!Aj!6X;? zBB1<%!HGWx)r)YeUcke=dWQ!s+CD*LClTf8yg|zBp*<>0P}MAckb`{nD6$p@xq`AN zgKL^O*7^H`9vA{DLS|RgVphVnTfk z1L)F7-PbUq5e&i@D}d(5Cz`gRKGt2^XVM3srA3iwA8HjKBuL~fS^5?59y zy2d3DqGL?y$ye6OkVyoQvZRESMGoONLU6JR2)UP6bApt8K>!Ik@=_+OMIc2gcWHVz zfTgWySCR<5j3fk**0s^gLXGo}1nG->ZWjl=nO_g2rfy*O#F(wtbc=$)Hj?TWAvZZn zDDTk9h^#gdd~L9Hnw`D>9-))(R9I z0+AyE#$>7?s4xFhU@Az};E*x&c`1t!b@mQ^){QL(JjrOGOYoRDV(M459Wd0O3bh2Z zPH}iP#bEf!EvB}{{}$4(VC$Su7qDe#Cz6i-rn=$RBAirdhX?;TFsM47VSwRWoho+Q zy6M~^I4LRUT3(6;=lC~93M@xZ+PT9gKvExLav`_P4(*ziDRSpkQi|YDqLy|?9oasl zj7BfJhJ{c;bYWpubbwKutVS{(8X+sbN+Wdw7ZzQ%CsY_C zz95*y286Ox&hFS#jA0vAkvI(2MU*}uWo4+c{06A=TbS#m!O(rU2W5Sg@89L3kJy|#8dA`b5yfCcxPX*yN?)id^OV2x-oZo#MnZ~(XNM2$lXy6K1xj-goA5A+`%sk# z(gZZi4lXHsqMiVli3UqY4|hjbVG`Yd9M<(Nriotm0z0EE$pzy<03L4r1}rYyuKUI6P$s+d@& z^OBcYnt-PwnkO3kJcRUNjlx$W&MzTl&)BPmhezxERTIeDO7B0d-@o zPZm;eq)`PLyK+;2GG$sK0wyEP$4Ap4QI#U|vtEjJjYYla0E&(-T9YxbVN6M)#Z7nY z5n}pzLG!UywJGa(tSd(jbki8=u%d-3Oh{0wopLOM?R|QhF2^bY@+aYZRKGeHp%K^x z*e?8)Q)kA!=~6%u+jEqzXm{XSc0Y?DN88t|>J_NNCvl;Ymk2lnm;{hR%kY{8 zs5*&O=9;pY@ihl^-p}W=_`V(Zhcwp07;_rQKkau+!e*3(mSikJO6^yT@PpJ$#{l_= zh-zS%N>Wx=dbownl)eed%V45|Z56Vb=zR#6BT6Nj26ilzf-4hj%0@>OL(QUm}@a*^JpRDpDdC?E;qkqaGC^;uC&y7=gk zjP#J228$w%K-wdCvLlUmE7}lkg4UIYMYSe)+)+oyc&eJP?MET2cY^XSSWs=QT~9zc z+N2`W>If7~bJK^xuzgUCg(^Tvxg#C5;0p*lQ3^K3u!=FNlHpN=L$2co3X&4-!h(cV z*Z>fUa5^vsd^BE9u)3_7hsPL0`7pmbKr(~WEJQ5M*GoG>fQmQ1l8gO93Pd5L5>xv& zP12hyeZ$;Dh_zgMs(V}1yYCAcl)ng=Xf2&Vs1`EJR-&sIhJ5-e8KtUH;B<`*d0`1N z26Kq%GeeFdzH*04MFtXY4*yz>lJQ*tOZa4R2Ea3)z|T4ESc3Fk zT%`8#?*Qq)(uEDfDDsYft6K|=OzXHMU*(23Q{X|Y`<}8Wf!yzOQ9*&HvFjLyxzOAc zCTR$*-u)|CFV{9KHn5yw&V;y?EUHQ79qlM`DnKl>-0La)76QhFbD$~aT2!*gRFe7t zRLVhB+LviTQ2t>g%h^vcMrfQfn`*_mg(N6tj?#VwCox$OBBnZ$phpssh0UF9+@TQ> zsc8(HIPpku%||X;hy6$)n}|s&=pHO#MM8Mw_c0l1zcy>D>wdNvad^h|*9HfM?k!CV z(GGZD)8LUiPMn`TmA)$A6G#Zey3*kRY~#Vd2xSrh#TN&$IFuLK@ngKU?`2&}m_kkT z8ly0U&A<|b#}^9TKtidZOGc`R>o|~Aknbr1ovaKl4uNpu+X|f07s`%cE`fme(TR?H zIQMk}1IBA#qN!;7;Mv|SAO%UOPpS?%8e<}~9lRmIp#}>Kv-wuV22P<|K_Zd>UCZE8Y2;4WaJ|z^*05}y81S~)E7>ma#PP2R_}rkGp_Y{NDS+z3@LkrKvg3k z67XZXn+Aj7Bmd*p=?D+l0Gs`kHn%7NryVI9lcU_%!+}R2vuP>9nk%wOK}bD^OR>i% zjA3cWIfwa$!jg_B3qnXAV=4k9<7MrFy%9(Y6Xl``tZo%(tpb%^ie#AGM~)K+tyR4- zD2+NGfoHQ?6BTS0c5PH+#v=$d5OLI(&L$mR6`Y`exrrhP+E4B8rc>Dt&fY!}3%tIM zpj|Eo^C31|nCp)55PDZBL^Q=y>hKk}V^x(wi)xCStC{$Vq5e-S*ENbXbuBDTc))Zq zk!A^u`FU9=1Ed2U?MQD74saumPW`nkwc^($q)#Z9Hk-d6O;z5pk*X6fgS!BS=+eSot3 zqYaWn7RtvReq3YwB>2d6l$E3M zaiJ){6W?Huj08j0)-u4mB?u>X%=hgNhhiG!J6nt|K0JU2UlT;Y0D1wCg<{lF5aQ=< z{+E=)73q;|MODw1*w!@Mfyyvehg?em7&Afvz?nRTrD{aI000TYu)2C=NR2{60GM{1 zBQ=<{VpR;1ElC!_qE0I_Kv?ljw7R_PIBL+Mx)4!#Oj+?2cuK$+L@5EoM6AR3)AVHGJ0qRH@{VsW1? zf}4nZB-od6rHaugQ+t8n^(Y`Aw2(St5-AgiH|RnUkqtIi{USlhsCtoELT%DZwb~-R z14d#@S}w(f;yuzUH^#O^{D>@TkjR8RZCuLuq& ziXaZh@~UfL6`FSKg(BS6RB=cSF13U)#*?h=Ozn_^%1~j(mIttYJcY?@FyHa zz;Mn{)Ra=}3Wt*wi}B<$hB+0!n9%ZuVfq{4gu*A}0P~w#Q12$Dg*zN#7Bl0D`DKS0 z+X@n}O*@5V_fh^Z7!0JfM3AGTc!ul5x11%|Tl=NEvgKYhR*!-c2pI1cl>#H_2D%f* ztcU^RNC!NuTGvO+XwDdRFo3~FE7FL&h^tGZRI-4PQe>gf>l=a1OJwqKl!C$xB+~SY z8G}(DGk(~KKul_?+Pgj1VBfYh6Q^IEp`9`6MFf8ab-{R+Hl42&J6BlL&pVKFogEec zL8zd^P!I;2F9{^;5uXU9$?L8jiD{w~xK2`5gi0pbIslh#NYF&>(5i0y65RvtH5Y_y`|b#$3cMtXRX)Lt4g<4BK^omaqoA58 zvJ*f55L5)^Z2tw4>vT?XSRKheI=w3dn9U*|02-Q71xDXPJ9s>xGuXV)C@+-yX-ltG z_zvgVi>F2+6F-`OXjViWm936I5m3Pl3fZ*{Lhf3Ji44QZ45L^K2E&Dg!U;Bq%LU=^ zSj*OcKugGxh^oUzj0#Me3%^i4l8k?FqJ0@)E&%v9UX9cT93=4IyQ;w!f{i64Wtgo2 zOa1|*cl{)gN|-CeVNR(zMtT{bv0DOB)Rz>S2_+YV!ysQqr4-ei>zIw8sK9`{v=LK) zbmCR!j$t(lo-syyvGwbLTy3Irns+fB2j0Bb%1awZ07p+CbZppp?vl?jDC z7krC$ak$g~Y+wmsosF`p!${?r+g_FFc^<=303DS<>4*^J^h@ApU$;#>)9ZE@Hbv!E{ zgH@uQ<|EkXvbs7VHy^5N`I1;&lNexSf*%_Vx62EDUDppjUjDKyeRSz9z+lWDi;V#u zJi)DEYs0Dvl@1sZ$EuQ%ubu!Kf2uFY5n^czfq`}xFk2K7xQEOPjy9*9OU^$^|9wx~G(z!l;frnsEvSqSsGI$PpUnnYIu z62SmXB|>Gz>xDp^RAQLK7!1zCFpCeT_NzDY5)mnGA&=x@E_)-{N9p4VA?{cO68)2b z^-Q&?vFOb1z^0u#&zs^A*I8QisR(zpS;U&|n#8g|l&6%E3tB`lAd1v4LKIzD`y;P| zuB>V8k!!@l#*ldSQE)svuv*bvb7M{Buu{E&rf2N0%x)xRtTZyMEVMVElz+IlIL8hg(n@%8dbW?Ct*oZmAx;k5RDmeehg>bGp$1Y|e zKL@#X0ULyXS(;y0_9zmW);#YdgI>JR(Nm2SEil-rLG_f-FmRM;NKp0g zAj%lu_j%hy2U?=*Ga|UJ5kaDg-Ius2ev`qo`t{DSqLG-OQbq-dSp_ievUOU;bSFS5 zBUUbG;Sg<^_ry1hCpil9RyTk&x{61-kcu`Jd-gk-T1dsoQ7t>ZB3~SCF;{=UPXM5G zuC`fNi%iTZW?Pks6mjriHa!IM2HWWvPe4tT)$w3c(1!&?g28wrt5z&!D!Tv)Ji5qH zEenNG&BRC@QeQGs+o7^As=5~IvY!k4H6Aox$vTaT=C8@N6$Ap-A6fh{LJOS{P3)xTd3btK;9PN%>%2rE9|MM{)HF02)be<3lX!L{s1j&>e*MsE8mDUoF@I z2*Gux14c&(jj@V5II-U`hE2y9(X>I@VF`7xY5O_`F#ZJW5pPJr`5*bgkd!n@AR3a{ zNsH_F?TD+Ekpqv4;0O>VG*aU<3R=rVCd6R^NngESOiXxy)aZ$1soVR!R)Rz6I#jMyhne!K0seiEoOMw3greZ6XQ>=eAn3dqt7FR54We7at@S zg-j8PQh{L@($w;Mgyw*Uh)9}Ex_Fw($-90tDKZb*}+YX!)to{}DN_r-S~Fn74fQ*M+9 z36wMYY0jw7SJ~T3_fx+BV}N92LY=1V&yfUm3RTXUp^+U0X{2X@>9lrva>gJqClDfK=O%v#pFyd@CmAb7&g*e*%6D}=GxsXZK5_M zHJFhgvZojTiC)}-e=X5+Y|J)v)Y;^sq5MpnN`sV&N=);ZazmN1x2tl$$W$GI~WV8 z1^WwZrdtSIJf?df!53i@Rfk$u*9w-D$T!%z%Pt^I)Uh|=IN3-S%HMPaOJWkRoR}PF zn4)F_jU8Ya8DR6ZxsZ&G(B>%VGcz@ zVM{Lo)xNrxIPjkONG1|+g^Z*Lrxk4tUZl6Kwu~;Qgw@-zZ2(1hL+uL-5{o+FKoWTO z^>YIRcOMB1=8=-&K$w+-<)};)p_s}$V$~7kIB~&LHX5x`zKrdhqf9zt5s|Mv#i8zf&=N12ugs-sX$pNV+@(b`11jAiRC6+ z0<511&OaWB=@%!-+ADojzcyBcxVE|$VG3`f)Y3?xBN2i3sAScbG|9?x=(Qq&9Fx?n zCFsj$_@rir>C_6gk#;ogOGUGUJ`blR0JcSK2h`l{cAA3(v5j-wa+(4BiDB=0i&Y2* zLwh9#0LG#Fc~!em8U}NS`W4)LDQoPuY_y1Mj4eRD_!bYdN->?s3_y_vXcpc`FAlch z!G?{1{Rk%-8&HkVdbOCcQx`m7rv}w9tg2JRYdZPZUQ4$@s1aqwBw#QYR|wvJpT!ky zgKd3GCt(}G(Jn{JxuX$vyfhv{09eB6HZrKFEUf;lM?uax?4+|9W|nQ&f9ysblqxPr z^eBlae4CnK*5ZGVbPS7XO{`KKF@rBAqDcfuFuYS=jzGEzGh?7g%I#~|vxzf_uEozd zk-iANy!g+$H=7OjDh*$?ECG^qoE&(DpJgE(@%3Q}YW_AU5oF=ToQ%O>Ffg7JbW0zx z>Z7bJK}v!$EvW8zw=`D(DS{kDV*q!!P6TCQm|Vy$DK15hkBq9Z8wQ3}?`wj{F6|sl;&;Gd5Z2LrzEMy(&(EZ022Xk_lokHjv3A z$Olrt6$C;y#u8>+NQY0DO84-iv8U2RKD4s%RI$UOGz8%X&S57-+WAdPSzv=h7@Kp% z)P%hpAW>%@L512BN*sRnE1Al&5QL6%ikDu%$WgV5PatE|4tk}@KX?$txm4kB3y(SE zBf*6r^*TMoa^>qC9->IfHMF)Nkr@DZB}`DOsBvvy(J5YS%5JE2U=PDw$5dF7kwn-@ z9F?WZVc16**-gxnI}EZ@V&HiM3FaBYBfGf^RbY&#rqEg`ITav5G?K9mTwx*Xi3XT@F&Hq3s2ay7WTMx9H{`!3r4d_7#4rxG zB1jyfp?O3^iEAlja?@JsqVtu7Iz-W3c06ED(FB~3nS@L*i{R9UJ@u&DxZ|T=oOBpq zpw*H5C6VXx?C%uhR#U1**6@hT58Q4GVdcxXO0{C9r6%62B7zr4MqB`MCfH0k8}wnIJseixMVU*k;=l;>RqiV4Ie$S+3|yXF-9GYSM@pX z3COf;tn~NPt>>O+E$5L((+rCosJILB8+W5vUDwxPg{*Lt3HMr5B38tnEd~{mvMz&p#2d;)d_4qI zM^ja%vM?+%Ayh6H6tr_;AYaiGA=HJb}ijNL#hUpKowlu3-&dLZjrSbrx$W;TigPtija%8h8#tTe0WeQci-HF z;xVjx%9GUvY@CcSrDU@Z1QnX3^f76!uHt^($rujDGjHT=N~sH&&0=zSNrxGzhDKR4 zj}mCd;YL|-9s9{bSO}Gg1AyMbe%R2RxRgCW!m|kML_YPd=#)0aeFcC)%TgVN!OVNG zsT#snZai-aMU3%+Pnb9XxBo zwl7!v*VJ+a1j1$xuX*mF_sGRkDrD17v zd7h3Q`FiMI0r7y+Hp|NvBMxo><1XZj?~Flc+kqFHyabOGS;GDZp~IDG^`B}oHgage zOEnNKrL3f^C_z(rOqb>d9Uej+Lm=%0(U7rPuT}_b+%|z{#l=hZQ-;BVIP%i=U9cZ7 zv9y3BupiORHQQKS5dniMjIk3BC@2B7Xx&&DdU@)dE6A$((^|=!D7YTwJB~;GR~p=8 z80i2B3DL#TZAfph1SzE(V4jh{<5k^59jQBv$qxDQ@-k5c873DbU=LlWKpk(O-A5gj z3c_+D8#AFaH9+reBIYV><`*1(kem(pQ>FOjL09oeqd=pL4}rjVw;VOu51)`jA~>Sl zw7qIma$-7JQ<{8Nfd7OeKN~R1-p)oJ?Y76Sfv1AUxzCfo)X71W7Hp zLk?MMRu0dyusPO7kj5T1Xf8F3Se9T&ta7Gk{N~F(A4(r`-9yu(kvcUC02f%2DX6fq zJ^4uKnh%i)Ht=%fRgQH5GmT19IS_~ns#p+<`bMUlt;?C@kb(oDuImvqYyLa~0Qov< zI&q1H4ZjBa3gDMv?MOBG^Q&&7rDK~40+JX5tRkI!B^RX07ynu@DG|asrl6g-@jw(8 z^b*8$p7Ams9tbf&M5HNjCEzz<3rUkvwF^}1_I4=$)#^Bo^3pJQK>4^SDU0e$h_y;B zUC+c!Cs>2EOe$ znGS}w!@vr*KIxqt28cse7$j)FfS-}X!I+rXu(a3sjEEB%Gk(*iFzV zYY}V~DqVBn6N~Enc;-XzY)>G~T-Bu);C(o|mVOBv5_@}~-A_BP$ z)4CT{Mb6JCHQg+HLM1z8Vv43D0+U#p;?nnhki)r1B9=A`KeY%X7%UNG<*C5}($O%9 zKx>206O@KD^$jOU=5#6`ijnv+(p(D4T3IO&oO34pQZtu1m8YWfqO$ZY&DdIm{X$HQ zi_jj)LdR20%0$Yx1CJQS9SsK$^imWmop}&tmSh!_5~Gf#^V7I%gs>@E!_unfBitRNH z9&ocEB8V6WQk!c%P#{HcQ}_c6CBqQ7v957^(xEDnDuNsa{5D9LII@=ZQHTrME7i^cpB^TMWT0kpTq>}7BB*T~NR-%DIIlyObK0*mZFq9AChzV>shH9 zZy_mJogU1EGv&*c0`(n7(r#`dRO=C*+6e|S!V-*G=mRq4h-MZI%Uog>B{tfyTgk){ z#?XU8jHgM4P@YN#1=tOM3@?H?pBD<(KtMMgn&{Ldj%cg{j2K6*(u~AAv_+sdr%Gxh z4C!fV6|oUb0d25goD!@;t8#$3@sx~HQ{W?x{574`fx}Tv(K}2PM82VR9?;*eB~(VN^JM<5kJ>w%Z;lP=`d zMeueFZEgxYaYQP%R8r9tEEg1PUxqTrJBVPe7A2F4WZZHr6|{O1}wedh=;(|h2_vENj_kr!3-I#G;A*L z>0E(8lXF{uR<-c~R_(yiw)7Bg0FVa#~QJ#8@Sq`yv1Qv}{-d-CH$?y_ex_`VO6Vthk#&@WrUc;;|B_bhXAW*W^jiDSB!32ZwBm;aY zL5<==gSM^CQ4u7pkM0{RTR5x8eoBFYP=ae(r4ZcF7+Cm*v#~aZE%8W0MannKQ)?lV zR!PoHwHWNH9~oE+DVCFrk)qjmeAUtjD8_|}S{s0-yVm{`OT)Sh+7eTsZ*B7Q?i?tB zPS+F$5Qe9|FOU|%jj=lJRHU^Lr*JuNJQq4z=H}A2a!3NO5-?0MO7>?6fA^sTY7|Er zpjIi6ErB(FvuJ7M@{ zt=E(TpE6_}x|@?2z!PK+TciV1RzM(+dRz}mjCc$nJZ9_V+8Eqbk@&2SlMBnh4Ti0r zl{U#4=3RrE0(q=CB62_Z)TG3zC=;bYhi3KnL9)je5WvNy{fecp!JEBX1@frJmj)*o z{K^pHz9689ASqT>iZao7-CYFNGSQVbV(&6>uD~g$wNG_^);>8c9nuLWw{)uG2INY^ zQpE*_r%xwFRpVOD!qTA*l7I}|q-sv<9hh1MxyEn_&;dtefQtidFSPndfq;^pKB8W( zE@wC++PkH-B%&c{BEjfisrqPLw1R>{M23$Q^{j*i=MSVvR3L(b;Ix4VPDSlz5r&-I zAr(z$L)4zuk}!Ob1z0!x-mNtE&0=U|3`Td%f}s|(BDJ`000YOMRm7(>_oa#~Ul6<- ziDIxo49LDq(g~t{Bwz(nIF}|cFiP8=;>aOMVbKKnKXShVk08Kts(~#e*Rs^!qs&uYBElN4un@UjtYE6wDtH_NQ03};8lZIpxlHgj2 zhLHku3|gIy8z36}Xl>cLaHMqbMnsvrX$i8oB3L+sl(26__kjj*VRg484dVa=jAbQo zJj2{H4CxX!k@*;)ALYPXr0v~!1xidsGABiPkQg^mkpgXJ`cYlpoi;_YojL6Y2zKM8 zEwa_&V+_Sw*Dnu1V?nIOQvpp>nilpOmu*hJ7)(5b%^B2U_w4YggY*$^rxSvar)2Ps zS?VP%XsZ$L5N7h)-->t?P!4z`La@jmP@bwzOvO@yP{0>fX>fKFK?m~(i^APE-p-|W z3d^rpx}|-TJe_c0%c%&e7E+gD1yQetvLbV~7X9-O6J>5B{lI~B>qHm*Zh$_!gW84F zYo>qyDAau;fgl;Y=yY-=OT8>0*d_0f;1Rd?)Vl<$(xOrnC=F29DK`=#a=|%+iS;IJ_-F%C zniY+)B5F*Lq{gWt$y=0+CCTV~BsOD^YYH4hV4E@#Py}tLGoQAJnsRgv$+=H3O7QCnZ72cbTFENz$O?>{(B7mRTe%LBFkXbx_1GsxLHN9^?EAn+ z7G~WPhdCrmgLOJ>;Fn-1kV*-XgiNFXiX}gH&hykb8id{iQK|B^48(lra;$>9m$Nj@ zIGxH(cjH1k^E9f%wH&ZUNOAy$#JqdCX_(b5XPC&+z@7AvNYzGy$fANnTR>?r4Zu3) zsokbe6<{%4a9P!cBn?lb$V~)!O3X(rKuNc1D||`YU-x7p2iuv#4K`Qf3kV$17#)|k zs0&b7)O{RL5rXsxRnsRmN`N7&T_GtSWtPLQ2$r*PJJ%79G>jBVOaoL(tH4)2!HYxf zQX)3SuJOGKD+dY7fgnYo3XdF#H57y?$JS4Qjsd!fVjTdKtU7}UiN*-PeR}~yiCI8G z^4pG6fdLW|0Zm$1Rz@nS0}ohCM~%o-CwW#N0)9=jI?eMg_DeHf5D)=S)NZby(ETmI z&nYw`8F92B;;^hMVW@nyV3~j|p=$&uBsKc(U`68<4k7_vtBdo?)5{jm0hn_M`^Lf8 zZ69_kOH+tsEP+a-lN<`9YOqkMBOa#|P(#wW!f#fp0I6-l7e+~1UGE#<`@ReR5DwiL zyPRo8DwE)5&TljvkDS5Z;0%DOq6~16x~NpWOu)HO&eRA8@2ttTyg##!Ag7r->v4y7WE1~+%CqKo008|$tS&iuU0E^n-o2uc#Va?ZTR_ zI{ssIG805_vv4Od3Q?oo6O!!O7*EX-Q;lLev;!RJh4N8FDz%$%KM|AM3(O88grrK{6o_0(S`^F4Fz5| zvFxd}R3{nS0gaFhb1?8ux{h`l6UFo+0ch|9p6}pcIqhsuBs^IRtN0?W(+mPmT{8a07XJ^#WI->t_N6y+DAWoQU#NQ^!%)eLn z)TWcq>sH-Fr2wY{FSP@fJ}pWKh~@@#0zHFi4=kQqSA03!hUDKq;-&}!!UTPq*Xn+$ zRt|{NWiFivXlI9vwWyT8d3PTd_5$t9k;u$HL1%XW2N%)mUq5jx+(*~Pkj0_HFw zGap&Ej}ibNx;3N5AvvosaD`}0Clib)(_EQz0Q%JF7?GIFJtb^f#fX>0n2FrTk3-r) z;J~=7MPOXfJ|dcWjS&+d-+%q*4&`tFh1f?pE#g=u5J*dIY=`XK*Za^m*Hc`RrKe=6 z5DB0UVlFf?%~%E3zAOM1dZN()`Po+dXhWj-5=~w3#0zcUU5}%~*95GILj!O_qMOEG zhlVX7C3Bq?RHtOHNQthHD;Z2_Nk#`o=ov3_H@&--vvPbSUCS9j#tOF^T0)aTyVDVu zqbSbL1~n8s!A?B72HhMC2#B*N`?VJDb<;J$E6_>L0B!(bjcmJ@r+2cHp6)h4ScU5v zP9itlQMISqkR9<83gt`kkPDab*zAf*AOs$QW0$h1W^9(0mn zT=qCmRVN^()Y5^UL|KHvex{@OHg_lyfbphF8<;XzjGpRGmfH4!(qyvoye^5OrCR5}cRRP6pk<4y?RTfENRp0|xcJo8Zm* zBw%$3s|D|r1|pTp3Y7rSDHa8eF$5zwq?3W(cRa%p-vBt}KwAWnVa`8Q#~h$%A!-!R zc!hV^@ZYA-g z1QhhiQb>`8Wg-VVKBwZtg3|^(O>eSpRt78MK_PSHV1Y=KF9()F%x=O;8$v@I zL^!!A$3&6zBsZWUp^QP6 zJAkrZ8%+Ftla+%N;X{W`NB$v@WctSd%pVjq<9m$+*fl*U-%YzSdDvgz;Lp1xtte2_ zo&!H@K}|WrG68phiFaen#gs34tlmW0V-$Xz6yG-p-%JgYvp*l;g>FUYCYi|x&Pega z!C-U?Yei5H19KyLJj&Mb&v;-O?;U6~#;JIqZt_n+y-OYo@&N^FQkn}Cvp51n6E*=w z8$$6MV%xVi>VB${QF@f(XN3t|6sU7BI2~zko3R&4$RR?^qJ0b8K zAHjtztN|PjF5sXRRB|WQ>V1r4YL;?Lm%Ks{CkI@hSUbZ( zcjyT3)|CJ`VI25;SdESf;Kiw@G%PVM8ww)G%uN6S*%;Tl+e>78fsrc2N1`A)5#!p^ z2|*^5#)4pFfSY7Vz`&Fz+z>&%-tBZgs@hu!C7WV7e^TLW5^zFb zIHYcRztvI+^IUdwoqB4lLZDWxTQkqN*}gu1DPQQ$l*h!sT!i=((}!>~yYaP#6$en#+; zlI|uy%CVe9wVRW+eE7DW^=Ae65GuiVVUrZGiTc_>$(B}&a|I}D#P$nkV-nJFFjTmd z0#hdZ3eIv+yEb7{)GHc4(YC(z&>LU1NeSYv zT|;3|`N{#ft|Wd>iMAg=+;qn#Zbb&A5JdN7(0>`~{6HA)>se9rw-N0PA@(5n0H2#5s|@**v>>`($yHQQ0IdWR4vzfv_$q6^$u9}2o}jxV}3}_M#@BbuLoq#CtFMnnlT58rq8d zAdh1F%3KM~_6*r>2%^49FiaRyIK@(htT?}kgw1V0O-RQ3#|f(QpQ0NBH(zfJkO0df zX2*N?l}y{xrV5IsTXor(+<~Vhd}jxj2FDcbft@*n#jc?`mgNOH)rm|MERjgysUlD) z4U#rNW@4`3-X$yt`>E}KMF7ee!D+@LvH`Wi>s*hdVY((2rz4+X5Jn%%vILpYC;gC$ zRLvz_?=}LVM7EX!z0-`3bj4@Rb2lMI7*b^#Sko!2&jvvfq8Y%ZXn-0? zQ3BoxL$(msr`Tjoxdi#iSxOQ!(n6?56KsWYU=Rwq?yz(*)#5@*)`F@gQUboLC6r{e zrzr{nEY-m(dm&L4N(^`>U^!?IVfCqcH7~2N@55PQQiZ>q89S&MCZmu!d05wqWCJLi z%G~~_v!LgNN@JLbZ&kK4=uw5Dz@_~yZAE0c5ElMyN`}xgkqA{MD{;tM6%x~%f~ypp z)&jMO4iEBC0#bzXf7GHWq(MbY+X0DOK_Pj35r%AO9>PLNfQMtycx{-MDi)jiUFHG; zQ*#lUkuV1=-O5m^64q|=ajhr<0GG&~@&QL;5%eHV4eO~;#+#@j+R(x-(1BIUY*~ zjvqI4x`mX_yY(h2Wa7^*GRi5w^B4@Y1k6+s@=Be+-JOQrA?}Z#yDb7r$ChQ)mHN z#S5izY-5-Sh4^tER{UH*r0{m}(T!Ox3_x(nK}FHZql7|}`%rSsGsbWGHcjSFnN+QX z-63^n8`W8qry)gdY6J_q9T*Idp50Aw-AZo+pcB~|exd3eG8hMiBQ!J!)sCQ#+=RL2 zftdIPwV74^HO!<}JRmYyv}U$87H z5>}qpm83fcAW=Ci1uS%k4ucJQo<|idR2#am7PZKMU`#Py)rztff2JHgjzj^a0V2B( zDqUboBWNO0pq?TYBn%}WV-{JQO1;KNMpKZz zb!|+h=pFhbU{lm9XEq2`<29YS1ecZFmZ4b@k4ylwYL}!{5DgsiAPgW2jx4^RE)_vZ z%R6j<8f63k0aaMbQwn!YYzh>Ra+G5gF&U1L^kx{LVn+_Lwc~)uOAee+o@TjLrd2$Z zl6nEIMlbEJWPj5)jH}qS6|NRgh=y*-Qx2#RhqWGQ95hMZx;RA~OtcS_Y!<)=ot#bK zC*HNNO;f@=N;H-NZLMJ=kdj?SyZJxDD0v?lk$P2!TxpOHl(ZW(^bQK7>e|`N3y(U* z01E+u=g7(u60lVIbzB08M_8W@s4HWxftLec4`t!~HC(1-K&UsKuhZ zohL&cDYV3VkgjlR>0FsZuT_Z3BdM~u1S5_lL2W>plNJq_rC5W}YJi04L6T%(nB-;7 z=tzZ8C{TvRVs7a#1t7bp-!OvWs- z*V~$I^66CKtnO{z{jAy2PU*djZR;uT7M*?}^rR?Wx0q7C?|GV5e2zmr0spfgqHI)h{+a2*aOfxtW6# zV0D}^oQ*Tn5*Abe1UrSkc42^kde$tA(;@!>IMC#8iW0~kv-;O6Sm^8lZAfxdT`Vz= zw25*c1~=YoU8I0Qr!E%IcMPCy4{9h7cM>yR1pSyXDw#4{6mh^IIWPD-04UjuECr-hamcdYbL5XTu&4!b2Iy!s zNyavfUqDf#rUofwkfkE)>L$a0FIz2Rq^`l8_LLZhM4T$*2`ftw59kc$(jZbMrXb*d zh1dn4L<6rW1VdEdVy(tgCdS3Ld8rH6j8rB-AQG4xuY7JGNvTQ?zwApPssp1?jLuIG z?udyCTG9o&9-g>1Ov1vd+FYij7KAF6i(0~c3go(M2M)d$PzB^U02#0y@Zt!M4i$E7 zY2$*_X+S2j^2jk&@@8RQ{(OmSWCboSkiwrqX*bsAsRUqRB3PV+WoQ*QwSd-xD%~n6sv9_ppx3#N_+GJGV{rY|1|z!M{FKz!SU{M`}U7L!3i(nL~$kEdXi zHe>XW8WpJJH8mo1y_2VjQ1VWJvZXW|I=NyW^0r8KI36x|2>tkh#U1<#OAnwxmiW3d z2Ek~U<2Tp=YipCXfTAG|lJ4NqM}pz@c)<}6#B^G72XP>U297%RQ#h=AKLShJ1XL+d z7239a%Kt5nmS?kCRbmoQ4*Glr;HY3nir)bJdSERF7>kOJYvaL!U++c|3DC5#UVxR3 zBT@O-69LPP9HO*muB$uHtqoDnYVf5r5%iR3KvNVKq9viMNHdnJqY1z&=yO5ardSt8 zj91rCi#wpxBhW(`kwDm1tFsg?6EbFN?x4wbv(_?EZf;Vhz5}wz0%A}L8z!vPS+u!C z1v*%nMGV44~g^fMzWz!B(^I6POF)v`8EaD@)QBfOrC5cY(=v? zQoB5DUL7P<3UJ_{1DmpST2LJiRK5;k@A?k8_Cyifp?~>c&={E8)`j*#Twn(8==Z_dL$F?21^yuBnKQw5`&gUu_dTW1dL;o)D7dq zn%Y`e&`=&pl6k6H!+LLZ6`@ASf}7hBP8Bq-LHG}f6#^D0=^aRF8PSZ91JKy4`)U*c zEI{MNsz9z{jn`aqbBFNk3s$LL&;bN)13=$pTv!DosskW4sY3E}0wp=fL?~Oi?|P0L zBtb%+%>?Q^Xt{-uMK4CEnF;zfbz|Oh?(u`X8q<0z+1ZAY?1uJVw4;SQz8n$nD0K29( z(Yqw|DKStcF*pkPh$Ct@%LSXY@evyYY+EXFL?L(+0hl{&+s4e7i zgnL_yIBneEMXNj&3llVP6&D6;UQ75Wpjv!XEDx8k2u>hiXD?pt0vFiFj*p}w`Su+W z1uqT2#5tCesDc_5NUIcLBvDz2GJRW@9KZ0LqPYfrBba&JPHv(e5CUMg=iw7vUJVGW08U* zz$#yLFf>x@DPL=gDMRK%0|a0pb@{4glZBOIJlW`Cdedbdp^qAA!xuW9Kt%w@myWaX zSqRrK0TD^)9eMg=E_sXTD}yvZ>Q4qlsOv$>c376QflmX*mw+wUp8}C$X;b`bXTlJK zP_0NR+Ef1`7g-Qfi^8Et&vLe+!a)iX0OAN%aHA{0(iM)>jxW>kt1c#>WOT4W7sgxN zNC_B8y$aM#I!?da>w(~-Vl9nOg#gM(6UCGXJYn8oF>U=4mYZ3A#vvG=|O;y zOd$gTlNee0$Pg??54e+m8?J!lM41|#jEP6>UJ4OhfwTue`cXx`LEDT`hssA-`V>lJ ziCNbvC_FV$KMJYEU|ETv7HKHlO^o;BHVWiPx?~1qRrI~UqIkqU&x9ldT!0JCfU-x6 z!};MX2MIe$smm0#@Dm9D69}r)gX{uvfa1)wZ7*O`em@ZtK0q+WO^g$Rs*z$OK~f{u zh)rZqDcKY;DauI9awq1ga1?tA3XR|?|16wlt|O1Ja3W`2a_E zQwx_+tW>=k_W^mP}A%2@#Zu3qgz|W|CvSoKryK4OR;rWKI&y8Fx;J-fapBbn?_4 z=vFVO_}U^{jQCA}9t?9zWNu7`{}7w}K?+7SG6B^A&C)P&-?b%hBzYwel5ZLE*T3>X zq9yphc5}x7GC(3#&>^K63?*3+1B8tsW`_{t-Jw`N{-}cH0)HyN9R?&>I)o94F92}GkF{#pkejz{CCc4S7)kP}uu{5B7U^`HP zG~BeWAxmi(m${PVW<|^4)Op5zVHIc!PC;QifD#jsIWZUs1a7Y!RT##Nr36@K8^ZQf z^NPSFfD)P&=UEVBDRSI*;R4LE*QA|m?X4u5B0BXjJjm#{n8}x*$cr zp3yuE{he1z&VjZA(ZFSVMg(MMPCqyp<39}XFM>3$h49WZr34c$fimVMX9A`HIj}om zg3G&Z)%ZvuGk{o{t|X>X1W`3p700@gqycA9J0tY$HI@UBG~go$<#-Ber&(h_N1Wa@ znuCV|y^H}q46SGv76%k4;3V1;VuXPX*SbtT##ma5$(i10&vItv+ftH)2nI7EPldH% z@!?w1O%UN7AiBc~`?zyp_DJ$T#tVfQK+N7{NMNR`2pas>TJFF~D9vl9(^l36AkT*n zwa5eo6ar5PAW@BiqYQfdTm+1}2@@3&utH2nt5Al?3O-uEPe72Dr?dg95zuEBaI}EA zoNWs7$FlTjl|9PPFv-)NW|WU^5=2(EQFbJ4p#W$t9f}k&rK;;8#g;3r#811piEIdB`BO`5X~p;GH%j2ZTpBXqJ_5ZfT}G?y&@ZLq-=%tE>Cpp8lf0I zu8Edh@y-*-)f98Jl8%)wYrwS1O%gTU50|bmmwFjx>(V-A43IZk5 zEny7688Q`#raHW66KETWCbNL=k!?#f-r|mSVAmF+960bLc*Rl=~ppaXZ7pfuNTqeku_ zEd5A8o}Q{&<|NUPgWjwX^$OP}nq8SNl(|KvieEX{n}l^63h`)s&8u06!Bf;g7_*k9c77-;n}s{Nj2IvzH{C>3pssu)`7?PqYyc1= z_FsT_s!zd78|tM%$ar@L)HEO^$;;4RM&!VP>IerUx{9D-!R-kqr^ipG=-Sb)%#49O2f zb`7u#?jy8Gm#9cfzhE3J9!ZI5;Kf`pYI((4jko5D53n_K@Il3lqpXp0O59R*MiWP#wRloG=i*5hqjz0ATiB_ zAo6p8TpNw_v?OoWGC&Xk#gc)H@}1HduY(q8*~J`&`IQx6LQ#A`3oFw{n0x91;L@{- zrc*wPu&$?CkL`0m{3^k^-W6*Tp&to&)7^&T~k8BkSH<=(5IJRB#Q-~U^-gykd z)a$8Yc%y`Rn#JodET{Vbg+~;KDJv21 zi$rdN5#|pB+S9G-LJbg3Z%k%3SNVO zhFsAo0lA8ze0Rh&-pQG$d~wjBEY3CYK#6!9@BM(lb1^`MITO8BTVIk^rY+zHMfHs0yD;C@|%M>^x!cG0f{TPmvh|nFC8YZAT z!J_xcLvBE3dNc+@=E)fO=XG7S1OH!Xwng0`XYmanP8`W!cbKkf<0WQMGJu;JjX?@9 zwU1V!uC_mCPJpOdimF)WKYOVYLWlpCU@s;^Z?oQAnWIT+D~x)LcEr)1tJrqa}fN+aDd7nN#} zs+a;2j7AnpOqh_C<*;M-(d=AH$*y4zWXQncn7#ap} z5}C+~`1JzWK+MK!A^qMP@!o=MP}S4x%}mCSq2=@%xW4NK~WR>Cn0~ z!)+u32W$$99p7XL#x;O8tjZgGX$(J zv=Rq~o2I4{=|K{zno+;&asElq@YH&8rZH1Ah{Uu8-X?HmAcMWec)lK=*lqq`{(kAy^l+6O5_ZWxPZ?3&tB zRof`B$v0pq8NEZF+(5BPqekhOg#v3)lBoc0%fMPtNlQ7w!k zC6orPH4EiC;Q1H7@T3Y_Vr$wE;PSyckqAO9I3f}p2dYoz0q`H6y>MUuq zAiT?*4f0roVnhiVjTo4N`(Bb%pji&y00){!*r80xhdlc4Y)9j z!0Zku$Cn0)+|6-OWOiX*v<(W-?b^?FfMyKmvdATP#QSWn^0xWF5rL{ zH;K(hU+Q&-u(0CWgbglp$d2t^1#k9$ioEG4~TAtLEqkUBEqm)QgnIfB|OkSr7a_Ur!2+oEJEn|Q#qjp@o zX)v7&h!^Eh9d>YsYX%NU$}vnzKm%9#U{miP0t-0e(h?vc@#`AzG=p4aia-xMts{Y9 z3bD;ug$Q+x%(ag(Isyg0-E(H_K--RHvrvdJ#!CY=XvMPN*pV9_CLwf)h7JpZg zAs{SmOOkqRXOLl-RH|N0C#L)8vzARt%eaFeNeV?j1n=E)*0MB+u%M?1jO~Dh<`rcj zcRKm|{Z^Z6SikBx^GHu{V9Ep_G+?@G37||@N}Dgf^f5`oFaSXW0}Tzr zf;bx<#e@Lp+%cUdwJqC+jPW{G1iJ%)*_83+DP57DDnzCh%Moq*u`cgrSzwfyS;$q0 zZ_{cK!A%sP%otx1)gn*y*$1tK?*2dwVXL@rbyU)Wja7qC6m~%8T;Y+J?K@ZhAq5RVkzb#vrlT)(e1ljmP0UwgU9h4-aA{5Bk<|`$s zk`s6>N~K6-K{Q|s(A0Sji&nJ_5h)T=3tlM7qJF9p-*joHqJO?(EjYU`GE874tj3hQ zl*Cksa%?~%LrmKeHgN2ZTnh!1Y(*k5UcI=(mw*O)L|uAQ4$2`Rb!j)eYXM;u0%57* z9sXi!J+dTG_5xCwNQtYvV~j_5$EN#2`Dlz6xxztTXmC$88W|G?+gC4^S+@ud>U!1= z=(OgaYN*j6Uvv2m!liB20wPF<`f13nE%QtgMDF`$2PNhYaB{L)Yijb)vvx7tLw*Ra4~8$ zAw~IA;gKx>{xl$g+8I#bHJ(;M2T35e3rI%8k}Fg( zVqHb)BX^K)yrMA95Ztkxdi{?-V1Z~6ZBb`A=XNO}O9YyfGyPc60Lhr*K1|do2XkqI z@d}u3En$GC2%=k=NW)DU({v)B7-Ez_Z?e^)mQw;oc&Q-vO*mNByLwfvqvZfMeQK6= z({ve^oAB|qh45r$QPBuS?>PQBAt*|}+v4>sf!X~vwQNmfK3dOyVF|*PEj*nu*etJb z0afofl9)2p%MI*$=%xp=O!r7dwlMu-Bk&PWNHIXYVAM(oMYkI1$6&HSiV`#k&ry+u zyt^X;rlg_E6llvpy{9nAD3oS@u>=fVKt>uqdKr%nF3o1aB>`)>rgJ!3k=ns-g(P8SzWcFu*c2*Ln~R91OyKKtQx>cLeux&GXp^OaOurR)Y@patwAvUgu$ph}@9tF|FxlY`C6L~FU9+V!%vFoFm)S(?$A0zC*@%PL~pAN7$Vx+T0U zDqGt2)G%z?3jH7^fBj3DzP1yC-N6`lu;UjzocceP@!xB30BHb9%TpI5QIB5&vzbaJ z+xM=atM#4M9R4gJWRM-iUF-2}L`U;;oQ!G#3ag7(q8yhsVsCL~Ks zp5E}H={To0@e%AcW##J>XmcBPKnh0O$rDg=;~@glo0imxz;xK7C5S1Xz|>_rlx!2k zrVFI)5n(6+P&6Y$n$)=hdc^0wyH+d&9P))V0IejdJ@uw&o~jO#0xARoW@rIA{CLen6e^Q)$u)sAWtRa*tSuX0s%)ZD96q&n#~tVYFCH? zRRKLHX(+7?3Ky{A$!a7gD_V$1M5YIsn`&SB@Z;qaZVTTVUf4j`MwSLGmKg%}AF(a6#xiz>mP zw-YSsqE8xzAMUU`EGrOzc=rfO@)W_kLJ4l7rH!{a zisqoMQilPgt5I$$@H*Zy~jti!V43q^$G^gvpE&K#vowUXARMpxFNA8QGI}m}LWKWrLwz^^9GDbIL%Uq5d7zsnj zQ;wxsfgWU-G(gOt)}DsxCiewF1m!SWU4R{%mv_qHKN?o0zR^GuS!%Z_07v6P35=AZ zA|FA)_d2$D_d++zf;Q!=g$dJS*~$k9ybi^+ zv$~|M7BC9SuZUB^wC6sr7GI50!aH6}^vFi6-9QHiu#=GAF?1QO73DjicD6^5BCPCU zdQB!ICr=-Fkx*VHa$1qC<&kSrw<3zw3spdgX(2Ge7W=rP2b~Bj*TS+~q{{?WidDNi z5L1p+wVS3(OGL#PY}52_MX5>&dcfyhmUH}o6&WZK?8*mM;-@i#VMq)_LNFe|O}6fc z34bRF?;u441$G=IGrmOmrg)c+!o52QQ-~D_8~8ST`ACLDrEO97B}sL-bHb-XU7!R6 zxd|D)Of8b9AB7XVu;c_rxgnAHNLX$FH%0=s0QO767J{DK_%=+quC){*2PU8dFp7^X z4r#o6u%JN57?8ub4|7eb4fU52Xjn|!fn*3wFDuY61xlXq@Fi!|@C`VaZY{Jrc@h;Z ziC|INpKZm99nl^I3Pk{pc7%Z%k7RB<*K%6pFhG{7dR2-PF~#?7ls5d@gASx%N7Y2` zxI&Ze;GMnkE4f)z4gh2VlDElfOfNd& zz;afmxtiAt6l8-7vcv*0a|Y{50Id!f(w3Eq;3mFRVC%lROt%rs3j31x(Q?XxwrD&- z$I%Z*^3=P9FeL$P$PJ`wIiMj0uztVwOOBha0cC(Cs73kO&=djyvfaTQa%Jo$0+Vgp zwV|yR*?MaCBoEX>)f8U@&}6WYLV*+%#%m}~A;|;bekHqpDRWT;GysZ|y3KJlB51(xfM@m54!;zt&=@oT&>PI?3; zjv5iIq129ObMqVtpjE1BG!dYYq5tJd)tL7_l{|9r!d1YD|gjDTUC6kF?k7 z4gkjUdGD0N{{^HagOzFXa?F5_1e`H^1;#rlc&6U8v7*WEEht8o9Ly4jVCV||aORwN z$1($y4^)-!sdZIB@Tzmd5dn#iM>lK(1UT5y$l|o;azz(_vYa5GP^qx!Lxj0N;4SEb zCkjX%P-Lk{wmLX#q3=A2^5xKE}1Y)>8s$S)J#o$PzBJzfcCC z22DIid^-^-`d7FHh|CXJ8DZ!eX#kK7V$I)QsMkxXK$Z!J+yt7_2-d)&8U0`aG3&~f z4rz~s5G06pW)zlobRmrV0WTB?86$yXfbuE9w=)Qc19J$N3o8m#qqVf5U0xqSwI&rr zvk;iMVBw#dPLEs2FK=H zS@8#;VcB?c!lthYY+o{d4gx8Bh|L8Md|w8sMI{Vy5$J^Kyi=(*SB^N4*9!#Tg1}e@ z(*`(L+=QWxqmvt~&?lOSrif45x=$Co2_hqP4KyHTzawC1i(oq0Xn!&WiwpR>qf$W} z5(JczK7tGZPza@AANZstF`CB+kR_(InS){O@CPX&*NWI=u%ZgRU_f)0`v(&{J_VZE>os=&1GrL1w%#Y zeZFUa`$PpL)oD`uromY>@i+j;%0fm;2_|6IToz(PI$8;MN1x+8lSjIrePBW={NEWs zOd=;pRLavwTON4xRT%oRDRT-0h}Go-D`L}hD8mSBO~kv3Bq~4Oz!=ok=4e6A|g!!lH zuuKfr(wkhaoLP-C06ambK`4qWp=Y`hU`yvvQkNG}R3{+8>l#Jz0g-_g{xYX4FW|5R zHM$@t<;VdM9EnKuK6`QI^q-Jh#k#@@4sdIE7Y+XA%7;$4E)#H5WC&Q=k4Vk~2!#@< z1KASXOkMuUU=@J)Xo_zi05_hA5woQgrBm=_BLKS}34v)OhesA|-rNKTi2R9JA+4>BCIqFYRPssgk0!?$^Y67(-j z2uW^;lPc{w|G0*O+(gq{@OQ^vOyOYHU-E&ub)~22&_@P*m7Yxmn)_hnfr7Wwp+=`P zQWwwz#cLlO{#cD0l;2*g+F;~Rq=He1aNI#%Bg(1vM9#2t(0sZ+6{NKN*KyywmzEM zH$=tKjAT>?X9bdze2g*D(&&Jys`Z-OAWwDN$+{ZK5s@;@q8y-MO@Fyh)tmv&=x_o= zF$X{*W26&HNXAnhYXBHQ1aM18gCxL<6pSd4Vy$ZmcC@W>AOj!kG6!N7Tmr7i60O8Q z5|WppGO=S8iCRwb#3KPGd?ywIgmTmeg`awyMY#}(+V8;S@DCQNot0C|{)v zjX13WHxhi8w<5>rp8Sf?v0Ah9jd)j1dyhR~kE!2yTS?p*KPjwJXz=dBc zYFOaVu<^D70hJ5{ybzu{#^a8zn1*PwA{7=*;XL03If=vVO{5K{L_TA@<+2@%%EyXy z;=)JFLeF$8LscW22z(N?93mj#g2Nc>k*ayDCKbemU+ndo+#nAk$3Opy!z{ai2KVmE z7Z~k6dc+h0z=cQb9YIpnL{5Ev`0c<9QV!1|1lQ5PM=Qpq;p7HS!_ddsY}2kmsun#( zek4J$d{yHc168WC1cG#>9OxX#fCU{VF_PZuZki-)B|rdTQpEz13yUxJ`2%lJ8XU9| z;y^8eJoOsD+B%C=e8JRQA}9)yZt5n%`d0yp5>VR2V_lmO@S*J`j%e=Sks%AB0o5o6PDnh0qf$p$OH9_G z7Ql+EbERi#0K(rj6#;?lVH>cjP_q09&ngOsgA1wbEsit17SNsC1|ctDBIo9o>e28Bl^Bc@esJ<1^-JUZ=oH{CnLWQF2-9|Fb$3UmaZ zaASBc6`@0==r#r#Tt#|h1`1o1Eev8v)I`b$ubUE(2GzmAc;HJ+t1>IYuGQ85_kB-k41q5(9QYW+ z2gn=jgvU#%1x>1uw!%?QwA5~vGVL1JVQy|xtot}}tuC`nhopq1K{jv@nW&KNS|byr zDn!u8I07bruof$j2Ab?6hH(|lf|Sy%hzQZxlrEqFRoHaYMRHn$oX8T7B&1Rw`IUe9 zsf8^>ifq|Orery}`FTeNL7tLvCwcEwWNE+}uWGe(c+0`oFGzw*1_JbnN@qgAl7X0J zSq^Iu@U)Tq-xmkm=>mWdvyIp)3PdpkJR&V9>2aPcvCbH_e%LZSs500#bc}F95V>zf z2msAmfOQmyutvbXLgXeIUZkY&zyfxdTMMM>rC2Cw3Xx?(RHE4(T<9#~m$11=r%8S; zEB8HhECDs9Or#kl$k2rZBx4ncfy-&ZeJc`}6>&r}xORlgLsX}B^x;%T8|+sC7y8S1 z=~0vmq#LP5@wGN_g0y7Vj?IE)S&k$m$;P#|6@efV`7>ed(-%Xbw2yXxr>>VLk*Fd$ z>0%Mwkrl?Z3F?V~JcM!#FxMA4g}8%se<0A~a$t;lU;ch~)kk>50&J;*bx4m|B&LO7 z1!OtPN(ts_b}A)8+)+5yshTof%g{TnUD%9NEr^t$L0Xb0C+46iP^%O;>=D;4Je7uo zIV5u3G0}-j=2XFN@$0E`4uxnp<9&%U#!b6`73JWdq9}}bL57(~QZ$+H970MJWUmTS zAS0Syw&^$*Icv#ZFAx&JAO*9i_kxmZrD=o$TF7=I4j+bw3nd$)_W=kr!%Spux(|#h zRlcXtaGW3^5u_vmkBn!DS_wf^r)m@ciVrhen&fZdLk}`yN>ba}l`KKw*zpnRgdqoP zqLQu*7RQ-{70|!rNedKu8UBLU7hDqwfk6P6RRsoK(ru!^uO#X_cNTld|xDcfSu>M-M< zILaS)8cOpTCR<$tc};4Jm=JR+6A)htv9x|92Bmpw3>Pw&U@V6e!jLLJco+Y{yB`G7 z3FE?}`z{QF5`l?{8oDwV3%H|X0kt!bfe&zFY)>e#iEA`5UC2=8*fdQ4;)q?y78Fq8 zu{ulbbZ)$_JhouNtO5agVEe+VixP;$6h2QFn%!XiXj_4vcJ?cxVfI;#(LJf!>Qt?86P>Sp=a9 ztB9Z?xH%UQyfG+aIPRFG^)Mxr%45SZyQC5Gk~I?N@E;V z+p5|o0(K>kA~672Vy!M37-6D?5kv{{H-(|GP+0*-wtPcpSrIj+c&Qy_AtM=N*>G8t zy@moo2MQ|+0&S1b;R2r8#Fk-eATWyxu6MYHs*&Ya6l=-WBeF5wg=Bz37?eXTg3|*n zsl~eLRXY>o209Tn((n!zAL$?R?x;PVv^rtPlX7f5rd!&MgINqpo`kH$ZBQ>CIS_^y z=aGhw zU7LfKKKWa2FRY4WtLYE{VP52^F)ko!X_!My90-f41Or%vxk^SHVIacK5mrfEM4~jA zSekWs5gt3@PX5>XoimLjNVuU1L{`y9vUN=!h7pG8DrG@3go!e3G|Y;&$wNRVPi;=F zgok2N@FH_d6RLxpqClaKh!EpS0HBY6)!mAPo>kNWZO!O@hXLJTIq;H}W`V7&bfp*N zOIvp<4P`FmW|&MMLvIA&CNK4rx~xlAV3d32E#cu zYAA#DcX6$Q}X~&U| zo1hj1s=VC~EKGo9v2?YVB@J4V2_j@g#$28J>=BBx3bDDcQxifYtZqeW*WH?zI0;78iba`` z#$aDdayiYyMj$MXg+PR5bs1nhxcDkf0@*4gq)dy-oTOccj7KKgaY%&`S&pb2fk5Go zdiAneBqnVamL_!qqY#+;0mooL5J6hLecShy$>cEwC?X*w$j|_ll7wPS#E3f{slqA9 z&aMlziIH|3ve)lQ*7DNxbw=j+aueoWSpnDqOYrs%($YAV&{^aPE5k7EBRybQ^41lZ zK1l^%q@^6R0$kOKg{Oc%LKRBFYIcGFqdR6P96>;qkG__nreiY1V3vPiBU^oBE$A_4 zA_O2)!k4*9fD)XEjlgWF(iT>5vlOBq4Mv=tO#~oyd5SfIIVzlWv2{(uO;f z`9%m;BxgKTSVeF?$)*sh<@-H>X_)H>Ax~8sMx2I`0en2N3NFwpe|_rL1A~bQ^_tXI zHZq2oRe;QSOccQ^=MZ%P$6Cs%3OGwoIVjdgU?D#(uP*h%&2;6M1#|3(Q!);;C}09m z4F@gh`0XW0oQhx-M$J)`@8zSc`s;x6p)PB>*?!i5+Ux&gkwO zYiU#TyQWaDn3f|gekDC)d>61~IL0W-Dsm%8FQm{X1dNB6-qgq>d3I zEo3=$K}N~05yx$0LQb#(9TCzbuBG9q><3#vh3Q$|>!p>*OfUO#3MHyA#$2s|FUWWV zH2i`{C}aHj4@5LTV~{6fE@4Fx)FMyT0ad^6xv3UGMBogPm!~*PjKhPPXqBvRUM-sV zU?+0c!A}T`mfQz8ke!_*Bff;;X`@FUUNj>XEa8QH%0U?mHC{szm}|QUS3xKzQ6ThC zBU^<)%*#Y_$|B?pNs_~zAVlR3X7v$F?7wDFo%{SmH%VSU>a5y?$eD@iJoN=p@Q_i#maQL%p`T?xtoO(%xXlc(!QlS&TvJ1V*#K@TG zi4O-lfed&VH4~60E?^G%>S7V9lN$_~2F^jAYVuDK}D@_$j@Y0U} zp*pRN9m#MTgTZSFPc^T9rifhf@V6?u;!*yKbuZ^1?^?qTK?=dF$g0B&pis<+fC&UJ z0+Xb5jR68GSzQ?6W@f>Pz-Ze5LKTXHC;|Iq17{c1rF_9b4M~u8(u28$e53KSg_Lg$ zSk!Ko_)2rFI%A{?RWzvxxh{+cpnP%(LCa%7MFayM71E)D9Z)TyE=yyF8*@Tp z(<~yBB7D$cn38D{m`Xz^Pe2fF!63|`O}(^%6pHf5V0g;VU>WKfztlh`BE;P8TbPeL%w0&6DcBXLoW0pdor*??RnKs;%Ge&@{ z@s*~L#Wx)<6zC{IUD9*PGJp?2iGHB!f*SzY04GTsgrysaTRLT{B349;` zM|E)`7%! zL;EroJd&Zvc0gR)sTC!ZA5Y+{qn*vvZV4c88#1sna!y23i$AhRk4o*N1{e&YPUezV zqbh8Iva&jbIN0c7&8}taiRKE(|AFjU)eHmt-Wfcxfx z0tnzjlQJ<#Y~pJNFS;TRX{e3~=toNclRR{a+&H<_)hIH?T|3N6*jxn~i6fekD(Xqd zAzxpJ=4&HzL8p%l7ANGTiZ5tL(tX<6nZQg*N~)M z!(inpq7@=lnNzPEgvE;s>EX_c5~$Y_3SpJ*Fg?MAMIV}s7tn@9B*Mzd7%f0`1S5`3 z2jE=DSzWf$gAb@>rFi#Q!gna% zWmEHG1rH$-jLX(Uq@cq9f@!X8ni*D09PG$BuR>Lrbcn#!;_h@Bp?n2JKhi3ImhSDrymVf04A8CuG1+*S-mI-J;L8w;=W}%6eXd7;{N)@!qnjAcUtkY@OBfzQ-={7!4SV&AZ zt~$^GO&mDT59-N)r||@0w(Ua0(v;wlwsm1S(

rWTI2>V%OklHp(`AQ;3R&u?zv7 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

jHVgZRW>GlzfDh-9yj3s=WG(L-uH@AY0Zv5> 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

?s;jdogsx0C%!-b<{8MnODV$Ec(2Xhl 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%0AeeXErC@1yoH)PQL_wIsaw%#k}%z%(?* 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 literal 786486 zcmX`zcg&Sl)&=l)Ch-q4w1ATkP^1J!KoIFj5$qs}FesojrHV>Nz}^)BDN0p}jjDol z1X1ZA2-v_v2P^j8GCx>}Z}Qzd_qpfnz4qE`uk+lC-!yMkx1eyriNCu3ukwkDf9L=H zRZ!~0JAZ`>O8%$dy!@7c4*uD|~J>y=ktnLBsxFTecq*vY?l-+lLw zKmNGuuDhmBpZ?o#za2PmK;$>yd~@>T$-n>p`;j9@R;*Yta^%PzJ9dcn@y8!qZ?X8# zJoC)jwQFGzbIOz{`}Xbo^Uptb?b`L|qmTad(@%#E9fE4xwr%_O?-%)-Z@&5P!w>Jd z=N{3#^|xfnk_R4m;G>T|TEBk%2OoR@l7PxtuwVhG@4fflci(-dDCtIx8uh~uKPX2` zskdy|0-*>KCr+F*XU-dMyz%hE4-4hyh7B9s`QnQ&{{H)KWx6A{D&hI^%P(Jh?X_p0 zeb$;MpM28Zy?ggUHGB4KrL9}HP8KN;D7*FZ=FPkBzWbnEzI?flP%8DRRjZ)7^UgcH zx)$HfMT-`x1!D@l{r1~G`Q($IfBxC^_U+qe&YUUE`0?YdvGRo%UH}Hk+A(3mgvE;& zEAaW}pI^Chr8THlJ8S@a_0?Ag4<5w!j2SZ~O`0Um9e3QJBU<_EufOUYo^h_BpMU=O z&Ye3|F>c&Ay~ClsQXtkA8&&$m6Hh$!&_kOyZ+_{emku93ylmMri}7ZSJD+~~sYE)3 zBDCX2TYvoV$FINsT2n$76-j}>L4yXJfByMHhYkg1{`~o{DGJqn`t%V*Iq>)D)$58Y zuDJ2W8!15qAe%OAI(F<>HCo)GM-L?2bkj|pI(2HJ^(B{F0sy({puD

&z?Qyz3#f}X3d(_uwg^3M;9iuK!gz^MkvAF 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%0A$XCz451;u2##BJ6FKN&;~9|z1fjk50=1Ze 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

HSk1&&U;2A9+MF_Y8i>Z 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 diff --git a/images/0001_mandelbrot.bmp b/images/0001_mandelbrot.bmp index 3b61d71af2f7deb112f5416273767ac4467f35a6..07fd459b84850c838ad00d4dcf54c888627cea27 100644 GIT binary patch delta 1078 zcmXBSZA@EL7{Kv+&gs2-!A#zqk*EuT#%&RdqAYRX zW|M8X5jIk@griHAF$tJ-BX&Y~NZe)MALt@pa5ek!d0~!a-G})&?<4VU2fsPr?)^p@)p$-N<;zK;o>P0iHN|?)O zWJ=7Ytx0JZ8h^92;ZIr<;2q^V5gjbxKcn6~xPT6x&hgQ}UA>+bju`Yxq>qP4pD_3; zxmuuwvK2uusHIdU1gK1CA2`tfJf+p8cwwNXLF7WT^P)YI#+p4o8tygCo{VXXV}Zt< z2)sd*hoG?|KZdBcGp&ivFB-SeSc%7~*kv8qWxeS*pIn`(PKWFjW40i$N+nm7`e8j| zVA7y|*m$LitbJOmu9OMts|)I=G$)MEoG@L^)!)DH((@vhTEFrCipYx1?DWemT&0m# z{b+Vm%P$5OwZyR=&N#*6qxed@y6h0tsGbs+CGyWmBb6rYWl*wW{GHbYO&mLEGE?bU z$xXxUUNa3G7F~3($iAJ<_Zsx@^RoEz$N2goD1OJxF1mh5sXkza>H`*Yw_(1$`1?$5 zpTxZeQbpD-46zx0FQMjp7Cm9#z;(|O%EGr#L8~mqc*E^eH>zwW99)A)+u{xz9 z(FpA4vri*5q6pe-5VSd~S-OXdsg1Sdw{5hvg7oNFurc*;5f10&ygf#ncFS+5|42E$ m)}=52e1zbLj|Fxkr@O22=5jnF%Pu;!x_0$`TFf)pO*iDiPy-@u{`pXFBqqr1r%@1SP z=OQ;>%z|AevPrgTn}ygcWVN#r4wRB*vQG$mh=Yb88<*3bKRp4BTx>Wvz= z(crw4N896a2%3L0b>Oq@QE*N0-H48q@t;#yD=woW=kp7x=e{V?@-KCw8XV*yI4F#~ zL2h_0P_rS3LaNldr~q}*w1;+d4xUZZCVAo9o+gR&(e4>*4h{RAg*0%|usj^r=qEkR zyAinRQ89w%uF^1~uI}^}y85SnI}N*?X4R@_!K#RcQ~bTTrG^a1+tB9;0;}Y`Ikhn) zGCC$58bkUyFZpX=XX;3ekh*mtHLCuo9{f?mY?1hQ=FR;Qm)yMZ{}G85o7w6&SzMi- zdVeuyQ`1eIgPI~(gbQ}*)C4^JD!GavplJOXXqcDQWwIG+qVlx02HYF^-+67lfnzHz zMk?==vuU8yWu%^Cl7o&^Sa;CXlR6O=*QM7!#S0%p<$Fe(z(Q^4F+xL+$=It~ct-ju zhdU&5Z-HE!yAMNni9blF@qtMsY!4g{Ouk&a*bnWB9OiY4{p@~+Y+RE>J0dna)Y#?c z4&os%t?t#YLGXewt`R&2?(UEm$bkI*>$>Yb@5tN%iQ6mD?tR3WI`R@v|6Y>~_<9e- z*W|%Ql89Y|>drJjuf1sJl8M+J18p`J+NQRf2kLPm25O)AZJvdCCt!v<0p-?HXbgUw zRMs_UOdX$AV0_wgnBd!Efa2LlFt`(%6(sCsH+_3dZHrhKE3`#yFJzL{Bb6|I%jRHz zG^?L~wH3FZ0*F;)3}aNF9=-Y7;T`E%B=zw7Qn`i7eR29ynH<|L!DtJlmGq7*2(H jVkMs`U<#QcrkE*VN|`5^GNzoVU@Dm>;lxVScSZjJOW&lX diff --git a/images/0001_plus_equal.bmp b/images/0001_plus_equal.bmp index cd96bb1ef51b3545085161118f7ff5883829ba26..643ef7a143d46013408efba58ba5d22bfeafc5db 100644 GIT binary patch literal 786486 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

(R5%# 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 |ZA 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

qs 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Ä}X$Vsw7$=6N zOMnBCtC__`$Y&oV8uF`K*OH1>wQIf{+9Tc({JJFC#oW1oa8wzE`Dn$g!ugLw*ecM> zQiJGHs^PSYT1}!#o$tlp?$=*>alCEJoHS9H8Nu%f$58!Lk z4G$dz8XCvxz&Nb-7T`ONTRPfX07M1I=uQXVkAUqC2#D>U$MFZ2_wV0_v0$7S5Y`)l z_Ra#rXz=TF&^ta58mMOk+Uo(e!^Mn*nf1*F_y7eR(c)*PM>N7LR$(ttBu|)4v{`tPyxt>q6^ka%tX|`)x>b1o z8c%5PvujMj2LyHaW2YFhW#G9gh3DN{)SE{a z8(0BG2$(l4jQ}jk+cI{9tA4i^cM^ohUJBf5(2fAGhHy1%TA_f3Amn3Zs=tp`+8He?4%Pn zXP-$`h^(oEicPN`G^L1Hom9OR?mj6{A_nB40ladnQr)Nj4-KsQ$KpMC=7mbMj5wFOlqi&Kv;q4F_pLG+NM|4H3lj2nFY>gM6VH`s}3~; z0rr{lm;fz&)d-DU0|eZPgj6m>($S`eJ=G(Vh7m!xaYzT;WfVn zxKYD-$nPk@cjEh?mQ$PdkARUGyj5-T|zXo_cLTtSu zNE__0kN^gN(y}Ll)vjYFlMTX(=ZKU<#(tYK(Ccbs128rqh$>05KIFO;|~z z%}y7{C!l~imWtYLt0LGWX*sl75gCd&nbI?Ehy#pJvE8goQ?Bg-1b=W;wW&iA`02AA z%v7a{){ORx^460lgD{bjHnr&p;8R!pmf7ag+aPANDfAji);xk8*rg^N1YJuMvnvhE zhnZ~<*ySCzJ2^ZgX~=$2%VKv%v1Oy>gB%K8uLtBv@O;Kb(0h}a>&w}0~ z0>_Fv)X2j_jXWkr9F_-^%y$aHIGS*=pt|^jrUFmQC3;3N_1wTGlO$_Q8R7{s4UJ_pikvDcsl+s-W^mMo%8_W*Wr&!(yYu*%;5G;-)IXfzISglC zBq5267y=+Z6lm3wiCAEVafHHCIi^sk98e*Kl%=`!YTn*4H8FK=lvvkwE}@OG!)A}< z2`hC5`OnCxx%nen^-rapMa;ZRk!c5+0`w^@h$@~^EL`32(Q;HatqxQCc`KOsI3zVe zF8f9i(xk`qD4YCApen+lqAFCE!ZxYe(?e`c^mu~Lh$-|^=3phGVga>AKrK;0#8l)S zlQb7C0_xL5R-{)UIMSp-Y&|>32OKwZy*)x+*)wHB0r@`NxI#m97~UNlhJ$WQcX$|! z3GKV#`z%8Y?FgXjmjaI(`!E@J`Wgd8^}rKc>)AnQMgw`bdY{>S{JC)90(6D6hNy?M zO$~D0?KP%c__OJA6G<*HivZLb@HYjUg#GD0S4CSoWL#6*YC{R>6^06tS@ob26rn~y zf~?UI;1GfQTxGC@o1M>wIP6VF)}%NXM5taCJk}s^kjS`l)ssPRL(wW%dx{Uq;M;>7 zV?>`WrgCkHc8bcU#d%ZERnn}npkWgB(TO~wjZDTPF=nlld(jBn*$_AA!V@Lx^57G$ z^WCQsL8y`mkj!l|9`TvdQ|G6NFzpnc@U2nus*!-lwUt!0?Qid>I-zs_>GI}Qr+^A~ zf|v^o8r95h@4!Pd>LDkv<+goln{$Q_Jn3F%AK}|sK`sG%iVS&TR%BgJ7sn8$QB_A+ z1R7%B9yFxkFok1>>P3$hc|&HwL4+X;GHwb2+#@ephHzNs8xM0sco77ekxeo(MP6(L z#~~;(Do1u?N#qZ4snyRz-@Vy9T^dA zcSwJo>%$RGSf;D&^%`q7oswo8&A#GiK`D~tVKefOj7EjS_9l)!%T9_UDP}=Q8&cEsrpqx;Q(FZ6X*>z#d`T zq}IqE^O{J6Ek|%HSuZkjN7y$X*ySex2MQnT7kLEuuIqikhkj^y?v|^^gvP)h&I3S? zPK3VXU>YKZiBt1Pz!3m5A!h`9hRGT{cmFqqA?OOR;kiGsg#TMxTQDD|QJ7DjBFH7< zQ9qB4@o(&y5$rn%2y0-UB7=@3DvW!T@Yg%;DiSItf}>^>ye3L$6{0Fc2_Zz%nt)M9 zn0E(8EX0y|#il8&D1RI-jQg`UNza-H+cko$Ak`tWik3~IYROO~%?-^E4jDsa3{e5v z3KID93c*ixYsh25rr>I)1BVfGDJppKHB2)2jI*Es@F}VXlQuPiljsTqVd07mT(}`S z#2uP0)w68JH);XohMp%>Yyrj@q=j#TScUS_%!@Xic@%KhKFfUj6ymfci= zyrV&RJJH-r;lJikV8Lkk545>q3aPY@xQY{QTe58n2h@)(DHNQC!-OpX-|n{M5fA{U z?H`60Px1^}6yp>vU(YNRJ%dFCle&4An0c+;yyhaw8d{?$jKs`{k6^PI&5p??=LZJO zuRv^q2yhMjeb<$1RiR2{P`(h=h9aqql@kFyjwC`_8CNiI2ouxujai{SK0cNRrz#Ft zEyq395nwt;fN#s1q!?1n8f$t}C>0K{%%EzlAo*iRJ*%SS5<(#nz$9;*RMqO-BWnaO zRXzbjEVo9zM`E@QqO!DbbSqN591}Mun%GJf83xIC6hL_)ETk+8wSX&o^<3l)jiR6b zo>kFi!E$U6Dt3oV>Crfy%L<0Nd}(53YG#!Og9K!U^DHQaLe03qwl%yL^V5EDS_!i2 zjYGliV268_X(B5{u)t5D#uuET!CUs#gL4^B!%wei-U58&vELzE_#g5EK=(jU;OosF zv$^4u@!1FV!rQLykQ_X+28VHAFSAYrd}7WEDi9kVkiutY0SqS$MfFg$>$*EXxb}xS zP?tO=L$FX*G zC`zsezOMTr!0iqu3!PHrvjWx#TO-TNs%^e0Ia4!Lcs)`vieoK`+`KqkGR3U=alf_c zAeDlL>SmOqKE=ai?;y(C5umw~lT`Cy*%sgrx`V)_1?D&f0J{$n0N+oLV8?AgVAo@M zDS-cE@SPMMF&zp{2RA79AG4dq$pSNNG&@nVNA$L2JwFk896=z-D=qF zm~Cg0hKSjr@PU16#}sGQaKMEIWJHE8BCH@oK<_b0y)cS-DyA?>rI50WU^bVGk^~)8 zEYK*fn~H>hM0+Gqxk3-=mSxpK7@uz(c6N&}7=iXO z;&d)V;_NT4RFQJ>3Lx_069h3r-E^i#a7hU%b$y%Bj1{n+3YQeBisb;u5~I$JC200S z0)(^7LuN6_P$%F~k8XC>n6e$%5egN{4vs#{R>7JDs(;xOSdBq(76D>nD{csliXj0Z ztS4`2BndDgWoSdnprGj$2AM1~#b%O{=#fnv1SSsTS62>P7ZsZY_@iwZJ4G2ZDIX+V z)_7uvn%Z*ELcy9LYmps$0j7a6J?QoUU+%mS`1s>jU0?g{eJ6r%{=VnKe1H#NK{v1k zeSq5EKxQGNg~KlVAeT)z-$4K~sQ`Hwa%TbXC+rbZGcTGcJyROEe(>Od`#;boj|0g& z0?3<}WakSg)>tqdttQfU;*c~-<a0>X(3Y0f_7AQ(+#f+nd+hQ!MZF5y@+^E`(nmR1y z5`v`aWlEtcNRkMC<}G!EESrM7o{YX#$aOWGSppuZn2Sv^OtFlK;178+M$M}}l?Q}z z8&Qzf4l5vrVsHt!5tOT*KM0KxWu`s@84LHwpe45{>@q_pH3fM-Lmrk*L_m)jUYQHd zcd_w#NAtS?oCj#KW|RZa7(OF3;NSaiFTy_NlXtz81#ba9{wU83wq1dOuHI}U6sZkrtw&23At8TGaj?B%2#P0?B~g$T2ilD9GK zS?W{}9)Ul?qDT@G@a&Lj7aL-Mj6|3YEnH|8%;nKUR19$vhd~^n&YDMo^{64(1QFhP zeEV|b8I`G$E?-(0h)@QSD{Dq2%cCliP#jrS%)zE|0wyFW$~A&8e>g0vPT@Ci-hBA* z!58M@DZCP4cJBx-R1OFWRD0Dt%l3S)QQv_J~PMM}SKvwIf#XbIhJq z*q9`Z8p1IP0v~~|6g9K3V3e3mL6=R0M{{{>q9P;a6}AWq9Vf}4^^zjpIP%yo5N%9h z4~naBGAvjlF}XG>mBbhkG>>!%$Q||UW0Ln+D*2!ZQ5-u(@vD5Lt8VqCV8p11abDsO zrm*%B%`Ag@+rf{vVYpkX>pg7uH$11Or%nVf&VD14JGU9hzm%<2V}p3gC?Je#E$%VTZbkk^%Or5tcY=;g4L#>oc z%FHSthR^J3Owe53oP90^E*lEiLey1~gt?*W1dv%CGL=6jvQk6%X15W@D0scbLq>Rr z1N3YMmh~QO5T}6qiSm{&Pz>S%L!}<Fr z=za5740=BeLq(T=0(4nJvkXq%tV}8hHX2}$Hj&-RPY!d^j`O~NMmQG`B>722&uzy6 z6-Ml9ih4_KkK5EnK?G~Gh*=0ilsaGZnuCv`AqIIR+JcYZkLkn~$3$!j9XD7o1w~=h zlOc&|T`t}-C>lyd@byx!FjSzT!dW#5M(|aLtZ5P*fT$3OQ&(zFkxMi!bYM$G_7gU> zQ^s`uZUo>9FaNEPKdxau2SFHUBM9c=Ftcsi3V;bUsc@>p1LsP7n z5lOPpid4Hs*drP8)UDb?2pskaSO|jgc94nSBo(2?XO@6a^OF{bHPP-M;G|G^lmdJL z2uWlCEhEGb0%B=$EiW=6Yhin9ENFVu35C(Dc|_C7B?~cXk29+zl$`Rj3lta*gB&1Z zIEC7*y? z>@ndvVTT1&r)w58J7!@S6tkemaWsH^hXTSQ!YhEkJ+Ng!hp%LsEe~7dNVQZ^!XjMC zMr_rUKna(at1g5}@NLsS8DaUH)O_u+D8Gi7mjitBXd>sGdns!Qslq6;vVdS&A`o^; zQ?snJNNT3`h~K1o9Gi<9GN@M7A#ar{BP8+7!e_+{;Sriugt`>Dp%F8heLedgiD_(7 zVTwz{cw!2b1905EeI)TM*EK-%R_GcaQ(gug0jk}KxMzd#fdvyOKOC+KEaNz9)CLj8 z8VA&{lD04G1(Li9RP7OFsjFvjK}LB?99V|!(&s7;>kRn|2Y`=;Cx9B>q6s^#xaVRI z_946rzk9x;AICQbOwFG?yNE(3w9CJh!bVl$prKx4(EhSd%>{um83)&BLkL3&AaMB4 zXMBf(oogX_!el54SEDv5z6MNiY;B5!X|-NJZBt7D0#gKth4ZW-L_@=8N;5m-nqZ=7 z8Aa3TXfUK8g+eObx_u(1D-lwoz~rc+l0<}DRC~%ynlz8fNm}(DC6}0L@o0#aG7CT1 zG^(+g5vvn7RpjyjRfL@`2s&%Kl>2D*xeeSP?*XI6bOXmpnLxX*hGrS)b} zczIa85m3fx8%>Fi`fGuMRX4xTzV~wW6q-cSPP${ZgGop<$F1%=g#aFsw zTOz3A3%j-Jjgk>zOe-=f6mnYX3E)VsS!9qMqb9{~Bk+Nu>DcEjF};bHCBQ2SF;PAQ z{)$K0C^m{Jia`RVmL@&Ayf6j&fHOFS{O&)!_WG*{kG|`JXt3!z?c%++w+EBKTEOdl z!9Ruu1biAogMHtE$a`@Bdd>@Gvk-?Q0z4XplYU1)`0@kH0XW=t7O)TAK{{Xo`}iKw zKKldPUNoRO4DVdv4EJc|BzVsP0`_JH!G8yYDI8b~Iw@T31haIAk1g*KV$0jP;wc@P z+E7l4K^au-ibNI!U-WFf-QC^1bqY8WxzyJ%!8D_liiUaJGo^_^x-1C3gi00yQ)Q}2 zbJ<5m&4sB5jhfU%98j)gs2gpIRpiQ59WqXayv8ya^*{ih9oAT8pK;dM^oXA)Xv?uo zQ?Spto=eP9R5mXBA*4^Wt^v+Xgg@J`g}u)Eg-D&efB&ACdajbqPB*irV^HH50elNr zff|lk>lL#e$Ut)N z9_H))$ouDPV9+_Px%Pvgzh-j;C=a?$0k;CW4jR*`4W|qO)5XAF8XDjWix{#- zCF4dBf@ZYHxOn^I;+woX9w!CAj8unmrx@A-3JD>PibMwG2m-4dRYbVTk*{h@D%F@O z0x7Cdbycwpden^4;)b>OVZ}GMlamwQ7WgB=Jn>Sgko>AB_=L^U6x`#9LgFeyov_TT zQ6*I6uxtw30ly()UyhSmM-~HNriotAgwsW zEP;xZS^{BwZ>*KQUVSQE6)UqOHFH^lfSAs92JCjUgFqIPtBU~~1I}O;thw;_$pylD zar06jO>KX7KTN3Z2V&^$_@?M@hv1&8zIOpez&@x4_y|Kz1S&?!XaGDy&jPI0;HMeE zHFSmN9{J#ly59XCv;uY*j!4pZgmz4H!#C;%?t={D_zVJJrv|3?7YEtl;i15aryxkQ zunVTt3 zh&9I9VNI#4Nd?S48GJ6`lW{XQ$^lune?dopCZ{&LEJHj@`3+dea6aV3Arp2%vmlW$ zaIgVgq2ZXtGKJ7=K%3D83@X~5?f|~~`93V^F8rV#`1?~Cy(=`#By6kdZDb*JOsGs1 zWl>6t&w9=)HbK^K1YDKkdqlKT)W02}XjQ(QLR~KiOtphVPu$Ql_TAn2V4LIHu%b^$Q=r=f5j_{%bocmMPXU?HwOR^cm<=5!DT5wJ9hw4hUA6v=|m z8rawH&80WBUTi%Qa|aVuQ}Z?tRTm}>K$e#rqZR&J2qxn~L51h|MrF#jowYe$E^KX~ z5Qj2bmi5#_2Et^}RLHPKUXvHb-EbhkohwrU#f~zQ6uD4rz)g1K3%V*iMD>YZi9${( zQ>geOo%5$CskAlN!KczO;Zfb9l{78#XuTwA&J>eMz3Z0OF?ixnfNVX1k{xj5J}Fkp zKqKGm)a?#a*gO2pD^qI(OcdXq)lA0+9$E-twC3>A^OJyuP+{C+5twKeSkn`;F0&g7 z`FX28X=wyBk7g0Wf~;bNorY*$1S%$pct+fGtJ%>;jAil5XQ98FU=Ld5>_? z6y&@A18Pi-VwZNK9%*p^;n4>c=adgYjwI@--~(8XKt6dKS}X&64gRBD`yB^*>W&N_ z1VpK9Di5^26aYrQRoV5RRWD*vufWuvSgBj3f}n!w3h6%|W|;%rzrkH8I*gK@gvyFF6zOPnE~2gbjB`vw#I>5Dx`3@ytp0J7|T+#@Xp z!K2?&8DSQOO@AQ?2HhF(ZCmp?7hnruQLf6&M(DV-I!jh^B5XxQmAA?lL$OuE2nDPf zt7^Sf+c<_al}x?j$M>k_p*@fUadwA^Y$aNTaKs7%2mXrRsV9k!Ihi#C%sC9(Ww}4{ zbSPM$uxQ@YtcawqBG5^*MyCKH6wxf$M_?R>7|Yg+qc@in8!|O0v+1}YcQ~{(G+2h} zjsUJ|#6UMDf^egZ5U`?#z&k|~iGVdCtQ}1tF`<23$g|EXdFHuY`0!s ztcBZXE}{9Ud8EY%4nF&>0-*v`9iR2)Qn7lFi{PPXvnI}#BM}I5kyPZ8l9)uxiy;{? zil&Px^3>TkB%!9Dg~^~mp{-GE6Si<(5z{cKNHH;{2y`M);j@_Vgi3Bymg+WC+DjUBOITxJNPG^7lqZxATCkfSqdN4~1 z{=3^FBoPJzd_71s1+gR@3KSg#_^!%^5HA0wCIHxhIS7UP`0Rjj&VX*WcfYGbT64K( z8=_(^J`iRW_*2oNF&SsaBbzvM{f_LYk}2U*TB<_y(84Hsf+nzTZgw=TPLQ2;rQf!u~ zSth>nvrOB>Xkj;tsy0*{5bY)cl45oo0bv%OBm`5{2u7rgpHNqW|MpZ+ERR4KM=vs6 zxy3|B3Z zFBZD3H ze3$&L-yS)`6AcYu0XTmEg6z|3C$h#YCsh%S5>jadSO%3w?RTqF02Zh`a8g%qB11(m06ho;^z=dmOL+zV{Y>Drf_SE|Qma#A8k@x&pFb=c z;*vPvivt-xd*r0)2q;%FyhW2-rHjuQE0qGKI4@{81kg&I3ReZ(qudD;Xyq^({@V_E zM_894v9%}*0sf#KPFf2Y4O7e=0S*y}tsZDG%>aZDQc!m-t6dhD^uu!?DIc8&@-#-G20HNM)Qii zNqL2a>P+c9@=%3yo+M9LBt=ynshME4Mxmp<0wVBr~*Ao3NgCs)}u;R zr4g1%oq$J1Jl2!9QevEu8rwt+?OCIG>DCZ*S!>JSm{f>ZYd4isiX3ysaR8r9HA7NO zDs>k0N>RO$$7hE#Kn8L`S_tp9-ydQ*i`^XTWfxaJ$^_|v4y3#2LN;fF?;Ebu8mQNk z0exNp3_(9k!0!4D5`iQThL8~3L4e~>&@)BF>|N=>xCW?!cES`vs7BFU8pt~iy62}( z7qoJoJR{W6dZQf^*>@fh;}T4eK0y`%`4vxvq!L6-z?F(eVKgLw@(vk9&)xwYUH^og zmSmCl(F3-d&O$_n1I{cfbvSzQF{5JkjYSL{UcRfywd&%40+T6^Xge2TmeE&w~1M%QEqFdrbv<- znEJLr^+SL#nzy!9ekyp6@Csl8N&YyAppE-K_YiK*cCmMP|7n0K@`(vw5vo#kD8jrF zc`hM5nwJkA`7a&(L@RY!pk-1s;@BxB6(zcQJ5k-*3qT(JODOaZBFw~CppM3bqM@!> zL0~|w=de)Q2(|;Lv=GY?PknKA3P?IJE0r3;PFu}Q9>9WnM$ps=Sh&K_D-uiugVMx; zDGVW!VSzt9;jq#oZzp=xbJCC>mqyk=yaHH~q-M_y3@*No%vjij}x zSn_5!S=08&>`JlI%oq7Wl>1M{KALOErK-66Sl&r{%v6 z@T;E#!haX$x~m!W?f_#Uyb~Py1@*AeqaIEo05zy&ElDVifYJ^NC=EgZI59x#5xhfw zu#YCGL06;r4uZfR$9X{v;Y|TSjEp0O4-`pa>Y3X!$C~9KW5T+f$rYaH>aRB%}2RV=!TLdcj(-KO9JEfJ6fz(^*Vx*eg^ zh0dfL6Jd#?E_&P}32y;1Y;>9DKg{vACE8$LoGr(p&;fx)2sKXf)<}z~4uW|s3{(gB z%;Iy#jD$^L;d+Mx+^**>VbPO%X{BOOJV)z^S(GLkj1W!+$GY@nY-bA;87B*`R08K+ z8@zL&)sph?$P>aM33H@aW?`DDQ`l-iDhl$bWo%?B1*O z_n%=tNGFB{eXyMtU3ln~0q!9@l7aJjbjaLUz>WA|+!^8J0#*}u*C%%yc{iOhw%OhJ9Su%f z$nPVqYrliQudxtGI#hymOP1W33lK47YrEI!(O`{$8m^M1p0^gra~$=+b7gNj1U|`D zhfu?<5ubpPvugywF0%!wGlfG^{h&q0XvXoYL!in}H)p80kPy&0D}5lckjm!u>(>g* zj^Gj}tq5VFrt`=Fi^Y@zmdS8Wohw&mOh5svUeHZUE`T8!*~By|lA;PN0b@avEJNZ3 zKSc=|<8T1M=O9`tIoaD5%;ceLimkv}j(}SiEV4jY>Ii`FRB)65n}EHO#bTWZm>#Vi zVHP0If=4}|<~jHD{4~2B@^ck!E&QYf($RWDBrS@=yv0FS8v9H+XEiwQ!;YiYZ~B~9 zMQY4(tfI(>TZtA%_#j{QyJA*#Wl-eBTyIFO<-!m`NyxYY&{Pfqe7Xp|GSJHrCXOBC z(QZ;*VKpttNsJLPdTubw6k(5vA*hGC&J-$WGMotst4tGd5^WYLBpK0Hq6pKeTZDoq zrdhA)2$Q647EJz_0>MwC8m%6g!ZbTcz9IAil|Ss8+CpSX1(H;pL9Sl;saU-T2guS# zOv)rwSz<~Am#hM9h$)R0bvD&DN$P0jicba;OeU zpRP5cg+d(2YXtN-%yLPA0Zf0L0=I1h6{U)N^Ks#au7*G;^me`XgRkqH+rD1`d<*hk z;0upYGCp)506rXsx8$`RVK3l?gb>&$vpxzrAgBbD5Yo-RLxB;v4Ln`lnbq?F6NF{~ zsG@N+ABgm1oKU7ffM4onL^}fT9Tx1Msgtn^n_6-ehWsivVG$7Ns44_Ra0uvC2pLUz zc+`uSAu$&;NWv8Shj<`DK>SVV!-o$|Bmz9qxWS)1cNz8>)=$8K;5Fgo4*@ueLu0k>*^lkHYJNPcaHs)gs)xGI4R zj#9TPt=HNO(bdo-E%W~zcF5ZcLX>?$6Ts>Rt@s55`iTjUTf6Je!J3J57&j9_w!CxIs% z7EH8w$`|D1A;fm=mpd+b;Sx;2ho6oB@4rS3k+hI*?@nIoFrO)lWyf&w zfyEy(mJbKuiPXrWV$cII^(^UNFun*Q@G-?-LM0kgFVP4bpep0G03-O)VMv8g34!rJ zs39pMZwjTtCb|lw>yZ|sQ-H8hiY7%vC#F1N<`07&Z8WaxBPv>6F^iKfH>fk7pLs>n zj4UIvpB>W-E=if7L<*iWMZ^(MqapQ2l2lt6bzryROnic zQIDc$yK&p7F!typkIxtI>fqqOO%+*Rv3)5}*S40lQ;@4B86< zMD>-)klwtV2vjMBtz~k<*(b>U(h`_cgpeQ|P9?@&n6m&FfgxLEWwJ}M9Na( z;jkk_9>sw$8lM=;B*np_AZTcTBEz!jcmlq-s&_-~aCG(cElcYCC~V1o7XY?yt=?|n zp-OHFgo%{Bd98&T9xZ2W7TZcpLVJXTSX51s+i~mS#1na8icRu*kA150VhhH_&9N); zKYsl1dg7ZWEOm<(TLKYa-$L~8Tnf1AL;$cY5Ft}+%8^kibDAT_TdyI2-|{7h7%iSK zXk+RHic6)Kd-7}4DNn2Y-XlI4b8%Jax)pdyMEij0Q31FQsEUOZDz>-@ng;juC0gmq z7eFJUq&2AodLCsNK@mhtXh;YZ@zY`lVU)0>Af!wUlzn@_Ea|E%b<(`1z&A@+ijTk~ zLypGN=eSDMz-L%*vY5#nhzOrXWd$q7o$|8dWSK zm&jq1rv-rA+~EgoCk4`4bl>JO!$ES@B;b3 z4frDr0cRMffnvI3U?5?}J1odBIrp)s8SkMw-|Z zd@o0S-sJ`-Mbaw7Ay2G}Zw^NXrLSilxXvc~*A)&RNVH_>gBsuoZNP}$1_von2sq$C z12{kn2)GB*HMNWfBM?JLC^lfC8YxPy`eei-F{PzcIIritrCG17S2WO~;ZZLdmXD)a zG;36QhO8P*zJ0BJSYuY1@t*^5PlJc1uE9Vs>w_WW_nF!$0RPcI*gyH~woJ=077` z;w|Hta%TzLcD_5N-2|G&N#y7}Jw5eR(6>JK1g%5gL{+263x!z0Mp06n3R1pgDrA-~ zglrN(OS+zztbtIE>?j5Tk}lnXPgk!QWz!UN$;?6+p9JZ$Q2pTB4rFK%Hmc8odAa`e zPcDN~@@j@60h1z#(dC9pGn&m#2Qv8}p$H4)n|aA6W>ID|uTge#!_tTmu&>!MDfwu^ zI9#$8a}h(}n0>iTkwG$4*wo{+qQF^M%9Obz3oUnoFNxBPBjYkC#j2E7%wkG6Nn3-K zDm7#+v~ZYG;fk3Gz$cF{G6a-_8L2k~Me-W4EZBpF2-w}qjG~DQY3v9T!YDB-qR1u( z1ha@)KAXfmvdPrjw{L;}lP6DrmE*woinfW1t82e2Yj;<$23jF1P8jYT!K=Lw{;=Im z9^U%-6*7~A%uo*6I|95iKtE>$Ek_2-2hJdoj8j0vVHvjTp{QFuASHu_>K-}Jp~EII zd~><^li?2wXr~FJ^>zS(J&q&oxA9Ms`m`=SqG8E0t}qn5K#8{eDg)6d>G+r=E6W0i zNl^(Qx)`7po9L+%>P!I*Uh&ZEJhU#Wwm`H)%`fAA1=jmj*Vi85a#%n@=me$Z1(-lf z-~&9}LVbL2!DACH0VO>|g)@*}^Rd_c8Pv$E(_f+?6=VQG0k0v#pox9Z$2i0wY&j~y z-a)7bD{cE=S`I$K!i#|3dTjxOEh9c+WxZt78+i?fj3EIOVH0&u@kE1iVIA-Uwy@XI ztjos=3v}zo)H@V_q>Z5Fh18~CpFtakpCWHNFdo`luPNYUXPixB`5LX>o2=B39*|l- z2i%q+zdMZ2ZtC2dtnvn}!sxS)*b0}%H}X;-Zr~kl3HTNzh720QWR?R0E?<&`WBVKB z9sy`zEtp)gd~UPGAHnAdZ|UZ(p#(gdh$&H$R^(9 zBaf<49udL?@#M&(l*x`9Auu(&UUGqOk21ReP}tEdkf8+wX(@FjVF+Oga~YLuvnOdf zmCqny*76}!WN?h?>9RnDE@1_u=UB6wLNjS9JTwcA>BuMvifB`?Plj)LHW|lk?N)@K zYwfhihx6tlOc7Jh^1^>K6?`7%9$_wtDnHTe=LsxTjqKQ`2*n9A^`);ea1*dk?Pcm5 zrcMSe%j^Ky&Wnn?qHXgsC}KEdRDvn=X<@RUu#+@n#}FBqk9OiH29|Nmg0@qru)t?V z5VY9w$R8`UBCGRgQbESyDoBA>u**xjuh3qEZ3?yLrV6paEHH=a8q@{U{!+;IfA0le z3f$*ibb&Cu^=FViefZeI8ZCbg>O}DF5B!N`3O+-4%|T$4q=p9aZp-Wk`yRomA!gwx z@JEbyiTTY3s(WO_Be!9!M?R25vjFp*1+M)*%fEZ~&aM<6Ri`|xS`ZYB>P2XUMe&dk zJu*f`NiR6$4Qc8}@nGtum?T{VLraqpXJy9_H^fAP`Hn?v(OV<@|M>Bv%dj}}O-x)q zZT!$1VUI%|NRFeNJo;XN?<$C?#sYs#mw%^&+EX^}0OY}!tbtZ2#0cPmCoUw!*nwKs z+b**fo?Dvww^OXwK57?-7?y@($YN~`m5<}mVg&(5^Jwq1saeBUR6K!EqOE~JG0{Um zpa&tnfEzgx<|!NJRyFI6mD^&^W{4`2S_En(XhICD&d^&IQf$&g@*q(?+3Z8_Z5 ztQ4OQaG;AHf-`!JCwxZexktbY4qa}`8bwJlyUe<1DG7fiFI z9uIlML}f~xqSa(U@IA_rbjYb3$qvE>*Gnt;>bCkXkEM(STGXi^BpshAx{5?kk&N<| zUyX4_RE2tAmZ>};OkE)nCZj4S0&07Q5e8Yv8ZmbCbPyXG|oKI62^OMo>-2&`(y@kqcb_&|(hg$G&{p+`j?P|K#Y zstUIY$C8gdkVidht=f=PXePo(;HV-Cw?^?4VKG-`?$2)Q%FhN=a(WvFXk%(VWrJToal$vh z1$2+{bqIVxU`K3m`)U%sNM4K1C{q$YYvfe}CUw47a(&AKS`~v;4M?M5{5u z7Ye&WibX)u(R#%r^$Jpvh@nw6QxRg4#AE?S3|XLpP>O5{GBCF;Zt$6t7H+-P?i6qo zdSl|V;MnXAP<{%mGt^BO6of4z5A9t_UHLE}4=f58U7L-&04%$E*jA1?%Yn(3z!U*s$RJo& zrT7dY$Or{_SgLM?$Y12T>ibFk^XJd%&|O;T`U3{{8Q@RG=M=gQ&BD$%Vb?eYqV@2Z zWu7}$V7Nm8%?%CTsB;QOc#UFm3>r8Q@B?{Ewjasp!%4atF}|@t zg#cmxNC!Yx*p2{sK|7A+S5rc^p2Abr_MIIEXILPay#0kXOIt&wsbq<^K^zLK8BM?u zK!s&0M!`EROfqz#I>N58A_N(dWC$2y!4NIY>_$aEW5M1mdSrweO~6DJrAPZBeeT@3 zyLa!p`9mUn1opfPWj_?0J`$38@^HC4^hce zWWl3vFYeW#)oTd61q+fc8iHn})-D>tBEPD|kc^9orbMefLuy0ZszYU<obx<}2S>{FOSN^ta!In7!eg6{v;f<`z%ooi$)jn4ur7ZPXVd!d8jstCM$3v+{Y zrFTojL}>2+0AIbizN$AM2L0e68hi!#&qDQ_V;T1+#6h;VWmIDR>qxuATsvQ9G3b^qW`Y9*au5?)|LDU@t zDj9^rWOxfy9eDuLN`(TdQ-PDMU7>d4@<|-X`(WZ`ve|6Vt})Qw-9xRaMx|&7N7&EK z&bsplw$NMX?BYJ}@Ds=OPBz z5Pvf5(DyFvLgI9@C__~2L1n3^#85AE07NuqRSvWlb|HdG@hHiSliUiD#mO;+@An*1LIUy2&b#sQsFJ8Rpr#e5D**FT=Rkknu03XHze?J+x z{6jg1h<#`G*>3ilI(HlZd{_;l-RFJ$@zGmPxSKF0NYns3T4S6%42ScyK)O*EX*3y+ zy%F#Y?VSbeI|U3O_@j66=D!oc{mOl;lv!-)R=|RwR|Ytutp>zIZ3vVeQ%n>aMPYB7 z;L{>W@&AQEg)m7aCsQI%06z1XGn(b_g!AIy{_hmP@!iZtl=EU})FJ3bsvgvdR7#CP z0&$W8>zo3>A5ggKdSo1aIt5%`amWCCkH8;5sXf(50De+|V?bEhu*^wItHQ0za+G3g z5ges^4t>+Ox4X>+LdHMx7cKMMW>v?Q7~> z1^6=M9mLf3g6~eM(Y4>ymS*3zbFI-v)~h!G6fp_1e9f*DTS*a8^(n}h)h8K2R(oWq zNFoke*a_&xGlOV>l|;gbvQwS2Sq3r1Jgu^jZn#H=eQt0D2f>q^F*Q>-2tAj~&Xno6 z8i#;&c_fn<4s|p>gXT&G2R~2Dh);_^MQedP#Mh%uWQ~@LAfp;t<0@eSrdG)oNFb&u zctvTJ0+z>H2QoTF`u;zlpp|R8}21zue)D^s{P#fyR>5tEp zg24yZb~p4^)wTlxikRGB4Z&MZ793g@3r~~^BSJyfhFvs+0#1tno2;4LRzl!Vq|VPa z6#GsDQIcOhMLk_&7K^qh`v?|YzkdDp?b{#}hPz7HEI!!UIJS?=JkTcN*6K|G>;r#y za>!DQ@Yaw+#5Z>MY01`WnKEdlvRpApz2pkDfE$uxK~OoGuzOSkh?h$D=I5lqp=N1Fu{`1PE(9YzjTq2rZhIsyd8TfY;~@(DN#fP?oVputJG zP&Nt!n0Lv9JT9mKksLvr;LG(G5`ruMrIFV>LRZ4>u7rV@l3=ryTBa$itFloiR>W;} zVs(}*GD=-jlW0S#k(NP^*lSWCT%7+A{Wn5>>s{<=*a7 z4;WY539Dfso1&RYOz^~&ijRVn(~19JPc;@a=)imQvC zdfj21JEk)=Ex{?m>~Mg6XOqo~usv<}XyCvVMb$SjzArp4_X1OBW5QZ=bc5h_zLH<7U5^MM06ic_tsbGWMV z=JiXwJJasvr2{jtbaS7X4I&PKb%yS zia`(%dW*H9Q4{kWkmu@ID9ng%vlFu%w160DV_G}D-9%k9G|R_qW)FJFxNWr1-tFlXG!_2tVK zyTdif?(mxI(D4#y7Do&V>^O_TzGlC-G@-CAD-voPk7&D3G_2ZQ7BP#(kuowk0#e1w zmm~_TP{U7=5ohueA;?B$L1k8u%z`rfDwd47dL)U-2uVx^DN2gR1WZR)j!5!~jJXg- zHA806Q6$ zjC+XI+VjT{BNQ=dq1opTJ5&%JTQyUrXuW#I2`5H?9sQhnd_A)|x;XNs?A;A)K$isj zRBC`N;|#LUECs300X;D0+E@-TYImd*KK)XPBasak7 zg+r1Zu%NnF)AC2Jg!oK=yc}^Ak^dCAVQTx73@yTTlWP(zg;8$%YS@b|X6jg;+0HwHs z5fcfOTt$c$cEVyV>4a&CyvB}4e33LH5ttOy6+MX{tY8rA5Fp$sAkn^WaMimKCp8Nf z7JfSQ(B4ou?8Jl{g5X1tFK-}4`ZQ*xp2KFzCHb;6Ud}&j)1C8>e*CIrUQT~R-Jg6#WA&rS(k7jDizUa;TEe21t)V) zBo$aY2;W4CjoX4oz@)-rlVVatu+O+a;c&)#3g~taSd^2FKW3y(jDW{dz+u%c0!|G< zaISS0bP5PX3kTXD+D`+n<=zhZVXiX~q*Js;v7#=XR_zr)v$;eMbi1yrXAPM9g~GnT zk(bS8<35k}8ArAItpQVb*@p%W6oIwh#aVBQi8+G9DuMCv++GuEv_*-GK%vFQGWAY; zifu^|5K{Y89D)eK6=cvP+F~e}1xjjI5K_e~YwEf5q2Y<77?n!$Be~i?q=y6!jI8z8ym>tuk9`g;&PmfK6FdY?UEuRd& z1Ub{4?C$n5C_4OvJlKt(Gyl?2m(g+v_z6w*65;*!V`h@>$wGT zRRk;$!Sjwk-zW&ULpr~_;J|+egiB^{&4_w{29js7fC7Oq29*S+liXU+i1izwSnW&=0zi9#GzoQ)#>RP zk_Dv&J;c!3D?9-d7OgTC7KG|0Cdh&b63ZPoWCye2q$nM7XH<4%ik6=B2`60`-JV$Dl_M zK$u!%$I^@S*#Uijx|x;$z6{+@nG`Bj#lXhkfegsY{cxf!+$w z^>f#9*j};iEN%;eH!7@}=& zA=uW|mTNz-h4yxeYrl(-&C;8%zXbwu@GV&L)0>0CZ6E$aY@a*46o7FckB0e)HPonC z`r2cZGkT8%KqXy00mq7=#Gs`Y1Y&04r@e1o)@%8C%M{OcjRlGrS`%lz6br8ih`iKS zb;@{F(=WVInMztO^{i1*Fe;&zB+IITL6Y@G$ur2T*zyF1IQ9HCA-qmT7k>CJ6e3{Z z4py-eM(+Zh5f&(zzE{XKvoggXZ!H0VqwqBN=Is*JLA)-#@Xd>W6Cot@u>xv=H3H5H zn6F3>69~uu#l{&y(kK;7M?fFL%`1kLeLKP`xFG@-tGx9J(t3r3W4267rqbRKdA4Chgh>4!S zhImFrlCTP7syZj^v1k(`&lDyxxgf&`3mRQEF^QR%H3?GuID|cFgmJVGn3hq+fL}>? zL^m~#U0_DeX#D2}UDjCkNL@}%#~NWWh>D6Nx6wxRMk5I1FlX4o)Tc zly~X~qi8i_s8K3}>1yogvssatQDIONXJ~VHcxY)p?{MCMC^#^Xod3?18z7U`ipaEET z$Osx>k_U{wfx|Z^iwY4sQ=85ztW*#@S`;4;g45P8N>UkHweVW6*cO?U2uya!P_zoP zNQxCBMNAmkX)ZF3Axq$)=2YS^Kb04!`uBE&5JLiP_`c+*WaqESchy(?4iqU93PB^ru~KY0^I8aw9mjcu<=b2y?I{&V zo(#Tb7o)=p1bIGtB#Gd*c-rp(!J{cWVw$CuvLHm5)(9U&Sa8%KrY8ObL_4tMl^D_` z58ge>f=7}b(PC~2kB0ahhb|799end<8G$2%&BCV9HV73&Qxq1G{Fg-X6zvhTZ>l64 zVn5yH!Y5#;5;AICnPo>)FgVe~47Kk_a)UF55UjDI*QgXX%gl$2utQ5^@|g@LInN-o zhAe?80_c|C6h`TqU0)?rA!bzcq*8BQW~Cm$sw<788sHwLtl39uLz>8|D340QBN=5t zt4ih-0flW8VSxlV=CSft6&0B3QZPm-8X|yiYv4F~=$aZ`=t!E804;M>nQQMTNE1nc ziMV9bxsgI+f2-iyI^nDJIe#S}c`Cox1e(Pp7S-C8WuM102AJYpIm zuQ!)Qm~X^_dUm9M5s$>QG8M3+?H$D+p^{Eg`hsiVUjy>yg4kTu%d#3HPcevApDZwl zPgk!DlnqmY!yjfDr^_q;7{xI)f#R%+5N0t&qc}3935YrQ5bcWYYY!v` zQ!e~BZ{GCZ0{Gg$zrXJ~>P`9k_wRbI2EZRWLHKo*h3Br~U>}+VaeCK&4Pt|R!0uZI znhNw|5DaRj!$g?f%^$G)s)OS!fP{{W4iTqApHPfy%mROV*4nMtax6-zNT`L&k<}@C z91F*kO;tfmwTPP(qh+vqKogT|96g#agBoGFtO=AyLu5>$V2U$&LrqsDm^d*J?xKK5V8y{n;bp;Z0j}|CfV!qIc*Oyw0v^FQ)Y3Z$^e`Wq z0eP?w-u-+J{6Q%2hjj=LYE<915#Zm|8tenueobK6EatKh%~n!KLS#i^XzDhFM|?pR zH=~+m3M>_T1in~_5-suZC=>#MNfJlsGz3#-5xqI#uip(##Ev5GCT|JODc(8@EQGhV zNk39>)n>7+Y!=%@ZwZj!4d0E(aiI5J;1s|oNy|#D-Ah3qy9l?-c*q$&C%J7$@WCUS znysW+vQ^l6MJ6$RDnfCyF>#y)HoN_Zwy!ZQ2SF@J>bAVzyzFCIg(;|T5>vQvXi2wV zl0>L6{wV2!T#-{Z3mF0An?@#DrVQa&4h~vxfhwOtJv+=wM*<9L1WY6^W;Yk2Xt3sy zH5|>Tq#NQY3sPy6HIJH6#@WF!SC52=aXA0^j4znH;xjE%vq`{Q+#}C2voVd5Z)rx! z&@Brs-vZgkXN{tUUu9zQWNDepLU7n*pO}T?aG3mx08TEkOu!HUUdeT}QQ}IONfVP< z8I(;K$5AuNk|aVY>SM~a!cdSQKY22mQ3er=5U{}NfxN~6O%Q|vVU)aupt&k^VuBC{ zeik>L<71W!VG;jKA@D6zK#bZBKIuba2n>ASKk&bD<%<7`%F*DD=zJ9QnZob9uIsQ1 zh68O!fP(5C~YN)d_-7s5Eh~oEDBYYF$#S5z||#fLK04-i52RqbUlk z#+nKzCdl|W$p{^JLzqTcuI$aCxdo-m4U&2?1oYx;J4>rOxs^IhEeGa14ZQ-0q@baX zj*i3-{yQjz!u_A5+bQ^B!J^E|AcBY14)z)J7e2n>_;3N(;XlF+-&x@13^Rjzu#X@B z_Z%F)g!rzaho}xvpWNM$0VWRxO>OhvicDnVSi2>Atitu2;f5h%nmC|wNE#I)kCso} zW1*`iF+Hw$+K(1uMk*`#9+|?Y(vo>)k2u1d1zuGgh}=f-S!6hG6WLv(ygj~YsxksMUVgUzH zI!XlR*)+sq92JX@K{6ic@`qX0Oo6Z07_>%A)>yz{8IzVizR+cyRClj5_?zQoa$S?NNq}z( zd9P?`DG84V%T!>d)-1W)+Iz%&0(BWI7VJGi{%iO2&3E(H*2?EUJ*9R zf7`@m67c%k@3Rlo^{K~43ExQl+Us{oNaX?#_I-^3(eT_SYkvat>C-14F&rPRuiy*3 zJ0^@mY}ei16ns`94Dq14_WS&Yqs_&X9{6LDXVyu93HiMOVB)~c4hzoMzh2>mT`nw5 zkoAJo+Sk@l6)n(uD;Sb!OC~9*N&+oJ#KZ?tx-oIYM2OS|UBb)?9hp?-mMZ+| ztgo}Y#r(viMg?lA;Vo&_A8$Ip0$9F$xd!@$i_ep#aAU|w7(yLzsyh{(flhU&oDar6 z%{mnwZoZ9V+f7}{0Q0T5GtdDzzUp@m8*Vm)#N(%5F8!ojOAS?M7sGv}2?|l2n+?+7 zS0FGyr*7z>eG#HR?l*%6tkuF%iIu1}N~dW6LNEw->3{-e7#Q5K?oU=s^ioOxwJ)ITh^1( z;RtS(&H=3g@qHZa-0M;rzf1_n6Pg9w@h&yUPyRarc1X9d7!3Y7cR4fl|%2%&4&6RJJp#-BxkB0%kqfD)!R^dGS zoOsx(uq4(%t5P?lRffy-_>tL%e4qbUty<-1V*8=M;qS{9=edvWKHK~3>iZn$vyZR7 zZSchfn1c(zx$W%o3D!~O(&_C3Eo1LI#CjJrye6FLYzDCN5ZHO>(uZW<{Nv}52*%-0 zjL*s5O?vKW(lM3VO0~EZ#|>aZ?V~x5dTk;PnDQ0N@3wbo$GhwJUBc*tK6e`+M~g>q zwN5`#Nvcu6ka!rDguoyX!f&pnobXbJ<+KHu-6Wdn>ovdNUcY|5*P^$8ek$BoKnmBu zTBnV^De`@hjnL;_b~2KIAE%=OAw3dxZo70hLRy7Uxc)d;q&C3ZUGUw6ZZpbVic&>$ zkCoEg3_>_5*90>g9;c-)J3Xu_lfNTWtUx@N$&gf^>k@_}@$0RQ1%Sbv2T~8&Jb277 zdf2WB;c){vX+3~mf1XD_$n+DCue>f@y5yzc=b*k2^COZsg+GFFm->;%8-XgcG@~L? zE|S;`z2$f5QFWxt!q88)blRn{9@x6)*)%-422TA4&>2Y2yJ4dt?Sgw1l~;iM!r;>& zCyxPRbTViHKc0F{Mol*Bh7Q~JH0uUKw=HRxOR`<+vnni+F?NBUQ+P{ihu1^J(%&{o z-M%Bt2~SM>B2a+{8CGSK0s}CSw8`G3)wYQeu@W)Ff;mfZ+Dc%#WKU0G%xHyE3j@in z*!U|`ma5hwAp{V~P%i+xDuhGw%gf)A?q%TT#{iF9B4R8h6~85ZRQQ|W4Hc2(LzENJ zyWTg7Hp{&syGP9K+i3gSE#W?~v&cEU8W{S}(+BTnaG$sn+ym5Fa+@J>hrqx~$2Neg zv38}6T1xVZrCF>3tqLStle@_|**3+1CqrrVu-gfh8U?~DkT75G`yH!4@^ldS%Gx&x zK7Ts=ee&-Esx!*xc1QQoqeneiek1VBH{V$GxxHf_%y6bT_MLA&@I(49A$SA8_^j(- zcckNik;PFD=~FSM179p2$G#MlI0h}@@lA*iavDpdLNvrKcazw`n&46)DpHrC+ygCj z6Pi`64=}Y|RCjN;oDf5cY9kv13_~oWB`IVF;3R;D0aGXS(2XR@5*cXC1-4&2K0o-X z$Jy>B>%HbXA0>OSdIji%AE2~IXZ$n-)3cwhqCh%jc&Q8-CrB{JYq zF)RV_1C)ysEj9xQBwCzW%Er$SE3ZpYvNz+Af=xWlYV@(+&jo&0&hVp<@4mbp3?BE4 z*951>)}>0}ak>+dhf1$p%0&QeG-$PB*(2;`^N@=2Tszf0w!%H6P5{FakD~OP7=D3z z>&fN8;29UlgU49`BisyN&eJ&{b^QmRM|HK4UzfV;>X>u@z*=Ok=k7XAx1uN|Xp!#0 z^J^~yA&*!g2&+1zZnOmqO$f%uCeYTv%Y{mmaNt4!+7uYJ6vQyl@`J%^CX)EoG74!G z*yiGdw?tlkP9z3S;dTcVqrC76v{ZPvidar5Djd?Ti2}0(gTxZ4JcX-^(*UN^h){Ms zBq>=SJOFv-w$-YG-LQ{roV1Cx`*xw5#~Z=0^=9`#cZfU631--3@}w|ZqR`z!ZG=1m z%~G2QLu-}`h7yKLRU}U#Y%?B&l#rx*ZJaEk>@251;R>{5=7Cq2CW!TiJ)1XA{}UF! z;q~VzK2Z5$$Cv-Uh48h%&!rAzN0tNp?Afz^==S$2PIU*kW8B#V#&C`~#MsU>AO8Jl z;03_|6Yf-Z$UDzn`l7>d?Dt;ackY>8I{*EW*K5f6?{D+`oT3x>RyG@D|7z-$B#L>r!D#b;Fm9cGV<$Sw$mmaH;)t!5OSm*}JDKvjR}^xa)m}(Z z2#+S~m_}LBNwJvuQQ^;nM|&wjqOtL`J22R~Pw;?QO(Z14Od%eAJPd*G1j~ifDvVe< zt@7jKGy^c;#ag8hlEK3tAv}_hl&hLY6Hvj^lTJ!FvnxAJG)%E0c^DU#|`6dA&E0u zFqdvNNcV<}W<|kki$N7;+tHASB(1hu9lA$4%fU8k;@_ITP zwtna6KltFwQeUh3S-_Y79qi6+-&#As9o@e0@q@PSF?@!1j61lT_KtnO8ZdiJ_;J7+ z#2>U9*t{Wy0N>~Eb9!qq9D0@{@)eL1&%wy!@TbIwHytBVQS{p^ zB@?d?Hb^;2u%s0*JZ#-yRG^-3Nh+{dQq8Db#tGRSB~do;2o%c?X{lSw z+`iNzNl`U)m?2i5wMC-Zt|53V38c-c6qIC`A+Z7XXeSteFbyv+jDDh}M^ci2V-{=R zaprs6oZh~(_2sR5sb^EifCVC~IizAUdIo!0c!WKw9!$f-Xn=Wab&_8Pz=U{yrQr9Y z0cJLjN0<#Ds@Fj&T+(mJP8kX}BUBFYFp8xOhHa^eh-C9K$Y$%F5Exz36ZacE`l$~F zk3g3&2CxvE40xTfq(!=?bpSXiG1uO103kSm5sqyo50A?RBoZ)}IIRK`iN}6fqEJ$U z9)z-}LTVv~5NrlK8MM)WY7#xaBiMxtVF<^g*w|_p9wMdYY9GA)0g1n_@cG~0^Y|?4NcYQGUs*f+{qEIws=i$Hk>080pzi$l@!r7&=^K7u zXz$;@-{;#7G)`Y=IMtp1&Us%4`4dyG3`d;NyF(TOKz3h@`@+Xd!Qt;$4-S7t&G*P( ze=A~hyGvEnoyl;+DAv9(+Hqy7-I9`p*DJdt)+IkMq$pd7heumD4V$hx0xfm3X<#>` zUTK;vWIzI7HUqUKn}(O=1#LpgPrwqbpHEAfR~-Op*GeKze%rAUaa1p zn&V$Ucq6#4+%r;RP#}IYwcWjzs@E*9qO7JChSN~5Ii1SKgM>n(2C70`Qh*VO!R(T9 zeqe^dSrzcI8NW4|FQX3cmctoj)pNAeL zGh~$ACC#!_G6qgaQS!2N&qL?&@e5+#4tdJ@Fy>(t>ozkO1`kdT5xnQCXH&Sd-LA>V zt-`ppUs|EV`dr3Vb;hbFJB1{J$0{(V83Py%Y}!y!g3VyKl!adu za{G%1(qJ1Sk*bRV;B-03op%;kEX{*c74<=--2=q90mvwXq{NuXAdi8VyIl#2!; z$wMwG{b(%-L|BAIKe0PJSnq&!5+>UGqis!Gj04Zk_%+#?Eu6wC{O* ziS0ALpA?+`j(s269r8{yC;um(eBvE}$ER5yGrX6A!@mV?F^qXSx0qb#`-^pMjm zR%*0tL#zQQyj#mm4-5|ycuL^WvT2s0L;#$G@j$u+@LBZ8kt5E3KY3}X9(s-XN=^BG zfYL@k3;6Kw?O+#l(tBATg;G!`HA0#h*t)kVl#yE6>V{Pzk_wSsAJRY%o4@D6lLgPI zbvz71peitcAw*ldDn~9!vGr79BO^8;@N5df+8{`VXG#jW;PD`2K-OUZ`1lEcRICsw zILY8)gNf4l>H?q>l~sO$+4NXKh&(Z1K4>}o|NQe$zcuiUkm120*t6zI^zcfA-*En$ zJ(a>efF3>1FEE2DsjV_R$evk#4D96lE=TyTK< zEb8QUfcI&i-K8_%tHDnV&Uqe(yx0zZrp-s6`}_h|53bV~$42ygH=<4467g1L0~F=HUiSKkcb^w>&mQM;d~{KE%L^CH$0 zZ%JPV>4tv`><@AMCpg}F?>!y!*)`hI*>NS*2N@G17mp#e6f{e!SbNQe0buA460kby z(m;Zchi#5Qh|bfmaIFxVAzYb6x zJ+WqO^g6IgwgUCvv)o*W1K$J7V-Uthf{Jag#p0osY&BBCPbV9OUkd$kZ&0hBb)KJ7 zpeSI5?$w8Sj)X^)O2fFp>;knIED6yH9*MGrXgeD@0}pVi!GN(Lu{yek2OF4GgTHMO zuB)QzEdVyFX+t85r2?gZR~E?2S&#!; z65E}i76V2`!YU8|7@H;hJ=6?CAg~$0r80%!q{s0~q|K!eIRgVE5_#PW_R;Kkcc?1F z{XnW%ew*vkoyPCRa5FgY-E2JWjbcT0cX?9)x5^%DR@w5xyE*$z(yN39O0=aKt4`-# zR*wEK6mC`p{V;l>y2Zss=ee(3`~{uApztSi&Lv+8IsE&ZI^PI6&wV)eS-q3r>FrAi zhm{Ym4t98d>f`$#9>;zkR;0ub&}E6gx>y5)eC4`9i@=!3aHvu)A} zi5SRju|a^m@L~mGqe0VL)>s*lL>tmfGYvz4%eH_=lyG{aKZ~_ zi84b5r2v+K081|Q0cS}?wL6yhMFHct>XGyS8lFDSl!r)v6y@Ra+Y^97>wJAJeQVS_TiuH{&725a;KTyQ~}(pINdG1HLPaKMVLml z7YxN#kq&EuRYv{SYHGC|fX$ChPYi&Cg#{n}9Y;@}K0STp_;s)Y%12XQ z`1|zk+;;N&Eb0dYF9KgyINp6_ozvR^(p$iA&U+(Rb+G%s$fwy|yLNH%_TA+Ch72AN8lZTH!D*k=Q{P zegobHxvX_U05EGlj}UeMoeVErMozmAz#tD;tV>j46WqNA)*T1{3@JtObRNImJb(VY zADaAZk^%z#BP%wToYfSvV4U%4?~PnT3#4 z!$qOBDbzyZwBu&sveYzsL}8HHV2^rLXK;!oL*BFr$EiyOzbyO=gbc3$-vW6gZG_%Z z;<+%p4|zPc9twVsv*GdV1wo;Q8Z$i%eo;ca7d*DY&5%3{By59$%1jSKxB-9!IYWCf zGw`T|RI?h*{F=!lq?<59y}~=eM6&R> z85r;wkPGLLB4-`>;B;ZwnTuN1OtPI zyqZsQ`yyixsfH+ut*e}nhW_xVT4{Ap38)}_)pPv#asQ6Se^0<~1ROzrGwTfZ(cK~M zQ1U6XW86OtF#MR{Mc`Bi@ZsMF*M1i8A>V7lY3A!|F9k~uc?=FWA8{RJzH#!2*9*mq z=I5V(?u|wU!S+#UY~|Qi9@ViKs&FT$Q7sI+rVzG@U~I7{`yxuO_+7HWY`0a0C;;0U zGoywex2hpRbOy}ajUUf8Ynq`tPCP<@wMdO_6|`lkjg>RqQuAX)229<+FS~T^-k5o;@)+>(YZSr~ z@zal47BwO_QbH0EU@63cu`$H#5{b_1zt!^MtPnE~9@VuwY#v)L%45Z**)!-6b{{fm z_E-UU?(B|-+QaK%^yFFEX53bUuXLjcAT zT~b|=*8w60KU;ws!mmKH!u0{6LUdK7`Tzs3EwU)nY~x@^MWVMbW?*#z>#f-E$iV>I z3|4ED85os}mau_Y6$sW2TBRfz0ARpYWk(3lBc$3bae~3Ksj{RN{l`O6dul10+5r5b z_zA`U%cZ!qd7fJ>Y-BkvNpityI~e!JivuyOXqJz)D{ z&<9Iy8$0d}v4eJ>$LvIHYZx@RZMw(WRsdDtfw2@bk66luSaRtmF$;Hlm?P}2VC%D5 z$ivCdOs&Wkr3y~1Qyn{)Vl5ev#4DfOhDv(s)F~fF{i%-+|Ne&42T_N=1K;oqN?-Nx zIE8#i;lsb-b>RDaXP4972YX-mcq#aazz2Q@m+$r+@=h~IJl+C*(Dmp2{N4-x9f5zO zBQGVYoAze@3lIg8N4a5NDoVYoFk4l)jkele1K5tX)%GzIWd;_`&>7{kHOIE%Q8FYz zt9Y>0CH=Q#P=!Yna)B`JOqWCoB$x&m9s_?#1)`D#GYo*(ZZL+u7VfhCyTobsn|E#W zJ6}H&X^vN_-??}N_+HLmM)@vi#flaFQSjlzhkyV5cbx7MBm&K{^t|I|n+YSJo{+&# z5}4SERf5#Q)eD(mv3ez3MV7?U>7MCn(jWXh`C<5hRm3R;BwGrwR(ZND!5B%hn!<4d z$EkI~$xA#21_lOUI$U+YJT#=-ED(9A8A9;GA`dq@OMy4B$h3lK__GYC%j8YM+8`>frEm_djZC|0U#YtI>}I= zOI4s^(57Cq9ie=PS0fJqJTP)gx=}+^r?mFM5~Jn|(P6d@V9-W}Su66R(zKds)?1eZ z$O1_rgCB$4VIbk*M}k3kyJiVqI;j~{fssUvSW#@WYPafAfk=itQK%sE1Czn&GVuak zDp`nGKV{U7R^i!Zc!nhycp}M@v5Mp^1kx(~iGf!*j6qeOy4@HITc!}%y%7vs*)8F& zaOqQona4gN7t3GC$VIWWf}ic!7oG;>V-p#4S~B>hQ=Je43EO5Vv+br3l`_?(MY>U| zBY`)hKxoMD`{u_v@58^3qZcn;^x@Od?NIVvz1fF$AN3vb{yNB~Q=jv{ z_~Hxam5=mRft~yYfDf@wc&`A%+re)$y*mtF`#8#0uU@@o%^D%zH4cC6qDMKpO&MNVZZma|RN2envI8V_XcV(?qVtUo?G39-d;8Ql`y8wBW&4zwg(EO0ju zTPK?VFz`!fM$5yHMHKyzcwkgPCx0Of8RxB-UNlx+QVUO{>~0c&_2hl(AAD)Gu4<+) z;%tP=rAwFkEr7qp^+nv>yLasuS{ZAd8KJv^r_@pqQwmP2atYBQ-LRU@0y#ygh&*K~ ziXp_%1XWmt5mo;gb(L&g1qP@Dm{Uisgxu-wLFCXCKBR z>85pb!?59<@K$-O+DB1r9+w6Nfsm0i93Z_WoDysdQiuZP@hQ~&6$ul4ghNa46LkiSSV66b|cH)t)eTSC_jH>pceTgthRAFEf zCJ-$KV1)XK8K8!Xg+ayBJ|Z{3Fpvbyk1D60bPq7B+Pi9`vpXkMb`VTDPWT#m7#r&uk`c^JWB&ktSGclsn2&Rn+;fJJ9vBEi(17oSSy98i!3P-}AL5i3G zs8W$7GCCDGeF@{VKX~wTOz{>Rri{wT${?Id>!`Q-20At%32p+4|Ci@X{* zMV&Zt!Z{D-Pf1s#%k#Z5Ub&aC{^gE0SSW{p3|i$BrpOAPCQP5ss>{?8T>_xUC_Sg{6X+6Z8)MbE7k!dW;CHi7{9J+@MSrGdxO>cIza_nLjk_q;p* zk$C+3gl|Kg>SAeg6o4_r;_T}qY~J9g%-#-S?WRt;bVM-hg3f3N!So31lI%)T7mRL< zqLd>Lj9^1IYMP>eQMrr_%E`+zyxZGjO4L}ODY7F z6loxUZCEmkq7w;KvIuAM)ZQw!r{3wdm0{Hj-Ao3<{Xm2Ff}6pBv70s>v!s^nDKVU8 z8KodhIKO)az|5In-l`@>H%N7KMD>(DYd!8nn3^D}t(jd}># zHbs>Hgz#jvBg7+72!(8Zqpblj*os9iHJ*0a>{sxPeZL>^W0W77^wxK7UYfe+`!v4= z(0P9=_2R{gK2)S1148$bT-8Dhem3!(IzuxnJOe!MVX~z)S1{Q!Hk3qv-ZXy5db_R zeoIQ$6Qe4SqryNW{Q$_N18^yw9TaXR7(cs8%>t2NhIHQ|@yt5RJt<^36CC5%JkgxW zcP8NBkqeSYAyV|b(Y{|>F^kePPBVa6XE4C41-b0<8VQD!LZB4LYbF6= z&G1%BSCh@k&jzq+Fv`x>M&j{H%FmY9jF5*INV#SS(+~Ubm0X9@5GR;K0u6p8)GJN^ zu?#7|9R9Y3ZHt|pHr;Hm?K{7>yp!Lhd*jvo|CNx-D}V;HZU$6d5FwFpTJoll7a@f_ zVkK3J*`)slci;J2Acwi%>iR_Ldmbmhlggio`Xb05sQB1#$=ktj^83Qy za9DMeIk>z9eCKoj{(YTMAXUVuonRWMRXdSxIkP5cvi`K)me}yhx8$@@i=OZp^lLay zyN}kA87F3e{6g@sE#c=EYw)lw!Sh>%9D$lI)-Lc20dj%WI$h;YGqxTUg@^(hw=r#C zh9%%KvXMwF5SU?LFuR@nQwVMM>k^&U%ysM5`MJQ$*IU)Q*zVlEecQ2v(@n)dq6E!J z8$#`JNueb(TB>-!>SV&vj<$O?FBy%PGD`RGcNHEtQ)Pe@u0PX zRvDHIxd;QsrV1suRE=uE4`!7-Vp8am0a+p`vw85D1AeTt?vXECf zkT^wIHIJlhOJF!tD3>gv2pAd5OAit=e9e*9l2}nZlG;RpPH9fVVGeK0Y?H&^s*kdM z3b60K9rEOQt#|3(pkJA83`_11y93|s(f|fS?slq6Xh~JCW*P*@Zb^a4v6NbV3KOX) z=b=?jLzKKiP~m~$gyC!nq5FX_muy9$^_`FN+=p|YIep-F`1nPn*^%zpzjW!+nKNg6 znD;9HuK=I_*}jNyDEVOj{`>F0^Ugb)Hf{1-OFt2KHTY@5;qSorPh9=h(l0a}T)+JC zi%K<0W6fG&vna6zsxv4w+X91ll{WYdNKOXLZJ!ccDp0ZQpg>DC0bZ-89{v4AnR=x* z08}l?(g14$r%M2AR*{>LNW^0hTvA{SXER_h%PXUyE`h*>W9$B8pt2DH84}qYQHDzl zDIrOq&l}#ATJ0_DPeXnEvuoF`|IWi(z>inn3kwSiZY}o@LOqeCc4>lKW=@=mOpGiP znpJ9*q2T~7$*>WWU{e%r*nm0XSEi8I%et0lT zQV=5r3=h1$Hc-KXQNmCcZIA{J9wY|+R*}G`R!9vuvr&bVmq()MhMSK&JUF!-Cj$=; zo1tl%P%g|aTO!gWzvfp6eumrICwz&(lt4+1a<{9It$_$#GGxT#((IlA#vst8)NIw! z3C7S9DaeDQU%dcBwpC(Cl0tUm20|f_RxzZIQ= zM1~~*Z2Zjx5DZ@-Y=%H3IG}1N@!BqsvwlWml}J3zG~hw#(kvI9JPb><7XuF^vLL}` zM(&brs7Us75`#*hOMx(Em-HA?KuRH;hGzYNv7~7pG{b@KCq7Y!Z-l+3y_fBXJIaP)h@3VxrUZ{mHt`g~s7PrlqAI^7xrA_9rGbYkY@C*& zsy|B50e(p2!2D8(Rg}SRm8X_kbtxkfgmh|!7EvM#PkJ~dl9Z~dfWfH)8U^f#({Q*#y6r7gmbSaQQJbPpTpwNsYLsZ>B!Xqz2 zwPW@?+FYOQ4etV{x}O625x|n;-N^>#Y?sLKE~7n#;qjdIdjSmI7d|#fq#}NWNFU4kZ2uY1oKolCp`&H7>t{+9#RWtU`sq(Y#oC{MUuRdV#ZT0h7ez2J$drP@3Z_e z*Xivar84|c%5T5@<}KjMf4>p#D~LUN_V{4$V}JjddSJij<@AE^C6f1o9~?Gs-t5%! zYhO`+{PBl&848rhOh7k0g{0C(3BM8mlu0DDL~4uK#tE^ThI-p39pG$3)yNhVz!EAv zV)@;5Y-Jh%W=RDa(z%)OW8lYZyW{6Kgd>3@5{V@q24Mz+Y?2siApi-FSrh|CM(NNp zz--o>dHPDZy?~LO5-kE~m1eh!-^=@H%kNEm|F6kcuU_>#8ZA9=;D9fJybdHK34>e} zrIS(+fI%PFgR^22CQwD97yuX)OAnoR7<$65G=tP`6`X8KU{aLJrPA278mAH<@ms2y zI8%rdST2_-q)H&ElA8HZ)p=my5;1s)#G@h_%9Y2eSaoS5awKd^V4{SZMHNdMKa9K* zS!KXei<1GMhPk?|ifUC8M9G*QhH(AND6qT$T470EoQi6dA6r8=_yKV8OJv9^ ziXnTofTfCBiRfnt)B%QA`1p~yB*5tQLyk(6S%36R2s~zq>WNDLGeQ8l_{Y;)hxt`% z#!Qb|7^+YsT5N2>x=9#0!3-H?r>zimA?K9Nuv9HbTw)MXMbcTVAz~RWC5p{uEi$ud z#>tSHfdniA8^fR$r&*11ks+#FG>BCroiNUPM>nG%0oZ;*cji0E*&bWRyGtMMoubZs zKL`MzGI!?p%D`jQY=}ijEr3Kum1-AB{8nuQNtBxd!x*D=QfQPVA@-VPVC8UvRf`SM zB^Xh%Kq^XG0Pxpju`pg_KJEL=>2teJn|>GUbGz>ue*E!A?*ji;%aXImf0w}jn#3Qa z`_AX{&p-FGK;H;i^$PG!h3~7^u3hUz;1jHOidsn0rIhGA8FXqm4aB&#(Q52=h39FD zty-elPy)3`INMESVyj-ZVYPckX)f`|qAfTP+EznbtYYK0DxA$vj3#ipMC;N_61+_* zHBLy~%hNU$0w9n8Ggt)zCzz`0;t#-+66MgtZzNT*)VdVg9|e8<@yDNi_L*Jy>8GE3 z59FU|cpV%$azuNPD1lUi{t9e!>7>ex3I?Z36~RaZ%ufsfFf{N}PG=9o-1X^+E>H-& z6lUpo&>~a|W-~%9B?%`F8K}%HX_`Q6Vo_N#vt<+lrrtDg)*teCEXk4pvp`g3A;79j z8O?(Mh7#cbLKt$XkpP##qRdv^BxZhw^4UnF!zOi0a*+f>s#V&6g&R28$i-HsOWC!; z9I{$aAw=R5zxKlDhFAt38D%j>c6MznUAu#U9(aY&qmUXR+>$H|DXdmYO@^1HJ;f;y zC%jc)%$Ag9Nl^m%(ef09tzEEcsniSry%k=Ikf7xe3(tuhn~E6H(FTl@p&O7okXo1o zz&Ehf7MBFGA%$Rubg4g<@JNJ^vu;=-KrM7yl2=lJNEk2#h!P@PMQDY90ay~{QlJ7k z9ojzLcg{PmeQtMa@i@aB_zrmhYz}!Z0`CZZC%m^nUkzE68jN3*&vkUd8+f#fp%A$w zr9>ZiDDgR@{;+uftX4=Js5*k{p^|k>)p*Qeg;)}6NTE_rQIIat63`Ol!-o%js&vBp zZo#|E>%if2>((v*gBaiZIDdTXw+gd!=gu{2*6{e+$FcAHciQ`2=+jR>^@YFhj=ucz zOaEs9<&&YqdZ{co34@s)ONI@4^yrblyxq2K+rEAKY>iid|4fsg#r$^Q^5x6*2Z`IvY{b?^ zYZ?FUj3sy;MjK5G8&bHUAe9Me7J>wTR31w*vWbT$ z(iZ3#zjQpndBFIkmclAycp#<2jGq&&K(@N3KF%+3egJ-Y(nd2tCD17~Ro#$@kr$?0 zLZgbU_V!l_mjFbvQ$>sv27Khw)y8U(!YXFvuu(bH@jKpqx8o%DY2V}KkffQ1ep}y8 zX_Rygp1upB){DdW&+lBt=~8Jft6g2S%cU~W8UZW`Hxo}MKY&_jGdP8en+i-(E;$)W z;4!nYNg+0!>g9CFNe`ZL=g#@}N6vFE0`E27+xQO-aQcMr*!O|IFNJ*h@X<#f`KrfP z{eI``7rst=F9@Ge{if1~|I?>WYnO)5piE-)s-id*D0-BVo6f34%lcJg`0UwO;1tScoI-^&_W^<;(kD7 z*abYC0^xat*JQS&Wbg|FFlgo%q9}w6V5bUaF!+jP13=SgGZSAe061N$ zS}sW0#c~3;XF} z3@LJ|F%`BF_@?0;Lent`8bj69_{dVcjE)U|AsNDyMgo=77mw zjd(06RzHFHWd!KPaLI2qX5q3EBZ}W=ODDYJ-S^CX5$xLrXS)xXTeoiY3rFAWJb3Wn z`t|F+)&KYWA9nEfKVAV&A>a9UarlnfcmBSec33%%WLH=B149-z@g8t9wc4U0vn58m zW{DD>`cI(_2&9~c;Sw9!7?kD`rU7HA{@X`*0BpdLS_R`F%m7ezw+$P{r5Qs?f^q6q z;$8XJ{ z2q{#Zfh3Ut_!VfCQ#nQ*=45aZgI`_5xYTO9fJz__zYwP`jEq)oJ@UFImsNP462XX) zj@o~2A}1t_5LArnQjU&LJT8?P!>@yLslA6uKEb{VC%R4$zQCItZ6WFr*LHXBH+5-CJrnA9TV zq)mvtWyBdRzg&=2XKr7>N@2;sOqFo@T_#ltVo4$Zpt}*RT7(-?0|?<%BZhWXE&O0Y zgp+4jk{yNsi7KHsa<&~32>~#38lse7l`7!UF=}|K%mj-9)=9C_$%PHf;0GYM9UOO* zrTWIOstQq4Ve@Ayxm@OyF(lb4#b8#2)N%=z-H=+WSx;n#VN+hQob^FnXi3s244CyQ zFvAi&r|iiVML@eFmYP8$pxxe&R~ zk_P};MfFO?LaZW8KMy@Fbt%ItJ#7jq3_j4BrB;+eIH^hr{g%iO$jKny z0MihK+arccP8({jKou!aB1>S{3_WqFOdi>jA*qaJ9vHnc%Zs_oIu8uM89**1qGXiF zj9h8%3Y%rN+NE^FOTk}ucpey>l_rHtN%3&XLO;SPA|t8%Vo~Abk-`t2V)-fYA;hm^ z{5h6?d}ky4+QlE=`bNm^NL`2$WRYEzT!s`X;wJ#Py!G5AjMb`uscCq?NYdw8!4PGn zO^ub4VO1ew0r)9VvLzlV7yyilMP8%Q*`{y;mqkWAWDBfImV~INLXaEewKbL`g>e_* z5zen7{$?JXq{=%Nh7#r1c04d)Y5^dXe{9weQ->j`HM(1{ zL7GcsRhQU!(o7o{yub=F?2T7Y5Ib@wt1r20NQ6dbw#2;AI?H59_6JSXQ5@Mo~ ziyq|VV&fMJkBUJ?;555zH*uy!c3pzx=V2o;8#&FABB4Z5x$^tbn?jG6VO1euXoZ*E zWi1j2S68;+WD=g#@1uzxT1pZVVi`~2xE2)`iU zp%9_rsHaCEXc3w*5CedjB!;9Uv8}4YES*^C%)(t#OQ#Odrc3<#5KNgaEg@lp5zi2a zffEuSW~)4A8o0YO7)ymfCAaqGn>KT{N&!A(Wa?jciLw6OL1A zbB4gckYd5CN&zX0KzbOu3a<(!@HAwXf^A8lkYE_d>#pDt0x65y%OVlk0#jn@b#u%a~>*v^%mGU_3N%lE(wD zC?46{?)IG%hMyrs6ZFI&L%OoC`PuZtSk(<>O3g1^EF*c5uww`S&mb5BJyNSn6dN06 z089K8Sotmu^#ZF@IND%rA^9m$5v1>Y3<7-Y4}iH%X^*N%H;BO_H4Th9#>pc!C-Idj zyh||58fD4W@QdPcVUZnAAAQW?1E7rJneUQ0Kh=3;ex%_xz^dz<~q)1ko2n{72OcxOyoOj};*q_o8Q2VgW&lw* z^-yY;c(myL{ri3j@a3EDfBY<<3e5133{~{85Lk9Ir&xg6Ra?M_kx_pb_?3VGKL9=D z3T#KffC)CsNFf8BM~Jy7HzymLO8^G(3~V?l8C@lbAJ6c!tzrOUOR7<9gIYYg!2^#L zfYabao*#x7Qq7iNl!i0gr6%YJj~OOal+Y~7fOm;s7G(;=DNv;<>dus_XAn#Y*w8W% zl8V`?8CVodcMcv%DO}2e1i4k|L~#;Rt~~8mAz%hV#hMNL@Yu}6qk_kS0kc&pN@OOZ zTOEnjnQVFt*}{_%r&$X6)l$YfqXUepD>ZwRq-KL34@qdn!uSrv zCt(WBE;)T9E0HMSLIl|Ue*N`VZ2?w-a0-<`652FGC0B;*l$VqZFP#t-F$7wLB&im4DI9qztQu+* zQXr%b2!Uzu;IYBu2U9}(WtFX;2HPqm7z{DOMd=AUfJCIi@U%5)h|MZ_2#KkO^czAj zbF$kPt2`L+7?xz0PTwSgugSnd#;1J^w1bfFoEQuk8r*^Y!q(Sx{(;`($B*4qU}me> zkie)#U7AyNREDHH=2}!YfD3oC$)zq-bP6O77%gW5l9M)xz{IKoa-_q+HUr2gMO5sx ziDJ-(x!r;3ev^p7&%=fU&^NJ?T9VzY?NYG8P}vSn236@d3*l4|43c(=tvPDX2@^?5 zT#{N3g;biVRhM#!44krqk%Sfuq0}zf3_}#V!o})241*-G2rCMkL^Rmh077aYg$$eo z!-z%5#vlr~k}(TO2Ag3Zmjwe45>YTzDNrJ-Rc!{t%t#>vhIr&WrGqJ*SZwgMi#D`E zl*7XxfS+LDcRLw?(fz2a?0J&0YNdqx6^asWz z8?4kygQv%3Axf|XRPi7z9HC)@ASo0=FuRR#S%C~B(ut}qc7Y*F8R3yg!NUeNv;|%| zPWbK$0t_jznK4@>OkUyeJOI_67#f80fR&M-Gd;4%NDTc_z>oxthZ9V5VvtT(=ls*w zpb{c!I;{$?QXWH6%yJ=Q10!VAcC*&u5lFVYqx`xgk-P{onDtx;5}a&-3>co|X%yKN z#g+@qDrDKo;7Os_lyI^Q2BcL=EYXIfaDGlQ2(yG4OlnaH1JETU*LH#36)Cr>G)rb4 zs~Uv|8>c{>gh6Ntr&TkysuNX1lm;xYkaj0k>VjblWZ*%F78U0R5ePur@T6HHO6qFW zc_=gl0{|+7Y~gIN+Jcra7_s^Y$_oaAS!zfyJOXP4HZWNz;e=tU$U6>LAQ->F!;inbY)G3$j8!(x@@l(W zoTLgj@bH@f42h5cK-NjI479~EG);KIT)uw&`uzFxzHK;p^5oU4SN*~lgDkWWAYAv% zQn)lL%B9(59mYn35K@8h>w|Qv&<&V&&|E`+DW{4dRurUxP&hx29kI&hF)SG*p=FSP zmS4#@A*+`MCaDUvN{JLO@KOvXq!cs@1ekj{eF!H~`AB#yi8AmgIh~yN2@_STMZuub zOiLKT2;omE4?sz=xfF{Kj~2CGGAb=q`GvTQUpg=z#bS1uDo#|DraAxvkEk>QkRdgG zwj}_U)GD(|anebT5Zc;YoU~aI1qQ%_q;_$-WaEJmLMnzd04sq)c)8f}S`BPYhHxbO zU{cE>Bw|5L>r}b6S;MMgm-N0)S}? zc~&j)qY`c)3~a7rAydw#1k7a}W^-cD9K)(5+RT3}KJYRV0svdGKn(!d5IFS|YJ_MnOqOPpeR*=0y7yBk>4n>TO#3gFJ2 zJ8$2d26?3u&S?n}iCh}awyNy{X||y}20Tt; z%EPB0jEkXHWDFh;rvQtNC!+OfD~4xP7Ep;ttv-DYB&-eU|Ht&Oe73oN+h+4 z0iY`MlOGRew7{s4#Kt*fP$Il~u?ck9BL-9R$>u~T9ElKwFnHv}%mCn|0Y)HjR8?A| zz(iFSRbW=NREQ-ZV4~PoiI-PJE~PL8x?zCj(o$;c38d6)+BDND8KO9aTb0En8)l5k zafvh8nB|3(s1&jg%to6$s{~Unmw_1=20%KOWwBJo^kY^UTdXBXiSH&6OQA~|P+`^q zGY?@1UAAPdmeDRAoaWNeP?zwdy$19wymR2m~V)KBN*fL=&XQ1B1c90ER?fTf^ul2q73K;U}2C9jx1yc+$h} z1_t0UC{dks#Ei}tu4H&J@EbK`bOV?kmxO^SN*0$0h0_dQ2w`fB0aDUh!4FS!OPqFG z$!44gf#c*s*!>{9o2t&UYo^P{1ttu$P2ps8zXc$s!s*l^U`ZKxAW5=HxmA~D03qrX zjwbuZjoh#!82Y6EHq2t%dVOF6+g9PVD?n^k@taE_ zoq?H?W-`>(J)Aav9(a220OL_qcL*^u!bnuNsckPrA=F!mP%!|&7{Vp8iVcHHM%npc z=rJrwD%_A%AOW;tQ$Buq`Af&o37;h3R;v_-Fq+N$(gEn0q!F6M^25+6yCt)5gai=Q zuJJ<>Nq~VF01Tl(9y4Y|@x-4bA^Z|?RA@xs&0LwVcHMk0kM88H)y zuv~RmYEBG=pfwV#752rYbO4b2j#Dx)yCknDNJ+tP(!&M-hQS~sCk?0oS`yoBAc_DC z@F@}1O-?zyL2wU(SX5>y3`_hN+Qe2l8At$dQYc(HyV;UlJW}vj1?Gp33Y$i?k8)vf zV+f(GD%up`%3%wyy*L?SiP0`g3?T?*QH!A!5@C~zlR_RX0;Yi;7=-*_MhdOUJ`+rz z85m}P^^+28Y=&H=BL)di{AN)+^hi{tM5dFI$1EMqs8nsJMIx7C2@uGW7)X8!B{C~P zAeig`sG8yNSmmGUnV(QF9S`pgz+2~rlJxYpu(SW9^jl{l&G#HSyW+$X@4;rR(WJh0JR8Jp&`=M$+|?XRphe6 zs1YDq0_5edOK7R>Mxuei4@{EOBvr4O$0Y&9Y7SqARbYNLLW2Ro z5GBNFjt7;B08;L<4T1rv z=kn@C%2h-UJg2-x(ag4lN)$ldE29mPT?kAys!m`fTQVdf2`AYYz{KL=A2+Q_iBw7( zelQu4r!x&`Wl;s5dJbd6BSm)$avp?YC8b0Z+hFim^<-h7O*jCLRSXibE5!BzS4&Ir z2oWgCQa21FRs0$CL=|e02pc?xNWkQR2a`?;NQO&wMQF7YmU!e6RYjF=7Sc>GFl`3s z*ka5QxfD)=FMlA}z-pIEw;4o|jla(Kh_UI>Nu&D*27iT6<+9!65u!An_EcMps#AWn zcoZUwC3u&#S>i!ES~1ly|B!_WGCW&GCD2bWJpxO|52h##U{VN=96$%)`3+8&x*>$E zb&%ai$-q!UzznIQ1rwf!04XRpFce~yHXT6AfY%cP60=LRsNjhaC@R@PzyOf9i8aRz zRum&J0Dh|QknIwW6lM*44C27NEpAHa}dg9FZjhK2^CH-5mlVBnh>ryN5U`Q34RBS@1vMLcOwp^mDN`VSb z1CpP3NIHd}io~T{vcT}b@BrJ^*yNSbfT70{8@W~F%|dcwFc6ATqnR+PXh|}wloNo{ z5T1A*0tnL|S;R^K4?t?A1qQ2ck#iDeFWLx~lpqmHDko;)oOtA_nMeR!8ZZcfYcHw- zAsGPlm@&J=ES)NF+5$`XtyU(R>`LH42*c15PB2NOR-H?;TrhQv2N*_MgwzTid4)(P zZ#*^M5`(1rTxx{cf(jl3jEszuYC;S!l!J{fS<1Gjvg5HjZ_)I9H9Va>U^P=}c_k%J z7OR#hH1Mc`$B-IEATVaD^-a%7l0^!Vz!(CBq{=G1D9#$lBbQZCs&*N^j0g>WJTekM z4+b-vTCkR|0T_79G7?bsS_&zo?iGti6Oe!jr^@WICNqcuK*(d23?1V{Zk7d`RpR+c zl87Iz;nEVn>e!Vak<@LUTqMC@P6I-L%IAa>%MY&u{J@M>E0C&M7q}{DGh{T#qhD$~ z804+620SWBllSn7*^X#A@MK-vKd;3AF~oriEWiff7qpiA&k=yqDJ^=Yqd*5E_fAH zwM%!q%kFjz)dI;we|pSy8#4`JhbkKIt3n^}Xo6TN3=G`>oMh>P)UJYQkN2mMJN`mKq5;a7GZsmogWEtQ4UPna^ev} zp+w3DhM`AB;W+WomS9UREeQb@q9{w%i$n^lq`*KGumQ;f9G+ilB$i}n@Qa1v7fwSm zFuSA{tp>8CgGZP`FssC%!cc0+wAE567?@!I7?6fQmv{^SFj=g+WNVk~_^FlA5|2wH z(pjn&En)yjn%_3L>^2|+Gk*eP)IcK{Vhvl2+zi|*CnSYr=!tNWY7r;q2!VAYWTo*l zWOtt+0Wj0=+a3Zq%~lh@mQ*3K<7b%bl3bOap&}&6)%gFW?k&l(B*(Gn{0C_d5u1oF z#5qO?9+%bDA|$$35gM!%M_e9rp5rwGl0;T`D9E9 zzII(OdBa1_u)wI0lLOngmH}|e*n<%uvr3u}a_T0PcQTAqUZa}FQ;Jgf8Ty1)Oy7m<5;_5{;>yPlRm)4^AV%7#Rs6HZ-Eb51gVFgkrZ|V9a2& zBr!nBhz{!y!c;0PsYv*Aoe_gHsD6Ip%Q+41z?=d7dZHLL_}DiUJw!7K=@z z0ZXdN6C!~VgFFebQb{I_K+7#7fJYKukZn#h@_Cluzi<*oNEARx9)kfHZc$=#ke(VH zT6hF#f!)B8dU;0Tz@uf40Z##mdbMcB32%0dknK$pQpU-Ej4Bv#=IfP*apOk_AWz*f zky+$@FRPG`0Y;$3@wc1Q&N+q0fcDlMu87c`!Hg8Y#w5 z)tIW-8nXa1u+coRk>;m8^DNNfc94)}4-&bStQrtAp*91f#445jJWzRdxU%^c1%^2T z+19Rv(@vP7khpcVpKSA=E>SIbJeicwhOUIrs=^S$K$>mq1#55_ z{$bLBq>Be0hMXt!Fp!c69pp&>IG8mW1cnna077}Nj1e`ID!f|>vsj*!z7(*`KB%OP z1x5(QXXZVX_)+Bzm3SrO%zLKAKV!&QV%`RHiScWM?1@m-=OZNV2&{r1fuSJrIC_0e zgiR_#Q8JVAm@}gt4`vxKPgN=naw5M~`LqnOPsu-w#7W8;3`~H^jS4_Xo;tkG!N1rS_jHu-3p6@}90uG}xN0sE z2%EH0gyR0)zMr2bBvC3HdSXzL!Kqz_H=6rcsuCE!69x7s%{22;Q*!7$)fZ5$*!l4Ap?;MrD{2NuKlrWqESiw$@rV$2lHq6DMm z$liZgz%LJsguOgSdOG=RpJ3cdvdxATc!7!7@Z`#Y@x((K54Ua$0d_kWc<>z=;|GHq zScxA$*KBy|1v3|9tB9YX5*SNLx*qTSOEs(5GQ+2%g&8ewU8Etk=mmD#kK5r*2@<(R zR1R<2<75M;TgDDi;Y4Ca?OHHMHWVr_d7|1KrPcy~B%~Spjw~4;KT%0ffU2}mIZ709 za!6MrQGujJfz<+Lm4PuHu0VnbdV92vrRj*-nF^Lm~s0>Seddao~sniB=YL_AEh5WJV7=dU%5BatspUGNOaq{ewF@~hCEWiVULBJs-hke;hLb6mNPTOcqY`Qo! zKJ!Q_Y4^nDZV!Bd@diTz6;4mITBs0p%WIK|7N#$v9T+W`Dk?}zFo;`*D{^Awx`>*q zJ?llH9ZVO1Gk_$R8*_~UJX+j1Rhe;{(XAJ_j{;NPP|W8atY4IBFc^mcJjexZhD{37 z-4l$cE{n~N{hZ$?YFMz^+oyI_u{?5a1xO&3P_g969v&qm1uYpBm_~OCp{t}T50ZkG zpk1%&$N}i`jNjfQ2|?x)4|OA8*68LT|6*V;U;tml?PH-r-dEFWhX{dHH9C-lbg=}S z5F4gwGys9BfB`oix&Q_#2_W7e$nm)G0LXjtps_szV^FddRR-|O^ii-$s`9BtNk$CD zfTyIPkdVM&3b(nmHyt_Bo=Qf8q>ED(NlQkEN{k#{zLDw1k6eqSE_(x3k_>>;L~M{~ zD6tYM%s4GmpsHPob0ubUArYtw+zzX%{x*a3ibxu&R z%DH6zv9W4h&(r;tf{<>Ly7g))NyU?jUL}OaX~`6mjzrSI!#S{Io0QSj=X2XFp9s;i z;Z~)^4}L+tVhpOlHo%4r2(a}D1X>IQMpt7km>G}(3yeVqz)eewlC?9VS4N{Q5+-6P zMGWnqE=w&9KmggdW)Cfxognu)FsGtDa(7)|R3P0v(1pRqNsaup4J2Bq2$T>q07g`X zy;g(}144UfE-gl;2q0fll?omV@}!kCqB6MY(E2D!2wO$8b-^>HMHN3hKaJL+@F#)C z5z}G=4BicfiX0XE$OSTn#Rfyd((( z$Abg_Ql6+r;CwP5#v>17EikQG*MwwHu>sRJZDU+XYOfI$m|N8d0yYq4kCqrxUVz8! zY(RoJEfbq6X-{J!v>!-~w2%PkLL$}+%)F@qZ~~*MkqyTn{Qv~Efy9;?1X3>{napU! zpi0+4-XMBOz&JRSASH<+DX9w;V_F_Ln*?dcP+&;J zFjNdnSEhDT$eWHsRi1N|C>}!G;?&ZZ0^UgDRMJaDBa>ik0C)lmaQ1{Gtr1?*x-baH zJ)=kE>BK`C6&p$x0*?~|HVnB^0wd%GP!ec&kei6Hepdp!wP917qOYT*H%H%NK08^+VY z&#EeLF)F(?8khjGRW}+!CkDn^f(#NSY1KkS@>#s!GrVf+qGbwNGCl`n4y!_PZmyP@ zPur%&AvsmCqXY)8-Q6Zl#HW1%aN_87)AISibg2RZQ1le^$56h_%d@8o4541LP#1Fx ztOX!fz^&S(+j*P!MI?b8;~)XXCh3W)NqLaeq6*Vy>NQ%^$(!AhVdyqOCL#A}ELMz- z^Wr`Pyd!S}X6(VO5jhX?JR|`?MY0z8eDX}XyR|?vAl5#@O~D3ow@DcmXvfy4*O&oI zE}#yn#ghsHG8+5hBpMb!Dwf=+==!Xl*$_AyJ>^?eUoC0mxuQ!2fGB`ims=wwIm^id z1G<_^Z{LNq{b&@J%i%{!BLf$F^W@x~9dnVUqLHi2%hS>;kf8;SmP0>3 zNO`mlHd<~O05C~TWZX)O=@JWYCe2AdCon3OrX&NeoegYCMnDK4Zx%6_0|S7NG4?D0 z7^kD83l&jFy{ck(PhbZXMKE0sa*A58fjO#X5ojl>-N?uVO+iIh3&*3~jTuI-Mq^_3 zw63V!X<0Zey$&aMsK~f=#fBfO>DcB0l`2>P6=r}vsRJPI({m_~O@IV|v=Sb2ydklZ z_Ss?^AEsgbba{i(%QHVO)%j^4T;m6$Q0*j%)zY;OY3oA{NmX_pk|2WK00vylhnf~?lUOdktupBSbu z>w`vu0lBAqKlOadP}E3NEYRByAstg>Tb^43W(NKyex7C8vvv#g6trh1vDttlnHe7! zRsW!*4#`q2*uCbl=y_Cu8LMmgqXpY*BC$xX?b9ckVJJ!@(km~M%oR7O-rjoBF1b+ z!%89tLtfYwVEI+u)G!q?II@l!+N_86&g7 zu#c+?lANOp9=QxKGaLdymNy+gl1fRpzy`4v(s-0u;#Q+1QGvW09tnIrrWPZ&bGk6R zC2iYvp;E$T5bYWr20Vdfl-x27Iqf`i;sl_A!IN~fFcdL+x)Cx70|~~z{UAXr#_xs^ zAOzEn3YeV3KY&Jt0Se^%At3?4Pl!V^64fp*gT&p)s>wB)&B)7mVx}&WPt5$dwV;9# zcf>FxFl%f;o>kqtkm#bW8ozwD{T`SBV6t|iq$S;bCOiO;sF1rM@r3EG3v8aMglt0# zLKuOyki(XifQ{@4;3>M}mq#L#D_Xvb3=(XrhE+04ip|xp2)J*=wk|O(cvUt8k{Qz# zZAko>nZewTe>_y=F)+ZDgHuwV*Q8>c0=-ByiXAW#L{%Z1RMLS|LP$$snQqhJ1O_0- z5)u<>?1!YJQC?S;S`HW$F@U+cJ>|ui(TcV3hM7rsHUJPBfkS?@J5(U42qfV#AY>zC zD{|{%9=H#U6WDjmJUmgb&S1V>AEjPDE#w+K5js3e%4l&|LWQKY(1JwbmZwD*-YiO( zwWxZ+8{7ymF?a$aqzEHVi((ED3XsHLd|;WWgap8&ipN6m_c?%_n3XiPb1^V8)+k_A zi)U^bC<)xQIq!o~0cO&TO$%dv3o5!WsB)DMbx1Kz0MBOE=-{+c0f2AhNl0u24&|E^ z8&1nXg`_FOxdN7~qy<z^fSyve3sDPCW0wXqWuG~lf+*){x6Cp4p=UT+1 zx#bX;d9>`&>a&_fBQO~J*bu^-0)U@BV$2xYIRrfZcYHUmJ>$(X8u z90|kLF6kgKjuKZKa?;LYP?7mL1EeRN1V3Isb0w95;g;T7T zE4Qw;XBGVv8{AVnyk2rRJq;yeOuq-PwjtvvF(WC6Q-EJal~!L68)mLJWK2~TOEO@F zf*imI7?fnd5E=owBW4L$pacNSzd+KWu(eV!Bx%9Ox2x>snHeo&03MLQNXEvVBSzSO zVot~*m#ook#y*Ezo-Q+}j9}-OsXJiQ`!UE1tVKqPl{%ha@G{z&N99QZp{g!;febTk z&sz9rI~yi~ftPoK`Dz9c0}%BD#y|_dCoQl(S)y6(>p$!N|JU#cL1NCc7RFUBv*Lvj%F+h=ur(4OER3-HabZKz}NF-rYaXO7A$*LPanAK_I0Sv7Y zAxKu+YPMkz@`+rpDOy6{?1>$md^RZt%k*?R%-D}!c>${SK%ys36X}J7SIq`L)HOz> zom(+K8mW7#imgQ~P}0S=+oW1BP*fG5)vl4-s`kUYn6#=cgbW~L&&cjZmh^Oc0&AoN z)?CQ*gC843(hjhex{St{!>5=WX$&%8z%?S2N0^7EL&%Aw15cU(TJ|QRSH_)Z3WRZSo zVT?ri+#1%=n1MbbS|kqI7VCC2vMQN+CheRna!3*d^Q(|2EjDbrYotN~SjkQTsIr8) z9SX9cN|ZW)Mxp^^sCe>^%5o?=wB%d9MtK=d#K7445W4IZUWOK*Ff#UlSyj?R8@arl zz_dd*P8dm+@Zj&r(1n!DfObjJ$a$lix+ezhZJt#Oo{)~Db{_O9X{4yP0J#7jIW~r6 zxUwjCU8<-wdcvRrZZst=s`3DisyIA}b}JdJ+kSLq2BDLd7E?IX!Sr$-Ie?fS7(suv zDj`=AD2-4fd-8b14b`WFv?aGAqlFO9kxQy-l+mRM6En|_rDd2&Nc`cs0``CZQQrTp z4V*IYdBbK`ECFK^5Yp?w_{ljiZl!(+o{-GDIjPi6Bz5JD9OsrPgN--LnGrLu#ivKo zfOvp0XvBcoQH@6&b7L~kwkl46BsLCx%9xqd2&NG&^5i7(&=tt5LL#B)fcf*tB(UCK zI0Z29$i=D-7`$h9VD~1AbFz(GF>2vDIjhGB7pzBG^LOPant=4}c664DxzOXtY%=rh}39qzmkb zn~MP@7y*-b42#wtoMo8Ak4?rZ0Pwq={g427cB3Msg^H?OQItd|NlsOxZ77K|rmB~D zPo)~geG78P#dwg2;XU~=2uo;n$s;s{k+IPQlf)B7)s~vE+fZQm`=)iV0fs~)en}*P z3=_&jRWVoiJ;8vf;532^9{Ajvnh;trc{jo)jguKkgh-60ipSlakto6It%Ry?#IU>= zkH8J1S0gi)qt{wIA*pz}l^o6^0V|Ny&I619p#ib!RkDZNx*c6MX;J{@VT#@CX~aWL zJ_BWt2y8=_b}=gL8DoPqx4b!Eo`K_$tV@f(-f6KxNENe2F+3j3In=^H9^}c{p?xwS zz{V|1gUjer>P86QiBqG15UY|HaIz;>a_hxoX}~b5JaEMlT^TVIEfoFH0}e^6=}ZJr z5=LO{O8AlV38=_$Vgr&?WR!s8v=aa-0*XpFqirs1@>x=C5Ko7BY<)p3h9ZGYa!U|{ z0V5$QpoOQ8m>(@jB%9G5v|tcuXFy{=X)t*)q*#ClbAipY6M+Tl<;U%bDsPZ{(rp{0 zIrri4fbmAoW|)Zqm}gy4!3(fyK?1B@7Z2JqBhRoB5_h`=bC~H@2aFa{qi+Wjp}D|3 zNg$yP>Bi#;Z!_>M*$pN`$Tn0V5$3s{kXuPR+ZwTnjpHD6fRUg|I(}51jmal(EIE=! zzzL7mGcBSp$Pp^_%b{o%RPvD2iN@*nrR-T1ELVDQN@{nKh?56ZV`88#NQJJJ#>a2} z*Z{P9Quj2GTS-6;9w)bbR!)$hHHf1Tn;TNT5@}BiywQ~uV6X}&2E7_%0BC!StWlQ| z8}O=;N9*Z}$gs_lXM4~~f)htO!br%8%@UPr%tmNlYyg{GK0hS1pv{{-pyG{f3yIuE z0q|tTtx;8%Pw$o#=!%2{asWw4(~(vZAV+8%;QZ&o?TJ}Sze7)fCs!o2sG1I~JVmx) zln^2(YAqxr9Z#I@1`$AFaO2dj#eU?mxn&$1MxxZzV(MV+gl7g2n$9f=kVB0!JxMSC z>DJ4qy8&Uc!HM=(L{|*cqD;zw-p0r82}#0{q=Fow=g-d%tL}Dd5Ij69o&6uPS@ar{T6OrbVkSAIgDhRc= zI`{^!#S>e)09isP34o0~?J{nb8i5D={Mft}?ee_2k<%5xbK>@dgh{cXbYZ3vGnm69 zFqlTt0vQe^WoV%lq^l7XjK2_IlzIXvAo|vr=;Wm=RcBvOy5a zv;nsg4+7-!Ja}Tp35fxKgg;Z-nYVK*a_825oQwg(@Grv;X?axdJKf)neF7vn0l<)y zl(7d4L?s=T=wid;R&vYa901-;5rZmP0e~bn8xWYnv3w;@FkcFx45219wO+I^-KxM~ zed0D?1bDG36{BTX(4qhgrP0W!=*si#2>8@4uS1=D3H{@V-{<7vi+ffQ_aL7!Ow&@DaJ<5KQ2S zAEqatl7x_B@ygD$;X1xsC? zdaeXW#Cls1FbQm^ECGYAoM{fJR!# zJu#??F=N2fS|s(7cI3sT)2LmkBd=;4cU@vUNWkF1@Mljlb$L4hAc_`1#x2J0$g|gH zstS)EkASGZJyc;lDWY{WN>b5o1L87mEI%@|Yz!?zMY%r zuhHFTVld2}Fu)1bi%JVuV8~TD2XJRb20#Y6_I`>6F=<^g4Xa8b73*p=NL4$r?KWvG zVuWa&{;KIF!4Ett0Q|^n%o3CKBQ_m2fhjoA4(W(tkT|6A0|0w!6maE8YH`{p-AeG( zHNxN#La>;>9t{1R2X=`=E+a$sZfl8Jpl&2){79o5oBPrYm{^VAhD&0P049M zU7bk!^jg?J)eiZ=lNmM~Es_WcwS+t)wO`Q z@_>g!SseJS5J3qS?UUs2ZFBjqU<@jq=TfkZmMX7{Dy!ZuwyHeHs$1 z0?!p5oOMZBnx`Z-jTlH|z%up9080vd7nzQjRs1yyWVC>}J<-zjbPPzSefDt?^4t>I zj}`&jhSkEG3}Z6DL=EVcmjs~FCebqN*+Q%x5)9j4ho(@1K?tvkhgKg&NzzeO;#?JT zisZC&tD1^f3l%po7z3F^DAr;FZciBaK9f%)&zLTOr|DGf$4y!ik_vL7s5~V-@pHwb z7EWRi@JUG0-(wi`Qf%SyG8w}FsftaNG2@Hzuz{8wyjY1SlgJr}1gBn9c*x~e3vdjC zbn&1?Od{Qu9HDQDQ*YCW+13u(*OXBL(?u>zdb6qoi9b9Z0ecQPBY0{-rQ~Tk_V6Iw zmL!`IkD*vi&QOpJHc3_98nyx5A>Cp;B*YZi#*eLqh#A1Zl_WxIk(~e5PD`VzKqEE) zv3W@s=u+a%P;AF+%%rKp&BMpX2V}jz1v&XCVrUjEdhyHPAt$4x#ne_L!yb%-p>;JW z7yuO*8I4-pi9$ArZ_)1on1nnSIp*^Z4#uc8D>r# z9^{>1jgvi=kUIi+z4^zlQA`Eix?~(A8o^Yxh*d3LN!*8Hk1;JZ_FbBtLsf&|0TAFZ zEIbBPifz^1_+auRTBE1D7L5)?{O%6eI1L4We@iC_hsZooPBv>QsB8u6;LzEm>eFzz@Bz57@l2vRrz)j0xKo@{aYf-f>w`5wzH=QHH17p_0KtBvsV7(3!;}gQnynDKYwCmLv6@25g z7I;)RF(`?{YvEA4LsuRxMR5lDl##gGzkrRtm)J48MpYv?teQ^1Oty(q)Fn@`g^&Xv z-HgU*;gHxE@Cj4}?G`^#Nl2o2G}@=9lG|5!vm^0(ThQ&z|8;{4|c_&E`wtz z0MH1Dfnj<6!I*KxdU>O(U8cptn?luY#W332eilatW}?1{k!#K2Kc$>*rw#F%R1DL}EHl+E}jSacbeY zm@CXM8SdL9YH6O3$m7S%fIP59%bsHC1W#q!~$JfoGiHkXt}WoP)Rxs!jinFUbJpjJuONMuPW&0gtkq(mCP$xqcJ6s zv{-(SQ-O(}2Qy|{7nmi>IPJt!fL2wDc7aKeprvliBwz&1oa6HUD5$i(&gr@?;eW5sXLsD3CV+DBLwLW z0vOMMB{7hLf%H@Y6JUs+t{|&yIHZx|#4m5OgtQ2ph6NC8C<9V=_h~Xih+C8PIgo3& zd1kOF!AlC@3tAM6K#~fHs$7|iB|`l&HKKB0#H1bLs5))WfZE$F24)DgnUY{=9pg9x zgfTGmyXF^sT1xan$%h9W3QqOu*mm$=20NFMjT6vsqqO?>c9Z7(ET0&ok zXoCRb6w5R3#@T4%oamBdKmdsxq2&n3rNw+-U*66+1P1es^opCvqykTV#LWV%ixW@$ zgji}hrjUn}bOgF+#IKQ@UX~;wacUIHJA!Dj-diUKf15J?%%pJPaF1`U*0r3`mZ1CA3WX5RwivsB$|nHgXPQR4{AA zgOh(Sx4D`HLw_PL1B)T8g(8d=oSEHZ9;T&RbIP2m>0$dQ1PZwQVUlm zMFK$1(T*ROb1E>~yb>6^JchoJXJAix0G!N=86XGHA0>IC0W`8I!=xc+BQVb}2zkgY z02O!u_B?fYIs$knW^N(-WXK_HgAGZXMvw#+7#5W-Ednjb%?k$Oe<@Vn6TjXZ%3#wD zX_lP0;UVoLFqjxjo_|ONEJ2r2pP(dlfmH-X4yKD4Fq{nn!-H+`jL=b%!PadLNU9pm z2r@iq_v8v5z%dBsL_MQLh}@G6mfSt@z@ze%XUx+PU{FPi%`qA+dH!dE0bPc22*E@4 zV}MabWd!O13<9*ks@T}*sh5y;gTPQZ%J&~GBpm?&5{-h6aUeOz3{OZv9Uik7Ox_a` ztlx$fBy?rKaPpZAB~M*!0IT8@*UK<+05DVIw6_@i6(kAiZV%c4TE09Uw1l|QBJT(c ziv++iBO3wSsQAn(WG3rH&JsU3EnK5K22U}05e&R`Y!0NGTQGSf4!lPEGAw0=F(sb)v_>klij}}*kn8Hl=9YmpX)z34$h zhSdm`Pg;6C9Uf{dqYJpCzqj!NBbQ+SiNMGjQ6;TW>B!!X0a(U#lC3cY`8K9Yo~WQt zjL?jdZb#mow}QY%gLFJm0T|PsWyFwlsgPDRh*&i(Yiu-6N#1}7!SrRd^a&bJERRZ~ zp;|jRuzoOo0vYSV)7QktiCA8X8(2$gk@v?Fvl3cUi2da+;764uJC6_#s~9AKF}I&q zVT8s{muDFR!UHSOCEBD^!Qkm@%+nnOYnfa`R5LHP5B-^&p#@zjM(K5y=!?}_tss$bip*F*$B$KIVp@Ptp6S3QqqDK6{s08v3 zKyL9=B?oCU0yb3jDiL)gm4N%=+C$1m1Y1R1wMq#_7m#2!k*@tBnH>?8&o|B&1&EGZ`BLkfuZ8 zCXGsqsz8ek6Jin)iC%snVN^*J0EiudjFz0ci8elI{0Xtt;Pyicrn~@&@j1629vBc{ z5YTnB06V~VXysF0My$Ogt8Amv4hewV6AW|vhZkT2khcxJv;edR*667!gPdW61!Cgv-g!DF^D zx_ttK0#lR$AS7lR3<+t+u)MW2d4UpgM>Wr4jq)+Lb54Q*RR#c#@Atoy;2a*a4KS7s z01|pt`B93q1)^1jgcm;t_pfcpGt7*VW$M)^=`Uga#a3qwjFSgT@PyImDImc^ z+e6zo)ks~eiibK;PrU+QgaBY{z-W|lsHCWcCEl!p3a}@`+Ucf_S>B#KoCwKjfy5IV9x&W=lGMvCTz&smYhOU4FS}|i{ z-uT2X)-F(OrFdYFP;o=L8EbZg$UXZ;l7u)!LIsbQc^I(_d)g&&iv2_h$!nL?i;$4R zn}x`jg*Q)}sFZlv&aoj!Vl&M5tvy-Ng?Ut3kWhg^1>cPB#wRC+!NaXUuP!%NMgvHq z*@5vh03*)^0IIsEkN_~L;~A|Cj3<5@FcCI07Gp!pBsM&%jwA++T;XJfVkCGxwUahf zf8l`jxlzg6L&HK+LF);Ow!ukgAqpVKznd{a81M)(=@tWHaANRuOvERE7={NVdU5(3 zo}R(coJ6jJ&k~4`9rU!6TICO3XiI4DAXh zOLSE^r%u%IY&j%o*#L6wZH9E@FpwBz5E{q4v@%SEF?q}|0?cWFwMl`DmOeKadtw;} zzb-dvL837DWxyoCsF2{T8$c$EYOAwnc!!)W0q1Q-tWjP-OPmk@!+82Mo|d2lgBFRk z0GrzJA;hWZ5=h2_nV&v4lLDMFj=h<;q#ItZo#aiUYBOS!cSx(UYRtZ)xZhhJ*Auh- zu%}DRe_9-3pwjl-Vi|#h8G|P$4kxNS$ZJ>Pq%Fx~HX|ou0C+KeNDLHPg^dZ!%b~oW zwE*Ks2oD2ayLmk&oif^G4CPQ~qwh;g7u#THQKhAkgs0`eYloq(%BsA>txU}Mk!cTH z`6x+qGj0Du`! zPHaN}V1x)6zyrWQyGmWG6dqF2ZDb^DU|vk3uME$sJSrJ5LT-vM9P08U4QZcIQCD*7 zRW)6!(2L z41g}W@~Dh}7K~v@F_Q^t(dZ92yd*G13}Ox`BGl+9sRe_(p*VMxS|)}J2#~Y{gz$U{ zP_gZaSqm68RG7JP2pP&UJ7NG-@>&{%grMz_WmjUeq@qsrI^8FSc0@J0I)KuR5-z=x>;0d*&AA5RLv;qX}3yjyY(vRg>ewtm?NoI zW|l~cwHP120l@@X-q-hc&q8G{tOP2DG!hi{o;>Z;Bd z7|;c*k!O}nj0z46Vk|kqLMQ5HcS`ZQ;WO7-3I4Pi&-Ge zE5=W&P0o39dE(J!IcR~AxbeGHbpg1kE8%n~IyjZ&H5!eI7FGdrWhZ!y&^Q+Bc#_k2 zOz(fnqYITC3VbyHCCH?kS_u-ZBv<5$EmLD30!H2wxja`4fH9`45?X}tg2pijKSX(e zadaV3)kV}(3(kzm2e4br_}e6Kv_{AY1N{OS)?z4mF=-`iGuAAeNJ0wOfTW9DQoxBk z6?dXa%s3Q*JrPpD0H(#!3#@8JL%~pYIDrhNk*#BM4G zdxGT_4@IkU;{nin`tPqM%gLLVRUUjb0cK_}IGpR!O9em)h6na^2{0p3(r9Tuq9hv{ z@yp0(fEnsyy&U4C0_kQ+kV#X^v}yscDi5mNoXBH?X&VA;PHy=jntv@kv!Pv5ucM1M zMy*mLTBGt)!ib&kc{nLfrgI zs>0-fE(Ru*2%%Nt2iVOv9y`awfa7U7@J=fgG%^XXMlgo)Gt(bBc~yi;_-Wx$s*AnzzBgi5x3azIEE_L3+d=3q=h#vcxuFk zT-+Dr%`CLobNgP9c}7B_&7cAx!GK1$r@$XNLKw8vD9=pCHtYcdfS4IiFsJ1pLN_JG!nvk}@1 zQ2}PX%-|Hm!{DTaP>XyS7% zm5>rnNj$!SOoQjY7T)AhQ4|a0iNa@wgahM4xGf)vxB>g|KHKU-I6?T$PX=FO3)x~F$TDLVW5e&4gtcxSwGpL5UooO5pZ-1|J=UvQhbGbgMt zVN_=N-=tCF-}~RcO;~mGq^vMurT>^P{$I}%COrA%lYjl|U!QsAnWvwA`hUme@y8#3 z>7|#{&p-eCV~;&X{=y3{(0T5;=R9iv?Qef$^^br2gVj?{J@v{fuTWNW5&rRye*~lP z_rL$0)nESdmnWWh!sClCzWD63&r$1&wu_i1zp;_7$vzR^e7}y z9%U!@0uwD9Af-u%xi0R2E5JRH)6~ujlO$XeOyidh4y%Uw{2|*IjqR4L96;^UXi{(T^^?@WRV4zxZKjX4^@#3$106%ce;N2+u#2F_rJgDs;hVr?U6?wDU8p4_OnMHee{3* ze>viaBR=)1PZ4U6ec}_JP=D-WAN$aUKBPYEu)~rcdg!67-~c)1m}9b{DG^cWI_|jR zKKHrLoqY1ilBGj()KN!$@{^zB(IXefAAfx2AN}Y@3B^MQife88o^r}5XPtGHhLpeh z)vsQB@x_;2cG=lypMB+(S6*_$OYJj=`GFMnZmL26>>tdn~3bHVG5jKee%^1>wCK7pR ztAXf+ua~|q_=XMwxmwH$N5#M|E5YQB6%(2iC<_3H@T60|c25`N>a~ zE?tT`=oc`mHzopWtjb8%4uVTAxdcl)eef-c=19Twga{mOV`&tveB>h^LB%*(yq9y`2J>A90ZYghnU}HTK5*&MDv;YEaD8kXdyQV%<@Q}E z2c@k`p;=w}jfvWr&@puIIfc+Hknmen2{~eGBq1|oRj3RFLr7wd0u-TMHBI3`(`6Dt z5sE}Zm#Elk%d4s9G#p(J$VozD&QOpZ^>V~SgbMHK#)LavNeF>6wB9uGtJYhnz~#%N zWZlou{j* z?svc2IfBqI4DWU>21A4R870#E<~P5I4ApM7s1l!ZXQyKfcSU(Il!Au%8)P-1j6^O% zASf5-<7b47%xfU(2>;Wc{I=zB0p%W%Jlc1hVQhhEK%(vZGH&Mob7HP!l0JH8n$z6bz06oR!fq&g3C$K&H&2HXXxaC}{Gc zK_9Zm(KZ`zG)@Vb+wU4|DFC6IFCf|!K#>bCFS(!tN3TKxqyS7YO@*99mnoKrqzDB7 zgsooq>V)#*MMyld(hv;H(17qIU;4TjT3Uo8ps0=fz4Q`gZN9jG#$yN#6j(z+$Pjp` zF-ZuS5G*+bYUCOo^Js`h1NX`+c~R4+MuAWm8iahwu~~z_N@_Hp4;Wy_F+ zz9I$sU>Vk?sffp;cw{GE*?}q^2x5SirofmJa+-JPrvM)~cWP(_tXB~L7v@V%O&5If zvV)_GP*>}UzDVFuW75oOh=ED2wJ8f#Fr(!=zFLS)`~X2|P_O~D^gwfkcB46hq5~ zceMcML)K=i!AkA$vV@#sjxCQSQFs<)-+$PGQY(6`H#Ldn;iQnP_Z=q)^0)S+@6EBZ zz?f(#_+Jg*(z5CfI_Mx0kpL8}L_{m3HlxQPw~FW}HeKuA5E&oYS^vgR!%#pVmq@!w zY#OqoP+Nl)ST5+Z;#V68Shj=|s3}G)d@&dSF@OTz(CKZ02-%8Ft<57Gg9d@7!nW{u zVMX2sr>0kz*n|YX!7OEwP-qfKiZiv6;$7hpW(opeMH4)X-giM5|O(K}aN=U4Xq?k#l>7rJGHG~0>#3OuxNln9mGh`w#MPOVQ2M9yhOgvJf zpsQ&Tm>3`^+T`?ULZA$=sMLfFod(-7Ew5GBzq}e3O!U#w~cILg2 zq}-W;jTOk)7~4n4h+V<$ZuQuh(3?V!B0<+k+tpp$DG+JX@q!==@lzI|P_eqyVt!CC z`C^Xby+n}k$Thj_$c3cfc|vI#C2}}@|M$HM|6_jqFKa@@!1fi$f$QF@_}qaBeGHHP z4I`Y_z3z2JQu>yMx>apvn|DKG=yVw|E5{l$xgHhcW_b{*TcwR5lU4|X)R?sVtbbjg zEL0;8*`r>C1dc~@;Zg0}0q9*4f$z89e!kD+^YQ1LU%K`qe{Hz{F`=#miqnK#?WAzJ zIrQ(p|9+o?kiT9yjuMBU0kIU_K&(TL-Uyrpos#jti;C52qu~d^s9{><#owmmRFJlY zv672gva%?6xkF$$+Rx*!ta^1xP{4+ZCO{w%05<-5HAt9H&{n81R2y&#jgm2J=~57e z!W}eqT&F?EPy>`MCTbHwZjK67R3zHNWbw`s!`wlW1uy)HK>$FC0f;WPG~q~!oFRM} z>D30#F9{ta*Yw2+!7#$q9*x>~+$eBLs2Dm~7Y3oiP&|Z`HNdJHUarZ7 zp`lj{40WkR%a&gXVg}McY0Fo5K={%iC-f)`5|2!HVGDsWD34$jF8Jb+e!=U~fM5VA z7ZNqf)Y!IcVCJ+?wT*x-R*LZCZmjeItY*$iR20;pIHD#(U6hM~FTL6f1t0}#ymO&# zC^Xb%$BKD*@j|}=;euQ|&;(P;a3~PM;a9-abTveJA(LP*mwB|`h2Ia6_}}ZceTV;j z^U>8?Kv!7=xap>w`jHn6;y^@-4ACZDMgF$1U2jvn#ra6qiX6s3IhmAhv}-%-1N0mTnQZU6uKTDf9C~>JHHP>jywww?K=d~yVZ#HeTURg zT>jOrAKPuWo%=s4JEB}fF17+|sc_x)ECp)q$^w=YTU~YvP1%u?&=J3Y|Fun_?FJ+l zY93=MhU5*M0TG5`NO{+QOpQB^fYa0^LUIZUM@EJrr^Ub*6Slk*UOU%-Ye1ktQ!hhS zrU{xyuxhqa5D$G_Tmvs2i7=!r63D!Rg4qbLr3n)PIRumFWX};yuJsb8BSN-J=u@T! zM=c`_65e^?P9)?EX=)Ja1)?E-ZIQzl6=f0)2<4?A81aidkCYjz75ZY(B{sSM;SEaKHhRCr{pChaIpSw#1nS9(bV7 zznIP4l^0h#ut>~CR-<~9=wj?vj6C~7PMJi(gf_LE9#?cHbQTEGK(&g5s*nIT4oE{( zzN;f!^ox_xuP_ikhDX<;axM@eYW%MYSBo|s4JNuA0Zh8gQ}alnHwBFC8ZSJo4vM+1 zUI&=a)LZVoHK@JRh=)03IeL_@-#$R8jezfFM$ZxH)N|xH88N@HMV?*+)JEI5Db7vY zs2It2z3W|*CQb7Gh2$}(K`_fkqqX7Dk|XhGSWK&xN$An!S}l4lHe=g{BC)QFp$4ng zDzuc_MC~ac)U;uebFH+eEFp&KR;C7W_Dhgx5xZl{39f+EWfEQN9S6M{kV`~cgp{>O zQgD2D3$XS&1)PQ!q=n(#$5q6+pvySC?QL({fB*fNh)RU5d69@s%=V5<#cbu<3k9G- zjYQTo1#eh%5mtmsM`=3`y>JwmqUFx^p+MQrGR__~8VYFqDm10s$Y)4mhq5Bn764l= zJd!ugzzwyUP=Q8A0Vqd>8exDm>4==12|&F9qYsdGfi>?L+BM1m6@F*{6^Vx_Na?_Y zLk$QtUCe=#vyuYWy42)^@e-%TiaE_u#N)r%1PGsN`r@Q-nl$vn*I=a$s&P(DIt59{ z79d!Lg!IKAJWi#S9}ZhkOr#@D5?%C#!IqGMdlx4E;0Hfwues;jXo{_;b_CpbbQ5Fyfe(B@gT7mt@wAKSBeyZwE$*~#$SBbjS_3_zXjJJ4P+($0 zj<5%okdYMdzk@*J#@sEpp%$}QXb^A$D;$f8UEm=jUUzs6bd8PO&+&6_DA=2j8VP+g zE_li@xdu5ChoCM=X>0Uaf&Wpx!dG{Gj?$5cqeLzqb>Vq!Uf6bUuqE*#z*fFax{A+e zvPSq$Mq`fCeA!SpT&}*(O(WnSK#7Lh_aB3AFwnl&9(Acn#IHsjBzA$wr65`Q6!c1yP#wJ(0li9NqHfp7 zlyW21P{@~wnh6AvK<0vNLLO-jkr-YG!sfTBq)(y0(Gn0Z4~onqUPla_&Spc+nOZFK}qWp`d0?zu^j&i7pXxL7`wY>MbgfP=HCtP^fbR z3O-EwFm-VX%?MCtMU9Xe9f>fcR>HbZhTP%vV^n_oIbNG^Vl8hR;~I$>Uv$JHergIe zU24Hl6QL0hoAJ@0$-CZ!#SfNT4NajCIM>aT$2{s1b~Q{+d#4i>K(&~0} zjzdcBV6LXVOJEO7tAT>tV{zK{+iyQ-&YU>bPJHJ(-|6!ab7+XjIQWE%WEiS_M?y{L z4n1N^MCx0RuOiM7G)RaT!P?`#7YWrT0w4&)1<#{24Uw$nB`FQlq}PiEWKm&SfEc*1 zH(Yq+1-Fy9)yuA{J{@eVjqP>eE4_{euL%m?rr<2lV8YzTA86R#$wlZzqeoqi6_Sn# zR^;j)$$Q6vDK^*s$RCKTNl>McM9#?H5rC@=ud(tHg!;TLb>np!qD`ddkN;oun%DS< zWF7XwM~yQL6h34Hzki7FpW`xj?%X~1+;gX$b}}#iK+7*RyYIfcGg`wZdR@MH9enV? zuF!H9j}z023DP#&hT8C2Cl0HS$cRv{HWT|-g7wn(sJDILk*#fF;X&4D$JI6+5XXpyJP#boc76vu$WJ&@Df&l0c z(kJ1u0O*K{f~+O0XsaoZOWC8T;ZzX}HLleH22Mc%5;!kh7cHwCiJZw0NeZBfka>WC zg9Rl3P{j{N%;ZwbBMBYgBBZGSghB(NP0j3NuE9_i@KY9E(Uuft5!R)lt3hVS$~bUW zj@pXZArW(l02c-cH0IDmVtfca%7Tdq$?1y$roaMJ2L-Ls2F{#Zkc8SyL;{K=rVXrjgBlyN>lI4*CP3KP18IzBKk z+Q9LZ}>>qJ)Z>C zqdN`c+&$l;ura?bx!`RyNZF%9LG6t2=nw&=9sgr^`Ns3)js|kQsR8$hhx6`4Blkui z9@%+Bv)(2Ue)n*LiSu2^#orNN*hx%;F?X&h7*Fj)_VH(xRaOxys&qOSYAIlEKfb@| zO>c4#JG)Um?#GG_0t}DvDfp)uyY03ciBsS`?|F~jz4zXG-+lMpXPkL* zfVYhdOec{=Wo=mG){LRHTC}Z0OUtmESqq6o!+NvoY$%#2U4q8ksboEyI0_CF0h=Zb zTuq`&@yMhpb~>bB!B`|Qe_2~F_+R?ojJo+-OUB1|ZoT!^l7iVXMhK!(THe-)EW{@G zVJnfyA|3mJp>zZm;&R4a5N%gs2=%(L&5~CAYp=HOHPLZIapm4p=y971Zb?Z6^$YxFLIc2 zOeX~6ASAFfISpBOgxtx68rSgkYUrZbNXpKmF0j-ZF=`qN=|C7pfM1#g| zMC(ytY~h222}-gInP?Z%IJ42aB*PG3dNklnBmy~Q?u3Dew%TedgkZb5Xd!%8FZV7N zY&T3FbNsmu`a4w@6hMz*!_8xC~a#D~Zq zLjojm7C2TE{0|2q^p!x68n{JxO_M}4xR47kK3^aZUsQyIfnCdyIReMtxCohdA@8dW zen!Z25S?C{7?qBO(*Xf`bPynUnsn6Io~GIn<1#N)y}~ABLID~>2$CW<4svlQczq}@ zHNL|69RYq0@H2|H0Oy^CFEeT{0!CQF6gUO2pU)sZ`#AeF3>yBQIB_C+N8>YR&NNMG zXRd!gZ)?V~G02vRbz=D$ zc?-;=&0_osDOi4%Pz$VM-Hce45xzEMUv(aQ@IehiYtN&GM{UQ1>64~p-Td*n1>h~f z35fPBKkLxKa}abCIv@;=!NmU#FHyA^1S~LUYKz~7DqyjZw0KCYbA^I!B1pA;#Fm0g zC1~}^h@_24Q<`+j7Zek1u>3-x36Qc{ZxTZ5R?!d%6PX%1wSfXiPI-7tvjMTBfeSSd zfaFCh7)3BJTmR7DGzBCcW#N%NFAU`c%A?eH@d%cSKq%7*0Wt)(^c6z-47JGx?@?eR z!iJ_H37^!&a-rsNG-u-wstl5#2E}@F2FiL;ye{{Yx*VYT9L7>LOBS#G3 zFoB~XBqrK)cu{C3Q8PD6p;7S2kPfH<@TfsSO&>l$Z3<3su)2eq-2`=JjPZjx_)^PABH=J%-q z8QQi0z0?a2zOl9U-RMvsuCt3>+iYjGR{)IUo{XI^h6^y}balkxuEAV*{m1z}9Uu+0 zwgzTs(3&Z2Lm>-^3!8~6u`s&KqhCh=e2-|&S%CCNFeDxKbBw1Z@6;ef3?xFX*3<;|bjJABnR+*^#OoKOM~vq-dg;aY9{ zojAO6>fmJor4H{)E$4)eQFs~z`Njx}art)CX5-DKscjXb&ewVaHhO*lZ9D!ZwID9fQD2 zP^=)(WMVY5rDJ1~q)-5GAh4yN7lN&=4H<%7F(@`59St#rtPKzpe2x5?3+XT?1Pe-S z%n9jaE*KyHxyyx+wD~GcP)rOva783)`h_GyP#%GUDt(4@wE5MJKuF+|c*KiLx9GjsuTQ2V~CNJsG!a#Ol=C;c3!= zNuQ>(Lrno7`sI!liGuK@27y%}3BUm-NA#=qPGn4sfJfwy))XXcc#SiPbKRxG)@^@- z4K`SH)m7J8YppG|*aG2$bsWgW*(6$Fh)TZF)aB1_giT(WvKB4Z&I0c*;;hZP(RTDW z%6T-uB9V)aLVn6|yKYst$BnsSge?!#YS~y_7GcYn#QL$!OcMl-Oq}!DV|RP{bX@W= zJbG6|qpkT?@9!neNltrEAYtpUqe z-IBBXEN^Y#qO#^&Br+0-uszc8C~SrlD0^%ZsVNZJ5!RI&X&8snSL>w=$HFrXpr|!O z{2CL=6sWZg+pP>iaTjAmL=5!;p|3DZrwEyFAxOdSLM?qRq9FP*vK=(I)(ciG9a-?D zW=a>I25Jm7q{9}DLC{N? zykXH>E)3z&q$UPo)08O%HGN%*XbY9TM0Dl4VP`7~-l>t(XAVjv3P3m%gh4?75TJn~ z49z1GYBH6bNEB@*jU;7;9s#O7ia~aiCBi8LP)sTU)1bgRg$9|>CZ()stBt3fYiD5| zB!iHVcK1^JkGi#8CUKF^&YNtqi9f^k;X;DQnK{O`!MQFe!Jsm?TsKee5w37P0XY#6 zKBB;rC=Rc>9-}6($dC&jvAv|20~8WAh#x!-$lR5YG-ZKLP#LcFeFh1mz9_mod`#X4 zf8@`vcN|~3Fy5?Lv)=KJcX&kLf}x`!cWr2h9j^)x0tqzNc*ulG+{u@D3vh9208fEk8r8(K0sF9tll{B}A^) zZoAPj1;z@jL!aCl!0qn#76$TfVOXo$2%k``&_VcK4LUV^1w!=@T>Gt-jsR^Zf>q(K zl`I1*%KhKpf0_wk!X9iW zm{X9o4Hz18LWKl^M~k-M(rA6!wwyAAqrp%qTCgPaIc2K=*GnQ7hG4U@!lBvV3>9eN z9~Qp=m_TFB5E^A%S_;~w!w>>Y!$l3qIF`{bJGuy|k+TwfGfNI!B#^}b*?>^fC_MV< zG*;9^B4%hbwX6A(DOma>+G>%AhatIi@~+Jmeh9#s&~Hu&8zK@qLL~uCjk&_3$n}x{ zr=wToWvwCXB5Z8+vJ#8{NF-|(NHpNk6b~KVLFr0D*`QG%7l6W@UifN{)xv`#7znZ; z!8e1zArR65>rq>T z9Jzz(l8D-tL^!?%`1KHFp>D6_?x-&3ww}A?YOAfb@x~kbQshvuwO#w|W%1a>Qfqfh z!6PW-hI!ol-EZw;`xnt+In;!ueU9*r0xOcZh0@^&CUY@k1_TO=sDH1q+-4@v2jR z4z@(344DWO;e+)ka)1s3E1jDRk&;o&afuHTAN&vzIK z+@XC3>l@$r#;H@MI$lw+`#jge(2LvEd|_!n>Qec9d2+f^U=g4l6SUhGH&trH;fW?OAef;t0+V4bg7Wjlnu1zRH zub)QJ_7n*yyFkMh0B$jRwEeAZE5FS`lTbXA6}{9fFujCQXeVk**3yzMTbdr_o4J&! zkx;XDG~j5rPeJKrWmy^)APR{vq{)_$N#SY716(knL!yf>a&;0(X~WS~B)m`nNDfoV zf)|De;i$AQT5a@8q z9F(gUqVN{rmq8aTyU!j({;1n+8Tq?5<1TD$80~la8fLrRD}aVbjGJCt*b#ta909n! z`x|1!kQfOoA}DQ_QZ(p5aV7T3&lW7ka0DO_T%mzmT-#vk6>ToGxzG-#bddRwHEGhM*S_|(>#VbmU$p(Gid3;Ep2xu5 z{F!4{(JI2OtwF(9;aHsPc1=x9E-4)4o=>5}f|^6Yw->CdNEvVYl)dSuoBBQ78KjN& z=|FJe(1)oA*sBVH6%S$;o3nss4Usr0^!iI4F)Pjp`I5_*T-^y}C>nVwAb%&3g87}i z;&FU1^s(JKG{}vxv9*RRGb3!Z5L$|soK>VIA-8zEqjBVbGxcV{J>NodC@5x)4tZNo z7a@to)0ZNDDnQs*KBs_#-aWw)fbFdo6RP1ukeb}PfDcyQk4$m9TRax9fW_Ia7f)+j z*y(Vm*hkW#*_JPU%C?4DE)b~MViYuJ%7Regg}jvp9I~xMPK|fH9I>(*J+cB$fk#5) z3BSFpAxJm|keq^HVbb1L_t**t0tzyH?Hq9pM9np2!BbFclhc=oct|v;iIxI){1SpH z0KUXeqCvOSnDNrsL3dU1{ihQAI2taT8Y6#>05xr~h3nW-Ba>@`mG-FOj zI@EFppN_6#78^_m+zEq_EgZ#A%hzZKfY2jbi_Ifpp?YLxC!!#KL%wLyB5dY&N&Jt> z(54G28p(RE9PCyI{h*2DLAifoxtJ3gI#Qz}9D)j1!FO+T zvM4a2A(@2IHwYrC%!%UW&!6wlX3)NG$aoc_Vr++k z;uXLr7l(r4Eb#3e&+NYY?sSka7DefJ9%(Cv0-zKeF5U||9r_UD+>r}8ObSd~xt#^% z;_+=C!((rkeE+KJ^{;=uNKkjDgD$q*vGs)tnQJRT#pgKIzq8X&kiVqpxU_papy?5T z>!L$K83>{D$ptI`!6^9O>C>B+jD`&>Fs|FhLZzTBY|0vH!MB#+(1+8mjMkV_ zBV=e0xEtuMEyfXqHWW23xXU$IfVz00#<18t8WVMxOtjfjfUm(&FdDX1HA7(-F112m zZBxTR5ex-mJn2Iqtc$$t^g;k74B2Ur=oOn7=!<~@rxX-cd_g0ZLJHafGa!0trc*G4 zU`54B0NSj?0N-PoLZb|dE!Wz*w0i^|RNjrDz@Rl0@U;n*+HiPbQWiiwvXvKDIusNN zBJl`KTZ6uuK4qGQLXdLtsGXI_sWqg!$bpN5P@5G6@i1uyS&>r^9xn<7(;;yn5J*gu zw!$@!pfrq$Ht=?qK>$udUpt{kRvd8wP|dGK^TlIpjF7(6S`QjxfFsVL)fNe-pi&kC zaDWg1$^zvI#;sdhY#O#C`ojO-0uTs-@`>}!w!Rdn*93Mbj6a^zZ9%B_=;2Q+PE^&`8nW)17 z;iNz#-Z)S(CPYdI+&c@h_0hmR-ycu9;rq0O9`PaXH1Q#NBvIt+7w{=lrmVjD>JukU z^j?6XoeR6{vP=Kc&6$WN=gpg^cAD(G^Uml9pf6^|$3@*odpCK$SZO#`oTQR!EQwaNM_nWy2`v~B<8P^m z!M7|^W&vP$vxe%;ml;u89=fnSs%L99%_#*Zg@(e4rWwTfYWIK3MzLW036O6-9ST;C zuS_P?6pO?fwyLc__$>*KibuP^hO&J$TJ2@g?yzhu&J?U7SunSKNSNCZ)-X*CIp(D; z22hugp-&n0*P+l-8b4^FI3OO8rkwfN#Cff6l4@oS5ruqgZJ0sH^2MW47Nw&G%LEjs^bI}*LP2uG&`9bc5f4p5kMOzU+77ih`J$=U zRU8{*Y5eBTK3$vFSYr)WS+;QK(}bXKM5x&2zT0qTcauxO9`|M5D}Xn5)S+c}!P*e~wIl{#G0>-Ys zt8BUDmTRxQ_WJ9ukI8ZJ+_`hHG=ineb$Cv(4yuGwRWooH=tw|0c`-2@a3W9Svs$zfKMMF8mte^vSC? zASa-#6Aqhi4^@8#DKMoh+IoCw!4MH2+3%-CcFe|ST6p%R(XdXGD#sz%j z-z$knBjEQPKSOI6c2mF+el}|@X-ii_v7D5)l67M6?I}}l5t?^x69gP5JGHUBC1>Is z0oIktLhMX*C3lXKfTdtsEe}%}`8x%i<39fTQy(+yFhcnF-$eWV;|OpfxJVd1_XqJ< zgf@{FEOowY2kTaZVza^p#-ku*Bz}(t&x9J~cD+ZodaYhNNJDHeDXY1lOjE6kqqd&5 z0C*vn9W-q?K**^vAtY&!@DhlSc(LE@FSeAKv!$THoZn$!Mb3&u{GhZM>ZL})iX$K# zky8_Sm`x(r9(74XfzOH>VG7Kdh^JA4BRpNjpuvU0uQ=f-5Ii!pwLR+Pg_V%N>Q!TM zP!xbmlRg~?;g>!`%G&fBK>8lVsbMq}u*R8!2Du>NXz7 z{BlP~0#=urtr}n%6^WX%nE8UE&PodODO1R!z;ua?N5K#ln~@P7HNAuk8xwMELUoHO zG_ZulERo_=3pH~wh*RNs&bB^(WUofODWNPfn3pGUkgtpiwO}X7DcdV7V)A}>4&K<7_W7fI2~b|T=qu!GNl*|p zj}YX=yzJBsj)0-5`3*;xka+JAa!UmQ9C<;e09o*AUL;beve2dvoJ4^P5SpksDm7N* z0D-5&7fdxFSYG4;2cX2+mai@dbjn&hBtS@-2!}@5y@yQ#v6@3UmOtv=Y_4LWF&Zx z%xUTcXrPAqrK49a02T3T^T>-XZB~R#AlR^KffW*cH@8fHpaoxM@ECHVW6#P|FCDv^ z-*7;Aq+pv1+fEm@(Qs{Z+xNl2cV<5aAb$snmjdtdwlfawbi(K38*Qfw^= zYYEkxI4i-+pk-kFSQ2WlhiVdP+H};u`4Czp_+K06TOlq9+Rl29_}_8h2q3gb{QakQ z4MB>~UTFPWdlsRPJZcNl?$E0tj5dp9FYK19bhPz~-(r>~9SC~KN!W71)+1$TYRGC` zYRRTd&HnYMhTstz1Up)3yD^Dm80r$I;G+dy9_>2~?uJPCHSGKjIKk57g>BP8O;if= z09kRtP)3x+Q$~7qkr;;p6Os`yVjC9+T)b zCe>Wa8m1yz9=U)+lRI5B#au{w4X-pwD8p1JuoVD9Xe6wt1*KP!l)wmeQLsI^i2w09 zxzV%x{Bnw!UF5jX%ZjpVIelB6mt0FnudNLYhr_q`ZCE^s^OgRt!#!WyZ=;R?#E1zg zs4=AP5-xkLf=&UKLtl+Z&?yE(hKNoBPhx4G>s`6sy`2Is+q>7=k4^>S zeZigI8_N9g|51)$_;TEM8{#Nm*x1P;Fc~Qx<;Wv8CPeRkf5GHJf=N!IXp7UMBEs0) z>EoE7EKWGu5P<7N^#W$%HwX%Ph3%{)G~g29+8YNRwyrFmVf|P+Z7#FfLbZxWJgTh$ z^X_HM;FwCQ1%hQ@N$8F9Sy6McI20@lH-GO1z1exKTefWU-#9hP4iPgBs_#F(_Bin! z1jt_-|7$xVOslj0iYuc$8usz8I&%~C|r7a`Kx|p}wNyr5NjYL}`D_w9rimEMX3?l)9mPvte!7rW0R?K3E zg6J!dgE*OO`wt6X7fH09SE6SWI0%Oi9 z9c?C}0$6II)vGXpNncwHjXS*zx#LU98ZZfg@C8jTLo>*Vgq(K;zH}(FqM$8z_}U1( z^c!=&ilHusQh>&znvMoT;Gp#K7=o_SC#1CqM|arm~UZx=onQ{Y_}p#bdbMjk8^!d?MvbC2C8 z-SKR2B#DZbELq|ez@KyYIlyV+LvD9wUsv4gd{;qqNXe_gMHgMhwi=-hlyH|B)X1rk)(!4 zfXH8sT0LsJE$d>1OFasb#3=*Ty_Xj*;&U9^cPC$*{8c{$>G)nDY|&E2V2%Jh?3T`j zq>!f~U=j`Z9KrEOC}y>2$)k4-2NOeQgrr1BUr5*<^B1#ofr+**`pza<3!WN7gfCf# zm-q!HD!!PQ+@{`aS}kU<)o5DDEilW^nzR5~=XA_IGG}WL9Qqa&WjGdqTY-bX*B+}& z!+ z0t2X5AwmB1wGE4WAs46GBg}e12|z|{6yl5 z8cnp6sgcm+T5N`hggaflXs9VDyu$$~BSO9?H-mcVb0iB;*J<{P+5de4805az>91U`6xh8DfxCT{fyx5`Igw)_O z5grMB2;{c58>2srLEwbx+tl_reF5~QU={=*7@SYdqb``t5k3Uw?sE3BFG83Z?VxKv zl1`sK-Cx2_o;(?C`n0Z)yE(hnyW69842HFw7g*HAUafen?F9j$VIhF%7xDT)gx(b& zVUQ$>bCl2#Lu7~;5vG?KAL_Yqz93cf;)ubJ$Xo;WQp5c5DhhRZW{yQ+;(u*?j{LQ; zv~$M~1Tc5qbytSi701fd*{8@?A%P4*w9xvJ178EZ0}%%D_edwdpinnH_wGaD5wQzi zU_j`LQ(;0*LcvjJC@3hr$V>1j8jkaV&>2)nMn)FQ>6kAI-uf})X5Z3Pd$eXOKND&; ztubn*Sg&-ZMS{j z{7Jm`Vg8%PAA(%|odtxBdq43w4y-x>Sh^a5Y5&?37MnOV2({H#vh^>8wuwYor6XAZ zD|+Rki@wKTOH*y9zz1R|r~#J|vTtnlLGLLj9 zG!!9FC=0d}c%e_Bk)acm22D8XMvNML0mxK3_`i7a%aOpKHNUzPOOAI48qC>BR*;cl z>E|V6wMT}mf)W5^w&F1k5EK$#=qQAWjJTi!LE(Z9O&}0TO&d751SzWpBY0}$03{_g z4fz&^NT8*oCg*}0OjacPmP;!Qrck-?sF!UX!L~#Rbx|W{TbxWh!Xzgl7b-7hN6x7t zS_&F;6yOX+OD;hf*|18pL(Z4@@wxl<3M;IDmR;ne03R}2j}7u*4%}vktN^5j5Zv+H z|AF}U?@NeFDSF428;{Fon{DP_7CQy}U^-{coGrK9@(piz!?bDB{G{tE3JyoenA67! z_f@=#@S*vFGgY(^30+A3;2>$OdWbx6m$LezZ87ysjSoKI-d-SYtwdIiwN{N4ZE z^IiMh^WE(cJ98iT6%zdKZh+psA!;}X$kjdyIS3pF;`gVsTv%g*6rsTEg_fegEMsd@ zkYd(Wi(k=YDYoDV+x|4km0{m8qyy9P*2_z~sg0nQrZ#u1q)7*Z5qe}ut~Uij5%MKS z?s!+AiAvF{i-dAG>5Q#MKep-x*p4tfmQNu0q7zJE(T$B6^u<%P!5r;Drffm~i-F7#cM$+RFt)ZVfah>2PG*y8mNq_dOYP zOt6vyC{E#fq)%DhC6GJ9_#f5Szlg{^)|KAf-rXC+`%hdVe}Aay_uP5&=8^c#bmq*N zYA^fF0w;p|Jf3uLpnX?v2Z4W9pnch8m!Vk%>$8%EBS7)(2xSF7@Wdk+ zVK|ulz$#kr2+k(3lsTp5WI^EY#Y2LAkuWrJF2tbiT_&dvi$X>9^s^EkHC^aeM#e{j zgbrnOG51X==2ug52063T-if5?AmCb>_}M%*l~$JtwKmM4C8S}Zt!*pE>N4ZxgeKY@ zo-Ze|FF96)8b9NF-}$W>)a`3e7kk9+6d=L*k$Jq@-wSmvyx0D)hIGgQ{#d}TWs12d`=bdG>>%w(OcH#LIF5Qv1O=DQbu|S;d=z7=C_7O zz{-dt0gKHeK(!QT3W+(7-q~G}{eO|T$NI4EmtVi%88pGuq%VCg-1kbKyzn$^Lkj+Q z8)5o3;tL5v?kkNyk9W`ak6~TE-JDStT0=(|70Xek<`i(X#>}V>@ggzwg<$86|f zs+$@KxsN<*%UJQ@PFbSvT{)V6wYg#}EZ^(<_wT3hgh zvbJt1lgM2yO(ujM=|HeMw8L*GD6q|iu`*(CNLW#`BLu+EBRNb8{Ib>7t7sF7khw=t zf@JFvj)v@bp(6%e4AnpcL+FtS9bRlSJ6h382Qnc93J^pthBl+9O4$g|WKIW~ILVEw z23rzVaN-|A;A}b7?lA=jaNz5uLqYu73P4T;F9tPDfaC>9jiIh|^s?2PITyM}#7Sb5 zbkXO6TurE zGj9wv2rClQC@EGZlsn35fXsyoN?U_6 zOzxTyZG~%jl^}cwY6HcKL>#r+g#;u`68h>K>D8s}I}fh&2?+CeuP4O+aB#awJOYP_ z|7kiP6kmaSIFf?Hg2I(oUWtV;xvMx3WaSh<`!4+cP6mJCE3AdnTz%bpDZ2opFE!16 z3-AF7TT(z}*cIRU^5b2=pBTCMyFhCD#N^iP>yQ({3xY0h3SH?rjrYJJZ7$S;L7|Ga zmk!4qK18-CF^Xn|oJr8Nz%&Gje*J3To4?o~AbGBxKHfw+fV7=QzWFMU>U-3{-6Y8ap|HB~^JfYMSUdRbK;)2AXhWyE0 z`_*1dq{+l6S!vdf6=E$}V`fv`sILw9iU|p)Ri&x*&_(`ykD@MVuC@ z9b|Qh#{w3ux<}DUL7SRlr;wB*p$#P|iKrp8gSvcDYws|D#+EXVnY1tHFqD3pwv1jI zfig5<=ps?H8#=)-@d&{fYOu{UHHL)J5s62D0}fbi3W6EBpv4UI^)?QIyhxUU zP|4W>@krC70Vy87AX8>;9LRg5!#qR13I=}Uofk7}BPLM4a^ zsk)`3DSS&p&3zTkcK`Q%2G!s?T#dl7qDwWJb=k)Jh}X{n4h6q5I}|X#-(KBIH5M;k z?578Zf?nU`u`y)suqdkZQh;VLGjRNm&pGvN+@+nS^8$0CP&7*}c&tm`CoCsLAC$aq zhyl1Gz`0|fNKm3LKaL)(1r3huT(%t_3cs=dD;=jEDh96bg69=&{7*+hV>doy#g-yjtnt~ANe~#1!ERu$JY3mYsIYD0#=$;WSz7WEdX+7y=7qD z$thTY*0AfkaWfHETHBW&)a|P8EN}#9ySk%&li~LZ(ORoQQp9Qhic?gCx~xMDnuNLpPef({6TsR7_G&2_!lZFTyp+Tc2JYB#Az}9q- zK%)tn0(XSKSxHte9OEFuKq%7`Ga)q~Oc*w&z?qbhL7=PvQ~>fqfjRF?L=Kj+1_^z( zVx!55M9quz2{Tj+0|M7P0->Xkrbir3-(H?FWr|;B?O)+B;kxhq-0=bj)w}lFYZ_hw zoCpHoPL~@dYDG2J4_zQDwC}4s%Eeb6)e0uWQMeredUO!rJvVbC2(k&Xc@yR_!Ai-WPBT=`i76Uec|UFTRJJ! zh}11wc;Xj;5B_oyr?(8u@9oAX3m1ODaL3#c;H*Je$ksKR3x+W9Fv?{pW#o^>)rww# z0?;cMZB9E5B5>nCho&RaxuA=!;K>OoGbGUfZsrKBM&CGNKA*rS8ZfS zT;|P-;~>rM|K!dElVt!=y+;>*I)t4aFv$s*E?sJ}@V`m4qJ(5kibtH*Hc87>V7fe3 z2t}?fX8Wb>DK9zFv>ha9zi8-fQ>fc_Y=P7CNJx!|M*{_cFqSZ4Tv*{o1DatW*9%a1 z8trj4c|qn~8x9=^8VrRec!tmftV`rD)o|#Lv(=>`YbJWNbpbCCLWXK;gfJD4IaPb) z5vH2Cz!(B)PNNiZC8so*8w#+5%t;IaAq0kmU`0+rQW^>zuyjc1Fd-C=P|0ZmR0xf& zUR{dZ^{N3D6-{kcA?xD0`4V2i5K^NrWoX(IbTJg0AjxG%4UWm;NJAGWa^aEC_gF^E zfop@R)(}92rpTGI)o3UHa;GgLa_MtQqL45Y9uorsoTMSukUL*$noPK3?opiN<-!XI zx!&3|b!AAXkvnSAX}D+>hFaT1b1DqTCFoIG4AR%u;6io~1S}rufMxE{==pU2s#m?r zR~Lf}K{`J8I0EP^nBPf(0o*E47QXcepI;3Wfss&+YbVl1V(#<)oX?jZ?1UKI$*~h= z!*Y5RWQ)O&;fTKu8vQ?&`%;1K+`TpYIzSmMI|VQUN=GjU2bnkv)HqG;6~H?J7E&;C zpJ=eO%Q=3=(tcLfmXupMZp7FM#zv1qMUQGl{798Yrcf#$0=jT)eY2)EWv~2!jH-U}*9vqY4E@mzqcB zHlQwE1h2sbiMD8&!yJ1XF+ZKSMS>!SKnGMqM~zi^X%p%a3`66?ge`nloYK^gl-R%; zO!|bn=sQb?GiTeJ#8 zA+gmq0-*S9`b0(IkyULqWoS_u5RVkN)77J1Xb=P`Jb-jWOTw4Z*b)j)Fm%L5p_rx2 zDG3F^7Z~sm_|*%67xgfGloe_wrI}(&!WIYQ0~eruj~eHyA`~yAxH22cBqT-h8u3^zj!lw@?`f+o7Y=_ z?e7~7=JZ9yI!b#77~thJ@9~ z5Z&Q4*KdsBBa9l=yXvDL#Dd%~Gfq}q*A)kZ+F5|W30)~`BWmF}P0)U|2HNCWkba5q zgN0u!{F?>*iPqpdVnD&^a})CKOwl;=%+b%&4wum2U~h+-XoW3Vxd>jPHcHo!tf5dO zOB35S6h?y(*{V^!T2v663rrXS*8s@G5D8eXHjvH;CUitDHB(^B>G*lVfQ#13ca&Qy zR+sf@X00eJYTm6n>m<1b9jnN?vc_D?jfsD_Vf+m{R#!CK|K0OZI;wX*HVgc6D&WF1 z)X1EgUNuen%so00NPsvF94H3aOMx@m9}EEL)*%LKRfNKJ7>URFS2W0_AS1gt8foXM zJxWA&x+rVVm!QJRHnE=NsBWi_FfkOe21rNN5ZZ>?K;JTznPRL8Msb_fVhG_5M5wX=1x2UBN3#U2}w1-Ola~h=CTlv!V4%dC>k^cz>1o#VOW?D z@&Z8{ns|igQLT$1e11ixmu5kdGPKYj~tK%(VbYkQWW5NHpN{!UY}p zhK>tGf)M1Z4U<0179lTLc?3vP7YTiBm<22~wz)2Mfx)MYi2MZXKac;w0}r_E`>O9F zj~{tGx_&zs{4u9q|Jme;6DK+Xyf19N`R4vJIJ!^-s(0bUiav(;Q&0aPP2YbaJzR(1 zaFcKJssT|~+I zu>fP^XSL%%(H5KQx`wN}_X2MvsJq)TaPfO*@e7E+ko*ld+yI9oCk}Hi+*%<3RFIYT zivWd+_R+6s#fgL^A~kPG3W?*O15OutBriM3>Jm2-sO*Fd?`tFn4Mg zPqU`ocOep*Ud>w1BC+x_c~JmT zuo)=~NrBat0)6RFh)H-6zc7k{5N5s1X_jU=@(zclF8HA63ycl~T>@4&ycEc3D#a-P z%CbwLU#*DhvfpR&@A< zBemiIPEM0V;;cNjT-8R;qcGGE^1_f8${LLxc`l5AHcbO442H$5L0K=rQsz-25+|`5 z6`I0RgAxW83J?*JQv<@O`xO_m@CD=5uYR>x02`bH|NCz{U`y`>c-}9${+P!O9si@Q zjS)T`!~C0UvWd^O6nyHzIoJTpF_}Gkwom#9$|Kf7u&7C`jruSZxq>;J3%JDrfzyx} z7VVL8BuPj!e)B=YHQxQ-_a6s=P~9rs9$hREh}ydVQo>Fcn}XK_mw#&X@ekha`w#v{ zHK<$R$XVdmUVkI%rGqaZ_@BP>1-_3zK!W0$E%J9Jy8NS7S(hD>M|5hrpmZk}C~fID z5y+9hWYr#xfJQXV3Rg4fbV%RO847PD9S0NuVp@+CZRQFWhLJDkyx@ORDJlsH430OT zCfb^@@T?`%Z8cggUd?0dg_c_Rog1IoPNnwZz`8$K$ zMwDNU|6hhJOFutZx>masYyp}Uv-MBt(ejp&B7Va5qb`M-T?tm!+H`DZkH!Ruw#{OL zkg(FWYaELl>0%-UUjhOoka*3&=ILu* z6JUZs;#mf4xG-P?wS%6u)Ei(IumGeg^%xn0Z46X_km$wq@8-WMR51vhJaOKc=Zna^ zE{Y057@Ug164zGgBFtz^zL-MuONBum$)|;hPzoySm#;8f(q#mbj7u`|f?$|}j7c7? zdSWhR)~KWT z78?#pi6i)u9ll#BW_Ivd&JHn4#H7y%7vx>CT%7XtY+GeSvl9TD%gf7t0&qC@*L_gV zk=TE?>r0NWBG3s`!W{Uo*&jcC?1MjS0rg#@BwE@7w9RJ!60eA%^3~&ds02CVG z7XyYz(|j5Ea`Up_kdgvC2CTZzv;Ofkr^PZsIY0$W{t-DO{g+I=dfjYc&*23)I5v-|5|APoP#N7xAbVVHf;s?&wS+G@Q5An>V+ zP49Rt1?<@d`~EuA=f|&KzwYhrc^mZ@Tk7X0eMHh@FIs0FifBpc2(6?A@DcT_d=r*k z9K!M?fRiHQE?K}2Bz1AZe4!08?6_1n_VpdQx>@CgUqz;_4T}w-Nq#!D@nowlKoKpmKPPwK|g`!JL0?C-U z#;-DRVb&mqPfI$W27$wo8O;m%9Px`OHb$tRMb!UImOLgPa5?vPfa*0o!E{_!X za!FD!R*6*smnAL*wAk1o&j<_Db4S-I6&&&|`67wKCYqwVK#OH5o5>sG1xW@+!3~$R zBtC8sgTgbjqYflF6*dkT(Q?N)m5g}5_FJ9~J#gRvRB?3l8QiJfkw90>D?VUcmwC4vktq+yhF)0F(ivfS#E!kVH5TYI$e|2|F9Jj2X(ofA~xw zWCJYi9l*`E7r*oRssJ~f?P0a^Im~zRcd&He_iX{N*a#fm9oj*SeF6R>*pUtx2SH#Q z{@Y^2>~EG}IBUS4qGM`XiG2jY5x@s=hEG7gR2vjokdBZbyxdDovJzoUn$TEAme2;M zmoH&34xGufFMx4{lDN95k{v`rFo}imj36-G5wlGJ__LPNmI(Jtv|DD_3&^-B?s5;U z2dRg-$Jj&SLGo~V@+~#{O~(O1^=y=$fkTH5fqfryyzBWyCP-e~6x)j3&|PrfeOOZm zj?kQ{u*%3wFeKI4kUK>$1)d*&>hBYxt;8P*h@7MX`fMn7CwyJtOo9OZKK=mzKmJ(! z<8nR$IsAJ&C{7QvhhLj`m<3;Rd88%fu{V3v5doSU6+zaR)G;l|T`#axFX$APyf@=h zVGyCk%xog`pmsy3q?rsAOp@#nW1l;Y)Etd3&OnJI4t0b85-u>8WD1))%V?5hgw3+m zdY>+)Ax<3j%S${|4AtOKS@;qd%Ywm=Nzo-40u@c1OsQluMF z6^4-F3xh{7@Iru1L$nlmA>0xI(~?UWvCo1_G{9Uf%!-PO@@1TBxe&H$=8G0# zaS|rUCMKGgfg`)b$dD8RK0910L7d9Bm6K;V1Cd>;S0~iT_Q+y!C6dCw$_6-Mmb7%DTnZDBa z^6z^B(DqX!8hm*>up=1o98C3U5Qa;tlfO%N1&3`|K4GETP1*(Q9bn&H;A@W}2$d|1p6>nN$ssJ4b~_!-sd1T zLw+udwgASJsETockc;pCe5sOWz=P*u@MOC0o)C`&8es{j!dt(_+B$8iBH_zj_7QJ3 zn>p>frxH|rt14p~HG7i+`>Fx$JsWD0N3**lDYuv@uZ?b*OQE7|2WYodj=mrE`$De< ze0i;Kn|$_hycZSlckDOpN1jjopFe-v7kmzqi$}fT8Sbq?k9d;J`mt8>*lR-^lDf%| zxW3R|_?U`-Q&VUzodp5tV(LfgE)lAyZfL_2MZ&z0x1^z1TQukNpJFMgFhs>T8KKg( z#H>ijGdS)AF){*UnKcBb?7L*gB~v_-5p&j9pv9><0rtso#{x%~mV_iP>iB3{EOVV% zgjEj!c{3S<7J2nS6Q;r-Cf8=(&DoTT0Ayj55j%u2Ma}|s$r?E9=K@WU>&jSOc`R`= zNn)7!B`+8;v!4q#*=K}sS-7;sBjGX?l{j7EBeFo)D&wqK5-nd=5!IFjrd$e-adg&r zRAEL`z^WxT$UqA+Q_ObD5G}Ec!}5r&V60}pyUBt&u*o|*Mz z27@k>VZq_BRoH@d@TO%mf@}aBm^=JCrTb9hbB&KSewqZ@-U5IL#0KNu3+xVdgtCBL z2%CZdBtes1%-0MwoF_)!o&x0@_3bIdyaKqi-Pj9kK>UhCJn&z@Fd2pmRZ{W=*Mdh# z*1)uc$x*Xa)sU1L2L6^5$11c3zX)0YpP~Uvsqmtzw9G0Tz^WDD3NDg9m$9it%4>&J zr5`_jxb^P4r_sag5i-L}_uWT+^>K@(BP7qj+S=OFX;gD{R$fJh@Jgy~2u1K7qzEeM zzFV~yxa7;=TFlU1JnpPG!G7_GR!H8tq@aw%cxdec9%e0I4lZQmsYZLe^-E1{)x)nr^i+>M742iLuQ63dy8(^bL9M9k zAYSSqlB~JZL=2Kdhg5V8F0`J8Ra-I{$4}8E%k`#JOzK<^&b~_)$g`{~Gg8CK$fba} zE9_LVCK&PaYnB3CTI5}(UP$=V88;hTQzWk@m}F!p0JPaE7dTuHLnpvHo2^QL9Z7|0 zAql_avqPPt8k*yNPzicgCd>MkcUi>HHw z@Lif&8^JJ6vBZT$pu!@AWK~|w(z41FJC@YMD!&NUI3-3=SpivLi77ac-Q? z=dcSXk%4{?z{#I59Dqpn2*3#HwK*{aUC?0%u@L|S5P>fSkmwTKCF4K?#Pe?VSbA+ee_BeZ;DF2kN~znBktG zGU`AJrb*3!+r7ES5e|zS2VcH&f#)KJ-Uzw#MW7EjoEp&HOjoT)B1obU?5KcBx@4IV z9MwSlQw7W#Y727N&s|~yP*tB$d`t2D#NS)`(B~CNUT!E%=ABIj*$4gzvAH$0Y=Zti zM-6R=c0=V=AL*;MT~#Sm4^0x^tAPi=tyCON#bZ_h!3f^w;?gbSQQe5Sj|LStQRKpI z>+$e-7xT-q$IL-O5o{=;^&c151-ub>HTeGhyY0Y>fbT;6lSERp>dTd9UPqff+db!I zPre@UsC(vXMoa`r9J5uIT9Txe(I$jL1AN^SlYsUyG*?}zF%6xFSx-@{=|GCUv&yD; z5H&t`jaThI4 zmzX%BB1wy|7}!L(j+HS%6M zWXgp~w*WzgmS|H{u97WZE?g=?mJ5a*(UPPqRIA+rGIHdL7uGCgt$?ZE^J|G65t3JY zVm%}-F{!i0DJ_nQoFbK~ZOP+AQmyh%k&2lZ8H6xIRRDyonz{#o&uiw)CXQ?V125!A-{v4_xk$X8PCxd z$EOV6$pJX1fn!hzdg$xg-)Yv{ffF4BBMI(+353z$KmaBXrVGa540TR@G6EnW15gF# zR&5;clS*g_193=#5wPZxJZml?39#bbJ|$Fy13btA-ChU6F0)O+>=ghXSjok~9;y&V z3s2<2c8*mF8;6|&;M)Sc0oe`=lHd;RsR^e-<<}6Q9OWhK5|lO6!bE^>^;7{%XamzF zEi!0B4P{4+46T;bM-9VpRSHN2Nm87|+D*(kQmbKvSbuT@KmFC#^XJdq3tIdFdD|Q< zGWKqe>%7gEB{x*{3{Y>oH2Zrh`=BzS<*rbM`glAPN*&xRkAy8)HM~K1dvfX4$VI^5 z-JJsOB4k{BR7sHTtRYzfBo))9Kmbj~?vSwhh|pdj3tx)-HG=is-uA=s z?(VJ~&0CDE%?pA{4d}7g2zpcJ>KARUl`wUjXWpeQ&>+#8$e=}%jP9^XhJen}Aa%4^ z$LS*+*32aJIGUK&wnUxBEa*Ej!-#lvA2Cr;Vc)1vg@M4wp-#(8p4n2+ssv~W3@5V$ zxZ_k(VzWx7yl{x|B|J;AbBSi3JmWZwu#c0jS^6vpl35`!6%1nHq=GLa@fZl|)e6BH zNl{f69)-j#CJuEoEjA6bxd<6Yi$`p13Xe_lB`7L+z~*KH1~8BKfYCBn5F4b418tB zEM_BqcGw?DiBq+4#w=x~s0f}qeja&W)b#IDfdB+-l|17vnc|lsd5*{n%qk;0J3E@! z3%FyyQ$3uAqF&7P8d%ovni>ptz|-UU8vgs{qnBY0?1EtW*CEZp56-}UM>uT`WCAW? z`W?bRKTT~we(E*?_yFPn2mt`jyBVkg7C1mLE!JRyGd3ngaLpqCVFzu=5UsG`46@pidYA zfLbDv0w6U2xX8tcuL8_MHo3c0DL8yj}pt8FK?vZ=pNl+P&N?EvT?lRi!j@z{r%^;&_@L$$$x|yWyg8(}M zQvtzy*!;b*lZlw&ypKHoA_&@hi-G)GTU-84$=iY7FA)Be)NdIY&Es90c-n&CyeYR7hkyn_>la#P4FV9%^$`_a1}HLKqp`pd4oSu_T{4BR6s$38Xh{|{`nnG8Ed09B~?yc>iAE3q-|5;#QQ7%sV{tH&9Uh*dUmI1R^KS``LKw3t~IPoQdv z5xO{*xTeJ^VeV)#r2?3=FgZ0dUOfr0DQoKNkgN`bDdNkBwJF%Y_LM3tPBK+8P9DX; z0-8W&p)L{@cq|t(;-RIyJQlW@7Gdflp+W}H9YK?%A|7JJEJ88hb41>%B^+ka0zjw_ z4zui#;RVfzLP;5i49O7^j!+pSUl>*iSLdu%8GNA%ObtmEHk;DSBLVT`g_!$g@XiP? z_y)^-@kqT}EFG8RaR~Q;)TKY7@cWw9)|G(5$=^9pKf*&z?SQARZ3Nm}*TXu{Y9KVM z^Q1E*%+a3k1c+I3meaounZ6t|1(Yy`lYWH_+H_ z0ub1=>Hx`FI|YyUfCWB42~4J_pqUjsBW7{>$m7Hgz2O+tvKL4e>^s;y$^v$9*O4A} zi4!Wq8Ng1=i5C)DCFAG|^Bs555U;_w;>d+x(Fz9YDu_%a1;BxGm=BgKLin#5hRTpf z5SD@(nyHhN8a`bz;j_{)3%WCCz);3Lib+a7+{A$%tApl>Y6vWJqr^SrPouMph;@)v5PQ|fp zaq1?DgaBG<#H@-$$~YpQv={$QpvS=MR@xfqI#C#oC=v(qKKOWJwGn*$_|bp*0r15M z|Gf)%De!g3qh7~(;B~Ic`a%b3LoI-*<@F2vA4sApYB5U&UBX&Vm$^jiJ_0N=s4F!o z8J$k1)NX~}$9a>+Z7kO5Gzqu&|So`@7h!los{6b)@F9(_%c41>Jmpv@c!+bVhS zu+K6Xt8`sb=Z<$Wyja317y<|$DPp>084+YLUUp_2s~oW)HY0FjEsu=If*2yF?2ykm z!X+=XnB|lOapnkf#7|6`vWjXa#a~F5He*P zEm~sKF`0^XH^sn|*aXnMU{j8nMe}YZpa?}M00cf$hNu|qzTmsG#4Is3dxXqXa zcqhgSF_%3meZwci2#*9Tu`F^l3&gBejcDEruXJcR2zlE1%D7c`ftWgbwTEfYz#riG zVy_LI*#JTv-RnF7Uw3T;8XWFf(&M_dFSMGsBPHX zpkxIj)BxWsFPAn2(b^Y?87@iU6IKD22s3N&T)t{T7tJOyMu?#;kzoo`z^ba4sZ%Kc z$Npo-j5ZzbCY92}_`{_PH#LQXk4Hp|f~QXH#jPl%#He(JUcwATSko(DEn zQk~r+mL2<5Mk(y1pxa{*fXm43umQQz-6_E%+6BzQZhI2BdrHiu>?G?BDlhzZ=@r2F z-=Bvw9l>_sXDmIXFLa7$z9z*11+`VJiq;hMlvdJr8n)gs zQ>@W2iRn1~UatjcdYKGG_H~)TEDp0~Ek?x%%`~Gei3>7}U^2pf#_<_v3KJY*MuZ83 zYi;tJ%7VvGOHw#=1>~GDIGLs?x?uL z^tTh5*J;pS@;FmL7)|QH3?INdFx24nwJ+r04h{SrceS~Ggx=cF*;1>*e?1PR0k4Jy zIKCkH5!v}4U#An+<{>=Li^-z_3drm3z)kz>dUK#^n-GZs)qROFvqp;w!fL`I0o`!J zO93zeJAx!^7Pd>dND7EK!#mkJzRKN+SF!-jUI4yi$E z7y${&l)CMn9b1X|c%uA==Vn`s^S|GDef)9$7m^_DTs{Hq?CkghQQhHb*KS%sAL%B~ zyx!5^ddjS8w18H^)Eb&Xr|3$wW;9#UxtK1QGP6dJWx|@h-exB zF_<#Tl+46feB%-c8JAqkh?b}@Y2maK2aTYs2nOTSimSr!|nFxU}~z*HbfODwQV z44-{`TA8Jl>oR4QJYl6U3y(*NC2o~jlHE9xM#++t1^c|a^@>wTanMxUS;7=qW``KF zjEjUK4uXJHOuEG{25}PRg&kp-F@=p_lP|LrazPQn2$h6+AxYg5NtI*}!6QdSLOpAW zV^t&tNo<(y#<6CwCT4>?VKPD@EGqW7=Bq?VIy>oB(CsS${M5zzNMG9mU?u>Ba!^rU zH#*vBJ(AuLv?t7gJi53(1+z}+mNYWH)0(eCECVIjrGL@78ydva`;55MJ68I0T%%XI{ZgKBLxfGNAb_!*0j|gVnN=~UW?)hB+Gx_tSvy$nK12jC;% zyq6%DkB0wfvx3+Z)C9;|YG05$tya>)-Ju=KR<(wn)fcrP0ZXBThGj$tVVWU;f%Z;Kn4-amZbdLf z3*aNXh8W(yd35*>Qb=k{HgztVL4218Duu0Q10@`0^)G&2@Y$y*M@vRwBM4_CgL09w z2=NyoP6@KiuK*_1#oxZN%o@O{I3-Jk*-$75Vh4u|7g?jLL`p%7FRN%C8DyM`YG@WP z8J00g3eU(|sKu03X3di2%UnFUV+5y=*f+X0Jklar*z9+w*k{wKNX&$DVU@fX%z_sc zUtAbmn9GQOLK)SWI)h9p1&%>dri2m1m=zmSm}G=ecm$ZrGGBycfn#Q{jA#*Kp)By} zirHmhR2CeU>@!|vc(g>A*%9=Yq2HZ&ox}CDt_0)Zc8%kz2T!!Zzt11`vPrX|S&NjM`IAP*VX=Y=pLQ-Wbc80;6@v?2TldcYIghftkSUHW1p z3jpE}>vaHt0DtcSvQq@xgx?lH6MRz;OZXrW=&1vF8=~{SIjlo4+y25Ml!Lu6+$?Ot zsE^dl@{&{qf$cc(%@V%)Lv=JQ8A)HE7-{>EAQ_6}8B`XD%T&JjWSHfPO@t+4A!