<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Creating a custom fluid using GenericPhase
OpenPNM comes with a small selection of pre-written phases (Air, Water, Mercury).  In many cases users will want different options but it is not feasible or productive to include a wide variety of fluids.  Consequntly OpenPNM has a mechanism for creating custom phases for this scneario.  This requires that the user have correlations for the properties of interest, such as the viscosity as a function of temperature in the form of a polynomial for instance.  This is process is described in the following tutuorial
Step1:  Now that a network is defined, we can create a GenericPhase object associated with it.  For this demo we'll make an oil phase, so let's call it oil
Step2:  As can be seen in the above printout, this phase has a temperature and pressure set at all locations, but has no other physical properties.  
There are 2 ways add physical properties. They can be hard-coded, or added as a 'pore-scale model'.
- Some are suitable as hard coded values, such as molecular mass
- Others should be added as a model, such as viscosity, which is a function of temperature so could vary spatially and should be updated depending on changing conditions in the simulation.
Start with hard-coding
Step3:  As can be seen, this puts the value of 100.0 g/mol in every pore.  Note that you could also assign each pore explicitly with a numpy array.  OpenPNM automatically assigns a scalar value to every location as shown above.
Step4:  You can also specify something like viscosity this way as well, but it's not recommended
Step5:  The problem with specifying the viscosity as a hard-coded value is that viscosity is a function of temperature (among other things), so if we adjust the temperature on the oil object it will have no effect on the hard-coded viscosity
Step6:  The correct way to specify something like viscosity is to use pore-scale models.  There is a large libary of pre-written models in the openpnm.models submodule.  For instance, a polynomial can be used as follows
Step7:  We can now see that our previously written values of viscosity (1600.0) have been overwritten by the values coming from the model
Step8:  And moreover, if we change the temperature the model will update the viscosity values
Step9:  Note the call to regenerate_models, which is necessary to actually re-run the model using the new temperature.
When a pore-scale model is added to an object, it is stored under the models attribute, which is a dictionary with names corresponding the property that is being calculated (i.e. 'pore.viscosity')
Step10:  We can reach into this dictionary and alter the parameters of the model if necessary
<ASSISTANT_TASK:>
Python Code:
import numpy as np
import openpnm as op
pn = op.network.Cubic(shape=[3, 3, 3], spacing=1e-4)
print(pn)
Explanation: Creating a custom fluid using GenericPhase
OpenPNM comes with a small selection of pre-written phases (Air, Water, Mercury).  In many cases users will want different options but it is not feasible or productive to include a wide variety of fluids.  Consequntly OpenPNM has a mechanism for creating custom phases for this scneario.  This requires that the user have correlations for the properties of interest, such as the viscosity as a function of temperature in the form of a polynomial for instance.  This is process is described in the following tutuorial:
Import the usual packages and instantiate a small network for demonstration purposes:
End of explanation
oil = op.phases.GenericPhase(network=pn)
print(oil)
Explanation: Now that a network is defined, we can create a GenericPhase object associated with it.  For this demo we'll make an oil phase, so let's call it oil:
End of explanation
oil['pore.molecular_mass'] = 100.0  # g/mol
print(oil['pore.molecular_mass'])
Explanation: As can be seen in the above printout, this phase has a temperature and pressure set at all locations, but has no other physical properties.  
There are 2 ways add physical properties. They can be hard-coded, or added as a 'pore-scale model'.
- Some are suitable as hard coded values, such as molecular mass
- Others should be added as a model, such as viscosity, which is a function of temperature so could vary spatially and should be updated depending on changing conditions in the simulation.
Start with hard-coding:
End of explanation
oil['pore.molecular_mass'] = np.ones(shape=[pn.Np, ])*120.0
print(oil['pore.molecular_mass'])
Explanation: As can be seen, this puts the value of 100.0 g/mol in every pore.  Note that you could also assign each pore explicitly with a numpy array.  OpenPNM automatically assigns a scalar value to every location as shown above.
End of explanation
oil['pore.viscosity'] = 1600.0  # cP
Explanation: You can also specify something like viscosity this way as well, but it's not recommended:
End of explanation
oil['pore.temperature'] = 100.0  # C
print(oil['pore.viscosity'])
Explanation: The problem with specifying the viscosity as a hard-coded value is that viscosity is a function of temperature (among other things), so if we adjust the temperature on the oil object it will have no effect on the hard-coded viscosity:
End of explanation
mod = op.models.misc.polynomial
oil.add_model(propname='pore.viscosity', model=mod, 
            a=[1600, 12, -0.05], prop='pore.temperature')
Explanation: The correct way to specify something like viscosity is to use pore-scale models.  There is a large libary of pre-written models in the openpnm.models submodule.  For instance, a polynomial can be used as follows:
$$ viscosity = a_0 + a_1 \cdot T + a_2 \cdot T^2 = 1600 + 12 T - 0.05 T^2$$
End of explanation
print(oil['pore.viscosity'])
Explanation: We can now see that our previously written values of viscosity (1600.0) have been overwritten by the values coming from the model:
End of explanation
oil['pore.temperature'] = 40.0  # C
oil.regenerate_models()
print(oil['pore.viscosity'])
Explanation: And moreover, if we change the temperature the model will update the viscosity values:
End of explanation
print(oil.models)
Explanation: Note the call to regenerate_models, which is necessary to actually re-run the model using the new temperature.
When a pore-scale model is added to an object, it is stored under the models attribute, which is a dictionary with names corresponding the property that is being calculated (i.e. 'pore.viscosity'):
End of explanation
oil.models['pore.viscosity']['a'] = [1200, 10, -0.02]
oil.regenerate_models()
print(oil['pore.viscosity'])
Explanation: We can reach into this dictionary and alter the parameters of the model if necessary:
End of explanation
<END_TASK>