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 :doc:`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. .. code-block:: python 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() .. code-block:: python 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 . .. code-block:: python 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 :doc:`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) .. code-block:: python from labgraph import Sample sample = Sample.get(sample_id) #sample_id is the ObjectID of the sample 2. 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. .. code-block:: python 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. .. code-block:: python 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), ) 3. When you have a single node, and you want to retrieve `Sample`s that contain that node .. code-block:: python 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 .. code-block:: python 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))