Types

abstract type AbstractAnimal end
abstract type AbstractPrimate <: AbstractAnimal end
abstract type AbstractHuman <: AbstractPrimate end
struct Person <: AbstractHuman end

The generalised framework for this package leans heavily on Julia's type system.

Primitive Types

The more primitive a man is the better he believes himself to be.

  • Erich Maria Remarque on idiots

The abstract progression types let users create specialised functions.

Lifting.AbstractProgressionType
abstract type AbstractProgression end
struct LinearProgression <: AbstractProgression end
struct DoubleProgression <: AbstractProgression end
struct PeriodProgression <: AbstractProgression end
struct BlockProgression <: AbstractProgression end

Progression types. Use these to make specialised functions and add new functionality.

source

Structures

Ideas that require people to reorganize their picture of the world provoke hostility.

  • James Gleick on organising things

In order to create programmes we need to create a set scheme. This is done via the SetScheme structure.

Lifting.SetSchemeType
mutable struct SetScheme{
    T1 <: Union{<:AbstractString, Vector{<:AbstractString}},
    T2 <: Union{<:Integer, Vector{<:Integer}},
    T3 <: Union{<:Real, Vector{<:Real}},
    T4 <: Union{<:Function, Vector{<:Function}},
    T5 <: Bool,
}
    type::T1
    sets::T2
    reps::T2
    intensity::T3
    rpe::T3
    addWeight::T3
    roundMode::T4
    wght::T3
    rpeMode::T5
end

This structure contains user-created set schemes. Its parameters are as follows:

  • type defines the set type,
  • sets the number of sets of the corresponding type,
  • reps is the number of reps in the set,
  • intensity is the set intensity,
  • rpe is the set's target RPE,
  • addWeight is extra weight on top of the normally calculated weight (useful for chains, bands or microplates),
  • wght is the set weight, which is calculated later when pairing progression schemes to exercises,
  • rpeMode whether the set scheme is defined via RPE or percentage intensity, if true, the values in intensity will be assumed to be RPE and the percentage intensity will be calculated using RPE and the number of reps in the set, else the values in intensity will be used as provided.

It uses the following constructor, which takes care of any required computations.

SetScheme(;
    type::T1 = "Default",
    sets::T2 = 5,
    reps::T2 = 5,
    intensity::T3 = 0.75,
    addWeight::T4 = 0,
    roundMode::T5 = floor,
    rpeMode::Bool = false,
) where {
    T1 <: Union{<:AbstractString, Vector{<:AbstractString}},
    T2 <: Union{<:Integer, Vector{<:Integer}},
    T3 <: Union{<:Real, Vector{<:Real}},
    T4 <: Union{<:Real, Vector{<:Real}},
    T5 <: Union{<:Function, Vector{<:Function}},
}

Every parameter has defaults, so users have the ability to provide only the ones they want to modify.

If rpeMode == true, intensity is based on RPE, which lies in the interval $x ∈ (0, 10]$. If rpeMode == false, then intensity is relative to training max and therefore lies in the interval $x ∈ [0, 1]$. The lenghts of all input vectors must be equal.

Example

Here we define a simple set scheme. Since we are not providing any added weight via addWeight, that parameter becomes a zero vector.

julia> SampleScheme = SetScheme(;
           type = ["Long Rest", "Longer Rest", "Longest Rest", "Optional Forced Reps"],
           sets = [1, 2, 1, 1],
           reps = [12, 14, 10, 5],
           intensity = [9.5, 10, 10, 10],
           roundMode = [floor, floor, ceil, ceil],
           rpeMode = true,
       )
SetScheme{Array{String,1},Array{Int64,1},Array{Float64,1},Array{Function,1},Bool}(["Long Rest", "Longer Rest", "Longest Rest", "Optional Forced Reps"], [1, 2, 1, 1], [12, 14, 10, 5], [0.6538806237677003, 0.6275409806672554, 0.7041013906002465, 0.8309098462816784], [9.5, 10.0, 10.0, 10.0], [0.0, 0.0, 0.0, 0.0], Function[floor, floor, ceil, ceil], [0.0, 0.0, 0.0, 0.0], true)
source

After creating our desired set schemes we use them to create a progression.

