From 26605b0d3fbfecf864585d697b0210fb444cc271 Mon Sep 17 00:00:00 2001
From: Michiel Cottaar <michiel.cottaar@ndcn.ox.ac.uk>
Date: Sat, 27 Jan 2024 18:32:49 +0000
Subject: [PATCH] Define fixed for gradients

---
 src/building_blocks.jl             |  15 +++-
 src/concrete_blocks.jl             | 121 -----------------------------
 src/gradients/fixed_gradients.jl   |  30 +++++--
 src/gradients/gradients.jl         |   2 +-
 src/gradients/instant_gradients.jl |  19 +----
 src/gradients/pulsed_gradients.jl  |  16 ++--
 6 files changed, 50 insertions(+), 153 deletions(-)
 delete mode 100644 src/concrete_blocks.jl

diff --git a/src/building_blocks.jl b/src/building_blocks.jl
index a27f177..a28ba2b 100644
--- a/src/building_blocks.jl
+++ b/src/building_blocks.jl
@@ -8,8 +8,8 @@ import ..Variables: variables, start_time, duration, end_time, gradient_strength
 Parent type for all individual components out of which a sequence can be built.
 
 Required methods:
-- [`duration`](@ref)(block, parameters): returns block duration in ms.
-- `to_concrete_block`(sequence, block): converts the block into a `ConcreteBlock`, which will be part of given `Sequence`.
+- [`duration`](@ref)(block, parameters): Return block duration in ms.
+- [`fixed`](block): Return an equivalent fixed BuildingBlock (i.e., `FixedBlock`, `FixedPulse`, `FixedGradient`, `FixedInstantPulse`, `FixedInstantGradient`, or `InstantReadout`). These all have in common that they have no free variables and explicitly set any gradient and RF pulse profiles.
 - [`variables`](@ref): A list of all functions that are used to compute variables of the building block. Any of these can be used in constraints or objective functions.
 """
 abstract type BuildingBlock end
@@ -68,6 +68,17 @@ Function used internally to convert a wide variety of objects into [`BuildingBlo
 to_block(bb::BuildingBlock) = bb
 
 
+"""
+    fixed(block::BuildingBlock)
+
+Return an equivalent fixed BuildingBlock.
+
+Possible return types are `FixedBlock`, `FixedPulse`, `FixedGradient`, `FixedInstantPulse`, `FixedInstantGradient`, or `InstantReadout`. 
+These all have in common that they have no free variables and explicitly set any gradient and RF pulse profiles.
+"""
+function fixed end
+
+
 """
     scanner_constraints!([model, ]building_block, scanner)
 
