-
Notifications
You must be signed in to change notification settings - Fork 74
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
24 changed files
with
8,569 additions
and
2,503 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package actorutils | ||
|
||
import ( | ||
"slices" | ||
|
||
alloraMath "github.com/allora-network/allora-chain/math" | ||
emissionstypes "github.com/allora-network/allora-chain/x/emissions/types" | ||
) | ||
|
||
// Returns the quantile value of the given sorted scores | ||
// e.g. if quantile is 0.25 (25%), for all the scores sorted from greatest to smallest | ||
// give me the value that is greater than 25% of the values and less than 75% of the values | ||
// the domain of this quantile is assumed to be between 0 and 1. | ||
// Scores should be of unique actors => no two elements have the same actor address. | ||
func GetQuantileOfScores( | ||
scores []emissionstypes.Score, | ||
quantile alloraMath.Dec, | ||
) (alloraMath.Dec, error) { | ||
// Sort scores in descending order. Address is used to break ties. | ||
slices.SortStableFunc(scores, func(x, y emissionstypes.Score) int { | ||
if x.Score.Lt(y.Score) { | ||
return 1 | ||
} else if x.Score.Gt(y.Score) { | ||
return -1 | ||
} else { | ||
if x.Address < y.Address { | ||
return 1 | ||
} else if x.Address > y.Address { | ||
return -1 | ||
} else { | ||
return 0 | ||
} | ||
} | ||
}) | ||
|
||
// If there are no scores then the quantile of scores is 0. | ||
// This better ensures chain continuity without consequence because in this situation | ||
// there is no meaningful quantile to calculate. | ||
if len(scores) == 0 { | ||
return alloraMath.ZeroDec(), nil | ||
} | ||
// n elements, q quantile | ||
// position = (1 - q) * (n - 1) | ||
nLessOne, err := alloraMath.NewDecFromUint64(uint64(len(scores) - 1)) | ||
if err != nil { | ||
return alloraMath.Dec{}, err | ||
} | ||
oneLessQ, err := alloraMath.OneDec().Sub(quantile) | ||
if err != nil { | ||
return alloraMath.Dec{}, err | ||
} | ||
position, err := oneLessQ.Mul(nLessOne) | ||
if err != nil { | ||
return alloraMath.Dec{}, err | ||
} | ||
|
||
lowerIndex, err := position.Floor() | ||
if err != nil { | ||
return alloraMath.Dec{}, err | ||
} | ||
lowerIndexInt, err := lowerIndex.Int64() | ||
if err != nil { | ||
return alloraMath.Dec{}, err | ||
} | ||
upperIndex, err := position.Ceil() | ||
if err != nil { | ||
return alloraMath.Dec{}, err | ||
} | ||
upperIndexInt, err := upperIndex.Int64() | ||
if err != nil { | ||
return alloraMath.Dec{}, err | ||
} | ||
|
||
if lowerIndex == upperIndex { | ||
return scores[lowerIndexInt].Score, nil | ||
} | ||
|
||
// in cases where the quantile is between two values | ||
// return lowerValue + (upperValue-lowerValue)*(position-lowerIndex) | ||
lowerScore := scores[lowerIndexInt] | ||
upperScore := scores[upperIndexInt] | ||
positionMinusLowerIndex, err := position.Sub(lowerIndex) | ||
if err != nil { | ||
return alloraMath.Dec{}, err | ||
} | ||
upperMinusLower, err := upperScore.Score.Sub(lowerScore.Score) | ||
if err != nil { | ||
return alloraMath.Dec{}, err | ||
} | ||
product, err := positionMinusLowerIndex.Mul(upperMinusLower) | ||
if err != nil { | ||
return alloraMath.Dec{}, err | ||
} | ||
ret, err := lowerScore.Score.Add(product) | ||
if err != nil { | ||
return alloraMath.Dec{}, err | ||
} | ||
return ret, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package actorutils_test | ||
|
||
import ( | ||
"fmt" | ||
"strconv" | ||
"testing" | ||
|
||
alloraMath "github.com/allora-network/allora-chain/math" | ||
alloratestutil "github.com/allora-network/allora-chain/test/testutil" | ||
actorutils "github.com/allora-network/allora-chain/x/emissions/keeper/actor_utils" | ||
emissionstypes "github.com/allora-network/allora-chain/x/emissions/types" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestGetQuantileOfScores(t *testing.T) { | ||
// Note: unsorted scores. GetQuantileOfScores should sort scores within its scope | ||
scores := []emissionstypes.Score{ | ||
{TopicId: 0, BlockHeight: 0, Address: "w1", Score: alloraMath.NewDecFromInt64(90)}, | ||
{TopicId: 0, BlockHeight: 0, Address: "w4", Score: alloraMath.NewDecFromInt64(60)}, | ||
{TopicId: 0, BlockHeight: 0, Address: "w3", Score: alloraMath.NewDecFromInt64(70)}, | ||
{TopicId: 0, BlockHeight: 0, Address: "w5", Score: alloraMath.NewDecFromInt64(50)}, | ||
{TopicId: 0, BlockHeight: 0, Address: "w2", Score: alloraMath.NewDecFromInt64(80)}, | ||
} | ||
|
||
quantile := alloraMath.MustNewDecFromString("0.5") | ||
expectedResult := alloraMath.NewDecFromInt64(70) | ||
|
||
result, err := actorutils.GetQuantileOfScores(scores, quantile) | ||
require.NoError(t, err) | ||
require.Equal(t, expectedResult, result) | ||
|
||
quantile = alloraMath.MustNewDecFromString("0.2") | ||
expectedResult = alloraMath.NewDecFromInt64(58) | ||
|
||
result, err = actorutils.GetQuantileOfScores(scores, quantile) | ||
require.NoError(t, err) | ||
expectedInt, err := expectedResult.Int64() | ||
require.NoError(t, err) | ||
actualInt, err := result.Int64() | ||
require.NoError(t, err) | ||
require.Equal(t, expectedInt, actualInt) | ||
} | ||
|
||
func TestGetQuantileOfScores2(t *testing.T) { | ||
scoresSorted := []emissionstypes.Score{ | ||
{Score: alloraMath.MustNewDecFromString("0.8"), Address: "w1", BlockHeight: 0, TopicId: 0}, | ||
{Score: alloraMath.MustNewDecFromString("0.7"), Address: "w2", BlockHeight: 0, TopicId: 0}, | ||
{Score: alloraMath.MustNewDecFromString("0.0"), Address: "w9", BlockHeight: 0, TopicId: 0}, | ||
{Score: alloraMath.MustNewDecFromString("0.3"), Address: "w6", BlockHeight: 0, TopicId: 0}, | ||
{Score: alloraMath.MustNewDecFromString("0.4"), Address: "w5", BlockHeight: 0, TopicId: 0}, | ||
{Score: alloraMath.MustNewDecFromString("0.9"), Address: "w0", BlockHeight: 0, TopicId: 0}, | ||
{Score: alloraMath.MustNewDecFromString("0.6"), Address: "w3", BlockHeight: 0, TopicId: 0}, | ||
{Score: alloraMath.MustNewDecFromString("0.5"), Address: "w4", BlockHeight: 0, TopicId: 0}, | ||
{Score: alloraMath.MustNewDecFromString("0.1"), Address: "w8", BlockHeight: 0, TopicId: 0}, | ||
{Score: alloraMath.MustNewDecFromString("0.2"), Address: "w7", BlockHeight: 0, TopicId: 0}, | ||
} | ||
quantile := alloraMath.MustNewDecFromString("0.2") | ||
expectedResult := alloraMath.MustNewDecFromString("0.18") | ||
|
||
result, err := actorutils.GetQuantileOfScores(scoresSorted, quantile) | ||
require.NoError(t, err) | ||
alloratestutil.InEpsilon5Dec(t, result, expectedResult) | ||
} | ||
|
||
func TestGetQuantileOfScoresCsv(t *testing.T) { | ||
for epoch := 301; epoch < 400; epoch++ { | ||
epochGet := alloratestutil.GetSortitionSimulatorValuesGetterForEpochs()[epoch] | ||
topicId := uint64(0) | ||
|
||
nParticipants, err := epochGet("n_participants").UInt64() | ||
require.NoError(t, err) | ||
nParticipantsDrawn, err := epochGet("n_participants_drawn").UInt64() | ||
require.NoError(t, err) | ||
|
||
// populate the data from the csv | ||
scoresSorted := make([]emissionstypes.Score, nParticipantsDrawn) | ||
for i := uint64(0); i < nParticipants; i++ { | ||
participantName := strconv.FormatUint(i, 10) | ||
active := epochGet(fmt.Sprintf("%s_active", participantName)) | ||
if active.Equal(alloraMath.OneDec()) { | ||
sortPosition := epochGet(fmt.Sprintf("%s_sort_position_quality_metrics", participantName)) | ||
sortPos, err := sortPosition.UInt64() | ||
require.NoError(t, err) | ||
qualityMetric := epochGet(fmt.Sprintf("%s_quality_metric", participantName)) | ||
scoresSorted[sortPos] = emissionstypes.Score{ | ||
TopicId: topicId, | ||
Address: participantName, | ||
BlockHeight: int64(epoch), | ||
Score: qualityMetric, | ||
} | ||
} | ||
} | ||
for _, score := range scoresSorted { | ||
require.NotEmpty(t, score) | ||
} | ||
expected := epochGet("quality_percentile") | ||
percentile_to_use := epochGet("percentile") | ||
quantile, err := percentile_to_use.Quo(alloraMath.NewDecFromInt64(int64(100))) | ||
require.NoError(t, err) | ||
result, err := actorutils.GetQuantileOfScores(scoresSorted, quantile) | ||
require.NoError(t, err) | ||
alloratestutil.InEpsilon5Dec(t, result, expected) | ||
} | ||
} |
File renamed without changes.
Oops, something went wrong.