Lifting.ProgressionType
struct Progression{
    T1 <: AbstractProgression,
    T2 <: AbstractString,
    T3 <: Integer,
    T4 <: Union{<:SetScheme, Vector{<:SetScheme}},
} <: AbstractProgression
    type::T1
    name::T2
    sessions::T3
    period::T3
    setScheme::T4
end

This structure holds user-defined progressions. Its parameters are as follows:

  • type is the progression type which is a subtype of AbstractProgression,
  • name is the progression name,
  • sessions is the number of different sessions in a week,
  • period is the progression period,
  • setScheme is a previously defined set scheme or vector of set schemes, the length must be equal to sessions * period.

This structure lets users create different types of progressions, from simple day in day out progressions to complex undulating periodisation and block progressions.

Progressions are created with the following constructor function,

function Progression(;
    type::T1,
    name::T2,
    sessions::T3 = 1,
    period::T3 = 1,
    setScheme::T4,
) where {
    T1 <: AbstractProgression,
    T2 <: AbstractString,
    T3 <: Integer,
    T4 <: Union{<:SetScheme, Vector{<:SetScheme}},
}

Example

Assuming we've run the example in SetScheme we can create a simple progression.

julia> SampleProgression = Progression(;
           type = LinearProgression(),
           name = "Progression Name",
           setScheme = SampleScheme,
       )
Progression{LinearProgression,String,Int64,SetScheme{Array{String,1},Array{Int64,1},Array{Float64,1},Array{Function,1},Bool}}(LinearProgression(), "Progression Name", 1, 1, SetScheme{Array{String,1},Array{Int64,1},Array{Float64,1},Array{Function,1},Bool}(["Long Rest", "Longer Rest", "Longest Rest", "Optional Forced Reps"], [1, 2, 1, 1], [12, 14, 10, 5], [0.6538806237677003, 0.6275409806672554, 0.7041013906002465, 0.8309098462816784], [9.5, 10.0, 10.0, 10.0], [0.0, 0.0, 0.0, 0.0], Function[floor, floor, ceil, ceil], [0.0, 0.0, 0.0, 0.0], true))
source

We also need to create an exercise.

Lifting.ExerciseType
mutable struct Exercise{
    T1 <: AbstractString,
    T2 <: Union{AbstractString, Vector{<:AbstractString}},
    T3 <: Union{AbstractString, Vector{<:AbstractString}},
    T4 <: Union{AbstractString, Vector{<:AbstractString}},
    T5 <: Union{AbstractString, Vector{<:AbstractString}},
    T6 <: Real,
    T7 <: Real,
    T8 <: Function,
}
    name::T1
    equipment::T2
    modality::T3
    size::T4
    muscles::T5
    trainingMax::T6
    roundBase::T7
    roundMode::T8
end

User created exercises are encapsulated in this structure. Its parameters are as follows:

  • name is the exercise name,
  • equipment is the equipment required,
  • modality are any modifiers to the exercise (for example blocks),
  • size are the size/resistance of said modifiers (for example 2 inch),
  • muscles are the muscles targeted,
  • trainingMax is the training maximum used to calculate set weights when pairing with a progression,
  • roundBase is the base to which set weights are rounded,
  • roundMode is the rounding function used.

This uses the following constructor which provides default values for anything users don't need/want to track,

function Exercise(;
    name::T1,
    equipment::T2 = "NA",
    modality::T3 = "Default",
    muscles::T4 = "NA",
    trainingMax::T6 = 0,
    size::T5 = "NA",
    roundBase::T7 = 2.5,
    roundMode::T8 = floor,
) where {
    T1 <: AbstractString,
    T2 <: Union{AbstractString, Vector{<:AbstractString}},
    T3 <: Union{AbstractString, Vector{<:AbstractString}},
    T4 <: Union{AbstractString, Vector{<:AbstractString}},
    T5 <: Union{AbstractString, Vector{<:AbstractString}},
    T6 <: Real,
    T7 <: Real,
    T8 <: Function,
}
end

Examples

Say we want to bench press off a 1 inch block and a semi supinated grip on a swiss bar. We can easily create this exercise