diff --git a/src/concrete_blocks.jl b/src/concrete_blocks.jl
deleted file mode 100644
index 19528ac..0000000
--- a/src/concrete_blocks.jl
+++ /dev/null
@@ -1,121 +0,0 @@
-module ConcreteBlocks
-import JuMP: has_values, optimize!, value
-import ..Variables: variables, duration
-import ..BuildingBlocks: BuildingBlock
-
-
-struct ConcreteRFPulse
-    time :: Vector{Number}
-    amplitude :: Vector{Number}
-    phase :: Vector{Number}
-end
-
-function ConcreteRFPulse(arr::Vector)
-    @assert all(length.(arr) .== 3)
-    ConcreteRFPulse(
-        [a[1] for a in arr],
-        [a[2] for a in arr],
-        [a[3] for a in arr],
-    )
-end
-
-ConcreteRFPulse(::Nothing) = nothing
-ConcreteRFPulse(values::Tuple{<:AbstractVector, <:AbstractVector, <:AbstractVector}) = ConcreteRFPulse(values...)
-
-struct ConcreteGradient
-    time :: Vector{Number}
-    Gx :: Vector{Number}
-    Gy :: Vector{Number}
-    Gz :: Vector{Number}
-end
-
-function ConcreteGradient(arr::Vector)
-    if length(arr[1]) == 4
-        @assert all(length.(arr) .== 4)
-        ConcreteGradient(
-            [a[1] for a in arr],
-            [a[2] for a in arr],
-            [a[3] for a in arr],
-            [a[4] for a in arr],
-        )
-    elseif length(arr[1]) == 2
-        @assert all(length.(arr) .== 2)
-        ConcreteGradient(
-            [a[1] for a in arr],
-            [a[2] for a in arr],
-        )
-    else
-        error()
-    end
-end
-
-function ConcreteGradient(times, gradients)
-    ConcreteGradient(
-        times,
-        [g[1] for g in gradients],
-        [g[2] for g in gradients],
-        [g[3] for g in gradients],
-    )
-end
-
-ConcreteGradient(::Nothing) = nothing
-ConcreteGradient(values::Tuple{<:Vector, <:Vector}) = ConcreteGradient(values...)
-ConcreteGradient(values::Tuple{<:Vector, <:Vector, <:Vector, <:Vector}) = ConcreteGradient(values...)
-
-
-abstract type AbstractConcreteBlock <: BuildingBlock end
-
-"""
-    ConcreteBlock(duration; pulse=nothing, gradient=nothing, rotate_bvec=false, readout_times=nothing)
-
-A [`BuildingBlock`](@ref) that is fully defined (i.e., there are no variables to be optimised).
-"""
-struct ConcreteBlock <: AbstractConcreteBlock
-    duration :: Float64
-    pulse :: Union{ConcreteRFPulse, Nothing}
-    gradient :: Union{ConcreteGradient, Nothing}
-    readout_times :: Vector{Float64}
-    rotate_gradient :: Bool
-end
-
-function ConcreteBlock(duration::Number; pulse=nothing, gradient=nothing, readout_times=Number[], rotate_gradient=false)
-    ConcreteBlock(duration, ConcreteRFPulse(pulse), ConcreteGradient(gradient), Float64.(readout_times), rotate_gradient)
-end
-
-
-has_values(c::AbstractConcreteBlock) = true
-duration(c::AbstractConcreteBlock) = 0.
-duration(c::ConcreteBlock) = c.duration
-
-
-"""
-    ConcreteBlock(other_building_block)
-
-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(block::BuildingBlock)
-    if !has_values(block)
-        error("Making `BuildingBlock` objects concrete is only possible after optimisation.")
-    end
-    return to_concrete_block(block)
-end
-
-
-"""
-    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(cb::ConcreteBlock)
-    return ConcreteBlock(cb.duration, cb.pulse, cb.gradient, cb.readout_times, cb.rotate_gradient, value.(cb.start_time))
-end
-
-variables(::Type{<:AbstractConcreteBlock}) = []
-
-
-end
\ No newline at end of file
diff --git a/src/gradients/fixed_gradients.jl b/src/gradients/fixed_gradients.jl
index ee5089e..a1c46fe 100644
--- a/src/gradients/fixed_gradients.jl
+++ b/src/gradients/fixed_gradients.jl
@@ -1,7 +1,7 @@
 module FixedGradients
 
 import ...BuildingBlock: GradientBlock
-import ...Variables: variables, duration
+import ...Variables: variables, duration, qval
 
 
 """
@@ -23,25 +23,27 @@ struct FixedGradient <: GradientBlock
     Gx :: Vector{Float64}
     Gy :: Vector{Float64}
     Gz :: Vector{Float64}
-    function FixedGradient(time::AbstractVector{<:Number}, Gx::AbstractVector{<:Number}, Gy::AbstractVector{<:Number}, Gz::AbstractVector{<:Number})
+    rotate :: Bool
+    function FixedGradient(time::AbstractVector{<:Number}, Gx::AbstractVector{<:Number}, Gy::AbstractVector{<:Number}, Gz::AbstractVector{<:Number}; rotate=false)
         @assert length(time) == length(Gx)
         @assert length(time) == length(Gy)
         @assert length(time) == length(Gz)
-        new(Float64.(time), Float64.(Gx), Float64.(Gy), Float64.(Gz))
+        new(Float64.(time), Float64.(Gx), Float64.(Gy), Float64.(Gz), rotate)
     end
 end
 
-function FixedGradient(time::AbstractVector{<:Number}, arr::AbstractVector{<:AbstractVector{<:Number}}) 
+function FixedGradient(time::AbstractVector{<:Number}, arr::AbstractVector{<:AbstractVector{<:Number}}; kwargs...) 
     @assert all(length.(arr) .== 3)
     FixedGradient(
         time,
         [a[1] for a in arr],
         [a[2] for a in arr],
-        [a[3] for a in arr],
+        [a[3] for a in arr];
+        kwargs...
     )
 end
 
-FixedGradient(time::AbstractVector{<:Number}, Gx::AbstractVector{<:Number}) = FixedGradient(time, Gx, zeros(length(time)), zeros(length(time)))
+FixedGradient(time::AbstractVector{<:Number}, Gx::AbstractVector{<:Number}; kwargs...) = FixedGradient(time, Gx, zeros(length(time)), zeros(length(time)); kwargs...)
 
 variables(::Type{<:FixedGradient}) = []
 
@@ -49,4 +51,20 @@ duration(fg::FixedGradient) = maximum(fg.time)
 
 Base.show(io::IO, fb::FixedGradient) = print(io, "FixedGradient for $(duration(fb)) ms")
 
+"""
+    FixedInstantGradient(orientation, qval)
+
+Instantaneous MR gradient with no free variables.
+"""
+struct FixedInstantGradient <: GradientBlock
+    orientation :: Any
+    qval :: Number
+end
+
+duraction(instant::FixedInstantGradient) = 0.
+qval(instant::FixedInstantGradient) = instant.qval
+
+fixed(f::Union{FixedGradient, FixedInstantGradient}) = f
+
+
 end
\ No newline at end of file
diff --git a/src/gradients/gradients.jl b/src/gradients/gradients.jl
index bde26c8..bdffe1d 100644
--- a/src/gradients/gradients.jl
+++ b/src/gradients/gradients.jl
@@ -9,9 +9,9 @@ Arbitrary gradient waveforms can be store din a [`ConcreteBlock`](@ref)
 """
 module Gradients
 include("integrate_gradients.jl")
