Skip to content

Commit 1fd4a14

Browse files
authored
Polymesh exterior normals, output plot fixes, faster seed positioning (#23)
* stop tracking duplicate aluminum micro file * facets on the exterior of the polymesh now have outward normals (#16) * updated changelog * changed plot limits, trying to get 3D axes equal * updated trimesh plotting in CLI * fixed typo in order of lines for empirical PDFs * added option to maximize the minimum edge length in a polygonal mesh * simplified seedlist indexing * optimized seedlist positioning method uses BFS in aabb tree for overlap detection and samples 100 positions at a time * fixed the logo script * fixed 2d axes behavior * simplified foam example * updated changelog * added missing zlim * updated sphinx version * indented changelog * removed unneeded version string functions * bumped version * simplified version getting
1 parent 6030911 commit 1fd4a14

File tree

17 files changed

+483
-134
lines changed

17 files changed

+483
-134
lines changed

CHANGELOG.rst

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,25 @@ All notable changes to this project will be documented in this file.
66
The format is based on `Keep a Changelog`_,
77
and this project adheres to `Semantic Versioning`_.
88

9+
`1.3.0`_ - 2020-06-25
10+
--------------------------
11+
Added
12+
'''''
13+
- Option to reduce the presence of short edges in polygonal meshes.
14+
15+
Changed
16+
'''''''
17+
- Optimized seed positioning algorithm by using breadth-first search
18+
in the AABB tree.
19+
- Facets in polygonal meshes are now always defined with a positive
20+
outward normal vector.
21+
22+
Fixed
23+
'''''
24+
- Plotting of 3D meshes.
25+
- Documentation for empirical PDFs.
26+
- Minor errors in examples.
27+
928
`1.2.2`_ - 2020-05-14
1029
--------------------------
1130
Fixed
@@ -88,7 +107,8 @@ Added
88107

89108
.. LINKS
90109
91-
.. _`Unreleased`: https://github.com/kip-hart/MicroStructPy/compare/v1.2.2...HEAD
110+
.. _`Unreleased`: https://github.com/kip-hart/MicroStructPy/compare/v1.3.0...HEAD
111+
.. _`1.3.0`: https://github.com/kip-hart/MicroStructPy/compare/v1.2.2...v1.3.0
92112
.. _`1.2.2`: https://github.com/kip-hart/MicroStructPy/compare/v1.2.1...v1.2.2
93113
.. _`1.2.1`: https://github.com/kip-hart/MicroStructPy/compare/v1.2.0...v1.2.1
94114
.. _`1.2.0`: https://github.com/kip-hart/MicroStructPy/compare/v1.1.2...v1.2.0

docs/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
matplotlib
22
numpy==1.17.3
33
pybind11==2.4.3
4-
sphinx==2.2.1
4+
sphinx==3.1.1
55
sphinx-gallery==0.4.0

docs/source/cli/material.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,12 @@ the complete list of available distributions and their input parameters.
150150

151151
In the case that the distribution type is "pdf" then the only other field
152152
should be ``filename``.
153-
For a PDF, the file should contain two lines: the first has the (n+1) bin
154-
locations and the second has the (n) bin heights.
153+
For a PDF, the file should contain two lines: the first has the (n) bin
154+
heights and the second has the (n+1) bin locations.
155155
A PDF file could contain, for example::
156156

157-
1, 2, 2.5
158157
0.5, 1
158+
1, 2, 2.5
159159

160160
For a CDF, the file should have two columns: the first being the size and the
161161
second being the CDF value.

docs/source/cli/settings.rst

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ quality, among other things. The default settings are:
4040
4141
<rtol> fit </rtol>
4242
43+
<edge_opt> False </edge_opt>
44+
<edge_opt_n_iter> 100 </edge_opt_n_iter>
45+
4346
<mesh_max_volume> inf </mesh_max_volume>
4447
<mesh_min_angle> 0 </mesh_min_angle>
4548
<mesh_max_edge_length> inf </mesh_max_edge_length>
@@ -244,6 +247,40 @@ an rtol value that maximizes the fit between input and output distributions.
244247
Acceptable values of rtol are 0 to 1 inclusive, though rtol below 0.2 will
245248
likely result in long runtimes.
246249

250+
edge_opt
251+
--------
252+
253+
The edge_opt field provides the option to maximize the shortest edge in the
254+
polygonal/polyhedral mesh.
255+
The default is ``<edge_opt> False </edge_opt>``, which skips the optimization
256+
process.
257+
This optimization is performed by making small adjustments to the positions of
258+
seeds surrounding the shortest edge, assessing if the change created an
259+
improvement, then either a) attempting a different change for the same edge if
260+
there was not improvement or b) moving on to the new shortest edge.
261+
The optimization algorithm exits when ``edge_opt_n_iter`` iterations have been
262+
performed on the same edge.
263+
264+
This flag is useful if the polygonal/polyhedral or triangular/tetrahedral are
265+
used in numerical simulations, such as finite element analysis.
266+
A high ratio of longest edge to shortest edge leads to a high ratio in maximum
267+
to minimum eigenvalue in FEA stiffness matrices, which can create problems for
268+
the FEA solver.
269+
Setting ``edge_opt`` to ``True`` will reduce short edges in the polygonal mesh,
270+
which translates into reduced short edges in the triangular mesh.
271+
This optimization process, however, will increase the time to generate a
272+
polygonal mesh.
273+
To track the progress of the optimizer, set ``verbose`` to ``True``.
274+
275+
edge_opt_n_iter
276+
---------------
277+
278+
This field specifies how many times the optimizer should attempt to increase
279+
the length of the shortest edge in the polygonal mesh.
280+
The default is ``<edge_opt_n_iter> 100 </edge_opt_n_iter>``, which limits the
281+
optimizer to 100 attempts per edge.
282+
This field is ignored if ``edge_opt`` is set to ``False``.
283+
247284
mesh_max_volume
248285
---------------
249286

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
aabbtree==2.3.1
1+
aabbtree==2.5.0
22
matplotlib==3.0.2
33
pybind11==2.4.3
44
MeshPy==2018.2.1

