From 42415e2908ae0e4223a1238e9c45d8689ce86805 Mon Sep 17 00:00:00 2001 From: Tyler Hanks Date: Mon, 1 Jul 2024 15:53:15 -0400 Subject: [PATCH 1/2] Add simple usage example. --- Project.toml | 1 + README.md | 60 ++++++++++++++++++++++++++++++++++++++++++- src/FinSetAlgebras.jl | 4 +++ src/Objectives.jl | 5 ++++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 3f47b3c..08a0793 100644 --- a/Project.toml +++ b/Project.toml @@ -24,3 +24,4 @@ Optim = "1.9.4" SparseArrays = "1.10.0" StatsBase = "0.34.3" julia = "1.10" +Reexport = "1.2.2" diff --git a/README.md b/README.md index 8dcdbba..da6660c 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,64 @@ [![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 = 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) + +# 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). + + diff --git a/src/FinSetAlgebras.jl b/src/FinSetAlgebras.jl index abd17ba..acda186 100644 --- a/src/FinSetAlgebras.jl +++ b/src/FinSetAlgebras.jl @@ -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) + 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. diff --git a/src/Objectives.jl b/src/Objectives.jl index ad0b8ce..30c52bd 100644 --- a/src/Objectives.jl +++ b/src/Objectives.jl @@ -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 ################################# From 60ac06c203ed195f29cbfccee7b5a6437dfdedb7 Mon Sep 17 00:00:00 2001 From: Tyler Hanks Date: Mon, 1 Jul 2024 16:20:14 -0400 Subject: [PATCH 2/2] Hard code docs and test params --- README.md | 24 +++++++++++++++--------- src/FinSetAlgebras.jl | 2 +- test/Objectives.jl | 27 +++++++++++++++++---------- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index da6660c..5de96e9 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,22 @@ using AlgebraicOptimization using Catlab # Problem parameters. -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 +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 = rand(-1:0.01:1,5) -b = rand(-1:0.01:1,3) -c = rand(-1:0.01:1,4) +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. diff --git a/src/FinSetAlgebras.jl b/src/FinSetAlgebras.jl index acda186..bf87137 100644 --- a/src/FinSetAlgebras.jl +++ b/src/FinSetAlgebras.jl @@ -87,7 +87,7 @@ function Open{T}(o::T) where T Open{T}(domain(o), o, id(domain(o))) end -function Open{T}(o::T, m::FinFunction) +function Open{T}(o::T, m::FinFunction) where T Open{T}(domain(o), o, m) end diff --git a/test/Objectives.jl b/test/Objectives.jl index 059fbc7..615e236 100644 --- a/test/Objectives.jl +++ b/test/Objectives.jl @@ -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)))