Skip to content

Commit

Permalink
feat: optimise over time
Browse files Browse the repository at this point in the history
  • Loading branch information
hopeyen committed Jul 7, 2022
1 parent 9805a34 commit fd62323
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 39 deletions.
12 changes: 7 additions & 5 deletions scripts/allocationopt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/julia
#!/usr/local/bin/julia
using Comonicon
using AllocationOpt

Expand All @@ -12,18 +12,19 @@ Optimises an indexer's allocations and pushes them to the action queue.
- `grtgas`: The maximum amount of GRT that you are willing to spend on each allocation transaction.
- `minimum_allocation_amount`: The minimum amount of GRT that you are willing to allocate to a subgraph.
- `maximum_new_allocations`: The maximum number of new allocations you would like the optimizer to open.
- `τ`: Interval [0,1]. As τ gets closer to 0, the optimiser selects greedy allocations that maximise your short-term, expected rewards, but network dynamics will affect you more. The opposite occurs as τ approaches 1.
- `management_server_url`: The URL that exposes the indexer managment server, including the port. Must begin with http. Example: http://localhost:18000.
- `indexer_service_network_url`: The URL that exposes the indexer service's network endpoint. Must begin with http. Example: http://localhost:7600/network.
"""
@cast function actionqueue(id, filepath, minimum_allocation_amount, maximum_new_allocations, management_server_url, indexer_service_network_url)
@cast function actionqueue(id, filepath, minimum_allocation_amount, maximum_new_allocations, τ, management_server_url, indexer_service_network_url)
# Read subgraph lists defined in the file
cols = read_filterlists(filepath)

# Pull network state from indexer service network endpoint
indexer, repo = network_state(id, cols..., indexer_service_network_url)

# Optimize for the indexer
ω = optimize_indexer(repo, indexer, parse(Float64, minimum_allocation_amount), parse(Int64, maximum_new_allocations))
ω = optimize_indexer(repo, indexer, parse(Float64, minimum_allocation_amount), parse(Int64, maximum_new_allocations), parse(Float64, τ))

# Push results to action queue
_ = push_allocations!(id, management_server_url, indexer_service_network_url, ω, cols...)
Expand All @@ -43,17 +44,18 @@ Optimises an indexer's allocations and generates indexer rules to change allocat
- `grtgas`: The maximum amount of GRT that you are willing to spend on each allocation transaction.
- `minimum_allocation_amount`: The minimum amount of GRT that you are willing to allocate to a subgraph.
- `maximum_new_allocations`: The maximum number of new allocations you would like the optimizer to open.
- `τ`: Interval [0,1]. As τ gets closer to 0, the optimiser selects greedy allocations that maximise your short-term, expected rewards, but network dynamics will affect you more. The opposite occurs as τ approaches 1.
- `indexer_service_network_url`: The URL that exposes the indexer service's network endpoint. Must begin with http. Example: http://localhost:7600/network.
"""
@cast function rules(id, filepath, minimum_allocation_amount, maximum_new_allocations, indexer_service_network_url)
@cast function rules(id, filepath, minimum_allocation_amount, maximum_new_allocations, τ, indexer_service_network_url)
# Read subgraph lists defined in the file
cols = read_filterlists(filepath)

# Pull network state from indexer service network endpoint
indexer, repo = network_state(id, cols..., indexer_service_network_url)

# Optimize for the indexer
ω = optimize_indexer(repo, indexer, parse(Float64, minimum_allocation_amount), parse(Int64, maximum_new_allocations))
ω = optimize_indexer(repo, indexer, parse(Float64, minimum_allocation_amount), parse(Int64, maximum_new_allocations), parse(Float64, τ))

