<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Generate Cubic Lattices of Various Shape, Sizes and Topologies
The Cubic lattice network is easily the most commonly used pore network topology. When people first learn about pore network modeling they often insist on creating network that are topologically equivalent or representative of the real network (i.e. random networks extracted from tomography images). In reality, however, a simple cubic network provides a very passable representation of more complex topologies, and provides several additional benefits as well; namely they are much easier to visualize, and applying boundary conditions is easier since the faces of the network are flat.
The examples below will demonstrate how to create various cubic lattice networks in OpenPNM using the Cubic class, as well as illustrating a few topological manipulations that can be performed, such as adding boundary pores, and trimming throats to create a more random-like topology.
Basic Cubic Lattice with Different Connectivity
Let's start with the most basic cubic lattice
Step1:  In this case pn will be a 10 x 10 x 10 cube with each pore spaced 1 unit away from it's neighbors in all directions.  Each pore is connected to the 6 neighbors adjacent to each face of the cubic lattice site in which it sits.  The image below illustrates the resulting network with pores shown as white spheres, along with a zoomed in view of the internals, showing the connectivity of the pores.
The Cubic network generator applies 6-connectivity by default, but different values can be specified.  In a cubic lattice, each pore can have up to 26 neighbors
Step2:  This yields the following network, which clearly has a LOT of connections!
Trimming Random Throats to Adjust Coordination Number
Often it is desired to create a distribution of coordination numbers on each pore, such that some pores have 2 neighbors and other have 8, while the overall average may be around 5.  It is computationally very challenging to specify a specific distribution, so OpenPNM does not offer this feature (yet); however it can be approximated manually by creating a highly connected network, and then trimming random throats to reduced the coordination numbers.  The following code block randomly selects 500 throats, then trims them from the network
Step3:  The following image shows histogram of the pore connectivity before and after trimming.  Before trimming the coordination numbers fall into 4 distinct bins depending on where the pores lies (internal, face, edge or corner), while after trimming the coordination numbers show some distribution around their original values.  If the trimming is too aggressive, OpenPNM might report an error message saying that isolated pores exist, which means that some regions of the network are now disconnected from the main network due to a lack of connected throats.
Creating Domains with More Interesting Shapes
Rectangular Domains with Non-Uniform Spacing
The shape and spacing arguments can of course be adjusted to create domains other than simple cubes
Step4:  This results in the following network with is squished in the x-direction.
Spherical and Other Arbitrary Domains
The Cubic class can generate networks of arbitrary shapes (i.e. spheres), but still with cubic connectivity.  This is accomplished using the template argument, which accepts a binary image of 1's and 0's.  The network will have pores where the 1's are and 0's elsewhere.  For instance, to make a spherical domain  for a catalyst pellet, generate an image of a sphere using Scipy's NDimage module, the pass this image to Cubic as follows
Step5:  This results in the following
<ASSISTANT_TASK:>
Python Code:
import openpnm as op
wrk = op.Workspace()
wrk.logelevel=50
pn = op.network.Cubic(shape=[10, 10, 10], spacing=1)
Explanation: Generate Cubic Lattices of Various Shape, Sizes and Topologies
The Cubic lattice network is easily the most commonly used pore network topology. When people first learn about pore network modeling they often insist on creating network that are topologically equivalent or representative of the real network (i.e. random networks extracted from tomography images). In reality, however, a simple cubic network provides a very passable representation of more complex topologies, and provides several additional benefits as well; namely they are much easier to visualize, and applying boundary conditions is easier since the faces of the network are flat.
The examples below will demonstrate how to create various cubic lattice networks in OpenPNM using the Cubic class, as well as illustrating a few topological manipulations that can be performed, such as adding boundary pores, and trimming throats to create a more random-like topology.
Basic Cubic Lattice with Different Connectivity
Let's start with the most basic cubic lattice:
End of explanation
pn = op.network.Cubic(shape=[10, 10, 10], spacing=1, connectivity=26)
Explanation: In this case pn will be a 10 x 10 x 10 cube with each pore spaced 1 unit away from it's neighbors in all directions.  Each pore is connected to the 6 neighbors adjacent to each face of the cubic lattice site in which it sits.  The image below illustrates the resulting network with pores shown as white spheres, along with a zoomed in view of the internals, showing the connectivity of the pores.
The Cubic network generator applies 6-connectivity by default, but different values can be specified.  In a cubic lattice, each pore can have up to 26 neighbors: 6 on each face, 8 on each corner, and 12 on each edge.  This is illustrated in the image below.
Cubic networks can have any combination of corners, edges, and faces, which is controlled with the connectivity argument by specifying the total number of neighbors (6, 8, 12, 14, 18, 20, or 26):
End of explanation
import scipy as sp
pn = op.network.Cubic(shape=[10, 10, 10], spacing=[1, 1, 1], connectivity=26)
print(pn.num_throats())
throats_to_trim = sp.random.randint(low=0, high=pn.Nt-1, size=500)
from openpnm import topotools as tt
tt.trim(network=pn, throats=throats_to_trim)
# randint returns some duplicate numbers so actual number of trimmed throats varies
assert pn.num_throats() < 10476
Explanation: This yields the following network, which clearly has a LOT of connections!
Trimming Random Throats to Adjust Coordination Number
Often it is desired to create a distribution of coordination numbers on each pore, such that some pores have 2 neighbors and other have 8, while the overall average may be around 5.  It is computationally very challenging to specify a specific distribution, so OpenPNM does not offer this feature (yet); however it can be approximated manually by creating a highly connected network, and then trimming random throats to reduced the coordination numbers.  The following code block randomly selects 500 throats, then trims them from the network:
End of explanation
pn = op.network.Cubic(shape=[20, 20, 10], spacing=[0.003, 0.02, 0.01])
Explanation: The following image shows histogram of the pore connectivity before and after trimming.  Before trimming the coordination numbers fall into 4 distinct bins depending on where the pores lies (internal, face, edge or corner), while after trimming the coordination numbers show some distribution around their original values.  If the trimming is too aggressive, OpenPNM might report an error message saying that isolated pores exist, which means that some regions of the network are now disconnected from the main network due to a lack of connected throats.
Creating Domains with More Interesting Shapes
Rectangular Domains with Non-Uniform Spacing
The shape and spacing arguments can of course be adjusted to create domains other than simple cubes:
End of explanation
import scipy.ndimage as spim
im = sp.ones([21, 21, 21])
im[10, 10, 10] = 0
dt = spim.distance_transform_bf(input=im)
sphere = dt < 10
pn = op.network.CubicTemplate(template=sphere, spacing=0.1)
Explanation: This results in the following network with is squished in the x-direction.
Spherical and Other Arbitrary Domains
The Cubic class can generate networks of arbitrary shapes (i.e. spheres), but still with cubic connectivity.  This is accomplished using the template argument, which accepts a binary image of 1's and 0's.  The network will have pores where the 1's are and 0's elsewhere.  For instance, to make a spherical domain  for a catalyst pellet, generate an image of a sphere using Scipy's NDimage module, the pass this image to Cubic as follows:
End of explanation
op.io.VTK.save(network=pn, filename='my_network')
Explanation: This results in the following:
All images of networks were made with paraview by exporting a VTK file with the following command:
End of explanation
<END_TASK>