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  }