# Create indexer rules
indexer_rules = create_rules!(id, indexer_service_network_url, ω, cols...)
Expand Down
11 changes: 10 additions & 1 deletion src/AllocationOpt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,29 @@ end
- `repo::Repository`: Contains the current network state.
- `minimum_allocation_amount::Real`: The minimum amount of GRT that you are willing to allocate to a subgraph.
- `maximum_new_allocations::Integer`: The maximum number of new allocations you would like the optimizer to open.
- `τ`: Interval [0,1]. As τ gets closer to 0, the optimiser selects greedy allocations that maximise your short-term, expected rewards, but network dynamics will affect you more. The opposite occurs as τ approaches 1.
```
"""
function optimize_indexer(
indexer::Indexer,
repo::Repository,
minimum_allocation_amount::Real,
maximum_new_allocations::Integer,
τ::AbstractFloat,
)
if τ > 1 || τ < 0
throw(ArgumentError("τ must be between 0 and 1."))
end
@warn "maximum_new_allocations is not currently optimised for."
@warn "minimum_allocation_amount is not currently optimised for."

# Optimise
# ω = optimize(indexer, repo, maximum_new_allocations, minimum_allocation_amount)
ω = optimize(indexer, repo)
Ω = stakes(repo)
ψ = signal.(repo.subgraphs)
σ = indexer.stake
Ωprime = discount(Ω, ψ, σ, τ)
ω = optimize(Ωprime, ψ, σ)

# Filter results with deployment IPFS hashes
suggested_allocations = Dict(
Expand Down
6 changes: 6 additions & 0 deletions src/domainmodel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,9 @@ id(x::GraphEntity) = x.id
allocation(i::Indexer) = i.allocations

allocated_stake(a::Allocation) = a.amount

stake(i::Indexer) = i.stake

function other_stake(repo::Repository, indexer::Indexer)
return sum(stake.(repo.indexers)) - stake(indexer)
end
23 changes: 17 additions & 6 deletions src/service.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function stakes(r::Repository)

# Match name to indexer allocations
ω = reduce(vcat, allocation.(r.indexers))
Ω = []
Ω = Float64[]
for subgraph in subgraphs
subgraph_allocations = filter(x -> x.ipfshash == subgraph, ω)
subgraph_amounts = allocated_stake.(subgraph_allocations)
Expand Down Expand Up @@ -48,11 +48,7 @@ end

solve_primal(Ω, ψ, v) = max.(0.0, .√.* Ω / v) - Ω)

function optimize(indexer::Indexer, repo::Repository)
ψ = signal.(repo.subgraphs)
Ω = stakes(repo)
σ = indexer.stake

function optimize::AbstractVector{T}, ψ::AbstractVector{T}, σ::T) where {T<:Real}
# Solve the dual and use that value to solve the primal
v = solve_dual(Ω, ψ, σ)
ω = solve_primal(Ω, ψ, v)
Expand All @@ -69,13 +65,28 @@ function projectsimplex(x::AbstractVector{T}, z) where {T<:Real}
return w
end

# NOTE: function not used
projectrange(low, high, x::T) where {T<:Real} = max(min(x, one(T) * high), one(T) * low)

# NOTE: function not used
shrink(z::T, α) where {T<:Real} = sign(z) .* max(abs(z) - α, zero(T))

# NOTE: function not used
∇f::T, ψ, Ω, μ, p) where {T<:Real} = -((ψ * Ω) /+ Ω + eps(T))^2) -* p)

# NOTE: function not used
# removed (ω .+ _) from the numerator to only include Ω, also removed minimum bound of 1: maximum(_, 1)
compute_λ(ω, ψ, Ω) = minimum(nonzero(((Ω) .^ 3) ./ (2 .* ψ)))

# NOTE: function not used
nonzero(v::Vector{<:Real}) = v[findall(v .!= 0.0)]

# TODO: Ωstar shouldn't be over filtered subgraphs, but all subgraphs.
function discount(
Ω::AbstractVector{T}, ψ::AbstractVector{T}, σ::T, τ::AbstractFloat
) where {T<:Real}
Ω0 = ones(length(Ω))
Ωstar = optimize(Ω0, ψ, σ)
Ωprime = projectsimplex* Ωstar + (1 - τ) * Ω, σ)
return Ωprime
end
14 changes: 13 additions & 1 deletion test/query.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using GraphQLClient

@testset "query" begin
gateway_url = "https://gateway.thegraph.com/network"
# gateway_url = "https://gateway.thegraph.com/network"
gateway_url = "https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-mainnet"

@testset "verify_ipfshashes" begin
# Should fail due to bad prefix
Expand Down Expand Up @@ -143,4 +144,15 @@ using GraphQLClient
allocations = query_indexer_allocations(client, id)
@test length(allocations) == alloc_length
end

@testset "other stake" begin
# calculate the stake constraint for indexer
client = Client(gateway_url)
subgraphs = query_subgraphs(client, String[], String[])
indexers = query_indexers(client, subgraphs)
indexer = indexers[findfirst(i -> length(i.allocations) > 0, indexers)]
repo = snapshot(client, String[], String[])

@test other_stake(repo, indexer) + indexer.stake == sum(i -> i.stake, indexers)
end
end
9 changes: 5 additions & 4 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ using GraphQLClient
include("../src/service.jl")
include("../src/ActionQueue.jl")

gateway_url = "https://gateway.thegraph.com/network"
gateway_url = "https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-mainnet"

# Tests
include("domainmodel.jl")
Expand All @@ -35,6 +35,7 @@ using GraphQLClient
end

@testset "optimize_indexer" begin
τ = 0.0
client = Client(gateway_url)
id = query(client, "indexers"; query_args=Dict("where" => Dict("stakedTokens_gte" => "100000000000000000000000")), output_fields="id").data["indexers"][1]["id"]
all_hashes = [
Expand All @@ -53,7 +54,7 @@ using GraphQLClient
repo, optindexer = network_state(
id, String[ipfshash], String[], String[], String[], gateway_url
)
allocs = optimize_indexer(optindexer, repo, 0.0, 10000)
allocs = optimize_indexer(optindexer, repo, 0.0, 10000, τ)
# Sum allocation amounts
ω = sum(values(allocs))
@test isapprox(ω, stake; atol=1e-6)
Expand All @@ -68,7 +69,7 @@ using GraphQLClient
String[],
gateway_url,
)
allocs = optimize_indexer(optindexer, repo, 0.0, 10000)
allocs = optimize_indexer(optindexer, repo, 0.0, 10000, τ)
# Sum allocation amounts
ω = sum(values(allocs))
@test isapprox(ω, stake; atol=1e-6)
Expand All @@ -82,7 +83,7 @@ using GraphQLClient
stake = togrt(indexer["delegatedTokens"]) + togrt(indexer["stakedTokens"])
cols = read_filterlists("example.csv")
repo, optindexer = network_state(id, cols..., gateway_url)
allocs = optimize_indexer(optindexer, repo, 0.0, 10000)
allocs = optimize_indexer(optindexer, repo, 0.0, 10000, τ)
ω = sum(values(allocs))
@test isapprox(ω, stake; atol=1e-6)
end
Expand Down
68 changes: 46 additions & 22 deletions test/service.jl
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@
],
)
indexer = Indexer("0x00", 5.0, Allocation[])
ω = optimize(indexer, repo)
Ω = stakes(repo)
ψ = signal.(repo.subgraphs)
σ = stake(indexer)
ω = optimize(Ω, ψ, σ)
@test isapprox(ω, [4.2, 0.8], atol=0.1)
end

Expand Down Expand Up @@ -129,42 +132,42 @@
high = 1
# Shouldn't project since already within range
x = [-0.5, 0.2, 0.8]
@test projectrange.(low, high, x) == x
@test_skip projectrange.(low, high, x) == x

# Should set out of range to within
x = [-5, -0.2, 8]
w = projectrange.(low, high, x)
@test maximum(w) == high
@test minimum(w) == low
@test w[2] == x[2]
@test w[1] == low
@test w[3] == high
@test_skip maximum(w) == high
@test_skip minimum(w) == low
@test_skip w[2] == x[2]
@test_skip w[1] == low
@test_skip w[3] == high

# Should be within the range whatever it is
x = rand(Int, 10)
w = projectrange.(low, high, x)
@test maximum(w) <= high
@test minimum(w) >= low
@test_skip maximum(w) <= high
@test_skip minimum(w) >= low
end

@testset "shrink" begin
# No need to shrink
z = [-5, 0, 2, 8]
α = 0
y = shrink.(z, α)
@test y == z
@test_skip y == z

# Shrink from positives and negatives
z = [-5, 0, 8]
α = 1
y = shrink.(z, α)
@test y == [-4, 0, 7]
@test_skip y == [-4, 0, 7]

# Shrink and zeros
z = [-5, 1, 3, 8]
α = 3
y = shrink.(z, α)
@test y == [-2, 0, 0, 5]
@test_skip y == [-2, 0, 0, 5]
end

@testset "∇f" begin
Expand All @@ -175,7 +178,7 @@
p = Float64[0, 0, 0]
μ = 0.1
df = ∇f.(ω, ψ, Ω, μ, p)
@test df [-2.5, -2, -8]
@test_skip df [-2.5, -2, -8]

# ω and p are 1
ψ = Float64[5, 2, 8]
Expand All @@ -184,7 +187,7 @@
p = Float64[1, 1, 1]
μ = 0.1
df = ∇f.(ω, ψ, Ω, μ, p)
@test df == [-10 / 9 - 0.1, -0.6, -2.1]
@test_skip df == [-10 / 9 - 0.1, -0.6, -2.1]

# various numbers
ψ = Float64[4, 5, 8]
Expand All @@ -193,26 +196,47 @@
p = Float64[0.1, 10, 2]
μ = 0.1
df = ∇f.(ω, ψ, Ω, μ, p)
@test df == [-0.26, -1.9375, -0.7]
@test_skip df == [-0.26, -1.9375, -0.7]
end

@testset "compute_λ" begin
ψ = [5, 2, 1, 1, 10, 4]
Ω = [2, 5, 1, 0, 0.1, 5]
ω = [0, 0, 0, 0, 0, 0]
λ = compute_λ(ω, ψ, Ω)
@test isapprox(λ, 5e-5, atol=0.00001)
@test_skip isapprox(λ, 5e-5, atol=0.00001)
end

@testset "nonzero" begin
# no zero
ψ = [5, 2, 1, 1, 10, 4]
@test nonzero(ψ) == ψ
ψ = Float64[5, 2, 1, 1, 10, 4]
@test_skip nonzero(ψ) == ψ
# some zero
Ω = [2, 5, 1, 0, 0.1, 5]
@test nonzero(Ω) == [2, 5, 1, 0.1, 5]
Ω = Float64[2, 5, 1, 0, 0.1, 5]
@test_skip nonzero(Ω) == [2, 5, 1, 0.1, 5]
# all zero
ω = [0, 0, 0, 0, 0, 0]
@test isempty(nonzero(ω))
ω = Float64[0, 0, 0, 0, 0, 0]
@test_skip isempty(nonzero(ω))
end

@testset "discount" begin
# τ = 1.0
Ω = Float64[2, 5, 3]
ψ = Float64[7, 2, 1]
σ = 10.0
τ = 1.0
Ωnew = discount(Ω, ψ, σ, τ)
Ω0 = ones(length(Ω))
@test Ωnew == optimize(Ω0, ψ, σ)

# τ = 0.0
τ = 0.0
Ωnew = discount(Ω, ψ, σ, τ)
@test Ωnew == Ω

# τ = 0.2, result should still sum to σ because of simplex projection
τ = 0.2
Ωnew = discount(Ω, ψ, σ, τ)
@test sum(Ωnew) == σ
end
end

0 comments on commit fd62323

Please sign in to comment.