Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enhance API with Printf functionality and examples #1375

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions examples/printf_example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package examples

import "github.com/consensys/gnark/frontend"

func PrintfExample(api frontend.API) {
x := api.Add(10, 20)
y := api.Mul(x, 2)

// Basic usage
api.Printf("x = %v, y = %v\n", x, y)

// Different formats for constants and variables
api.Printf("dec: %d hex: %x\n", x, x)

// Debugging information
api.Printf("coeff: %c var: %i\n", x, y)
}
137 changes: 121 additions & 16 deletions frontend/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
package frontend

import (
"fmt"
"math/big"
"strings"

"github.com/consensys/gnark/constraint/solver"
)
Expand All @@ -17,16 +19,16 @@ type API interface {
// Add returns res = i1+i2+...in
Add(i1, i2 Variable, in ...Variable) Variable

// MulAcc sets and return a = a + (b*c).
// MulAcc sets and returns a = a + (b*c).
//
// ! The method may mutate a without allocating a new result. If the input
// is used elsewhere, then first initialize new variable, for example by
// is used elsewhere, then first initialize a new variable, for example by
// doing:
//
// acopy := api.Mul(a, 1)
// acopy = api.MulAcc(acopy, b, c)
//
// ! But it may not modify a, always use MulAcc(...) result for correctness.
// ! But it may not modify a; always use MulAcc(...) result for correctness.
MulAcc(a, b, c Variable) Variable

// Neg returns -i
Expand Down Expand Up @@ -55,47 +57,47 @@ type API interface {

// ToBinary unpacks a Variable in binary,
// n is the number of bits to select (starting from lsb)
// n default value is fr.Bits the number of bits needed to represent a field element
// n default value is fr.Bits, the number of bits needed to represent a field element
//
// The result in little endian (first bit= lsb)
// The result is in little endian (first bit = lsb)
ToBinary(i1 Variable, n ...int) []Variable

// FromBinary packs b, seen as a fr.Element in little endian
// This function constrain the bits b... to be boolean (0 or 1)
// This function constrains the bits b... to be boolean (0 or 1)
FromBinary(b ...Variable) Variable

// Xor returns a ^ b
// This function constrain a and b to be boolean (0 or 1)
// This function constrains a and b to be boolean (0 or 1)
Xor(a, b Variable) Variable

// Or returns a | b
// This function constrain a and b to be boolean (0 or 1)
// This function constrains a and b to be boolean (0 or 1)
Or(a, b Variable) Variable

// And returns a & b
// This function constrain a and b to be boolean (0 or 1)
// This function constrains a and b to be boolean (0 or 1)
And(a, b Variable) Variable

// ---------------------------------------------------------------------------------------------
// Conditionals

// Select if b is true, yields i1 else yields i2
// This function constrain b to be boolean (0 or 1)
// Select yields i1 if b is true, else yields i2
// This function constrains b to be boolean (0 or 1)
Select(b Variable, i1, i2 Variable) Variable

// Lookup2 performs a 2-bit lookup between i1, i2, i3, i4 based on bits b0
// and b1. Returns i0 if b0=b1=0, i1 if b0=1 and b1=0, i2 if b0=0 and b1=1
// and b1. Returns i0 if b0=b1=0, i1 if b0=1 and b1=0, i2 if b0=0 and b1=1,
// and i3 if b0=b1=1.
// This function constrain b0 and b1 to be boolean (0 or 1)
// This function constrains b0 and b1 to be boolean (0 or 1)
Lookup2(b0, b1 Variable, i0, i1, i2, i3 Variable) Variable

// IsZero returns 1 if a is zero, 0 otherwise
IsZero(i1 Variable) Variable

// Cmp returns:
// * 1 if i1>i2,
// * 0 if i1=i2,
// * -1 if i1<i2.
// * 1 if i1 > i2,
// * 0 if i1 == i2,
// * -1 if i1 < i2.
//
// If the absolute difference between the variables i1 and i2 is known, then
// it is more efficient to use the bounded methods in package
Expand Down Expand Up @@ -140,6 +142,15 @@ type API interface {
// ConstantValue is a shortcut to api.Compiler().ConstantValue()
// Deprecated: use api.Compiler().ConstantValue() instead
ConstantValue(v Variable) (*big.Int, bool)

// Printf formats and outputs values according to the format string.
// Supported format specifiers:
// %d - output as an integer
// %x - output in hexadecimal format
// %v - standard value output
// %c - output coefficient ID
// %i - output variable ID
Printf(format string, args ...Variable)
}

// BatchInverter returns a slice of variables containing the inverse of each element in i1
Expand All @@ -157,3 +168,97 @@ type PlonkAPI interface {
// AddPlonkConstraint asserts qL.a + qR.b + qM.ab + qO.o + qC
AddPlonkConstraint(a, b, o Variable, qL, qR, qO, qM, qC int)
}

// Helper functions
func (api *API) toString(v Variable, base int) string {
// Convert Variable to a string with the specified base
// ...
}

func (api *API) getCoeffID(v Variable) int {
// Get the coefficient ID
// ...
}

func (api *API) getVarID(v Variable) int {
// Get the variable ID
// ...
}

func (api *API) Printf(format string, args ...Variable) {
if api == nil {
return
}

var result strings.Builder

argIndex := 0
for i := 0; i < len(format); i++ {
if format[i] != '%' {
result.WriteByte(format[i])
continue
}

if i+1 >= len(format) {
break
}

i++

if argIndex >= len(args) {
continue
}

v := args[argIndex]
switch format[i] {
case 'd':
if val, isConst := api.Compiler().ConstantValue(v); isConst {
if val == nil {
result.WriteString("<error>")
continue
}
result.WriteString(val.String())
} else {
result.WriteString("<var>")
}
case 'x':
if val, isConst := api.Compiler().ConstantValue(v); isConst {
if val == nil {
result.WriteString("<error>")
continue
}
result.WriteString(fmt.Sprintf("%x", val))
} else {
result.WriteString("<var>")
}
case 'v':
if val, isConst := api.Compiler().ConstantValue(v); isConst {
if val == nil {
result.WriteString("<error>")
continue
}
result.WriteString(val.String())
} else {
result.WriteString("<var>")
}
case 'c':
result.WriteString(fmt.Sprintf("coeff_%d", api.Compiler().GetCoefficient(v)))
case 'i':
result.WriteString(fmt.Sprintf("var_%d", api.Compiler().GetTermIndex(v)))
}

argIndex++
}

api.Println(result.String())
}

type Compiler interface {
// Existing methods...

// GetCoefficient returns the coefficient ID for the variable
GetCoefficient(v Variable) int

// GetTermIndex returns the term index for the variable
GetTermIndex(v Variable) int
}
48 changes: 48 additions & 0 deletions frontend/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package api

import (
"testing"
"strings"

"github.com/your-project/frontend"
"github.com/stretchr/testify/require"
)

func TestPrintf(t *testing.T) {
assert := require.New(t)

var circuit struct {
X, Y frontend.Variable
Const frontend.Variable
}

api := NewTestAPI(t)

// Create a mock to capture output
var output strings.Builder
api.SetOutput(&output)

circuit.Const = 42
api.Printf("const decimal: %d\n", circuit.Const)
assert.Contains(output.String(), "const decimal: 42")

// Test variables
circuit.X = api.Add(10, 20)
circuit.Y = api.Mul(5, 5)

api.Printf("variables: %v %v\n", circuit.X, circuit.Y)

// Test special formats
api.Printf("coeff: %c var: %i\n", circuit.X, circuit.Y)

// Test mixed output
api.Printf("mixed: %d %x %v %c %i\n",
circuit.Const, circuit.X, circuit.Y, circuit.X, circuit.Y)

// Test edge cases
api.Printf("")
api.Printf("%")
api.Printf("%%")
api.Printf("%d")
api.Printf("%d%d", circuit.X)
}