Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
vector_field.test.cpp
Go to the documentation of this file.
2
6
7#include <gtest/gtest.h>
8#include <type_traits>
9
10namespace {
11
12using bb::fq;
13using bb::fr;
16
17// Build an array of 5 random field elements for a test case.
18std::array<fr, 5> random_five()
19{
21 for (size_t i = 0; i < 5; ++i) {
23 }
24 return out;
25}
26
27// Compare two 5-element field arrays modulo p (ignoring non-canonical limb
28// representations). Uses fr::operator== which does reduce_once internally.
29bool field_array_eq(const std::array<fr, 5>& a, const std::array<fr, 5>& b)
30{
31 for (size_t i = 0; i < 5; ++i) {
32 if (a[i] != b[i]) {
33 return false;
34 }
35 }
36 return true;
37}
38
39TEST(VectorFieldTest, RoundtripConstructionPreservesValues)
40{
41 auto input = random_five();
42 Vec v(input);
43 auto out = v.to_array();
44 EXPECT_TRUE(field_array_eq(input, out));
45}
46
47TEST(VectorFieldTest, AdditionMatchesScalarFieldAdd)
48{
49 for (int trial = 0; trial < 32; ++trial) {
50 auto a = random_five();
51 auto b = random_five();
52 std::array<fr, 5> expected;
53 for (size_t i = 0; i < 5; ++i) {
54 expected[i] = a[i] + b[i];
55 }
56 Vec va(a), vb(b);
57 auto got = (va + vb).to_array();
58 EXPECT_TRUE(field_array_eq(expected, got)) << "trial " << trial;
59 }
60}
61
62TEST(VectorFieldTest, SubtractionMatchesScalarFieldSub)
63{
64 for (int trial = 0; trial < 32; ++trial) {
65 auto a = random_five();
66 auto b = random_five();
67 std::array<fr, 5> expected;
68 for (size_t i = 0; i < 5; ++i) {
69 expected[i] = a[i] - b[i];
70 }
71 Vec va(a), vb(b);
72 auto got = (va - vb).to_array();
73 EXPECT_TRUE(field_array_eq(expected, got)) << "trial " << trial;
74 }
75}
76
77TEST(VectorFieldTest, MultiplicationMatchesScalarFieldMul)
78{
79 // 150 random trials — matches the correctness-harness requirement for the
80 // q1s1 kernel. See https://gist.github.com/AztecBot/b8e2e1d5c85d54e10fb34b48461361e0
81 for (int trial = 0; trial < 150; ++trial) {
82 auto a = random_five();
83 auto b = random_five();
84 std::array<fr, 5> expected;
85 for (size_t i = 0; i < 5; ++i) {
86 expected[i] = a[i] * b[i];
87 }
88 Vec va(a), vb(b);
89 auto got = (va * vb).to_array();
90 EXPECT_TRUE(field_array_eq(expected, got)) << "trial " << trial;
91 }
92}
93
94TEST(VectorFieldTest, EqualityDetectsMatchesAndMismatches)
95{
96 auto a = random_five();
97 Vec va(a);
98
99 // Same values — all 5 bits set.
100 Vec vb(a);
101 EXPECT_EQ(va.eq_mask(vb), 0b11111u);
102
103 // Flip lane 0: bit 0 clears.
104 auto a2 = a;
105 a2[0] = a2[0] + fr(1);
106 Vec vc(a2);
107 EXPECT_EQ(va.eq_mask(vc), 0b11110u);
108
109 // Flip lane 3: bit 3 clears.
110 auto a3 = a;
111 a3[3] = a3[3] + fr(1);
112 Vec vd(a3);
113 EXPECT_EQ(va.eq_mask(vd), 0b10111u);
114}
115
116TEST(VectorFieldTest, EqualityAcceptsAliasedCoarseRepresentations)
117{
118 // a and a+p are the same element mod p. VectorField's coarse-form eq
119 // (d==0 ∨ d==p) must recognise both as equal.
120 auto a = random_five();
121 std::array<fr, 5> a_plus_p;
122 constexpr fr p_as_field{ bb::Bn254FrParams::modulus_0,
126 for (size_t i = 0; i < 5; ++i) {
127 // We only have the low-level add that goes through the coarse-form
128 // path; using it here exercises the round-trip.
129 a_plus_p[i] = a[i] + p_as_field;
130 }
131
132 Vec va(a);
133 Vec vb(a_plus_p);
134 EXPECT_EQ(va.eq_mask(vb), 0b11111u);
135}
136
137TEST(VectorFieldTest, IsZeroDetectsZeroAndP)
138{
140 vals[0] = fr::zero();
141 vals[1] = fr::one();
142 vals[2] = fr::zero();
143 vals[3] = fr::random_element() + fr(1); // non-zero (almost certainly)
144 vals[4] = fr::zero();
145
146 Vec v(vals);
147 uint32_t iz = v.is_zero_mask();
148 // Lanes 0, 2, 4 should be zero; lanes 1, 3 non-zero.
149 EXPECT_EQ(iz & 1u, 1u);
150 EXPECT_EQ(iz & 2u, 0u);
151 EXPECT_EQ(iz & 4u, 4u);
152 EXPECT_EQ(iz & 8u, 0u);
153 EXPECT_EQ(iz & 16u, 16u);
154}
155
156TEST(VectorFieldTest, IsZeroAcceptsAliasedZero)
157{
158 // field p ≡ 0 mod p: should also be reported as zero.
159 constexpr fr p_as_field{ bb::Bn254FrParams::modulus_0,
163 std::array<fr, 5> vals{ fr::zero(), p_as_field, fr::zero(), p_as_field, fr::one() };
164 Vec v(vals);
165 uint32_t iz = v.is_zero_mask();
166 EXPECT_EQ(iz, 0b01111u);
167}
168
169TEST(VectorFieldTest, AddAssociativity)
170{
171 auto a = random_five();
172 auto b = random_five();
173 auto c = random_five();
174 Vec va(a), vb(b), vc(c);
175
176 auto ab_c = ((va + vb) + vc).to_array();
177 auto a_bc = (va + (vb + vc)).to_array();
178 EXPECT_TRUE(field_array_eq(ab_c, a_bc));
179}
180
181TEST(VectorFieldTest, SubToZeroIsZero)
182{
183 auto a = random_five();
184 Vec va(a);
185 auto diff = (va - va).to_array();
186 for (const auto& d : diff) {
187 EXPECT_TRUE(d.is_zero());
188 }
189}
190
191TEST(VectorFieldTest, MulByOneIsIdentity)
192{
193 auto a = random_five();
195 Vec va(a), v1(ones);
196 auto got = (va * v1).to_array();
197 EXPECT_TRUE(field_array_eq(a, got));
198}
199
200TEST(VectorFieldTest, DistributivityMulOverAdd)
201{
202 auto a = random_five();
203 auto b = random_five();
204 auto c = random_five();
205 Vec va(a), vb(b), vc(c);
206
207 auto lhs = (va * (vb + vc)).to_array(); // a * (b + c)
208 auto rhs_l = (va * vb).to_array(); // a * b
209 auto rhs_r = (va * vc).to_array(); // a * c
210 Vec vrl(rhs_l), vrr(rhs_r);
211 auto rhs = (vrl + vrr).to_array(); // a*b + a*c
212
213 EXPECT_TRUE(field_array_eq(lhs, rhs));
214}
215
216TEST(VectorFieldTest, MultiplicationCommutative)
217{
218 for (int trial = 0; trial < 32; ++trial) {
219 auto a = random_five();
220 auto b = random_five();
221 Vec va(a), vb(b);
222 auto ab = (va * vb).to_array();
223 auto ba = (vb * va).to_array();
224 EXPECT_TRUE(field_array_eq(ab, ba)) << "trial " << trial;
225 }
226}
227
228TEST(VectorFieldTest, MultiplicationAssociative)
229{
230 // (a * b) * c == a * (b * c). Guards against carry-chain asymmetry: the
231 // q1s1 kernel's left-vs-right operand paths cross the reduction at
232 // different points, so any reduction-induced skew would surface here.
233 for (int trial = 0; trial < 32; ++trial) {
234 auto a = random_five();
235 auto b = random_five();
236 auto c = random_five();
237 Vec va(a), vb(b), vc(c);
238
239 Vec ab_vec(std::array<fr, 5>{ (va * vb).to_array() });
240 auto lhs = (ab_vec * vc).to_array();
241
242 Vec bc_vec(std::array<fr, 5>{ (vb * vc).to_array() });
243 auto rhs = (va * bc_vec).to_array();
244
245 EXPECT_TRUE(field_array_eq(lhs, rhs)) << "trial " << trial;
246 }
247}
248
249TEST(VectorFieldTest, SquaringMatchesScalarMul)
250{
251 // v * v parity. Exercises the kernel's same-operand path, which is the
252 // primary use in batch_affine_double / batch_normalize's lambda^2 and
253 // (3x)*acc steps. Distinct from operand-shuffled mul tests because both
254 // inputs share the same SoA buffer.
255 for (int trial = 0; trial < 64; ++trial) {
256 auto a = random_five();
257 std::array<fr, 5> expected;
258 for (size_t i = 0; i < 5; ++i) {
259 expected[i] = a[i] * a[i];
260 }
261 Vec va(a);
262 auto got = (va * va).to_array();
263 EXPECT_TRUE(field_array_eq(expected, got)) << "trial " << trial;
264 }
265}
266
267TEST(VectorFieldTest, MultiplicationEdgeValues)
268{
269 // Multiplications by 0, 1, p-1, and small constants. random_element()
270 // almost never hits these boundary lanes, so the bulk parity test does
271 // not cover them.
272 const fr zero = fr::zero();
273 const fr one = fr::one();
274 const fr neg_one = -fr::one();
275 const fr two(2);
276 const fr small(7);
277
278 auto rnd = random_five();
279 std::array<fr, 5> mixed = { zero, one, neg_one, two, small };
280 Vec vr(rnd), vm(mixed);
281
282 std::array<fr, 5> expected;
283 for (size_t i = 0; i < 5; ++i) {
284 expected[i] = rnd[i] * mixed[i];
285 }
286 auto got = (vr * vm).to_array();
287 EXPECT_TRUE(field_array_eq(expected, got));
288
289 // Zero-vector multiplied by anything is zero.
290 Vec vz(std::array<fr, 5>{ zero, zero, zero, zero, zero });
291 auto zr = (vz * vr).to_array();
292 for (const auto& x : zr) {
293 EXPECT_TRUE(x.is_zero());
294 }
295
296 // (p-1) * (p-1) == 1 — exercises near-modulus reduction in every lane.
297 Vec vn(std::array<fr, 5>{ neg_one, neg_one, neg_one, neg_one, neg_one });
298 auto nn = (vn * vn).to_array();
299 for (const auto& x : nn) {
300 EXPECT_EQ(x, one);
301 }
302}
303
304TEST(VectorFieldTest, GatherScatterRoundTrip)
305{
307 for (size_t i = 0; i < 16; ++i) {
308 src[i] = fr::random_element();
309 }
310 std::array<size_t, 5> idx{ 3, 0, 7, 15, 9 };
311
312 Vec v = Vec::gather(src.data(), idx);
313
315 for (size_t i = 0; i < 16; ++i) {
316 dst[i] = fr::zero();
317 }
318 v.scatter(dst.data(), idx);
319
320 for (size_t L = 0; L < 5; ++L) {
321 EXPECT_EQ(dst[idx[L]], src[idx[L]]) << "lane " << L;
322 }
323}
324
325TEST(VectorFieldTest, LinearMemoryCtorAndStoreToRoundTrip)
326{
327 // VectorField(const Field*) + store_to over 5 contiguous Fr should be
328 // the identity: it's the AoS↔interleaved transpose, applied both ways.
329 // The SIMD-fast pack uses different shuffles than the scalar pack used
330 // by gather/scatter, so this test catches any bit-level errors in the
331 // shuffle-based path.
333 for (size_t i = 0; i < 5; ++i) {
334 src[i] = fr::random_element();
335 }
336 Vec v(src.data());
337 for (size_t L = 0; L < 5; ++L) {
338 EXPECT_EQ(v.get(L), src[L]) << "lane " << L;
339 }
341 for (size_t i = 0; i < 5; ++i) {
342 dst[i] = fr::zero();
343 }
344 v.store_to(dst.data());
345 for (size_t L = 0; L < 5; ++L) {
346 EXPECT_EQ(dst[L], src[L]) << "lane " << L;
347 }
348}
349
350TEST(VectorFieldTest, LinearMemoryCtorMatchesGatherForLinearIndices)
351{
352 // For consecutive indices, the linear-memory ctor and gather should
353 // produce bit-identical VectorFields. (gather goes through
354 // store_from_array's scalar pack; the linear-memory ctor goes through
355 // the SIMD-shuffle pack.)
357 for (size_t i = 0; i < 5; ++i) {
358 src[i] = fr::random_element();
359 }
360 Vec a = Vec::gather(src.data(), std::array<size_t, 5>{ 0, 1, 2, 3, 4 });
361 Vec b(src.data());
362 auto aa = a.to_array();
363 auto bb = b.to_array();
364 for (size_t L = 0; L < 5; ++L) {
365 EXPECT_EQ(aa[L], bb[L]) << "lane " << L;
366 }
367}
368
369TEST(VectorFieldTest, GatherLanesMatchArray)
370{
372 for (size_t i = 0; i < 16; ++i) {
373 src[i] = fr::random_element();
374 }
375 std::array<size_t, 5> idx{ 2, 5, 1, 8, 0 };
376
377 Vec v = Vec::gather(src.data(), idx);
378 for (size_t L = 0; L < 5; ++L) {
379 EXPECT_EQ(v.get(L), src[idx[L]]) << "lane " << L;
380 }
381}
382
383TEST(VectorFieldTest, MixedAddBroadcast)
384{
385 auto a = random_five();
387 Vec va(a);
388 Vec bcast(std::array<fr, 5>{ s, s, s, s, s });
389
390 {
391 auto lhs = (va + s).to_array();
392 auto rhs = (va + bcast).to_array();
393 EXPECT_TRUE(field_array_eq(lhs, rhs));
394 }
395 {
396 auto lhs = (s + va).to_array();
397 auto rhs = (bcast + va).to_array();
398 EXPECT_TRUE(field_array_eq(lhs, rhs));
399 }
400 {
401 auto lhs = (va - s).to_array();
402 auto rhs = (va - bcast).to_array();
403 EXPECT_TRUE(field_array_eq(lhs, rhs));
404 }
405 {
406 auto lhs = (s - va).to_array();
407 auto rhs = (bcast - va).to_array();
408 EXPECT_TRUE(field_array_eq(lhs, rhs));
409 }
410 {
411 auto lhs = (va * s).to_array();
412 auto rhs = (va * bcast).to_array();
413 EXPECT_TRUE(field_array_eq(lhs, rhs));
414 }
415 {
416 auto lhs = (s * va).to_array();
417 auto rhs = (bcast * va).to_array();
418 EXPECT_TRUE(field_array_eq(lhs, rhs));
419 }
420}
421
422TEST(VectorFieldTest, ScalarTypeAlias)
423{
425 SUCCEED();
426}
427
428TEST(VectorFieldTest, CoarseInputArithmeticMatchesScalar)
429{
430 // +/-/* must accept operands already in the lazy-reduced "coarse" form -- a
431 // value in [0, 2p), not yet conditionally reduced to [0, p) -- which a prior
432 // `+` leaves behind between polynomial passes. The other arithmetic tests
433 // feed only canonical [0, p) inputs from random_element(), so the
434 // lazy-reduction branches for coarse operands go unexercised. Here we build
435 // coarse operands (a + b lands in [0, 2p)) and feed them back into +/-/*,
436 // checking the result mod p against scalar fr arithmetic.
437 for (int trial = 0; trial < 150; ++trial) {
438 auto a = random_five();
439 auto b = random_five();
440 auto c = random_five();
441 auto d = random_five();
442
443 Vec coarse_l = Vec(a) + Vec(b); // lanes in [0, 2p)
444 Vec coarse_r = Vec(c) + Vec(d);
445
446 std::array<fr, 5> add_exp;
447 std::array<fr, 5> sub_exp;
448 std::array<fr, 5> mul_exp;
449 for (size_t i = 0; i < 5; ++i) {
450 fr l = a[i] + b[i];
451 fr r = c[i] + d[i];
452 add_exp[i] = l + r;
453 sub_exp[i] = l - r;
454 mul_exp[i] = l * r;
455 }
456
457 EXPECT_TRUE(field_array_eq(add_exp, (coarse_l + coarse_r).to_array())) << "add trial " << trial;
458 EXPECT_TRUE(field_array_eq(sub_exp, (coarse_l - coarse_r).to_array())) << "sub trial " << trial;
459 EXPECT_TRUE(field_array_eq(mul_exp, (coarse_l * coarse_r).to_array())) << "mul trial " << trial;
460 }
461
462 // Deterministic maximally-coarse operands: (p-1) + (p-1) = 2p-2, the largest
463 // value the coarse form holds, in every lane.
464 const fr neg_one = -fr::one();
465 std::array<fr, 5> maxes{ neg_one, neg_one, neg_one, neg_one, neg_one };
466 Vec max_coarse = Vec(maxes) + Vec(maxes); // 2p-2 per lane
467 const fr m = neg_one + neg_one; // (p-1)+(p-1) = p-2 mod p
468 std::array<fr, 5> add_exp;
469 std::array<fr, 5> sub_exp;
470 std::array<fr, 5> mul_exp;
471 for (size_t i = 0; i < 5; ++i) {
472 add_exp[i] = m + m;
473 sub_exp[i] = m - m;
474 mul_exp[i] = m * m;
475 }
476 EXPECT_TRUE(field_array_eq(add_exp, (max_coarse + max_coarse).to_array()));
477 EXPECT_TRUE(field_array_eq(sub_exp, (max_coarse - max_coarse).to_array()));
478 EXPECT_TRUE(field_array_eq(mul_exp, (max_coarse * max_coarse).to_array()));
479}
480
481TEST(VectorFieldTest, CoarseStoreReloadRoundTrip)
482{
483 // Between polynomial passes a coarse (unreduced, [0, 2p)) VectorField is stored
484 // to memory by one pass and reloaded by the next. The earlier round-trip tests
485 // stored only canonical values from random_element(); here we store a coarse
486 // value (a + b, left unreduced) and check that both the store_to/linear-memory
487 // ctor path (SIMD shuffle) and the scatter/gather path (scalar random access)
488 // reproduce it mod p.
489 for (int trial = 0; trial < 64; ++trial) {
490 auto a = random_five();
491 auto b = random_five();
492 Vec coarse = Vec(a) + Vec(b); // lanes in [0, 2p)
493 std::array<fr, 5> expected;
494 for (size_t i = 0; i < 5; ++i) {
495 expected[i] = a[i] + b[i];
496 }
497
498 // store_to -> linear-memory ctor reload (SIMD-shuffle transpose).
499 {
501 coarse.store_to(buf.data());
502 Vec reloaded(buf.data());
503 EXPECT_TRUE(field_array_eq(expected, reloaded.to_array())) << "store_to/ctor trial " << trial;
504 }
505 // scatter -> gather reload (scalar random-access path).
506 {
508 std::array<size_t, 5> idx{ 0, 1, 2, 3, 4 };
509 coarse.scatter(buf.data(), idx);
510 Vec reloaded = Vec::gather(buf.data(), idx);
511 EXPECT_TRUE(field_array_eq(expected, reloaded.to_array())) << "scatter/gather trial " << trial;
512 }
513 }
514
515 // Deterministic maximally-coarse value: (p-1) + (p-1) = 2p-2 in every lane.
516 const fr neg_one = -fr::one();
517 std::array<fr, 5> maxes{ neg_one, neg_one, neg_one, neg_one, neg_one };
518 Vec max_coarse = Vec(maxes) + Vec(maxes);
519 const fr m = neg_one + neg_one;
520 std::array<fr, 5> expected{ m, m, m, m, m };
522 max_coarse.store_to(buf.data());
523 Vec reloaded(buf.data());
524 EXPECT_TRUE(field_array_eq(expected, reloaded.to_array()));
525}
526
527// =====================================================================
528// VectorField<Bn254FqParams> coverage.
529//
530// MSM curve arithmetic operates on Fq, so VectorField needs an Fq instance
531// with its own kernel specialization (the WASM-SIMD operator* body resolves
532// R_INV_WASM / P_WASM against the surrounding class scope and so picks up
533// Fq's modulus when included inside the Fq specialization in
534// vector_field_wasm.cpp).
535//
536// These tests mirror the Fr suite for the operations exercised by
537// batch_affine_add_interleaved (construction, add, sub, mul, eq, is_zero,
538// distributivity). dot_product is not yet specialized for Fq and is not
539// tested here.
540// =====================================================================
541
542std::array<fq, 5> random_five_fq()
543{
545 for (size_t i = 0; i < 5; ++i) {
546 out[i] = fq::random_element();
547 }
548 return out;
549}
550
551bool field_array_eq_fq(const std::array<fq, 5>& a, const std::array<fq, 5>& b)
552{
553 for (size_t i = 0; i < 5; ++i) {
554 if (a[i] != b[i]) {
555 return false;
556 }
557 }
558 return true;
559}
560
561TEST(VectorFieldFqTest, RoundtripConstructionPreservesValues)
562{
563 auto input = random_five_fq();
564 VecFq v(input);
565 auto out = v.to_array();
566 EXPECT_TRUE(field_array_eq_fq(input, out));
567}
568
569TEST(VectorFieldFqTest, AdditionMatchesScalarFieldAdd)
570{
571 for (int trial = 0; trial < 32; ++trial) {
572 auto a = random_five_fq();
573 auto b = random_five_fq();
574 std::array<fq, 5> expected;
575 for (size_t i = 0; i < 5; ++i) {
576 expected[i] = a[i] + b[i];
577 }
578 VecFq va(a), vb(b);
579 auto got = (va + vb).to_array();
580 EXPECT_TRUE(field_array_eq_fq(expected, got)) << "trial " << trial;
581 }
582}
583
584TEST(VectorFieldFqTest, SubtractionMatchesScalarFieldSub)
585{
586 for (int trial = 0; trial < 32; ++trial) {
587 auto a = random_five_fq();
588 auto b = random_five_fq();
589 std::array<fq, 5> expected;
590 for (size_t i = 0; i < 5; ++i) {
591 expected[i] = a[i] - b[i];
592 }
593 VecFq va(a), vb(b);
594 auto got = (va - vb).to_array();
595 EXPECT_TRUE(field_array_eq_fq(expected, got)) << "trial " << trial;
596 }
597}
598
599TEST(VectorFieldFqTest, MultiplicationMatchesScalarFieldMul)
600{
601 // 150 random trials — matches the correctness-harness requirement for the
602 // q1s1 kernel that the Fr coverage uses. This is the test that exercises
603 // VectorField<Bn254FqParams>::operator* — the new Fq specialization.
604 for (int trial = 0; trial < 150; ++trial) {
605 auto a = random_five_fq();
606 auto b = random_five_fq();
607 std::array<fq, 5> expected;
608 for (size_t i = 0; i < 5; ++i) {
609 expected[i] = a[i] * b[i];
610 }
611 VecFq va(a), vb(b);
612 auto got = (va * vb).to_array();
613 EXPECT_TRUE(field_array_eq_fq(expected, got)) << "trial " << trial;
614 }
615}
616
617TEST(VectorFieldFqTest, EqualityDetectsMatchesAndMismatches)
618{
619 auto a = random_five_fq();
620 VecFq va(a);
621
622 VecFq vb(a);
623 EXPECT_EQ(va.eq_mask(vb), 0b11111u);
624
625 auto a_flipped = a;
626 a_flipped[0] = a[0] + fq::one();
627 VecFq vc(a_flipped);
628 EXPECT_EQ(va.eq_mask(vc), 0b11110u);
629}
630
631TEST(VectorFieldFqTest, IsZeroDetectsZeroAndP)
632{
633 std::array<fq, 5> zeros{};
634 for (auto& x : zeros) {
635 x = fq::zero();
636 }
637 VecFq v_zero(zeros);
638 EXPECT_EQ(v_zero.is_zero_mask(), 0b11111u);
639
640 auto non_zero = random_five_fq();
641 non_zero[0] = fq::one();
642 VecFq v_nz(non_zero);
643 EXPECT_EQ(v_nz.is_zero_mask(), 0u);
644}
645
646TEST(VectorFieldFqTest, DistributivityMulOverAdd)
647{
648 for (int trial = 0; trial < 32; ++trial) {
649 auto a = random_five_fq();
650 auto b = random_five_fq();
651 auto c = random_five_fq();
652 std::array<fq, 5> expected;
653 for (size_t i = 0; i < 5; ++i) {
654 expected[i] = a[i] * (b[i] + c[i]);
655 }
656 VecFq va(a), vb(b), vc(c);
657 auto got = (va * (vb + vc)).to_array();
658 EXPECT_TRUE(field_array_eq_fq(expected, got)) << "trial " << trial;
659 }
660}
661
662TEST(VectorFieldFqTest, MulByOneIsIdentity)
663{
664 auto a = random_five_fq();
666 for (auto& x : ones) {
667 x = fq::one();
668 }
669 VecFq va(a), v_one(ones);
670 auto got = (va * v_one).to_array();
671 EXPECT_TRUE(field_array_eq_fq(a, got));
672}
673
674TEST(VectorFieldFqTest, ScalarTypeAlias)
675{
677 SUCCEED();
678}
679
680// `Vec` as a drop-in for `field<Params>` in templated relations / Univariate<Vec, K>: the static
681// identities, scalar-broadcast ctors, sqr, and lane-wise invert.
682
683TEST(VectorFieldTest, OneIsAllOnes)
684{
685 auto a = Vec::one().to_array();
686 for (size_t k = 0; k < 5; ++k) {
687 EXPECT_EQ(a[k], fr::one()) << "lane " << k;
688 }
689}
690
691TEST(VectorFieldTest, ZeroIsAllZeros)
692{
693 auto a = Vec::zero().to_array();
694 for (size_t k = 0; k < 5; ++k) {
695 EXPECT_TRUE(a[k].is_zero()) << "lane " << k;
696 }
697}
698
699TEST(VectorFieldTest, ScalarBroadcastCtors)
700{
702 auto a = Vec(s).to_array();
703 for (size_t k = 0; k < 5; ++k) {
704 EXPECT_EQ(a[k], s) << "lane " << k;
705 }
706
707 auto b = Vec(-2).to_array();
708 fr neg2 = fr(-2);
709 for (size_t k = 0; k < 5; ++k) {
710 EXPECT_EQ(b[k], neg2) << "lane " << k;
711 }
712
713 auto c = Vec(uint64_t{ 42 }).to_array();
714 fr forty_two = fr(uint64_t{ 42 });
715 for (size_t k = 0; k < 5; ++k) {
716 EXPECT_EQ(c[k], forty_two) << "lane " << k;
717 }
718}
719
720TEST(VectorFieldTest, SqrMatchesSelfMul)
721{
722 for (int trial = 0; trial < 16; ++trial) {
723 auto a = random_five();
724 std::array<fr, 5> expected;
725 for (size_t k = 0; k < 5; ++k) {
726 expected[k] = a[k] * a[k];
727 }
728 auto got = Vec(a).sqr().to_array();
729 EXPECT_TRUE(field_array_eq(expected, got)) << "trial " << trial;
730 }
731}
732
733TEST(VectorFieldTest, InvertLanewise)
734{
735 auto a = random_five();
736 std::array<fr, 5> expected;
737 for (size_t k = 0; k < 5; ++k) {
738 expected[k] = a[k].invert();
739 }
740 auto got = Vec(a).invert().to_array();
741 EXPECT_TRUE(field_array_eq(expected, got));
742
743 // x * x.invert() == 1 lane-wise.
744 auto prod = (Vec(a) * Vec(a).invert()).to_array();
745 for (size_t k = 0; k < 5; ++k) {
746 EXPECT_EQ(prod[k], fr::one()) << "lane " << k;
747 }
748}
749
750// Contiguous/gather transposes for Fq — the MSM production path (g1 coordinates are Fq),
751// mirroring the Fr round-trips above.
752TEST(VectorFieldFqTest, LinearMemoryCtorAndStoreToRoundTrip)
753{
755 for (size_t i = 0; i < 5; ++i) {
756 src[i] = fq::random_element();
757 }
758 VecFq v(src.data());
759 for (size_t L = 0; L < 5; ++L) {
760 EXPECT_EQ(v.get(L), src[L]) << "lane " << L;
761 }
763 for (size_t i = 0; i < 5; ++i) {
764 dst[i] = fq::zero();
765 }
766 v.store_to(dst.data());
767 for (size_t L = 0; L < 5; ++L) {
768 EXPECT_EQ(dst[L], src[L]) << "lane " << L;
769 }
770}
771
772TEST(VectorFieldFqTest, LinearMemoryCtorMatchesGatherForLinearIndices)
773{
775 for (size_t i = 0; i < 5; ++i) {
776 src[i] = fq::random_element();
777 }
778 VecFq a = VecFq::gather(src.data(), std::array<size_t, 5>{ 0, 1, 2, 3, 4 });
779 VecFq b(src.data());
780 auto a_arr = a.to_array();
781 auto b_arr = b.to_array();
782 for (size_t L = 0; L < 5; ++L) {
783 EXPECT_EQ(a_arr[L], b_arr[L]) << "lane " << L;
784 }
785}
786
787} // namespace
TEST(acir_formal_proofs, uint_terms_add)
Tests 128-bit unsigned addition Verifies that the ACIR implementation of addition is correct Executio...
static constexpr uint64_t modulus_0
Definition fr.hpp:32
static constexpr uint64_t modulus_3
Definition fr.hpp:35
static constexpr uint64_t modulus_2
Definition fr.hpp:34
static constexpr uint64_t modulus_1
Definition fr.hpp:33
FF a
FF b
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
bb::VectorAffineElementPushSpan< BaseParams > lhs
bb::VectorAffineElementPushSpan< BaseParams > out
bb::VectorAffineElementPushSpan< BaseParams > rhs
static VectorField zero() noexcept
Field get(size_t i) const noexcept
std::array< Field, 5 > to_array() const noexcept
static VectorField gather(const Field *base, std::array< size_t, 5 > idx, size_t offset=0) noexcept
void store_to(Field *base) const noexcept
void scatter(Field *base, std::array< size_t, 5 > idx, size_t offset=0) const noexcept
static VectorField one() noexcept
static constexpr field one()
constexpr field invert() const noexcept
static field random_element(numeric::RNG *engine=nullptr) noexcept
BB_INLINE constexpr field sqr() const noexcept
static constexpr field zero()
bb::VectorField< bb::Bn254FrParams > Vec