Aliquot Code Walkthrough

This walkthrough is intended for readers who have an intermediate or expert level of coding knowledge. For an overview on how to use Aliquot in the workflow editor, see this guide.

About this tutorial

This walkthrough will go through each of the 10 sections within an Antha element and describe what each part does. To follow along, you can either copy each code block into your editor of choice or download the final file from here.

Things you'll need

Nothing special is needed to use this lesson.

What you'll learn

After completing this lesson, you will learn the following:

  • The different sections within an Antha elements
  • Why some sections are empty
  • The difference between the types of information which comes into and goes out of an element
  • Basic functions used in an Aliquot element

The basics

Antha element files are comprised of 11 essential parts.

  • The element name
  • import
  • Parameters
  • Data
  • Inputs
  • Outputs
  • Requirements
  • Setup
  • Steps
  • Analysis
  • Validation

The element name

Every element in the Antha environment needs a unique element name. For this element, the name is "Aliquot".

Good names are simple, descriptive, and must begin with a capital letter. An element name must also be an unbroken sequence of characters, but does not need to match the file name, though it is better if it does. To designate the name of an element, use the word "protocol" immediately before it.

1
protocol Aliquot

import

This element imports three different libraries. They are wtype, wutil, and mixer.

Element imports can be either Antha elements or Go packages.

1
2
3
4
5
import (
    "github.com/antha-lang/antha/antha/anthalib/wtype"
    "github.com/antha-lang/antha/antha/anthalib/wutil"
    "github.com/antha-lang/antha/antha/anthalib/mixer"
)

Parameters

The Aliquot element takes six pieces of data as its Parameters. Parameters are the non-physical inputs for an element, like temperature, duration, or volume. The physical inputs for an element are described in the Inputs block.

Parameter types can be either basic types, such as bool, int, or string, or complex types, like Volume. Basic types will be lower case, complex types will be upper case. A full list of valid Antha-specific parameter types can be found in the wunit library.

All names in the Parameters block must begin with a capital letter.

1
2
3
4
5
6
7
8
Parameters (
    SolutionVolume Volume
    VolumePerAliquot  Volume
    NumberofAliquots int
    PreMix bool // optional field. Select if the solution to be aliquoted should be premixed prior to transfer
    ChangeSolutionName string // optional field to change the name of the component
    OptimisePlateUsage bool
)

SolutionVolume

This parameter is the total volume of the solution you wish to aliquot, usually in ul.

VolumePerAliquot

This parameter is the volume of each aliquot you are trying to make, usually in ul. It is used as a check to ensure that there is enough liquid in your solution to make all the aliquots.

NumberofAliquots

This parameter is the desired number of aliquots to make. It is unitless.

PreMix

This parameter is an optional field which determines whether the initial solution should me mixed before aliquoting. You might want to set this to "true" if you have a solution which tends to separate into different phases when left to sit for any length of time.

ChangeSolutionName

This parameter is an optional field which lets the user say both whether to change the Solution CName and what to change it to. You might want to use this if you have many elements using lots of solutions with the same name. For instance, if you are aliquoting many water samples, it may be helpful for your own record keeping to rename them to be "Water1," "Water2," etc.

OptimisePlateUsage

This parameter is used to determine whether MixNamed (OptimisePlateUsage = true) or MixInto (OptimisePlateUsage = false) will be used when aliquoting your solution. When MixNamed is used, all aliquots will be sent to a plate named "AliquotPlate".

Data

The Data block of an Antha element defines the information produced by the element as a data output. These include things like final sample volume, number of aliquots performed, or thaw-time required.

For this Aliquot element, there are no data outputs, so this block is empty. However, it is important to still include it in the element file or else it won't build properly.

1
2
3
Data (

)

Inputs

The Inputs block of an element file defines the physical materials required by the element. These include things like solution sample, DNA part, or multi-well plate. Inputs are also declared in the parameters file, but as specific values. Here, the Inputs are "types" only.

All names in the Inputs block must begin with a capital letter.

1
2
3
4
Inputs (
    Solution *wtype.LHComponent
    OutPlate *wtype.LHPlate // this time we're specifying what plate we're using
)

Solution

This input is the liquid from which you wish to make aliquots. It has the type *wtype.LHComponent, which means that it is a valid liquid in the Antha environment. The asterisk indicates that it is a pointer to the type which means that it adopts the properties of the wtype.LHComponent type without making a copy of it.

OutPlate

This input is the plate that will be used to put the aliquots made during the execution of this element.

Outputs

The Outputs section lists the physical things that are generated by an element.

All names in the Outputs block must begin with a capital letter.

1
2
3
Outputs (
    Aliquots []*wtype.LHComponent
)

