Jump to content

SPy Assets: Referencing Child and Parent Attributes in Calculations


Kristopher Wiggins

Recommended Posts

  • Seeq Team

The SPy library supports the creation of asset trees through the spy.assets models. These asset trees can include various types of items such as signals and conditions, calculations like scorecard metrics, and can be used to create numerous Workbench Analysis Worksheets and Organizer Topic Documents. One question that commonly comes up when making these trees is how to reference attributes that are located in other parts of the tree.

Roll-Ups

The first example of referencing other items in the tree is through roll-ups. These type of calculations "roll-up" attributes from levels below where the roll-up calculation is being performed, whether the level is directly beneath or multiple levels below. These attributes are then combined using logic you provide. For signals and scalars, the options are Average, Maximum, Minimum, Range, Sum and Multiply. For conditions, the options are Union, Intersect, Counts, Count Overlaps, and Combine With. Below are examples where .Cities() are a component beneath the current class. All attributes and assets beneath the Cities component will be searched through and included in the roll-up based on the criteria given in the pick function. Here, we're filtering based on Name but any property such as Type can be supplied. Note Seeq Workbench's search mechanism is used here, so wildcards and regular expressions can be included. Lastly, we specify the kind of roll-up we'd like to perform.   

    @Asset.Attribute()
    def Regional_Compressor_Running_Poorly(self,metadata):
        return self.Cities().pick({'Name':'Compressor Running Poorly'}).roll_up('union')
    
    @Asset.Attribute()
    def Regional_Total_Energy_Consumption(self,metadata):
        return self.Cities().pick({'Name':'Total Daily Energy Consumption'}).roll_up('sum')

Child Attributes

The second example looks at how to reference child attributes without rolling them up. Maybe there's a particular attribute that needs to be included in a calculation used at a higher level in the asset tree. For this scenario, the pick function can be used once again. Rather than do a roll-up, we'll just index the particular item we want. Most of the time the goal is to reference a specific item using this method so the criteria passed into the pick function should be specific enough to find one item so the index will always be 0. One property that may be of interest for this is Template, where you can specify the particular class used that will contain the item wanted.

    @Asset.Attribute()
    def Child_Power_Low(self, metadata):
        child_power = self.Cities().pick({"Name": "Compressor Power", "Asset": "/Area (A|C|D)/"})[0]
        return {
            'Name': "Child Power Low",
            'Type': "Condition",
            "Formula": "$child_power < 5",
            "Formula Parameters" : {"child_power":child_power}
        }

Parent Attributes

The next example looks at how we can reference parent attributes in calculations that are beneath it. Rather than reference a particular component, we'll use the parent. From there we'll include the attribute we'd want to reference from our parent asset. If looking to reference attributes at higher levels of the tree, chain multiple ".parent". For example, "self.parent.parent" will look two levels above the current level.

    @Asset.Attribute()
    def Parent_Temp_Multiplied(self, metadata):
        parent_temp = self.parent.Temperature()
        return {
            'Name': "Parent Temp Multiplied",
            'Type': "Signal",
            "Formula": "$parent_temp * 10",
            "Formula Parameters" : {"parent_temp":parent_temp}
        }

Advanced Selection

In this example, we'll look at how can we combine the previously mentioned options to find items located in other parts of the tree. Here, we're looking to reference items located at the same level of the tree but in another class so it's not located beneath the same asset. We have two separate assets beneath the regions, Temperature Items and Power Items. The Temperature Item class has a calculation called Max Temperature 1 When Compressor Is On which references an attribute beneath its corresponding Power Item class. To fetch this attribute, we go up a level to the parent, navigate down to the Power_Items and then pick that attribute.

class Region(Asset):
    @Asset.Component()
    def Temperature_Items(self,metadata):
        return self.build_components(template=Temperature_Item, metadata=metadata, column_name='Region Temp')
    @Asset.Component()
    def Power_Items(self,metadata):
        return self.build_components(template=Power_Item, metadata=metadata, column_name='Region Power')
class Power_Item(Asset):
    @Asset.Attribute()
    def Power_1(self,metadata):
        return {
            'Name':'Power 1',
            'Type':'Signal',
            'Formula':'$power',
            'Formula Parameters': {'$power':metadata[metadata['Name'].str.contains('Power')].iloc[0]['ID']}
        }
class Temperature_Item(Asset):
    @Asset.Attribute()
    def Temperature_1(self,metadata):
        return {
            'Name':'Temperature 1',
            'Type':'Signal',
            'Formula':'$temp',
            'Formula Parameters': {'$temp':metadata[metadata['Name'].str.contains('Temperature')].iloc[0]['ID']}
        }
    @Asset.Attribute()
    def Temp_When_Comp_On(self, metadata):
        power_adjacent_class = self.parent.Power_Items().pick({'Name':"Power 1"})[0]
        return {
            'Name': "Max Temperature 1 When Compressor Is On",
            'Type': 'Signal',
            'Formula': '$temp1.aggregate(maxValue(), ($power1<5).removeLongerThan(7d), durationKey())',
            'Formula Parameters':{
                'temp1':self.Temperature_1(),
                'power1': power_adjacent_class
            }
        }

