libSBOL  2.3.3
Getting Started with SBOL

This beginner’s guide introduces the basic principles of libSBOL for new users.

This guide is not meant to be a comprehensive documentation of the library or the SBOL standard. Refer to documentation about specific Classes for detailed information about the API. In addition, refer to the specification document for a complete description of the SBOL data standard. For help configuring a client project, see Creating a Visual Studio project on Windows or Creating an XCode project on Mac OSX. For example code see the Sequence Assembly and Biosystem Design tutorials or the example directory of the project source.

Creating an SBOL Document

In a previous era, engineers might sit at a drafting board and draft a design by hand. The engineer's drafting sheet in LibSBOL is called a Document. The Document serves as a container, initially empty, for SBOL data objects. All file I/O operations are performed on the Document to populate it with SBOL objects representing design elements. Usually the first step is to create a Document in which to put your objects. This can be done by calling the Document constructor. The read and write methods are used for reading and writing files in SBOL format.

Document& doc = *new Document();
doc.read("CRISPR_example.xml");
doc.write("CRISPR_example.xml");

Reading a Document will wipe its contents clean before import. However, you can import objects from multiple files into a single Document object using doc.append("file.xml"). This can be advantageous when you want to integrate multiple ComponentDefinitions from multiple files into a single design. (Experienced C++ programmers may find that returning a reference from a new operator is an unusual idiomatic choice. See A Note on our Idiomatic Use of References for a discussion about this stylistic preference in this tutorial.)

A Document may contain different types of SBOL objects, including ComponentDefinitions, ModuleDefinitions, Sequences, SequenceAnnotations, and Models. These objects are collectively referred to as TopLevel objects because they can be referenced directly from a Document. To determine the total number of objects in a Document, use the size method:

cout << doc.size() << endl;

In order to review the ComponentDefinitions contained in a Document:

cout << doc.componentDefinitions.size() << endl;
for( auto & cd : doc.componentDefinitions )
{
cout << cd.identity.get() << endl;
}

Similarly, you can iterate through a Document's moduleDefinitions, sequences, sequenceAnnotations, and models.

Memory management of pointers is encapsulated with the close method. Pointers to a Document and all SBOL objects contained therein may be deleted by calling doc.close().

Creating SBOL Data Objects

Both structural and functional details of biological designs can be described with SBOL data objects. The principle classes for describing the structure and primary sequence of a design are ComponentDefinitions, Components, and Sequences, SequenceAnnotations. The principle classes for describing the function of a design are ModuleDefinitions, Modules, and Interactions. In the official SBOL specification document, these classes and their properties are represented as Unified Modeling Language (UML) diagrams. For example, following is the diagram for a ComponentDefinition which will be referred to in later sections.

component_definition_uml.png

When a new object is created, it must be assigned a unique identity, or uniform resource identifier (URI). A typical URI consists of a scheme, a namespace, and an identifier, although other forms of URI's are allowed. In this tutorial, we use URI's of the type http://sys-bio.org/my_design, where the scheme is indicated by http://, the namespace is sys-bio.org and the identifier is my_design.

Objects can be created by calling their respective constructors. The following constructs a ModuleDefinition:

ModuleDefinition& CRISPRTemplate = *new ModuleDefinition("http://sys-bio.org/CRISPRTemplate");

LibSBOL provides a few global configuration options that make URI construction easy. The first configuration option allows you to specify a default namespace for new object creation. If the default namespace is set, then only an identifier needs to be passed to the constructor. This identifier will be automatically appended to the default namespace. Setting the default namespace is like signing your homework and claims ownership of an object.

setHomespace("http://sys-bio.org");
ModuleDefinition& CRISPRTemplate = *new ModuleDefinition("CRISPRTemplate");

Another configuration option enables automatic construction of SBOL-compliant URIs. These URIs consist of a namespace, an identifier, AND a Maven version number. In addition, SBOL-compliance simplifies autoconstruction of certain types of SBOL objects, as we will see later. LibSBOL operates in SBOL-compliant mode by default. However, some RDF power users will prefer to operate in "open-world" mode and provide the full raw URI when constructing objects. To disable URI construction, SBOL-compliance use toggleSBOLCompliance().

Some constructors have required fields. In the specification document, required fields are indicated as properties with a cardinality of 1 or more. For example, a ComponentDefinition (see the UML diagram above) has only one required field, the type, which specifies the molecular type of a component. Arguments to a constructor are always determined by whether the official SBOL specification document indicates if it is required. Required fields SHOULD be specified when calling a constructor. If they are not, then they will be assigned default values. The following creates a protein component. If the BioPAX term for protein were not specified, then the constructor would create a ComponentDefinition of DNA by default.

ComponentDefinition& Cas9 = *new ComponentDefinition("Cas9", BIOPAX_PROTEIN);

Notice the type is specified using a predefined constant. The ComponentDefinition::type property is one of many SBOL properties that use standard ontology terms as property values. The ComponentDefinition::type property uses the Sequence Ontology to be specific. Many commonly used ontological terms are provided by libSBOL as predefined constants in the constants.h header. See the help page for the sbol::ComponentDefinition class or other specific class to find a table that lists the available terms.

Adding Objects to a Document

In some cases a developer may want to use SBOL objects as intermediate data structures in a computational biology workflow. In this case the user is free to manipulate objects independently of a Document. However, if the user wishes to write out a file with all the information contained in their object, they must first add it to the Document. This is done using a templated add method.

doc.add<ModuleDefinition>(CRISPRTemplate);
doc.add<ComponentDefinition>(Cas9);

