Skip to content

Commit

Permalink
Merge pull request #19 from AlgebraicJulia/usage-docs
Browse files Browse the repository at this point in the history
Add simple usage example.
  • Loading branch information
tylerhanks authored Jul 1, 2024
2 parents e3cf87c + 60ac06c commit 907bfbf
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 11 deletions.
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ Optim = "1.9.4"
SparseArrays = "1.10.0"
StatsBase = "0.34.3"
julia = "1.10"
Reexport = "1.2.2"
66 changes: 65 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,70 @@
[![Code Coverage](https://codecov.io/gh/AlgebraicJulia/AlgebraicOptimization.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/AlgebraicJulia/AlgebraicOptimizatione.jl)
[![CI/CD](https://github.com/AlgebraicJulia/AlgebraicOptimization.jl/actions/workflows/julia_ci.yml/badge.svg)](https://github.com/AlgebraicJulia/AlgebraicOptimization.jl/actions/workflows/julia_ci.yml)

Building and solving optimization problems compositionally.
This package is designed for building large optimization problems out of simpler subproblems and automatically compiling them to a distributed solver.

## Basic Usage

The most simple use of this package is making and solving a composite optimization problem. For example, say we have three subproblems whose objectives are given by some random quadratic cost functions:
```julia
using AlgebraicOptimization
using Catlab

# Problem parameters.
P = [2.1154 -0.3038 0.368 -1.5728 -1.203
-0.3038 1.5697 1.0226 0.159 -0.946
0.368 1.0226 1.847 -0.4916 -1.2668
-1.5728 0.159 -0.4916 2.2192 1.5315
-1.203 -0.946 -1.2668 1.5315 1.9281]
Q = [0.2456 0.3564 -0.0088
0.3564 0.5912 -0.0914
-0.0088 -0.0914 0.8774]
R = [2.0546 -1.333 -0.5263 0.3189
-1.333 1.0481 -0.0211 0.2462
-0.5263 -0.0211 0.951 -0.7813
0.3189 0.2462 -0.7813 1.5813]

a = [-0.26, 0.22, 0.09, 0.19, -0.96]
b = [-0.72, 0.12, 0.41]
c = [0.55, 0.51, 0.6, -0.61]

# Subproblem objectives.
# A PrimalObjective wraps an objective function with its input dimension.
f = PrimalObjective(FinSet(5),x->x'*P*x + a'*x)
g = PrimalObjective(FinSet(3),x->x'*Q*x + b'*x)
h = PrimalObjective(FinSet(4),x->x'*R*x + c'*x)
```

Now, to compose these subproblems, we need to make them into *open* problems. An open problem specifies which components of a problem's domain are open to composition with other problems. We do this as follows:
```julia
# Make open problems.
# The first argument is the primal objective we are wrapping, the second argument is a function
# specifying which components of the objective are exposed.
p1 = Open{PrimalObjective}(FinSet(5), PrimalObjective(FinSet(5),f), FinFunction([2,4], 5))
p2 = Open{PrimalObjective}(FinSet(3), PrimalObjective(FinSet(3),g), id(FinSet(3)))
p3 = Open{PrimalObjective}(FinSet(4), PrimalObjective(FinSet(4),h), FinFunction([1,3,4]))
```

To specify the composition pattern of our subproblems, we use Catlab's relation macro to make an undirected wiring diagram and `oapply` to compose our subproblems.
```julia
d = @relation_diagram (x,y,z) begin
f(u,x)
g(u,w,y)
h(u,w,z)
end

composite_prob = oapply(d, [p1,p2,p3])
```

Now, we can solve the composite problem with distributed gradient descent:
```julia
# Arguments are problem, initial guess, step size, and number of iterations
sol = solve(composite, repeat([100.0], dom(composite_prob).n), 0.1, 100)
```
Currently, we just support unconstrained and equality constrained problems with plans to add support for inequality constrained and disciplined convex programs.

More complete documentation and quality of life improvements are also on the way.

This package is an implementation of [A Compositional Framework for First-Order Optimization](https://arxiv.org/abs/2403.05711).


4 changes: 4 additions & 0 deletions src/FinSetAlgebras.jl
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ function Open{T}(o::T) where T
Open{T}(domain(o), o, id(domain(o)))
end

function Open{T}(o::T, m::FinFunction) where T
Open{T}(domain(o), o, m)
end

dom(obj::Open{T}) where T = dom(obj.m)

# Implement the hom_map for a cospan-algebra based on the hom map for a finset-algebra.
Expand Down
5 changes: 5 additions & 0 deletions src/Objectives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ function gradient_flow(f::Open{PrimalObjective})
return Open{Optimizer}(f.S, x -> -ForwardDiff.gradient(f.o, x), f.m)
end

function solve(f::Open{PrimalObjective}, x0::Vector{Float64}, ss::Float64, n_steps::Int)
solver = Euler(gradient_flow(f), ss)
return simulate(solver, x0, n_steps)
end

# Saddle Problems and Dual Ascent
#################################

Expand Down
27 changes: 17 additions & 10 deletions test/Objectives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,23 @@ d = @relation (x,y,z) begin
h(u,w,z)
end

P = rand(-1:0.01:1,5,5)
P = P'*P
Q = rand(-1:0.01:1,3,3)
Q = Q'*Q
R = rand(-1:0.01:1,4,4)
R = R'*R

a = rand(-1:0.01:1,5)
b = rand(-1:0.01:1,3)
c = rand(-1:0.01:1,4)

P = [2.1154 -0.3038 0.368 -1.5728 -1.203
-0.3038 1.5697 1.0226 0.159 -0.946
0.368 1.0226 1.847 -0.4916 -1.2668
-1.5728 0.159 -0.4916 2.2192 1.5315
-1.203 -0.946 -1.2668 1.5315 1.9281]
Q = [0.2456 0.3564 -0.0088
0.3564 0.5912 -0.0914
-0.0088 -0.0914 0.8774]
R = [2.0546 -1.333 -0.5263 0.3189
-1.333 1.0481 -0.0211 0.2462
-0.5263 -0.0211 0.951 -0.7813
0.3189 0.2462 -0.7813 1.5813]

a = [-0.26, 0.22, 0.09, 0.19, -0.96]
b = [-0.72, 0.12, 0.41]
c = [0.55, 0.51, 0.6, -0.61]

p1 = Open{PrimalObjective}(FinSet(5), PrimalObjective(FinSet(5),x->x'*P*x + a'*x), FinFunction([2,4], 5))
p2 = Open{PrimalObjective}(FinSet(3), PrimalObjective(FinSet(3),x->x'*Q*x + b'*x), id(FinSet(3)))
Expand Down

2 comments on commit 907bfbf

@tylerhanks
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request updated: JuliaRegistries/General/108409

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.0.1 -m "<description of version>" 907bfbf4b12db1c3b03ce71e564f4a5208ac34a3
git push origin v0.0.1

Please sign in to comment.