From d69ac51cfc6a92078399d246350e07d78146f4d9 Mon Sep 17 00:00:00 2001 From: "Documenter.jl" Date: Wed, 19 Jun 2024 09:31:39 +0000 Subject: [PATCH] build based on 10e16a6 --- dev/.documenter-siteinfo.json | 2 +- dev/0-quickstart/index.html | 2 +- dev/1-metabolic-modeling.jl | 38 +++++++- dev/1-metabolic-modeling/index.html | 17 +++- dev/2-quadratic-optimization/index.html | 2 +- dev/3-mixed-integer-optimization/index.html | 2 +- dev/4-functional-tree-processing/index.html | 2 +- dev/5-jump-integration/index.html | 2 +- dev/index.html | 2 +- dev/objects.inv | Bin 2405 -> 2442 bytes dev/reference/index.html | 102 ++++++++++---------- dev/search_index.js | 2 +- 12 files changed, 109 insertions(+), 64 deletions(-) diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index 53c7062..8f1a469 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.10.4","generation_timestamp":"2024-06-06T07:27:15","documenter_version":"1.4.1"}} \ No newline at end of file +{"documenter":{"julia_version":"1.10.4","generation_timestamp":"2024-06-19T09:31:35","documenter_version":"1.4.1"}} \ No newline at end of file diff --git a/dev/0-quickstart/index.html b/dev/0-quickstart/index.html index 2a18e41..1e88eb8 100644 --- a/dev/0-quickstart/index.html +++ b/dev/0-quickstart/index.html @@ -123,4 +123,4 @@ :bread => 8.86076 :weizen => 2.05696

How much extra resources is consumed by the factory?

optimal_c.factory.resources
ConstraintTrees.Tree{Float64} with 2 elements:
   :heat  => 50.4747
-  :water => 38.2911

And what is the factory profit in the end?

optimal_c.factory.profit
361.5506329113926

This page was generated using Literate.jl.

+ :water => 38.2911

And what is the factory profit in the end?

optimal_c.factory.profit
361.5506329113926

This page was generated using Literate.jl.

diff --git a/dev/1-metabolic-modeling.jl b/dev/1-metabolic-modeling.jl index c2f05f1..f685c68 100644 --- a/dev/1-metabolic-modeling.jl +++ b/dev/1-metabolic-modeling.jl @@ -357,14 +357,15 @@ Dict(k => v.fluxes.R_BIOMASS_Ecoli_core_w_GAM for (k, v) in result.community) # change at a single place of the tree may easily change values also in other # parts of any trees, including completely different trees # - the "convenient way" of making sure that the above problem never happens is -# to deep-copy the whole tree structure, which is typically quite detrimental -# to memory use and program efficiency +# to copy-on-write the whole tree structure, which is typically quite +# detrimental to memory use and program efficiency # #md # !!! danger "Rules of thumb for safe use of in-place modification" #md # Only use the in-place modifications if: #md # - there is code that explicitly makes sure there is no false sharing via references, e.g. using a deep copy #md # - the in-place modifications are the last thing happening to the constraint tree before it is used by the solver #md # - the in-place modification code is not a part of a re-usable library +#md # - you are using a suitable wrapper interface such as [Accessors.jl](https://github.com/JuliaObjects/Accessors.jl) # # Now, if you are completely sure that ignoring the robustness guidelines will # help your code, you can do the in-place tree modifications quite easily using @@ -391,6 +392,39 @@ result.exchanges @test result_with_more_oxygen.exchanges.oxygen < -19.0 #src +# ### Alternative: Using Accessors.jl +# +# Accessors.jl implement a "lensy" way to update immutable data structures. +# That comes with a nice outcome of doing the right amount of shallow copyies +# for you automatically, thus avoiding much of the technical danger of in-place +# modifications. (You still lose the equational reasoning on your code, but that +# may not be an issue at all in usual codebases.) +# +# Accessors interface is used simply through macros `@set` (which sets a deeply +# nested field in a structure, returning a modified copy), or with `@reset` +# which automatically "assigns" the result back to the original variable: + +using Accessors + +c = @set c.exchanges.biomass.bound = C.Between(-50, 50) + +# The above code is equivalent to: + +@reset c.exchanges.biomass.bound = C.Between(-50, 50) + +# ...and it is also possible to use string and symbol indexes to pick the +# individual tree items: + +@reset c[:exchanges]["biomass"].bound = C.Between(-50, 50) + +# All of these operations give us: + +c.exchanges.biomass + +@test typeof(c.exchanges.biomass.bound) == C.Between #src +@test c.exchanges.biomass.bound.lower == -50 #src +@test c.exchanges.biomass.bound.upper == 50 #src + # ## Seeing the differences between the results # # ConstraintTrees.jl defines its own version of `zip` function that can apply a diff --git a/dev/1-metabolic-modeling/index.html b/dev/1-metabolic-modeling/index.html index dcfdbd1..17a8c0d 100644 --- a/dev/1-metabolic-modeling/index.html +++ b/dev/1-metabolic-modeling/index.html @@ -197,13 +197,24 @@ :biomass => 0.469658 :oxygen => -10.0

Finally, we can iterate over all species in the small community and see how much biomass was actually contributed by each:

Dict(k => v.fluxes.R_BIOMASS_Ecoli_core_w_GAM for (k, v) in result.community)
Dict{Symbol, Float64} with 2 entries:
   :species2 => 0.442232
-  :species1 => 0.027425

Modifying constraint systems in-place

Constraint trees can be modified in-place in a way that allows you to easily change small values in the trees without reconstructing them from the ground up.

Although in-place modification is extremely convenient and looks much easier than rebuilding the tree, it may be very detrimental to the robustness and efficiency of the programs, for several reasons:

Rules of thumb for safe use of in-place modification

Only use the in-place modifications if:

  • there is code that explicitly makes sure there is no false sharing via references, e.g. using a deep copy
  • the in-place modifications are the last thing happening to the constraint tree before it is used by the solver
  • the in-place modification code is not a part of a re-usable library

Now, if you are completely sure that ignoring the robustness guidelines will help your code, you can do the in-place tree modifications quite easily using both dot-access and array-index syntax.

You can thus, e.g., set a single bound:

c.exchanges.oxygen.bound = C.Between(-20.0, 20.0)
ConstraintTrees.Between(-20.0, 20.0)

...or rebuild a whole constraint (using a tuple shortcut for Between):

c.exchanges.biomass = C.Constraint(c.exchanges.biomass.value, (-20, 20))
ConstraintTrees.Constraint(ConstraintTrees.LinearValue([192], [1.0]), ConstraintTrees.Between(-20.0, 20.0))

...or even add new constraints, here using the index syntax for demonstration:

c[:exchanges][:production_is_zero] = C.Constraint(c.exchanges.biomass.value, 0)
ConstraintTrees.Constraint(ConstraintTrees.LinearValue([192], [1.0]), ConstraintTrees.EqualTo(0.0))

...or remove some constraints (this erases the constraint that was added just above):

delete!(c.exchanges, :production_is_zero)
DataStructures.SortedDict{Symbol, Union{ConstraintTrees.Constraint, ConstraintTrees.Tree{ConstraintTrees.Constraint}}, Base.Order.ForwardOrdering} with 2 entries:
+  :species1 => 0.027425

Modifying constraint systems in-place

Constraint trees can be modified in-place in a way that allows you to easily change small values in the trees without reconstructing them from the ground up.

Although in-place modification is extremely convenient and looks much easier than rebuilding the tree, it may be very detrimental to the robustness and efficiency of the programs, for several reasons:

Rules of thumb for safe use of in-place modification

Only use the in-place modifications if:

  • there is code that explicitly makes sure there is no false sharing via references, e.g. using a deep copy
  • the in-place modifications are the last thing happening to the constraint tree before it is used by the solver
  • the in-place modification code is not a part of a re-usable library
  • you are using a suitable wrapper interface such as Accessors.jl

Now, if you are completely sure that ignoring the robustness guidelines will help your code, you can do the in-place tree modifications quite easily using both dot-access and array-index syntax.

You can thus, e.g., set a single bound:

c.exchanges.oxygen.bound = C.Between(-20.0, 20.0)
ConstraintTrees.Between(-20.0, 20.0)

...or rebuild a whole constraint (using a tuple shortcut for Between):

c.exchanges.biomass = C.Constraint(c.exchanges.biomass.value, (-20, 20))
ConstraintTrees.Constraint(ConstraintTrees.LinearValue([192], [1.0]), ConstraintTrees.Between(-20.0, 20.0))

...or even add new constraints, here using the index syntax for demonstration:

c[:exchanges][:production_is_zero] = C.Constraint(c.exchanges.biomass.value, 0)
ConstraintTrees.Constraint(ConstraintTrees.LinearValue([192], [1.0]), ConstraintTrees.EqualTo(0.0))

...or remove some constraints (this erases the constraint that was added just above):

