1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
|
// 2009 © Václav Šmilauer <eudoxos@arcig.cz>
#include <lib/base/Logging.hpp>
#include <lib/base/Math.hpp>
#include <lib/pyutil/doc_opts.hpp>
#include <preprocessing/dem/SpherePack.hpp>
CREATE_CPP_LOCAL_LOGGER("_packSpheres.cpp");
// BOOST_PYTHON_MODULE cannot be inside yade namespace, it has 'extern "C"' keyword, which strips it out of any namespaces.
BOOST_PYTHON_MODULE(_packSpheres)
try {
using SpherePack = ::yade::SpherePack;
using Vector3r = ::yade::Vector3r;
using Matrix3r = ::yade::Matrix3r;
using Real = ::yade::Real;
boost::python::scope().attr("__doc__") = "Creation, manipulation, IO for generic sphere packings.";
YADE_SET_DOCSTRING_OPTS;
boost::python::class_<SpherePack>(
"SpherePack",
"Set of spheres represented as centers and radii. This class is returned by :yref:`yade.pack.randomDensePack`, "
":yref:`yade.pack.randomPeriPack` and others. The object supports iteration over spheres, as in \n\n\t>>> sp=SpherePack()\n\t>>> for "
"center,radius in sp: print(center,radius)\n\n\t>>> for sphere in sp: print(sphere[0],sphere[1]) ## same, but without unpacking the tuple "
"automatically\n\n\t>>> for i in range(0,len(sp)): print(sp[i][0], sp[i][1]) ## same, but accessing spheres by index\n\n\n.. admonition:: "
"Special constructors\n\n\tConstruct from list of ``[(c1,r1),(c2,r2),…]``. To convert two same-length lists of ``centers`` and ``radii``, "
"construct with ``zip(centers,radii)``.\n",
boost::python::init<boost::python::optional<boost::python::list>>(
boost::python::args("list"), "Empty constructor, optionally taking list [ ((cx,cy,cz),r), … ] for initial data."))
.def("add", &SpherePack::add, "Add single sphere to packing, given center as 3-tuple and radius")
.def("toList", &SpherePack::toList, "Return packing data as python list.")
.def("fromList", &SpherePack::fromList, "Make packing from given list, same format as for constructor. Discards current data.")
.def("fromList",
&SpherePack::fromLists,
(boost::python::arg("centers"), boost::python::arg("radii")),
"Make packing from given list, same format as for constructor. Discards current data.")
.def("load", &SpherePack::fromFile, (boost::python::arg("fileName")), "Load packing from external text file (current data will be discarded).")
.def("save", &SpherePack::toFile, (boost::python::arg("fileName")), "Save packing to external text file (will be overwritten).")
.def("fromSimulation", &SpherePack::fromSimulation, "Make packing corresponding to the current simulation. Discards current data.")
//The basic sphere generator
.def("makeCloud",
&SpherePack::makeCloud,
(boost::python::arg("minCorner") = Vector3r(Vector3r::Zero()),
boost::python::arg("maxCorner") = Vector3r(Vector3r::Zero()),
boost::python::arg("rMean") = -1,
boost::python::arg("rRelFuzz") = 0,
boost::python::arg("num") = -1,
boost::python::arg("periodic") = false,
boost::python::arg("porosity") = 0.65,
boost::python::arg("psdSizes") = std::vector<Real>(),
boost::python::arg("psdCumm") = std::vector<Real>(),
boost::python::arg("distributeMass") = false,
boost::python::arg("seed") = -1,
boost::python::arg("hSize") = Matrix3r(Matrix3r::Zero())),
R"""(Create a random cloud of particles enclosed in a parallelepiped. The resulting packing is a gas-like state with no contacts between particles initially. Usually used as a first step before reaching a dense packing.
:param Vector3 minCorner: lower corner of an axis-aligned box
:param Vector3 maxCorner: upper corner of an axis-aligned box
:param Matrix3 hSize: base vectors of a generalized box (arbitrary parallelepiped, typically :yref:`Cell::hSize`), superseeds minCorner and maxCorner if defined. For periodic boundaries only.
:param float rMean: mean radius or spheres
:param float rRelFuzz: dispersion of radius relative to rMean
:param int num: number of spheres to be generated. If negative (default), generate as many as possible with stochastic sizes, ending after a fixed number of tries to place the sphere in space, else generate exactly ``num`` spheres with deterministic size distribution.
:param bool periodic: whether the packing to be generated should be periodic
:param float porosity: initial guess for the iterative generation procedure (if ``num``>1). The algorithm will be retrying until the number of generated spheres is ``num``. The first iteration tries with the provided porosity, but next iterations increase it if necessary (hence an initialy high porosity can speed-up the algorithm). If ``psdSizes`` is not defined, ``rRelFuzz`` ($z$) and ``num`` ($N$) are used so that the porosity given ($\rho$) is approximately achieved at the end of generation, $r_m=\sqrt[3]{\frac{V(1-\rho)}{\frac{4}{3}\pi(1+z^2)N}}$. The default is $\rho$=0.5. The optimal value depends on ``rRelFuzz`` or ``psdSizes``.
:param psdSizes: sieve sizes (particle diameters) when particle size distribution (PSD) is specified.
:param psdCumm: cummulative fractions of particle sizes given by ``psdSizes``; must be the same length as *psdSizes* and should be non-decreasing.
:param bool distributeMass: if ``True``, given distribution reflects mass per radius (the most common), else number of spheres per radius.
:param seed: number used to initialize the random number generator.
:returns: number of created spheres, which can be lower than ``num`` depending on the method used.
.. note::
- Works in 2D if ``minCorner[k]=maxCorner[k]`` for one coordinate.
- If ``num`` is defined, then sizes generation is deterministic, giving the best fit of target distribution. It enables spheres placement in descending size order, thus giving lower porosity than the random generation.
- By default (with ``distributeMass==False``), the distribution is applied to particle count (i.e. particle count percent passing). The typical geomechanics sense of "particle size distribution" is the distribution of *mass fraction* (i.e. mass percent passing); this can be achieved with ``distributeMass=True``.
- Sphere radius distribution can be specified using one of the following ways:
1. ``rMean``, ``rRelFuzz`` and ``num`` gives uniform radius distribution in $rMean×(1 \pm rRelFuzz)$. Less than ``num`` spheres can be generated if it is too high.
2. ``rRelFuzz``, ``num`` and (optional) ``porosity``, which estimates mean radius so that ``porosity`` is attained at the end. ``rMean`` must be less than 0 (default). ``porosity`` is only an initial guess for the generation algorithm, which will retry with higher porosity until the prescibed *num* is obtained.
3. ``psdSizes`` and ``psdCumm``, two arrays specifying points of the `particle size distribution <http://en.wikipedia.org/wiki/Particle_size_distribution>`_ function. As many spheres as possible are generated.
4. ``psdSizes``, ``psdCumm``, ``num``, and (optional) ``porosity``, like above but if ``num`` is not obtained, ``psdSizes`` will be scaled down uniformly, until ``num`` is obtained (see :yref:`appliedPsdScaling<yade._packSpheres.SpherePack.appliedPsdScaling>`).
)""")
.def("psd",
&SpherePack::psd,
(boost::python::arg("bins") = 50, boost::python::arg("mass") = true),
"Return `particle size distribution <http://en.wikipedia.org/wiki/Particle_size_distribution>`__ of the packing.\n\n:param int bins: "
"number of bins between minimum and maximum diameter\n:param mass: Compute relative mass rather than relative particle count for each "
"bin. Corresponds to :yref:`distributeMass parameter for makeCloud<yade.pack.SpherePack.makeCloud>`.\n:returns: tuple of "
"``(cumm,edges)``, where ``cumm`` are cummulative fractions for respective diameters and ``edges`` are those diameter values. Dimension "
"of both arrays is equal to ``bins+1``.")
//The variant for clumps
.def("makeClumpCloud",
&SpherePack::makeClumpCloud,
(boost::python::arg("minCorner"),
boost::python::arg("maxCorner"),
boost::python::arg("clumps"),
boost::python::arg("periodic") = false,
boost::python::arg("num") = -1,
boost::python::arg("seed") = -1),
R"""(Create a random (in particles positions and orientations) cloud of clumps the same way `makeCloud <https://yade-dem.org/doc/yade.pack.html?highlight=makecloud#yade._packSpheres.SpherePack.makeCloud>`_ does with spheres. The parameters ``minCorner``, ``maxCorner``, ``periodic``, ``num`` and ``seed`` are the same as in makeCloud_. The parameter ``clumps`` is a list containing all the different clumps to be appended as ``SpherePack`` objects. Here is an exemple that shows how to create a cloud made of 10 identical clumps :
.. code-block:: python
clp = SpherePack([((0,0,0), 1e-2), ((1e-2,0,0), 1e-2)]) # The clump we want a cloud of
sp = SpherePack()
sp.makeClumpCloud((0,0,0), (1,1,1), [clp], num=10, seed=42)
sp.toSimulation() # All the particles in the cloud are now appended to O.bodies
)""")
.def("aabb", &SpherePack::aabb_py, "Get axis-aligned bounding box coordinates, as 2 3-tuples.")
.def("dim", &SpherePack::dim, "Return dimensions of the packing in terms of aabb(), as a 3-tuple.")
.def("center", &SpherePack::midPt, "Return coordinates of the bounding box center.")
.def_readwrite(
"cellSize",
&SpherePack::cellSize,
"Size of periodic cell; is Vector3(0,0,0) if not periodic. (Change this property only if you know what you're doing).")
.def_readwrite("isPeriodic", &SpherePack::isPeriodic, "was the packing generated in periodic boundaries?")
.def("cellFill",
&SpherePack::cellFill,
"Repeat the packing (if periodic) so that the results has dim() >= given size. The packing retains periodicity, but changes cellSize. "
"Raises exception for non-periodic packing.")
.def("cellRepeat",
&SpherePack::cellRepeat,
"Repeat the packing given number of times in each dimension. Periodicity is retained, cellSize changes. Raises exception for non-periodic "
"packing.")
.def("relDensity",
&SpherePack::relDensity,
"Relative packing density, measured as sum of spheres' volumes / aabb volume.\n(Sphere overlaps are ignored.)")
.def("translate", &SpherePack::translate, "Translate all spheres by given vector.")
.def("rotate",
&SpherePack::rotate,
(boost::python::arg("axis"), boost::python::arg("angle")),
"Rotate all spheres around packing center (in terms of aabb()), given axis and angle of the rotation.")
.def("scale", &SpherePack::scale, "Scale the packing around its center (in terms of aabb()) by given factor (may be negative).")
.def("hasClumps", &SpherePack::hasClumps, "Whether this object contains clumps.")
.def("getClumps",
&SpherePack::getClumps,
"Return lists of sphere ids sorted by clumps they belong to. The return value is (standalones,[clump1,clump2,…]), where each item is list "
"of id's of spheres.")
.def("__len__", &SpherePack::len, "Get number of spheres in the packing")
.def("__getitem__", &SpherePack::getitem, "Get entry at given index, as tuple of center and radius.")
.def("__iter__", &SpherePack::getIterator, "Return iterator over spheres.")
.def_readonly("appliedPsdScaling", &SpherePack::appliedPsdScaling, "A factor between 0 and 1, uniformly applied on all sizes of of the PSD.");
boost::python::class_<SpherePack::_iterator>("SpherePackIterator", boost::python::init<SpherePack::_iterator&>())
.def("__iter__", &SpherePack::_iterator::iter)
.def("__next__", &SpherePack::_iterator::next);
} catch (...) {
LOG_FATAL("Importing this module caused an exception and this module is in an inconsistent state now.");
PyErr_Print();
PyErr_SetString(PyExc_SystemError, __FILE__);
boost::python::handle_exception();
throw;
}
|