Quickstart

This tutorial is a quick introduction to entering and retrieving data using Labgraph. We assume you already installed Labgraph, installed MongoDB, and created a config file to link Labgraph to MongoDB. If you have not done this, please see the installation guide. Throughout this tutorial we will assume you are working in a lab that performs solid state synthesis (grind powders, heat them, grind them, measure them).

Defining your Lab: Actors and AnalysisMethods

All steps you perform in your lab – Actions, Measurements, and Analyses – are linked to an Actor or AnalysisMethod. You should define these one time when you are setting up Labgraph for your lab.

from labgraph import Actor

mortar_and_pestle = Actor(
    name='mortar and pestle',
    description='Grinds powders into smaller powders.',
    tags=['grinding', 'powder', 'solid state synthesis'], #tags are optional, but they are useful for searching for actors later
)
mortar_and_pestle.save() # saves the actor to the database

furnace = Actor(
    name="box furnace",
    description="Heats things to high temperatures.",
    tags=['heating', 'solid state synthesis'],
)
furnace.save()

diffractometer = Actor(
    name="Rigaku Smartlab",
    description="Measures the diffraction pattern of a sample. This diffractometer is manufactured by Rigaku and is a Smartlab model.",
    tags=['XRD', 'diffraction', 'solid state synthesis'],
)
diffractometer.save()
from labgraph import AnalysisMethod

phase_identification = AnalysisMethod(
    name="phase identification",
    description="Given a diffraction measurement, identifies the material phases present in the sample.",
    tags=['XRD', 'diffraction', 'solid state synthesis'],
)
phase_identification.save()

Now you can create Actions, Measurements, or Analysis nodes that use these Actors or AnalysisMethods.

Adding a Sample to your Database

In Labgraph, a Sample is a subgraph of nodes in the total Labgraph that are associated with a single process within your lab. We usually enter data as part of a Sample object. The simplest scenario is a “linear process” – a set of Action`s that follow one another. Let’s make a `Sample for a simple linear solid-state synthesis experiment where we 1) grind up table salt, 2) heat it, 3) grind it again, 4) take a diffraction measurement, then 5) analyze the diffraction measurement .

from labgraph import Sample, Material, Action, Ingredient, WholeIngredient, Actor, AnalysisMethod

# define your starting material

starting_material = Material(
    name="NaCl",
)

mortar_and_pestle = Actor.get_by_name(name='mortar and pestle') #get the actor we defined earlier
grinding = Action(
    name="grinding",
    ingredients=[
        Ingredient(
            material=starting_material,
            quantity=1,
            units="g",
        ),
    ]
    actor=mortar_and_pestle,
)
ground_material = Material(
    name="ground NaCl",
    ## your metadata
    description="NaCl ground up in a mortar and pestle.", # we can add optional metadata fields to any node. this is where you decide what information you need to store!
)
grinding.add_generated_material(ground_material) # indicate that this material was generated by the grinding action


furnace = Actor.get_by_name(name='box furnace')
heating = Action(
    name="heating",
    ingredients=[
        WholeIngredient(material=ground_material), #shortcut Ingredient that says 100% of the material was used
    ],
    actor=furnace,
    ## your metadata
    heating_temperature_celsius = 1000,
    heating_duration_minutes = 30,
)
heated_material = heating.make_generic_generated_material() # make a generic material node to represent the material that was generated by the heating action. We don't need to manually create material nodes to bridge actions together if we don't want to.

grinding_after_heating = Action(
    name="grinding",
    ingredients=[
        WholeIngredient(material=heated_material),
    ],
    actor=mortar_and_pestle,
    ## your metadata
    grinding_duration_minutes = 10
)
ground_material_after_heating = grinding_after_heating.make_generic_generated_material()

