Environmental Attributes & Inheritance
A location has an environment (temperature, humidity, and so on) as well as a place in the hierarchy. The environment flows down the hierarchy the way physical containment does.
The examples on this page use a room holding CHESS's Incubator, which has three shelves, with a plate on the first shelf:
julia> room = build_location(loc"Room", "Room A");
julia> incubator = build_location(loc"Incubator", "Incubator");
julia> shelf = incubator[1];
julia> plate = build_location(loc"WP96", "Plate 1");
julia> move_into!(room, incubator)
julia> move_into!(shelf, plate)Defining new attribute kinds
A location's environment is made up of attribute kinds, registered with the @attribute macro. Like location kinds, each is a named constant plus a registry entry, recalled by name. CHESS already includes common kinds such as Temperature and Humidity, so this example defines a new one:
julia> @attribute BarometricPressure u"atm"
AttributeKind(BarometricPressure)Registered kinds are recalled with @attr_str:
julia> attr"Temperature"
AttributeKind(Temperature)A location's own attributes
Calling an attribute kind with a value creates an attribute. set_attribute!(loc, attribute) sets a location's own attribute, and attributes(x) reads that own set back as an AttributeDict, an alias of Dict{Symbol, Attribute}.
julia> set_attribute!(room, attr"Temperature"(21u"°C"))
julia> attributes(room)
Dict{Symbol, Attribute} with 1 entry:
:Temperature => 21.0 °CInherited environment
environment(x) is the inherited view: x's own attributes override its parent's environment, recursively. A location with no attributes of its own just inherits its parent's.
Here the room also sets Humidity and BarometricPressure. The incubator overrides Temperature with a value and Humidity with Unknown, an indeterminate reading such as a broken sensor produces. Unknown differs from missing, which means no local value. The shelf and the plate set nothing:
julia> set_attribute!(room, attr"Humidity"(45u"percent"))
julia> set_attribute!(room, BarometricPressure(1u"atm"))
julia> set_attribute!(incubator, attr"Temperature"(37u"°C"))
julia> set_attribute!(incubator, attr"Humidity"(Unknown))
julia> environment(plate)[:Temperature]
37.0 °C
julia> environment(plate)[:Humidity]
Unknown
julia> sort(collect(keys(environment(plate))))
3-element Vector{Symbol}:
:BarometricPressure
:Humidity
:TemperatureEach attribute takes a different path down the chain:
BarometricPressureis set at the room and inherited unchanged by the plate.Temperatureis set at the room, overridden at the incubator, and inherited from there.Humidityis set at the room and overridden withUnknownat the incubator.Unknownpropagates to the plate like any other value.
The plate has no attributes of its own, so all of its values are inherited.
Setting an attribute to missing clears the location's own value, so the location falls back to what it inherits. Here the incubator's Temperature falls back to the room's:
julia> set_attribute!(incubator, attr"Temperature"(missing))
julia> environment(plate)[:Temperature]
21.0 °CUnknown values and units
Unknown is the only value of the type UnknownValue. isunknown tests a value for it, as ismissing tests for missing. CHESSCore.value returns the value of an attribute. attribute_unit returns the canonical unit of an attribute:
julia> isunknown(CHESSCore.value(attr"Humidity"(Unknown)))
true
julia> isunknown(CHESSCore.value(attr"Humidity"(45u"percent")))
false
julia> attribute_unit(attr"Temperature"(21u"°C"))
°CStocks & Chemistry describes putting material into wells: chemicals, reagents, stocks, and transfers.