Only TopLevel objects need to be added to a Document. These top level objects include ComponentDefinitions, ModuleDefinitions, Sequences, Models. Child objects are automatically associated with the parent object's Document.

Getting, Setting, and Editing Optional Fields

Objects may also include optional fields. These are indicated in UML as properties having a cardinality of 0 or more. Except for the molecular type field, all properties of a ComponentDefinition are optional. Optional properties can only be set after the object is created. The following code creates a DNA component which is designated as a promoter:

ComponentDefinition& TargetPromoter = *new ComponentDefinition("TargetPromoter", BIOPAX_DNA, "1.0.0");
TargetPromoter.roles.set(SO_PROMOTER)

All properties have a set and a get method. To view the value of a property:

cout << TargetPromoter.roles.get() << endl;

This returns the string "http://identifiers.org/so/SO:0000167" which is the Sequence Ontology term for a promoter.

Note also that some properties support a list of values. A property with a cardinality indicated by an asterisk symbol indicates that the property may hold an arbitrary number of values. For example, a ComponentDefinition may be assigned multiple roles. To add a new role:

TargetPromoter.roles.add(SO "0000568");

Creating and Editing Child Objects

Some SBOL objects can be composed into hierarchical parent-child relationships. In the specification diagrams, these relationshipss are indicated by black diamond arrows. In the UML diagram above, the black diamond indicates that ComponentDefinitions are parents of SequenceAnnotations. Properties of this type can be modified using the add method and passing the child object as the argument.

SequenceAnnotation& point_mutation = *new SequenceAnnotation("point_mutation");
TargetPromoter.annotations.add(point_mutation);

If you are operating in SBOL-compliant mode, you may prefer to take a shortcut:

TargetPromoter.annotations.create("point_mutation");

The create method captures the construction and addition of the SequenceAnnotation in a single function call. Another advantage of the create method is the construction of SBOL-compliant URIs. If operating in SBOL-compliant mode, you will almost always want to use the create method. The create method ALWAYS takes one argument–the URI of the new object. All other values are initialized with default values. You can change these values after object creation, however. When operating in open-world mode, it is preferable to follow the first example and use the constructor and add method.

Creating and Editing Reference Properties

Some SBOL objects point to other objects by way of references. For example, ComponentDefinitions point to their corresponding Sequences. Properties of this type should be set with the URI of the related object.

ComponentDefinition& EYFPGene = *new ComponentDefinition("EYFPGene", BIOPAX_DNA);
Sequence& seq = *new Sequence("EYFPSequence", "atgnnntaa", SBOL_ENCODING_IUPAC);
EYFPGene.sequences.set(seq.identity.get());

Iterating and Indexing Lists

Some properties can contain multiple values or objects. As mentioned under Getting, Setting, and Editing Optional Fields additional values can be specified with the add method. In addition you may iterate over lists of objects or values.

// Iterate through objects (black diamond properties in UML)
for( auto & p : Cas9ComplexFormation.participations)
{
cout << p.identity.get() << endl;
cout << p.roles.get() << endl;
}
// Iterate through references (white diamond properties in UML)
for (auto & role : reaction_participant.roles)
{
cout << role << endl;
}

Numerical indexing of lists works as well:

cout << Cas9ComplexFormation.participations[0].identity.get() << endl;

This concludes the basic methods for manipulating SBOL data structures. Now that you're familiar with these basic methods, you are ready to learn about libSBOL's high-level design interface for synthetic biology. See Sequence Assembly.

A Note on our Idiomatic Use of References

In C++ creating a reference to an object using the new operator is a matter of stylistic controversy. For one thing, mixing references with the new operator can make low level memory management confusing for some developers. The reason this isn't an issue here is because libSBOL encapsulates memory management within the Document object. Pointers to all the objects in a Document are freed when the Document::close method is called. Coming from a Python background to C++, we wanted to maximize the object-oriented experience using the library and minimize the use of pointers. Rest assured however, that if you disagree with this idiomatic style, you can still work with pointers in the more traditional way.

Creating an XCode project on Mac OSX

The libSBOL repository contains example Xcode (for Mac) and Visual Studio (for Windows) project files. In Xcode open the example.xcodeproj file included in the example/Xcode directory. This project assumes that you have already installed libSBOL via an installer executable or have run make install, assuming you have built libSBOL from source. (See Introduction for build instructions) Running make install places the headers in /usr/local/include/sbol/ and the library in /usr/local/lib. This project targets OS 10.9 and later. If you have an older system you will have to change the Build Settings > Deployment > OS X Deployment Target (eg, from 10.9 to 10.8) depending on your system version

If you have installed libSBOL to a custom location and you would like to configure your Xcode project from scratch:

#include "sbol.h"
using namespace sbol;

Now link and run your client application:

$ g++ -std=c++11 -I../source -I/usr/local/include/raptor2 -L/usr/local/lib -lraptor2 -ljsoncpp -lcurl -lsbol client_app.cpp -o client_app
$ ./client_app

Here, -I flags specify paths to include folders with necessary header files. -L flag specifies path to the libSBOL library. -o flag specifies the file name of the output. Change the values according to your setup.

Creating a Visual Studio project on Windows

It is strongly recommended to use Visual Studio 2015

1) RUN THE INSTALLER

2) CREATE A NEW PROJECT IN VISUAL STUDIO

3) SPECIFY DEPENDENCIES

4) INCLUDE SBOL HEADER AND NAMESPACE IN THE CLIENT APP

#include "sbol.h"
using namespace sbol;

5) If you want to build using 64 bit libraries, make sure you target 'x64' and follow the above steps.