setup.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from setuptools import setup
1414

1515
desc = 'Microstructure modeling, mesh generation, analysis, and visualization.'
16-
vs_fname = ('src/microstructpy', '_vs.py')
1716

1817

1918
def read(*fname):
@@ -26,18 +25,12 @@ def find_version(*fname):
2625
if line.startswith('__version__') and '=' in line:
2726
ver_str = line.split('=')[-1].strip().strip('\"').strip('\'')
2827
break
29-
30-
for line in read(*vs_fname).split('\n'):
31-
if '+' in line:
32-
tag = line.split('+')[-1].strip().strip('\"').strip('\'')
33-
ver_str += '+' + tag
34-
break
3528
return ver_str
3629

3730

3831
setup(
3932
name='microstructpy',
40-
version=find_version('src/microstructpy', '__init__.py'),
33+
version=find_version('src', 'microstructpy', '__init__.py'),
4134
license='MIT License',
4235
description=desc,
4336
long_description=read('README.rst'),
@@ -82,8 +75,8 @@ def find_version(*fname):
8275
'multi-sphere'
8376
],
8477
install_requires=[
85-
'aabbtree',
86-
'matplotlib>=2.2.0,<3.1.0',
78+
'aabbtree>=2.5.0',
79+
'matplotlib>=3.0.0',
8780
'pybind11', # must come before meshpy for successful install
8881
'lsq-ellipse',
8982
'meshpy>=2018.2.1',

src/microstructpy/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,5 @@
33
import microstructpy.meshing
44
import microstructpy.seeding
55
import microstructpy.verification
6-
from microstructpy._vs import _ver_str
76

8-
__version__ = '1.2.2'
9-
__version__ = _ver_str(__version__)
7+
__version__ = '1.3.0'

src/microstructpy/_misc.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919

2020
mpl_plural_kwargs = {'edgecolors', 'facecolors', 'linewidths', 'antialiaseds',
2121
'offsets'}
22+
plt_3d_adj = {
23+
'left': 0.4,
24+
'right': 1,
25+
'bottom': 0,
26+
'top': 0.8,
27+
}
2228

2329

2430
# --------------------------------------------------------------------------- #
@@ -163,3 +169,21 @@ def tangent_sphere(points, radii=None, simplices=None):
163169
# return results
164170
spheres = np.hstack((posc, rc.reshape(-1, 1)))
165171
return np.squeeze(spheres)
172+
173+
174+
def axisEqual3D(ax):
175+
'''From stackoverflow: https://stackoverflow.com/a/19248731'''
176+
extents = np.array([getattr(ax, 'get_{}lim'.format(d))() for d in 'xyz'])
177+
sz = extents[:, 1] - extents[:, 0]
178+
centers = np.mean(extents, axis=1)
179+
maxsize = max(abs(sz))
180+
r = maxsize/2
181+
for ctr, dim in zip(centers, 'xyz'):
182+
getattr(ax, 'set_{}lim'.format(dim))(ctr - r, ctr + r)
183+
184+
185+
def ax_objects(ax):
186+
n = 0
187+
for att in ['collections', 'images', 'lines', 'patches', 'texts']:
188+
n += len(getattr(ax, att))
189+
return n

src/microstructpy/_vs.py

Lines changed: 0 additions & 2 deletions
This file was deleted.

src/microstructpy/cli.py

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@ def read_input(filename):
174174

175175

176176
def run(phases, domain, verbose=False, restart=True, directory='.',
177-
filetypes={}, rng_seeds={}, plot_axes=True, rtol='fit',
177+
filetypes={}, rng_seeds={}, plot_axes=True, rtol='fit', edge_opt=False,
178+
edge_opt_n_iter=100,
178179
mesh_max_volume=float('inf'), mesh_min_angle=0,
179180
mesh_max_edge_length=float('inf'), verify=False, color_by='material',
180181
colormap='viridis', seeds_kwargs={}, poly_kwargs={}, tri_kwargs={}):
@@ -243,10 +244,18 @@ def run(phases, domain, verbose=False, restart=True, directory='.',
243244
244245
The default value is ``'fit'``, which uses the mean and variance
245246
of the size distribution to estimate a value for rtol.
247+
edge_opt (bool): *(optional)* This option will maximize the minimum
248+
edge length in the PolyMesh. The seeds associated with the
249+
shortest edge are displaced randomly to find improvement and
250+
this process iterates until `n_iter` attempts have been made
251+
for a given edge. Defaults to False.
252+
edge_opt_n_iter (int): *(optional)* Maximum number of iterations per
253+
edge during optimization. Ignored if `edge_opt` set to False.
254+
Defaults to 100.
246255
mesh_max_volume (float): *(optional)* The maximum volume (area in 2D)
247256
of a mesh cell in the triangular mesh. Default is infinity,
248257
which turns off the maximum volume quality setting.
249-
Value should be stritly positive.
258+
Value should be strictly positive.
250259
mesh_min_angle (float): *(optional)* The minimum interior angle,
251260
in degrees, of a cell in the triangular mesh. For 3D meshes,
252261
this is the dihedral angle between faces of the tetrahedron.
@@ -384,7 +393,8 @@ def run(phases, domain, verbose=False, restart=True, directory='.',
384393
if verbose:
385394
print('Creating polygon mesh.')
386395

387-
pmesh = PolyMesh.from_seeds(seeds, domain)
396+
pmesh = PolyMesh.from_seeds(seeds, domain, edge_opt, edge_opt_n_iter,
397+
verbose)
388398

389399
# Write polymesh
390400
poly_types = filetypes.get('poly', [])
@@ -614,7 +624,6 @@ def plot_seeds(seeds, phases, domain, plot_files=[], plot_axes=True,
614624
ax.get_yaxis().set_visible(False)
615625
else:
616626
ax._axis3don = False
617-
fig.add_axes(ax)
618627

619628
# Plot seeds
620629
edge_kwargs.setdefault('edgecolors', {2: 'k', 3: 'none'}[n_dim])
@@ -624,9 +633,26 @@ def plot_seeds(seeds, phases, domain, plot_files=[], plot_axes=True,
624633
else:
625634
seeds.plot(facecolors=seed_colors, **edge_kwargs)
626635

636+
# Crop to Domain
637+
d_lims = domain.limits
638+
if n_dim == 2:
639+
plt.axis('square')
640+
plt.xlim(d_lims[0])
641+
plt.ylim(d_lims[1])
642+
elif n_dim == 3:
643+
plt.gca().set_xlim(d_lims[0])
644+
plt.gca().set_ylim(d_lims[1])
645+
plt.gca().set_zlim(d_lims[2])
646+
647+
_misc.axisEqual3D(plt.gca())
648+
627649
# Save plot
628650
for fname in plot_files:
629-
plt.savefig(fname, bbox_inches='tight', pad_inches=0)
651+
if n_dim == 3:
652+
fig.subplots_adjust(**_misc.plt_3d_adj)
653+
plt.savefig(fname, bbox_inches='tight', pad_inches=0.15)
654+
else:
655+
plt.savefig(fname, bbox_inches='tight', pad_inches=0)
630656

631657
plt.close('all')
632658

@@ -747,7 +773,11 @@ def plot_poly(pmesh, phases, plot_files=['polymesh.png'], plot_axes=True,
747773

748774
# save plot
749775
for fname in plot_files:
750-
plt.savefig(fname, bbox_inches='tight', pad_inches=0)
776+
if n_dim == 3:
777+
fig.subplots_adjust(**_misc.plt_3d_adj)
778+
plt.savefig(fname, bbox_inches='tight', pad_inches=0.15)
779+
else:
780+
plt.savefig(fname, bbox_inches='tight', pad_inches=0)
751781
plt.close('all')
752782

753783

@@ -848,7 +878,6 @@ def plot_tri(tmesh, phases, seeds, pmesh, plot_files=[], plot_axes=True,
848878
ax.get_yaxis().set_visible(False)
849879
else:
850880
ax._axis3don = False
851-
fig.add_axes(ax)
852881

853882
# Determine which facets are visible
854883
vis_regions = set()
@@ -888,18 +917,27 @@ def plot_tri(tmesh, phases, seeds, pmesh, plot_files=[], plot_axes=True,
888917
# plot triangle mesh
889918
edge_kwargs.setdefault('linewidths', {2: 0.5, 3: 0.1}[n_dim])
890919
edge_kwargs.setdefault('edgecolors', 'k')
891-
if given_names and color_by in ('material', 'material number'):
920+
if color_by in ('material', 'material number'):
892921
n = len(phases)
922+
if n_dim == 2:
923+
cs = seed_colors
924+
else:
925+
cs = facet_colors
926+
cs.append('none')
927+
893928
cs = [_phase_color_by(i, phases, color_by, colormap) for i in range(n)]
894929
cs.append('none')
895930

896931
old_e_att = np.copy(tmesh.element_attributes)
897932
old_f_att = np.copy(tmesh.facet_attributes)
898933
tmesh.element_attributes = [seeds[i].phase for i in old_e_att]
899-
tmesh.facet_attributes = facet_phases
934+
tmesh.facet_attributes = [facet_phases[a] for a in old_f_att]
900935

901-
tmesh.plot(facecolors=cs, index_by='attribute', material=phase_names,
902-
**edge_kwargs)
936+
if given_names:
937+
tmesh.plot(facecolors=cs, index_by='attribute',
938+
material=phase_names, **edge_kwargs)
939+
else:
940+
tmesh.plot(facecolors=cs, index_by='attribute', **edge_kwargs)
903941

904942
tmesh.element_attributes = old_e_att
905943
tmesh.facet_attributes = old_f_att
@@ -909,7 +947,11 @@ def plot_tri(tmesh, phases, seeds, pmesh, plot_files=[], plot_axes=True,
909947

910948
# save plot
911949
for fname in plot_files:
912-
plt.savefig(fname, bbox_inches='tight', pad_inches=0)
950+
if n_dim == 3:
951+
fig.subplots_adjust(**_misc.plt_3d_adj)
952+
plt.savefig(fname, bbox_inches='tight', pad_inches=0.15)
953+
else:
954+
plt.savefig(fname, bbox_inches='tight', pad_inches=0)
913955

914956
plt.close('all')
915957

@@ -943,8 +985,8 @@ def dict_convert(dictionary, filepath='.'):
943985
Second, if the value of ``dist_type`` is ``histogram``, then the remaining
944986
key should also be ``filename`` and its value should be the path to a CSV
945987
file.
946-
For the histogram, the first row of this CDF should be the *n+1* bin
947-
locations and the second row should be the *n* bin heights.
988+
For the histogram, the first row of this CDF should be the *n* bin heights
989+
and the second row should be the *n+1* bin locations.
948990
949991
Additionally, if a key in the dictionary contains ``filename`` or
950992
``directory`` and the value associated with that key is a relative path,

0 commit comments

Comments
 (0)