+include("fixed_gradients.jl")
 include("pulsed_gradients.jl")
 include("instant_gradients.jl")
-include("fixed_gradients.jl")
 
 import ..BuildingBlock: GradientBlock
 
diff --git a/src/gradients/instant_gradients.jl b/src/gradients/instant_gradients.jl
index 430b594..7b9641c 100644
--- a/src/gradients/instant_gradients.jl
+++ b/src/gradients/instant_gradients.jl
@@ -1,9 +1,10 @@
 module InstantGradients
 import JuMP: @constraint, @variable, Model, owner_model
 import ...Variables: qval, bval, start_time, duration, variables, get_free_variable, VariableType
-import ...BuildingBlocks: GradientBlock
+import ...BuildingBlocks: GradientBlock, fixed
 import ...ConcreteBlocks: to_concrete_block, AbstractConcreteBlock
 import ...BuildSequences: @global_model_constructor
+import ..FixedGradients: FixedInstantGradient
 
 """
     InstantGradientBlock(; orientation=:bvec, qval=nothing)
@@ -40,20 +41,8 @@ bval(instant::InstantGradientBlock) = 0.
 duration(instant::InstantGradientBlock) = 0.
 variables(::Type{<:InstantGradientBlock}) = [qval]
 
-"""
-    ConcreteInstantGradient(orientation, qval)
-
-Instantaneous MR gradient with no free variables.
-
-See [`InstantGradientBlock`](@ref) for a version where [`qval`](@ref) is variable.
-"""
-struct ConcreteInstantGradient <: AbstractConcreteBlock
-    orientation :: Any
-    qval :: Number
-end
-
-function to_concrete_block(block::InstantGradientBlock)
-    return ConcreteInstantGradient(block.orientation, value(qval(block)))
+function fixed(block::InstantGradientBlock)
+    return FixedInstantGradient(block.orientation, value(qval(block)))
 end
 
 
diff --git a/src/gradients/pulsed_gradients.jl b/src/gradients/pulsed_gradients.jl
index 12f4b56..5e34a45 100644
--- a/src/gradients/pulsed_gradients.jl
+++ b/src/gradients/pulsed_gradients.jl
@@ -6,9 +6,10 @@ module PulsedGradients
 import JuMP: @constraint, @variable, Model, VariableRef, owner_model, value
 import StaticArrays: SVector
 import ...Variables: qval, bval, rise_time, flat_time, slew_rate, gradient_strength, variables, duration, δ, get_free_variable, VariableType
-import ...BuildingBlocks: GradientBlock, duration, set_simple_constraints!
+import ...BuildingBlocks: GradientBlock, duration, set_simple_constraints!, fixed
 import ...ConcreteBlocks: ConcreteBlock, to_concrete_block
 import ...BuildSequences: @global_model_constructor
+import ..FixedGradients: FixedGradient
 
 
 """
@@ -83,7 +84,7 @@ end
 variables(::Type{<:PulsedGradient}) = [qval, δ, gradient_strength, duration, rise_time, flat_time, slew_rate]
 
 
-function to_concrete_block(block::PulsedGradient)
+function fixed(block::PulsedGradient)
     if block.orientation == :bvec
         rotate = true
         qvec = [value(qval(block)), 0., 0.]
@@ -98,12 +99,11 @@ function to_concrete_block(block::PulsedGradient)
     end
     t_rise = value(rise_time(block))
     t_d = value(δ(block))
-    return ConcreteBlock(t_d + t_rise, gradient=[
-        (0., zeros(3)),
-        (t_rise, qvec),
-        (t_d, qvec),
-        (t_d + t_rise, zeros(3)),
-    ], rotate_gradient=rotate)
+    return FixedBlock(
+        [0., t_rise, t_d, td + t_rise],
+        [zeros(3), qvec, qvec, zeros(3)]; 
+        rotate=rotate
+    )
 end
 
 
-- 
GitLab