From 5755750a09681ae6946ed344c43091c2737314f9 Mon Sep 17 00:00:00 2001 From: Michael Garstka Date: Mon, 6 May 2024 16:49:43 +0100 Subject: [PATCH] Add How-to-develop guide - Upgrade Documenter version to 1.4.1 - Fix bug in sum_abs_k_eigenvalues --- docs/Project.toml | 4 +- docs/src/contributing.md | 116 +++++++++++++++++- .../examples/closest_correlation_matrix.md | 26 ++-- docs/src/examples/lp.md | 30 ++--- docs/src/examples/qp.md | 22 ++-- docs/src/examples/svm_primal.md | 52 ++++---- docs/src/index.md | 9 +- examples/logistic_regression.jl | 2 +- examples/sum_abs_k_eigenvalues.jl | 2 +- 9 files changed, 184 insertions(+), 79 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 02af85a1..c15de95b 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -17,8 +17,8 @@ StatsFuns = "4c63d2b9-4356-54db-8cca-17b64c39e42c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] -Distributions = "^0.25" -Documenter = "0.27.24" +COSMO = "^0.8.9" +Documenter = "^1.4.1" Literate = "2.8" Plots = "^1.3.3" StatsFuns = "^1.3" diff --git a/docs/src/contributing.md b/docs/src/contributing.md index 0b033101..d351cc75 100644 --- a/docs/src/contributing.md +++ b/docs/src/contributing.md @@ -1,14 +1,118 @@ # Contributing -Contributions are always welcome: +This package is currently in maintenance mode. -* If you want to contribute features, bug fixes, etc, please take a look at our __Code Style Guide__ below -* Please report any issues and bugs that you encounter in [Issues](https://github.com/oxfordcontrol/ossdp/issues) -* As an open source project we are also interested in any projects and applications that use COSMO. Please let us know via email to: michael.garstka[at]eng.ox.ac.uk +We are trying to keep it compatible with new releases of JuMP/MOI. Contributions for bug fixes or new features are always welcome. Enhancement ideas are tagged as such in the [Issues](https://github.com/oxfordcontrol/ossdp/issues) section. If you are unsure how to develop, continue with the section below. -## Code Style Guide -The code in this repository follows the naming and style conventions of [Julia Base](https://docs.julialang.org/en/v1.0/manual/style-guide/#Style-Guide-1) with a few modifications. This style guide is heavily "inspired" by the guides of [John Myles White](https://github.com/johnmyleswhite/Style.jl) and [JuMP](http://www.juliaopt.org/JuMP.jl/latest/style). +# How-to develop +Let's assume you found a bug that you want to fix or develop a new feature. You have already downloaded the latest julia version as well as git. Follow these steps to make changes, test the changes, and open your first PR. This assumes that you don't yet have push access to the repository and need to fork. + +- Create a new fork in Github by clicking on `Fork` at the top right of the repository page and name it e.g. `migarstka/COSMO.jl`. (If you have repository push rights, you don't need to fork and can clone the repository directly.) + +- Clone the forked repository to your machine with: +```bash +$ git clone git@github.com:migarstka/COSMO.jl.git +``` + +- Navigate into the new folder and create a new branch: +```bash +$ git checkout -b mg/bug_fix +``` + +- Make your code changes by modifying the files in the repository. + +- As a first sanity check that nothing obvious is broken, you can run one of the examples in the `examples/` folder. Open julia and navigate into the git repository. Next, we want to activate and instantiate the repositories virtual environment. In the julia REPL type `]`, then to activate the current environment (i.e. current `Project.toml` file): + +```julia +(@v1.8) pkg> activate . +``` +Next, instantiate the environment, i.e. install the solver's dependencies defined in `Project.toml`: + +```julia +(COSMO) pkg> instantiate +``` + +- Since we instantiated the local envrionment, `COSMO` now refers to the local version that includes your changes. You can solve an example problem using the modified code: +```julia +julia> include("examples/qp.jl") +``` + +Let's assume you have made some changes and want to check whether the bug is fixed or the new feature broke anything. In both cases you should add some new tests for the change to `/test/UnitTest` and `/test/run_cosmo_tests.jl`. If you want to run existing tests follow the steps below. + +### How-to run tests +You can run all package-native tests with the main test file: +```julia +julia> include("test/run_cosmo_tests.jl") +``` +The full test suite also includes several MathOptInterface test problems that you can run with: +```julia +julia> include("test/runtests.jl") +``` +These take longer to complete, so I recommend using `test/run_cosmo_tests.jl` during iterative development and then switch to +`test/runtests.jl` once the previous tests pass. + +### Open a PR +Once all tests pass, you have added additional tests for your changes, and used docstrings to describe any new functionality in the code, you can open a PR. Commit your changes with a detailed commit message, and push your branch to the (fork) remote. +```bash +$ git add . +$ git commit +[Write commit message] +$ git push +``` +Then navigate to [https://github.com/oxfordcontrol/COSMO.jl/compare](https://github.com/oxfordcontrol/COSMO.jl/compare) and create a new PR with `base repository: oxfordcontrol/COSMO.jl` `base: master` and `head repository: [your-fork-repo]` `base:mg/bug_fix`. +Ask me or other collaborators to review it, before it can get merged. + +## Making a new release +Changes to `master` are periodically bundled in a new release version of the package. If you have permissions, you can make a new release by following these steps: + +1. Ensure all tests pass (locally and in Github Actions) +2. Locally run `bumpversion patch` (or `bumpversion minor` or `bumpversion major` depending on [semver](https://semver.org/) convention) to increment the version number across the repository (most importantly in `Project.toml`). +3. Check and commit the changes and use the commit message `Bump version to X`. +4. Push changes to remote with `git push`. +5. Let the julia package registry know about the new release by commenting `@JuliaRegistrator register` on the bump commit (like [here](https://github.com/oxfordcontrol/COSMO.jl/commit/20764ba075e1f598ec17990fb1721dcb5a3b418b#commitcomment-141674207)). +6. This will open a PR in the `JuliaRegistries` repository and a new release will typically be approved within hours. +7. Update the [CHANGELOG](https://github.com/oxfordcontrol/COSMO.jl/blob/master/CHANGELOG.md) and describe the changes bundled in the new release. + +## Updating COSMO's documentation +The code for COSMO's documentation resides in the `/docs` folder of the repository and is created with the [Documenter.jl package](https://documenter.juliadocs.org/stable/). There are two main versions of this documentation. **Stable** is based on the committed changes of the latest tagged release. **Dev** is based on the latest commit of `master`. + +The documentation has its own environment defined inside `/docs` (see `docs/Project.toml` file). To edit the documentation, edit the files in `/docs`. It's advised to run the documentation generation process locally do ensure the layout is as expected and that `Literate` code examples are built without errors. To generate the documentation locally, follow these steps: + +- Clone the repository and make your changes (as described in the section above). +- Navigate into the `/docs` folder and start julia. +- Activate and instantiate the (docs) environment, after `]`, type: + +```julia +(@v1.8) pkg> activate . +``` + +```julia +(docs) pkg> instantiate +``` + +- Build the documentation locally into the `/build` folder with + +```julia +include("make.jl") +``` + +(The first build will take several minutes. Subsequent changes will take ~30s.) + +- After the new documentation has been built, you can serve the files in `/build` in your browser, e.g. using a python webserver. Navigate into `/build` and run: +```bash +python3 -m http.server --bind localhost +``` + +- View the local documentation in your browser of choice at `http://127.0.0.1:8000/` + +- If you are satisfied with the results, open a PR for the changes as described in the section above. + + + +# Code Style Guide + +The code in this repository follows the naming and style conventions of [Julia Base](https://docs.julialang.org/en/v1.0/manual/style-guide/#Style-Guide-1) with a few modifications. This style guide is heavily "inspired" by the guides of [John Myles White](https://github.com/johnmyleswhite/Style.jl) and [JuMP](https://jump.dev/JuMP.jl/stable/developers/style/). ### Formatting * Use one tab when indenting a new block (except `module`) diff --git a/docs/src/examples/closest_correlation_matrix.md b/docs/src/examples/closest_correlation_matrix.md index 213dd4fb..3f628909 100644 --- a/docs/src/examples/closest_correlation_matrix.md +++ b/docs/src/examples/closest_correlation_matrix.md @@ -1,6 +1,6 @@ The source files for all examples can be found in [/examples](https://github.com/oxfordcontrol/COSMO.jl/tree/master/examples/). ```@meta -EditURL = "/../examples/closest_correlation_matrix.jl" +EditURL = "../../../examples/closest_correlation_matrix.jl" ``` # Closest Correlation Matrix @@ -17,22 +17,22 @@ The problem is given by: Notice that we use `JuMP` to model the problem. `COSMO` is chosen as the backend solver. And COSMO-specific settings are passed using the `optimizer_with_attributes()` function. -```@example closest_correlation_matrix +````@example closest_correlation_matrix using COSMO, JuMP, LinearAlgebra, SparseArrays, Test, Random -``` +```` -```@example closest_correlation_matrix +````@example closest_correlation_matrix rng = Random.MersenneTwister(12345); # create a random test matrix C n = 8; C = -1 .+ rand(rng, n, n) .* 2; c = vec(C); nothing #hide -``` +```` Define problem in `JuMP`: -```@example closest_correlation_matrix +````@example closest_correlation_matrix q = -vec(C); r = 0.5 * vec(C)' * vec(C); m = JuMP.Model(optimizer_with_attributes(COSMO.Optimizer, "verbose" => true)); @@ -42,20 +42,20 @@ x = vec(X); for i = 1:n @constraint(m, X[i, i] == 1.); end -``` +```` Solve the `JuMP` model with `COSMO` and query the solution `X_sol`: -```@example closest_correlation_matrix +````@example closest_correlation_matrix status = JuMP.optimize!(m); obj_val = JuMP.objective_value(m); X_sol = JuMP.value.(X); nothing #hide -``` +```` Double check result against known solution: -```@example closest_correlation_matrix +````@example closest_correlation_matrix known_opt_val = 12.5406 known_solution = [ 1.0 0.732562 -0.319491 -0.359985 -0.287543 -0.15578 0.0264044 -0.271438; @@ -67,11 +67,11 @@ known_solution = [ 0.0264044 0.126612 -0.248641 0.141151 0.137518 -0.731556 1.0 -0.436274; -0.271438 -0.187489 -0.395299 0.286088 0.0262425 0.0841783 -0.436274 1.0 ]; @test isapprox(obj_val, known_opt_val , atol=1e-3) -``` +```` -```@example closest_correlation_matrix +````@example closest_correlation_matrix @test norm(X_sol - known_solution, Inf) < 1e-3 -``` +```` --- diff --git a/docs/src/examples/lp.md b/docs/src/examples/lp.md index 1fe73032..e1e1ed2a 100644 --- a/docs/src/examples/lp.md +++ b/docs/src/examples/lp.md @@ -1,6 +1,6 @@ The source files for all examples can be found in [/examples](https://github.com/oxfordcontrol/COSMO.jl/tree/master/examples/). ```@meta -EditURL = "/../examples/lp.jl" +EditURL = "../../../examples/lp.jl" ``` # Linear Program @@ -16,22 +16,22 @@ We want to solve the following linear program with decision variable `x`: ``` The problem can be solved with `COSMO` in the following way: -```@example lp +````@example lp using COSMO, LinearAlgebra, SparseArrays, Test -``` +```` -```@example lp +````@example lp ##Define problem data: c = [1; 2; 3; 4.]; A = Matrix(1.0I, 4, 4); b = [10.; 10; 10; 10]; n = 4; nothing #hide -``` +```` Create the constraints $Ax + b \in \mathcal{K}$: -```@example lp +````@example lp # Ax <= b c1 = COSMO.Constraint(-A, b, COSMO.Nonnegatives); # x >= 1 @@ -41,34 +41,34 @@ c3 = COSMO.Constraint(1, -5, COSMO.Nonnegatives, n, 2:2); # x1 + x3 >= 4 c4 = COSMO.Constraint([1 0 1 0], -4, COSMO.Nonnegatives); nothing #hide -``` +```` Define matrix $P$ and vector $q$ for the objective function: -```@example lp +````@example lp P = spzeros(4, 4); q = c; nothing #hide -``` +```` Create, assemble and solve the model: -```@example lp +````@example lp settings = COSMO.Settings(verbose=true, eps_abs = 1e-4, eps_rel = 1e-5); model = COSMO.Model(); assemble!(model, P, q, [c1; c2; c3; c4], settings = settings); res = COSMO.optimize!(model) -``` +```` Compare the result to the known solution: -```@example lp +````@example lp @test isapprox(res.x[1:4], [3; 5; 1; 1], atol=1e-2, norm = (x -> norm(x, Inf))) -``` +```` -```@example lp +````@example lp @test isapprox(res.obj_val, 20.0, atol=1e-2) -``` +```` --- diff --git a/docs/src/examples/qp.md b/docs/src/examples/qp.md index 03c00102..3c6a66b3 100644 --- a/docs/src/examples/qp.md +++ b/docs/src/examples/qp.md @@ -1,6 +1,6 @@ The source files for all examples can be found in [/examples](https://github.com/oxfordcontrol/COSMO.jl/tree/master/examples/). ```@meta -EditURL = "/../examples/qp.jl" +EditURL = "../../../examples/qp.jl" ``` # Quadratic Program @@ -13,7 +13,7 @@ We want to solve the following quadratic program with decision variable `x`: ``` The problem can be solved with `COSMO` in the following way. Start by defining the problem data -```@example qp +````@example qp using COSMO, SparseArrays, LinearAlgebra, Test q = [1; 1.]; @@ -22,41 +22,41 @@ A = [1. 1; 1 0; 0 1]; l = [1.; 0; 0]; u = [1; 0.7; 0.7]; nothing #hide -``` +```` First, we decide to solve the problem with two one-sided constraints using `COSMO.Nonnegatives` as the convex set: -```@example qp +````@example qp Aa = [-A; A] ba = [u; -l] constraint1 = COSMO.Constraint(Aa, ba, COSMO.Nonnegatives); nothing #hide -``` +```` Next, we define the settings object, the model and then assemble everything: -```@example qp +````@example qp settings = COSMO.Settings(verbose=true); model = COSMO.Model(); assemble!(model, P, q, constraint1, settings = settings); res = COSMO.optimize!(model); nothing #hide -``` +```` Alternatively, we can also use two-sided constraints with `COSMO.Box`: -```@example qp +````@example qp constraint1 = COSMO.Constraint(A, zeros(3), COSMO.Box(l, u)); model = COSMO.Model(); assemble!(model, P, q, constraint1, settings = settings); res_box = COSMO.optimize!(model); nothing #hide -``` +```` Let's check that the solution is correct: -```@example qp +````@example qp @testset "QP Problem" begin @test norm(res.x[1:2] - [0.3; 0.7], Inf) < 1e-3 @test norm(res_box.x[1:2] - [0.3; 0.7], Inf) < 1e-3 @@ -64,7 +64,7 @@ Let's check that the solution is correct: @test abs(res_box.obj_val - 1.88) < 1e-3 end nothing -``` +```` --- diff --git a/docs/src/examples/svm_primal.md b/docs/src/examples/svm_primal.md index d75ac2c3..b737331d 100644 --- a/docs/src/examples/svm_primal.md +++ b/docs/src/examples/svm_primal.md @@ -1,6 +1,6 @@ The source files for all examples can be found in [/examples](https://github.com/oxfordcontrol/COSMO.jl/tree/master/examples/). ```@meta -EditURL = "/../examples/svm_primal.jl" +EditURL = "../../../examples/svm_primal.jl" ``` # Support Vector Machine @@ -9,12 +9,12 @@ We are showing how to solve a support vector machine problem with COSMO (and JuM ## Generating the Dataset We want to classify the points in this example dataset with $m = 100$ samples and $n = 2$ features: -```@example svm_primal +````@example svm_primal using Distributions: MvNormal using Plots, LinearAlgebra, SparseArrays, Random, Test -``` +```` -```@example svm_primal +````@example svm_primal # Generate dataset rng = Random.MersenneTwister(123); num_samples = 100; @@ -23,13 +23,13 @@ Xneg = rand(rng, MvNormal([-1.5, -1.5], 1.25), div(num_samples, 2))'; ypos = ones(div(num_samples, 2)); yneg = -ones(div(num_samples, 2)); nothing #hide -``` +```` -```@example svm_primal +````@example svm_primal # Plot dataset plot(Xpos[:, 1], Xpos[:, 2], color = :red, st=:scatter, markershape = :rect, label = "positive", xlabel = "x1", ylabel = "x2") plot!(Xneg[:, 1], Xneg[:, 2], color = :blue, st=:scatter, markershape = :circle, label = "negative") -``` +```` with samples $(x_1, x_2, \ldots, x_m) \in \mathbb{R}^2$ and labels $y_i \in \{-1,1\}$. @@ -58,22 +58,22 @@ This allows us to write the problems in standard QP format: ``` Next, we will remove the bias term $b$ by adding an initial feature $x_0 = -1$ to each sample (now: $n = 3$): -```@example svm_primal +````@example svm_primal X = [-ones(num_samples) [Xpos; Xneg]]; y = [ypos; yneg]; m, n = size(X) -``` +```` ## Modelling in JuMP We can model this problem using `JuMP` and then hand it to `COSMO`: -```@example svm_primal +````@example svm_primal using JuMP, COSMO -``` +```` -```@example svm_primal +````@example svm_primal λ = 1.0; # hyperparameter -model = JuMP.Model(with_optimizer(COSMO.Optimizer, verbose=true)); +model = JuMP.Model(optimizer_with_attributes(COSMO.Optimizer, "verbose" => true)); @variable(model, w[1:n]); @@ -81,13 +81,13 @@ model = JuMP.Model(with_optimizer(COSMO.Optimizer, verbose=true)); @objective(model, Min, w' * w + λ * ones(m)' * t); @constraint(model, diagm(0 => y) * X * w .+ t .- 1 .>= 0); status = JuMP.optimize!(model) -``` +```` The optimal weights $w = [w_0, w_1, w_2]^\top$ (where $w_0 = b$) are: -```@example svm_primal +````@example svm_primal w_opt = JuMP.value.(w) -``` +```` ## Plotting the hyperplane The separating hyperplane is defined by $w^\top x - b = 0$. To plot the hyperplane, we calculate $x_2$ over a range of $x_1$ values: @@ -95,50 +95,50 @@ The separating hyperplane is defined by $w^\top x - b = 0$. To plot the hyperpla x_2 = (-w_1 x_1 - w_0) / w_2, \text{ where } w_0 = b. ``` -```@example svm_primal +````@example svm_primal x1 = -4:0.1:4; x2 = (-w_opt[2] * x1 .- w_opt[1]) / w_opt[3] plot!(x1, x2, label = "SVM separator", legend = :topleft) -``` +```` ## Modelling with COSMO The problem can also be solved by transforming it directly into `COSMO`'s problem format. Define `COSMO``s $x$-variable to be $x=[w, t]^\top$ and choose $P$, $q$, accordingly: -```@example svm_primal +````@example svm_primal P = blockdiag(spdiagm(0 => ones(n)), spzeros(m, m)); q = [zeros(n); 0.5 * λ * ones(m)]; nothing #hide -``` +```` Next we transform the first constraint $y_i (w^\top x_i - b) \geq 1 - t_i, \quad \text{for } i = 1,\ldots, m$ into `COSMO`'s constraint format: $Ax + b \in \mathcal{K}$. -```@example svm_primal +````@example svm_primal A1 = [(spdiagm(0 => y) * X) spdiagm(0 => ones(m))]; b1 = -ones(m); cs1 = COSMO.Constraint(A1, b1, COSMO.Nonnegatives); nothing #hide -``` +```` It remains to specify the constraint $t_i \geq 0, \quad \text{for } i = 1,\ldots, m$: -```@example svm_primal +````@example svm_primal A2 = spdiagm(0 => ones(m)); b2 = zeros(m); cs2 = COSMO.Constraint(A2, b2, COSMO.Nonnegatives, m+n, n+1:m+n); nothing #hide -``` +```` Create, assemble and solve the `COSMO.Model`: -```@example svm_primal +````@example svm_primal model2 = COSMO.Model(); assemble!(model2, P, q, [cs1; cs2]); result2 = COSMO.optimize!(model2); w_opt2 = result2.x[1:3]; @test norm(w_opt2 - w_opt, Inf) < 1e-3 -``` +```` --- diff --git a/docs/src/index.md b/docs/src/index.md index 96aa9f63..6a9effa6 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -21,13 +21,14 @@ with decision variables ``x \in \mathbb{R}^n``, ``s \in \mathbb{R}^m`` and data * __Arbitrary precision types__: You can solve problems with any floating point precision. * __Open Source__: Our code is available on [GitHub](https://github.com/oxfordcontrol/COSMO.jl) and distributed under the Apache 2.0 Licence -!!! Audience: User - COSMO has both a native interface and can also be interfaced through JuMP. If you are interested in using COSMO to solve your optimization problem, we recommend using JuMP to define your problem. Install this package as described below, take a look at our [Examples](@ref) and the [JuMP documentation](https://jump.dev/JuMP.jl/stable/) for inspiration. +!!! tip -!!! Audience: Researcher / Developer + **For users:** COSMO has both a native interface and can also be interfaced through JuMP. If you are interested in using COSMO to solve your optimization problem, we recommend using JuMP to define your problem. Install this package as described below, take a look at our [Examples](@ref) and the [JuMP documentation](https://jump.dev/JuMP.jl/stable/) for inspiration. - If you are interested in COSMO's algorithm, chordal decomposition or acceleration methods take a look at [Method](@ref), [Chordal Decomposition](@ref), and [Acceleration](@ref). If you want low-level control over the algorithm, it can make sense to start with COSMO's native interface. +!!! tip + + **For researchers and developers:** If you are interested in COSMO's algorithm, chordal decomposition or acceleration methods take a look at [Method](@ref), [Chordal Decomposition](@ref), and [Acceleration](@ref). If you want low-level control over the algorithm, it can make sense to start with COSMO's native interface. Moreover, take a look at [Contributing](@ref) if you want to propose changes. ## Installation diff --git a/examples/logistic_regression.jl b/examples/logistic_regression.jl index dff3175a..4df6f180 100644 --- a/examples/logistic_regression.jl +++ b/examples/logistic_regression.jl @@ -252,4 +252,4 @@ res = COSMO.optimize!(model); #- using Test theta_cosmo = res.x[2:2+n_theta-1] -@test norm(theta_cosmo - theta) < 1e-10 +@test norm(theta_cosmo - theta) < 1e-4 diff --git a/examples/sum_abs_k_eigenvalues.jl b/examples/sum_abs_k_eigenvalues.jl index 112ba1cc..73c5b2bb 100644 --- a/examples/sum_abs_k_eigenvalues.jl +++ b/examples/sum_abs_k_eigenvalues.jl @@ -40,7 +40,7 @@ k_λ_abs = sum(sort(abs.(eigen(A).values), rev = true)[1:k]) # # Alternatively, we can solve the dual problem: #- -model = JuMP.Model(with_optimizer(COSMO.Optimizer, verbose=true)); +model = JuMP.Model(optimizer_with_attributes(COSMO.Optimizer, "verbose" => true)); @variable(model, V[1:n, 1:n], PSD); @variable(model, U[1:n, 1:n], PSD); @variable(model, z);