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)