Skip to content

Commit

Permalink
Merge pull request #62 from osmosis-labs/refactor_pow
Browse files Browse the repository at this point in the history
Speedup 2:1 pools, and separate pow benchmarks to a separate file
  • Loading branch information
ValarDragon authored Apr 6, 2021
2 parents 7a6c81f + e7ef7b4 commit bd0dd19
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 72 deletions.
49 changes: 33 additions & 16 deletions x/gamm/keeper/math.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import (
// TODO: Analyze choice here
var powPrecision, _ = sdk.NewDecFromStr("0.00000001")

// Singletons
var zero sdk.Dec = sdk.ZeroDec()
var one_half sdk.Dec = sdk.MustNewDecFromStr("0.5")
var one sdk.Dec = sdk.OneDec()
var two sdk.Dec = sdk.MustNewDecFromStr("2")

// calcSpotPrice returns the spot price of the pool
// This is the weight-adjusted balance of the tokens in the pool.
// so spot_price = (B_in / W_in) / (B_out / W_out)
Expand Down Expand Up @@ -77,7 +83,7 @@ func calcInGivenOut(
diff := tokenBalanceOut.Sub(tokenAmountOut)
y := tokenBalanceOut.Quo(diff)
foo := pow(y, weightRatio)
foo = foo.Sub(sdk.OneDec())
foo = foo.Sub(one)
tokenAmountIn := sdk.OneDec().Sub(swapFee)
return (tokenBalanceIn.Mul(foo)).Quo(tokenAmountIn)

Expand Down Expand Up @@ -191,7 +197,8 @@ func calcPoolInGivenSingleOut(

/*********************************************************/

func subSign(a, b sdk.Dec) (sdk.Dec, bool) {
// absDifferenceWithSign returns | a - b |, (a - b).sign()
func absDifferenceWithSign(a, b sdk.Dec) (sdk.Dec, bool) {
if a.GTE(b) {
return a.Sub(b), false
} else {
Expand All @@ -208,18 +215,18 @@ func pow(base sdk.Dec, exp sdk.Dec) sdk.Dec {
// You can see this by recalling that `i = (-1)^(.5)`. We have to go to complex numbers to define this.
// (And would have to implement complex logarithms)
// We don't have a need for negative bases, so we don't include any such logic.
if base.LTE(sdk.ZeroDec()) {
panic(fmt.Errorf("base have to be greater than zero"))
if !base.IsPositive() {
panic(fmt.Errorf("base must be greater than 0"))
}
// TODO: Remove this, we can do a
if base.GTE(sdk.OneDec().MulInt64(2)) {
panic(fmt.Errorf("base have to be lesser than two"))
// TODO: Remove this if we want to generalize the function,
// we can adjust the algorithm in this setting.
if base.GTE(two) {
panic(fmt.Errorf("base must be lesser than two"))
}

// We will use an approximation algorithm to compute the power.
// Since computing an integer power is easy, we split up the exponent into
// an integer component and a fractional component.
// a
integer := exp.TruncateDec()
fractional := exp.Sub(integer)

Expand All @@ -229,28 +236,38 @@ func pow(base sdk.Dec, exp sdk.Dec) sdk.Dec {
return integerPow
}

partialResult := powApprox(base, fractional, powPrecision)
fractionalPow := powApprox(base, fractional, powPrecision)

return integerPow.Mul(partialResult)
return integerPow.Mul(fractionalPow)
}

// Contract: 0 < base < 2
func powApprox(base sdk.Dec, exp sdk.Dec, precision sdk.Dec) sdk.Dec {
if base.LTE(sdk.ZeroDec()) {
panic(fmt.Errorf("base must be greater than zero"))
if exp.IsZero() {
return sdk.ZeroDec()
}
if base.GTE(sdk.OneDec().MulInt64(2)) {
panic(fmt.Errorf("base must be less than two"))

// Common case optimization
// Optimize for it being equal to one-half
if exp.Equal(one_half) {
output, err := base.ApproxSqrt()
if err != nil {
panic(err)
}
return output
}
// TODO: Make an approx-equal function, and then check if exp * 3 = 1, and do a check accordingly

a := exp
x, xneg := subSign(base, sdk.OneDec())
x, xneg := absDifferenceWithSign(base, one)
term := sdk.OneDec()
sum := sdk.OneDec()
negative := false

// TODO: Document this computation via taylor expansion
for i := 1; term.GTE(precision); i++ {
bigK := sdk.OneDec().MulInt64(int64(i))
c, cneg := subSign(a, bigK.Sub(sdk.OneDec()))
c, cneg := absDifferenceWithSign(a, bigK.Sub(one))
term = term.Mul(c.Mul(x))
term = term.Quo(bigK)

Expand Down
58 changes: 2 additions & 56 deletions x/gamm/keeper/math_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import (
"github.com/stretchr/testify/require"
)

func TestSubSign(t *testing.T) {
func TestAbsDifferenceWithSign(t *testing.T) {
decA, err := sdk.NewDecFromStr("3.2")
require.NoError(t, err)
decB, err := sdk.NewDecFromStr("4.3432389")
require.NoError(t, err)

s, b := subSign(decA, decB)
s, b := absDifferenceWithSign(decA, decB)
require.True(t, b)

expectedDec, err := sdk.NewDecFromStr("1.1432389")
Expand Down Expand Up @@ -272,57 +272,3 @@ func TestCalcPoolInGivenSingleOut(t *testing.T) {
"expected value & actual value's difference should less than precision*10000",
)
}

func BenchmarkPow(b *testing.B) {
tests := []struct {
base sdk.Dec
exp sdk.Dec
}{
{
base: sdk.NewDecWithPrec(12, 1),
exp: sdk.NewDecWithPrec(12, 1),
},
{
base: sdk.NewDecWithPrec(5, 1),
exp: sdk.NewDecWithPrec(11122, 3),
},
{
base: sdk.NewDecWithPrec(1, 1),
exp: sdk.NewDecWithPrec(492, 8),
},
{
base: sdk.NewDecWithPrec(2423, 7),
exp: sdk.NewDecWithPrec(1213, 1),
},
{
base: sdk.NewDecWithPrec(493, 3),
exp: sdk.NewDecWithPrec(121, 8),
},
{
base: sdk.NewDecWithPrec(249, 6),
exp: sdk.NewDecWithPrec(2304, 1),
},
{
base: sdk.NewDecWithPrec(2342, 4),
exp: sdk.NewDecWithPrec(322, 1),
},
{
base: sdk.NewDecWithPrec(999, 6),
exp: sdk.NewDecWithPrec(1424, 1),
},
{
base: sdk.NewDecWithPrec(1234, 3),
exp: sdk.NewDecWithPrec(1203, 1),
},
{
base: sdk.NewDecWithPrec(122, 5),
exp: sdk.NewDecWithPrec(1232, 1),
},
}

for i := 0; i < b.N; i++ {
for _, test := range tests {
pow(test.base, test.exp)
}
}
}
86 changes: 86 additions & 0 deletions x/gamm/keeper/pow_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package keeper

import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
)

func BenchmarkPow(b *testing.B) {
tests := []struct {
base sdk.Dec
exp sdk.Dec
}{
// TODO: Choose selection here more robustly
{
base: sdk.MustNewDecFromStr("1.2"),
exp: sdk.MustNewDecFromStr("1.2"),
},
{
base: sdk.MustNewDecFromStr("0.5"),
exp: sdk.MustNewDecFromStr("11.122"),
},
{
base: sdk.MustNewDecFromStr("0.1"),
exp: sdk.MustNewDecFromStr("0.00000492"),
},
{
base: sdk.MustNewDecFromStr("0.0002423"),
exp: sdk.MustNewDecFromStr("0.1234"),
},
{
base: sdk.MustNewDecFromStr("0.493"),
exp: sdk.MustNewDecFromStr("0.00000121"),
},
{
base: sdk.MustNewDecFromStr("0.000249"),
exp: sdk.MustNewDecFromStr("2.304"),
},
{
base: sdk.MustNewDecFromStr("0.2342"),
exp: sdk.MustNewDecFromStr("32.2"),
},
{
base: sdk.MustNewDecFromStr("0.000999"),
exp: sdk.MustNewDecFromStr("142.4"),
},
{
base: sdk.MustNewDecFromStr("1.234"),
exp: sdk.MustNewDecFromStr("120.3"),
},
{
base: sdk.MustNewDecFromStr("0.00122"),
exp: sdk.MustNewDecFromStr("123.2"),
},
}

for i := 0; i < b.N; i++ {
for _, test := range tests {
pow(test.base, test.exp)
}
}
}

func BenchmarkSqrtPow(b *testing.B) {
tests := []struct {
base sdk.Dec
}{
// TODO: Choose selection here more robustly
{
base: sdk.MustNewDecFromStr("1.29847"),
},
{
base: sdk.MustNewDecFromStr("1.313135"),
},
{
base: sdk.MustNewDecFromStr("1.65976735939"),
},
}
one_half := sdk.MustNewDecFromStr("0.5")

for i := 0; i < b.N; i++ {
for _, test := range tests {
pow(test.base, one_half)
}
}
}

0 comments on commit bd0dd19

Please sign in to comment.