Aliquots

This output is an array of *wtype.LHComponent liquids. The aliquots will be placed in the OutPlate.

Requirements

The Requirements block describes checks that the user wants to be in place before the element runs. For instance, if you only want to allow plates from a certain manufacturer, you would put that requirement here. Currently the Requirements section is not supported in Antha but will be soon. Therefore the Requirements block here is empty.

1
2
3
Requirements {

}

Setup

The Setup block is performed once the first time that an element is executed. This can be used to perform any configuration that is needed globally for the element, and is also used to define any special setup that may be needed for groups of concurrent tasks that might be executed at the same time. Any variables that need to be accessed by the Steps function globally can be defined here as well, but need to be handled with care to avoid concurrency problems.

At this current time, the Setup block is not supported by Antha, but will be in future releases.

1
2
3
4
// Conditions to run on startup
Setup {

}

Steps

The heart of an Antha element is the Steps block, which defines the actual steps taken to transform a set of input parameters and samples into the output data and samples. The Steps are a kernel function, meaning they share no information for every concurrent sample that is processed, and define the workflow to transform a single block of inputs and samples into a single set of outputs, even if the element is operating on an entire array (such as micro-titre plate of samples at once).

Typically the Steps block is the longest block of the entire element.

Breaking down the Steps block

The Steps block is always introduced with the word "Steps" and a curly brace.

1
Steps {

First we're going to check that the number of Aliquots required is possible with the Aliquot volume and the solution volume. If there isn't enough volume in the solution, the system will throw an error.

1
2
3
4
5
    number := SolutionVolume.SIValue()/VolumePerAliquot.SIValue()
    possiblenumberofAliquots, _ := wutil.RoundDown(number)
    if possiblenumberofAliquots < NumberofAliquots {
        Errorf("Not enough solution for this many aliquots")
    }

Next we're going to check our optional parameters PreMix and ChangeSolutionName and perform actions as appropriate.

1
2
3
4
5
6
7
    if PreMix {
        Solution.Type = wtype.LTPreMix
    }

    if ChangeSolutionName != ""{
        Solution.CName = ChangeSolutionName
    }

Then we instantiate a variable which represents the aliquots we're making. We do so with the make command and set it equal to an empty array of pointers to the LHComponent type.

1
    aliquots := make([]*wtype.LHComponent,0)

Now that we have a place to put the aliquots as we make them, we can set instructions for pulling aliquots out of the solution. We do this with a basic for loop.

1
    for i := 0; i < NumberofAliquots; i++ {

For each aliquot we want to make, we will do some checks and perform some operations. The first thing we want to do is to check whether the solution being aliquoted is DNA. If it is, we want to add a line of code which tells the system not to mix the solution. We do this by setting the solution type to wtype.LTDoNotMix. If the user has specified that the parameter PreMix be true, then this line of code will overwrite that specification.

1
2
3
        if Solution.TypeName() == "dna"{
            Solution.Type = wtype.LTDoNotMix
        }

Then we make a new aliquot from the sample solution by calling the Sample method from the mixer library.

1
        aliquotSample := mixer.Sample(Solution, VolumePerAliquot)

After pulling an aliquot from the solution, we need to put it into a plate. Depending on whether the parameter OptimisePlateUsage has been set to true, we will use either the method MixNamed (if it has) or MixInto (if it has not).

1
2
3
4
5
6
        var aliquot *wtype.LHComponent
        if OptimisePlateUsage {
            aliquot = MixNamed(OutPlate.Type, "","AliquotPlate", aliquotSample)
        } else {
            aliquot = MixInto(OutPlate, "", aliquotSample)
        }

After the aliquot variable has been set, we add it to our aliquot array and close the for loop with another curly brace. Once the for loop finishes, we set the Outputs variable Aliquots (with a capital "A") equal to the temporary variable aliquots. Doing this ensures that the information about our new elements can be accessed outside of this element. We then close the Steps block with a final curly brace.

1
2
3
4
5
6
7
8
        if aliquot != nil {
            aliquots = append(aliquots,aliquot)
        }
    }

    Aliquots = aliquots

}

