diff --git a/docs/src/defining_sequence.md b/docs/src/defining_sequence.md index 04e494b14814c9a795d0c9fa07fddd4815d5f7cd..cc28e3f7516b2485eeaf4ed1337092e6875954c9 100644 --- a/docs/src/defining_sequence.md +++ b/docs/src/defining_sequence.md @@ -13,10 +13,10 @@ This name will have to be unique. The next step is to define the sequence constructor: ```julia -function DiffusionSpinEcho(; scanner=DefaultScanner, parameters..., variables...) +function DiffusionSpinEcho(; scanner=DefaultScanner, parameters..., vars...) build_sequence(scanner) do (g1, g2) = dwi_gradients(...) - return Sequence([ + seq = Sequence([ :excitation => excitation_pulse(...), nothing, :gradient => g1, @@ -26,7 +26,10 @@ function DiffusionSpinEcho(; scanner=DefaultScanner, parameters..., variables... g2, nothing, :readout => readout_event(...), - ], name=:DiffusionSpinEcho, variables...) + nothing, + ], name=:DiffusionSpinEcho, vars...) + add_cost_function!(variables.duration(seq[6]) + variables.duration(seq[7])) + return seq end end ``` @@ -42,6 +45,7 @@ The crucial part here are the individual parts used to build the sequence, defin :gradient2 => g2, nothing, :readout => readout_event(...), + nothing ] ``` @@ -49,6 +53,17 @@ We can see that this sequence is built in order by an excitation pulse, some unk Some of these components have been given specific names (e.g., `:excitation => ...`). This is optional, but can be useful to refer to individual components. There are [helper functions](@ref helper_functions) available to create these components. +After creating the sequence object, we can now add secondary objectives to the cost function (using [`add_cost_function!`](@ref)). +In this example, we have: +```julia +add_cost_function!(variables.duration(seq[6]) + variables.duration(seq[7])) +``` +If we check the order of the sequence component, we see that this minimises the sum of the duration of the second gradient and the wait block before this gradient. +This cost function has been added to maximise the time between the second gradient and the readout (and hence minimise the effect of eddy currents on the readout). +Note that this is a secondary cost function that will only take effect if it does not interfere with any user-defined constraints and cost functions (see [sequence optimisation](@ref sequence_optimisation)). +Some secondary cost functions will be automatically defined for you within the individual components (e.g., a trapezoidal gradient has a secondary cost function to maximise the slew rate). +There is even a tertiary cost function, which minimises the total sequence duration. + The next step is to define [summary variables](@ref variables) that the user can constrain when setting up a specific instance of this sequence: ```julia @defvar begin diff --git a/docs/src/sequence_optimisation.md b/docs/src/sequence_optimisation.md index b3469fd4574e8ecafb07f2aed77e0a55d52c5eb8..0ab0233bdcc45e99a55330c3622fd5980e74b5f1 100644 --- a/docs/src/sequence_optimisation.md +++ b/docs/src/sequence_optimisation.md @@ -11,6 +11,10 @@ In addition to the user-defined constraints, this optimisation will also take in Internally, MRIBuilder will then optimise the `BuildingBlock` free parameters to match any user-defined constraints and/or objectives. This optimisation uses the [Ipopt](https://github.com/coin-or/Ipopt) optimiser accessed through the [JuMP.jl](https://jump.dev/JuMP.jl/stable/) library. +In addition to any user-defined objectives, the developer might also define secondary objectives (e.g., manimise the total sequence duration). +These objective functions will only be considered if they do not affect the result of the user-defined primary objective. +More details on these developer-defined secondary objectives can be found in the section on [defining new sequences](@ref defining_sequences) + ## [Summary variables](@id variables) All variables are available as members of the [`variables`](@ref) structure. ```@meta diff --git a/src/variables.jl b/src/variables.jl index 76184d4b2c94e7385cf5911e4c8c5cebb7ca3e9e..b8d9b85fe448f4404413a9ae9788832a16f33da9 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -462,6 +462,8 @@ Terms added at a lower level will be optimised before any terms with a higher le By default, the term is added to the `level=2`, which is appropriate for any cost functions added by the developer, which will generally only be optimised after any user-defined cost functions (which are added at `level=1` by [`add_simple_constraint!`](@ref) or [`set_simple_constraints!`](@ref). + +Any sequence will also have a `level=3` cost function, which minimises the total sequence duration. """ function add_cost_function!(func, level=2) push!(GLOBAL_MODEL[][2], (Float64(level), func))