<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Using and Creating Labels
Labels are a way to 'bookmark' certain pores for easier lookup later, such as specifying boundary conditions.  When networks are generated they include a set of relevent labels that have been added for convenience.  It's also possible for users to add their own labels.  This tutorial will cover how they work, how to add them, and how to use them.
Step1:  Using Pre-defined Labels
The simple Cubic network has several labels added during creating.  We can see these labels when we inspecting (i.e. print) the network,
Step2:  In the above output we can see 2 'properties', which is the term used for numerical data, and 10 'labels'.  Labels are also Numpy arrays, so are stored in the OpenPNM objects along 'propery' arrays. The different is that 'labels' are boolean type (i.e. True, False).
A boolean array, such as 'pore.left' is an Np long with True values indicating which pores possess that label.  
The label 'pore.left' clearly indicates which pores on of the 'left' side of the network.  We can inspect the label array directly
Step3:  But it's more useful to extract the locations of the true values
Step4:  OpenPNM objects all have a method for accessing such locations directly without calling where
Step5:  The advantage of the using the OpenPNM method is that you can create more complex queries, such as pores that are 'left' or 'back'
Step6:  Or, pores that are both left and back
Step7:  Because this is a 2D network, we only found one 'corner' pore ([0]).  In 3D this would have found all the pores on the 'edge'.
Points to Note about Using Labels
Labels can be applied to throats in exactly the same way as decribed here for pores.
Every OpenPNM object has a pores and a throats method that can be used to retrieve the pore or throat indices  where a label or combination of labels has been applied.
For internal purposes, every OpenPNM object has pre-defined labels called 'pore.all' and 'throat.all'.  These are True everywhere and are used internally for determining the number of pore and throats on an object. 
The format of creating an entire ND-array to label just a few locations may seem a bit wasteful of data storage since many False values are created to indicate pores do not have a label.  However, such boolean area can be used to index into Numpy arrays.
Labels are used throughout any given script, particularly for specifying boundary conditions, so are an important feature to understand.
Defining Custom Labels
It is common in OpenPNM scripts to do some complex searching to find all pores that satisfy some set of conditions.  It can be helpful to label these for later use.  There are two ways to do this, by directly creating a boolean mask and storing it, or by calling the set_label method.
Creating Boolean Masks
Assume we want to label the 'corner' pore we found above.  First catch the result in a variable
Step8:  Then create an empty label array (filled with False values) on the network
Step9:  Now insert True values at the desired array indices corresponding to the pore location
Step10:  Using set_label
The process of creating an empty label array, then filling the True values is a bit annoying, so there is a helper/shortcut method on all OpenPNM objects.  It can be used to create a new label, or to add to an existing one.  Let's label another corner pore as 'pore.corner'
Step11:  The set_label method has a mode argument that can be used to change the behavior, such as 'add' or 'remove' labels from the given locations (default is mode='add'), 'purge' all existing indices from the given label, or 'remove' which purges then adds the given pores to the specified label.
<ASSISTANT_TASK:>
Python Code:
import openpnm as op
%config InlineBackend.figure_formats = ['svg']
import numpy as np
Explanation: Using and Creating Labels
Labels are a way to 'bookmark' certain pores for easier lookup later, such as specifying boundary conditions.  When networks are generated they include a set of relevent labels that have been added for convenience.  It's also possible for users to add their own labels.  This tutorial will cover how they work, how to add them, and how to use them.
End of explanation
pn = op.network.Cubic(shape=[5, 5, 1])
print(pn)
Explanation: Using Pre-defined Labels
The simple Cubic network has several labels added during creating.  We can see these labels when we inspecting (i.e. print) the network,
End of explanation
print(pn['pore.left'])
Explanation: In the above output we can see 2 'properties', which is the term used for numerical data, and 10 'labels'.  Labels are also Numpy arrays, so are stored in the OpenPNM objects along 'propery' arrays. The different is that 'labels' are boolean type (i.e. True, False).
A boolean array, such as 'pore.left' is an Np long with True values indicating which pores possess that label.  
The label 'pore.left' clearly indicates which pores on of the 'left' side of the network.  We can inspect the label array directly:
End of explanation
np.where(pn['pore.left'])[0]
Explanation: But it's more useful to extract the locations of the true values:
End of explanation
pn.pores('left')
Explanation: OpenPNM objects all have a method for accessing such locations directly without calling where:
End of explanation
pn.pores(['left', 'back'])
Explanation: The advantage of the using the OpenPNM method is that you can create more complex queries, such as pores that are 'left' or 'back':
End of explanation
pn.pores(['left', 'back'], mode='and')
Explanation: Or, pores that are both left and back:
End of explanation
Ps = pn.pores(['left', 'back'], mode='and')
Explanation: Because this is a 2D network, we only found one 'corner' pore ([0]).  In 3D this would have found all the pores on the 'edge'.
Points to Note about Using Labels
Labels can be applied to throats in exactly the same way as decribed here for pores.
Every OpenPNM object has a pores and a throats method that can be used to retrieve the pore or throat indices  where a label or combination of labels has been applied.
For internal purposes, every OpenPNM object has pre-defined labels called 'pore.all' and 'throat.all'.  These are True everywhere and are used internally for determining the number of pore and throats on an object. 
The format of creating an entire ND-array to label just a few locations may seem a bit wasteful of data storage since many False values are created to indicate pores do not have a label.  However, such boolean area can be used to index into Numpy arrays.
Labels are used throughout any given script, particularly for specifying boundary conditions, so are an important feature to understand.
Defining Custom Labels
It is common in OpenPNM scripts to do some complex searching to find all pores that satisfy some set of conditions.  It can be helpful to label these for later use.  There are two ways to do this, by directly creating a boolean mask and storing it, or by calling the set_label method.
Creating Boolean Masks
Assume we want to label the 'corner' pore we found above.  First catch the result in a variable:
End of explanation
pn['pore.corner'] = False
print(pn['pore.corner'])
Explanation: Then create an empty label array (filled with False values) on the network:
End of explanation
pn['pore.corner'][Ps] = True
print(pn.pores('corner'))
Explanation: Now insert True values at the desired array indices corresponding to the pore location:
End of explanation
Ps = Ps = pn.pores(['left', 'front'], mode='and')
pn.set_label(label='corner', pores=Ps)
print(pn.pores('corner'))
Explanation: Using set_label
The process of creating an empty label array, then filling the True values is a bit annoying, so there is a helper/shortcut method on all OpenPNM objects.  It can be used to create a new label, or to add to an existing one.  Let's label another corner pore as 'pore.corner':
End of explanation
pn.set_label(label='pore.corner', mode='purge')
print('pore.corner' in pn.keys())
Explanation: The set_label method has a mode argument that can be used to change the behavior, such as 'add' or 'remove' labels from the given locations (default is mode='add'), 'purge' all existing indices from the given label, or 'remove' which purges then adds the given pores to the specified label.
End of explanation
<END_TASK>