openpnm 1.0.0__zip

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. OpenPNM-1.1/MANIFEST.in +2 -0
  2. OpenPNM-1.1/OpenPNM/Algorithms/__FickianDiffusion__.py +67 -0
  3. OpenPNM-1.1/OpenPNM/Algorithms/__FourierConduction__.py +63 -0
  4. OpenPNM-1.1/OpenPNM/Algorithms/__GenericAlgorithm__.py +235 -0
  5. OpenPNM-1.1/OpenPNM/Algorithms/__GenericLinearTransport__.py +641 -0
  6. OpenPNM-1.1/OpenPNM/Algorithms/__InvasionPercolationForImbibition__.py +703 -0
  7. OpenPNM-1.1/OpenPNM/Algorithms/__InvasionPercolationTimed__.py +702 -0
  8. OpenPNM-1.1/OpenPNM/Algorithms/__InvasionPercolation__.py +156 -0
  9. OpenPNM-1.1/OpenPNM/Algorithms/__OhmicConduction__.py +64 -0
  10. OpenPNM-1.1/OpenPNM/Algorithms/__OrdinaryPercolation__.py +402 -0
  11. OpenPNM-1.1/OpenPNM/Algorithms/__StokesFlow__.py +64 -0
  12. OpenPNM-1.1/OpenPNM/Algorithms/__Tortuosity__.py +91 -0
  13. OpenPNM-1.1/OpenPNM/Algorithms/__init__.py +48 -0
  14. OpenPNM-1.1/OpenPNM/Base/__Controller__.py +480 -0
  15. OpenPNM-1.1/OpenPNM/Base/__Core__.py +1522 -0
  16. OpenPNM-1.1/OpenPNM/Base/__ModelsDict__.py +345 -0
  17. OpenPNM-1.1/OpenPNM/Base/__Tools__.py +72 -0
  18. OpenPNM-1.1/OpenPNM/Base/__init__.py +32 -0
  19. OpenPNM-1.1/OpenPNM/Geometry/__Boundary__.py +80 -0
  20. OpenPNM-1.1/OpenPNM/Geometry/__Cube_and_Cuboid__.py +64 -0
  21. OpenPNM-1.1/OpenPNM/Geometry/__GenericGeometry__.py +106 -0
  22. OpenPNM-1.1/OpenPNM/Geometry/__SGL10__.py +67 -0
  23. OpenPNM-1.1/OpenPNM/Geometry/__Stick_and_Ball__.py +68 -0
  24. OpenPNM-1.1/OpenPNM/Geometry/__TestGeometry__.py +51 -0
  25. OpenPNM-1.1/OpenPNM/Geometry/__Toray090__.py +68 -0
  26. OpenPNM-1.1/OpenPNM/Geometry/__Voronoi__.py +98 -0
  27. OpenPNM-1.1/OpenPNM/Geometry/__init__.py +47 -0
  28. OpenPNM-1.1/OpenPNM/Geometry/models/__init__.py +33 -0
  29. OpenPNM-1.1/OpenPNM/Geometry/models/pore_area.py +27 -0
  30. OpenPNM-1.1/OpenPNM/Geometry/models/pore_centroid.py +35 -0
  31. OpenPNM-1.1/OpenPNM/Geometry/models/pore_diameter.py +127 -0
  32. OpenPNM-1.1/OpenPNM/Geometry/models/pore_misc.py +55 -0
  33. OpenPNM-1.1/OpenPNM/Geometry/models/pore_seed.py +212 -0
  34. OpenPNM-1.1/OpenPNM/Geometry/models/pore_surface_area.py +28 -0
  35. OpenPNM-1.1/OpenPNM/Geometry/models/pore_vertices.py +19 -0
  36. OpenPNM-1.1/OpenPNM/Geometry/models/pore_volume.py +133 -0
  37. OpenPNM-1.1/OpenPNM/Geometry/models/throat_area.py +47 -0
  38. OpenPNM-1.1/OpenPNM/Geometry/models/throat_centroid.py +80 -0
  39. OpenPNM-1.1/OpenPNM/Geometry/models/throat_diameter.py +106 -0
  40. OpenPNM-1.1/OpenPNM/Geometry/models/throat_length.py +95 -0
  41. OpenPNM-1.1/OpenPNM/Geometry/models/throat_misc.py +42 -0
  42. OpenPNM-1.1/OpenPNM/Geometry/models/throat_normal.py +31 -0
  43. OpenPNM-1.1/OpenPNM/Geometry/models/throat_offset_vertices.py +191 -0
  44. OpenPNM-1.1/OpenPNM/Geometry/models/throat_perimeter.py +26 -0
  45. OpenPNM-1.1/OpenPNM/Geometry/models/throat_seed.py +12 -0
  46. OpenPNM-1.1/OpenPNM/Geometry/models/throat_shape_factor.py +37 -0
  47. OpenPNM-1.1/OpenPNM/Geometry/models/throat_surface_area.py +44 -0
  48. OpenPNM-1.1/OpenPNM/Geometry/models/throat_vector.py +27 -0
  49. OpenPNM-1.1/OpenPNM/Geometry/models/throat_vertices.py +19 -0
  50. OpenPNM-1.1/OpenPNM/Geometry/models/throat_volume.py +45 -0
  51. OpenPNM-1.1/OpenPNM/Network/__Cubic__.py +316 -0
  52. OpenPNM-1.1/OpenPNM/Network/__DelaunayCubic__.py +127 -0
  53. OpenPNM-1.1/OpenPNM/Network/__Delaunay__.py +600 -0
  54. OpenPNM-1.1/OpenPNM/Network/__GenericNetwork__.py +1184 -0
  55. OpenPNM-1.1/OpenPNM/Network/__MatFile__.py +331 -0
  56. OpenPNM-1.1/OpenPNM/Network/__TestNet__.py +109 -0
  57. OpenPNM-1.1/OpenPNM/Network/__init__.py +40 -0
  58. OpenPNM-1.1/OpenPNM/Network/models/__init__.py +12 -0
  59. OpenPNM-1.1/OpenPNM/Network/models/pore_topology.py +106 -0
  60. OpenPNM-1.1/OpenPNM/Phases/__Air__.py +63 -0
  61. OpenPNM-1.1/OpenPNM/Phases/__GenericPhase__.py +146 -0
  62. OpenPNM-1.1/OpenPNM/Phases/__Mercury__.py +71 -0
  63. OpenPNM-1.1/OpenPNM/Phases/__TestPhase__.py +46 -0
  64. OpenPNM-1.1/OpenPNM/Phases/__Water__.py +56 -0
  65. OpenPNM-1.1/OpenPNM/Phases/__init__.py +38 -0
  66. OpenPNM-1.1/OpenPNM/Phases/models/__init__.py +22 -0
  67. OpenPNM-1.1/OpenPNM/Phases/models/contact_angle.py +34 -0
  68. OpenPNM-1.1/OpenPNM/Phases/models/density.py +81 -0
  69. OpenPNM-1.1/OpenPNM/Phases/models/diffusivity.py +95 -0
  70. OpenPNM-1.1/OpenPNM/Phases/models/electrical_conductivity.py +10 -0
  71. OpenPNM-1.1/OpenPNM/Phases/models/misc.py +125 -0
  72. OpenPNM-1.1/OpenPNM/Phases/models/molar_density.py +69 -0
  73. OpenPNM-1.1/OpenPNM/Phases/models/molar_mass.py +31 -0
  74. OpenPNM-1.1/OpenPNM/Phases/models/surface_tension.py +104 -0
  75. OpenPNM-1.1/OpenPNM/Phases/models/thermal_conductivity.py +98 -0
  76. OpenPNM-1.1/OpenPNM/Phases/models/vapor_pressure.py +69 -0
  77. OpenPNM-1.1/OpenPNM/Phases/models/viscosity.py +103 -0
  78. OpenPNM-1.1/OpenPNM/Physics/__GenericPhysics__.py +111 -0
  79. OpenPNM-1.1/OpenPNM/Physics/__Standard__.py +51 -0
  80. OpenPNM-1.1/OpenPNM/Physics/__TestPhysics__.py +50 -0
  81. OpenPNM-1.1/OpenPNM/Physics/__init__.py +30 -0
  82. OpenPNM-1.1/OpenPNM/Physics/models/__init__.py +18 -0
  83. OpenPNM-1.1/OpenPNM/Physics/models/capillary_pressure.py +122 -0
  84. OpenPNM-1.1/OpenPNM/Physics/models/diffusive_conductance.py +82 -0
  85. OpenPNM-1.1/OpenPNM/Physics/models/electrical_conductance.py +59 -0
  86. OpenPNM-1.1/OpenPNM/Physics/models/generic_source_term.py +564 -0
  87. OpenPNM-1.1/OpenPNM/Physics/models/hydraulic_conductance.py +76 -0
  88. OpenPNM-1.1/OpenPNM/Physics/models/multiphase.py +133 -0
  89. OpenPNM-1.1/OpenPNM/Physics/models/thermal_conductance.py +67 -0
  90. OpenPNM-1.1/OpenPNM/Postprocessing/Graphics.py +251 -0
  91. OpenPNM-1.1/OpenPNM/Postprocessing/Plots.py +369 -0
  92. OpenPNM-1.1/OpenPNM/Postprocessing/__init__.py +10 -0
  93. OpenPNM-1.1/OpenPNM/Utilities/IO.py +277 -0
  94. OpenPNM-1.1/OpenPNM/Utilities/Shortcuts.py +17 -0
  95. OpenPNM-1.1/OpenPNM/Utilities/__init__.py +16 -0
  96. OpenPNM-1.1/OpenPNM/Utilities/misc.py +226 -0
  97. OpenPNM-1.1/OpenPNM/Utilities/transformations.py +1923 -0
  98. OpenPNM-1.1/OpenPNM/Utilities/vertexops.py +824 -0
  99. OpenPNM-1.1/OpenPNM/__init__.py +56 -0
  100. OpenPNM-1.1/OpenPNM.egg-info/PKG-INFO +11 -0
  101. OpenPNM-1.1/OpenPNM.egg-info/SOURCES.txt +107 -0
  102. OpenPNM-1.1/OpenPNM.egg-info/dependency_links.txt +1 -0
  103. OpenPNM-1.1/OpenPNM.egg-info/requires.txt +1 -0
  104. OpenPNM-1.1/OpenPNM.egg-info/top_level.txt +1 -0
  105. OpenPNM-1.1/PKG-INFO +11 -0
  106. OpenPNM-1.1/README.txt +88 -0
  107. OpenPNM-1.1/setup.cfg +7 -0
  108. OpenPNM-1.1/setup.py +39 -0