delete!(c.exchanges, :production_is_zero)
DataStructures.SortedDict{Symbol, Union{ConstraintTrees.Constraint, ConstraintTrees.Tree{ConstraintTrees.Constraint}}, Base.Order.ForwardOrdering} with 2 entries:
   :biomass => ConstraintTrees.Constraint(ConstraintTrees.LinearValue(#= ... =#), ConstraintTrees.Between(-20.0, 20.0))
   :oxygen  => ConstraintTrees.Constraint(ConstraintTrees.LinearValue(#= ... =#), ConstraintTrees.Between(-20.0, 20.0))

In the end, the flux optimization yields an expectably different result:

result_with_more_oxygen =
     C.substitute_values(c, optimized_vars(c, c.exchanges.biomass.value, GLPK.Optimizer))
 result.exchanges
ConstraintTrees.Tree{Float64} with 2 elements:
   :biomass => 0.469658
-  :oxygen  => -10.0

Seeing the differences between the results

ConstraintTrees.jl defines its own version of zip function that can apply a function to the contents of several trees, "zipping" them over the same keys in the structure. This is vaguely similar but otherwise not related to the zip from Julia base (similarly, ConstraintTrees.jl have their own specific map).

In practice, this allows you to create combined trees with various nice properties very quickly. For example, you can find how much the values have changed between our two communities:

C.zip((x, y) -> y - x, result, result_with_more_oxygen, Float64)
ConstraintTrees.Tree{Float64} with 3 elements:
+  :oxygen  => -10.0

Alternative: Using Accessors.jl

Accessors.jl implement a "lensy" way to update immutable data structures. That comes with a nice outcome of doing the right amount of shallow copyies for you automatically, thus avoiding much of the technical danger of in-place modifications. (You still lose the equational reasoning on your code, but that may not be an issue at all in usual codebases.)

Accessors interface is used simply through macros @set (which sets a deeply nested field in a structure, returning a modified copy), or with @reset which automatically "assigns" the result back to the original variable:

using Accessors
+
+c = @set c.exchanges.biomass.bound = C.Between(-50, 50)
ConstraintTrees.Tree{ConstraintTrees.Constraint} with 3 elements:
+  :community            => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#= 2 elements =#)
+  :exchange_constraints => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#= 2 elements =#)
+  :exchanges            => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#= 2 elements =#)

The above code is equivalent to:

@reset c.exchanges.biomass.bound = C.Between(-50, 50)
ConstraintTrees.Tree{ConstraintTrees.Constraint} with 3 elements:
+  :community            => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#= 2 elements =#)
+  :exchange_constraints => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#= 2 elements =#)
+  :exchanges            => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#= 2 elements =#)

...and it is also possible to use string and symbol indexes to pick the individual tree items:

@reset c[:exchanges]["biomass"].bound = C.Between(-50, 50)
ConstraintTrees.Tree{ConstraintTrees.Constraint} with 3 elements:
+  :community            => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#= 2 elements =#)
+  :exchange_constraints => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#= 2 elements =#)
+  :exchanges            => ConstraintTrees.Tree{ConstraintTrees.Constraint}(#= 2 elements =#)

All of these operations give us:

c.exchanges.biomass
ConstraintTrees.Constraint(ConstraintTrees.LinearValue([192], [1.0]), ConstraintTrees.Between(-50.0, 50.0))

Seeing the differences between the results

ConstraintTrees.jl defines its own version of zip function that can apply a function to the contents of several trees, "zipping" them over the same keys in the structure. This is vaguely similar but otherwise not related to the zip from Julia base (similarly, ConstraintTrees.jl have their own specific map).

In practice, this allows you to create combined trees with various nice properties very quickly. For example, you can find how much the values have changed between our two communities:

C.zip((x, y) -> y - x, result, result_with_more_oxygen, Float64)
ConstraintTrees.Tree{Float64} with 3 elements:
   :community            => ConstraintTrees.Tree{Float64}(#= 2 elements =#)
   :exchange_constraints => ConstraintTrees.Tree{Float64}(#= 2 elements =#)
   :exchanges            => ConstraintTrees.Tree{Float64}(#= 2 elements =#)

The result is again a Tree, with the contained type specified by the last argument (Float64 in this case). We can explore it right away as the other result trees. Also, it is possible to call this kind of function using the Julia do notation, making the syntax a bit neater:

difference = C.zip(result, result_with_more_oxygen, Float64) do x, y
@@ -260,4 +271,4 @@
   :R_D_LACt2                  => false
   :R_ENO                      => true
   :R_ETOHt2r                  => false
-  ⋮                           => ⋮

More high-level functions like zip are described in an example on functional tree processing.


This page was generated using Literate.jl.

+ ⋮ => ⋮

More high-level functions like zip are described in an example on functional tree processing.


This page was generated using Literate.jl.

diff --git a/dev/2-quadratic-optimization/index.html b/dev/2-quadratic-optimization/index.html index 6ecfa1d..b017371 100644 --- a/dev/2-quadratic-optimization/index.html +++ b/dev/2-quadratic-optimization/index.html @@ -57,4 +57,4 @@ :line => ConstraintTrees.Tree{Float64}(#= 1 element =#) :objective => 58.9726

If the optimization worked well, we can nicely get out the position of the closest point to the line that is in the elliptical area:

(st.ellipse.point.x, st.ellipse.point.y)
(1.4143143342289577, 9.292943593846765)

...as well as the position on the line that is closest to the ellipse:

st.line.point
ConstraintTrees.Tree{Float64} with 2 elements:
   :x => 4.84863
-  :y => 2.42431

...and, with a little bit of extra math, the minimized distance:

sqrt(st.objective)
7.679360836187363

This page was generated using Literate.jl.

+ :y => 2.42431

...and, with a little bit of extra math, the minimized distance:

sqrt(st.objective)
7.679360836187363

This page was generated using Literate.jl.

diff --git a/dev/3-mixed-integer-optimization/index.html b/dev/3-mixed-integer-optimization/index.html index 0a8ecdb..0c17460 100644 --- a/dev/3-mixed-integer-optimization/index.html +++ b/dev/3-mixed-integer-optimization/index.html @@ -78,4 +78,4 @@ ).sides
ConstraintTrees.Tree{Float64} with 3 elements:
   :a => 3.0
   :b => 4.0
-  :c => 5.0

This page was generated using Literate.jl.

+ :c => 5.0

This page was generated using Literate.jl.

diff --git a/dev/4-functional-tree-processing/index.html b/dev/4-functional-tree-processing/index.html index c65efb1..1559224 100644 --- a/dev/4-functional-tree-processing/index.html +++ b/dev/4-functional-tree-processing/index.html @@ -117,4 +117,4 @@ y/x = ConstraintTrees.Constraint(ConstraintTrees.LinearValue([3], [1.0]), ConstraintTrees.Between(-0.010000000000000009, 1.99)) y/y = ConstraintTrees.Constraint(ConstraintTrees.LinearValue([4], [1.0]), ConstraintTrees.EqualTo(0.78)) z/x = ConstraintTrees.Constraint(ConstraintTrees.LinearValue([5], [1.0]), ConstraintTrees.Between(-0.09999999999999998, 1.9)) -z/y = ConstraintTrees.Constraint(ConstraintTrees.LinearValue([6], [1.0]), ConstraintTrees.EqualTo(0.8))

To prevent uncertainty, both functions always traverse the keys in sorted order.


This page was generated using Literate.jl.

+z/y = ConstraintTrees.Constraint(ConstraintTrees.LinearValue([6], [1.0]), ConstraintTrees.EqualTo(0.8))

To prevent uncertainty, both functions always traverse the keys in sorted order.


This page was generated using Literate.jl.

diff --git a/dev/5-jump-integration/index.html b/dev/5-jump-integration/index.html index 83b68f5..af014af 100644 --- a/dev/5-jump-integration/index.html +++ b/dev/5-jump-integration/index.html @@ -52,4 +52,4 @@ end qvalue = 123 + (x.vars.x1.value + x.vars.x2.value) * (x.vars.x3.value - 321) -jump_qvalue = substitute_jump(qvalue, V)

\[ V_{1}\times V_{3} + V_{2}\times V_{3} - 321 V_{1} - 321 V_{2} + 123 \]


This page was generated using Literate.jl.

+jump_qvalue = substitute_jump(qvalue, V)

\[ V_{1}\times V_{3} + V_{2}\times V_{3} - 321 V_{1} - 321 V_{2} + 123 \]


This page was generated using Literate.jl.

diff --git a/dev/index.html b/dev/index.html index 377ecbc..48ecc3d 100644 --- a/dev/index.html +++ b/dev/index.html @@ -1,2 +1,2 @@ -README · ConstraintTrees.jl

ConstraintTrees.jl

ConstraintTrees.ConstraintTreesModule

Package ConstraintTrees.jl provides a simple data structure ConstraintTree for organizing the contents of linear and quadratic constrained optimization problems. As a main goal, it abstracts over the distinction between constraints and variables, allowing much tidier representation for many kinds of complex constraint systems.

The primary purpose of ConstraintTrees.jl is to work with COBREXA.jl; but the package is otherwise completely independent, lightweight, dependency-free and usecase-agnostic. Generally, it is intended to be used with JuMP and the documentation uses JuMP for demonstrations, but any other solver framework will do just as well.

The package is structured as follows:

  • There is no representation for variables in the model; instead, values depend on anonymous numbered variables, and, if suitable, special named values may "implicitly" serve as representations for variables. This assumption erases the distinction between a "simple" variable and a complex derived linear combination, allowing more freedom in model construction.
  • Variables may be combined into LinearValues and QuadraticValues, which are affine combinations and quadratic-affine combinations (respecitively) of values of some selected variables.
  • Values may be bounded to an interval or exact value using a Constraint
  • A collection of named Constraints is called a ConstraintTree; it behaves mostly as a specialized Symbol-keyed dictionary.
  • ConstraintTrees can be very easily organized into subdirectories, combined and made independent on each other using operators ^, *, and + – this forms the basis of the "tidy" algebra of constraints.
  • A variable assignment, which is typically the "solution" for a given constraint tree, can be combined with a ConstraintTree to create a "value tree" via substitute_values, which enables browsing of the optimization results in the very same structure as the input ConstraintTree.

You can follow the examples in documentation and the docstrings of package contents for more details.

source
+README · ConstraintTrees.jl

ConstraintTrees.jl

ConstraintTrees.ConstraintTreesModule

Package ConstraintTrees.jl provides a simple data structure ConstraintTree for organizing the contents of linear and quadratic constrained optimization problems. As a main goal, it abstracts over the distinction between constraints and variables, allowing much tidier representation for many kinds of complex constraint systems.

The primary purpose of ConstraintTrees.jl is to work with COBREXA.jl; but the package is otherwise completely independent, lightweight, dependency-free and usecase-agnostic. Generally, it is intended to be used with JuMP and the documentation uses JuMP for demonstrations, but any other solver framework will do just as well.

The package is structured as follows:

  • There is no representation for variables in the model; instead, values depend on anonymous numbered variables, and, if suitable, special named values may "implicitly" serve as representations for variables. This assumption erases the distinction between a "simple" variable and a complex derived linear combination, allowing more freedom in model construction.
  • Variables may be combined into LinearValues and QuadraticValues, which are affine combinations and quadratic-affine combinations (respecitively) of values of some selected variables.
  • Values may be bounded to an interval or exact value using a Constraint
  • A collection of named Constraints is called a ConstraintTree; it behaves mostly as a specialized Symbol-keyed dictionary.
  • ConstraintTrees can be very easily organized into subdirectories, combined and made independent on each other using operators ^, *, and + – this forms the basis of the "tidy" algebra of constraints.
  • A variable assignment, which is typically the "solution" for a given constraint tree, can be combined with a ConstraintTree to create a "value tree" via substitute_values, which enables browsing of the optimization results in the very same structure as the input ConstraintTree.

You can follow the examples in documentation and the docstrings of package contents for more details.

source
diff --git a/dev/objects.inv b/dev/objects.inv index 65efe931d53251584c52de77b77a5dfd999fd329..a4e2a0d247b4e1048bbad2dfed2295e82946a31f 100644 GIT binary patch delta 2337 zcmV++3EuYQ5{eU$NC7gDNj`sVZ`(Ey{;prap)C2-IZop{8sRvr=rt_{CAh}v`m;eu z4l*R6@0YJH-uw`}z}q?t*%OlBs-P6_iaexc*)f!BoF?#1JGr`{hz%@JJ2Tk_KjC+E zx)X{Qy><29K0&9nsz85xea45lx<3f4F1WB_Y994 ztPNeheZn_dZ92X&80sKP!V2Rx!-Q7|2hm^y6+?JA7};0^t*L>N3LZ(++4F@9cXsj% zL!O=dI)ytH(m#LLPB4eS`4((e#AK7ejoAF=e!a~LQ*kGyfcaW@$s_p|HE_}!4EW%J zLBkp?G2BuDAj@$wG8@BR4ime_eruK<02(9z#dZr^BHhlpj=lFtF-<9}=+)_;NmV{Ma?7wkQoePd_A^^@#UUng8C(ktV^hm0lBd!Qyvk*Z_ z5hMH9HrVh_J+a8Gy?`51a4sUlZen^lpTc>D7;eJZ8$-b05|m>WfV6e%qu5I1a~{cj zCK59h@@0RAI~`D$9X<@k5*5tBKsk^EGs0MVj{o8evj~~z=0$&o-RCq2w#^n0T?pdu zk^Y`J;*J!ZF#8zqCI^{7(Sr<7g1>nkVrCu4?eH3w6NjFG#Zfv*25Bh4>V|6sX|9pY-%`#kwt^oSqK|cwj~`) zSa)%?cNdNAG+Dc;X-$FHW=%;yWi>97eAj`v9&6`AW7xJi+*OH);~kEuzGiPv-bdL2oI`%{3=sU}b<#qTWJm$O?t6 z!-3NXLvj#m2O&8qIIUxT(f4J44^|j`7Y={Uz`aDIE~>0WQwNRy73er`2cl;RYn4 z)o=5^GVtC{-O=28AEURBBR`{e$sB){dXq=UP}Lz}_VRQ!d-?n9uJTY#X|u#bd<(dGAgi2eg0S5JE3; zX}7y7v2n-P-m*=AzR@f~nW`Z$MJ0uH-?#S@9MpicxM6#5SE?=ix?VQL932#^n;dMH zrW5BOb&s|&f$hqbDkIVX?ZhdDWS1#6>mpke)UZcv$TJX=TtP4*OZIWCT&`>qHQEqh z1;bQFqG(<22!zbKsV&zcrHFqsUONr`P1ym}Cksu$5cN{KbdQ@(nSN?J=~PJ9hFCbN znbZ!pJeoY;hmP5&zvrZoiQ`?eS4K&=i~4V**c02<_1;Y1EMMjPwyUx2=*GB4`fS0H z>^F2`2p^ll4oG0o+>cc&LnX_RbxR}IziydOz#Lr3v|lEP^-jr6`b2+_X}7qR01hGP z-gGBJ_k~^YFj^D^*_S_x8yTIilALgIJw}y5cz7oTh;8uAU=E?jCN@`UwkoKdm3qhY zIi#3mmHSW7veSbiVh_C@8V8S6q3qLDvfpD}%09^i{5_f- z7_w8gH9K}&zo&b}t;>IyAIW*^J_G~{kx&N~{3b!%d(GczO57RQ+PZmb<7(Ng;_R)W zw>)Q$XsthZON*^~LJ6347cP)RLW2azSz=J6_>|t05?wU0Ch}vpEXD2;d z2}|sxq1NFO4=TqdQq8TSayaNL##Jwjk)@BP8%GUa!qt1{4NiYwPg@?CK!B+c{#NKe zC3R2hOpTvf=@eyiy6KgAi>fo1J$2g_^|gAZ2iiK7Q0hWDxW~fXKs>HupXPX@6@#9k z?zvj%Ca1xeGVjmX_koim>(DNDq{;Q6zOm~2%kZIBAwEWwoqMif9@Pmmyw%*N^0$TF z`xE8~y;~;-*JXd^{Cg|Rx`pzW*dbfB?AVK#|48)foMCCA9~KqI_wyZ47iQX*KwocT z9_+r*g#Aopm=C>oMgtlox$!f$Bfnj;9QG|qRm68^S7+0Y69-P|TtYk!731Uj+jw$+ z`~QCZ{~*5oA3J9|r1Tp7o+u$Q!3w_q|_lPIJ%JEIXwK%jdq+x^bVC(bQI+w!Y>ede|G$4eil5( z|JHH%sjNs*>F@RD&(HpMqC-U1uSCLK0jRl;Ta1hqNp^hH{P51iou0SJxD=fhB5ZCfnd={IO0q zLh+)vuKwF+=!8}kXs>_IxQ(l8^k+SP7Zh|&3z~&gM`<2DL+{mPaGGajRggFXvz6Pj zqA4!(q-J;}CRv1UNK#WQRAyiaSGAYqVoEc72jp-l#Grpr3nfJLp@PdixdCfn z*lZj&ua$jkMg+?WhYTvwICWYyWm}FW)_X}Bsrl+71$Ny~PZ6Y%(98_}$%{`6j~lFY zUA}#S8?81SZVb9Q$da(ac+D`u3PBJJHc&o<=YyV&h0~fEI77h`i8_0E?82QL{>qSN zhrdmU9mkR%Y$tyhA#lD1n-wuxC2%7)zqwzo^TJfz8B)M}Exe2)`5HBFk_`rYaL%A% zjTRqnLjoYnandsz!(R*sc8~qmEIj};7W|LbYltP1c7AjgQzWGz#HFh^YxjfmIi2{gLBwf7I}h#N*E;p0>JP zq$!W^Ijw#u@in=jnBX-DtGw8L=L&T$IJSuZI6HjZg`6F}(9F{dQsoiiiXb)%5u_9` zvX5nCDA~H-9ljVF0k2A!e36I_w90r%59J2tVts5W379yW>Pv$d` zn5mF2I)dhrw8)f;pIRU_*)y!#~HH$@~^5I==xW_`pj5GvGs7h1bB|IP~N+ zN9n|Oq@e_hYpxN*t@H?6bGM7Q+HA&C`+OLLln8rH56<&^BYe5Y>#J+-yP8!&?;Bc_ z6GMjtMG{o20=bbDC~%NuB@ij?Mk_LLFLfXzT#SZK}&81AB?2t8Km1p zW{|>2V8Poggbga&YKrBGySUoA>$P^8ESuD{rZ#I6mW)wDg{P~QQKea1#z-)&3O$BZ z7cBmhBrX{U6$wwsyP`Rmr`S%ZUch#UVWI{TNlYPDVFs6Vnt~-iE9;qzDri)P!mq{U zG>U&|;9Wj9;CyGrrB=z=d(RQV=p|bpz}1^x%I<^5cHhQAP2yo)RM+y!5q?+D8_LpR z9ddg>CS2!9)LV!RS)s6XIB*(aNDe~nAS4F`Cw0s(`ds#Bu)^q5I5-7*2~Wpi4Vf*) ztGtH&oWxWG0$E>xAP&;9q3w+|it4@b6jpy~BpJt@(4L$!%ga7Vv2+-n>a>SdcrvDwoF?{vj9u)Dw=;q zc4GJ*tElpt#ncAR!VYDKkk6owHhshb3cy(Ud*f(nX!R#We%tt-XjR-BsU~Q>O|SM9 zM^TF!oc7D&E4sY~iD>cr(cc-c_e*y)XYT=e4>|HH`j{NSQm^v}8LB!Y%wC@?X0PAO z4xe*5@P9FTc?yw*OM|yod9%Y)7|DNR!d%V*b5IPa5et3QIu4MyI?lES2tIlb;C{Pd zBfmCl_FYdajxEPn7SmHb0U_c6yKue~Z$KS|BmOia*%l4O*b@9`8Ao?=``%{NFRu;| z|L46seH_q&LkPXZrQPnT#Ks+Cd&@Qf`bM(|WvYe{DJm(n`@X%OKu`nHLc@Rd-mX+z z_;pz}#T*?JD@}H`OVb2%m%2yWn80@BN|h1mfOg^lK}MG;HtQl=6x6UsY{)YZlU#u_ zB1`shtz52bQfRaxzzT*#9f_i4xgp>(%cizmij*RbSM6c&17&AJUo2q)hNzd?rF-1; zkm;wkGn@+P+7L^OY9_UVEsuXD&-V$(?9-n)DP-b!lWdhy5>8S7jTC!g+q&MH>6_)N zoZogewjJFV*GQi&n3L^_PITdYGuRFZ44V6~YGtTo*|TnG1pC)56AGA}E1C9t9I@Uh zxk;Z0GVK=E62LA&x;Nb!q5Hxvxf>l91=*HQ#f|jNKa!mA;JS}0gYbXwP6`m);G4l5 zLibHSn!(x;@)fiPE+E} z$kx`)TN_u)W)){|6}^AuIeSEV#i6zMhb*S5A6}vXO3jrQV|Ik;|UCZHs^UR=v|BZJkOebs-(x zW8rQf?pLu-Bi?Ampl7Iit`@q_b?Y_{2{Y+#y9(wPL1~eGt#?RRH{C3H5*f%Ft5#OC&o=!hb z95|(O3Gp~ojQ8tr-_j(+J|`0{=q!Yx}wT{ V@-S-}QwPAbvoVvm{{gEH4PSrmWkCP{ diff --git a/dev/reference/index.html b/dev/reference/index.html index 30ac197..50c97f3 100644 --- a/dev/reference/index.html +++ b/dev/reference/index.html @@ -1,6 +1,6 @@ -Reference · ConstraintTrees.jl

Reference

Values

ConstraintTrees.preduceMethod
preduce(op, xs; init, stack_type) -> Any
-

An alternative of Base.reduce which does a "pairwise" reduction in the shape of a binary merge tree, like in mergesort. In general this is a little more complex, but if the reduced value "grows" with more elements added (such as when adding a lot of LinearValues together), this is able to prevent a complexity explosion by postponing "large" reducing operations as much as possible.

In the specific case with adding lots of LinearValues and QuadraticValues together, this effectively squashes the reduction complexity from something around O(n^2) to O(n) (with a little larger constant factor.

source
ConstraintTrees.substitute_valuesFunction
substitute_values(
+Reference · ConstraintTrees.jl

Reference

Values

ConstraintTrees.preduceMethod
preduce(op, xs; init, stack_type) -> Any
+

An alternative of Base.reduce which does a "pairwise" reduction in the shape of a binary merge tree, like in mergesort. In general this is a little more complex, but if the reduced value "grows" with more elements added (such as when adding a lot of LinearValues together), this is able to prevent a complexity explosion by postponing "large" reducing operations as much as possible.

In the specific case with adding lots of LinearValues and QuadraticValues together, this effectively squashes the reduction complexity from something around O(n^2) to O(n) (with a little larger constant factor.

source
ConstraintTrees.valueMethod
value(
     x::Union{ConstraintTrees.Value, Real}
 ) -> Union{ConstraintTrees.Value, Real}
-

Returns any Real- or Value-typed x. This is a convenience overload; typically one enjoys this more when extracting values from Constraints.

source

Linear and affine values

ConstraintTrees.LinearValueType
struct LinearValue <: ConstraintTrees.Value

A representation of a "value" in a linear constrained optimization problem. The value is an affine linear combination of several variables.

LinearValues can be combined additively and multiplied by real-number constants.

Multiplying two LinearValues yields a quadratic form (in a QuadraticValue).

Fields

  • idxs::Vector{Int64}: Indexes of the variables used by the value. The indexes must always be sorted in strictly increasing order. The affine element has index 0.
  • weights::Vector{Float64}: Coefficients of the variables selected by idxs.
source

Linear and affine values

ConstraintTrees.LinearValueType
struct LinearValue <: ConstraintTrees.Value

A representation of a "value" in a linear constrained optimization problem. The value is an affine linear combination of several variables.

LinearValues can be combined additively and multiplied by real-number constants.

Multiplying two LinearValues yields a quadratic form (in a QuadraticValue).

Fields

  • idxs::Vector{Int64}: Indexes of the variables used by the value. The indexes must always be sorted in strictly increasing order. The affine element has index 0.
  • weights::Vector{Float64}: Coefficients of the variables selected by idxs.
source
ConstraintTrees.add_sparse_linear_combinationMethod
add_sparse_linear_combination(
     a_idxs::Vector{Int64},
     a_weights::Array{T, 1},
     b_idxs::Vector{Int64},
     b_weights::Array{T, 1}
 ) -> Tuple{Vector{Int64}, Vector}
-

Helper function for implementing LinearValue-like objects. Given "sparse" representations of linear combinations, it computes a "merged" linear combination of 2 values added together.

Zeroes are not filtered out.

source

Quadratic values

ConstraintTrees.QuadraticValueType
struct QuadraticValue <: ConstraintTrees.Value

A representation of a quadratic form in the constrained optimization problem. The QuadraticValue is an affine quadratic combination (i.e., a polynomial of maximum degree 2) over the variables.

QuadraticValues can be combined additively and multiplied by real-number constants. The cleanest way to construct a QuadraticValue is to multiply two LinearValues.

Fields

  • idxs::Vector{Tuple{Int64, Int64}}: Indexes of variable pairs used by the value. The indexes must always be sorted in strictly co-lexicographically increasing order, and the second index must always be greater than or equal to the first one. (Speaking in matrix terms, the indexing follows the indexes in an upper triangular matrix by columns.)

    As an outcome, the second index of the last index pair can be used as the upper bound of all variable indexes.

    As with LinearValue, index 0 represents the affine element.

  • weights::Vector{Float64}: Coefficient of the variable pairs selected by idxs.
source
ConstraintTrees.QuadraticValueMethod
QuadraticValue(
+

Helper function for implementing LinearValue-like objects. Given "sparse" representations of linear combinations, it computes a "merged" linear combination of 2 values added together.

Zeroes are not filtered out.

source

Quadratic values

ConstraintTrees.QuadraticValueType
struct QuadraticValue <: ConstraintTrees.Value

A representation of a quadratic form in the constrained optimization problem. The QuadraticValue is an affine quadratic combination (i.e., a polynomial of maximum degree 2) over the variables.

QuadraticValues can be combined additively and multiplied by real-number constants. The cleanest way to construct a QuadraticValue is to multiply two LinearValues.

Fields

  • idxs::Vector{Tuple{Int64, Int64}}: Indexes of variable pairs used by the value. The indexes must always be sorted in strictly co-lexicographically increasing order, and the second index must always be greater than or equal to the first one. (Speaking in matrix terms, the indexing follows the indexes in an upper triangular matrix by columns.)

    As an outcome, the second index of the last index pair can be used as the upper bound of all variable indexes.

    As with LinearValue, index 0 represents the affine element.

  • weights::Vector{Float64}: Coefficient of the variable pairs selected by idxs.
source
ConstraintTrees.QuadraticValueMethod
QuadraticValue(
     x::SparseArrays.SparseMatrixCSC{Float64}
 ) -> ConstraintTrees.QuadraticValue
-

Shortcut for making a QuadraticValue out of a square sparse matrix. The matrix is force-symmetrized by calculating x' + x.

source
ConstraintTrees.add_sparse_quadratic_combinationMethod
add_sparse_quadratic_combination(
     a_idxs::Vector{Tuple{Int64, Int64}},
     a_weights::Array{T, 1},
     b_idxs::Vector{Tuple{Int64, Int64}},
     b_weights::Array{T, 1}
 ) -> Tuple{Vector{Tuple{Int64, Int64}}, Vector}
-

Helper function for implementing QuadraticValue-like objects. Given 2 sparse representations of quadratic combinations, it computes a "merged" one with the values of both added together.

Zeroes are not filtered out.

source
ConstraintTrees.multiply_sparse_linear_combinationMethod
multiply_sparse_linear_combination(
     a_idxs::Vector{Int64},
     a_weights::Array{T, 1},
     b_idxs::Vector{Int64},
     b_weights::Array{T, 1}
 ) -> Tuple{Vector{Tuple{Int64, Int64}}, Vector}
-

Helper function for multiplying two LinearValue-like objects to make a QuadraticValue-like object. This computes and merges the product.

Zeroes are not filtered out.

source

Constraints

Bounds

ConstraintTrees.BetweenType
mutable struct Between <: ConstraintTrees.Bound

Representation of an "interval" bound; consisting of lower and upper bound value.

Fields

  • lower::Float64: Lower bound

  • upper::Float64: Upper bound

source
ConstraintTrees.BoundType
abstract type Bound

Abstract type of all bounds usable in constraints, including Between and EqualTo.

To make broadcasting work, length(::Bound) = 1 has been extended. This allows functions like variables to broadcast a single supplied bound across all constraints.

source
ConstraintTrees.EqualToType
mutable struct EqualTo <: ConstraintTrees.Bound

Representation of an "equality" bound; contains the single "equal to this" value.

Fields

  • equal_to::Float64: Equality bound value
source

Constrained values

ConstraintTrees.ConstraintType
mutable struct Constraint

A representation of a single constraint that may limit the given value by a specific Bound.

Constraints without a bound (nothing in the bound field) are possible; these have no impact on the optimization problem but the associated value becomes easily accessible for inspection and building other constraints.

Fields

  • value::ConstraintTrees.Value: A value (typically a LinearValue or a QuadraticValue) that describes what the constraint constraints.

  • bound::Union{Nothing, ConstraintTrees.Bound}: A bound that the value must satisfy. Should be a subtype of MaybeBound: Either nothing if there's no bound, or e.g. EqualTo, Between or similar structs.

source

Constraints

Bounds

ConstraintTrees.BetweenType
mutable struct Between <: ConstraintTrees.Bound

Representation of an "interval" bound; consisting of lower and upper bound value.

Fields

  • lower::Float64: Lower bound

  • upper::Float64: Upper bound

source
ConstraintTrees.BoundType
abstract type Bound

Abstract type of all bounds usable in constraints, including Between and EqualTo.

To make broadcasting work, length(::Bound) = 1 has been extended. This allows functions like variables to broadcast a single supplied bound across all constraints.

source
ConstraintTrees.EqualToType
mutable struct EqualTo <: ConstraintTrees.Bound

Representation of an "equality" bound; contains the single "equal to this" value.

Fields

  • equal_to::Float64: Equality bound value
source

Constrained values

ConstraintTrees.ConstraintType
mutable struct Constraint

A representation of a single constraint that may limit the given value by a specific Bound.

Constraints without a bound (nothing in the bound field) are possible; these have no impact on the optimization problem but the associated value becomes easily accessible for inspection and building other constraints.

Fields

  • value::ConstraintTrees.Value: A value (typically a LinearValue or a QuadraticValue) that describes what the constraint constraints.

  • bound::Union{Nothing, ConstraintTrees.Bound}: A bound that the value must satisfy. Should be a subtype of MaybeBound: Either nothing if there's no bound, or e.g. EqualTo, Between or similar structs.

source
ConstraintTrees.boundMethod
bound(
     x::ConstraintTrees.Constraint
 ) -> Union{Nothing, ConstraintTrees.Bound}
-

Simple accessor for getting out the bound from the constraint that can be used for broadcasting (as opposed to the dot-field access).

source
ConstraintTrees.substituteMethod
substitute(
+

Simple accessor for getting out the bound from the constraint that can be used for broadcasting (as opposed to the dot-field access).

source
ConstraintTrees.substituteMethod
substitute(
     x::ConstraintTrees.Constraint,
     y
 ) -> ConstraintTrees.Constraint
-

Substitute anything vector-like as variables into the constraint's value, producing a constraint with the new value.

source
ConstraintTrees.valueMethod
value(
     x::ConstraintTrees.Constraint
 ) -> ConstraintTrees.Value
-

Simple accessor for getting out the value from the constraint that can be used for broadcasting (as opposed to the dot-field access).

source

Labeled trees

ConstraintTrees.TreeType
struct Tree{X}

A base "labeled tree" structure. Supports many interesting operations such as merging.

source
ConstraintTrees.elemsMethod
elems(
+

Simple accessor for getting out the value from the constraint that can be used for broadcasting (as opposed to the dot-field access).

source

Labeled trees

ConstraintTrees.TreeType
struct Tree{X}

A base "labeled tree" structure. Supports many interesting operations such as merging.

source
ConstraintTrees.elemsMethod
elems(
     x::ConstraintTrees.Tree
 ) -> DataStructures.SortedDict{Symbol, Union{ConstraintTrees.Tree{X}, X}} where X
-

Get the elements dictionary out of the Tree. This is useful for getting an iterable container for working with many items at once.

Also, because of the overload of getproperty for Tree, this serves as a simpler way to get the elements without an explicit use of getfield.

source
ConstraintTrees.imapMethod
imap(f, x) -> Any
+

Get the elements dictionary out of the Tree. This is useful for getting an iterable container for working with many items at once.

Also, because of the overload of getproperty for Tree, this serves as a simpler way to get the elements without an explicit use of getfield.

source
ConstraintTrees.imapMethod
imap(f, x) -> Any
 imap(f, x, ::Type{T}) -> Any
-

Like map, but keeping the "index" path and giving it to the function as the first parameter. The "path" in the tree is reported as a tuple of symbols.

source
ConstraintTrees.imapreduceMethod
imapreduce(f, op, x; init) -> Any
-

Like mapreduce but reporting the "tree directory path" where the reduced elements occur, like with imap. (Single elements from different directory paths are not reduced together.)

source
ConstraintTrees.imergeMethod
imerge(f, x, y) -> Any
+

Like map, but keeping the "index" path and giving it to the function as the first parameter. The "path" in the tree is reported as a tuple of symbols.

source
ConstraintTrees.imapreduceMethod
imapreduce(f, op, x; init) -> Any
+

Like mapreduce but reporting the "tree directory path" where the reduced elements occur, like with imap. (Single elements from different directory paths are not reduced together.)

source
ConstraintTrees.mapMethod
map(f, x) -> Any
 map(f, x, ::Type{T}) -> Any
-

Run a function over everything in the tree. The resulting tree will contain elements of type specified by the 3rd argument. (This needs to be specified explicitly, because the typesystem generally cannot guess the universal type correctly.)

Note this is a specialized function specific for Trees that behaves differently from Base.map.

source
ConstraintTrees.mapreduceMethod
mapreduce(f, op, x; init) -> Any
-

Reduce all items in a Tree. As with Base.reduce, the reduction order is not guaranteed, and the initial value may be used any number of times.

Note this is a specialized function specific for Trees that behaves differently from Base.mapreduce.

source
ConstraintTrees.mergeMethod
merge(f, x, y) -> Any
+

Run a function over everything in the tree. The resulting tree will contain elements of type specified by the 3rd argument. (This needs to be specified explicitly, because the typesystem generally cannot guess the universal type correctly.)

Note this is a specialized function specific for Trees that behaves differently from Base.map.

source
ConstraintTrees.mapreduceMethod
mapreduce(f, op, x; init) -> Any
+

Reduce all items in a Tree. As with Base.reduce, the reduction order is not guaranteed, and the initial value may be used any number of times.

Note this is a specialized function specific for Trees that behaves differently from Base.mapreduce.

source
ConstraintTrees.mergeMethod
merge(f, x, y) -> Any
 merge(f, x, y, ::Type{T}) -> Any
-

Run a function over the values in the merge of all paths in the trees (currently there is support for 2 and 3 trees). This is an "outer join" equivalent of zip. Missing elements are replaced by missing in the function call parameters, and the function may return missing to omit elements.

Note this is a specialized function specific for Trees that behaves differently from Base.merge.

source
ConstraintTrees.optional_tree_keysMethod
optional_tree_keys(
+

Run a function over the values in the merge of all paths in the trees (currently there is support for 2 and 3 trees). This is an "outer join" equivalent of zip. Missing elements are replaced by missing in the function call parameters, and the function may return missing to omit elements.

Note this is a specialized function specific for Trees that behaves differently from Base.merge.

source
ConstraintTrees.optional_tree_keysMethod
optional_tree_keys(
     _::Missing
 ) -> DataStructures.SortedSet{Any, Base.Order.ForwardOrdering}
-

Get a sorted set of keys from a tree that is possibly missing.

source
ConstraintTrees.reduceMethod
reduce(op, x; init) -> Any
-

Like mapreduce but the mapped function is identity.

To avoid much type suffering, the operation should ideally preserve the type of its arguments. If you need to change the type, you likely want to use mapreduce.

Note this is a specialized function specific for Trees that behaves differently from Base.reduce.

source
ConstraintTrees.traverseMethod
traverse(f, x) -> Any
-

Like map, but discards the results, thus relying only on the side effects of f.

Technically the name should be for, but that's a Julia keyword.

source
ConstraintTrees.reduceMethod
reduce(op, x; init) -> Any
+

Like mapreduce but the mapped function is identity.

To avoid much type suffering, the operation should ideally preserve the type of its arguments. If you need to change the type, you likely want to use mapreduce.

Note this is a specialized function specific for Trees that behaves differently from Base.reduce.

source
ConstraintTrees.traverseMethod
traverse(f, x) -> Any
+

Like map, but discards the results, thus relying only on the side effects of f.

Technically the name should be for, but that's a Julia keyword.

source
ConstraintTrees.zipMethod
zip(f, x, y) -> Any
 zip(f, x, y, ::Type{T}) -> Any
-

Run a function over the values in the intersection of paths in several trees (currently there is support for 2 and 3 trees). This is an "inner join" – all extra elements are ignored. "Outer join" can be done via merge.

As with map, the inner type of the resulting tree must be specified by the last parameter..

Note this is a specialized function specific for Trees that behaves differently from Base.zip.

source

Constraint trees

ConstraintTrees.ConstraintTreeType
struct Tree{ConstraintTrees.Constraint}

A hierarchical tree of many constraints that together describe a constrained system. The tree may recursively contain other trees in a directory-like structure, which contain Constraints as leaves.

Members of the constraint tree are accessible via the record dot syntax as properties; e.g. a constraint labeled with :abc in a constraint tree t may be accessed as t.abc and as t[:abc], and can be found while iterating through elems(t).

Constructing the constraint trees

Use operator ^ to put a name on a constraint to convert it into a single element ConstraintTree:

x = :my_constraint ^ Constraint(LinearValue(...), 1.0)
+

Run a function over the values in the intersection of paths in several trees (currently there is support for 2 and 3 trees). This is an "inner join" – all extra elements are ignored. "Outer join" can be done via merge.

As with map, the inner type of the resulting tree must be specified by the last parameter..

Note this is a specialized function specific for Trees that behaves differently from Base.zip.

source

Constraint trees

ConstraintTrees.ConstraintTreeType
struct Tree{ConstraintTrees.Constraint}

A hierarchical tree of many constraints that together describe a constrained system. The tree may recursively contain other trees in a directory-like structure, which contain Constraints as leaves.

Members of the constraint tree are accessible via the record dot syntax as properties; e.g. a constraint labeled with :abc in a constraint tree t may be accessed as t.abc and as t[:abc], and can be found while iterating through elems(t).

Constructing the constraint trees

Use operator ^ to put a name on a constraint to convert it into a single element ConstraintTree:

x = :my_constraint ^ Constraint(LinearValue(...), 1.0)
 dir = :my_constraint_dir ^ x
 
-dir.my_constraint_dir.my_constraint.bound   # returns 1.0

Use operator * to glue two constraint trees together while sharing the variable indexes specified by the contained LinearValues and QuadraticValues.

my_constraints = :some_constraints ^ Constraint(...) * :more_constraints ^ Constraint(...)

Use operator + to glue two constraint trees together without sharing of any variables. The operation will renumber the variables in the trees so that the sets of variable indexes used by either tree are completely disjunct, and then glue the trees together as with *:

two_independent_systems = my_system + other_system

Variable sharing limitations

Because of the renumbering, you can not easily use constraints and values from the values before the addition in the constraint tree that is the result of the addition. There is no check against that – the resulting ConstraintTree will be valid, but will probably describe a different optimization problem than you intended.

As a rule of thumb, avoid necessary parentheses in expressions that work with the constraint trees: While t1 * t2 + t3 might work just as intended, t1 * (t2 + t3) is almost certainly wrong because the variables in t1 that are supposed to connect to variables in either of t2 and t3 will not connect properly because of renumbering of both t2 and t3. If you need to construct a tree like that, do the addition first, and construct the t1 after that, based on the result of the addition.

source
ConstraintTrees.incr_var_idxsMethod
incr_var_idxs(
+dir.my_constraint_dir.my_constraint.bound   # returns 1.0

Use operator * to glue two constraint trees together while sharing the variable indexes specified by the contained LinearValues and QuadraticValues.

my_constraints = :some_constraints ^ Constraint(...) * :more_constraints ^ Constraint(...)

Use operator + to glue two constraint trees together without sharing of any variables. The operation will renumber the variables in the trees so that the sets of variable indexes used by either tree are completely disjunct, and then glue the trees together as with *:

two_independent_systems = my_system + other_system

Variable sharing limitations

Because of the renumbering, you can not easily use constraints and values from the values before the addition in the constraint tree that is the result of the addition. There is no check against that – the resulting ConstraintTree will be valid, but will probably describe a different optimization problem than you intended.

As a rule of thumb, avoid necessary parentheses in expressions that work with the constraint trees: While t1 * t2 + t3 might work just as intended, t1 * (t2 + t3) is almost certainly wrong because the variables in t1 that are supposed to connect to variables in either of t2 and t3 will not connect properly because of renumbering of both t2 and t3. If you need to construct a tree like that, do the addition first, and construct the t1 after that, based on the result of the addition.

source
ConstraintTrees.incr_var_idxsMethod
incr_var_idxs(
     x::ConstraintTrees.Tree{ConstraintTrees.Constraint},
     incr::Int64
 ) -> ConstraintTrees.Tree{ConstraintTrees.Constraint}
-

Offset all variable indexes in a ConstraintTree by the given increment.

source
ConstraintTrees.substitute_valuesMethod
substitute_values(
     x::ConstraintTrees.Tree,
     y::AbstractVector
 ) -> Any
@@ -121,14 +121,14 @@
     y::AbstractVector,
     ::Type{T}
 ) -> Any
-

Substitute variable values from y into the constraint tree's constraint's values, getting a tree of "solved" constraint values for the given variable assignment.

The third argument forces the output type (it is forwarded to map). The type gets defaulted from eltype(y).

source
ConstraintTrees.var_countMethod
var_count(x::ConstraintTrees.LinearValue) -> Int64
-

Find the expected count of variables in a LinearValue. (This is a O(1) operation, relying on the ordering of the indexes.)

source
ConstraintTrees.var_countMethod
var_count(x::ConstraintTrees.QuadraticValue) -> Int64
-

Find the expected count of variables in a QuadraticValue. (This is a O(1) operation, relying on the co-lexicographical ordering of indexes.)

source
ConstraintTrees.var_countMethod
var_count(
+

Substitute variable values from y into the constraint tree's constraint's values, getting a tree of "solved" constraint values for the given variable assignment.

The third argument forces the output type (it is forwarded to map). The type gets defaulted from eltype(y).

source
ConstraintTrees.var_countMethod
var_count(x::ConstraintTrees.LinearValue) -> Int64
+

Find the expected count of variables in a LinearValue. (This is a O(1) operation, relying on the ordering of the indexes.)

source
ConstraintTrees.var_countMethod
var_count(x::ConstraintTrees.QuadraticValue) -> Int64
+

Find the expected count of variables in a QuadraticValue. (This is a O(1) operation, relying on the co-lexicographical ordering of indexes.)

source
ConstraintTrees.variableMethod
variable(; bound, idx) -> ConstraintTrees.Constraint
-

Allocate a single unnamed variable, returning a Constraint with an optionally specified bound.

source
ConstraintTrees.variablesMethod
variables(; keys, bounds)
-

Make a trivial constraint system that creates variables with indexes in range 1:length(keys) named in order as given by keys.

Parameter bounds is either nothing for creating variables without bounds assigned to them, a single bound for creating variables with the same constraint assigned to them all, or an iterable object of same length as keys with individual bounds for each variable in the same order as keys.

The individual bounds should be subtypes of Bound, or nothing. To pass a single bound for all variables, use e.g. bounds = EqualTo(0).

source
ConstraintTrees.variables_forMethod
variables_for(makebound, ts::ConstraintTrees.Tree) -> Any
-

Allocate a variable for each item in a constraint tree (or any other kind of tree) and return a ConstraintTree with variables bounded by the makebound function, which converts a given tree element's value into a bound for the corresponding variable.

source

Trees for storing solved values

+

Find the expected count of variables in a ConstraintTree.

source
ConstraintTrees.variableMethod
variable(; bound, idx) -> ConstraintTrees.Constraint
+

Allocate a single unnamed variable, returning a Constraint with an optionally specified bound.

source
ConstraintTrees.variablesMethod
variables(; keys, bounds)
+

Make a trivial constraint system that creates variables with indexes in range 1:length(keys) named in order as given by keys.

Parameter bounds is either nothing for creating variables without bounds assigned to them, a single bound for creating variables with the same constraint assigned to them all, or an iterable object of same length as keys with individual bounds for each variable in the same order as keys.

The individual bounds should be subtypes of Bound, or nothing. To pass a single bound for all variables, use e.g. bounds = EqualTo(0).

source
ConstraintTrees.variables_forMethod
variables_for(makebound, ts::ConstraintTrees.Tree) -> Any
+

Allocate a variable for each item in a constraint tree (or any other kind of tree) and return a ConstraintTree with variables bounded by the makebound function, which converts a given tree element's value into a bound for the corresponding variable.

source

Trees for storing solved values

diff --git a/dev/search_index.js b/dev/search_index.js index d21b284..622d0a0 100644 --- a/dev/search_index.js +++ b/dev/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"EditURL = \"3-mixed-integer-optimization.jl\"","category":"page"},{"location":"3-mixed-integer-optimization/#Example:-Mixed-integer-optimization-(MILP)","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"","category":"section"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"This example demonstrates the extension of ConstraintTree bounds structures to accommodate new kinds of problems. In particular, we create a new kind of Bound that is restricting the value to be a full integer, and then solve a geometric problem with that.","category":"page"},{"location":"3-mixed-integer-optimization/#Creating-a-custom-bound","page":"Example: Mixed integer optimization (MILP)","title":"Creating a custom bound","text":"","category":"section"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"All bounds contained in constraints are subtypes of the abstract ConstraintTrees.Bound. These include ConstraintTrees.EqualTo and ConstraintTrees.Between, but the types can be extended as necessary, given the final rewriting of the constraint system to JuMP can handle the new bounds.","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"Let's make a small \"marker\" bound for something that needs to be integer-ish, between 2 integers:","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"import ConstraintTrees as C\n\nmutable struct IntegerFromTo <: C.Bound\n from::Int\n to::Int\nend","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"We can now write e.g. a bound on the number on a thrown six-sided die as follows:","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"IntegerFromTo(1, 6)","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"...and include this bound in constraints and variables:","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"dice_system = C.variables(keys = [:first_dice, :second_dice], bounds = IntegerFromTo(1, 6))","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"Now the main thing that is left is to be able to translate this bound to JuMP for solving. We can slightly generalize our constraint-translation system from the previous examples for this purpose, by separating out the functions that create the constraints:","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"import JuMP\n\nfunction jump_constraint(m, x, v::C.Value, b::C.EqualTo)\n JuMP.@constraint(m, C.substitute(v, x) == b.equal_to)\nend\n\nfunction jump_constraint(m, x, v::C.Value, b::C.Between)\n isinf(b.lower) || JuMP.@constraint(m, C.substitute(v, x) >= b.lower)\n isinf(b.upper) || JuMP.@constraint(m, C.substitute(v, x) <= b.upper)\nend","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"JuMP does not support direct integrality constraints, so we need to make a small digression with a slack variable:","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"function jump_constraint(m, x, v::C.Value, b::IntegerFromTo)\n var = JuMP.@variable(m, integer = true)\n JuMP.@constraint(m, var >= b.from)\n JuMP.@constraint(m, var <= b.to)\n JuMP.@constraint(m, C.substitute(v, x) == var)\nend\n\nfunction milp_optimized_vars(cs::C.ConstraintTree, objective::C.Value, optimizer)\n model = JuMP.Model(optimizer)\n JuMP.@variable(model, x[1:C.var_count(cs)])\n JuMP.@objective(model, JuMP.MAX_SENSE, C.substitute(objective, x))\n C.traverse(cs) do c\n isnothing(c.bound) || jump_constraint(model, x, c.value, c.bound)\n end\n JuMP.set_silent(model)\n JuMP.optimize!(model)\n JuMP.value.(model[:x])\nend","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"Let's try to solve a tiny system with the dice first. What's the best value we can throw if the dice are thrown at least 1.5 points apart?","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"dice_system *=\n :points_distance^C.Constraint(\n dice_system.first_dice.value - dice_system.second_dice.value,\n C.Between(1.5, Inf),\n )","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"For solving, we use GLPK (it has MILP capabilities).","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"import GLPK\ndices_thrown = C.substitute_values(\n dice_system,\n milp_optimized_vars(\n dice_system,\n dice_system.first_dice.value + dice_system.second_dice.value,\n GLPK.Optimizer,\n ),\n)","category":"page"},{"location":"3-mixed-integer-optimization/#A-more-realistic-example-with-geometry","page":"Example: Mixed integer optimization (MILP)","title":"A more realistic example with geometry","text":"","category":"section"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"Let's find the size of the smallest right-angled triangle with integer side sizes (aka a Pythagorean triple).","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"vars = C.variables(keys = [:a, :b, :c], bounds = IntegerFromTo(1, 100))","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"For simpliclty, we make a shortcut for \"values\" in all variables:","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"v = C.map(C.value, vars, C.Value)","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"With that shortcut, the constraint tree constructs quite easily:","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"triangle_system =\n :sides^vars *\n :circumference^C.Constraint(sum(values(v))) *\n :a_less_than_b^C.Constraint(v.b - v.a, (0, Inf)) *\n :b_less_than_c^C.Constraint(v.c - v.b, (0, Inf)) *\n :right_angled^C.Constraint(C.squared(v.a) + C.squared(v.b) - C.squared(v.c), 0.0)","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"We will need a solver that supports both quadratic and integer optimization:","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"import SCIP\ntriangle_sides =\n C.substitute_values(\n triangle_system,\n milp_optimized_vars(\n triangle_system,\n -triangle_system.circumference.value,\n SCIP.Optimizer,\n ),\n ).sides","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"This page was generated using Literate.jl.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"EditURL = \"4-functional-tree-processing.jl\"","category":"page"},{"location":"4-functional-tree-processing/#Example:-Processing-the-trees-functionally","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"","category":"section"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"The main goal of ConstraintTrees.jl is to make the constraint-manipulating code orderly and elegant, and preferably short. To improve the manipulation of large constraint trees, the package also provides a small functional-programming-inspired framework that allows one to easily transform, summarize and combine all kinds of trees without writing repetitive code.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"You might already seen the zip function in the metabolic modeling example. There are more functions that behave like zip, so let's have a little summary here:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"map applies a function to all elements (including the nested ones) of a tree\nmapreduce transforms all elements of a tree using a given function (first parameter) and then combines the result using the second function (a binary operator); reduce is a shortcut where the map function is an identity\nzip combines elements common to both trees using a given zipping function\nmerge combines all elements in both trees (including the ones present in only one tree) using a given merging function\nvariables_for allocates a variable for each constraint in the tree and allows the user to specify bounds","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Additionally, all these have their \"indexed\" variant which allows you to know the path where the tree elements are being merged. The path is passed to the handling function as a tuple of symbols. The variants are prefixed with i:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"imap\nimapreduce (here the path refers to the common directory of the reduced elements) together with the shortcut ireduce\nizip\nimerge\nvariables_ifor","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"danger: Naming conflicts with Julia base\nNames of some of the higher-order function conflict with Julia base package and are not compatible. We recommend using them with named imports, such as by import ConstraintTrees as C and then C.zip and C.merge.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"For demonstration, let's make a very simple constrained system.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"import ConstraintTrees as C\n\nconstraints = :point^C.variables(keys = [:x, :y], bounds = C.Between(0, 1))","category":"page"},{"location":"4-functional-tree-processing/#Transforming-trees-with-map","page":"Example: Processing the trees functionally","title":"Transforming trees with map","text":"","category":"section"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Let's make a tree where the bounds are 2 times bigger and negated:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.map(constraints) do x\n C.Constraint(x.value, -2 * x.bound)\nend\n\nx.point.x","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"With imap, we can detect that we are working on a specific constraint and do something entirely different:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.imap(constraints) do path, x\n if path == (:point, :x)\n C.Constraint(x.value, 100 * x.bound)\n else\n x\n end\nend\n\n[x.point.x, x.point.y]","category":"page"},{"location":"4-functional-tree-processing/#Summarizing-the-trees-with-mapreduce-and-reduce","page":"Example: Processing the trees functionally","title":"Summarizing the trees with mapreduce and reduce","text":"","category":"section"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"How many constraints are there in the tree?","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.mapreduce(init = 0, _ -> 1, +, constraints)","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"What if we want to sum all constraints' values?","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.reduce(constraints, init = C.Constraint(zero(C.LinearValue))) do x, y\n C.Constraint(value = x.value + y.value)\nend","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"What if we want to reduce the point specially?","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.ireduce(constraints, init = C.Constraint(zero(C.LinearValue))) do path, x, y\n if path == (:point,)\n println(\"reducing in point/ subtree: $(x.value) + $(y.value)\")\n end\n C.Constraint(value = x.value + y.value)\nend;\nnothing #hide","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x","category":"page"},{"location":"4-functional-tree-processing/#Comparing-trees-with-zip","page":"Example: Processing the trees functionally","title":"Comparing trees with zip","text":"","category":"section"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Assume we have two solutions of the constraint system above, as follows:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"s1 = C.substitute_values(constraints, [0.9, 0.8])\ns2 = C.substitute_values(constraints, [0.99, 0.78])","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Let's compute the squared distance between individual items:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.zip(s1, s2, Float64) do x, y\n (x - y)^2\nend\n\nx.point","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"What if we want to put extra weight on distances between specific variables?","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.izip(s1, s2, Float64) do path, x, y\n if path == (:point, :x)\n 10\n else\n 1\n end * (x - y)^2\nend\n\nx.point","category":"page"},{"location":"4-functional-tree-processing/#Combining-trees-with-merge","page":"Example: Processing the trees functionally","title":"Combining trees with merge","text":"","category":"section"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Zipping trees together always produces a tree that only contains the intersection of keys from both original trees. That is not very useful if one wants to e.g. add new elements from extended trees. merge-style functions implement precisely that.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"The \"zipping\" function in merge takes 2 arguments; any of these may be missing in case one of the trees does not contain the elements. Also, a key may be omitted by returning missing from the function.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Let's make some very heterogeneous trees and try to combine them:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"t1 = :x^s1.point * :y^s2.point\nt2 = :x^s2.point * :z^s1.point\nt3 = :y^s2.point","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"As a nice combination function, we can try to compute an average on all positions from the first 2 trees:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"t = C.merge(t1, t2, Float64) do x, y\n ismissing(x) && return y\n ismissing(y) && return x\n (x + y) / 2\nend\n\nt.x\n\nt.y\n\nt.z","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Merge can also take 3 parameters (which is convenient in some situations). We may also want to omit certain output completely:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"tz = C.merge(t1, t2, t3, Float64) do x, y, z\n ismissing(z) && return missing\n ismissing(x) && return y\n ismissing(y) && return x\n (x + y) / 2\nend\n\ntz.y","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"We also have the indexed variants; for example this allows us to only merge the x elements in points:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"tx = C.imerge(t1, t2, Float64) do path, x, y\n last(path) == :x || return missing\n ismissing(x) && return y\n ismissing(y) && return x\n (x + y) / 2\nend\n\ntx.x","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"For completeness, we demonstrate a trick with easily coalescing the \"missing\" things to compute the means more easily:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"miss(_::Missing, _, def) = def;\nmiss(x, f, _) = f(x);\nfixmean(a) = miss(a, x -> (x, 1), (0, 0));\n\ntx = C.imerge(t1, t2, t3, Float64) do path, x, y, z\n last(path) == :x || return missing\n tmp = fixmean.([x, y, z])\n sum(first.(tmp)) / sum(last.(tmp))\nend\n\ntx.y","category":"page"},{"location":"4-functional-tree-processing/#Allocating-trees-of-variables-using-variables_for","page":"Example: Processing the trees functionally","title":"Allocating trees of variables using variables_for","text":"","category":"section"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"In many cases it is convenient to make a new model from the old by allocating new variables for whatever \"old\" tree out there. For example, one might wish to allocate a new variable for an approximate value (plus-minus-one) for each of the above tree's values. variables_for allocates one variable for each element of the given tree, and allows you to create bounds for the variables via the given function:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.variables_for(t) do a\n C.Between(a - 1, a + 1)\nend\n\nt.x.x\n\nx.x.x","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Note that the variables for the other subtrees are different now:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x.x.x.value\n\nx.y.x.value","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"As in all cases with indexes, you may match the tree path to do a special action. For example, to make sure that all y coordinates are exact in the new system:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.variables_ifor(t) do path, a\n if last(path) == :y\n C.EqualTo(a)\n else\n C.Between(a - 1, a + 1)\n end\nend\n\nx.x\n\nC.bound.(values(x.x))","category":"page"},{"location":"4-functional-tree-processing/#Looping-through-the-trees-with-traverse","page":"Example: Processing the trees functionally","title":"Looping through the trees with traverse","text":"","category":"section"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Since we are writing our code in an imperative language, it is often quite beneficial to run a function over the trees just for the side effect.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"For this purpose, traverse and itraverse work precisely like map and imap, except no tree is returned and the only \"output\" of the functions are their side effect.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"For example, you can write a less-functional counting of number of constraints in the tree as follows:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"constraint_count = 0\nC.traverse(x) do _\n global constraint_count += 1\nend\nconstraint_count","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"The indexed variant of traverse works as expected; it may be beneficial e.g. for printing the contents of the constraint trees in a \"flat\" form, or potentially working with other path-respecting data structures.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"C.itraverse(x) do ix, c\n path = join(String.(ix), '/')\n println(\"$path = $c\")\nend;\nnothing #hide","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"To prevent uncertainty, both functions always traverse the keys in sorted order.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"This page was generated using Literate.jl.","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"EditURL = \"5-jump-integration.jl\"","category":"page"},{"location":"5-jump-integration/#Better-integration-with-JuMP","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"","category":"section"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"The examples in this documentation generally used the simple and straightforward method of converting the trees and values to JuMP system, which depends on algebraic operators working transparently with JuMP values within function substitute.","category":"page"},{"location":"5-jump-integration/#Substitution-folding-problem","page":"Better integration with JuMP","title":"Substitution folding problem","text":"","category":"section"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"Despite the simplicity, this approach is sometimes sub-optimal, especially in cases when the result of the substitution is recalculated with added values. For example, in the naive case, JuMP is forced to successively build representations for all intermediate expressions with incomplete variables, until all variables are in place. In turn, this may very easily reach a quadratic computational complexity.","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"More generally, any representation of substitution result that \"does not reduce() easily\" will suffer from this problem. A different (often specialized) approach is thus needed.","category":"page"},{"location":"5-jump-integration/#Solution:-Prevent-successive-folding","page":"Better integration with JuMP","title":"Solution: Prevent successive folding","text":"","category":"section"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"For such cases, it is recommended to replace the substitute calls with a custom function that can interpret the required Values itself, and converts them without the overhead of creating temporary values.","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"import ConstraintTrees as C","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"First, let's create a lot of variables, and a constraint that will usually trigger this problem (and a JuMP warning) if used with normal substitute:","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"x = :vars^C.variables(keys = Symbol.(\"x$i\" for i = 1:1000), bounds = C.Between(0, 10))\nx *= :sum^C.Constraint(sum(C.value.(values(x.vars))))","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"Now, imagine the expressions are represented e.g. by sparse vectors of fixed size (as common in linear-algebraic systems). We can produce the vectors efficiently as follows:","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"import SparseArrays: sparsevec\nv = x.sum.value\n\nvalue_in_a_vector = sparsevec(v.idxs, v.weights, 1000)","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"This usually requires only a single memory allocation, and runs in time linear with the number of variables in the value. As an obvious downside, you need to implement this functionality for all kinds of Values you encounter.","category":"page"},{"location":"5-jump-integration/#Solution-for-JuMP","page":"Better integration with JuMP","title":"Solution for JuMP","text":"","category":"section"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"LinearValues can be translated to JuMP's AffExprs:","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"using JuMP, GLPK\n\nfunction substitute_jump(val::C.LinearValue, vars)\n e = AffExpr() # unfortunately @expression(model, 0) is not type stable and gives an Int\n for (i, w) in zip(val.idxs, val.weights)\n if i == 0\n add_to_expression!(e, w)\n else\n add_to_expression!(e, w, vars[i])\n end\n end\n e\nend\n\nmodel = Model(GLPK.Optimizer)\n@variable(model, V[1:1000])\njump_value = substitute_jump(x.sum.value, V)","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"This function can be re-used in functions like optimized_vars as shown in other examples in the documentation.","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"For QuadraticValues, the same approach extends only with a minor modification:","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"function substitute_jump(val::C.QuadraticValue, vars)\n e = QuadExpr() # unfortunately @expression(model, 0) is not type stable and gives an Int\n for ((i, j), w) in zip(val.idxs, val.weights)\n if i == 0 && j == 0\n add_to_expression!(e, w)\n elseif i == 0 # the symmetric case is prohibited\n add_to_expression!(e, w, vars[j])\n else\n add_to_expression!(e, w, vars[i], vars[j])\n end\n end\n e\nend\n\nqvalue = 123 + (x.vars.x1.value + x.vars.x2.value) * (x.vars.x3.value - 321)\njump_qvalue = substitute_jump(qvalue, V)","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"This page was generated using Literate.jl.","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"EditURL = \"0-quickstart.jl\"","category":"page"},{"location":"0-quickstart/#Quick-start","page":"Quick start","title":"Quick start","text":"","category":"section"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"The primary purpose of ConstraintTrees.jl is to make the representation of constraint systems neat, and thus make their manipulation easy and high-level. In short, the package abstracts the users from keeping track of variable and constraint indexes in matrix form, and gives a nice data structure that describes the system, while keeping all variable allocation and constraint organization completely implicit.","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Here we demonstrate the absolutely basic concepts on the \"field allocation\" problem.","category":"page"},{"location":"0-quickstart/#The-problem:-Field-area-allocation","page":"Quick start","title":"The problem: Field area allocation","text":"","category":"section"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Suppose we have 100 square kilometers of field, 500 kilos of fertilizer and 300 kilos of insecticide. We also have a practically infinite supply of wheat and barley seeds. If we decide to sow barley, we can make 550🪙 per square kilometer of harvest; if we decide to go with wheat instead, we can make 350🪙. Unfortunately each square kilometer of wheat requires 6 kilos of fertilizer, and 1 kilo of insecticide, whereas each square kilometer of barley requires 2 kilos of fertilizer and 4 kilos of insecticide, because insects love barley.","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"How much of our fields should we allocate to wheat and barley to maximize our profit?","category":"page"},{"location":"0-quickstart/#Field-area-allocation-with-ConstraintTrees.jl","page":"Quick start","title":"Field area allocation with ConstraintTrees.jl","text":"","category":"section"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Let's import the package and start constructing the problem:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"import ConstraintTrees as C","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Let's name our system s. We first need a few variables:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s = C.variables(keys = [:wheat, :barley])","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"With ConstraintTrees.jl, we can (and want to!) label everything very nicely – the constraint trees are essentially directory structures, so one can prefix everything with symbols to put it into nice directories, e.g. as such:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":":area^s","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"To be absolutely realistic, we also want to make sure that all areas are non-negative. To demonstrate how to do that nicely from the start, we rather re-do the constraints with an appropriate interval bound:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s = :area^C.variables(keys = [:wheat, :barley], bounds = C.Between(0, Inf))","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Constraint trees can be browsed using dot notation, or just like dictionaries:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s.area","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s[:area].barley","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"(For convenience in some cases, string indexes are also supported:)","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s[\"area\"][\"barley\"]","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Now let's start rewriting the problem into the constraint-tree-ish description. First, we only have 100 square kilometers of area:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"total_area = s.area.wheat.value + s.area.barley.value\n\ntotal_area_constraint = C.Constraint(total_area, (0, 100))","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"We can add any kind of constraint into the existing constraint trees by \"merging\" multiple trees with operator *:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s *= :total_area^total_area_constraint","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Now let's add constraints for resources. We can create whole ConstraintTree structures like dictionaries in place, as follows:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s *=\n :resources^C.ConstraintTree(\n :fertilizer =>\n C.Constraint(s.area.wheat.value * 6 + s.area.barley.value * 2, (0, 500)),\n :insecticide =>\n C.Constraint(s.area.wheat.value * 1 + s.area.barley.value * 4, (0, 300)),\n )","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"We can also represent the expected profit as a constraint (although we do not need to actually put a constraint bound there):","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s *= :profit^C.Constraint(s.area.wheat.value * 350 + s.area.barley.value * 550)","category":"page"},{"location":"0-quickstart/#Solving-the-system-with-JuMP","page":"Quick start","title":"Solving the system with JuMP","text":"","category":"section"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"We can now take the structure of the constraint tree, translate it to any suitable linear optimizer interface, and have it solved. For popular reasons we choose JuMP with GLPK – the code is left uncommented here as-is; see the other examples for a slightly more detailed explanation:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"import JuMP\nfunction optimized_vars(cs::C.ConstraintTree, objective::C.LinearValue, optimizer)\n model = JuMP.Model(optimizer)\n JuMP.@variable(model, x[1:C.var_count(cs)])\n JuMP.@objective(model, JuMP.MAX_SENSE, C.substitute(objective, x))\n C.traverse(cs) do c\n b = c.bound\n if b isa C.EqualTo\n JuMP.@constraint(model, C.substitute(c.value, x) == b.equal_to)\n elseif b isa C.Between\n val = C.substitute(c.value, x)\n isinf(b.lower) || JuMP.@constraint(model, val >= b.lower)\n isinf(b.upper) || JuMP.@constraint(model, val <= b.upper)\n end\n end\n JuMP.optimize!(model)\n JuMP.value.(model[:x])\nend\n\nimport GLPK\noptimal_variable_assignment = optimized_vars(s, s.profit.value, GLPK.Optimizer)","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"This gives us the optimized variable values! If we cared to remember what they stand for, we might already know how much barley to sow. On the other hand, the main point of ConstraintTree.jl is that one should not be forced to remember things like variable ordering and indexes, or be forced to manually calculate how much money we actually make or how much fertilizer is going to be left – instead, we can simply feed the variable values back to the constraint tree, and get a really good overview of all values in our constrained system:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_s = C.substitute_values(s, optimal_variable_assignment)","category":"page"},{"location":"0-quickstart/#Browsing-the-result","page":"Quick start","title":"Browsing the result","text":"","category":"section"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_s is now like the original constraint tree, just the contents are \"plain old values\" instead of the constraints as above. Thus we can easily see our profit:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_s.profit","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"The occupied area for each crop:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_s.area","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"The consumed resources:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_s.resources","category":"page"},{"location":"0-quickstart/#Increasing-the-complexity","page":"Quick start","title":"Increasing the complexity","text":"","category":"section"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"A crucial property of constraint trees is that the users do not need to care about what kind of value they are manipulating – no matter if something is a variable or a derived value, the code that works with it is the same. For example, we can use the actual prices for our resources (30🪙 and 110🪙 for a kilo of fertilizer and insecticide, respectively) to make a corrected profit:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s *=\n :actual_profit^C.Constraint(\n s.profit.value - 30 * s.resources.fertilizer.value -\n 110 * s.resources.insecticide.value,\n )","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Is the result going to change if we optimize for the corrected profit?","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"realistically_optimal_s =\n C.substitute_values(s, optimized_vars(s, s.actual_profit.value, GLPK.Optimizer))","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"realistically_optimal_s.area","category":"page"},{"location":"0-quickstart/#Combining-constraint-systems:-Let's-have-a-factory!","page":"Quick start","title":"Combining constraint systems: Let's have a factory!","text":"","category":"section"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"The second crucial property of constraint trees is the ability to easily combine different constraint systems into one. Let's pretend we also somehow obtained a food factory that produces malty sweet bread and wheaty weizen-style beer, with various extra consumptions of water and heat for each of the products. For simplicity, let's just create the corresponding constraint system (f as a factory) here:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"f = :products^C.variables(keys = [:bread, :weizen], bounds = C.Between(0, Inf))\nf *= :profit^C.Constraint(25 * f.products.weizen.value + 35 * f.products.bread.value)","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"We can make the constraint systems more complex by adding additional variables. To make sure the variables do not \"conflict\", one must use the + operator. While constraint systems combined with * always share variables, constraint systems combined with + are independent.","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"f += :materials^C.variables(keys = [:wheat, :barley], bounds = C.Between(0, Inf))","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"How much resources are consumed by each product, with a limit on each:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"f *=\n :resources^C.ConstraintTree(\n :heat => C.Constraint(\n 5 * f.products.bread.value + 3 * f.products.weizen.value,\n (0, 1000),\n ),\n :water => C.Constraint(\n 2 * f.products.bread.value + 10 * f.products.weizen.value,\n (0, 3000),\n ),\n )","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"How much raw materials are required for each product:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"f *=\n :material_allocation^C.ConstraintTree(\n :wheat => C.Constraint(\n 8 * f.products.bread.value + 2 * f.products.weizen.value -\n f.materials.wheat.value,\n 0,\n ),\n :barley => C.Constraint(\n 0.5 * f.products.bread.value + 10 * f.products.weizen.value -\n f.materials.barley.value,\n 0,\n ),\n )","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Having the two systems at hand, we can connect the factory \"system\" f to the field \"system\" s, making a compound system c as such:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"c = :factory^f + :fields^s","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"warning: Operators for combining constraint trees\nAlways remember to use + instead of * when combining independent constraint systems. If we use *, the variables in both systems will become implicitly shared, which is rarely what one wants in the first place. Use * only if adding additional constraints to an existing system. As a rule of thumb, one can remember the boolean interpretation of * as \"and\" and of + as \"or\".On a side note, the operator ^ was chosen mainly to match the algebraic view of the tree combination, and nicely fit into Julia's operator priority structure.","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"To actually connect the systems (which now exist as completely independent parts of s), let's add a transport – the barley and wheat produced on the fields is going to be the only barley and wheat consumed by the factory, thus their production and consumption must sum to net zero:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"c *= :transport^C.zip(c.fields.area, c.factory.materials) do area, material\n C.Constraint(area.value - material.value, 0)\nend","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"info: High-level constraint tree manipulation\nThere is also a dedicated example with many more useful functions like zip above.","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Finally, let's see how much money can we make from having the factory supported by our fields in total!","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_c =\n C.substitute_values(c, optimized_vars(c, c.factory.profit.value, GLPK.Optimizer))","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"How much field area did we allocate?","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_c.fields.area","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"How much of each of the products does the factory make in the end?","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_c.factory.products","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"How much extra resources is consumed by the factory?","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_c.factory.resources","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"And what is the factory profit in the end?","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_c.factory.profit","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"This page was generated using Literate.jl.","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"EditURL = \"2-quadratic-optimization.jl\"","category":"page"},{"location":"2-quadratic-optimization/#Example:-Quadratic-optimization","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"","category":"section"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"In this example we demonstrate the use of quadratic constraints and values. We assume that the reader is already familiar with the construction of ConstraintTrees; if not, it is advisable to read the previous part of the documentation first.","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"In short, quadratic values and constraints are expressed similarly as other contents of the constraint trees using type QuadraticValue, which is basically an affine-quadratic alike of the affine-linear LinearValue.","category":"page"},{"location":"2-quadratic-optimization/#Working-with-quadratic-values-and-constraints","page":"Example: Quadratic optimization","title":"Working with quadratic values and constraints","text":"","category":"section"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"Algebraically, you can construct QuadraticValues simply by multiplying the linear LinearValues:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"import ConstraintTrees as C\n\nsystem = C.variables(keys = [:x, :y, :z])\nqv = system.x.value * (system.y.value + 2 * system.z.value)","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"As with LinearValues, the QuadraticValues can be easily combined, giving a nice way to specify e.g. weighted sums of squared errors with respect to various directions. We can thus represent common formulas for error values:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"error_val =\n C.squared(system.x.value + system.y.value - 1) +\n C.squared(system.y.value + 5 * system.z.value - 3)","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"This allows us to naturally express quadratic constraint (e.g., that an error must not be too big); and directly observe the error values in the system.","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"system = :vars^system * :error^C.Constraint(error_val, C.Between(0, 100))","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"(For simplicity, you can also use the Constraint constructor to make quadratic constraints out of QuadraticValues – it will overload properly.)","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"Let's pretend someone has solved the system, and see how much \"error\" the solution has:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"solution = [1.0, 2.0, -1.0]\nst = C.substitute_values(system, solution)\nst.error","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"...not bad for a first guess.","category":"page"},{"location":"2-quadratic-optimization/#Building-quadratic-systems","page":"Example: Quadratic optimization","title":"Building quadratic systems","text":"","category":"section"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"Let's create a small quadratic system that finds the closest distance between an ellipse and a line and let some of the conic solvers available in JuMP solve it. First, let's make a representation of a point in 2D:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"point = C.variables(keys = [:x, :y])","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"We can create a small system that constraints the point to stay within a simple elliptical area centered around (0.0, 10.0):","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"ellipse_system = C.ConstraintTree(\n :point => point,\n :in_area => C.Constraint(\n C.squared(point.x.value) / 4 + C.squared(10.0 - point.y.value),\n C.Between(-Inf, 1.0),\n ),\n)","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"We now create another small system that constraints another point to stay on a line that crosses (0, 0) and (2, 1). We could do this using a dot-product representation of line, but that would lead to issues later (mainly, the solver that we are planning to use only supports positive definite quadratic forms as constraints). Instead, let's use a single-variable-parametrized line equation.","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"line_param = C.variable().value\nline_system =\n :point^C.ConstraintTree(\n :x => C.Constraint(0 + 2 * line_param),\n :y => C.Constraint(0 + 1 * line_param),\n )","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"Finally, let's connect the systems using + operator and add the objective that would minimize the distance of the points:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"s = :ellipse^ellipse_system + :line^line_system\n\ns *=\n :objective^C.Constraint(\n C.squared(s.ellipse.point.x.value - s.line.point.x.value) +\n C.squared(s.ellipse.point.y.value - s.line.point.y.value),\n )","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"(Note that if we used * to connect the systems, the variables from the definition of point would not be duplicated, and various non-interesting logic errors would follow.)","category":"page"},{"location":"2-quadratic-optimization/#Solving-quadratic-systems-with-JuMP","page":"Example: Quadratic optimization","title":"Solving quadratic systems with JuMP","text":"","category":"section"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"To solve the above system, we need a matching solver that can work with quadratic constraints. Also, we need to slightly generalize the function that translates the constraints into JuMP Models to support the quadratic constraints.","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"import JuMP\nfunction quad_optimized_vars(cs::C.ConstraintTree, objective::C.Value, optimizer)\n model = JuMP.Model(optimizer)\n JuMP.@variable(model, x[1:C.var_count(cs)])\n JuMP.@objective(model, JuMP.MAX_SENSE, C.substitute(objective, x))\n C.traverse(cs) do c\n b = c.bound\n if b isa C.EqualTo\n JuMP.@constraint(model, C.substitute(c.value, x) == b.equal_to)\n elseif b isa C.Between\n val = C.substitute(c.value, x)\n isinf(b.lower) || JuMP.@constraint(model, val >= b.lower)\n isinf(b.upper) || JuMP.@constraint(model, val <= b.upper)\n end\n end\n JuMP.set_silent(model)\n JuMP.optimize!(model)\n JuMP.value.(model[:x])\nend","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"We can now load a suitable optimizer and solve the system by maximizing the negative squared error:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"import Clarabel\nst = C.substitute_values(s, quad_optimized_vars(s, -s.objective.value, Clarabel.Optimizer))","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"If the optimization worked well, we can nicely get out the position of the closest point to the line that is in the elliptical area:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"(st.ellipse.point.x, st.ellipse.point.y)","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"...as well as the position on the line that is closest to the ellipse:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"st.line.point","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"...and, with a little bit of extra math, the minimized distance:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"sqrt(st.objective)","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/#Reference","page":"Reference","title":"Reference","text":"","category":"section"},{"location":"reference/#Values","page":"Reference","title":"Values","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [ConstraintTrees]\nPages = [\"src/value.jl\"]","category":"page"},{"location":"reference/#ConstraintTrees.Value","page":"Reference","title":"ConstraintTrees.Value","text":"abstract type Value\n\nAbstract type of all values usable in constraints, including LinearValue and QuadraticValue.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.preduce-Tuple{Any, Any}","page":"Reference","title":"ConstraintTrees.preduce","text":"preduce(op, xs; init, stack_type) -> Any\n\n\nAn alternative of Base.reduce which does a \"pairwise\" reduction in the shape of a binary merge tree, like in mergesort. In general this is a little more complex, but if the reduced value \"grows\" with more elements added (such as when adding a lot of LinearValues together), this is able to prevent a complexity explosion by postponing \"large\" reducing operations as much as possible.\n\nIn the specific case with adding lots of LinearValues and QuadraticValues together, this effectively squashes the reduction complexity from something around O(n^2) to O(n) (with a little larger constant factor.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.substitute_values","page":"Reference","title":"ConstraintTrees.substitute_values","text":"substitute_values(\n x::ConstraintTrees.Value,\n y::AbstractVector\n) -> Any\nsubstitute_values(\n x::ConstraintTrees.Value,\n y::AbstractVector,\n _\n) -> Any\n\n\nSubstutite a value into a Value-typed x. This is a convenience overload for the purpose of having substitute_values to run on both Constraints and Values.\n\n\n\n\n\n","category":"function"},{"location":"reference/#ConstraintTrees.sum-Tuple{Any}","page":"Reference","title":"ConstraintTrees.sum","text":"sum(xs; init) -> Any\n\n\nAlias for preduce that uses + as the operation.\n\nNot as versatile as the sum from Base, but much faster for growing values like LinearValues and QuadraticValues.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.value-Tuple{T} where T<:Union{ConstraintTrees.Value, Real}","page":"Reference","title":"ConstraintTrees.value","text":"value(\n x::Union{ConstraintTrees.Value, Real}\n) -> Union{ConstraintTrees.Value, Real}\n\n\nReturns any Real- or Value-typed x. This is a convenience overload; typically one enjoys this more when extracting values from Constraints.\n\n\n\n\n\n","category":"method"},{"location":"reference/#Linear-and-affine-values","page":"Reference","title":"Linear and affine values","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [ConstraintTrees]\nPages = [\"src/linear_value.jl\"]","category":"page"},{"location":"reference/#ConstraintTrees.LinearValue","page":"Reference","title":"ConstraintTrees.LinearValue","text":"struct LinearValue <: ConstraintTrees.Value\n\nA representation of a \"value\" in a linear constrained optimization problem. The value is an affine linear combination of several variables.\n\nLinearValues can be combined additively and multiplied by real-number constants.\n\nMultiplying two LinearValues yields a quadratic form (in a QuadraticValue).\n\nFields\n\nidxs::Vector{Int64}: Indexes of the variables used by the value. The indexes must always be sorted in strictly increasing order. The affine element has index 0.\n\nweights::Vector{Float64}: Coefficients of the variables selected by idxs.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.LinearValue-Tuple{Real}","page":"Reference","title":"ConstraintTrees.LinearValue","text":"LinearValue(x::Real) -> ConstraintTrees.LinearValue\n\n\nConstruct a constant LinearValue with a single affine element.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.LinearValue-Tuple{SparseArrays.SparseVector{Float64}}","page":"Reference","title":"ConstraintTrees.LinearValue","text":"LinearValue(\n x::SparseArrays.SparseVector{Float64}\n) -> ConstraintTrees.LinearValue\n\n\nShortcut for making a LinearValue out of a linear combination defined by the SparseVector.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.add_sparse_linear_combination-Union{Tuple{T}, Tuple{Vector{Int64}, Vector{T}, Vector{Int64}, Vector{T}}} where T","page":"Reference","title":"ConstraintTrees.add_sparse_linear_combination","text":"add_sparse_linear_combination(\n a_idxs::Vector{Int64},\n a_weights::Array{T, 1},\n b_idxs::Vector{Int64},\n b_weights::Array{T, 1}\n) -> Tuple{Vector{Int64}, Vector}\n\n\nHelper function for implementing LinearValue-like objects. Given \"sparse\" representations of linear combinations, it computes a \"merged\" linear combination of 2 values added together.\n\nZeroes are not filtered out.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.substitute-Tuple{ConstraintTrees.LinearValue, Any}","page":"Reference","title":"ConstraintTrees.substitute","text":"substitute(x::ConstraintTrees.LinearValue, y) -> Any\n\n\nSubstitute anything vector-like as variable values into a LinearValue and return the result.\n\n\n\n\n\n","category":"method"},{"location":"reference/#Quadratic-values","page":"Reference","title":"Quadratic values","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [ConstraintTrees]\nPages = [\"src/quadratic_value.jl\"]","category":"page"},{"location":"reference/#ConstraintTrees.QuadraticValue","page":"Reference","title":"ConstraintTrees.QuadraticValue","text":"struct QuadraticValue <: ConstraintTrees.Value\n\nA representation of a quadratic form in the constrained optimization problem. The QuadraticValue is an affine quadratic combination (i.e., a polynomial of maximum degree 2) over the variables.\n\nQuadraticValues can be combined additively and multiplied by real-number constants. The cleanest way to construct a QuadraticValue is to multiply two LinearValues.\n\nFields\n\nidxs::Vector{Tuple{Int64, Int64}}: Indexes of variable pairs used by the value. The indexes must always be sorted in strictly co-lexicographically increasing order, and the second index must always be greater than or equal to the first one. (Speaking in matrix terms, the indexing follows the indexes in an upper triangular matrix by columns.)\nAs an outcome, the second index of the last index pair can be used as the upper bound of all variable indexes.\nAs with LinearValue, index 0 represents the affine element.\n\nweights::Vector{Float64}: Coefficient of the variable pairs selected by idxs.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.QuadraticValue-Tuple{ConstraintTrees.LinearValue}","page":"Reference","title":"ConstraintTrees.QuadraticValue","text":"QuadraticValue(\n x::ConstraintTrees.LinearValue\n) -> ConstraintTrees.QuadraticValue\n\n\nConstruct a QuadraticValue that is equivalent to a given LinearValue.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.QuadraticValue-Tuple{Real}","page":"Reference","title":"ConstraintTrees.QuadraticValue","text":"QuadraticValue(x::Real) -> ConstraintTrees.QuadraticValue\n\n\nConstruct a constant QuadraticValue with a single affine element.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.QuadraticValue-Tuple{SparseArrays.SparseMatrixCSC{Float64}}","page":"Reference","title":"ConstraintTrees.QuadraticValue","text":"QuadraticValue(\n x::SparseArrays.SparseMatrixCSC{Float64}\n) -> ConstraintTrees.QuadraticValue\n\n\nShortcut for making a QuadraticValue out of a square sparse matrix. The matrix is force-symmetrized by calculating x' + x.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.add_sparse_quadratic_combination-Union{Tuple{T}, Tuple{Vector{Tuple{Int64, Int64}}, Vector{T}, Vector{Tuple{Int64, Int64}}, Vector{T}}} where T","page":"Reference","title":"ConstraintTrees.add_sparse_quadratic_combination","text":"add_sparse_quadratic_combination(\n a_idxs::Vector{Tuple{Int64, Int64}},\n a_weights::Array{T, 1},\n b_idxs::Vector{Tuple{Int64, Int64}},\n b_weights::Array{T, 1}\n) -> Tuple{Vector{Tuple{Int64, Int64}}, Vector}\n\n\nHelper function for implementing QuadraticValue-like objects. Given 2 sparse representations of quadratic combinations, it computes a \"merged\" one with the values of both added together.\n\nZeroes are not filtered out.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.colex_le-Tuple{Any, Any}","page":"Reference","title":"ConstraintTrees.colex_le","text":"colex_le(, ) -> Any\n\n\nInternal helper for co-lex ordering of indexes.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.multiply_sparse_linear_combination-Union{Tuple{T}, Tuple{Vector{Int64}, Vector{T}, Vector{Int64}, Vector{T}}} where T","page":"Reference","title":"ConstraintTrees.multiply_sparse_linear_combination","text":"multiply_sparse_linear_combination(\n a_idxs::Vector{Int64},\n a_weights::Array{T, 1},\n b_idxs::Vector{Int64},\n b_weights::Array{T, 1}\n) -> Tuple{Vector{Tuple{Int64, Int64}}, Vector}\n\n\nHelper function for multiplying two LinearValue-like objects to make a QuadraticValue-like object. This computes and merges the product.\n\nZeroes are not filtered out.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.squared-Tuple{ConstraintTrees.LinearValue}","page":"Reference","title":"ConstraintTrees.squared","text":"squared(\n a::ConstraintTrees.LinearValue\n) -> ConstraintTrees.QuadraticValue\n\n\nBroadcastable shortcut for multiplying a LinearValue with itself. Produces a QuadraticValue.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.substitute-Tuple{ConstraintTrees.QuadraticValue, Any}","page":"Reference","title":"ConstraintTrees.substitute","text":"substitute(x::ConstraintTrees.QuadraticValue, y) -> Any\n\n\nSubstitute anything vector-like as variable values into the QuadraticValue and return the result.\n\n\n\n\n\n","category":"method"},{"location":"reference/#Constraints","page":"Reference","title":"Constraints","text":"","category":"section"},{"location":"reference/#Bounds","page":"Reference","title":"Bounds","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [ConstraintTrees]\nPages = [\"src/bound.jl\"]","category":"page"},{"location":"reference/#ConstraintTrees.MaybeBound","page":"Reference","title":"ConstraintTrees.MaybeBound","text":"Shortcut for all possible Bounds including the \"empty\" bound that does not constraint anything (represented by nothing).\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.Between","page":"Reference","title":"ConstraintTrees.Between","text":"mutable struct Between <: ConstraintTrees.Bound\n\nRepresentation of an \"interval\" bound; consisting of lower and upper bound value.\n\nFields\n\nlower::Float64: Lower bound\nupper::Float64: Upper bound\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.Bound","page":"Reference","title":"ConstraintTrees.Bound","text":"abstract type Bound\n\nAbstract type of all bounds usable in constraints, including Between and EqualTo.\n\nTo make broadcasting work, length(::Bound) = 1 has been extended. This allows functions like variables to broadcast a single supplied bound across all constraints.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.EqualTo","page":"Reference","title":"ConstraintTrees.EqualTo","text":"mutable struct EqualTo <: ConstraintTrees.Bound\n\nRepresentation of an \"equality\" bound; contains the single \"equal to this\" value.\n\nFields\n\nequal_to::Float64: Equality bound value\n\n\n\n\n\n","category":"type"},{"location":"reference/#Constrained-values","page":"Reference","title":"Constrained values","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [ConstraintTrees]\nPages = [\"src/constraint.jl\"]","category":"page"},{"location":"reference/#ConstraintTrees.Constraint","page":"Reference","title":"ConstraintTrees.Constraint","text":"mutable struct Constraint\n\nA representation of a single constraint that may limit the given value by a specific Bound.\n\nConstraints without a bound (nothing in the bound field) are possible; these have no impact on the optimization problem but the associated value becomes easily accessible for inspection and building other constraints.\n\nFields\n\nvalue::ConstraintTrees.Value: A value (typically a LinearValue or a QuadraticValue) that describes what the constraint constraints.\nbound::Union{Nothing, ConstraintTrees.Bound}: A bound that the value must satisfy. Should be a subtype of MaybeBound: Either nothing if there's no bound, or e.g. EqualTo, Between or similar structs.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.bound-Tuple{ConstraintTrees.Constraint}","page":"Reference","title":"ConstraintTrees.bound","text":"bound(\n x::ConstraintTrees.Constraint\n) -> Union{Nothing, ConstraintTrees.Bound}\n\n\nSimple accessor for getting out the bound from the constraint that can be used for broadcasting (as opposed to the dot-field access).\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.substitute-Tuple{ConstraintTrees.Constraint, Any}","page":"Reference","title":"ConstraintTrees.substitute","text":"substitute(\n x::ConstraintTrees.Constraint,\n y\n) -> ConstraintTrees.Constraint\n\n\nSubstitute anything vector-like as variables into the constraint's value, producing a constraint with the new value.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.substitute_values-2","page":"Reference","title":"ConstraintTrees.substitute_values","text":"substitute_values(\n x::ConstraintTrees.Constraint,\n y::AbstractVector\n) -> Any\nsubstitute_values(\n x::ConstraintTrees.Constraint,\n y::AbstractVector,\n _\n) -> Any\n\n\nOverload of substitute_values for a single constraint.\n\n\n\n\n\n","category":"function"},{"location":"reference/#ConstraintTrees.value-Tuple{ConstraintTrees.Constraint}","page":"Reference","title":"ConstraintTrees.value","text":"value(\n x::ConstraintTrees.Constraint\n) -> ConstraintTrees.Value\n\n\nSimple accessor for getting out the value from the constraint that can be used for broadcasting (as opposed to the dot-field access).\n\n\n\n\n\n","category":"method"},{"location":"reference/#Labeled-trees","page":"Reference","title":"Labeled trees","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [ConstraintTrees]\nPages = [\"src/tree.jl\"]","category":"page"},{"location":"reference/#ConstraintTrees.OptionalTree","page":"Reference","title":"ConstraintTrees.OptionalTree","text":"Helper type for implementation of merge-related functions.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.Tree","page":"Reference","title":"ConstraintTrees.Tree","text":"struct Tree{X}\n\nA base \"labeled tree\" structure. Supports many interesting operations such as merging.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.elems-Tuple{ConstraintTrees.Tree}","page":"Reference","title":"ConstraintTrees.elems","text":"elems(\n x::ConstraintTrees.Tree\n) -> DataStructures.SortedDict{Symbol, Union{ConstraintTrees.Tree{X}, X}} where X\n\n\nGet the elements dictionary out of the Tree. This is useful for getting an iterable container for working with many items at once.\n\nAlso, because of the overload of getproperty for Tree, this serves as a simpler way to get the elements without an explicit use of getfield.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.imap-Union{Tuple{T}, Tuple{Any, Any}, Tuple{Any, Any, Type{T}}} where T","page":"Reference","title":"ConstraintTrees.imap","text":"imap(f, x) -> Any\nimap(f, x, ::Type{T}) -> Any\n\n\nLike map, but keeping the \"index\" path and giving it to the function as the first parameter. The \"path\" in the tree is reported as a tuple of symbols.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.imapreduce-Tuple{Any, Any, Any}","page":"Reference","title":"ConstraintTrees.imapreduce","text":"imapreduce(f, op, x; init) -> Any\n\n\nLike mapreduce but reporting the \"tree directory path\" where the reduced elements occur, like with imap. (Single elements from different directory paths are not reduced together.)\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.imerge-Union{Tuple{T}, Tuple{Any, Any, Any}, Tuple{Any, Any, Any, Type{T}}} where T","page":"Reference","title":"ConstraintTrees.imerge","text":"imerge(f, x, y) -> Any\nimerge(f, x, y, ::Type{T}) -> Any\n\n\nIndex-reporting variant of merge (see imap for reference).\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.ireduce-Tuple{Any, Any}","page":"Reference","title":"ConstraintTrees.ireduce","text":"ireduce(op, x; init) -> Any\n\n\nIndexed version of reduce (internally uses imapreduce).\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.itraverse-Tuple{Any, Any}","page":"Reference","title":"ConstraintTrees.itraverse","text":"itraverse(f, x) -> Any\n\n\nitraverse is to traverse like imap is to map.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.izip-Union{Tuple{T}, Tuple{Any, Any, Any}, Tuple{Any, Any, Any, Type{T}}} where T","page":"Reference","title":"ConstraintTrees.izip","text":"izip(f, x, y) -> Any\nizip(f, x, y, ::Type{T}) -> Any\n\n\nIndex-reporting variant of zip (see imap for reference).\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.map-Union{Tuple{T}, Tuple{Any, Any}, Tuple{Any, Any, Type{T}}} where T","page":"Reference","title":"ConstraintTrees.map","text":"map(f, x) -> Any\nmap(f, x, ::Type{T}) -> Any\n\n\nRun a function over everything in the tree. The resulting tree will contain elements of type specified by the 3rd argument. (This needs to be specified explicitly, because the typesystem generally cannot guess the universal type correctly.)\n\nNote this is a specialized function specific for Trees that behaves differently from Base.map.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.mapreduce-Tuple{Any, Any, Any}","page":"Reference","title":"ConstraintTrees.mapreduce","text":"mapreduce(f, op, x; init) -> Any\n\n\nReduce all items in a Tree. As with Base.reduce, the reduction order is not guaranteed, and the initial value may be used any number of times.\n\nNote this is a specialized function specific for Trees that behaves differently from Base.mapreduce.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.merge-Union{Tuple{T}, Tuple{Any, Any, Any}, Tuple{Any, Any, Any, Type{T}}} where T","page":"Reference","title":"ConstraintTrees.merge","text":"merge(f, x, y) -> Any\nmerge(f, x, y, ::Type{T}) -> Any\n\n\nRun a function over the values in the merge of all paths in the trees (currently there is support for 2 and 3 trees). This is an \"outer join\" equivalent of zip. Missing elements are replaced by missing in the function call parameters, and the function may return missing to omit elements.\n\nNote this is a specialized function specific for Trees that behaves differently from Base.merge.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.optional_tree_get-Tuple{Missing, Any}","page":"Reference","title":"ConstraintTrees.optional_tree_get","text":"optional_tree_get(_::Missing, _) -> Missing\n\n\nGet a key from a tree that is possibly missing.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.optional_tree_keys-Tuple{Missing}","page":"Reference","title":"ConstraintTrees.optional_tree_keys","text":"optional_tree_keys(\n _::Missing\n) -> DataStructures.SortedSet{Any, Base.Order.ForwardOrdering}\n\n\nGet a sorted set of keys from a tree that is possibly missing.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.reduce-Tuple{Any, Any}","page":"Reference","title":"ConstraintTrees.reduce","text":"reduce(op, x; init) -> Any\n\n\nLike mapreduce but the mapped function is identity.\n\nTo avoid much type suffering, the operation should ideally preserve the type of its arguments. If you need to change the type, you likely want to use mapreduce.\n\nNote this is a specialized function specific for Trees that behaves differently from Base.reduce.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.traverse-Tuple{Any, Any}","page":"Reference","title":"ConstraintTrees.traverse","text":"traverse(f, x) -> Any\n\n\nLike map, but discards the results, thus relying only on the side effects of f.\n\nTechnically the name should be for, but that's a Julia keyword.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.zip-Union{Tuple{T}, Tuple{Any, Any, Any}, Tuple{Any, Any, Any, Type{T}}} where T","page":"Reference","title":"ConstraintTrees.zip","text":"zip(f, x, y) -> Any\nzip(f, x, y, ::Type{T}) -> Any\n\n\nRun a function over the values in the intersection of paths in several trees (currently there is support for 2 and 3 trees). This is an \"inner join\" – all extra elements are ignored. \"Outer join\" can be done via merge.\n\nAs with map, the inner type of the resulting tree must be specified by the last parameter..\n\nNote this is a specialized function specific for Trees that behaves differently from Base.zip.\n\n\n\n\n\n","category":"method"},{"location":"reference/#Constraint-trees","page":"Reference","title":"Constraint trees","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [ConstraintTrees]\nPages = [\"src/constraint_tree.jl\"]","category":"page"},{"location":"reference/#ConstraintTrees.ConstraintTreeElem","page":"Reference","title":"ConstraintTrees.ConstraintTreeElem","text":"A shortcut for the type of the values in ConstraintTree.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.ConstraintTree","page":"Reference","title":"ConstraintTrees.ConstraintTree","text":"struct Tree{ConstraintTrees.Constraint}\n\nA hierarchical tree of many constraints that together describe a constrained system. The tree may recursively contain other trees in a directory-like structure, which contain Constraints as leaves.\n\nMembers of the constraint tree are accessible via the record dot syntax as properties; e.g. a constraint labeled with :abc in a constraint tree t may be accessed as t.abc and as t[:abc], and can be found while iterating through elems(t).\n\nConstructing the constraint trees\n\nUse operator ^ to put a name on a constraint to convert it into a single element ConstraintTree:\n\nx = :my_constraint ^ Constraint(LinearValue(...), 1.0)\ndir = :my_constraint_dir ^ x\n\ndir.my_constraint_dir.my_constraint.bound # returns 1.0\n\nUse operator * to glue two constraint trees together while sharing the variable indexes specified by the contained LinearValues and QuadraticValues.\n\nmy_constraints = :some_constraints ^ Constraint(...) * :more_constraints ^ Constraint(...)\n\nUse operator + to glue two constraint trees together without sharing of any variables. The operation will renumber the variables in the trees so that the sets of variable indexes used by either tree are completely disjunct, and then glue the trees together as with *:\n\ntwo_independent_systems = my_system + other_system\n\nVariable sharing limitations\n\nBecause of the renumbering, you can not easily use constraints and values from the values before the addition in the constraint tree that is the result of the addition. There is no check against that – the resulting ConstraintTree will be valid, but will probably describe a different optimization problem than you intended.\n\nAs a rule of thumb, avoid necessary parentheses in expressions that work with the constraint trees: While t1 * t2 + t3 might work just as intended, t1 * (t2 + t3) is almost certainly wrong because the variables in t1 that are supposed to connect to variables in either of t2 and t3 will not connect properly because of renumbering of both t2 and t3. If you need to construct a tree like that, do the addition first, and construct the t1 after that, based on the result of the addition.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.incr_var_idx-Tuple{Int64, Int64}","page":"Reference","title":"ConstraintTrees.incr_var_idx","text":"incr_var_idx(x::Int64, incr::Int64) -> Int64\n\n\nInternal helper for manipulating variable indices.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.incr_var_idxs-Tuple{ConstraintTrees.Constraint, Int64}","page":"Reference","title":"ConstraintTrees.incr_var_idxs","text":"incr_var_idxs(\n x::ConstraintTrees.Constraint,\n incr::Int64\n) -> ConstraintTrees.Constraint\n\n\nOffset all variable indexes in a ConstraintTree by the given increment.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.incr_var_idxs-Tuple{ConstraintTrees.LinearValue, Int64}","page":"Reference","title":"ConstraintTrees.incr_var_idxs","text":"incr_var_idxs(\n x::ConstraintTrees.LinearValue,\n incr::Int64\n) -> ConstraintTrees.LinearValue\n\n\nOffset all variable indexes in a LinearValue by the given increment.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.incr_var_idxs-Tuple{ConstraintTrees.QuadraticValue, Int64}","page":"Reference","title":"ConstraintTrees.incr_var_idxs","text":"incr_var_idxs(\n x::ConstraintTrees.QuadraticValue,\n incr::Int64\n) -> ConstraintTrees.QuadraticValue\n\n\nOffset all variable indexes in a QuadraticValue by the given increment.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.incr_var_idxs-Tuple{ConstraintTrees.Tree{ConstraintTrees.Constraint}, Int64}","page":"Reference","title":"ConstraintTrees.incr_var_idxs","text":"incr_var_idxs(\n x::ConstraintTrees.Tree{ConstraintTrees.Constraint},\n incr::Int64\n) -> ConstraintTrees.Tree{ConstraintTrees.Constraint}\n\n\nOffset all variable indexes in a ConstraintTree by the given increment.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.substitute_values-Union{Tuple{T}, Tuple{ConstraintTrees.Tree, AbstractVector}, Tuple{ConstraintTrees.Tree, AbstractVector, Type{T}}} where T","page":"Reference","title":"ConstraintTrees.substitute_values","text":"substitute_values(\n x::ConstraintTrees.Tree,\n y::AbstractVector\n) -> Any\nsubstitute_values(\n x::ConstraintTrees.Tree,\n y::AbstractVector,\n ::Type{T}\n) -> Any\n\n\nSubstitute variable values from y into the constraint tree's constraint's values, getting a tree of \"solved\" constraint values for the given variable assignment.\n\nThe third argument forces the output type (it is forwarded to map). The type gets defaulted from eltype(y).\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.var_count-Tuple{ConstraintTrees.Constraint}","page":"Reference","title":"ConstraintTrees.var_count","text":"var_count(x::ConstraintTrees.Constraint) -> Int64\n\n\nFind the expected count of variables in a Constraint.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.var_count-Tuple{ConstraintTrees.LinearValue}","page":"Reference","title":"ConstraintTrees.var_count","text":"var_count(x::ConstraintTrees.LinearValue) -> Int64\n\n\nFind the expected count of variables in a LinearValue. (This is a O(1) operation, relying on the ordering of the indexes.)\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.var_count-Tuple{ConstraintTrees.QuadraticValue}","page":"Reference","title":"ConstraintTrees.var_count","text":"var_count(x::ConstraintTrees.QuadraticValue) -> Int64\n\n\nFind the expected count of variables in a QuadraticValue. (This is a O(1) operation, relying on the co-lexicographical ordering of indexes.)\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.var_count-Tuple{ConstraintTrees.Tree{ConstraintTrees.Constraint}}","page":"Reference","title":"ConstraintTrees.var_count","text":"var_count(\n x::ConstraintTrees.Tree{ConstraintTrees.Constraint}\n) -> Int64\n\n\nFind the expected count of variables in a ConstraintTree.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.variable-Tuple{}","page":"Reference","title":"ConstraintTrees.variable","text":"variable(; bound, idx) -> ConstraintTrees.Constraint\n\n\nAllocate a single unnamed variable, returning a Constraint with an optionally specified bound.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.variables-Tuple{}","page":"Reference","title":"ConstraintTrees.variables","text":"variables(; keys, bounds)\n\n\nMake a trivial constraint system that creates variables with indexes in range 1:length(keys) named in order as given by keys.\n\nParameter bounds is either nothing for creating variables without bounds assigned to them, a single bound for creating variables with the same constraint assigned to them all, or an iterable object of same length as keys with individual bounds for each variable in the same order as keys.\n\nThe individual bounds should be subtypes of Bound, or nothing. To pass a single bound for all variables, use e.g. bounds = EqualTo(0).\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.variables_for-Tuple{Any, ConstraintTrees.Tree}","page":"Reference","title":"ConstraintTrees.variables_for","text":"variables_for(makebound, ts::ConstraintTrees.Tree) -> Any\n\n\nAllocate a variable for each item in a constraint tree (or any other kind of tree) and return a ConstraintTree with variables bounded by the makebound function, which converts a given tree element's value into a bound for the corresponding variable.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.variables_ifor-Tuple{Any, ConstraintTrees.Tree}","page":"Reference","title":"ConstraintTrees.variables_ifor","text":"variables_ifor(makebound, ts::ConstraintTrees.Tree) -> Any\n\n\nLike variables_for but the makebound function also receives a path to the variable, as with imap.\n\n\n\n\n\n","category":"method"},{"location":"reference/#Trees-for-storing-solved-values","page":"Reference","title":"Trees for storing solved values","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [ConstraintTrees]\nPages = [\"src/value_tree.jl\"]","category":"page"},{"location":"#ConstraintTrees.jl","page":"README","title":"ConstraintTrees.jl","text":"","category":"section"},{"location":"","page":"README","title":"README","text":"Modules = [ConstraintTrees]\nPages = [\"src/ConstraintTrees.jl\"]","category":"page"},{"location":"#ConstraintTrees.ConstraintTrees","page":"README","title":"ConstraintTrees.ConstraintTrees","text":"Package ConstraintTrees.jl provides a simple data structure ConstraintTree for organizing the contents of linear and quadratic constrained optimization problems. As a main goal, it abstracts over the distinction between constraints and variables, allowing much tidier representation for many kinds of complex constraint systems.\n\nThe primary purpose of ConstraintTrees.jl is to work with COBREXA.jl; but the package is otherwise completely independent, lightweight, dependency-free and usecase-agnostic. Generally, it is intended to be used with JuMP and the documentation uses JuMP for demonstrations, but any other solver framework will do just as well.\n\nThe package is structured as follows:\n\nThere is no representation for variables in the model; instead, values depend on anonymous numbered variables, and, if suitable, special named values may \"implicitly\" serve as representations for variables. This assumption erases the distinction between a \"simple\" variable and a complex derived linear combination, allowing more freedom in model construction.\nVariables may be combined into LinearValues and QuadraticValues, which are affine combinations and quadratic-affine combinations (respecitively) of values of some selected variables.\nValues may be bounded to an interval or exact value using a Constraint\nA collection of named Constraints is called a ConstraintTree; it behaves mostly as a specialized Symbol-keyed dictionary.\nConstraintTrees can be very easily organized into subdirectories, combined and made independent on each other using operators ^, *, and + – this forms the basis of the \"tidy\" algebra of constraints.\nA variable assignment, which is typically the \"solution\" for a given constraint tree, can be combined with a ConstraintTree to create a \"value tree\" via substitute_values, which enables browsing of the optimization results in the very same structure as the input ConstraintTree.\n\nYou can follow the examples in documentation and the docstrings of package contents for more details.\n\n\n\n\n\n","category":"module"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"EditURL = \"1-metabolic-modeling.jl\"","category":"page"},{"location":"1-metabolic-modeling/#Example:-Metabolic-modeling","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"In this example we demonstrate the use of ConstraintTree structure for solving the metabolic modeling tasks. At the same time, we show how to export the structure to JuMP, and use value trees to find useful information about the result.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"First, let's import some packages:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"import ConstraintTrees as C","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"We will need a constraint-based metabolic model; for this test we will use the usual \"E. Coli core metabolism\" model as available from BiGG:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"import Downloads: download\n\ndownload(\"http://bigg.ucsd.edu/static/models/e_coli_core.xml\", \"e_coli_core.xml\")\n\nimport SBML\necoli = SBML.readSBML(\"e_coli_core.xml\")","category":"page"},{"location":"1-metabolic-modeling/#Allocating-and-constraining-variables","page":"Example: Metabolic modeling","title":"Allocating and constraining variables","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Let's first build the constrained representation of the problem. First, we will need a variable for each of the reactions in the model.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c = C.variables(keys = Symbol.(keys(ecoli.reactions)))","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"info: Pretty-printing\nBy default, Julia shows relatively long namespace prefixes before all identifiers, which clutters the output. You can import individual names form ConstraintTrees package to improve the pretty-printing, using e.g.: import ConstraintTrees: Constraint, Tree, LinearValue.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"The above operation returns a ConstraintTree. You can browse these as a dictionary:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c[:R_PFK]","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"...or much more conveniently using the record dot syntax as properties:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c.R_PFK","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"The individual LinearValues in constraints behave like sparse vectors that refer to variables: The first field represents the referenced variable indexes, and the second field represents the coefficients. Compared to the sparse vectors, information about the total number of variables is not stored explicitly.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Operator ^ is used to name individual constraints and directories in the hierarchy. Let us name our constraints as \"fluxes\" (which is a common name in metabolic modeling) and explore the result:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c = :fluxes^c","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"We can see that there is now only a single \"top-level directory\" in the constraint system, which can be explored with the dot access again:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c.fluxes.R_PFK","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Indexing via values is again possible via the usual bracket notation, and can be freely combined with the dot notation:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c[:fluxes][:R_PFK]","category":"page"},{"location":"1-metabolic-modeling/#Adding-single-variable-constraints","page":"Example: Metabolic modeling","title":"Adding single-variable constraints","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Each element in the constraint tree consists of a linear combination of the variables, which can be freely used to construct (and constraint) new linear combinations of variables. As the simplest use, we can constraint the variables (using Constraints) to their valid bounds as defined by the model:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"rxn_constraints =\n let rxn_bounds = Symbol.(keys(ecoli.reactions)) .=> zip(SBML.flux_bounds(ecoli)...)\n C.ConstraintTree(\n r => C.Constraint(value = c.fluxes[r].value, bound = (lb, ub)) for\n (r, ((lb, _), (ub, _))) in rxn_bounds # SBML units are ignored for simplicity\n )\n end","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Note that in the example we use a simplified Dict-like construction of the ConstraintTree. You might equivalently write the code as a product (using prod()) of constraints that are individually labeled using the ^ operator, but the direct dictionary construction is faster because it skips many intermediate steps, and looks much more like idiomatic Julia code.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"To combine the constraint trees, we can make a nice directory for the constraints and add them to the tree using operator *. Making \"products\" of constraint trees combines the trees in a way that they share their variables. In particular, using the values from c.fluxes in the constraints within rxn_constraints here will constraint precisely the same variables (and thus values) as the ones in the original system.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c = c * :constraints^rxn_constraints","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Our model representation now contains 2 \"directories\":","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"collect(keys(c))","category":"page"},{"location":"1-metabolic-modeling/#Value-and-constraint-arithmetics","page":"Example: Metabolic modeling","title":"Value and constraint arithmetics","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Values may be combined additively and multiplied by real constants; which allows us to easily create more complex linear combination of any values already occurring in the model:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"3 * c.fluxes.R_PFK.value - c.fluxes.R_ACALD.value / 2","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"For simplicity, you can also scale whole constraints, but it is impossible to add them together because the meaning of the bounds would get broken:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"(3 * c.fluxes.R_PFK, -c.fluxes.R_ACALD / 2)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"To process constraints in bulk, you may use C.value for easier access to values when making new constraints:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"sum(C.value.(values(c.fluxes)))","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Notably, ConstraintTrees provide their own implementation of sum which typically works faster when adding many Values together. The basic interface and results are otherwise the same as with the sum from Base:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"C.sum(C.value.(values(c.fluxes)))","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"danger: `Base.sum` vs. `ConstraintTrees.sum`\nSince the sum from Base package is usually implemented as a left fold, it does not behave optimally when the temporary sub-results grow during the computation (and thus their addition becomes gradually slower). In turn, using the Base.sum for summing up LinearValues and QuadraticValues may take time quadratic in the number of added items. sum from ConstraintTrees uses a different addition order which reduces the amount of large items added together (implemented by \"pairwise\" preduce), and in works in almost-linear time in most cases.","category":"page"},{"location":"1-metabolic-modeling/#Affine-values","page":"Example: Metabolic modeling","title":"Affine values","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"To simplify various modeling goals (mainly calculation of various kinds of \"distances\"), the values support inclusion of an affine element – the variable with index 0 is assumed to be the \"affine unit\", and its assigned value is fixed at 1.0.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"To demonstrate, let's make a small system with 2 variables.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"system = C.variables(keys = [:x, :y])","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"To add an affine element to a LinearValue, simply add it as a Real number, as in the linear transformations below:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"system =\n :original_coords^system *\n :transformed_coords^C.ConstraintTree(\n :xt => C.Constraint(1 + system.x.value + 4 + system.y.value),\n :yt => C.Constraint(0.1 * (3 - system.y.value)),\n )","category":"page"},{"location":"1-metabolic-modeling/#Adding-combined-constraints","page":"Example: Metabolic modeling","title":"Adding combined constraints","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Metabolic modeling relies on the fact that the total rates of any metabolite getting created and consumed by the reaction equals to zero (which corresponds to conservation of mass). We can now add corresponding \"stoichiometric\" network constraints by following the reactants and products in the SBML structure:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"stoi_constraints = C.ConstraintTree(\n Symbol(m) => C.Constraint(\n value = -C.sum(\n (\n sr.stoichiometry * c.fluxes[Symbol(rid)].value for\n (rid, r) in ecoli.reactions for sr in r.reactants if sr.species == m\n ),\n init = zero(C.LinearValue), # sometimes the sums are empty\n ) + C.sum(\n (\n sr.stoichiometry * c.fluxes[Symbol(rid)].value for\n (rid, r) in ecoli.reactions for sr in r.products if sr.species == m\n ),\n init = zero(C.LinearValue),\n ),\n bound = 0.0,\n ) for m in keys(ecoli.species)\n);\nnothing #hide","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Let's have a closer look at one of the constraints:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"stoi_constraints.M_acald_c","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Again, we can label the stoichiometry properly and add it to the bigger model representation:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c = c * :stoichiometry^stoi_constraints","category":"page"},{"location":"1-metabolic-modeling/#Saving-the-objective","page":"Example: Metabolic modeling","title":"Saving the objective","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Constraint based models typically optimize a certain linear formula. Constraint trees do not support setting objectives (they are not constraints), but we can save the objective as a harmless unconstrained \"constraint\" that can be used later to refer to the objective more easily. We can save that information into the constraint system immediately:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c *=\n :objective^C.Constraint(\n C.sum(\n c.fluxes[Symbol(rid)].value * coeff for\n (rid, coeff) in (keys(ecoli.reactions) .=> SBML.flux_objective(ecoli)) if\n coeff != 0.0;\n init = 0.0,\n ),\n )","category":"page"},{"location":"1-metabolic-modeling/#Constrained-system-solutions-and-value-trees","page":"Example: Metabolic modeling","title":"Constrained system solutions and value trees","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"To aid exploration of variable assignments in the constraint trees, we can convert them to value trees. These have the very same structure as constraint trees, but carry only the \"solved\" constraint values instead of full constraints.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Let's demonstrate this quickly on the example of system with affine variables from above. First, let's assume that someone solved the system (in some way) and produced a solution of variables as follows:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"solution = [1.0, 5.0] # corresponds to :x and :y in order given in `variables`","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"A value tree for this solution is constructed in a straightforward manner:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"st = C.substitute_values(system, solution)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"We can now check the values of the original coordinates","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"st.original_coords","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"The other constraints automatically get their values that correspond to the overall variable assignment:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"st.transformed_coords","category":"page"},{"location":"1-metabolic-modeling/#Solving-the-constraint-system-using-JuMP","page":"Example: Metabolic modeling","title":"Solving the constraint system using JuMP","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"We can make a small function that throws our model into JuMP, optimizes it, and gives us back a variable assignment vector. This vector can then be used to determine and browse the values of constraints and variables using a Float64-valued tree.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"import JuMP\nfunction optimized_vars(cs::C.ConstraintTree, objective::C.LinearValue, optimizer)\n model = JuMP.Model(optimizer)\n JuMP.@variable(model, x[1:C.var_count(cs)])\n JuMP.@objective(model, JuMP.MAX_SENSE, C.substitute(objective, x))\n C.traverse(cs) do c\n b = c.bound\n if b isa C.EqualTo\n JuMP.@constraint(model, C.substitute(c.value, x) == b.equal_to)\n elseif b isa C.Between\n val = C.substitute(c.value, x)\n isinf(b.lower) || JuMP.@constraint(model, val >= b.lower)\n isinf(b.upper) || JuMP.@constraint(model, val <= b.upper)\n end\n end\n JuMP.optimize!(model)\n JuMP.value.(model[:x])\nend","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"With this in hand, we can use an external linear problem solver to find the optimum of the constrained system:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"import GLPK\noptimal_variable_assignment = optimized_vars(c, c.objective.value, GLPK.Optimizer)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"To explore the solution more easily, we can make a tree with values that correspond to ones in our constraint tree:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"result = C.substitute_values(c, optimal_variable_assignment)\n\nresult.fluxes.R_BIOMASS_Ecoli_core_w_GAM","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"result.fluxes.R_PFK","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Sometimes it is unnecessary to recover the values for all constraints, so we are better off selecting just the right subtree:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"C.substitute_values(c.fluxes, optimal_variable_assignment)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"C.substitute_values(c.objective, optimal_variable_assignment)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"We'll save the result for future use at the end of this example:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"result_single_organism = result","category":"page"},{"location":"1-metabolic-modeling/#Combining-and-extending-constraint-systems","page":"Example: Metabolic modeling","title":"Combining and extending constraint systems","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Constraint trees can be extended with new variables from another constraint trees using the + operator. Contrary to the * operator, adding the constraint trees does not share the variables between operands, and the resulting constraint tree will basically contain two disconnected trees that solve independently. The user is expected to create additional constraints to connect the independent parts.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Here, we demonstrate this by creating a community of two slightly different E. Coli species: First, we disable functionality of a different reaction in each of the models to create a diverse group of differently handicapped organisms:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c =\n :community^(\n :species1^(c * :handicap^C.Constraint(c.fluxes.R_PFK.value, 0)) +\n :species2^(c * :handicap^C.Constraint(c.fluxes.R_ACALD.value, 0))\n )","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"We can create additional variables that represent total community intake of oxygen, and total community production of biomass:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c += :exchanges^C.variables(keys = [:oxygen, :biomass], bounds = [(-10.0, 10.0), nothing])","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"These can be constrained so that the total influx (or outflux) of each of the registered metabolites is in fact equal to total consumption or production by each of the species:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c *=\n :exchange_constraints^C.ConstraintTree(\n :oxygen => C.Constraint(\n value = c.exchanges.oxygen.value - c.community.species1.fluxes.R_EX_o2_e.value -\n c.community.species2.fluxes.R_EX_o2_e.value,\n bound = 0.0,\n ),\n :biomass => C.Constraint(\n value = c.exchanges.biomass.value -\n c.community.species1.fluxes.R_BIOMASS_Ecoli_core_w_GAM.value -\n c.community.species2.fluxes.R_BIOMASS_Ecoli_core_w_GAM.value,\n bound = 0.0,\n ),\n )","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Let's see how much biomass are the two species capable of producing together:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"result =\n C.substitute_values(c, optimized_vars(c, c.exchanges.biomass.value, GLPK.Optimizer))\nresult.exchanges","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Finally, we can iterate over all species in the small community and see how much biomass was actually contributed by each:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Dict(k => v.fluxes.R_BIOMASS_Ecoli_core_w_GAM for (k, v) in result.community)","category":"page"},{"location":"1-metabolic-modeling/#Modifying-constraint-systems-in-place","page":"Example: Metabolic modeling","title":"Modifying constraint systems in-place","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Constraint trees can be modified in-place in a way that allows you to easily change small values in the trees without reconstructing them from the ground up.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Although in-place modification is extremely convenient and looks much easier than rebuilding the tree, it may be very detrimental to the robustness and efficiency of the programs, for several reasons:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"changing any data breaks assumptions on anything that was already derived from the data\nfor efficiency, the tree structures are not copied by default if there's no need to do it, and only shared by references; which means that a naive change at a single place of the tree may easily change values also in other parts of any trees, including completely different trees\nthe \"convenient way\" of making sure that the above problem never happens is to deep-copy the whole tree structure, which is typically quite detrimental to memory use and program efficiency","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"danger: Rules of thumb for safe use of in-place modification\nOnly use the in-place modifications if:there is code that explicitly makes sure there is no false sharing via references, e.g. using a deep copy\nthe in-place modifications are the last thing happening to the constraint tree before it is used by the solver\nthe in-place modification code is not a part of a re-usable library","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Now, if you are completely sure that ignoring the robustness guidelines will help your code, you can do the in-place tree modifications quite easily using both dot-access and array-index syntax.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"You can thus, e.g., set a single bound:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c.exchanges.oxygen.bound = C.Between(-20.0, 20.0)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"...or rebuild a whole constraint (using a tuple shortcut for Between):","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c.exchanges.biomass = C.Constraint(c.exchanges.biomass.value, (-20, 20))","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"...or even add new constraints, here using the index syntax for demonstration:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c[:exchanges][:production_is_zero] = C.Constraint(c.exchanges.biomass.value, 0)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"...or remove some constraints (this erases the constraint that was added just above):","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"delete!(c.exchanges, :production_is_zero)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"In the end, the flux optimization yields an expectably different result:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"result_with_more_oxygen =\n C.substitute_values(c, optimized_vars(c, c.exchanges.biomass.value, GLPK.Optimizer))\nresult.exchanges","category":"page"},{"location":"1-metabolic-modeling/#Seeing-the-differences-between-the-results","page":"Example: Metabolic modeling","title":"Seeing the differences between the results","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"ConstraintTrees.jl defines its own version of zip function that can apply a function to the contents of several trees, \"zipping\" them over the same keys in the structure. This is vaguely similar but otherwise not related to the zip from Julia base (similarly, ConstraintTrees.jl have their own specific map).","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"In practice, this allows you to create combined trees with various nice properties very quickly. For example, you can find how much the values have changed between our two communities:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"C.zip((x, y) -> y - x, result, result_with_more_oxygen, Float64)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"The result is again a Tree, with the contained type specified by the last argument (Float64 in this case). We can explore it right away as the other result trees. Also, it is possible to call this kind of function using the Julia do notation, making the syntax a bit neater:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"difference = C.zip(result, result_with_more_oxygen, Float64) do x, y\n y - x\nend","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Exploring the difference works as expected:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"difference.community.species1.fluxes","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"For convenience in special cases, zip is also overloaded for 3 arguments. We can, for a completely artificial example, check if the absolute flux change was bigger in the first or in the second organism in the community when compared to the original single-organism flux (which we luckily saved above):","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"changes = C.zip(\n result.community.species1,\n result.community.species2,\n result_single_organism,\n Bool,\n) do s1, s2, orig\n abs(s1 - orig) > abs(s2 - orig)\nend\n\nchanges.fluxes","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"More high-level functions like zip are described in an example on functional tree processing.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"This page was generated using Literate.jl.","category":"page"}] +[{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"EditURL = \"3-mixed-integer-optimization.jl\"","category":"page"},{"location":"3-mixed-integer-optimization/#Example:-Mixed-integer-optimization-(MILP)","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"","category":"section"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"This example demonstrates the extension of ConstraintTree bounds structures to accommodate new kinds of problems. In particular, we create a new kind of Bound that is restricting the value to be a full integer, and then solve a geometric problem with that.","category":"page"},{"location":"3-mixed-integer-optimization/#Creating-a-custom-bound","page":"Example: Mixed integer optimization (MILP)","title":"Creating a custom bound","text":"","category":"section"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"All bounds contained in constraints are subtypes of the abstract ConstraintTrees.Bound. These include ConstraintTrees.EqualTo and ConstraintTrees.Between, but the types can be extended as necessary, given the final rewriting of the constraint system to JuMP can handle the new bounds.","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"Let's make a small \"marker\" bound for something that needs to be integer-ish, between 2 integers:","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"import ConstraintTrees as C\n\nmutable struct IntegerFromTo <: C.Bound\n from::Int\n to::Int\nend","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"We can now write e.g. a bound on the number on a thrown six-sided die as follows:","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"IntegerFromTo(1, 6)","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"...and include this bound in constraints and variables:","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"dice_system = C.variables(keys = [:first_dice, :second_dice], bounds = IntegerFromTo(1, 6))","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"Now the main thing that is left is to be able to translate this bound to JuMP for solving. We can slightly generalize our constraint-translation system from the previous examples for this purpose, by separating out the functions that create the constraints:","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"import JuMP\n\nfunction jump_constraint(m, x, v::C.Value, b::C.EqualTo)\n JuMP.@constraint(m, C.substitute(v, x) == b.equal_to)\nend\n\nfunction jump_constraint(m, x, v::C.Value, b::C.Between)\n isinf(b.lower) || JuMP.@constraint(m, C.substitute(v, x) >= b.lower)\n isinf(b.upper) || JuMP.@constraint(m, C.substitute(v, x) <= b.upper)\nend","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"JuMP does not support direct integrality constraints, so we need to make a small digression with a slack variable:","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"function jump_constraint(m, x, v::C.Value, b::IntegerFromTo)\n var = JuMP.@variable(m, integer = true)\n JuMP.@constraint(m, var >= b.from)\n JuMP.@constraint(m, var <= b.to)\n JuMP.@constraint(m, C.substitute(v, x) == var)\nend\n\nfunction milp_optimized_vars(cs::C.ConstraintTree, objective::C.Value, optimizer)\n model = JuMP.Model(optimizer)\n JuMP.@variable(model, x[1:C.var_count(cs)])\n JuMP.@objective(model, JuMP.MAX_SENSE, C.substitute(objective, x))\n C.traverse(cs) do c\n isnothing(c.bound) || jump_constraint(model, x, c.value, c.bound)\n end\n JuMP.set_silent(model)\n JuMP.optimize!(model)\n JuMP.value.(model[:x])\nend","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"Let's try to solve a tiny system with the dice first. What's the best value we can throw if the dice are thrown at least 1.5 points apart?","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"dice_system *=\n :points_distance^C.Constraint(\n dice_system.first_dice.value - dice_system.second_dice.value,\n C.Between(1.5, Inf),\n )","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"For solving, we use GLPK (it has MILP capabilities).","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"import GLPK\ndices_thrown = C.substitute_values(\n dice_system,\n milp_optimized_vars(\n dice_system,\n dice_system.first_dice.value + dice_system.second_dice.value,\n GLPK.Optimizer,\n ),\n)","category":"page"},{"location":"3-mixed-integer-optimization/#A-more-realistic-example-with-geometry","page":"Example: Mixed integer optimization (MILP)","title":"A more realistic example with geometry","text":"","category":"section"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"Let's find the size of the smallest right-angled triangle with integer side sizes (aka a Pythagorean triple).","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"vars = C.variables(keys = [:a, :b, :c], bounds = IntegerFromTo(1, 100))","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"For simpliclty, we make a shortcut for \"values\" in all variables:","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"v = C.map(C.value, vars, C.Value)","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"With that shortcut, the constraint tree constructs quite easily:","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"triangle_system =\n :sides^vars *\n :circumference^C.Constraint(sum(values(v))) *\n :a_less_than_b^C.Constraint(v.b - v.a, (0, Inf)) *\n :b_less_than_c^C.Constraint(v.c - v.b, (0, Inf)) *\n :right_angled^C.Constraint(C.squared(v.a) + C.squared(v.b) - C.squared(v.c), 0.0)","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"We will need a solver that supports both quadratic and integer optimization:","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"import SCIP\ntriangle_sides =\n C.substitute_values(\n triangle_system,\n milp_optimized_vars(\n triangle_system,\n -triangle_system.circumference.value,\n SCIP.Optimizer,\n ),\n ).sides","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"","category":"page"},{"location":"3-mixed-integer-optimization/","page":"Example: Mixed integer optimization (MILP)","title":"Example: Mixed integer optimization (MILP)","text":"This page was generated using Literate.jl.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"EditURL = \"4-functional-tree-processing.jl\"","category":"page"},{"location":"4-functional-tree-processing/#Example:-Processing-the-trees-functionally","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"","category":"section"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"The main goal of ConstraintTrees.jl is to make the constraint-manipulating code orderly and elegant, and preferably short. To improve the manipulation of large constraint trees, the package also provides a small functional-programming-inspired framework that allows one to easily transform, summarize and combine all kinds of trees without writing repetitive code.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"You might already seen the zip function in the metabolic modeling example. There are more functions that behave like zip, so let's have a little summary here:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"map applies a function to all elements (including the nested ones) of a tree\nmapreduce transforms all elements of a tree using a given function (first parameter) and then combines the result using the second function (a binary operator); reduce is a shortcut where the map function is an identity\nzip combines elements common to both trees using a given zipping function\nmerge combines all elements in both trees (including the ones present in only one tree) using a given merging function\nvariables_for allocates a variable for each constraint in the tree and allows the user to specify bounds","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Additionally, all these have their \"indexed\" variant which allows you to know the path where the tree elements are being merged. The path is passed to the handling function as a tuple of symbols. The variants are prefixed with i:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"imap\nimapreduce (here the path refers to the common directory of the reduced elements) together with the shortcut ireduce\nizip\nimerge\nvariables_ifor","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"danger: Naming conflicts with Julia base\nNames of some of the higher-order function conflict with Julia base package and are not compatible. We recommend using them with named imports, such as by import ConstraintTrees as C and then C.zip and C.merge.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"For demonstration, let's make a very simple constrained system.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"import ConstraintTrees as C\n\nconstraints = :point^C.variables(keys = [:x, :y], bounds = C.Between(0, 1))","category":"page"},{"location":"4-functional-tree-processing/#Transforming-trees-with-map","page":"Example: Processing the trees functionally","title":"Transforming trees with map","text":"","category":"section"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Let's make a tree where the bounds are 2 times bigger and negated:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.map(constraints) do x\n C.Constraint(x.value, -2 * x.bound)\nend\n\nx.point.x","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"With imap, we can detect that we are working on a specific constraint and do something entirely different:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.imap(constraints) do path, x\n if path == (:point, :x)\n C.Constraint(x.value, 100 * x.bound)\n else\n x\n end\nend\n\n[x.point.x, x.point.y]","category":"page"},{"location":"4-functional-tree-processing/#Summarizing-the-trees-with-mapreduce-and-reduce","page":"Example: Processing the trees functionally","title":"Summarizing the trees with mapreduce and reduce","text":"","category":"section"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"How many constraints are there in the tree?","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.mapreduce(init = 0, _ -> 1, +, constraints)","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"What if we want to sum all constraints' values?","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.reduce(constraints, init = C.Constraint(zero(C.LinearValue))) do x, y\n C.Constraint(value = x.value + y.value)\nend","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"What if we want to reduce the point specially?","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.ireduce(constraints, init = C.Constraint(zero(C.LinearValue))) do path, x, y\n if path == (:point,)\n println(\"reducing in point/ subtree: $(x.value) + $(y.value)\")\n end\n C.Constraint(value = x.value + y.value)\nend;\nnothing #hide","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x","category":"page"},{"location":"4-functional-tree-processing/#Comparing-trees-with-zip","page":"Example: Processing the trees functionally","title":"Comparing trees with zip","text":"","category":"section"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Assume we have two solutions of the constraint system above, as follows:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"s1 = C.substitute_values(constraints, [0.9, 0.8])\ns2 = C.substitute_values(constraints, [0.99, 0.78])","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Let's compute the squared distance between individual items:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.zip(s1, s2, Float64) do x, y\n (x - y)^2\nend\n\nx.point","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"What if we want to put extra weight on distances between specific variables?","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.izip(s1, s2, Float64) do path, x, y\n if path == (:point, :x)\n 10\n else\n 1\n end * (x - y)^2\nend\n\nx.point","category":"page"},{"location":"4-functional-tree-processing/#Combining-trees-with-merge","page":"Example: Processing the trees functionally","title":"Combining trees with merge","text":"","category":"section"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Zipping trees together always produces a tree that only contains the intersection of keys from both original trees. That is not very useful if one wants to e.g. add new elements from extended trees. merge-style functions implement precisely that.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"The \"zipping\" function in merge takes 2 arguments; any of these may be missing in case one of the trees does not contain the elements. Also, a key may be omitted by returning missing from the function.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Let's make some very heterogeneous trees and try to combine them:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"t1 = :x^s1.point * :y^s2.point\nt2 = :x^s2.point * :z^s1.point\nt3 = :y^s2.point","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"As a nice combination function, we can try to compute an average on all positions from the first 2 trees:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"t = C.merge(t1, t2, Float64) do x, y\n ismissing(x) && return y\n ismissing(y) && return x\n (x + y) / 2\nend\n\nt.x\n\nt.y\n\nt.z","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Merge can also take 3 parameters (which is convenient in some situations). We may also want to omit certain output completely:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"tz = C.merge(t1, t2, t3, Float64) do x, y, z\n ismissing(z) && return missing\n ismissing(x) && return y\n ismissing(y) && return x\n (x + y) / 2\nend\n\ntz.y","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"We also have the indexed variants; for example this allows us to only merge the x elements in points:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"tx = C.imerge(t1, t2, Float64) do path, x, y\n last(path) == :x || return missing\n ismissing(x) && return y\n ismissing(y) && return x\n (x + y) / 2\nend\n\ntx.x","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"For completeness, we demonstrate a trick with easily coalescing the \"missing\" things to compute the means more easily:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"miss(_::Missing, _, def) = def;\nmiss(x, f, _) = f(x);\nfixmean(a) = miss(a, x -> (x, 1), (0, 0));\n\ntx = C.imerge(t1, t2, t3, Float64) do path, x, y, z\n last(path) == :x || return missing\n tmp = fixmean.([x, y, z])\n sum(first.(tmp)) / sum(last.(tmp))\nend\n\ntx.y","category":"page"},{"location":"4-functional-tree-processing/#Allocating-trees-of-variables-using-variables_for","page":"Example: Processing the trees functionally","title":"Allocating trees of variables using variables_for","text":"","category":"section"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"In many cases it is convenient to make a new model from the old by allocating new variables for whatever \"old\" tree out there. For example, one might wish to allocate a new variable for an approximate value (plus-minus-one) for each of the above tree's values. variables_for allocates one variable for each element of the given tree, and allows you to create bounds for the variables via the given function:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.variables_for(t) do a\n C.Between(a - 1, a + 1)\nend\n\nt.x.x\n\nx.x.x","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Note that the variables for the other subtrees are different now:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x.x.x.value\n\nx.y.x.value","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"As in all cases with indexes, you may match the tree path to do a special action. For example, to make sure that all y coordinates are exact in the new system:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"x = C.variables_ifor(t) do path, a\n if last(path) == :y\n C.EqualTo(a)\n else\n C.Between(a - 1, a + 1)\n end\nend\n\nx.x\n\nC.bound.(values(x.x))","category":"page"},{"location":"4-functional-tree-processing/#Looping-through-the-trees-with-traverse","page":"Example: Processing the trees functionally","title":"Looping through the trees with traverse","text":"","category":"section"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"Since we are writing our code in an imperative language, it is often quite beneficial to run a function over the trees just for the side effect.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"For this purpose, traverse and itraverse work precisely like map and imap, except no tree is returned and the only \"output\" of the functions are their side effect.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"For example, you can write a less-functional counting of number of constraints in the tree as follows:","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"constraint_count = 0\nC.traverse(x) do _\n global constraint_count += 1\nend\nconstraint_count","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"The indexed variant of traverse works as expected; it may be beneficial e.g. for printing the contents of the constraint trees in a \"flat\" form, or potentially working with other path-respecting data structures.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"C.itraverse(x) do ix, c\n path = join(String.(ix), '/')\n println(\"$path = $c\")\nend;\nnothing #hide","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"To prevent uncertainty, both functions always traverse the keys in sorted order.","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"","category":"page"},{"location":"4-functional-tree-processing/","page":"Example: Processing the trees functionally","title":"Example: Processing the trees functionally","text":"This page was generated using Literate.jl.","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"EditURL = \"5-jump-integration.jl\"","category":"page"},{"location":"5-jump-integration/#Better-integration-with-JuMP","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"","category":"section"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"The examples in this documentation generally used the simple and straightforward method of converting the trees and values to JuMP system, which depends on algebraic operators working transparently with JuMP values within function substitute.","category":"page"},{"location":"5-jump-integration/#Substitution-folding-problem","page":"Better integration with JuMP","title":"Substitution folding problem","text":"","category":"section"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"Despite the simplicity, this approach is sometimes sub-optimal, especially in cases when the result of the substitution is recalculated with added values. For example, in the naive case, JuMP is forced to successively build representations for all intermediate expressions with incomplete variables, until all variables are in place. In turn, this may very easily reach a quadratic computational complexity.","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"More generally, any representation of substitution result that \"does not reduce() easily\" will suffer from this problem. A different (often specialized) approach is thus needed.","category":"page"},{"location":"5-jump-integration/#Solution:-Prevent-successive-folding","page":"Better integration with JuMP","title":"Solution: Prevent successive folding","text":"","category":"section"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"For such cases, it is recommended to replace the substitute calls with a custom function that can interpret the required Values itself, and converts them without the overhead of creating temporary values.","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"import ConstraintTrees as C","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"First, let's create a lot of variables, and a constraint that will usually trigger this problem (and a JuMP warning) if used with normal substitute:","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"x = :vars^C.variables(keys = Symbol.(\"x$i\" for i = 1:1000), bounds = C.Between(0, 10))\nx *= :sum^C.Constraint(sum(C.value.(values(x.vars))))","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"Now, imagine the expressions are represented e.g. by sparse vectors of fixed size (as common in linear-algebraic systems). We can produce the vectors efficiently as follows:","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"import SparseArrays: sparsevec\nv = x.sum.value\n\nvalue_in_a_vector = sparsevec(v.idxs, v.weights, 1000)","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"This usually requires only a single memory allocation, and runs in time linear with the number of variables in the value. As an obvious downside, you need to implement this functionality for all kinds of Values you encounter.","category":"page"},{"location":"5-jump-integration/#Solution-for-JuMP","page":"Better integration with JuMP","title":"Solution for JuMP","text":"","category":"section"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"LinearValues can be translated to JuMP's AffExprs:","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"using JuMP, GLPK\n\nfunction substitute_jump(val::C.LinearValue, vars)\n e = AffExpr() # unfortunately @expression(model, 0) is not type stable and gives an Int\n for (i, w) in zip(val.idxs, val.weights)\n if i == 0\n add_to_expression!(e, w)\n else\n add_to_expression!(e, w, vars[i])\n end\n end\n e\nend\n\nmodel = Model(GLPK.Optimizer)\n@variable(model, V[1:1000])\njump_value = substitute_jump(x.sum.value, V)","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"This function can be re-used in functions like optimized_vars as shown in other examples in the documentation.","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"For QuadraticValues, the same approach extends only with a minor modification:","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"function substitute_jump(val::C.QuadraticValue, vars)\n e = QuadExpr() # unfortunately @expression(model, 0) is not type stable and gives an Int\n for ((i, j), w) in zip(val.idxs, val.weights)\n if i == 0 && j == 0\n add_to_expression!(e, w)\n elseif i == 0 # the symmetric case is prohibited\n add_to_expression!(e, w, vars[j])\n else\n add_to_expression!(e, w, vars[i], vars[j])\n end\n end\n e\nend\n\nqvalue = 123 + (x.vars.x1.value + x.vars.x2.value) * (x.vars.x3.value - 321)\njump_qvalue = substitute_jump(qvalue, V)","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"","category":"page"},{"location":"5-jump-integration/","page":"Better integration with JuMP","title":"Better integration with JuMP","text":"This page was generated using Literate.jl.","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"EditURL = \"0-quickstart.jl\"","category":"page"},{"location":"0-quickstart/#Quick-start","page":"Quick start","title":"Quick start","text":"","category":"section"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"The primary purpose of ConstraintTrees.jl is to make the representation of constraint systems neat, and thus make their manipulation easy and high-level. In short, the package abstracts the users from keeping track of variable and constraint indexes in matrix form, and gives a nice data structure that describes the system, while keeping all variable allocation and constraint organization completely implicit.","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Here we demonstrate the absolutely basic concepts on the \"field allocation\" problem.","category":"page"},{"location":"0-quickstart/#The-problem:-Field-area-allocation","page":"Quick start","title":"The problem: Field area allocation","text":"","category":"section"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Suppose we have 100 square kilometers of field, 500 kilos of fertilizer and 300 kilos of insecticide. We also have a practically infinite supply of wheat and barley seeds. If we decide to sow barley, we can make 550🪙 per square kilometer of harvest; if we decide to go with wheat instead, we can make 350🪙. Unfortunately each square kilometer of wheat requires 6 kilos of fertilizer, and 1 kilo of insecticide, whereas each square kilometer of barley requires 2 kilos of fertilizer and 4 kilos of insecticide, because insects love barley.","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"How much of our fields should we allocate to wheat and barley to maximize our profit?","category":"page"},{"location":"0-quickstart/#Field-area-allocation-with-ConstraintTrees.jl","page":"Quick start","title":"Field area allocation with ConstraintTrees.jl","text":"","category":"section"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Let's import the package and start constructing the problem:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"import ConstraintTrees as C","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Let's name our system s. We first need a few variables:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s = C.variables(keys = [:wheat, :barley])","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"With ConstraintTrees.jl, we can (and want to!) label everything very nicely – the constraint trees are essentially directory structures, so one can prefix everything with symbols to put it into nice directories, e.g. as such:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":":area^s","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"To be absolutely realistic, we also want to make sure that all areas are non-negative. To demonstrate how to do that nicely from the start, we rather re-do the constraints with an appropriate interval bound:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s = :area^C.variables(keys = [:wheat, :barley], bounds = C.Between(0, Inf))","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Constraint trees can be browsed using dot notation, or just like dictionaries:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s.area","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s[:area].barley","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"(For convenience in some cases, string indexes are also supported:)","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s[\"area\"][\"barley\"]","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Now let's start rewriting the problem into the constraint-tree-ish description. First, we only have 100 square kilometers of area:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"total_area = s.area.wheat.value + s.area.barley.value\n\ntotal_area_constraint = C.Constraint(total_area, (0, 100))","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"We can add any kind of constraint into the existing constraint trees by \"merging\" multiple trees with operator *:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s *= :total_area^total_area_constraint","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Now let's add constraints for resources. We can create whole ConstraintTree structures like dictionaries in place, as follows:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s *=\n :resources^C.ConstraintTree(\n :fertilizer =>\n C.Constraint(s.area.wheat.value * 6 + s.area.barley.value * 2, (0, 500)),\n :insecticide =>\n C.Constraint(s.area.wheat.value * 1 + s.area.barley.value * 4, (0, 300)),\n )","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"We can also represent the expected profit as a constraint (although we do not need to actually put a constraint bound there):","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s *= :profit^C.Constraint(s.area.wheat.value * 350 + s.area.barley.value * 550)","category":"page"},{"location":"0-quickstart/#Solving-the-system-with-JuMP","page":"Quick start","title":"Solving the system with JuMP","text":"","category":"section"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"We can now take the structure of the constraint tree, translate it to any suitable linear optimizer interface, and have it solved. For popular reasons we choose JuMP with GLPK – the code is left uncommented here as-is; see the other examples for a slightly more detailed explanation:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"import JuMP\nfunction optimized_vars(cs::C.ConstraintTree, objective::C.LinearValue, optimizer)\n model = JuMP.Model(optimizer)\n JuMP.@variable(model, x[1:C.var_count(cs)])\n JuMP.@objective(model, JuMP.MAX_SENSE, C.substitute(objective, x))\n C.traverse(cs) do c\n b = c.bound\n if b isa C.EqualTo\n JuMP.@constraint(model, C.substitute(c.value, x) == b.equal_to)\n elseif b isa C.Between\n val = C.substitute(c.value, x)\n isinf(b.lower) || JuMP.@constraint(model, val >= b.lower)\n isinf(b.upper) || JuMP.@constraint(model, val <= b.upper)\n end\n end\n JuMP.optimize!(model)\n JuMP.value.(model[:x])\nend\n\nimport GLPK\noptimal_variable_assignment = optimized_vars(s, s.profit.value, GLPK.Optimizer)","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"This gives us the optimized variable values! If we cared to remember what they stand for, we might already know how much barley to sow. On the other hand, the main point of ConstraintTree.jl is that one should not be forced to remember things like variable ordering and indexes, or be forced to manually calculate how much money we actually make or how much fertilizer is going to be left – instead, we can simply feed the variable values back to the constraint tree, and get a really good overview of all values in our constrained system:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_s = C.substitute_values(s, optimal_variable_assignment)","category":"page"},{"location":"0-quickstart/#Browsing-the-result","page":"Quick start","title":"Browsing the result","text":"","category":"section"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_s is now like the original constraint tree, just the contents are \"plain old values\" instead of the constraints as above. Thus we can easily see our profit:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_s.profit","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"The occupied area for each crop:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_s.area","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"The consumed resources:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_s.resources","category":"page"},{"location":"0-quickstart/#Increasing-the-complexity","page":"Quick start","title":"Increasing the complexity","text":"","category":"section"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"A crucial property of constraint trees is that the users do not need to care about what kind of value they are manipulating – no matter if something is a variable or a derived value, the code that works with it is the same. For example, we can use the actual prices for our resources (30🪙 and 110🪙 for a kilo of fertilizer and insecticide, respectively) to make a corrected profit:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"s *=\n :actual_profit^C.Constraint(\n s.profit.value - 30 * s.resources.fertilizer.value -\n 110 * s.resources.insecticide.value,\n )","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Is the result going to change if we optimize for the corrected profit?","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"realistically_optimal_s =\n C.substitute_values(s, optimized_vars(s, s.actual_profit.value, GLPK.Optimizer))","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"realistically_optimal_s.area","category":"page"},{"location":"0-quickstart/#Combining-constraint-systems:-Let's-have-a-factory!","page":"Quick start","title":"Combining constraint systems: Let's have a factory!","text":"","category":"section"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"The second crucial property of constraint trees is the ability to easily combine different constraint systems into one. Let's pretend we also somehow obtained a food factory that produces malty sweet bread and wheaty weizen-style beer, with various extra consumptions of water and heat for each of the products. For simplicity, let's just create the corresponding constraint system (f as a factory) here:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"f = :products^C.variables(keys = [:bread, :weizen], bounds = C.Between(0, Inf))\nf *= :profit^C.Constraint(25 * f.products.weizen.value + 35 * f.products.bread.value)","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"We can make the constraint systems more complex by adding additional variables. To make sure the variables do not \"conflict\", one must use the + operator. While constraint systems combined with * always share variables, constraint systems combined with + are independent.","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"f += :materials^C.variables(keys = [:wheat, :barley], bounds = C.Between(0, Inf))","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"How much resources are consumed by each product, with a limit on each:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"f *=\n :resources^C.ConstraintTree(\n :heat => C.Constraint(\n 5 * f.products.bread.value + 3 * f.products.weizen.value,\n (0, 1000),\n ),\n :water => C.Constraint(\n 2 * f.products.bread.value + 10 * f.products.weizen.value,\n (0, 3000),\n ),\n )","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"How much raw materials are required for each product:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"f *=\n :material_allocation^C.ConstraintTree(\n :wheat => C.Constraint(\n 8 * f.products.bread.value + 2 * f.products.weizen.value -\n f.materials.wheat.value,\n 0,\n ),\n :barley => C.Constraint(\n 0.5 * f.products.bread.value + 10 * f.products.weizen.value -\n f.materials.barley.value,\n 0,\n ),\n )","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Having the two systems at hand, we can connect the factory \"system\" f to the field \"system\" s, making a compound system c as such:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"c = :factory^f + :fields^s","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"warning: Operators for combining constraint trees\nAlways remember to use + instead of * when combining independent constraint systems. If we use *, the variables in both systems will become implicitly shared, which is rarely what one wants in the first place. Use * only if adding additional constraints to an existing system. As a rule of thumb, one can remember the boolean interpretation of * as \"and\" and of + as \"or\".On a side note, the operator ^ was chosen mainly to match the algebraic view of the tree combination, and nicely fit into Julia's operator priority structure.","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"To actually connect the systems (which now exist as completely independent parts of s), let's add a transport – the barley and wheat produced on the fields is going to be the only barley and wheat consumed by the factory, thus their production and consumption must sum to net zero:","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"c *= :transport^C.zip(c.fields.area, c.factory.materials) do area, material\n C.Constraint(area.value - material.value, 0)\nend","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"info: High-level constraint tree manipulation\nThere is also a dedicated example with many more useful functions like zip above.","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"Finally, let's see how much money can we make from having the factory supported by our fields in total!","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_c =\n C.substitute_values(c, optimized_vars(c, c.factory.profit.value, GLPK.Optimizer))","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"How much field area did we allocate?","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_c.fields.area","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"How much of each of the products does the factory make in the end?","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_c.factory.products","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"How much extra resources is consumed by the factory?","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_c.factory.resources","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"And what is the factory profit in the end?","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"optimal_c.factory.profit","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"","category":"page"},{"location":"0-quickstart/","page":"Quick start","title":"Quick start","text":"This page was generated using Literate.jl.","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"EditURL = \"2-quadratic-optimization.jl\"","category":"page"},{"location":"2-quadratic-optimization/#Example:-Quadratic-optimization","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"","category":"section"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"In this example we demonstrate the use of quadratic constraints and values. We assume that the reader is already familiar with the construction of ConstraintTrees; if not, it is advisable to read the previous part of the documentation first.","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"In short, quadratic values and constraints are expressed similarly as other contents of the constraint trees using type QuadraticValue, which is basically an affine-quadratic alike of the affine-linear LinearValue.","category":"page"},{"location":"2-quadratic-optimization/#Working-with-quadratic-values-and-constraints","page":"Example: Quadratic optimization","title":"Working with quadratic values and constraints","text":"","category":"section"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"Algebraically, you can construct QuadraticValues simply by multiplying the linear LinearValues:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"import ConstraintTrees as C\n\nsystem = C.variables(keys = [:x, :y, :z])\nqv = system.x.value * (system.y.value + 2 * system.z.value)","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"As with LinearValues, the QuadraticValues can be easily combined, giving a nice way to specify e.g. weighted sums of squared errors with respect to various directions. We can thus represent common formulas for error values:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"error_val =\n C.squared(system.x.value + system.y.value - 1) +\n C.squared(system.y.value + 5 * system.z.value - 3)","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"This allows us to naturally express quadratic constraint (e.g., that an error must not be too big); and directly observe the error values in the system.","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"system = :vars^system * :error^C.Constraint(error_val, C.Between(0, 100))","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"(For simplicity, you can also use the Constraint constructor to make quadratic constraints out of QuadraticValues – it will overload properly.)","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"Let's pretend someone has solved the system, and see how much \"error\" the solution has:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"solution = [1.0, 2.0, -1.0]\nst = C.substitute_values(system, solution)\nst.error","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"...not bad for a first guess.","category":"page"},{"location":"2-quadratic-optimization/#Building-quadratic-systems","page":"Example: Quadratic optimization","title":"Building quadratic systems","text":"","category":"section"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"Let's create a small quadratic system that finds the closest distance between an ellipse and a line and let some of the conic solvers available in JuMP solve it. First, let's make a representation of a point in 2D:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"point = C.variables(keys = [:x, :y])","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"We can create a small system that constraints the point to stay within a simple elliptical area centered around (0.0, 10.0):","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"ellipse_system = C.ConstraintTree(\n :point => point,\n :in_area => C.Constraint(\n C.squared(point.x.value) / 4 + C.squared(10.0 - point.y.value),\n C.Between(-Inf, 1.0),\n ),\n)","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"We now create another small system that constraints another point to stay on a line that crosses (0, 0) and (2, 1). We could do this using a dot-product representation of line, but that would lead to issues later (mainly, the solver that we are planning to use only supports positive definite quadratic forms as constraints). Instead, let's use a single-variable-parametrized line equation.","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"line_param = C.variable().value\nline_system =\n :point^C.ConstraintTree(\n :x => C.Constraint(0 + 2 * line_param),\n :y => C.Constraint(0 + 1 * line_param),\n )","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"Finally, let's connect the systems using + operator and add the objective that would minimize the distance of the points:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"s = :ellipse^ellipse_system + :line^line_system\n\ns *=\n :objective^C.Constraint(\n C.squared(s.ellipse.point.x.value - s.line.point.x.value) +\n C.squared(s.ellipse.point.y.value - s.line.point.y.value),\n )","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"(Note that if we used * to connect the systems, the variables from the definition of point would not be duplicated, and various non-interesting logic errors would follow.)","category":"page"},{"location":"2-quadratic-optimization/#Solving-quadratic-systems-with-JuMP","page":"Example: Quadratic optimization","title":"Solving quadratic systems with JuMP","text":"","category":"section"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"To solve the above system, we need a matching solver that can work with quadratic constraints. Also, we need to slightly generalize the function that translates the constraints into JuMP Models to support the quadratic constraints.","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"import JuMP\nfunction quad_optimized_vars(cs::C.ConstraintTree, objective::C.Value, optimizer)\n model = JuMP.Model(optimizer)\n JuMP.@variable(model, x[1:C.var_count(cs)])\n JuMP.@objective(model, JuMP.MAX_SENSE, C.substitute(objective, x))\n C.traverse(cs) do c\n b = c.bound\n if b isa C.EqualTo\n JuMP.@constraint(model, C.substitute(c.value, x) == b.equal_to)\n elseif b isa C.Between\n val = C.substitute(c.value, x)\n isinf(b.lower) || JuMP.@constraint(model, val >= b.lower)\n isinf(b.upper) || JuMP.@constraint(model, val <= b.upper)\n end\n end\n JuMP.set_silent(model)\n JuMP.optimize!(model)\n JuMP.value.(model[:x])\nend","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"We can now load a suitable optimizer and solve the system by maximizing the negative squared error:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"import Clarabel\nst = C.substitute_values(s, quad_optimized_vars(s, -s.objective.value, Clarabel.Optimizer))","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"If the optimization worked well, we can nicely get out the position of the closest point to the line that is in the elliptical area:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"(st.ellipse.point.x, st.ellipse.point.y)","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"...as well as the position on the line that is closest to the ellipse:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"st.line.point","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"...and, with a little bit of extra math, the minimized distance:","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"sqrt(st.objective)","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"","category":"page"},{"location":"2-quadratic-optimization/","page":"Example: Quadratic optimization","title":"Example: Quadratic optimization","text":"This page was generated using Literate.jl.","category":"page"},{"location":"reference/#Reference","page":"Reference","title":"Reference","text":"","category":"section"},{"location":"reference/#Values","page":"Reference","title":"Values","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [ConstraintTrees]\nPages = [\"src/value.jl\"]","category":"page"},{"location":"reference/#ConstraintTrees.Value","page":"Reference","title":"ConstraintTrees.Value","text":"abstract type Value\n\nAbstract type of all values usable in constraints, including LinearValue and QuadraticValue.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.preduce-Tuple{Any, Any}","page":"Reference","title":"ConstraintTrees.preduce","text":"preduce(op, xs; init, stack_type) -> Any\n\n\nAn alternative of Base.reduce which does a \"pairwise\" reduction in the shape of a binary merge tree, like in mergesort. In general this is a little more complex, but if the reduced value \"grows\" with more elements added (such as when adding a lot of LinearValues together), this is able to prevent a complexity explosion by postponing \"large\" reducing operations as much as possible.\n\nIn the specific case with adding lots of LinearValues and QuadraticValues together, this effectively squashes the reduction complexity from something around O(n^2) to O(n) (with a little larger constant factor.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.substitute_values","page":"Reference","title":"ConstraintTrees.substitute_values","text":"substitute_values(\n x::ConstraintTrees.Value,\n y::AbstractVector\n) -> Any\nsubstitute_values(\n x::ConstraintTrees.Value,\n y::AbstractVector,\n _\n) -> Any\n\n\nSubstutite a value into a Value-typed x. This is a convenience overload for the purpose of having substitute_values to run on both Constraints and Values.\n\n\n\n\n\n","category":"function"},{"location":"reference/#ConstraintTrees.sum-Tuple{Any}","page":"Reference","title":"ConstraintTrees.sum","text":"sum(xs; init) -> Any\n\n\nAlias for preduce that uses + as the operation.\n\nNot as versatile as the sum from Base, but much faster for growing values like LinearValues and QuadraticValues.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.value-Tuple{T} where T<:Union{ConstraintTrees.Value, Real}","page":"Reference","title":"ConstraintTrees.value","text":"value(\n x::Union{ConstraintTrees.Value, Real}\n) -> Union{ConstraintTrees.Value, Real}\n\n\nReturns any Real- or Value-typed x. This is a convenience overload; typically one enjoys this more when extracting values from Constraints.\n\n\n\n\n\n","category":"method"},{"location":"reference/#Linear-and-affine-values","page":"Reference","title":"Linear and affine values","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [ConstraintTrees]\nPages = [\"src/linear_value.jl\"]","category":"page"},{"location":"reference/#ConstraintTrees.LinearValue","page":"Reference","title":"ConstraintTrees.LinearValue","text":"struct LinearValue <: ConstraintTrees.Value\n\nA representation of a \"value\" in a linear constrained optimization problem. The value is an affine linear combination of several variables.\n\nLinearValues can be combined additively and multiplied by real-number constants.\n\nMultiplying two LinearValues yields a quadratic form (in a QuadraticValue).\n\nFields\n\nidxs::Vector{Int64}: Indexes of the variables used by the value. The indexes must always be sorted in strictly increasing order. The affine element has index 0.\n\nweights::Vector{Float64}: Coefficients of the variables selected by idxs.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.LinearValue-Tuple{Real}","page":"Reference","title":"ConstraintTrees.LinearValue","text":"LinearValue(x::Real) -> ConstraintTrees.LinearValue\n\n\nConstruct a constant LinearValue with a single affine element.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.LinearValue-Tuple{SparseArrays.SparseVector{Float64}}","page":"Reference","title":"ConstraintTrees.LinearValue","text":"LinearValue(\n x::SparseArrays.SparseVector{Float64}\n) -> ConstraintTrees.LinearValue\n\n\nShortcut for making a LinearValue out of a linear combination defined by the SparseVector.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.add_sparse_linear_combination-Union{Tuple{T}, Tuple{Vector{Int64}, Vector{T}, Vector{Int64}, Vector{T}}} where T","page":"Reference","title":"ConstraintTrees.add_sparse_linear_combination","text":"add_sparse_linear_combination(\n a_idxs::Vector{Int64},\n a_weights::Array{T, 1},\n b_idxs::Vector{Int64},\n b_weights::Array{T, 1}\n) -> Tuple{Vector{Int64}, Vector}\n\n\nHelper function for implementing LinearValue-like objects. Given \"sparse\" representations of linear combinations, it computes a \"merged\" linear combination of 2 values added together.\n\nZeroes are not filtered out.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.substitute-Tuple{ConstraintTrees.LinearValue, Any}","page":"Reference","title":"ConstraintTrees.substitute","text":"substitute(x::ConstraintTrees.LinearValue, y) -> Any\n\n\nSubstitute anything vector-like as variable values into a LinearValue and return the result.\n\n\n\n\n\n","category":"method"},{"location":"reference/#Quadratic-values","page":"Reference","title":"Quadratic values","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [ConstraintTrees]\nPages = [\"src/quadratic_value.jl\"]","category":"page"},{"location":"reference/#ConstraintTrees.QuadraticValue","page":"Reference","title":"ConstraintTrees.QuadraticValue","text":"struct QuadraticValue <: ConstraintTrees.Value\n\nA representation of a quadratic form in the constrained optimization problem. The QuadraticValue is an affine quadratic combination (i.e., a polynomial of maximum degree 2) over the variables.\n\nQuadraticValues can be combined additively and multiplied by real-number constants. The cleanest way to construct a QuadraticValue is to multiply two LinearValues.\n\nFields\n\nidxs::Vector{Tuple{Int64, Int64}}: Indexes of variable pairs used by the value. The indexes must always be sorted in strictly co-lexicographically increasing order, and the second index must always be greater than or equal to the first one. (Speaking in matrix terms, the indexing follows the indexes in an upper triangular matrix by columns.)\nAs an outcome, the second index of the last index pair can be used as the upper bound of all variable indexes.\nAs with LinearValue, index 0 represents the affine element.\n\nweights::Vector{Float64}: Coefficient of the variable pairs selected by idxs.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.QuadraticValue-Tuple{ConstraintTrees.LinearValue}","page":"Reference","title":"ConstraintTrees.QuadraticValue","text":"QuadraticValue(\n x::ConstraintTrees.LinearValue\n) -> ConstraintTrees.QuadraticValue\n\n\nConstruct a QuadraticValue that is equivalent to a given LinearValue.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.QuadraticValue-Tuple{Real}","page":"Reference","title":"ConstraintTrees.QuadraticValue","text":"QuadraticValue(x::Real) -> ConstraintTrees.QuadraticValue\n\n\nConstruct a constant QuadraticValue with a single affine element.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.QuadraticValue-Tuple{SparseArrays.SparseMatrixCSC{Float64}}","page":"Reference","title":"ConstraintTrees.QuadraticValue","text":"QuadraticValue(\n x::SparseArrays.SparseMatrixCSC{Float64}\n) -> ConstraintTrees.QuadraticValue\n\n\nShortcut for making a QuadraticValue out of a square sparse matrix. The matrix is force-symmetrized by calculating x' + x.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.add_sparse_quadratic_combination-Union{Tuple{T}, Tuple{Vector{Tuple{Int64, Int64}}, Vector{T}, Vector{Tuple{Int64, Int64}}, Vector{T}}} where T","page":"Reference","title":"ConstraintTrees.add_sparse_quadratic_combination","text":"add_sparse_quadratic_combination(\n a_idxs::Vector{Tuple{Int64, Int64}},\n a_weights::Array{T, 1},\n b_idxs::Vector{Tuple{Int64, Int64}},\n b_weights::Array{T, 1}\n) -> Tuple{Vector{Tuple{Int64, Int64}}, Vector}\n\n\nHelper function for implementing QuadraticValue-like objects. Given 2 sparse representations of quadratic combinations, it computes a \"merged\" one with the values of both added together.\n\nZeroes are not filtered out.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.colex_le-Tuple{Any, Any}","page":"Reference","title":"ConstraintTrees.colex_le","text":"colex_le(, ) -> Any\n\n\nInternal helper for co-lex ordering of indexes.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.multiply_sparse_linear_combination-Union{Tuple{T}, Tuple{Vector{Int64}, Vector{T}, Vector{Int64}, Vector{T}}} where T","page":"Reference","title":"ConstraintTrees.multiply_sparse_linear_combination","text":"multiply_sparse_linear_combination(\n a_idxs::Vector{Int64},\n a_weights::Array{T, 1},\n b_idxs::Vector{Int64},\n b_weights::Array{T, 1}\n) -> Tuple{Vector{Tuple{Int64, Int64}}, Vector}\n\n\nHelper function for multiplying two LinearValue-like objects to make a QuadraticValue-like object. This computes and merges the product.\n\nZeroes are not filtered out.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.squared-Tuple{ConstraintTrees.LinearValue}","page":"Reference","title":"ConstraintTrees.squared","text":"squared(\n a::ConstraintTrees.LinearValue\n) -> ConstraintTrees.QuadraticValue\n\n\nBroadcastable shortcut for multiplying a LinearValue with itself. Produces a QuadraticValue.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.substitute-Tuple{ConstraintTrees.QuadraticValue, Any}","page":"Reference","title":"ConstraintTrees.substitute","text":"substitute(x::ConstraintTrees.QuadraticValue, y) -> Any\n\n\nSubstitute anything vector-like as variable values into the QuadraticValue and return the result.\n\n\n\n\n\n","category":"method"},{"location":"reference/#Constraints","page":"Reference","title":"Constraints","text":"","category":"section"},{"location":"reference/#Bounds","page":"Reference","title":"Bounds","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [ConstraintTrees]\nPages = [\"src/bound.jl\"]","category":"page"},{"location":"reference/#ConstraintTrees.MaybeBound","page":"Reference","title":"ConstraintTrees.MaybeBound","text":"Shortcut for all possible Bounds including the \"empty\" bound that does not constraint anything (represented by nothing).\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.Between","page":"Reference","title":"ConstraintTrees.Between","text":"mutable struct Between <: ConstraintTrees.Bound\n\nRepresentation of an \"interval\" bound; consisting of lower and upper bound value.\n\nFields\n\nlower::Float64: Lower bound\nupper::Float64: Upper bound\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.Bound","page":"Reference","title":"ConstraintTrees.Bound","text":"abstract type Bound\n\nAbstract type of all bounds usable in constraints, including Between and EqualTo.\n\nTo make broadcasting work, length(::Bound) = 1 has been extended. This allows functions like variables to broadcast a single supplied bound across all constraints.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.EqualTo","page":"Reference","title":"ConstraintTrees.EqualTo","text":"mutable struct EqualTo <: ConstraintTrees.Bound\n\nRepresentation of an \"equality\" bound; contains the single \"equal to this\" value.\n\nFields\n\nequal_to::Float64: Equality bound value\n\n\n\n\n\n","category":"type"},{"location":"reference/#Constrained-values","page":"Reference","title":"Constrained values","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [ConstraintTrees]\nPages = [\"src/constraint.jl\"]","category":"page"},{"location":"reference/#ConstraintTrees.Constraint","page":"Reference","title":"ConstraintTrees.Constraint","text":"mutable struct Constraint\n\nA representation of a single constraint that may limit the given value by a specific Bound.\n\nConstraints without a bound (nothing in the bound field) are possible; these have no impact on the optimization problem but the associated value becomes easily accessible for inspection and building other constraints.\n\nFields\n\nvalue::ConstraintTrees.Value: A value (typically a LinearValue or a QuadraticValue) that describes what the constraint constraints.\nbound::Union{Nothing, ConstraintTrees.Bound}: A bound that the value must satisfy. Should be a subtype of MaybeBound: Either nothing if there's no bound, or e.g. EqualTo, Between or similar structs.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.bound-Tuple{ConstraintTrees.Constraint}","page":"Reference","title":"ConstraintTrees.bound","text":"bound(\n x::ConstraintTrees.Constraint\n) -> Union{Nothing, ConstraintTrees.Bound}\n\n\nSimple accessor for getting out the bound from the constraint that can be used for broadcasting (as opposed to the dot-field access).\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.substitute-Tuple{ConstraintTrees.Constraint, Any}","page":"Reference","title":"ConstraintTrees.substitute","text":"substitute(\n x::ConstraintTrees.Constraint,\n y\n) -> ConstraintTrees.Constraint\n\n\nSubstitute anything vector-like as variables into the constraint's value, producing a constraint with the new value.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.substitute_values-2","page":"Reference","title":"ConstraintTrees.substitute_values","text":"substitute_values(\n x::ConstraintTrees.Constraint,\n y::AbstractVector\n) -> Any\nsubstitute_values(\n x::ConstraintTrees.Constraint,\n y::AbstractVector,\n _\n) -> Any\n\n\nOverload of substitute_values for a single constraint.\n\n\n\n\n\n","category":"function"},{"location":"reference/#ConstraintTrees.value-Tuple{ConstraintTrees.Constraint}","page":"Reference","title":"ConstraintTrees.value","text":"value(\n x::ConstraintTrees.Constraint\n) -> ConstraintTrees.Value\n\n\nSimple accessor for getting out the value from the constraint that can be used for broadcasting (as opposed to the dot-field access).\n\n\n\n\n\n","category":"method"},{"location":"reference/#Labeled-trees","page":"Reference","title":"Labeled trees","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [ConstraintTrees]\nPages = [\"src/tree.jl\"]","category":"page"},{"location":"reference/#ConstraintTrees.OptionalTree","page":"Reference","title":"ConstraintTrees.OptionalTree","text":"Helper type for implementation of merge-related functions.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.Tree","page":"Reference","title":"ConstraintTrees.Tree","text":"struct Tree{X}\n\nA base \"labeled tree\" structure. Supports many interesting operations such as merging.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.elems-Tuple{ConstraintTrees.Tree}","page":"Reference","title":"ConstraintTrees.elems","text":"elems(\n x::ConstraintTrees.Tree\n) -> DataStructures.SortedDict{Symbol, Union{ConstraintTrees.Tree{X}, X}} where X\n\n\nGet the elements dictionary out of the Tree. This is useful for getting an iterable container for working with many items at once.\n\nAlso, because of the overload of getproperty for Tree, this serves as a simpler way to get the elements without an explicit use of getfield.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.imap-Union{Tuple{T}, Tuple{Any, Any}, Tuple{Any, Any, Type{T}}} where T","page":"Reference","title":"ConstraintTrees.imap","text":"imap(f, x) -> Any\nimap(f, x, ::Type{T}) -> Any\n\n\nLike map, but keeping the \"index\" path and giving it to the function as the first parameter. The \"path\" in the tree is reported as a tuple of symbols.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.imapreduce-Tuple{Any, Any, Any}","page":"Reference","title":"ConstraintTrees.imapreduce","text":"imapreduce(f, op, x; init) -> Any\n\n\nLike mapreduce but reporting the \"tree directory path\" where the reduced elements occur, like with imap. (Single elements from different directory paths are not reduced together.)\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.imerge-Union{Tuple{T}, Tuple{Any, Any, Any}, Tuple{Any, Any, Any, Type{T}}} where T","page":"Reference","title":"ConstraintTrees.imerge","text":"imerge(f, x, y) -> Any\nimerge(f, x, y, ::Type{T}) -> Any\n\n\nIndex-reporting variant of merge (see imap for reference).\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.ireduce-Tuple{Any, Any}","page":"Reference","title":"ConstraintTrees.ireduce","text":"ireduce(op, x; init) -> Any\n\n\nIndexed version of reduce (internally uses imapreduce).\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.itraverse-Tuple{Any, Any}","page":"Reference","title":"ConstraintTrees.itraverse","text":"itraverse(f, x) -> Any\n\n\nitraverse is to traverse like imap is to map.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.izip-Union{Tuple{T}, Tuple{Any, Any, Any}, Tuple{Any, Any, Any, Type{T}}} where T","page":"Reference","title":"ConstraintTrees.izip","text":"izip(f, x, y) -> Any\nizip(f, x, y, ::Type{T}) -> Any\n\n\nIndex-reporting variant of zip (see imap for reference).\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.map-Union{Tuple{T}, Tuple{Any, Any}, Tuple{Any, Any, Type{T}}} where T","page":"Reference","title":"ConstraintTrees.map","text":"map(f, x) -> Any\nmap(f, x, ::Type{T}) -> Any\n\n\nRun a function over everything in the tree. The resulting tree will contain elements of type specified by the 3rd argument. (This needs to be specified explicitly, because the typesystem generally cannot guess the universal type correctly.)\n\nNote this is a specialized function specific for Trees that behaves differently from Base.map.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.mapreduce-Tuple{Any, Any, Any}","page":"Reference","title":"ConstraintTrees.mapreduce","text":"mapreduce(f, op, x; init) -> Any\n\n\nReduce all items in a Tree. As with Base.reduce, the reduction order is not guaranteed, and the initial value may be used any number of times.\n\nNote this is a specialized function specific for Trees that behaves differently from Base.mapreduce.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.merge-Union{Tuple{T}, Tuple{Any, Any, Any}, Tuple{Any, Any, Any, Type{T}}} where T","page":"Reference","title":"ConstraintTrees.merge","text":"merge(f, x, y) -> Any\nmerge(f, x, y, ::Type{T}) -> Any\n\n\nRun a function over the values in the merge of all paths in the trees (currently there is support for 2 and 3 trees). This is an \"outer join\" equivalent of zip. Missing elements are replaced by missing in the function call parameters, and the function may return missing to omit elements.\n\nNote this is a specialized function specific for Trees that behaves differently from Base.merge.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.optional_tree_get-Tuple{Missing, Any}","page":"Reference","title":"ConstraintTrees.optional_tree_get","text":"optional_tree_get(_::Missing, _) -> Missing\n\n\nGet a key from a tree that is possibly missing.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.optional_tree_keys-Tuple{Missing}","page":"Reference","title":"ConstraintTrees.optional_tree_keys","text":"optional_tree_keys(\n _::Missing\n) -> DataStructures.SortedSet{Any, Base.Order.ForwardOrdering}\n\n\nGet a sorted set of keys from a tree that is possibly missing.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.reduce-Tuple{Any, Any}","page":"Reference","title":"ConstraintTrees.reduce","text":"reduce(op, x; init) -> Any\n\n\nLike mapreduce but the mapped function is identity.\n\nTo avoid much type suffering, the operation should ideally preserve the type of its arguments. If you need to change the type, you likely want to use mapreduce.\n\nNote this is a specialized function specific for Trees that behaves differently from Base.reduce.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.traverse-Tuple{Any, Any}","page":"Reference","title":"ConstraintTrees.traverse","text":"traverse(f, x) -> Any\n\n\nLike map, but discards the results, thus relying only on the side effects of f.\n\nTechnically the name should be for, but that's a Julia keyword.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.zip-Union{Tuple{T}, Tuple{Any, Any, Any}, Tuple{Any, Any, Any, Type{T}}} where T","page":"Reference","title":"ConstraintTrees.zip","text":"zip(f, x, y) -> Any\nzip(f, x, y, ::Type{T}) -> Any\n\n\nRun a function over the values in the intersection of paths in several trees (currently there is support for 2 and 3 trees). This is an \"inner join\" – all extra elements are ignored. \"Outer join\" can be done via merge.\n\nAs with map, the inner type of the resulting tree must be specified by the last parameter..\n\nNote this is a specialized function specific for Trees that behaves differently from Base.zip.\n\n\n\n\n\n","category":"method"},{"location":"reference/#Constraint-trees","page":"Reference","title":"Constraint trees","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [ConstraintTrees]\nPages = [\"src/constraint_tree.jl\"]","category":"page"},{"location":"reference/#ConstraintTrees.ConstraintTreeElem","page":"Reference","title":"ConstraintTrees.ConstraintTreeElem","text":"A shortcut for the type of the values in ConstraintTree.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.ConstraintTree","page":"Reference","title":"ConstraintTrees.ConstraintTree","text":"struct Tree{ConstraintTrees.Constraint}\n\nA hierarchical tree of many constraints that together describe a constrained system. The tree may recursively contain other trees in a directory-like structure, which contain Constraints as leaves.\n\nMembers of the constraint tree are accessible via the record dot syntax as properties; e.g. a constraint labeled with :abc in a constraint tree t may be accessed as t.abc and as t[:abc], and can be found while iterating through elems(t).\n\nConstructing the constraint trees\n\nUse operator ^ to put a name on a constraint to convert it into a single element ConstraintTree:\n\nx = :my_constraint ^ Constraint(LinearValue(...), 1.0)\ndir = :my_constraint_dir ^ x\n\ndir.my_constraint_dir.my_constraint.bound # returns 1.0\n\nUse operator * to glue two constraint trees together while sharing the variable indexes specified by the contained LinearValues and QuadraticValues.\n\nmy_constraints = :some_constraints ^ Constraint(...) * :more_constraints ^ Constraint(...)\n\nUse operator + to glue two constraint trees together without sharing of any variables. The operation will renumber the variables in the trees so that the sets of variable indexes used by either tree are completely disjunct, and then glue the trees together as with *:\n\ntwo_independent_systems = my_system + other_system\n\nVariable sharing limitations\n\nBecause of the renumbering, you can not easily use constraints and values from the values before the addition in the constraint tree that is the result of the addition. There is no check against that – the resulting ConstraintTree will be valid, but will probably describe a different optimization problem than you intended.\n\nAs a rule of thumb, avoid necessary parentheses in expressions that work with the constraint trees: While t1 * t2 + t3 might work just as intended, t1 * (t2 + t3) is almost certainly wrong because the variables in t1 that are supposed to connect to variables in either of t2 and t3 will not connect properly because of renumbering of both t2 and t3. If you need to construct a tree like that, do the addition first, and construct the t1 after that, based on the result of the addition.\n\n\n\n\n\n","category":"type"},{"location":"reference/#ConstraintTrees.incr_var_idx-Tuple{Int64, Int64}","page":"Reference","title":"ConstraintTrees.incr_var_idx","text":"incr_var_idx(x::Int64, incr::Int64) -> Int64\n\n\nInternal helper for manipulating variable indices.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.incr_var_idxs-Tuple{ConstraintTrees.Constraint, Int64}","page":"Reference","title":"ConstraintTrees.incr_var_idxs","text":"incr_var_idxs(\n x::ConstraintTrees.Constraint,\n incr::Int64\n) -> ConstraintTrees.Constraint\n\n\nOffset all variable indexes in a ConstraintTree by the given increment.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.incr_var_idxs-Tuple{ConstraintTrees.LinearValue, Int64}","page":"Reference","title":"ConstraintTrees.incr_var_idxs","text":"incr_var_idxs(\n x::ConstraintTrees.LinearValue,\n incr::Int64\n) -> ConstraintTrees.LinearValue\n\n\nOffset all variable indexes in a LinearValue by the given increment.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.incr_var_idxs-Tuple{ConstraintTrees.QuadraticValue, Int64}","page":"Reference","title":"ConstraintTrees.incr_var_idxs","text":"incr_var_idxs(\n x::ConstraintTrees.QuadraticValue,\n incr::Int64\n) -> ConstraintTrees.QuadraticValue\n\n\nOffset all variable indexes in a QuadraticValue by the given increment.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.incr_var_idxs-Tuple{ConstraintTrees.Tree{ConstraintTrees.Constraint}, Int64}","page":"Reference","title":"ConstraintTrees.incr_var_idxs","text":"incr_var_idxs(\n x::ConstraintTrees.Tree{ConstraintTrees.Constraint},\n incr::Int64\n) -> ConstraintTrees.Tree{ConstraintTrees.Constraint}\n\n\nOffset all variable indexes in a ConstraintTree by the given increment.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.substitute_values-Union{Tuple{T}, Tuple{ConstraintTrees.Tree, AbstractVector}, Tuple{ConstraintTrees.Tree, AbstractVector, Type{T}}} where T","page":"Reference","title":"ConstraintTrees.substitute_values","text":"substitute_values(\n x::ConstraintTrees.Tree,\n y::AbstractVector\n) -> Any\nsubstitute_values(\n x::ConstraintTrees.Tree,\n y::AbstractVector,\n ::Type{T}\n) -> Any\n\n\nSubstitute variable values from y into the constraint tree's constraint's values, getting a tree of \"solved\" constraint values for the given variable assignment.\n\nThe third argument forces the output type (it is forwarded to map). The type gets defaulted from eltype(y).\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.var_count-Tuple{ConstraintTrees.Constraint}","page":"Reference","title":"ConstraintTrees.var_count","text":"var_count(x::ConstraintTrees.Constraint) -> Int64\n\n\nFind the expected count of variables in a Constraint.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.var_count-Tuple{ConstraintTrees.LinearValue}","page":"Reference","title":"ConstraintTrees.var_count","text":"var_count(x::ConstraintTrees.LinearValue) -> Int64\n\n\nFind the expected count of variables in a LinearValue. (This is a O(1) operation, relying on the ordering of the indexes.)\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.var_count-Tuple{ConstraintTrees.QuadraticValue}","page":"Reference","title":"ConstraintTrees.var_count","text":"var_count(x::ConstraintTrees.QuadraticValue) -> Int64\n\n\nFind the expected count of variables in a QuadraticValue. (This is a O(1) operation, relying on the co-lexicographical ordering of indexes.)\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.var_count-Tuple{ConstraintTrees.Tree{ConstraintTrees.Constraint}}","page":"Reference","title":"ConstraintTrees.var_count","text":"var_count(\n x::ConstraintTrees.Tree{ConstraintTrees.Constraint}\n) -> Int64\n\n\nFind the expected count of variables in a ConstraintTree.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.variable-Tuple{}","page":"Reference","title":"ConstraintTrees.variable","text":"variable(; bound, idx) -> ConstraintTrees.Constraint\n\n\nAllocate a single unnamed variable, returning a Constraint with an optionally specified bound.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.variables-Tuple{}","page":"Reference","title":"ConstraintTrees.variables","text":"variables(; keys, bounds)\n\n\nMake a trivial constraint system that creates variables with indexes in range 1:length(keys) named in order as given by keys.\n\nParameter bounds is either nothing for creating variables without bounds assigned to them, a single bound for creating variables with the same constraint assigned to them all, or an iterable object of same length as keys with individual bounds for each variable in the same order as keys.\n\nThe individual bounds should be subtypes of Bound, or nothing. To pass a single bound for all variables, use e.g. bounds = EqualTo(0).\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.variables_for-Tuple{Any, ConstraintTrees.Tree}","page":"Reference","title":"ConstraintTrees.variables_for","text":"variables_for(makebound, ts::ConstraintTrees.Tree) -> Any\n\n\nAllocate a variable for each item in a constraint tree (or any other kind of tree) and return a ConstraintTree with variables bounded by the makebound function, which converts a given tree element's value into a bound for the corresponding variable.\n\n\n\n\n\n","category":"method"},{"location":"reference/#ConstraintTrees.variables_ifor-Tuple{Any, ConstraintTrees.Tree}","page":"Reference","title":"ConstraintTrees.variables_ifor","text":"variables_ifor(makebound, ts::ConstraintTrees.Tree) -> Any\n\n\nLike variables_for but the makebound function also receives a path to the variable, as with imap.\n\n\n\n\n\n","category":"method"},{"location":"reference/#Trees-for-storing-solved-values","page":"Reference","title":"Trees for storing solved values","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [ConstraintTrees]\nPages = [\"src/value_tree.jl\"]","category":"page"},{"location":"#ConstraintTrees.jl","page":"README","title":"ConstraintTrees.jl","text":"","category":"section"},{"location":"","page":"README","title":"README","text":"Modules = [ConstraintTrees]\nPages = [\"src/ConstraintTrees.jl\"]","category":"page"},{"location":"#ConstraintTrees.ConstraintTrees","page":"README","title":"ConstraintTrees.ConstraintTrees","text":"Package ConstraintTrees.jl provides a simple data structure ConstraintTree for organizing the contents of linear and quadratic constrained optimization problems. As a main goal, it abstracts over the distinction between constraints and variables, allowing much tidier representation for many kinds of complex constraint systems.\n\nThe primary purpose of ConstraintTrees.jl is to work with COBREXA.jl; but the package is otherwise completely independent, lightweight, dependency-free and usecase-agnostic. Generally, it is intended to be used with JuMP and the documentation uses JuMP for demonstrations, but any other solver framework will do just as well.\n\nThe package is structured as follows:\n\nThere is no representation for variables in the model; instead, values depend on anonymous numbered variables, and, if suitable, special named values may \"implicitly\" serve as representations for variables. This assumption erases the distinction between a \"simple\" variable and a complex derived linear combination, allowing more freedom in model construction.\nVariables may be combined into LinearValues and QuadraticValues, which are affine combinations and quadratic-affine combinations (respecitively) of values of some selected variables.\nValues may be bounded to an interval or exact value using a Constraint\nA collection of named Constraints is called a ConstraintTree; it behaves mostly as a specialized Symbol-keyed dictionary.\nConstraintTrees can be very easily organized into subdirectories, combined and made independent on each other using operators ^, *, and + – this forms the basis of the \"tidy\" algebra of constraints.\nA variable assignment, which is typically the \"solution\" for a given constraint tree, can be combined with a ConstraintTree to create a \"value tree\" via substitute_values, which enables browsing of the optimization results in the very same structure as the input ConstraintTree.\n\nYou can follow the examples in documentation and the docstrings of package contents for more details.\n\n\n\n\n\n","category":"module"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"EditURL = \"1-metabolic-modeling.jl\"","category":"page"},{"location":"1-metabolic-modeling/#Example:-Metabolic-modeling","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"In this example we demonstrate the use of ConstraintTree structure for solving the metabolic modeling tasks. At the same time, we show how to export the structure to JuMP, and use value trees to find useful information about the result.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"First, let's import some packages:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"import ConstraintTrees as C","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"We will need a constraint-based metabolic model; for this test we will use the usual \"E. Coli core metabolism\" model as available from BiGG:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"import Downloads: download\n\ndownload(\"http://bigg.ucsd.edu/static/models/e_coli_core.xml\", \"e_coli_core.xml\")\n\nimport SBML\necoli = SBML.readSBML(\"e_coli_core.xml\")","category":"page"},{"location":"1-metabolic-modeling/#Allocating-and-constraining-variables","page":"Example: Metabolic modeling","title":"Allocating and constraining variables","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Let's first build the constrained representation of the problem. First, we will need a variable for each of the reactions in the model.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c = C.variables(keys = Symbol.(keys(ecoli.reactions)))","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"info: Pretty-printing\nBy default, Julia shows relatively long namespace prefixes before all identifiers, which clutters the output. You can import individual names form ConstraintTrees package to improve the pretty-printing, using e.g.: import ConstraintTrees: Constraint, Tree, LinearValue.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"The above operation returns a ConstraintTree. You can browse these as a dictionary:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c[:R_PFK]","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"...or much more conveniently using the record dot syntax as properties:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c.R_PFK","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"The individual LinearValues in constraints behave like sparse vectors that refer to variables: The first field represents the referenced variable indexes, and the second field represents the coefficients. Compared to the sparse vectors, information about the total number of variables is not stored explicitly.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Operator ^ is used to name individual constraints and directories in the hierarchy. Let us name our constraints as \"fluxes\" (which is a common name in metabolic modeling) and explore the result:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c = :fluxes^c","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"We can see that there is now only a single \"top-level directory\" in the constraint system, which can be explored with the dot access again:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c.fluxes.R_PFK","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Indexing via values is again possible via the usual bracket notation, and can be freely combined with the dot notation:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c[:fluxes][:R_PFK]","category":"page"},{"location":"1-metabolic-modeling/#Adding-single-variable-constraints","page":"Example: Metabolic modeling","title":"Adding single-variable constraints","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Each element in the constraint tree consists of a linear combination of the variables, which can be freely used to construct (and constraint) new linear combinations of variables. As the simplest use, we can constraint the variables (using Constraints) to their valid bounds as defined by the model:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"rxn_constraints =\n let rxn_bounds = Symbol.(keys(ecoli.reactions)) .=> zip(SBML.flux_bounds(ecoli)...)\n C.ConstraintTree(\n r => C.Constraint(value = c.fluxes[r].value, bound = (lb, ub)) for\n (r, ((lb, _), (ub, _))) in rxn_bounds # SBML units are ignored for simplicity\n )\n end","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Note that in the example we use a simplified Dict-like construction of the ConstraintTree. You might equivalently write the code as a product (using prod()) of constraints that are individually labeled using the ^ operator, but the direct dictionary construction is faster because it skips many intermediate steps, and looks much more like idiomatic Julia code.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"To combine the constraint trees, we can make a nice directory for the constraints and add them to the tree using operator *. Making \"products\" of constraint trees combines the trees in a way that they share their variables. In particular, using the values from c.fluxes in the constraints within rxn_constraints here will constraint precisely the same variables (and thus values) as the ones in the original system.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c = c * :constraints^rxn_constraints","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Our model representation now contains 2 \"directories\":","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"collect(keys(c))","category":"page"},{"location":"1-metabolic-modeling/#Value-and-constraint-arithmetics","page":"Example: Metabolic modeling","title":"Value and constraint arithmetics","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Values may be combined additively and multiplied by real constants; which allows us to easily create more complex linear combination of any values already occurring in the model:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"3 * c.fluxes.R_PFK.value - c.fluxes.R_ACALD.value / 2","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"For simplicity, you can also scale whole constraints, but it is impossible to add them together because the meaning of the bounds would get broken:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"(3 * c.fluxes.R_PFK, -c.fluxes.R_ACALD / 2)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"To process constraints in bulk, you may use C.value for easier access to values when making new constraints:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"sum(C.value.(values(c.fluxes)))","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Notably, ConstraintTrees provide their own implementation of sum which typically works faster when adding many Values together. The basic interface and results are otherwise the same as with the sum from Base:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"C.sum(C.value.(values(c.fluxes)))","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"danger: `Base.sum` vs. `ConstraintTrees.sum`\nSince the sum from Base package is usually implemented as a left fold, it does not behave optimally when the temporary sub-results grow during the computation (and thus their addition becomes gradually slower). In turn, using the Base.sum for summing up LinearValues and QuadraticValues may take time quadratic in the number of added items. sum from ConstraintTrees uses a different addition order which reduces the amount of large items added together (implemented by \"pairwise\" preduce), and in works in almost-linear time in most cases.","category":"page"},{"location":"1-metabolic-modeling/#Affine-values","page":"Example: Metabolic modeling","title":"Affine values","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"To simplify various modeling goals (mainly calculation of various kinds of \"distances\"), the values support inclusion of an affine element – the variable with index 0 is assumed to be the \"affine unit\", and its assigned value is fixed at 1.0.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"To demonstrate, let's make a small system with 2 variables.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"system = C.variables(keys = [:x, :y])","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"To add an affine element to a LinearValue, simply add it as a Real number, as in the linear transformations below:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"system =\n :original_coords^system *\n :transformed_coords^C.ConstraintTree(\n :xt => C.Constraint(1 + system.x.value + 4 + system.y.value),\n :yt => C.Constraint(0.1 * (3 - system.y.value)),\n )","category":"page"},{"location":"1-metabolic-modeling/#Adding-combined-constraints","page":"Example: Metabolic modeling","title":"Adding combined constraints","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Metabolic modeling relies on the fact that the total rates of any metabolite getting created and consumed by the reaction equals to zero (which corresponds to conservation of mass). We can now add corresponding \"stoichiometric\" network constraints by following the reactants and products in the SBML structure:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"stoi_constraints = C.ConstraintTree(\n Symbol(m) => C.Constraint(\n value = -C.sum(\n (\n sr.stoichiometry * c.fluxes[Symbol(rid)].value for\n (rid, r) in ecoli.reactions for sr in r.reactants if sr.species == m\n ),\n init = zero(C.LinearValue), # sometimes the sums are empty\n ) + C.sum(\n (\n sr.stoichiometry * c.fluxes[Symbol(rid)].value for\n (rid, r) in ecoli.reactions for sr in r.products if sr.species == m\n ),\n init = zero(C.LinearValue),\n ),\n bound = 0.0,\n ) for m in keys(ecoli.species)\n);\nnothing #hide","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Let's have a closer look at one of the constraints:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"stoi_constraints.M_acald_c","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Again, we can label the stoichiometry properly and add it to the bigger model representation:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c = c * :stoichiometry^stoi_constraints","category":"page"},{"location":"1-metabolic-modeling/#Saving-the-objective","page":"Example: Metabolic modeling","title":"Saving the objective","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Constraint based models typically optimize a certain linear formula. Constraint trees do not support setting objectives (they are not constraints), but we can save the objective as a harmless unconstrained \"constraint\" that can be used later to refer to the objective more easily. We can save that information into the constraint system immediately:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c *=\n :objective^C.Constraint(\n C.sum(\n c.fluxes[Symbol(rid)].value * coeff for\n (rid, coeff) in (keys(ecoli.reactions) .=> SBML.flux_objective(ecoli)) if\n coeff != 0.0;\n init = 0.0,\n ),\n )","category":"page"},{"location":"1-metabolic-modeling/#Constrained-system-solutions-and-value-trees","page":"Example: Metabolic modeling","title":"Constrained system solutions and value trees","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"To aid exploration of variable assignments in the constraint trees, we can convert them to value trees. These have the very same structure as constraint trees, but carry only the \"solved\" constraint values instead of full constraints.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Let's demonstrate this quickly on the example of system with affine variables from above. First, let's assume that someone solved the system (in some way) and produced a solution of variables as follows:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"solution = [1.0, 5.0] # corresponds to :x and :y in order given in `variables`","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"A value tree for this solution is constructed in a straightforward manner:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"st = C.substitute_values(system, solution)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"We can now check the values of the original coordinates","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"st.original_coords","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"The other constraints automatically get their values that correspond to the overall variable assignment:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"st.transformed_coords","category":"page"},{"location":"1-metabolic-modeling/#Solving-the-constraint-system-using-JuMP","page":"Example: Metabolic modeling","title":"Solving the constraint system using JuMP","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"We can make a small function that throws our model into JuMP, optimizes it, and gives us back a variable assignment vector. This vector can then be used to determine and browse the values of constraints and variables using a Float64-valued tree.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"import JuMP\nfunction optimized_vars(cs::C.ConstraintTree, objective::C.LinearValue, optimizer)\n model = JuMP.Model(optimizer)\n JuMP.@variable(model, x[1:C.var_count(cs)])\n JuMP.@objective(model, JuMP.MAX_SENSE, C.substitute(objective, x))\n C.traverse(cs) do c\n b = c.bound\n if b isa C.EqualTo\n JuMP.@constraint(model, C.substitute(c.value, x) == b.equal_to)\n elseif b isa C.Between\n val = C.substitute(c.value, x)\n isinf(b.lower) || JuMP.@constraint(model, val >= b.lower)\n isinf(b.upper) || JuMP.@constraint(model, val <= b.upper)\n end\n end\n JuMP.optimize!(model)\n JuMP.value.(model[:x])\nend","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"With this in hand, we can use an external linear problem solver to find the optimum of the constrained system:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"import GLPK\noptimal_variable_assignment = optimized_vars(c, c.objective.value, GLPK.Optimizer)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"To explore the solution more easily, we can make a tree with values that correspond to ones in our constraint tree:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"result = C.substitute_values(c, optimal_variable_assignment)\n\nresult.fluxes.R_BIOMASS_Ecoli_core_w_GAM","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"result.fluxes.R_PFK","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Sometimes it is unnecessary to recover the values for all constraints, so we are better off selecting just the right subtree:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"C.substitute_values(c.fluxes, optimal_variable_assignment)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"C.substitute_values(c.objective, optimal_variable_assignment)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"We'll save the result for future use at the end of this example:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"result_single_organism = result","category":"page"},{"location":"1-metabolic-modeling/#Combining-and-extending-constraint-systems","page":"Example: Metabolic modeling","title":"Combining and extending constraint systems","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Constraint trees can be extended with new variables from another constraint trees using the + operator. Contrary to the * operator, adding the constraint trees does not share the variables between operands, and the resulting constraint tree will basically contain two disconnected trees that solve independently. The user is expected to create additional constraints to connect the independent parts.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Here, we demonstrate this by creating a community of two slightly different E. Coli species: First, we disable functionality of a different reaction in each of the models to create a diverse group of differently handicapped organisms:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c =\n :community^(\n :species1^(c * :handicap^C.Constraint(c.fluxes.R_PFK.value, 0)) +\n :species2^(c * :handicap^C.Constraint(c.fluxes.R_ACALD.value, 0))\n )","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"We can create additional variables that represent total community intake of oxygen, and total community production of biomass:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c += :exchanges^C.variables(keys = [:oxygen, :biomass], bounds = [(-10.0, 10.0), nothing])","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"These can be constrained so that the total influx (or outflux) of each of the registered metabolites is in fact equal to total consumption or production by each of the species:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c *=\n :exchange_constraints^C.ConstraintTree(\n :oxygen => C.Constraint(\n value = c.exchanges.oxygen.value - c.community.species1.fluxes.R_EX_o2_e.value -\n c.community.species2.fluxes.R_EX_o2_e.value,\n bound = 0.0,\n ),\n :biomass => C.Constraint(\n value = c.exchanges.biomass.value -\n c.community.species1.fluxes.R_BIOMASS_Ecoli_core_w_GAM.value -\n c.community.species2.fluxes.R_BIOMASS_Ecoli_core_w_GAM.value,\n bound = 0.0,\n ),\n )","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Let's see how much biomass are the two species capable of producing together:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"result =\n C.substitute_values(c, optimized_vars(c, c.exchanges.biomass.value, GLPK.Optimizer))\nresult.exchanges","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Finally, we can iterate over all species in the small community and see how much biomass was actually contributed by each:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Dict(k => v.fluxes.R_BIOMASS_Ecoli_core_w_GAM for (k, v) in result.community)","category":"page"},{"location":"1-metabolic-modeling/#Modifying-constraint-systems-in-place","page":"Example: Metabolic modeling","title":"Modifying constraint systems in-place","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Constraint trees can be modified in-place in a way that allows you to easily change small values in the trees without reconstructing them from the ground up.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Although in-place modification is extremely convenient and looks much easier than rebuilding the tree, it may be very detrimental to the robustness and efficiency of the programs, for several reasons:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"changing any data breaks assumptions on anything that was already derived from the data\nfor efficiency, the tree structures are not copied by default if there's no need to do it, and only shared by references; which means that a naive change at a single place of the tree may easily change values also in other parts of any trees, including completely different trees\nthe \"convenient way\" of making sure that the above problem never happens is to copy-on-write the whole tree structure, which is typically quite detrimental to memory use and program efficiency","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"danger: Rules of thumb for safe use of in-place modification\nOnly use the in-place modifications if:there is code that explicitly makes sure there is no false sharing via references, e.g. using a deep copy\nthe in-place modifications are the last thing happening to the constraint tree before it is used by the solver\nthe in-place modification code is not a part of a re-usable library\nyou are using a suitable wrapper interface such as Accessors.jl","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Now, if you are completely sure that ignoring the robustness guidelines will help your code, you can do the in-place tree modifications quite easily using both dot-access and array-index syntax.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"You can thus, e.g., set a single bound:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c.exchanges.oxygen.bound = C.Between(-20.0, 20.0)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"...or rebuild a whole constraint (using a tuple shortcut for Between):","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c.exchanges.biomass = C.Constraint(c.exchanges.biomass.value, (-20, 20))","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"...or even add new constraints, here using the index syntax for demonstration:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c[:exchanges][:production_is_zero] = C.Constraint(c.exchanges.biomass.value, 0)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"...or remove some constraints (this erases the constraint that was added just above):","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"delete!(c.exchanges, :production_is_zero)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"In the end, the flux optimization yields an expectably different result:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"result_with_more_oxygen =\n C.substitute_values(c, optimized_vars(c, c.exchanges.biomass.value, GLPK.Optimizer))\nresult.exchanges","category":"page"},{"location":"1-metabolic-modeling/#Alternative:-Using-Accessors.jl","page":"Example: Metabolic modeling","title":"Alternative: Using Accessors.jl","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Accessors.jl implement a \"lensy\" way to update immutable data structures. That comes with a nice outcome of doing the right amount of shallow copyies for you automatically, thus avoiding much of the technical danger of in-place modifications. (You still lose the equational reasoning on your code, but that may not be an issue at all in usual codebases.)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Accessors interface is used simply through macros @set (which sets a deeply nested field in a structure, returning a modified copy), or with @reset which automatically \"assigns\" the result back to the original variable:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"using Accessors\n\nc = @set c.exchanges.biomass.bound = C.Between(-50, 50)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"The above code is equivalent to:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"@reset c.exchanges.biomass.bound = C.Between(-50, 50)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"...and it is also possible to use string and symbol indexes to pick the individual tree items:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"@reset c[:exchanges][\"biomass\"].bound = C.Between(-50, 50)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"All of these operations give us:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"c.exchanges.biomass","category":"page"},{"location":"1-metabolic-modeling/#Seeing-the-differences-between-the-results","page":"Example: Metabolic modeling","title":"Seeing the differences between the results","text":"","category":"section"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"ConstraintTrees.jl defines its own version of zip function that can apply a function to the contents of several trees, \"zipping\" them over the same keys in the structure. This is vaguely similar but otherwise not related to the zip from Julia base (similarly, ConstraintTrees.jl have their own specific map).","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"In practice, this allows you to create combined trees with various nice properties very quickly. For example, you can find how much the values have changed between our two communities:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"C.zip((x, y) -> y - x, result, result_with_more_oxygen, Float64)","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"The result is again a Tree, with the contained type specified by the last argument (Float64 in this case). We can explore it right away as the other result trees. Also, it is possible to call this kind of function using the Julia do notation, making the syntax a bit neater:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"difference = C.zip(result, result_with_more_oxygen, Float64) do x, y\n y - x\nend","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"Exploring the difference works as expected:","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"difference.community.species1.fluxes","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"For convenience in special cases, zip is also overloaded for 3 arguments. We can, for a completely artificial example, check if the absolute flux change was bigger in the first or in the second organism in the community when compared to the original single-organism flux (which we luckily saved above):","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"changes = C.zip(\n result.community.species1,\n result.community.species2,\n result_single_organism,\n Bool,\n) do s1, s2, orig\n abs(s1 - orig) > abs(s2 - orig)\nend\n\nchanges.fluxes","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"More high-level functions like zip are described in an example on functional tree processing.","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"","category":"page"},{"location":"1-metabolic-modeling/","page":"Example: Metabolic modeling","title":"Example: Metabolic modeling","text":"This page was generated using Literate.jl.","category":"page"}] }