From ae7f61358fb003f1fd48d960fcddc94ba8fae133 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 19 May 2025 11:14:11 +1200 Subject: [PATCH 1/6] [docs] add MathOptChordalDecomposition --- docs/packages.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/packages.toml b/docs/packages.toml index 9226a0f91a4..e51a9814e96 100644 --- a/docs/packages.toml +++ b/docs/packages.toml @@ -183,6 +183,9 @@ user = "lanl-ansi" rev = "v0.1.9" extension = true +[MathOptChordalDecomposition] + user = "samuelsonric" + rev = "v0.2.0" [MathOptSymbolicAD] user = "lanl-ansi" rev = "v0.2.2" From 70f9f54628b02c95904e3c76d2a20f77ef33eec4 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 19 May 2025 14:33:20 +1200 Subject: [PATCH 2/6] Update --- docs/Project.toml | 4 +- docs/make.jl | 1 + .../tutorials/conic/chordal_decomposition.jl | 115 ++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 docs/src/tutorials/conic/chordal_decomposition.jl diff --git a/docs/Project.toml b/docs/Project.toml index 33f93e0893c..b44bac45fd9 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -27,6 +27,7 @@ LinearOperatorCollection = "a4a2c56f-fead-462a-a3ab-85921a5f2575" Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +MathOptChordalDecomposition = "691ff971-50ce-4a2e-a182-eb537bf792c3" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" MultiObjectiveAlgorithms = "0327d340-17cd-11ea-3e99-2fd5d98cecda" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" @@ -72,7 +73,8 @@ JSONSchema = "1.4.1" LinearOperatorCollection = "2.1.0" Literate = "2.20.1" MarkdownAST = "0.1.2" -MathOptInterface = "=1.38.1" +MathOptChordalDecomposition = "=0.2.0" +MathOptInterface = "=1.39.0" MultiObjectiveAlgorithms = "=1.4.0" PATHSolver = "=1.7.8" ParametricOptInterface = "0.11.0" diff --git a/docs/make.jl b/docs/make.jl index 991180a8fd2..a1202e213b8 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -394,6 +394,7 @@ const _PAGES = [ "tutorials/conic/arbitrary_precision.md", "tutorials/conic/start_values.md", "tutorials/conic/simple_examples.md", + "tutorials/conic/chordal_decomposition.md", "tutorials/conic/logistic_regression.md", "tutorials/conic/experiment_design.md", "tutorials/conic/min_ellipse.md", diff --git a/docs/src/tutorials/conic/chordal_decomposition.jl b/docs/src/tutorials/conic/chordal_decomposition.jl new file mode 100644 index 00000000000..753e4b99574 --- /dev/null +++ b/docs/src/tutorials/conic/chordal_decomposition.jl @@ -0,0 +1,115 @@ +# Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors #src +# This Source Code Form is subject to the terms of the Mozilla Public License #src +# v.2.0. If a copy of the MPL was not distributed with this file, You can #src +# obtain one at https://mozilla.org/MPL/2.0/. #src + +# # Chordal decomposition + +# The purpose of this tutorial is to show how to use [MathOptChordalDecomposition.jl](@ref) +# to improve the performance of models with PSD constraints. + +# ## Required packages + +# This tutorial uses the following packages: + +using JuMP +import Downloads +import LinearAlgebra +import MathOptChordalDecomposition +import SCS +import SparseArrays + +# ## Background + +# Chordal decomposition is a technique for decomposing a large PSD constraint +# into a set of smaller PSD constraints and some linear equality constraints. + +# If the original PSD constraint is sparse, the decomposed problem can be faster +# to solve than the original. + +# For more information on chordal decomposition, watch Michael Garstka's talk at +# [JuMP-dev 2019](https://www.youtube.com/watch?v=H4Q0ZXDqB70). + +# Some solvers, such as [Clarabel.jl](@ref) and [COSMO.jl](@ref) implement +# chordal decomposition internally. Others, such as [SCS.jl](@ref) do not +# implement chordal decomposition. + +# The Julia package [MathOptChordalDecomposition.jl](@ref) is a MathOptInterface +# layer that implements chordal decomposition of sparse semidefinite constraints. +# It can be used to wrap any solver which supports PSD constraints. + +# ## JuMP Model + +# To demonstrate the benefits of chordal decomposition, we use the `mcp124-1` +# model from [SDPLIB](http://euler.nmt.edu/~brian/sdplib/sdplib.html). + +file = "mcp124-1.dat-s" +dir = mktempdir() +filename = joinpath(dir, file) +Downloads.download( + "https://raw.githubusercontent.com/vsdp/SDPLIB/refs/heads/master/data/$file", + filename, +) +## This line is needed to work-around a bug in MathOptInterface v1.40 and +## earlier +write(filename, replace(read(filename, String), r"[,{}]" => " ")) +model = read_from_file(filename) + +# This model has 124 decision variables and one PSD constraint. This PSD +# constraint is sparse, which means that many elements of the matrix are zero. + +# To view the matrix, use [`all_constraints`](@ref) to get a list of the +# constraints, then use [`constraint_object`](@ref) to get the function and set +# form of the constraint: + +S = MOI.PositiveSemidefiniteConeTriangle +constraints = all_constraints(model, Vector{AffExpr}, S) +con = constraint_object(constraints[1]); +con.set +con.func + +# The constraint function is given in vectorized form. Use [`reshape_vector`](@ref) +# to convert it into a matrix: + +F = reshape_vector(con.func, SymmetricMatrixShape(con.set.side_dimension)) + +# The `F` matrix is dense, but many elements are zero. Use `SparseArrays.sparse` +# to turn it into a sparse matrix: + +A = SparseArrays.sparse(F) + +# The sparse matrix has 422 nonzeros, which is a density of 2.7%: + +SparseArrays.nnz(A) / size(A, 1)^2 + +# ## Solution speed + +# [SCS.jl](@ref) is a first-order solver that does not exploit the sparsity of +# PSD constraints. Let's solve it and see how long it took: + +set_optimizer(model, SCS.Optimizer) +@time optimize!(model) + +# In comparison, if we wrap `SCS.Optimizer` in `MathOptChordalDecomposition.Optimizer`, +# then the problem takes less than 1 second to solve: + +set_optimizer(model, () -> MathOptChordalDecomposition.Optimizer(SCS.Optimizer)) +@time optimize!(model) + +# The difference in performance is because of the chordal decomposition. The +# decomposed problem introduced new variables (there are now 1,155 variables +# instead of 124) and constraints (there are now 115 PSD constraints instead of +# one), but each PSD constraint is smaller than the original. + +decom = unsafe_backend(model) + +# With a bit of effort, we can compute the number of PSD constraints of each +# size: + +count_by_size = Dict{Int,Int}() +for ci in MOI.get(decom, MOI.ListOfConstraintIndices{MOI.VectorOfVariables,S}()) + set = MOI.get(decom, MOI.ConstraintSet(), ci) + n = set.side_dimension + count_by_size[n] = get(count_by_size, n, 0) + 1 +end +count_by_size From 7625f7d344f0b31d7e4590babbf6fbf9c13904ab Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Mon, 19 May 2025 15:30:39 +1200 Subject: [PATCH 3/6] Update chordal_decomposition.jl --- docs/src/tutorials/conic/chordal_decomposition.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/tutorials/conic/chordal_decomposition.jl b/docs/src/tutorials/conic/chordal_decomposition.jl index 753e4b99574..a5161768c60 100644 --- a/docs/src/tutorials/conic/chordal_decomposition.jl +++ b/docs/src/tutorials/conic/chordal_decomposition.jl @@ -30,8 +30,8 @@ import SparseArrays # For more information on chordal decomposition, watch Michael Garstka's talk at # [JuMP-dev 2019](https://www.youtube.com/watch?v=H4Q0ZXDqB70). -# Some solvers, such as [Clarabel.jl](@ref) and [COSMO.jl](@ref) implement -# chordal decomposition internally. Others, such as [SCS.jl](@ref) do not +# Some solvers, such as [Clarabel.jl](/packages/Clarabel) and [COSMO.jl](/packages/COSMO) +# implement chordal decomposition internally. Others, such as [SCS.jl](@ref) do not # implement chordal decomposition. # The Julia package [MathOptChordalDecomposition.jl](@ref) is a MathOptInterface From 24b4bb6b1e90959831213bfd53507b16c19469cb Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Mon, 19 May 2025 16:05:29 +1200 Subject: [PATCH 4/6] Update chordal_decomposition.jl --- docs/src/tutorials/conic/chordal_decomposition.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/src/tutorials/conic/chordal_decomposition.jl b/docs/src/tutorials/conic/chordal_decomposition.jl index a5161768c60..06dc3f3ff74 100644 --- a/docs/src/tutorials/conic/chordal_decomposition.jl +++ b/docs/src/tutorials/conic/chordal_decomposition.jl @@ -30,13 +30,15 @@ import SparseArrays # For more information on chordal decomposition, watch Michael Garstka's talk at # [JuMP-dev 2019](https://www.youtube.com/watch?v=H4Q0ZXDqB70). -# Some solvers, such as [Clarabel.jl](/packages/Clarabel) and [COSMO.jl](/packages/COSMO) -# implement chordal decomposition internally. Others, such as [SCS.jl](@ref) do not -# implement chordal decomposition. +# Some solvers, such as [Clarabel.jl](https://github.com/oxfordcontrol/Clarabel.jl) +# and [COSMO.jl](https://github.com/oxfordcontrol/COSMO.jl) implement chordal +# decomposition internally. Others, such as [SCS.jl](@ref) do not implement +# chordal decomposition. # The Julia package [MathOptChordalDecomposition.jl](@ref) is a MathOptInterface # layer that implements chordal decomposition of sparse semidefinite constraints. -# It can be used to wrap any solver which supports PSD constraints. +# It can be used to wrap any solver which supports PSD constraints and does not +# implement chordal decomposition internally. # ## JuMP Model From ae3f55f768e81715089c5db06d4ffe52383a2d0a Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 20 May 2025 10:10:57 +1200 Subject: [PATCH 5/6] Update --- docs/src/tutorials/conic/chordal_decomposition.jl | 8 +++++++- docs/styles/config/vocabularies/JuMP/accept.txt | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/src/tutorials/conic/chordal_decomposition.jl b/docs/src/tutorials/conic/chordal_decomposition.jl index 06dc3f3ff74..852f722ab28 100644 --- a/docs/src/tutorials/conic/chordal_decomposition.jl +++ b/docs/src/tutorials/conic/chordal_decomposition.jl @@ -68,6 +68,9 @@ S = MOI.PositiveSemidefiniteConeTriangle constraints = all_constraints(model, Vector{AffExpr}, S) con = constraint_object(constraints[1]); con.set + +#- + con.func # The constraint function is given in vectorized form. Use [`reshape_vector`](@ref) @@ -80,7 +83,7 @@ F = reshape_vector(con.func, SymmetricMatrixShape(con.set.side_dimension)) A = SparseArrays.sparse(F) -# The sparse matrix has 422 nonzeros, which is a density of 2.7%: +# The sparse matrix has 422 non-zeros, which is a density of 2.7%: SparseArrays.nnz(A) / size(A, 1)^2 @@ -115,3 +118,6 @@ for ci in MOI.get(decom, MOI.ListOfConstraintIndices{MOI.VectorOfVariables,S}()) count_by_size[n] = get(count_by_size, n, 0) + 1 end count_by_size + +# The largest PSD constraint is now of size 10, which is much smaller than the +# original 124-by-124 matrix. diff --git a/docs/styles/config/vocabularies/JuMP/accept.txt b/docs/styles/config/vocabularies/JuMP/accept.txt index cab071d6c9c..37d17eb0c58 100644 --- a/docs/styles/config/vocabularies/JuMP/accept.txt +++ b/docs/styles/config/vocabularies/JuMP/accept.txt @@ -206,6 +206,7 @@ Ferrolho Fourer Frobenius Garfinkel +Garstka Gleixner Goemans Grothey From a3c57f9e5c5c90068b97b090df6e01e7694bd700 Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 20 May 2025 11:22:37 +1200 Subject: [PATCH 6/6] Update --- .../tutorials/conic/chordal_decomposition.jl | 14 +- docs/src/tutorials/conic/mcp124-1.dat-s | 389 ++++++++++++++++++ 2 files changed, 390 insertions(+), 13 deletions(-) create mode 100644 docs/src/tutorials/conic/mcp124-1.dat-s diff --git a/docs/src/tutorials/conic/chordal_decomposition.jl b/docs/src/tutorials/conic/chordal_decomposition.jl index 852f722ab28..abbc1aeec94 100644 --- a/docs/src/tutorials/conic/chordal_decomposition.jl +++ b/docs/src/tutorials/conic/chordal_decomposition.jl @@ -13,8 +13,6 @@ # This tutorial uses the following packages: using JuMP -import Downloads -import LinearAlgebra import MathOptChordalDecomposition import SCS import SparseArrays @@ -45,17 +43,7 @@ import SparseArrays # To demonstrate the benefits of chordal decomposition, we use the `mcp124-1` # model from [SDPLIB](http://euler.nmt.edu/~brian/sdplib/sdplib.html). -file = "mcp124-1.dat-s" -dir = mktempdir() -filename = joinpath(dir, file) -Downloads.download( - "https://raw.githubusercontent.com/vsdp/SDPLIB/refs/heads/master/data/$file", - filename, -) -## This line is needed to work-around a bug in MathOptInterface v1.40 and -## earlier -write(filename, replace(read(filename, String), r"[,{}]" => " ")) -model = read_from_file(filename) +model = read_from_file(joinpath(@__DIR__, "mcp124-1.dat-s")) # This model has 124 decision variables and one PSD constraint. This PSD # constraint is sparse, which means that many elements of the matrix are zero. diff --git a/docs/src/tutorials/conic/mcp124-1.dat-s b/docs/src/tutorials/conic/mcp124-1.dat-s new file mode 100644 index 00000000000..a52ed0f7f7f --- /dev/null +++ b/docs/src/tutorials/conic/mcp124-1.dat-s @@ -0,0 +1,389 @@ + 124 + 1 + 124 +1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 +0 1 1 1 0.250000 +0 1 1 87 -0.250000 +0 1 2 2 0.500000 +0 1 2 45 -0.250000 +0 1 2 67 -0.250000 +0 1 3 3 0.250000 +0 1 3 8 -0.250000 +0 1 5 5 0.750000 +0 1 5 69 -0.250000 +0 1 5 109 -0.250000 +0 1 5 114 -0.250000 +0 1 6 6 0.500000 +0 1 6 54 -0.250000 +0 1 6 74 -0.250000 +0 1 7 7 0.250000 +0 1 7 27 -0.250000 +0 1 8 8 0.750000 +0 1 8 12 -0.250000 +0 1 8 38 -0.250000 +0 1 9 9 0.500000 +0 1 9 39 -0.250000 +0 1 9 78 -0.250000 +0 1 10 10 1.500000 +0 1 10 11 -0.250000 +0 1 10 18 -0.250000 +0 1 10 37 -0.250000 +0 1 10 63 -0.250000 +0 1 10 80 -0.250000 +0 1 10 85 -0.250000 +0 1 11 11 0.750000 +0 1 11 106 -0.250000 +0 1 11 119 -0.250000 +0 1 12 12 0.750000 +0 1 12 31 -0.250000 +0 1 12 44 -0.250000 +0 1 14 14 0.500000 +0 1 14 53 -0.250000 +0 1 14 69 -0.250000 +0 1 16 16 0.750000 +0 1 16 49 -0.250000 +0 1 16 75 -0.250000 +0 1 16 121 -0.250000 +0 1 17 17 1.0 +0 1 17 30 -0.250000 +0 1 17 41 -0.250000 +0 1 17 65 -0.250000 +0 1 17 103 -0.250000 +0 1 18 18 0.500000 +0 1 18 53 -0.250000 +0 1 19 19 0.250000 +0 1 19 47 -0.250000 +0 1 20 20 0.500000 +0 1 20 63 -0.250000 +0 1 20 122 -0.250000 +0 1 21 21 0.750000 +0 1 21 43 -0.250000 +0 1 21 54 -0.250000 +0 1 21 101 -0.250000 +0 1 22 22 0.250000 +0 1 22 124 -0.250000 +0 1 23 23 0.500000 +0 1 23 114 -0.250000 +0 1 23 117 -0.250000 +0 1 25 25 1.0 +0 1 25 45 -0.250000 +0 1 25 54 -0.250000 +0 1 25 103 -0.250000 +0 1 25 106 -0.250000 +0 1 26 26 0.250000 +0 1 26 38 -0.250000 +0 1 27 27 0.750000 +0 1 27 45 -0.250000 +0 1 27 74 -0.250000 +0 1 28 28 0.250000 +0 1 28 69 -0.250000 +0 1 29 29 0.250000 +0 1 29 40 -0.250000 +0 1 30 30 0.750000 +0 1 30 53 -0.250000 +0 1 30 115 -0.250000 +0 1 31 31 0.500000 +0 1 31 124 -0.250000 +0 1 32 32 0.250000 +0 1 32 54 -0.250000 +0 1 33 33 1.0 +0 1 33 43 -0.250000 +0 1 33 51 -0.250000 +0 1 33 66 -0.250000 +0 1 33 104 -0.250000 +0 1 34 34 1.0 +0 1 34 40 -0.250000 +0 1 34 98 -0.250000 +0 1 34 116 -0.250000 +0 1 34 120 -0.250000 +0 1 35 35 1.250000 +0 1 35 42 -0.250000 +0 1 35 98 -0.250000 +0 1 35 110 -0.250000 +0 1 35 111 -0.250000 +0 1 35 123 -0.250000 +0 1 36 36 0.500000 +0 1 36 108 -0.250000 +0 1 36 116 -0.250000 +0 1 37 37 0.750000 +0 1 37 82 -0.250000 +0 1 37 83 -0.250000 +0 1 38 38 1.0 +0 1 38 78 -0.250000 +0 1 38 113 -0.250000 +0 1 39 39 1.250000 +0 1 39 46 -0.250000 +0 1 39 86 -0.250000 +0 1 39 92 -0.250000 +0 1 39 114 -0.250000 +0 1 40 40 0.750000 +0 1 40 114 -0.250000 +0 1 41 41 0.500000 +0 1 41 51 -0.250000 +0 1 42 42 0.500000 +0 1 42 63 -0.250000 +0 1 43 43 1.250000 +0 1 43 45 -0.250000 +0 1 43 63 -0.250000 +0 1 43 91 -0.250000 +0 1 44 44 0.250000 +0 1 45 45 1.500000 +0 1 45 111 -0.250000 +0 1 45 114 -0.250000 +0 1 46 46 0.500000 +0 1 46 51 -0.250000 +0 1 47 47 0.750000 +0 1 47 59 -0.250000 +0 1 47 72 -0.250000 +0 1 49 49 1.0 +0 1 49 74 -0.250000 +0 1 49 96 -0.250000 +0 1 49 123 -0.250000 +0 1 50 50 0.250000 +0 1 50 123 -0.250000 +0 1 51 51 0.750000 +0 1 52 52 0.250000 +0 1 52 110 -0.250000 +0 1 53 53 1.250000 +0 1 53 79 -0.250000 +0 1 53 98 -0.250000 +0 1 54 54 1.500000 +0 1 54 100 -0.250000 +0 1 54 112 -0.250000 +0 1 55 55 1.0 +0 1 55 70 -0.250000 +0 1 55 75 -0.250000 +0 1 55 84 -0.250000 +0 1 55 120 -0.250000 +0 1 57 57 0.750000 +0 1 57 78 -0.250000 +0 1 57 121 -0.250000 +0 1 57 124 -0.250000 +0 1 59 59 0.500000 +0 1 59 101 -0.250000 +0 1 61 61 0.250000 +0 1 61 109 -0.250000 +0 1 62 62 0.250000 +0 1 62 112 -0.250000 +0 1 63 63 1.0 +0 1 64 64 0.500000 +0 1 64 92 -0.250000 +0 1 64 106 -0.250000 +0 1 65 65 0.250000 +0 1 66 66 0.500000 +0 1 66 111 -0.250000 +0 1 67 67 0.750000 +0 1 67 72 -0.250000 +0 1 67 81 -0.250000 +0 1 68 68 0.750000 +0 1 68 97 -0.250000 +0 1 68 118 -0.250000 +0 1 68 119 -0.250000 +0 1 69 69 0.750000 +0 1 70 70 0.750000 +0 1 70 102 -0.250000 +0 1 70 121 -0.250000 +0 1 71 71 0.250000 +0 1 71 112 -0.250000 +0 1 72 72 0.500000 +0 1 74 74 1.250000 +0 1 74 78 -0.250000 +0 1 74 82 -0.250000 +0 1 75 75 0.750000 +0 1 75 112 -0.250000 +0 1 76 76 0.250000 +0 1 76 82 -0.250000 +0 1 77 77 0.250000 +0 1 77 86 -0.250000 +0 1 78 78 1.250000 +0 1 78 117 -0.250000 +0 1 79 79 0.250000 +0 1 80 80 0.250000 +0 1 81 81 0.250000 +0 1 82 82 1.0 +0 1 82 110 -0.250000 +0 1 83 83 0.750000 +0 1 83 96 -0.250000 +0 1 83 120 -0.250000 +0 1 84 84 0.250000 +0 1 85 85 0.750000 +0 1 85 93 -0.250000 +0 1 85 108 -0.250000 +0 1 86 86 0.750000 +0 1 86 124 -0.250000 +0 1 87 87 1.250000 +0 1 87 100 -0.250000 +0 1 87 102 -0.250000 +0 1 87 109 -0.250000 +0 1 87 118 -0.250000 +0 1 89 89 0.250000 +0 1 89 93 -0.250000 +0 1 91 91 0.250000 +0 1 92 92 0.750000 +0 1 92 112 -0.250000 +0 1 93 93 0.500000 +0 1 94 94 0.250000 +0 1 94 109 -0.250000 +0 1 95 95 0.500000 +0 1 95 110 -0.250000 +0 1 95 120 -0.250000 +0 1 96 96 0.500000 +0 1 97 97 0.250000 +0 1 98 98 0.750000 +0 1 99 99 0.250000 +0 1 99 122 -0.250000 +0 1 100 100 0.500000 +0 1 101 101 0.500000 +0 1 102 102 0.750000 +0 1 102 120 -0.250000 +0 1 103 103 0.750000 +0 1 103 116 -0.250000 +0 1 104 104 0.500000 +0 1 104 119 -0.250000 +0 1 105 105 0.250000 +0 1 105 115 -0.250000 +0 1 106 106 0.750000 +0 1 108 108 0.500000 +0 1 109 109 1.250000 +0 1 109 112 -0.250000 +0 1 110 110 1.500000 +0 1 110 112 -0.250000 +0 1 110 123 -0.250000 +0 1 111 111 0.750000 +0 1 112 112 1.750000 +0 1 113 113 0.250000 +0 1 114 114 1.250000 +0 1 115 115 0.500000 +0 1 116 116 0.750000 +0 1 117 117 0.500000 +0 1 118 118 0.500000 +0 1 119 119 0.750000 +0 1 120 120 1.250000 +0 1 121 121 0.750000 +0 1 122 122 0.500000 +0 1 123 123 1.0 +0 1 124 124 1.0 +1 1 1 1 1.0 +2 1 2 2 1.0 +3 1 3 3 1.0 +4 1 4 4 1.0 +5 1 5 5 1.0 +6 1 6 6 1.0 +7 1 7 7 1.0 +8 1 8 8 1.0 +9 1 9 9 1.0 +10 1 10 10 1.0 +11 1 11 11 1.0 +12 1 12 12 1.0 +13 1 13 13 1.0 +14 1 14 14 1.0 +15 1 15 15 1.0 +16 1 16 16 1.0 +17 1 17 17 1.0 +18 1 18 18 1.0 +19 1 19 19 1.0 +20 1 20 20 1.0 +21 1 21 21 1.0 +22 1 22 22 1.0 +23 1 23 23 1.0 +24 1 24 24 1.0 +25 1 25 25 1.0 +26 1 26 26 1.0 +27 1 27 27 1.0 +28 1 28 28 1.0 +29 1 29 29 1.0 +30 1 30 30 1.0 +31 1 31 31 1.0 +32 1 32 32 1.0 +33 1 33 33 1.0 +34 1 34 34 1.0 +35 1 35 35 1.0 +36 1 36 36 1.0 +37 1 37 37 1.0 +38 1 38 38 1.0 +39 1 39 39 1.0 +40 1 40 40 1.0 +41 1 41 41 1.0 +42 1 42 42 1.0 +43 1 43 43 1.0 +44 1 44 44 1.0 +45 1 45 45 1.0 +46 1 46 46 1.0 +47 1 47 47 1.0 +48 1 48 48 1.0 +49 1 49 49 1.0 +50 1 50 50 1.0 +51 1 51 51 1.0 +52 1 52 52 1.0 +53 1 53 53 1.0 +54 1 54 54 1.0 +55 1 55 55 1.0 +56 1 56 56 1.0 +57 1 57 57 1.0 +58 1 58 58 1.0 +59 1 59 59 1.0 +60 1 60 60 1.0 +61 1 61 61 1.0 +62 1 62 62 1.0 +63 1 63 63 1.0 +64 1 64 64 1.0 +65 1 65 65 1.0 +66 1 66 66 1.0 +67 1 67 67 1.0 +68 1 68 68 1.0 +69 1 69 69 1.0 +70 1 70 70 1.0 +71 1 71 71 1.0 +72 1 72 72 1.0 +73 1 73 73 1.0 +74 1 74 74 1.0 +75 1 75 75 1.0 +76 1 76 76 1.0 +77 1 77 77 1.0 +78 1 78 78 1.0 +79 1 79 79 1.0 +80 1 80 80 1.0 +81 1 81 81 1.0 +82 1 82 82 1.0 +83 1 83 83 1.0 +84 1 84 84 1.0 +85 1 85 85 1.0 +86 1 86 86 1.0 +87 1 87 87 1.0 +88 1 88 88 1.0 +89 1 89 89 1.0 +90 1 90 90 1.0 +91 1 91 91 1.0 +92 1 92 92 1.0 +93 1 93 93 1.0 +94 1 94 94 1.0 +95 1 95 95 1.0 +96 1 96 96 1.0 +97 1 97 97 1.0 +98 1 98 98 1.0 +99 1 99 99 1.0 +100 1 100 100 1.0 +101 1 101 101 1.0 +102 1 102 102 1.0 +103 1 103 103 1.0 +104 1 104 104 1.0 +105 1 105 105 1.0 +106 1 106 106 1.0 +107 1 107 107 1.0 +108 1 108 108 1.0 +109 1 109 109 1.0 +110 1 110 110 1.0 +111 1 111 111 1.0 +112 1 112 112 1.0 +113 1 113 113 1.0 +114 1 114 114 1.0 +115 1 115 115 1.0 +116 1 116 116 1.0 +117 1 117 117 1.0 +118 1 118 118 1.0 +119 1 119 119 1.0 +120 1 120 120 1.0 +121 1 121 121 1.0 +122 1 122 122 1.0 +123 1 123 123 1.0 +124 1 124 124 1.0