diff --git a/src/MRIBuilder.jl b/src/MRIBuilder.jl index be2a6dd641f780b0ba9e2e1336740c12ea671ab8..281f3c0f4e0152effc7ac14ad35b5c4165e6bba2 100644 --- a/src/MRIBuilder.jl +++ b/src/MRIBuilder.jl @@ -18,8 +18,8 @@ export BuildingBlock, scanner_constraints! import .SequenceBuilders: SequenceBuilder, start_time, end_time, duration, TR export SequenceBuilder, start_time, end_time, duration, TR -import .ConcreteBlocks: ConcreteBlock -export ConcreteBlock +import .ConcreteBlocks: ConcreteBlock, Sequence +export ConcreteBlock, Sequence import .Wait: WaitBlock export WaitBlock @@ -36,4 +36,7 @@ export InstantReadout import .Scanners: Scanner, Siemens_Connectom, Siemens_Prisma, Siemens_Terra export Scanner, Siemens_Connectom, Siemens_Prisma, Siemens_Terra +using JuMP +export @constraint, @objective, objective_function, optimize!, has_values, value + end diff --git a/src/concrete_blocks.jl b/src/concrete_blocks.jl index 9c6336c91d6081cb14119da2cca2dc473af94d6d..20d4d3f14b916d0c4fe9a1d0fa62eeb2886e66d9 100644 --- a/src/concrete_blocks.jl +++ b/src/concrete_blocks.jl @@ -1,9 +1,9 @@ module ConcreteBlocks -import JuMP: has_values +import JuMP: has_values, optimize!, model import ..BuildingBlocks: BuildingBlock, BuildingBlockPlaceholder, properties -import ..SequenceBuilders: SequenceBuilder +import ..SequenceBuilders: SequenceBuilder, to_block, AbstractSequence -abstract type AbstractConcreteBlock end +abstract type AbstractConcreteBlock <: BuildingBlock end struct ConcreteRFPulse time :: Vector{Number} @@ -50,19 +50,19 @@ ConcreteGradient(values::Tuple{<:Vector, <:Vector, <:Vector, <:Vector}) = Concre A [`BuildingBlock`](@ref) that is fully defined (i.e., there are no variables to be optimised). """ struct ConcreteBlock <: AbstractConcreteBlock - builder :: SequenceBuilder duration :: Float64 pulse :: Union{ConcreteRFPulse, Nothing} gradient :: Union{ConcreteGradient, Nothing} readout_times :: Vector{Float64} end -ConcreteBlock(args...; kwargs...) = BuildingBlockPlaceholder{ConcreteBlock}(args...; kwargs...) - -function ConcreteBlock(builder::SequenceBuilder, duration::Number; pulse=nothing, gradient=nothing, readout_times=Number[]) +function ConcreteBlock(duration::Number; pulse=nothing, gradient=nothing, readout_times=Number[]) ConcreteBlock(builder, duration, ConcreteRFPulse(pulse), ConcreteGradient(gradient), Float64.(readout_times)) end +to_block(::SequenceBuilder, concrete::AbstractConcreteBlock) = concrete +has_values(::AbstractConcreteBlock) = true + """ ConcreteBlock(other_building_block) @@ -72,27 +72,48 @@ Creates a [`ConcreteBlock`](@ref) from another [`BuildingBlock`](@ref). This will raise an error if the other [`BuildingBlock`](@ref) has not been optimised yet. If it has been optimised, then [`to_concrete_block`](@ref) will be called. """ -function ConcreteBlock(builder::SequenceBuilder, block::BuildingBlock) +function ConcreteBlock(block::BuildingBlock) if !has_values(block) error("Making `BuildingBlock` objects concrete is only possible after optimisation.") end - return to_concrete_block(builder, block) + return to_concrete_block(block) end """ - to_concrete_block(builder, other_building_block) + to_concrete_block(other_building_block) Internal function used to create [`ConcreteBlock`](@ref) from any [`BuildingBlock`](@ref). This needs to be defined for every [`BuildingBlock`](@ref) """ -function to_concrete_block(builder::SequenceBuilder, cb::ConcreteBlock) - return ConcreteBlock(builder, cb.duration, cb.pulse, cb.gradient, cb.readout_times) +function to_concrete_block(cb::AbstractConcreteBlock) + return cb end properties(::Type{<:ConcreteBlock}) = [] has_values(c::ConcreteBlock) = true + +""" + Sequence(builder::SequenceBuilder) + +A fully defined sequence with no free variables. + +When created from a [`SequenceBuilder`](@ref), all non-fixed variables are optimised given any constraints +and the resulting sequence is returned. +""" +struct Sequence <: AbstractSequence + blocks :: Vector{<:AbstractConcreteBlock} + TR :: Number +end + +function Sequence(seq::SequenceBuilder) + if !has_values(seq) + optimize!(seq.model) + end + return Sequence(ConcreteBlock.(seq.blocks), value(seq.TR)) +end + end \ No newline at end of file diff --git a/src/sequence_builders.jl b/src/sequence_builders.jl index 18790a366a8e11533686573c07ab2423025348b4..3b09b76987dd7a155c163397559daf897fbe2ca1 100644 --- a/src/sequence_builders.jl +++ b/src/sequence_builders.jl @@ -5,6 +5,11 @@ import Ipopt import ..BuildingBlocks: BuildingBlock, BuildingBlockPlaceholder, match_blocks!, duration, apply_simple_constraint!, scanner_constraints!, start_time, end_time import ...Scanners: Scanner +""" +Parent type for a sequence with free variables ([`SequenceBuilder`](@ref)) or without free variables (`ConcreteSequence`). +""" +abstract type AbstractSequence end + """ SequenceBuilder(blocks...) @@ -13,7 +18,7 @@ Defines a sequence as a series of [`BuildingBlock`](@ref) objects. After defining the blocks, the user can add one or more constraints and an objective function to the properties of the [`BuildingBlock`](@ref) objects. A sequence matching these constraints will be produced by calling [`solve`](@ref)(builder) or [`Sequence`](@ref)(builder). """ -struct SequenceBuilder +struct SequenceBuilder <: AbstractSequence model :: Model scanner :: Scanner blocks :: Vector{<:BuildingBlock} @@ -35,6 +40,8 @@ struct SequenceBuilder end end +get_blocks(seq::SequenceBuilder) = seq.blocks + function to_block(model::SequenceBuilder, placeholder::BuildingBlockPlaceholder{T}) where {T} block = T(model, placeholder.args...; placeholder.kwargs...) if isassigned(placeholder.concrete) @@ -45,7 +52,7 @@ function to_block(model::SequenceBuilder, placeholder::BuildingBlockPlaceholder{ return block end -Base.getindex(model::SequenceBuilder, i::Integer) = model.blocks[i] +Base.getindex(model::AbstractSequence, i::Integer) = get_blocks(model)[i] function SequenceBuilder(blocks...; kwargs...) ipopt_opt = optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0) @@ -68,12 +75,11 @@ function Base.show(io::IO, builder::SequenceBuilder) end -Base.length(sb::SequenceBuilder) = length(sb.blocks) +Base.length(sb::AbstractSequence) = length(get_blocks(sb)) builder(bb::BuildingBlock) = bb.builder owner_model(bb::BuildingBlock) = owner_model(builder(bb)) owner_model(sb::SequenceBuilder) = sb.model has_values(object::Union{BuildingBlock, SequenceBuilder}) = has_values(owner_model(object)) -optimize!(sb::SequenceBuilder) = optimize!(owner_model(sb)) """ TR(sequence::SequenceBuilder)