diffractometer = Actor.get_by_name(name='Rigaku Smartlab')
xrd_measurement = Measurement(
    name="XRD measurement",
    material=ground_material_after_heating, #a Measurement always acts upon a Material node!
    actor=diffractometer,
    ## your metadata
    scan_parameters = {
    "twotheta": [10,20,30],
    "dwell_time_ms": 100,
    },
    results = {
    "twotheta": [10.01, 20.13, 29.95],
    "counts" : [100, 200, 75],
    },
)

phase_identification = AnalysisMethod.get_by_name(name='phase identification')
xrd_analysis = Analysis(
    name="XRD analysis",
    measurements=[xrd_measurement], #an analysis acts upon Measurement(s) and/or Analysis(s)
    upstream_analyses = [], #we didnt use any earlier analyses to perform this Analysis, but if we did the Analysis objects would go here.
    analysis_method=phase_identification,
    ## your metadata
    results = {
    "phase": ["NaCl", "NaOH"],
    "volume_fraction": [1.0, 0.0],
    },
)

sample = Sample(
    name="sample 1",
    description="Heating NaCl and measuring the resulting material phase.",
    tags=['solid state synthesis', 'XRD', 'salty'], #tags are optional, but they are useful for searching for samples later
    nodes=[
        starting_material,
        grinding,
        ground_material,
        heating,
        heated_material,
        grinding_after_heating,
        ground_material_after_heating,
        xrd_measurement,
        xrd_analysis,
    ],
    ## your metadata
    performed_by="Rishi Kumar"
)
sample.save() #save the sample to the database

When you run sample.save(), a few things happen. First, labgraph will ensure that this Sample object is a valid directed acyclic graph (DAG). Second, labgraph will ensure that the addition of this Sample and its nodes will not violate any existing DAG structures in the database. Assuming these conditions are met, labgraph will all of the individual nodes to the database, then save the overarching Sample to the database. Please refer to entering data for a deeper explanation of this behavior and guidance on how to save individual nodes to the database using node.save().

Retrieving Data

We can retrieve individual nodes or Samples from the database. In fact, we retrieved Actor and AnalysisMethod nodes in the example above. Let’s retrieve the Sample we just created.

  1. When you know the unique identifier (ObjectID)

from labgraph import Sample

sample = Sample.get(sample_id) #sample_id is the ObjectID of the sample
  1. When you do not know the ObjectID of your node, but you have some other identifying information

Most of the time you will not know the ObjectID of your node/Sample. You can search for nodes by name, tags, or content using class methods. Note that these methods will return a list of samples – other than ObjectId, none of these fields are unique, so multiple samples may match your query.

from labgraph import Sample

sample = Sample.get_by_name(name="sample 1")

#or

sample = Sample.get_by_tags(tags=['solid state synthesis', 'XRD', 'salty'])

#or

sample = Sample.get_by_contents(contents={'performed_by': 'Rishi Kumar'})

Finally, you can perform more sophisticated queries using the “filter” method. This allows you to make a typical MongoDB find query, and additionally to filter results by datetime. The example below will return all samples with a “performed_by” field equal to “Rishi Kumar” that were created in the first week of 2020.

from labgraph import Sample

sample = Sample.filter(
    filter_dict={'performed_by': 'Rishi Kumar'},
    datetime_min=datetime(2020, 1, 1),
    datetime_max=datetime(2020, 1, 7),
)
  1. When you have a single node, and you want to retrieve `Sample`s that contain that node

from labgraph import Sample, Material

starting_materials = Material.get_by_name(name="NaCl") #returns list of Material nodes with name "NaCl"

samples = Sample.get_by_node(node=starting_materials[0]) #returns list of Samples that contain this Material node.

Often we want to retrive groups of data that match some criteria. You can do this by composing the methods above

from labgraph import Sample, Material

starting_materials = Material.get_by_name(name="NaCl") #returns list of Material nodes with name "NaCl"
all_samples = set()
for material in Material.get_by_name(name="NaCl"):
    all_samples.update(Sample.get_by_node(node=material))