github.com/consensys/gnark-crypto@v0.14.0/internal/generator/fft/template/tests/fft.go.tmpl (about) 1 import ( 2 "math/big" 3 "testing" 4 "strconv" 5 6 {{ template "import_fr" . }} 7 8 "github.com/leanovate/gopter" 9 "github.com/leanovate/gopter/prop" 10 "github.com/leanovate/gopter/gen" 11 12 "fmt" 13 14 ) 15 16 func TestFFT(t *testing.T) { 17 parameters := gopter.DefaultTestParameters() 18 parameters.MinSuccessfulTests = 5 19 properties := gopter.NewProperties(parameters) 20 21 for maxSize := 2; maxSize <= 1 << 10; maxSize <<= 1 { 22 23 domainWithPrecompute := NewDomain(uint64(maxSize)) 24 domainWithoutPrecompute := NewDomain(uint64(maxSize), WithoutPrecompute()) 25 26 27 for domainName, domain := range map[string]*Domain{ 28 "with precompute": domainWithPrecompute, 29 "without precompute": domainWithoutPrecompute, 30 } { 31 domainName := domainName 32 domain := domain 33 t.Logf("domain: %s", domainName) 34 properties.Property("DIF FFT should be consistent with dual basis", prop.ForAll( 35 36 // checks that a random evaluation of a dual function eval(gen**ithpower) is consistent with the FFT result 37 func(ithpower int) bool { 38 39 pol := make([]fr.Element, maxSize) 40 backupPol := make([]fr.Element, maxSize) 41 42 for i := 0; i < maxSize; i++ { 43 pol[i].SetRandom() 44 } 45 copy(backupPol, pol) 46 47 domain.FFT(pol, DIF) 48 BitReverse(pol) 49 50 sample := domain.Generator 51 sample.Exp(sample, big.NewInt(int64(ithpower))) 52 53 eval := evaluatePolynomial(backupPol, sample) 54 55 return eval.Equal(&pol[ithpower]) 56 57 }, 58 gen.IntRange(0, maxSize-1), 59 )) 60 61 62 63 properties.Property("DIF FFT on cosets should be consistent with dual basis", prop.ForAll( 64 65 // checks that a random evaluation of a dual function eval(gen**ithpower) is consistent with the FFT result 66 func(ithpower int) bool { 67 68 pol := make([]fr.Element, maxSize) 69 backupPol := make([]fr.Element, maxSize) 70 71 for i := 0; i < maxSize; i++ { 72 pol[i].SetRandom() 73 } 74 copy(backupPol, pol) 75 76 domain.FFT(pol, DIF, OnCoset()) 77 BitReverse(pol) 78 79 sample := domain.Generator 80 sample.Exp(sample, big.NewInt(int64(ithpower))). 81 Mul(&sample, &domain.FrMultiplicativeGen) 82 83 eval := evaluatePolynomial(backupPol, sample) 84 85 return eval.Equal(&pol[ithpower]) 86 87 }, 88 gen.IntRange(0, maxSize-1), 89 )) 90 91 properties.Property("DIT FFT should be consistent with dual basis", prop.ForAll( 92 93 // checks that a random evaluation of a dual function eval(gen**ithpower) is consistent with the FFT result 94 func(ithpower int) bool { 95 96 pol := make([]fr.Element, maxSize) 97 backupPol := make([]fr.Element, maxSize) 98 99 for i := 0; i < maxSize; i++ { 100 pol[i].SetRandom() 101 } 102 copy(backupPol, pol) 103 104 BitReverse(pol) 105 domain.FFT(pol, DIT) 106 107 sample := domain.Generator 108 sample.Exp(sample, big.NewInt(int64(ithpower))) 109 110 eval := evaluatePolynomial(backupPol, sample) 111 112 return eval.Equal(&pol[ithpower]) 113 114 }, 115 gen.IntRange(0, maxSize-1), 116 )) 117 118 properties.Property("bitReverse(DIF FFT(DIT FFT (bitReverse))))==id", prop.ForAll( 119 120 func() bool { 121 122 pol := make([]fr.Element, maxSize) 123 backupPol := make([]fr.Element, maxSize) 124 125 for i := 0; i < maxSize; i++ { 126 pol[i].SetRandom() 127 } 128 copy(backupPol, pol) 129 130 BitReverse(pol) 131 domain.FFT(pol, DIT) 132 domain.FFTInverse(pol, DIF) 133 BitReverse(pol) 134 135 check := true 136 for i := 0; i < len(pol); i++ { 137 check = check && pol[i].Equal(&backupPol[i]) 138 } 139 return check 140 }, 141 )) 142 143 for nbCosets := 2; nbCosets < 5; nbCosets++ { 144 properties.Property(fmt.Sprintf("bitReverse(DIF FFT(DIT FFT (bitReverse))))==id on %d cosets", nbCosets), prop.ForAll( 145 146 func() bool { 147 148 pol := make([]fr.Element, maxSize) 149 backupPol := make([]fr.Element, maxSize) 150 151 for i := 0; i < maxSize; i++ { 152 pol[i].SetRandom() 153 } 154 copy(backupPol, pol) 155 156 check := true 157 158 for i := 1; i <= nbCosets; i++ { 159 160 BitReverse(pol) 161 domain.FFT(pol, DIT, OnCoset()) 162 domain.FFTInverse(pol, DIF, OnCoset()) 163 BitReverse(pol) 164 165 for i := 0; i < len(pol); i++ { 166 check = check && pol[i].Equal(&backupPol[i]) 167 } 168 } 169 170 return check 171 }, 172 )) 173 } 174 175 properties.Property("DIT FFT(DIF FFT)==id", prop.ForAll( 176 177 func() bool { 178 179 pol := make([]fr.Element, maxSize) 180 backupPol := make([]fr.Element, maxSize) 181 182 for i := 0; i < maxSize; i++ { 183 pol[i].SetRandom() 184 } 185 copy(backupPol, pol) 186 187 domain.FFTInverse(pol, DIF) 188 domain.FFT(pol, DIT) 189 190 check := true 191 for i := 0; i < len(pol); i++ { 192 check = check && (pol[i] == backupPol[i]) 193 } 194 return check 195 }, 196 )) 197 198 199 properties.Property("DIT FFT(DIF FFT)==id on cosets", prop.ForAll( 200 201 func() bool { 202 203 pol := make([]fr.Element, maxSize) 204 backupPol := make([]fr.Element, maxSize) 205 206 for i := 0; i < maxSize; i++ { 207 pol[i].SetRandom() 208 } 209 copy(backupPol, pol) 210 211 domain.FFTInverse(pol, DIF, OnCoset()) 212 domain.FFT(pol, DIT, OnCoset()) 213 214 for i := 0; i < len(pol); i++ { 215 if !(pol[i].Equal(&backupPol[i])) { 216 return false 217 } 218 } 219 220 // compute with nbTasks == 1 221 domain.FFTInverse(pol, DIF, OnCoset(), WithNbTasks(1)) 222 domain.FFT(pol, DIT, OnCoset(), WithNbTasks(1)) 223 224 for i := 0; i < len(pol); i++ { 225 if !(pol[i].Equal(&backupPol[i])) { 226 return false 227 } 228 } 229 230 return true 231 }, 232 )) 233 } 234 properties.TestingRun(t, gopter.ConsoleReporter(false)) 235 } 236 237 } 238 239 // -------------------------------------------------------------------- 240 // benches 241 242 func BenchmarkFFT(b *testing.B) { 243 244 const maxSize = 1 << 20 245 246 pol := make([]fr.Element, maxSize) 247 pol[0].SetRandom() 248 for i := 1; i < maxSize; i++ { 249 pol[i] = pol[i-1] 250 } 251 252 for i := 8; i < 20; i++ { 253 sizeDomain := 1 << i 254 b.Run("fft 2**"+strconv.Itoa(i)+"bits", func(b *testing.B) { 255 domain := NewDomain(uint64(sizeDomain)) 256 b.ResetTimer() 257 for j := 0; j < b.N; j++ { 258 domain.FFT(pol[:sizeDomain], DIT) 259 } 260 }) 261 b.Run("fft 2**"+strconv.Itoa(i)+"bits (coset)", func(b *testing.B) { 262 domain := NewDomain(uint64(sizeDomain)) 263 b.ResetTimer() 264 for j := 0; j < b.N; j++ { 265 domain.FFT(pol[:sizeDomain], DIT, OnCoset()) 266 } 267 }) 268 } 269 270 } 271 272 func BenchmarkFFTDITCosetReference(b *testing.B) { 273 const maxSize = 1 << 20 274 275 pol := make([]fr.Element, maxSize) 276 pol[0].SetRandom() 277 for i := 1; i < maxSize; i++ { 278 pol[i] = pol[i-1] 279 } 280 281 domain := NewDomain(maxSize) 282 283 b.ResetTimer() 284 for j := 0; j < b.N; j++ { 285 domain.FFT(pol, DIT, OnCoset()) 286 } 287 } 288 289 func BenchmarkFFTDIFReference(b *testing.B) { 290 const maxSize = 1 << 20 291 292 pol := make([]fr.Element, maxSize) 293 pol[0].SetRandom() 294 for i := 1; i < maxSize; i++ { 295 pol[i] = pol[i-1] 296 } 297 298 domain := NewDomain(maxSize) 299 300 b.ResetTimer() 301 for j := 0; j < b.N; j++ { 302 domain.FFT(pol, DIF) 303 } 304 } 305 306 func evaluatePolynomial(pol []fr.Element, val fr.Element) fr.Element { 307 var acc, res, tmp fr.Element 308 res.Set(&pol[0]) 309 acc.Set(&val) 310 for i := 1; i < len(pol); i++ { 311 tmp.Mul(&acc, &pol[i]) 312 res.Add(&res, &tmp) 313 acc.Mul(&acc, &val) 314 } 315 return res 316 }