@@ -0,0 +1,80 @@
1
+ r"""
2
+ ===============================================================================
3
+ Submodule -- throat_centroid
4
+ ===============================================================================
5
+
6
+ """
7
+ import scipy as _sp
8
+ from OpenPNM.Utilities import transformations as tr
9
+ from OpenPNM.Utilities import vertexops as vo
10
+ from scipy.spatial import ConvexHull
11
+
12
+ def voronoi(geometry,
13
+ **kwargs):
14
+ r"""
15
+ Calculate the geometrical centroid of the throat from the voronoi vertices.
16
+ """
17
+ verts = geometry['throat.vertices']
18
+ offset_verts = geometry['throat.offset_vertices']
19
+ value = _sp.ndarray([len(verts),3])
20
+ for i in range(len(verts)):
21
+ if len(offset_verts[i]) > 2:
22
+ value[i] = _sp.array([offset_verts[i][:,0].mean(),offset_verts[i][:,1].mean(),offset_verts[i][:,2].mean()])
23
+ elif len(verts[i]) > 2:
24
+ value[i] = _sp.array([verts[i][:,0].mean(),verts[i][:,1].mean(),verts[i][:,2].mean()])
25
+ else:
26
+ value[i] = _sp.array([0,0,0])
27
+
28
+ return value
29
+
30
+ def centre_of_mass(geometry,
31
+ **kwargs):
32
+ r"""
33
+ Calculate the centre of mass of the throat from the voronoi vertices.
34
+ """
35
+ Nt = geometry.num_throats()
36
+ outer_verts = geometry['throat.vertices']
37
+ offset_verts = geometry['throat.offset_vertices']
38
+ normal = geometry['throat.normal']
39
+ z_axis = [0,0,1]
40
+ value = _sp.ndarray([Nt,3])
41
+ for i in range(Nt):
42
+ if len(offset_verts[i]) > 2:
43
+ verts = offset_verts[i]
44
+ elif len(outer_verts[i]) > 2:
45
+ verts = outer_verts[i]
46
+ else:
47
+ verts = []
48
+ if len(verts) > 0:
49
+ " For boundaries some facets will already be aligned with the axis - if this is the case a rotation is unnecessary and could also cause problems "
50
+ angle = tr.angle_between_vectors(normal[i],z_axis)
51
+ if (angle==0.0)or(angle==_sp.pi):
52
+ "We are already aligned"
53
+ rotate_input = False
54
+ facet = verts
55
+ else:
56
+ rotate_input = True
57
+ M = tr.rotation_matrix(tr.angle_between_vectors(normal[i],z_axis),tr.vector_product(normal[i],z_axis))
58
+ facet = _sp.dot(verts,M[:3,:3].T)
59
+ "Now we have a rotated facet aligned with the z axis - make 2D"
60
+ facet_2D = _sp.column_stack((facet[:,0],facet[:,1]))
61
+ z = _sp.unique(_sp.around(facet[:,2],10))
62
+ if len(z) == 1:
63
+ "We need the vertices arranged in order so perform a convex hull"
64
+ hull = ConvexHull(facet_2D)
65
+ ordered_facet_2D = facet_2D[hull.vertices]
66
+ "Call the routine to calculate an area wighted centroid from the 2D polygon"
67
+ COM_2D = vo.PolyWeightedCentroid2D(ordered_facet_2D)
68
+ COM_3D = _sp.hstack((COM_2D,z))
69
+ "If we performed a rotation we need to rotate back"
70
+ if (rotate_input):
71
+ MI = tr.inverse_matrix(M)
72
+ " Unrotate the offset coordinates using the inverse of the original rotation matrix"
73
+ value[i] = _sp.dot(COM_3D,MI[:3,:3].T)
74
+ else:
75
+ value[i] = COM_3D
76
+ else:
77
+ print("Rotation Failed: "+str(_sp.unique(facet[:,2])))
78
+
79
+
80
+ return value
@@ -0,0 +1,106 @@
1
+ r"""
2
+ ===============================================================================
3
+ Submodule -- throat_diameter
4
+ ===============================================================================
5
+
6
+ """
7
+ import scipy as _sp
8
+ import numpy as np
9
+
10
+ import OpenPNM.Utilities.transformations as tr
11
+ from scipy.spatial import ConvexHull
12
+ import time
13
+
14
+
15
+ def cylinder(geometry,
16
+ tsd_name,tsd_shape,tsd_loc,tsd_scale,
17
+ throat_seed='throat.seed',
18
+ tsd_offset=0,
19
+ **kwargs):
20
+ r"""
21
+ Calculate throat diameter from seeds for a cylindrical throat
22
+ """
23
+ import scipy.stats as spst
24
+ prob_fn = getattr(spst,tsd_name)
25
+ P = prob_fn(tsd_shape,loc=tsd_loc,scale=tsd_scale)
26
+ value=P.ppf(geometry[throat_seed])+tsd_offset
27
+ return value
28
+
29
+ def voronoi(geometry,
30
+ throat_area='throat.area',
31
+ **kwargs):
32
+ r"""
33
+ Calculate throat diameter from analysis of Voronoi facets
34
+ Equivalent circular diameter from voronoi area
35
+ Could do better here and work out minimum diameter from verts
36
+ """
37
+ areas = geometry[throat_area]
38
+ value = 2*_sp.sqrt(areas/_sp.pi)#64 bit sqrt doesn't work!
39
+ return value
40
+
41
+ def incircle(geometry,
42
+ **kwargs):
43
+ r"""
44
+ Calculate the incircle diameter by linear programming and the simplex search algorithm using the offset vertices
45
+ generated by the voronoi diagram offsetting routine
46
+ """
47
+ import warnings
48
+ try:
49
+ import pulp as pu
50
+ Nt = geometry.num_throats()
51
+ verts = geometry['throat.offset_vertices']
52
+ normals = geometry['throat.normal']
53
+ value = _sp.zeros(Nt)
54
+ for i in range(Nt):
55
+ if len(verts[i]) > 2:
56
+ pts = tr.rotate_and_chop(verts[i],normals[i],[0,0,1])
57
+ "Work out central point to use as initial guess"
58
+ C = np.mean(pts,axis=0)
59
+ "Compute convex hull to find points lying on the hull in order"
60
+ hull = ConvexHull(pts, qhull_options='QJ Pp')
61
+ "For each simplex making up the hull collect the end points"
62
+ A = pts[hull.vertices]
63
+ B = pts[np.roll(hull.vertices,-1)]
64
+ I = np.array([[0,1],[-1,0]])
65
+ "Normal of the simplices"
66
+ N = np.dot((B-A),I)
67
+ #L = np.sqrt(np.sum(np.square(N),axis=1))
68
+ "Normalize the normal vector"
69
+ L = np.linalg.norm(N,axis=1)
70
+ F = np.vstack((L,L)).T
71
+ N /= F
72
+ "Mid-points of the simplex"
73
+ M = (B+A)/2
74
+ #C = np.mean(A)
75
+ "If normals point out of hull change sign to point in"
76
+ pointing_out = (np.sum((M-C)*N,axis=1)>0)
77
+ N[pointing_out]*= -1
78
+ "Define Linear Program Variables"
79
+ "The centre of the incircle adjustment"
80
+ cx = pu.LpVariable("cx",None,None,pu.LpContinuous)
81
+ cy = pu.LpVariable("cy",None,None,pu.LpContinuous)
82
+ "Radius of the incircle"
83
+ R = pu.LpVariable("R",0,None,pu.LpContinuous)
84
+ "Slack variables for shortest distance between centre and simplices"
85
+ S = pu.LpVariable.dict("SlackVariable",range(len(A)),0,None,pu.LpContinuous)
86
+ "Set up LP problem"
87
+ prob = pu.LpProblem("FindInRadius",pu.LpMaximize)
88
+ "Objective Function"
89
+ prob += R
90
+ for j in range(len(A)):
91
+ " Ni.(C-Ai)-Si = 0"
92
+ prob += N[j][0]*(C[0]+cx) + N[j][1]*(C[1]+cy) - N[j][0]*A[j][0] - N[j][1]*A[j][1] - S[j] == 0
93
+ "Si >= R"
94
+ prob += S[j] >= R
95
+ "Solve the LP"
96
+ with warnings.catch_warnings():
97
+ warnings.simplefilter("ignore")
98
+ prob.solve()
99
+ "As the radius is the objective function we can get it from the objective or as R.value()"
100
+ value[i] = 2*R.value()
101
+ else:
102
+ value[i] = 0.0
103
+
104
+ return value
105
+ except ImportError:
106
+ print("Cannot use incircle method without installing pulp package")
@@ -0,0 +1,95 @@
1
+ r"""
2
+ ===============================================================================
3
+ Submodule -- throat_length
4
+ ===============================================================================
5
+
6
+ """
7
+ import scipy as _sp
8
+
9
+ def straight(network,
10
+ geometry,
11
+ pore_diameter='pore.diameter',
12
+ L_negative = 1e-9,
13
+ **kwargs):
14
+ r"""
15
+ Calculate throat length
16
+
17
+ Parameters
18
+ ----------
19
+ L_negative : float
20
+ The default throat length to use when negative lengths are found. The
21
+ default is 1 nm. To accept negative throat lengths, set this value to
22
+ ``None``.
23
+ """
24
+ #Initialize throat_property['length']
25
+ throats = network.throats(geometry.name)
26
+ pore1 = network['throat.conns'][:,0]
27
+ pore2 = network['throat.conns'][:,1]
28
+ C1 = network['pore.coords'][pore1]
29
+ C2 = network['pore.coords'][pore2]
30
+ E = _sp.sqrt(_sp.sum((C1-C2)**2,axis=1)) #Euclidean distance between pores
31
+ D1 = network[pore_diameter][pore1]
32
+ D2 = network[pore_diameter][pore2]
33
+ value = E-(D1+D2)/2.
34
+ value = value[throats]
35
+ if _sp.any(value<0) and (L_negative is not None):
36
+ print('Negative throat lengths are calculated. Arbitrary positive length assigned: '+str(L_negative))
37
+ Ts = _sp.where(value<0)[0]
38
+ value[Ts] = L_negative
39
+ return value
40
+
41
+ def voronoi(network,
42
+ geometry,
43
+ **kwargs):
44
+ r"""
45
+ Calculate the centre to centre distance from centroid of pore1 to centroid of throat to centroid of pore2
46
+ This is tricky as connections are defined at network level but centroids are stored on geometry.
47
+ The pore and throat map relates the geometry index to the network index but we must look up the index of the map
48
+ to go back to geometry index of the connected pores.
49
+ This will probably break down when a throat connects two different geometries
50
+ """
51
+ throats = geometry.map_throats(network,geometry.throats())
52
+ connections = network['throat.conns'][throats]
53
+ net_pore1 = connections[:,0]
54
+ net_pore2 = connections[:,1]
55
+ pore_centroids = network['pore.centroid']
56
+ throat_centroids = network['throat.centroid'][throats]
57
+ v1 = throat_centroids-pore_centroids[net_pore1]
58
+ v2 = throat_centroids-pore_centroids[net_pore2]
59
+ value = _sp.ndarray(len(connections))
60
+ for i in range(len(connections)):
61
+ value[i] = _sp.linalg.norm(v1[i])+_sp.linalg.norm(v2[i])
62
+ return value
63
+
64
+ def c2c(network,
65
+ geometry,
66
+ **kwargs):
67
+ r"""
68
+ Calculate throat length
69
+ """
70
+ #Initialize throat_property['length']
71
+ throats = network.throats(geometry.name)
72
+ pore1 = network['throat.conns'][:,0]
73
+ pore2 = network['throat.conns'][:,1]
74
+ C1 = network['pore.coords'][pore1]
75
+ C2 = network['pore.coords'][pore2]
76
+ E = _sp.sqrt(_sp.sum((C1-C2)**2,axis=1)) #Euclidean distance between pores
77
+ value = E
78
+ value = value[throats]
79
+ if _sp.any(value<0):
80
+ geometry._logger.warning('Negative throat lengths are calculated. Arbitrary positive length assigned (1e9 meters)')
81
+ Ts = _sp.where(value<0)[0]
82
+ value[Ts] = 1e-9
83
+ return value
84
+
85
+ def constant(network,
86
+ geometry,
87
+ const,
88
+ **kwargs):
89
+ r"""
90
+ Calculate throat length
91
+ """
92
+ #Initialize throat_property['length']
93
+ throats = network.throats(geometry.name)
94
+ value = _sp.ones(len(throats))*const
95
+ return value
@@ -0,0 +1,42 @@
1
+ r"""
2
+ ===============================================================================
3
+ throat_misc -- Miscillaneous and generic functions to apply to throats
4
+ ===============================================================================
5
+
6
+ """
7
+ import scipy as _sp
8
+
9
+ def random(geometry,
10
+ seed=None,
11
+ num_range=[0,1],
12
+ **kwargs):
13
+ r"""
14
+ Assign random number to throats
15
+ note: should this be called 'poisson'?
16
+ """
17
+ range_size = num_range[1]-num_range[0]
18
+ range_min = num_range[0]
19
+ _sp.random.seed(seed)
20
+ value = _sp.random.rand(geometry.num_throats(),)
21
+ value = value*range_size + range_min
22
+ return value
23
+
24
+
25
+ def neighbor(geometry,
26
+ network,
27
+ pore_prop='pore.seed',
28
+ mode='min',
29
+ **kwargs):
30
+ r"""
31
+ Adopt a value based on the neighboring pores
32
+ """
33
+ throats = network.throats(geometry.name)
34
+ P12 = network.find_connected_pores(throats)
35
+ pvalues = network[pore_prop][P12]
36
+ if mode == 'min':
37
+ value = _sp.amin(pvalues,axis=1)
38
+ if mode == 'max':
39
+ value = _sp.amax(pvalues,axis=1)
40
+ if mode == 'mean':
41
+ value = _sp.mean(pvalues,axis=1)
42
+ return value
@@ -0,0 +1,31 @@
1
+ r"""
2
+ ===============================================================================
3
+ throat_normal -- calculate from the normal vector to the throat vertices
4
+ ===============================================================================
5
+
6
+ """
7
+ import scipy as sp
8
+ from scipy.spatial import ConvexHull
9
+
10
+ def voronoi(network,
11
+ geometry,
12
+ **kwargs):
13
+ r"""
14
+ Update the throat normals from the voronoi vertices
15
+ """
16
+ verts = geometry["throat.vertices"]
17
+ value = sp.ndarray([len(verts),3])
18
+ for i in range(len(verts)):
19
+ if len(sp.unique(verts[i][:,0]))==1:
20
+ verts_2d = sp.vstack((verts[i][:,1],verts[i][:,2])).T
21
+ elif len(sp.unique(verts[i][:,1]))==1:
22
+ verts_2d = sp.vstack((verts[i][:,0],verts[i][:,2])).T
23
+ else:
24
+ verts_2d = sp.vstack((verts[i][:,0],verts[i][:,1])).T
25
+ hull = ConvexHull(verts_2d, qhull_options='QJ Pp')
26
+ sorted_verts = verts[i][hull.vertices]
27
+ v1 = sorted_verts[1]-sorted_verts[0]
28
+ v2 = sorted_verts[-1]-sorted_verts[0]
29
+ value[i] = sp.cross(v1,v2)
30
+
31
+ return value
@@ -0,0 +1,191 @@
1
+ r"""
2
+ ===============================================================================
3
+ throat_offset_vertices -- Offeset throat vertices using a fibre radius parameter
4
+ ===============================================================================
5
+
6
+ """
7
+ import scipy as sp
8
+ import OpenPNM.Utilities.vertexops as vo
9
+ import OpenPNM.Utilities.transformations as tr
10
+
11
+ def voronoi(network,
12
+ geometry,
13
+ offset,
14
+ **kwargs):
15
+ r"""
16
+ Offset the throat vertices effectively erroding the facet by the offset distance supplied
17
+ """
18
+ Nt = geometry.num_throats()
19
+ area = sp.ndarray(Nt)
20
+ perimeter = sp.ndarray(Nt)
21
+ offset_verts = sp.ndarray(Nt,dtype=object)
22
+ offset_error = sp.ndarray(Nt)
23
+ throat_COM = sp.ndarray([Nt,3])
24
+ for i in range(Nt):
25
+ offset_rand = (sp.random.rand(1)-0.5)*offset
26
+ throat_verts=geometry["throat.vertices"][i]
27
+ throat_normal=geometry["throat.normal"][i]
28
+ area[i],perimeter[i],offset_verts[i],throat_COM[i],offset_error[i] = vo.get_throat_geom(throat_verts,throat_normal,offset)
29
+
30
+ for i in range(Nt):
31
+ if offset_error[i] > 0 and len(offset_verts[i]) > 0:
32
+ offset_verts[i]=[]
33
+ "Properties that depend on the offset vertices are the area, perimeter and the centroid or COM"
34
+ "To speed things up we could save them all now rather than processing them individually"
35
+ if kwargs["set_dependent"]==True:
36
+ geometry["throat.area"]=area
37
+ geometry["throat.perimeter"]=perimeter
38
+ geometry["throat.centroid"]=throat_COM
39
+
40
+ return offset_verts
41
+
42
+ def distance_transform(network,
43
+ geometry,
44
+ offset,
45
+ **kwargs):
46
+ r"""
47
+ Use the Voronoi vertices and perform image analysis to obtain throat properties
48
+ """
49
+
50
+ import math
51
+ import numpy as np
52
+ from skimage.morphology import convex_hull_image
53
+ from skimage.measure import regionprops
54
+ from scipy import ndimage
55
+
56
+ Nt = geometry.num_throats()
57
+ area = sp.zeros(Nt)
58
+ perimeter = sp.zeros(Nt)
59
+ centroid = sp.zeros([Nt,3])
60
+ incentre = sp.zeros([Nt,3])
61
+ inradius = sp.zeros(Nt)
62
+ equiv_diameter = sp.zeros(Nt)
63
+ eroded_verts = sp.ndarray(Nt,dtype=object)
64
+
65
+ res=200
66
+ vertices = geometry["throat.vertices"]
67
+ normals = geometry["throat.normal"]
68
+ z_axis = [0,0,1]
69
+
70
+ for i in range(Nt):
71
+ " For boundaries some facets will already be aligned with the axis - if this is the case a rotation is unnecessary and could also cause problems "
72
+ angle = tr.angle_between_vectors(normals[i],z_axis)
73
+ if (angle==0.0)or(angle==np.pi):
74
+ "We are already aligned"
75
+ rotate_facet = False
76
+ facet = vertices[i]
77
+ else:
78
+ rotate_facet = True
79
+ M = tr.rotation_matrix(tr.angle_between_vectors(normals[i],z_axis),tr.vector_product(normals[i],z_axis))
80
+ facet = np.dot(vertices[i],M[:3,:3].T)
81
+ x = facet[:,0]
82
+ y = facet[:,1]
83
+ z = facet[:,2]
84
+ "Get points in 2d for image analysis"
85
+ pts = np.column_stack((x,y))
86
+ "translate points so min sits at the origin"
87
+ translation = [pts[:,0].min(),pts[:,1].min()]
88
+ pts -= translation
89
+ order = np.int(math.ceil(-np.log10(np.max(pts))))
90
+ "Normalise and scale the points so that largest span equals the resolution to save on memory and create clear image"
91
+ max_factor = np.max([pts[:,0].max(),pts[:,1].max()])
92
+ f = res/max_factor
93
+ "Scale the offset and define a circular structuring element with radius"
94
+ r = f*offset
95
+ "Only proceed if r is less than half the span of the image"
96
+ if r <= res/2:
97
+ pts *= f
98
+ minp1 = pts[:,0].min()
99
+ minp2 = pts[:,1].min()
100
+ maxp1 = pts[:,0].max()
101
+ maxp2 = pts[:,1].max()
102
+ img = np.zeros([np.int(math.ceil(maxp1-minp1)+1),np.int(math.ceil(maxp2-minp2)+1)])
103
+ int_pts = np.around(pts,0).astype(int)
104
+ for pt in int_pts:
105
+ img[pt[0]][pt[1]]=1
106
+ "Pad with zeros all the way around the edges"
107
+ img_pad = np.zeros([np.shape(img)[0]+2,np.shape(img)[1]+2])
108
+ img_pad[1:np.shape(img)[0]+1,1:np.shape(img)[1]+1]=img
109
+
110
+ "All points should lie on this plane but could be some rounding errors so use the order parameter"
111
+ z_plane = sp.unique(np.around(z,order+2))
112
+ if len(z_plane) > 1:
113
+ print("rotation for image analysis failed")
114
+ "Fill in the convex hull polygon"
115
+ convhullimg = convex_hull_image(img_pad)
116
+ "Perform a Distance Transform and black out points less than r to create binary erosion"
117
+ "This is faster than performing an erosion and dt can also be used later to find incircle"
118
+ eroded = ndimage.distance_transform_edt(convhullimg)
119
+ #eroded = dt.copy()
120
+ eroded[eroded<=r]=0
121
+ eroded[eroded>r]=1
122
+ "If we are left with less than 3 non-zero points then the throat is fully occluded"
123
+ if np.sum(eroded)>=3:
124
+ "Do some image analysis to extract the key properties"
125
+ regions = regionprops(eroded[1:np.shape(img)[0]+1,1:np.shape(img)[1]+1].astype(int))
126
+ if len(regions) == 1: # Change this to cope with genuine multi-region throats
127
+ for props in regions:
128
+ x0,y0 = props.centroid
129
+ equiv_diameter[i] = props.equivalent_diameter
130
+ area[i] = props.area
131
+ perimeter[i] = props.perimeter
132
+ coords = props.coords
133
+ "Undo the translation, scaling and truncation on the centroif"
134
+ centroid2d = [x0,y0]/f
135
+ centroid2d += (translation)
136
+ centroid3d = np.concatenate((centroid2d,z_plane))
137
+ "Distance transform the eroded facet to find the incentre and inradius"
138
+ #dt[dt>r] -= r
139
+ dt = ndimage.distance_transform_edt(eroded)
140
+ inx0,iny0 = np.asarray(np.unravel_index(dt.argmax(), dt.shape)).astype(float)
141
+ incentre2d=[inx0,iny0]
142
+ "Undo the translation, scaling and truncation on the incentre"
143
+ incentre2d /= f
144
+ incentre2d += (translation)
145
+ incentre3d = np.concatenate((incentre2d,z_plane))
146
+ "The offset vertices will be those in the coords that are closest to the originals"
147
+ offset_verts = []
148
+ for pt in int_pts:
149
+ vert = np.argmin(np.sum(np.square(coords-pt),axis=1))
150
+ if vert not in offset_verts:
151
+ offset_verts.append(vert)
152
+ "If we are left with less than 3 different vertices then the throat is fully occluded as we can't make a shape with non-zero area"
153
+ if len(offset_verts) >= 3:
154
+ offset_coords = coords[offset_verts].astype(float)
155
+ "Undo the translation, scaling and truncation on the offset_verts"
156
+ offset_coords /= f
157
+ offset_coords_3d = np.vstack((offset_coords[:,0]+translation[0],offset_coords[:,1]+translation[1],np.ones(len(offset_verts))*z_plane)).T
158
+
159
+ " Get matrix to un-rotate the co-ordinates back to the original orientation if we rotated in the first place"
160
+ if (rotate_facet):
161
+ MI = tr.inverse_matrix(M)
162
+ " Unrotate the offset coordinates "
163
+ incentre[i] = np.dot(incentre3d,MI[:3,:3].T)
164
+ centroid[i] = np.dot(centroid3d,MI[:3,:3].T)
165
+ eroded_verts[i] = np.dot(offset_coords_3d,MI[:3,:3].T)
166
+
167
+ else:
168
+ incentre[i] = incentre3d
169
+ centroid[i] = centroid3d
170
+ eroded_verts[i] = offset_coords_3d
171
+
172
+ inradius[i] = dt.max()
173
+ "Undo scaling on other parameters"
174
+ area[i] /= f*f
175
+ perimeter[i] /= f
176
+ equiv_diameter[i] /= f
177
+ inradius[i] /= f
178
+ else:
179
+ area[i]=0
180
+ perimeter[i]=0
181
+ equiv_diameter[i]=0
182
+
183
+ if kwargs["set_dependent"]==True:
184
+ geometry["throat.area"] = area
185
+ geometry["throat.perimeter"] = perimeter
186
+ geometry["throat.centroid"] = centroid
187
+ geometry["throat.diameter"] = equiv_diameter
188
+ geometry["throat.indiameter"] = inradius*2
189
+ geometry["throat.incentre"] = incentre
190
+
191
+ return eroded_verts
@@ -0,0 +1,26 @@
1
+ r"""
2
+ ===============================================================================
3
+ Submodule -- throat_perimeter
4
+ ===============================================================================
5
+
6
+ """
7
+ import scipy as _sp
8
+ import OpenPNM.Utilities.transformations as tr
9
+ import OpenPNM.Utilities.vertexops as vo
10
+
11
+ def voronoi(geometry,
12
+ **kwargs):
13
+ r"""
14
+ Use the Voronoi verts and throat normals to work out the perimeter
15
+ """
16
+ Nt = geometry.num_throats()
17
+ verts = geometry['throat.offset_vertices']
18
+ normals = geometry['throat.normal']
19
+ perimeter = _sp.ndarray(Nt)
20
+ for i in range(Nt):
21
+ if len(verts[i]) > 2:
22
+ verts_2D = tr.rotate_and_chop(verts[i],normals[i],[0,0,1])
23
+ perimeter[i] = vo.PolyPerimeter2D(verts_2D)
24
+ else:
25
+ perimeter[i] = 0.0
26
+ return perimeter
@@ -0,0 +1,12 @@
1
+ r"""
2
+ ===============================================================================
3
+ Submodule -- throat_seeds
4
+ ===============================================================================
5
+
6
+ """
7
+ import scipy as _sp
8
+
9
+
10
+ def na():
11
+ pass
12
+
@@ -0,0 +1,37 @@
1
+ r"""
2
+ ===============================================================================
3
+ Submodule -- throat_shape_factor
4
+ ===============================================================================
5
+
6
+ """
7
+ import scipy as _sp
8
+
9
+ def compactness(geometry,
10
+ throat_perimeter='throat.perimeter',
11
+ throat_area='throat.area',
12
+ **kwargs):
13
+ r"""
14
+ Mortensen et al. have shown that the Hagen-Poiseuille hydraluic resistance is linearly dependent on the compactness
15
+ Defined as perimeter^2/area.
16
+ The dependence is not universal as shapes with sharp corners provide more resistance than those that are more elliptical
17
+ Count the number of vertices and apply the right correction
18
+ """
19
+ "Only apply to throats with an area"
20
+ ts = geometry.throats()[geometry[throat_area]>0]
21
+ P = geometry[throat_perimeter]
22
+ A = geometry[throat_area]
23
+ C = _sp.ones(geometry.num_throats())
24
+ C[ts] = P[ts]**2/A[ts]
25
+ verts = geometry["throat.offset_vertices"]
26
+ for i in range(len(verts)):
27
+ if len(verts[i]) == 3:
28
+ "Triangular Correction"
29
+ C[i] = C[i]*(25/17) + (40*_sp.sqrt(3)/17)
30
+ elif len(verts[i]) == 4:
31
+ "Rectangular Correction"
32
+ C[i] = C[i]*(22/7) + (65/3)
33
+ elif len(verts[i]) > 4:
34
+ "Approximate Elliptical Correction"
35
+ C[i] = C[i]*(8/3) + (8*_sp.pi/3)
36
+
37
+ return C
@@ -0,0 +1,44 @@
1
+ r"""
2
+ ===============================================================================
3
+ Submodule -- throat_surface_area
4
+ ===============================================================================
5
+
6
+ """
7
+ import scipy as _sp
8
+
9
+ def cylinder(geometry,
10
+ throat_diameter='throat.diameter',
11
+ throat_length='throat.length',
12
+ **kwargs):
13
+ r"""
14
+ Calculate throat area for a cylindrical throat
15
+ """
16
+ D = geometry[throat_diameter]
17
+ L = geometry[throat_length]
18
+ value = _sp.constants.pi*D*L
19
+ return value
20
+
21
+ def cuboid(geometry,
22
+ throat_diameter='throat.diameter',
23
+ throat_length='throat.length',
24
+ **kwargs):
25
+ r"""
26
+ Calculate throat area for a cuboid throat
27
+ """
28
+ D = geometry[throat_diameter]
29
+ L = geometry[throat_length]
30
+ value = 4*D*L
31
+ return value
32
+
33
+ def extrusion(geometry,
34
+ throat_perimeter='throat.perimeter',
35
+ throat_length='throat.length',
36
+ **kwargs):
37
+ r"""
38
+ Calculate surface area from perimeter and length -
39
+ perimeter calculated when throat area is calculated so must be run in correct order
40
+ """
41
+ P = geometry[throat_perimeter]
42
+ L = geometry[throat_length]
43
+ value = P*L
44
+ return value