The full Steps block

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Steps {
    number := SolutionVolume.SIValue()/VolumePerAliquot.SIValue()
    possiblenumberofAliquots, _ := wutil.RoundDown(number)
    if possiblenumberofAliquots < NumberofAliquots {
        Errorf("Not enough solution for this many aliquots")
    }

    if PreMix {
        Solution.Type = wtype.LTPreMix
    }

    if ChangeSolutionName != ""{
        Solution.CName = ChangeSolutionName
    }

    aliquots := make([]*wtype.LHComponent,0)


    for i := 0; i < NumberofAliquots; i++ {
        if Solution.TypeName() == "dna"{
        Solution.Type = wtype.LTDoNotMix
        }
        aliquotSample := mixer.Sample(Solution, VolumePerAliquot)
        var aliquot *wtype.LHComponent
        if OptimisePlateUsage{
        aliquot = MixNamed(OutPlate.Type, "","AliquotPlate", aliquotSample)
        }else{
        aliquot = MixInto(OutPlate, "", aliquotSample)
        }
        if aliquot != nil{
                aliquots = append(aliquots,aliquot)
            }
    }
    Aliquots = aliquots
}

Analysis

The "Analysis" block defines how the results of the Steps can should be transformed into final values, if appropriate. Computing the final protein concentration of a Sample assay requires having the data back from the control samples, performing a linear regression, and then using those results to normalize the plate reader results.

For this Aliquot element, there is no analysis, so this block is empty. However, it is important to still include it in the element file or else it won't build properly.

1
2
3
Analysis {

}

Validation

The Validation block allows the definition of specific tests to verify the correct execution of an element, along with reporting capabilities (and the ability to declare the execution a failure). For example, the Sample assay can only handle a specific linear range of concentrations, so if the amount of protein in the sample is above or below that range, the assay will fail.

For this Aliquot element, there is no validation needed, so this block is empty. However, it is important to still include it in the element file or else it won't build properly.

1
2
3
4
5
6
// A block of tests to perform to validate that the sample was processed
//correctly. Optionally, destructive tests can be performed to validate
//results on a dipstick basis
Validation {

}

The full code

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
// Aliquot a solution into a specified plate.
// optionally premix the solution before aliquoting
protocol Aliquot

import (
    "github.com/antha-lang/antha/antha/anthalib/wtype"
    "github.com/antha-lang/antha/antha/anthalib/wutil"
    "github.com/antha-lang/antha/antha/anthalib/mixer"
)


// Input parameters for this protocol (data)
Parameters (
    SolutionVolume Volume
    VolumePerAliquot  Volume
    NumberofAliquots int
    PreMix bool // optional field. Select if the solution to be aliquoted should be premixed prior to transfer
    ChangeSolutionName string // optional field to change the name of the component
    OptimisePlateUsage bool
)

// Data which is returned from this protocol, and data types
Data (

)


// Physical Inputs to this protocol with types
Inputs (
    Solution *wtype.LHComponent
    OutPlate *wtype.LHPlate // this time we're specifying what plate we're using

)

// Physical outputs from this protocol with types
Outputs (
    Aliquots []*wtype.LHComponent
)

Requirements {

}

// Conditions to run on startup
Setup {

}

// The core process for this protocol, with the steps to be performed
// for every input
Steps {

    number := SolutionVolume.SIValue()/VolumePerAliquot.SIValue()
    possiblenumberofAliquots, _ := wutil.RoundDown(number)
    if possiblenumberofAliquots < NumberofAliquots {
        Errorf("Not enough solution for this many aliquots")
    }

    // if PreMix is selected change liquid type accordingly
    if PreMix {
        Solution.Type = wtype.LTPreMix
    }

    // if a solution name is given change the name
    if ChangeSolutionName != ""{
        Solution.CName = ChangeSolutionName
    }

    aliquots := make([]*wtype.LHComponent,0)


    for i := 0; i < NumberofAliquots; i++ {
        if Solution.TypeName() == "dna"{
        Solution.Type = wtype.LTDoNotMix
        }
        aliquotSample := mixer.Sample(Solution, VolumePerAliquot)

        // the MixInto command is used instead of Mix to specify the plate
        // MixInto allows you to specify the exact plate to MixInto (i.e. rather than just a plate type. e.g. barcode 123214234)
        // the three input fields to the MixInto command represent
        // 1\. the plate
        // 2\. well location as a  string e.g. "A1" (in this case leaving it blank "" will leave the well location up to the scheduler),
        // 3\. the sample or array of samples to be mixed
        var aliquot *wtype.LHComponent
        if OptimisePlateUsage{
        aliquot = MixNamed(OutPlate.Type, "","AliquotPlate", aliquotSample)
        }else{
        aliquot = MixInto(OutPlate, "", aliquotSample)
        }
        if aliquot != nil{
                aliquots = append(aliquots,aliquot)
            }
    }
    Aliquots = aliquots
}
// Run after controls and a steps block are completed to
// post process any data and provide downstream results
Analysis {
}

// A block of tests to perform to validate that the sample was processed
//correctly. Optionally, destructive tests can be performed to validate
//results on a dipstick basis
Validation {

}