Item Group

To help with even more complex attribute selections, we introduced the ability to include ItemGroup rather than using the pick and parent functions. ItemGroup provides an alternate way of findings items located in other parts of the tree using established Python logic. Below are two examples using ItemGroup to perform selections that would be very complex to do with the pick function

Advanced Roll-up

Roll-ups using the pick reference one component beneath your class but what if there was a need for a roll-up across multiple components. ItemGroup can be used for a simple roll-up as well as this complex example. Rather than specifying a particular component and picking in it, we can use ItemGroup to iterate over every asset. Here, we retrieve every High Power attribute beneath the assets if the asset is a child of the current asset. 

    @Asset.Attribute()
    def Compressor_High_Power(self, metadata):
        # Helpful functions:
        #  asset.is_child_of(self)      - Is the asset one of my direct children?
        #  asset.is_parent_of(self)     - Is the asset my direct parent?
        #  asset.is_descendant_of(self) - Is the asset below me in the tree?
        #  asset.is_ancestor_of(self)   - Is the asset above me? (i.e. parent/grandparent/great-grandparent/etc)
        return ItemGroup([
            asset.High_Power() for asset in self.all_assets()
            if asset.is_child_of(self)
        ]).roll_up('union')

Referencing Items In A Different Section

In this example, we're looking to reference attributes in other similar assets, but these assets are located in different sections of the tree. We can use the previous option in the Advanced Selection section but what if these compressors weren't necessarily at the same level of the tree or were beneath different components. This would mean they have different pathways and the method previously stated wouldn't work. Using ItemGroup we can iterate through all assets and find any that are also based on the Compressor class. Here we also exclude the current asset and then perform a roll-up based on all of the other High Powers.

    @Asset.Attribute()
    def Other_Compressors_Are_High_Power(self, metadata):
        return ItemGroup([
            asset.High_Power() for asset in self.all_assets()
            if isinstance(asset, Compressor) and self != asset
        ]).roll_up('union')

 

Edited by Kristopher Wiggins
  • Thanks 2
Link to comment
Share on other sites

  • 1 year later...

@Kristopher Wiggins   I am working on a tree using the spy.assets.Tree and need to reference an attribute in a child asset to calculate a condition in the parent. Your example above shows how to do this using templates. Is there a way to do this using spy.assests.Tree.insert() passing a dataframe? If the attribute is on the same level, this is easily done by adding the attribute's name in the dictionary of the "Formula Parameters" column. How does one reference it when it is on a different level?

Link to comment
Share on other sites

  • Seeq Team

With spy.assets.Tree() there isn't a direct route to reference attributes belonging to child attributes in calculations. Instead you can create a rollup that will create an attribute based on the child attributes, and then use that roll-up attribute as an input into your calculations. For example in the code below, we need to create roll-up attributes that reference the child attributes. In this case I'm looking at a particular Area's but you can refine the roll_up_parameters to find the item you're looking for. Once the roll-up attribute is created you can then include it in other calculations at that level of the tree, as is done in the last tree insertion step.

test.insert(name='Cooling Tower Temp',
            roll_up_statistic='Average',
            roll_up_parameters='Area B >> Temperature',
            parent='Cooling Tower 1')
test.insert(name='Cooling Tower Temp',
            roll_up_statistic='Average',
            roll_up_parameters='Area D >> Temperature',
            parent='Cooling Tower 2')
test.insert(name='Doing Something to Temperature',
            formula='$temp*100',
            formula_parameters={'$temp':'Cooling Tower Temp'},
            parent='Cooling Tower ?')

 

Link to comment
Share on other sites

  • 1 month later...
  • Seeq Team

@TJDataYou should be able to do this using absolute or relative paths when specifying your formula parameters using >> and .. to traverse up and down your tree. For example, if we start with a tree that looks like the following:

Example
└── Cooling Tower 1
    ├── Shifts
    ├── Area A
    │   └── Temperature
    └── Area B
        └── Temperature

Traversing Up the Tree: We can insert a calculation under each area that references the Shifts condition with the following (note the usage of .. in the formula parameters):

tree.insert('Shift Max Temp', 
            formula='$temp.aggregate(maxValue(), $shifts, startKey())',
            formula_parameters={
                'shifts': '.. >> Shifts',
                'temp': 'Temperature'
            }, 
            parent='Cooling Tower 1 >> Area *')

Traversing Down the Tree: We can insert a calculation under Cooling Tower 1 that references an attribute of a child asset with the following:

tree.insert('Area A High Temp',
            formula='$temp > 100',
            formula_parameters={'temp': 'Area A >> Temperature'},
            parent='Cooling Tower 1'
           )
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...