diff --git a/src/MRIBuilder.jl b/src/MRIBuilder.jl index 11be17556dfde67a8c2e9b14592c83cdddcc2470..63399d5313cbea27f0457e09ef51a522ae500682 100644 --- a/src/MRIBuilder.jl +++ b/src/MRIBuilder.jl @@ -3,10 +3,10 @@ Builds and optimises NMR/MRI sequences. """ module MRIBuilder -include("variables.jl") -include("building_blocks.jl") include("scanners.jl") include("build_sequences.jl") +include("variables.jl") +include("building_blocks.jl") include("wait.jl") include("gradients/gradients.jl") include("pulses/pulses.jl") diff --git a/src/build_sequences.jl b/src/build_sequences.jl index 41deba6647f9dd6dab6cfb97cd3f2716f4a5f3b0..5600724d53a6a9426678e9f74f34e9fc30c5c957 100644 --- a/src/build_sequences.jl +++ b/src/build_sequences.jl @@ -2,8 +2,7 @@ module BuildSequences import JuMP: Model, optimizer_with_attributes, optimize! import Ipopt import Juniper -import ..Scanners: Scanner, scanner_constraints! -import ..BuildingBlocks: BuildingBlock, apply_simple_constraint!, match_blocks! +import ..Scanners: Scanner const GLOBAL_MODEL = Ref(Model()) const IGNORE_MODEL = GLOBAL_MODEL[] @@ -75,11 +74,4 @@ function global_scanner() end -scanner_constraints!(bb::BuildingBlock) = scanner_constraints!(bb, global_scanner()) - -apply_simple_constraint!(variable, value) = apply_simple_constraint!(global_model(), variable, value) -match_blocks!(block1::BuildingBlock, block2::BuildingBlock, property_list) = match_blocks!(global_model(), block1, block2, property_list) -scanner_constraints!(building_block::BuildingBlock, scanner::Scanner, func::Function) = scanner_constraints!(building_block, scanner, func) -scanner_constraints!(building_block::BuildingBlock) = scanner_constraints!(building_block, global_scanner()) - end \ No newline at end of file diff --git a/src/building_blocks.jl b/src/building_blocks.jl index 3c278dd276ca26e4c01e1a54a471ddf2ae8b6002..a2cab1dfb5f51c9af81fa28ec461d03536f3e2cb 100644 --- a/src/building_blocks.jl +++ b/src/building_blocks.jl @@ -2,6 +2,8 @@ module BuildingBlocks import JuMP: value, Model, @constraint, @objective, objective_function, AbstractJuMPScalar import Printf: @sprintf import ..Variables: variables, start_time, duration, end_time, gradient_strength, slew_rate, effective_time, VariableType, qval_square +import ..BuildSequences: global_model, global_scanner +import ..Scanners: Scanner """ Parent type for all individual components out of which a sequence can be built. @@ -234,17 +236,23 @@ function set_simple_constraints!(block::BuildingBlock, kwargs) end """ - apply_simple_constraint!([model, ]variable, value) + apply_simple_constraint!(variable, value) -Add a single constraint or objective to the JuMP `model`. -This is an internal function used by [`set_simple_constraints`](@ref). +Add a single constraint or objective to the `variable`. + +`value` can be one of: +- `nothing`: do nothing +- `:min`: minimise the variable +- `:max`: maximise the variable +- `number`: fix variable to this value +- `equation`: fix variable to the result of this equation """ -apply_simple_constraint!(model::Model, variable, ::Nothing) = nothing -apply_simple_constraint!(model::Model, variable, value::Symbol) = apply_simple_constraint!(model, variable, Val(value)) -apply_simple_constraint!(model::Model, variable, ::Val{:min}) = @objective model Min objective_function(model) + variable -apply_simple_constraint!(model::Model, variable, ::Val{:max}) = @objective model Min objective_function(model) - variable -apply_simple_constraint!(model::Model, variable, value::VariableType) = @constraint model variable == value -apply_simple_constraint!(model::Model, variable::AbstractVector, value::AbstractVector) = [apply_simple_constraint!(model, v1, v2) for (v1, v2) in zip(variable, value)] +apply_simple_constraint!(variable, ::Nothing) = nothing +apply_simple_constraint!(variable, value::Symbol) = apply_simple_constraint!(variable, Val(value)) +apply_simple_constraint!(variable, ::Val{:min}) = @objective global_model() Min objective_function(global_model()) + variable +apply_simple_constraint!(variable, ::Val{:max}) = @objective global_model() Min objective_function(global_model()) - variable +apply_simple_constraint!(variable, value::VariableType) = @constraint model variable == value +apply_simple_constraint!(variable::AbstractVector, value::AbstractVector) = [apply_simple_constraint!(v1, v2) for (v1, v2) in zip(variable, value)] """ @@ -253,9 +261,9 @@ apply_simple_constraint!(model::Model, variable::AbstractVector, value::Abstract Matches the listed variables between two [`BuildingBlock`](@ref) objects. By default all shared variables (i.e., those with the same name) are matched. """ -function match_blocks!(model::Model, block1::BuildingBlock, block2::BuildingBlock, property_list) +function match_blocks!(block1::BuildingBlock, block2::BuildingBlock, property_list) for fn in property_list - @constraint model fn(block1) == fn(block2) + @constraint global_model() fn(block1) == fn(block2) end end @@ -264,4 +272,50 @@ function match_blocks!(block1::BuildingBlock, block2::BuildingBlock) match_blocks!(block1, block2, property_list) end +""" + scanner_constraints!(building_block[, scanner]) + +Adds the gradient strength and slew rate constraints from a specific [`Scanner`](@ref) to a [`BuildingBlock`]{@ref}. + +This is applied iteratively to each part of a `Sequence`. +""" +scanner_constraints!(building_block::BuildingBlock) = scanner_constraints!(building_block, global_scanner()) + +function scanner_constraints!(building_block::BuildingBlock, scanner::Scanner) + for func in [gradient_strength, slew_rate] + if isfinite(func(scanner)) + scanner_constraints!(building_block, scanner, func) + end + end +end + +function scanner_constraints!(building_block::BuildingBlock, scanner::Scanner, func::Function) + model = global_model() + if func in variables(building_block) + # apply constraint at this level + res_bb = func(building_block) + if res_bb isa AbstractVector + if isnothing(building_block.rotate) + # no rotation; apply constraint to each dimension independently + for expr in res_bb + @constraint model expr <= func(scanner) + @constraint model expr >= -func(scanner) + end + else + # with rotation: apply constraint to total squared + total_squared = sum(map(n->n^2, res_bb)) + @constraint model total_squared <= func(scanner)^2 + end + else + @constraint model res_bb <= func(scanner) + @constraint model res_bb >= -func(scanner) + end + elseif building_block isa ContainerBlock + # apply constraints at lower level + for (_, child_block) in get_children_blocks(building_block) + scanner_constraints!(child_block, scanner, func) + end + end +end + end \ No newline at end of file diff --git a/src/scanners.jl b/src/scanners.jl index 6c026a8e896a8f680744637027983d1b1818d5be..b35eef4812e1e0bb89f5cb9d27871c312602ce3d 100644 --- a/src/scanners.jl +++ b/src/scanners.jl @@ -2,10 +2,6 @@ Define general [`Scanner`](@ref) type and methods as well as some concrete scanners. """ module Scanners -import JuMP: Model, @constraint -import ..Variables: gradient_strength, slew_rate -import ..BuildingBlocks: BuildingBlock, get_children_blocks, ContainerBlock -import ..Variables: variables const gyromagnetic_ratio = 42576.38476 # (kHz/T) @@ -88,45 +84,4 @@ predefined_scanners = Dict( :Siemens_Connectom => Siemens_Connectom, ) -""" - scanner_constraints!(building_block[, scanner]) - -Adds any constraints from a specific scanner to a [`BuildingBlock`]{@ref}. -""" -function scanner_constraints!(building_block::BuildingBlock, scanner::Scanner) - for func in [gradient_strength, slew_rate] - if isfinite(func(scanner)) - scanner_constraints!(building_block, scanner, func) - end - end -end - -function scanner_constraints!(model::Model, building_block::BuildingBlock, scanner::Scanner, func::Function) - if func in variables(building_block) - # apply constraint at this level - res_bb = func(building_block) - if res_bb isa AbstractVector - if isnothing(building_block.rotate) - # no rotation; apply constraint to each dimension independently - for expr in res_bb - @constraint model expr <= func(scanner) - @constraint model expr >= -func(scanner) - end - else - # with rotation: apply constraint to total squared - total_squared = sum(map(n->n^2, res_bb)) - @constraint model total_squared <= func(scanner)^2 - end - else - @constraint model res_bb <= func(scanner) - @constraint model res_bb >= -func(scanner) - end - elseif building_block isa ContainerBlock - # apply constraints at lower level - for (_, child_block) in get_children_blocks(building_block) - scanner_constraints!(model, child_block, scanner, func) - end - end -end - end \ No newline at end of file diff --git a/src/variables.jl b/src/variables.jl index d1b7f0d9489067cc486c7cbbe4a0563656235a51..004b9a8109cccd422f794da4a174bac400a7c0a3 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -1,6 +1,7 @@ module Variables import JuMP: @variable, Model, @objective, objective_function, value, AbstractJuMPScalar -import .. +import ..Scanners: gradient_strength, slew_rate +import ..BuildSequences: global_model all_variables_symbols = [ :block => [