Skip to content

Commit

Permalink
Merge branch 'main' into cairo0_hint-pow
Browse files Browse the repository at this point in the history
  • Loading branch information
MaksymMalicki authored Mar 28, 2024
2 parents d9642b1 + 183d8a9 commit c415b94
Show file tree
Hide file tree
Showing 14 changed files with 498 additions and 244 deletions.
17 changes: 14 additions & 3 deletions pkg/hintrunner/core/hint.go
Original file line number Diff line number Diff line change
Expand Up @@ -1372,7 +1372,10 @@ func (hint *AssertLeFindSmallArc) Execute(vm *VM.VirtualMachine, ctx *hinter.Hin
})

// Exclude the largest arc after sorting
ctx.ExcludedArc = lengthsAndIndices[2].Position
err = ctx.ScopeManager.AssignVariable("excluded", lengthsAndIndices[2].Position)
if err != nil {
return err
}

rangeCheckPtrMemAddr, err := hinter.ResolveAsAddress(vm, hint.RangeCheckPtr)
if err != nil {
Expand Down Expand Up @@ -1448,7 +1451,11 @@ func (hint *AssertLeIsFirstArcExcluded) Execute(vm *VM.VirtualMachine, ctx *hint
}

var writeValue mem.MemoryValue
if ctx.ExcludedArc != 0 {
excluded, err := ctx.ScopeManager.GetVariableValue("excluded")
if err != nil {
return err
}
if excluded != 0 {
writeValue = mem.MemoryValueFromInt(1)
} else {
writeValue = mem.MemoryValueFromInt(0)
Expand All @@ -1472,7 +1479,11 @@ func (hint *AssertLeIsSecondArcExcluded) Execute(vm *VM.VirtualMachine, ctx *hin
}

var writeValue mem.MemoryValue
if ctx.ExcludedArc != 1 {
excluded, err := ctx.ScopeManager.GetVariableValue("excluded")
if err != nil {
return err
}
if excluded != 1 {
writeValue = mem.MemoryValueFromInt(1)
} else {
writeValue = mem.MemoryValueFromInt(0)
Expand Down
21 changes: 6 additions & 15 deletions pkg/hintrunner/core/hint_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,10 +318,7 @@ func BenchmarkAssertLeIsFirstArcExcluded(b *testing.B) {
vm.Context.Ap = 0
vm.Context.Fp = 0

ctx := hinter.HintRunnerContext{
ExcludedArc: 0,
}

ctx := hinter.SetContextWithScope(map[string]any{"excluded": 0})
var skipExcludeAFlag hinter.ApCellRef = 1

b.ResetTimer()
Expand All @@ -331,7 +328,7 @@ func BenchmarkAssertLeIsFirstArcExcluded(b *testing.B) {
SkipExcludeAFlag: skipExcludeAFlag,
}

err := hint.Execute(vm, &ctx)
err := hint.Execute(vm, ctx)
if err != nil {
b.Error(err)
break
Expand All @@ -346,10 +343,7 @@ func BenchmarkAssertLeIsSecondArcExcluded(b *testing.B) {
vm.Context.Ap = 0
vm.Context.Fp = 0

ctx := hinter.HintRunnerContext{
ExcludedArc: 0,
}

ctx := hinter.SetContextWithScope(map[string]any{"excluded": 0})
var skipExcludeBMinusA hinter.ApCellRef = 1

b.ResetTimer()
Expand All @@ -359,7 +353,7 @@ func BenchmarkAssertLeIsSecondArcExcluded(b *testing.B) {
SkipExcludeBMinusA: skipExcludeBMinusA,
}

err := hint.Execute(vm, &ctx)
err := hint.Execute(vm, ctx)
if err != nil {
b.Error(err)
break
Expand All @@ -373,10 +367,7 @@ func BenchmarkAssertLeFindSmallArc(b *testing.B) {
vm := VM.DefaultVirtualMachine()

rand := utils.DefaultRandGenerator()
ctx := hinter.HintRunnerContext{
ExcludedArc: 0,
}

ctx := hinter.SetContextWithScope(map[string]any{"excluded": 0})
rangeCheckPtr := vm.Memory.AllocateBuiltinSegment(&builtins.RangeCheck{})

b.ResetTimer()
Expand All @@ -397,7 +388,7 @@ func BenchmarkAssertLeFindSmallArc(b *testing.B) {
RangeCheckPtr: hinter.Deref{Deref: hinter.ApCellRef(0)},
}

if err := hint.Execute(vm, &ctx); err != nil &&
if err := hint.Execute(vm, ctx); err != nil &&
!assert.ErrorContains(b, err, "check write: 2**128 <") {
b.FailNow()
}
Expand Down
30 changes: 10 additions & 20 deletions pkg/hintrunner/core/hint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -993,11 +993,9 @@ func TestAssertLeFindSmallArc(t *testing.T) {
RangeCheckPtr: hinter.Deref{Deref: hinter.ApCellRef(0)},
}

ctx := hinter.HintRunnerContext{
ExcludedArc: 0,
}
ctx := hinter.SetContextWithScope(map[string]any{"excluded": 0})

err := hint.Execute(vm, &ctx)
err := hint.Execute(vm, ctx)

require.NoError(t, err)

Expand All @@ -1008,31 +1006,30 @@ func TestAssertLeFindSmallArc(t *testing.T) {
actualRem2 := utils.ReadFrom(vm, 2, 2)
actualQuotient2 := utils.ReadFrom(vm, 2, 3)
actual1Ptr := utils.ReadFrom(vm, 1, 0)
actualExcludedArc, err := ctx.ScopeManager.GetVariableValue("excluded")

require.NoError(t, err)

require.Equal(t, tc.expectedRem1, actualRem1)
require.Equal(t, tc.expectedQuotient1, actualQuotient1)
require.Equal(t, tc.expectedRem2, actualRem2)
require.Equal(t, tc.expectedQuotient2, actualQuotient2)
require.Equal(t, expectedPtr, actual1Ptr)
require.Equal(t, tc.expectedExcludedArc, ctx.ExcludedArc)
require.Equal(t, tc.expectedExcludedArc, actualExcludedArc)
}
}

func TestAssertLeIsFirstArcExcluded(t *testing.T) {
vm := VM.DefaultVirtualMachine()

ctx := hinter.HintRunnerContext{
ExcludedArc: 2,
}

ctx := hinter.SetContextWithScope(map[string]any{"excluded": 2})
var flag hinter.ApCellRef = 0

hint := AssertLeIsFirstArcExcluded{
SkipExcludeAFlag: flag,
}

err := hint.Execute(vm, &ctx)

err := hint.Execute(vm, ctx)
require.NoError(t, err)

expected := mem.MemoryValueFromInt(1)
Expand All @@ -1047,24 +1044,18 @@ func TestAssertLeIsSecondArcExcluded(t *testing.T) {
vm.Context.Ap = 0
vm.Context.Fp = 0

ctx := hinter.HintRunnerContext{
ExcludedArc: 1,
}

ctx := hinter.SetContextWithScope(map[string]any{"excluded": 1})
var flag hinter.ApCellRef = 0

hint := AssertLeIsSecondArcExcluded{
SkipExcludeBMinusA: flag,
}

err := hint.Execute(vm, &ctx)

err := hint.Execute(vm, ctx)
require.NoError(t, err)

expected := mem.MemoryValueFromInt(0)

actual := utils.ReadFrom(vm, VM.ExecutionSegment, 0)

require.Equal(t, expected, actual)
}

Expand All @@ -1079,7 +1070,6 @@ func TestRandomEcPoint(t *testing.T) {
}

err := hint.Execute(vm)

require.NoError(t, err)

expectedX := mem.MemoryValueFromFieldElement(
Expand Down
181 changes: 181 additions & 0 deletions pkg/hintrunner/hinter/dict.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package hinter

import (
"fmt"

VM "github.com/NethermindEth/cairo-vm-go/pkg/vm"
mem "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory"
f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp"
)

// Used to keep track of all dictionaries data
type Dictionary struct {
// The data contained on a dictionary
data map[f.Element]*mem.MemoryValue
// Unique id assigned at the moment of creation
idx uint64
}

// Gets the memory value at certain key
func (d *Dictionary) At(key *f.Element) (*mem.MemoryValue, error) {
if value, ok := d.data[*key]; ok {
return value, nil
}
return nil, fmt.Errorf("no value for key %s", key)
}

// Given a key and a value, it sets the value at the given key
func (d *Dictionary) Set(key *f.Element, value *mem.MemoryValue) {
d.data[*key] = value
}

// Returns the initialization number when the dictionary was created
func (d *Dictionary) InitNumber() uint64 {
return d.idx
}

// Used to manage dictionaries creation
type DictionaryManager struct {
// a map that links a segment index to a dictionary
dictionaries map[uint64]Dictionary
}

func InitializeDictionaryManager(ctx *HintRunnerContext) {
if ctx.DictionaryManager.dictionaries == nil {
ctx.DictionaryManager.dictionaries = make(map[uint64]Dictionary)
}
}

// It creates a new segment which will hold dictionary values. It links this
// segment with the current dictionary and returns the address that points
// to the start of this segment
func (dm *DictionaryManager) NewDictionary(vm *VM.VirtualMachine) mem.MemoryAddress {
newDictAddr := vm.Memory.AllocateEmptySegment()
dm.dictionaries[newDictAddr.SegmentIndex] = Dictionary{
data: make(map[f.Element]*mem.MemoryValue),
idx: uint64(len(dm.dictionaries)),
}
return newDictAddr
}

// Given a memory address, it looks for the right dictionary using the segment index. If no
// segment is associated with the given segment index, it errors
func (dm *DictionaryManager) GetDictionary(dictAddr *mem.MemoryAddress) (Dictionary, error) {
dict, ok := dm.dictionaries[dictAddr.SegmentIndex]
if ok {
return dict, nil
}
return Dictionary{}, fmt.Errorf("no dictionary at address %s", dictAddr)
}

// Given a memory address and a key it returns the value held at that position. The address is used
// to locate the correct dictionary and the key to index on it
func (dm *DictionaryManager) At(dictAddr *mem.MemoryAddress, key *f.Element) (*mem.MemoryValue, error) {
if dict, ok := dm.dictionaries[dictAddr.SegmentIndex]; ok {
return dict.At(key)
}
return nil, fmt.Errorf("no dictionary at address %s", dictAddr)
}

// Given a memory address,a key and a value it stores the value at the correct position.
func (dm *DictionaryManager) Set(dictAddr *mem.MemoryAddress, key *f.Element, value *mem.MemoryValue) error {
if dict, ok := dm.dictionaries[dictAddr.SegmentIndex]; ok {
dict.Set(key, value)
return nil
}
return fmt.Errorf("no dictionary at address %s", dictAddr)
}

// Used to keep track of squashed dictionaries
type SquashedDictionaryManager struct {
// A map from each key to a list of indices where the key is present
// the list in reversed order.
// Note: The indices should be Felts, but current memory limitations
// make it impossible to use an index that big so we use uint64 instead
KeyToIndices map[f.Element][]uint64

// A descending list of keys
Keys []f.Element
}

func InitializeSquashedDictionaryManager(ctx *HintRunnerContext) error {
if ctx.SquashedDictionaryManager.KeyToIndices != nil ||
ctx.SquashedDictionaryManager.Keys != nil {
return fmt.Errorf("squashed dictionary manager already initialized")
}
ctx.SquashedDictionaryManager.KeyToIndices = make(map[f.Element][]uint64, 100)
ctx.SquashedDictionaryManager.Keys = make([]f.Element, 0, 100)
return nil
}

// It adds another index to the list of indices associated to the given key
// If the key is not present, it creates a new entry
func (sdm *SquashedDictionaryManager) Insert(key *f.Element, index uint64) {
keyIndex := *key
if indices, ok := sdm.KeyToIndices[keyIndex]; ok {
sdm.KeyToIndices[keyIndex] = append(indices, index)
} else {
sdm.KeyToIndices[keyIndex] = []uint64{index}
}
}

// It returns the smallest key in the key list
func (sdm *SquashedDictionaryManager) LastKey() (f.Element, error) {
if len(sdm.Keys) == 0 {
return f.Element{}, fmt.Errorf("no keys left")
}
return sdm.Keys[len(sdm.Keys)-1], nil
}

// It pops out the smallest key in the key list
func (sdm *SquashedDictionaryManager) PopKey() (f.Element, error) {
key, err := sdm.LastKey()
if err != nil {
return key, err
}

sdm.Keys = sdm.Keys[:len(sdm.Keys)-1]
return key, nil
}

// It returns the list of indices associated to the smallest key
func (sdm *SquashedDictionaryManager) LastIndices() ([]uint64, error) {
key, err := sdm.LastKey()
if err != nil {
return nil, err
}

return sdm.KeyToIndices[key], nil
}

// It returns smallest index associated with the smallest key
func (sdm *SquashedDictionaryManager) LastIndex() (uint64, error) {
key, err := sdm.LastKey()
if err != nil {
return 0, err
}

indices := sdm.KeyToIndices[key]
if len(indices) == 0 {
return 0, fmt.Errorf("no indices for key %s", &key)
}

return indices[len(indices)-1], nil
}

// It pops out smallest index associated with the smallest key
func (sdm *SquashedDictionaryManager) PopIndex() (uint64, error) {
key, err := sdm.LastKey()
if err != nil {
return 0, err
}

indices := sdm.KeyToIndices[key]
if len(indices) == 0 {
return 0, fmt.Errorf("no indices for key %s", &key)
}

index := indices[len(indices)-1]
sdm.KeyToIndices[key] = indices[:len(indices)-1]
return index, nil
}
Loading

0 comments on commit c415b94

Please sign in to comment.