julia> BenchAccessory = Exercise(;
           name = "Swiss Bar Bench",
           modality = ["Block" , "Semi-Supinated"],
           equipment = "Swiss Bar",
           size = "1 inch",
           muscles = ["Pecs", "Triceps", "Front Delts"],
           trainingMax = 100,
           roundBase = 2.5,
           roundMode = floor,
       )
Exercise{String,String,Array{String,1},String,Array{String,1},Float64,Float64,typeof(floor)}("Swiss Bar Bench", "Swiss Bar", ["Block", "Semi-Supinated"], "1 inch", ["Pecs", "Triceps", "Front Delts"], 100.0, 2.5, floor)
source

Finally, we create a programme using the following structure.

Lifting.ProgrammeType
struct Programme{
    T1 <: AbstractProgramme,
    T2 <: AbstractString,
    T3 <: Dict{Any, Any},
    T4 <: Vector{Any},
}
    type::T1
    name::T2
    exerProg::T3
    days::T4
end

User created programmes are made with this structure. The parameters are as follows:

  • type is the exercise type and is a subtype of AbstractProgramme (we recommend users define custom concrete types for their programmes),
  • name is the programme name (we recommend the name is the same as the programme type),
  • exerProg is a dictionary which pairs exercises and progressions with the key-value pair name => (exercise, progression).
  • days is the programme schedule where each entry in the vector has all the exercises for the day.

Each programme is different so the recommendation is to create a typed makeDays function for the programme type.

Warning

makeDays must be explicitly imported either with import Lifting: makeDays or Lifting.makeDays for it to be automatically used in updateMaxes!.

Examples

Assuming we are using the previously defined Exercise and Progression, we can create a programme. There are a few ways to do so but this is recommended.

# Define the programme's concrete type. Can be used to extend `makeDays`.
struct SampleProgramme <: AbstractProgramme end

# Define a dictionary that will contain an Exercise and its corresponding
# progression as a tuple or vector.
exerProg = Dict()

# Push exercises and progressions to dictionary. Using named tuples is
# recommended because it enables dot syntax as well as regular indexing.
# Each exercise is paired with its progression.
push!(
    exerProg,
    "BenchAccessory" => (exercise = BenchAccessory, progression = SampleProgression),
)

# Define the a function that creates the schedule. This lets you automatically
# update training maxes and makes changing the programme easier. Here we also
# define rest days but you can leave them out if need be. `makeDays` will be
# called for whatever type is given as its first argument. Different programme
# types will necessarily have and call different `makeDays` functions.
import Lifting: makeDays
function makeDays(::SampleProgramme, exerProg)

    # One sub array per day you want to track.
    week = [[], [], [], [], [], [], []]

    # You can 'unroll' the dictionary contents or push them directly to your
    # schedule. If using named tuples dot syntax and normal numeric indexing
    # both work
    # exerProg["BenchAccessory"].exercise == exerProg["BenchAccessory"][1]
    BenchAccessory = exerProg["BenchAccessory"].exercise
    BenchAccessoryProg = exerProg["BenchAccessory"].progression


    # Push the exercise and progression to whichever day of the week you want.
    # If the progression has multiple set schemes, the corresponding scheme can
    # be accessed by adding the index as the last argument.
    # push!(week[1], BenchAccessory, BenchAccessoryProg, 2)
    # Which would fail because there is only one set scheme, but if
    # BenchAccessoryProg were to have more entries that would take the second.
    # push! is overloaded so it also automatically calculates the required
    # weights. You can do this manually but this is easier and more reliable.
    push!(week[1], BenchAccessory, BenchAccessoryProg)   # Day 1
    push!(week[2], "Rest")  # Day 2
    push!(week[3], BenchAccessory, BenchAccessoryProg)   # Day 3
    push!(week[4], "Rest")  # Day 4
    push!(week[5], BenchAccessory, BenchAccessoryProg)   # Day 5
    push!(week[5], "Rest")  # Day 6
    push!(week[7], "Rest")  # Day 7

    return week
end

# Create weekly schedule.
week = makeDays(SampleProgramme(), exerProg)

# Create programme.
sampleProgramme = Programme(
                            SampleProgramme(),
                            "SampleProgramme",
                            exerProg,
                            week
                        )

It's recommended users either import the default dictionary Lifting_Programmes and store their programmes there with key equal to the programme name and type. They can also create their own dictionary for their own creations.

source