granular 0.0.0__tar.gz

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.
granular-0.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Steffen Richters-Finger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.1
2
+ Name: granular
3
+ Version: 0.0.0
4
+ Summary: python library for the numerical simulation of granular materials
5
+ Home-page: https://pypi.org/project/granular/
6
+ Author: Steffen Richters-Finger
7
+ Author-email: srichters@uni-muenster.de
8
+ License: MIT
9
+ Project-URL: Source, https://github.com/RichtersFinger/granular
10
+ Platform: UNKNOWN
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Topic :: Scientific/Engineering :: Physics
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+
24
+ # granular
25
+ library for the numerical simulation of granular materials
26
+
27
+
@@ -0,0 +1,2 @@
1
+ # granular
2
+ library for the numerical simulation of granular materials
File without changes
@@ -0,0 +1,202 @@
1
+ """
2
+ # geometry.py
3
+
4
+ This module contains definitions that can be used to generate and process
5
+ grain shapes that are supported by `granular`, i.e., star domains.
6
+
7
+
8
+ ## Sampling
9
+ A sampling-process (useful for generating different representations of
10
+ grains) can be performed using the `SamplingStrategy`-classes. These,
11
+ again, require a `SamplingContext` storing information like number of
12
+ samples.
13
+
14
+ Generate an array of equidistantly distributed samples for the
15
+ function x**2 by entering
16
+ ```
17
+ >>> from granular.geometry import EquidistantSampling, SamplingContext
18
+ >>> samples = EquidistantSampling.sample(shape=lambda x: x**2, context=SamplingContext())
19
+ >>> np.shape(samples)
20
+ (50, 2)
21
+ ```
22
+
23
+ ## Shape generating functions
24
+ This module provides functions that can be used to generate `Shape`s,
25
+ i.e., functions which themselves represent closed, planar curves. For
26
+ example, using the factory `shape_superformula`, a star-like `Shape` can
27
+ be created by
28
+ ```
29
+ >>> from granular.geometry import shape_superformula
30
+ >>> shape = shape_superformula((2, 9, 9), 5, 1.0)
31
+ >>> shape([1, 2, 3])
32
+ array([0.37628812, 0.77859841, 0.7103601])
33
+ ```
34
+ """
35
+
36
+ from typing import TypeAlias, Callable, Optional
37
+ from abc import ABC, abstractmethod
38
+ from dataclasses import dataclass, field
39
+ from math import cos, sin
40
+ import numpy as np
41
+ from numpy.typing import NDArray
42
+ from scipy import optimize
43
+
44
+ Shape: TypeAlias = Callable[[float | NDArray], float | NDArray]
45
+
46
+
47
+ @dataclass
48
+ class SamplingContext:
49
+ """
50
+ Record class representing information on a sampling context.
51
+
52
+ Keyword arguments:
53
+ domain -- domain over which the sampling should be done
54
+ (default corresponds to an interval [0:1])
55
+ num -- total number of samples
56
+ (default 50)
57
+ endpoint -- if `True`, the first and last sample are identical
58
+ (default `False`)
59
+ """
60
+
61
+ domain: list = field(default_factory=lambda: [0.0, 1.0])
62
+ num: int = 50
63
+ endpoint: bool = False
64
+
65
+
66
+ class SamplingStrategy(ABC):
67
+ """
68
+ Interface for sampling strategies.
69
+ """
70
+
71
+ @staticmethod
72
+ @abstractmethod
73
+ def samples(
74
+ shape: Shape,
75
+ context: Optional[SamplingContext] = None
76
+ ) -> NDArray:
77
+ """
78
+ Returns an array of `num` sample locations based on `shape`.
79
+
80
+ Keyword arguments:
81
+ shape -- callable function generating a shape to be sampled
82
+ context -- sampling context
83
+ (default uses a `SamplingContext` with default
84
+ values)
85
+ """
86
+ raise NotImplementedError
87
+
88
+ @classmethod
89
+ def sample(
90
+ cls,
91
+ shape: Shape,
92
+ context: Optional[SamplingContext] = None,
93
+ values_only: bool = False
94
+ ) -> NDArray:
95
+ """
96
+ Returns an array of `num` sampled locations based on `shape`
97
+ as pairs of sample position and sampled value (shape=(num, 2)).
98
+
99
+ Keyword arguments:
100
+ shape -- callable function generating a shape to be sampled
101
+ context -- sampling context
102
+ (default uses a `SamplingContext` with default
103
+ values)
104
+ values_only -- if `True`, the returned array only contains the
105
+ sampled values (shape=(num,))
106
+ """
107
+
108
+ x = cls.samples(shape, context=context)
109
+ y = shape(x)
110
+
111
+ if values_only:
112
+ return y
113
+ return np.stack((x, y), axis=-1)
114
+
115
+
116
+ class EquidistantSampling(SamplingStrategy):
117
+ """
118
+ Samples for equidistant sampling.
119
+ """
120
+
121
+ @staticmethod
122
+ def samples(
123
+ shape: Shape,
124
+ context: Optional[SamplingContext] = None
125
+ ) -> NDArray:
126
+ return np.linspace(
127
+ *(context.domain or [0.0, 1.0]),
128
+ num=context.num,
129
+ endpoint=context.endpoint
130
+ )
131
+
132
+
133
+ class FavorCurvatureSampling(SamplingStrategy):
134
+ """
135
+ Samples with higher frequency in regions of greater curvature.
136
+ """
137
+
138
+ @staticmethod
139
+ def samples(
140
+ shape: Shape,
141
+ context: Optional[SamplingContext] = None
142
+ ) -> NDArray:
143
+
144
+ raise NotImplementedError
145
+
146
+
147
+ def shape_superformula(
148
+ n: tuple[int, int, int],
149
+ m: int, r: float,
150
+ vectorize: bool = True
151
+ ) -> Shape:
152
+ """
153
+ Returns a callable function that generates a planar and closed
154
+ curve based on the superformula.
155
+
156
+ r(p) ~ r * [|cos(mp/4)|**n2 + |sin(mp/4)|**n3]**(-1/n1)
157
+
158
+ Keyword arguments:
159
+ n -- three-tuple containing the interger values `n1`, `n2`, `n3`
160
+ m -- angular frequency `m`
161
+ r -- radius of the bounding sphere
162
+ vectorize -- if `True`, use `np.vectorize` to make compatible with
163
+ `NDArray`
164
+ (default `True`)
165
+ """
166
+
167
+ def _generate_super(_r):
168
+ def __r(p):
169
+ return _r * (
170
+ (abs(cos((_p := 0.25*m*p))))**n[1]
171
+ + (abs(sin(_p)))**n[2]
172
+ )**(-1.0/n[0])
173
+ if vectorize:
174
+ return np.vectorize(__r)
175
+ return __r
176
+
177
+ # rescale to precisely fit bounding volume into requested size r
178
+ return _generate_super(
179
+ r/_generate_super(r)(
180
+ optimize.fminbound(_generate_super(-r), 0.0, 2.0*np.pi)
181
+ )
182
+ )
183
+
184
+
185
+ def shape_fourier(
186
+ d: tuple[float, ...],
187
+ r: float,
188
+ vectorize: bool = True
189
+ ) -> Shape:
190
+ """
191
+ Returns a callable function that generates an irregular, planar and
192
+ closed curve based on the a Fourier expansion.
193
+
194
+ Keyword arguments:
195
+ d -- tuple of Fourier descriptors
196
+ r -- radius of the bounding sphere
197
+ vectorize -- if `True`, use `np.vectorize` to make compatible with
198
+ `NDArray`
199
+ (default `True`)
200
+ """
201
+
202
+ raise NotImplementedError
@@ -0,0 +1,161 @@
1
+ """
2
+ # grain.py
3
+
4
+ This module defines the `Grain`-base class as well as more specialized
5
+ classes and supplemental helper-functions for an efficient generation of
6
+ `Grain`s.
7
+
8
+ ## Grain
9
+ A `Grain` stores information on shape (including preprocessed
10
+ information that is being used in the simulation afterwards). It also
11
+ gets assigned a unique identifier via the uuid-module (uuid4).
12
+ Furthermore, `Grain`-type classes provide a `sample`-method that can be
13
+ issued to generate a sampled representation in the form of a `numpy`-
14
+ `NDArray`.
15
+
16
+ """
17
+
18
+ from typing import Optional
19
+ from dataclasses import dataclass
20
+ import uuid
21
+ import numpy as np
22
+ from numpy.typing import NDArray
23
+ from scipy import optimize
24
+ from granular.geometry \
25
+ import Shape, SamplingStrategy, EquidistantSampling, SamplingContext
26
+
27
+
28
+ @dataclass
29
+ class Representations:
30
+ """
31
+ Record class defining the storage-format of different
32
+ representations of `Grain`s.
33
+
34
+ Keyword arguments:
35
+ ground_truth -- ground truth for the shape of a `Grain`
36
+ sampled -- list of `NDArray`s for the sampled `Shape` at different
37
+ levels of detail; `sampled[0]` represents the lowest lod
38
+ (default `None`)
39
+ bounding_box -- (UNUSED) bounding box representation
40
+ (default `None`)
41
+ bounding_sphere -- `float` characterizing size of the bounding
42
+ sphere with a point of reference located at the
43
+ reference point for the shape-generating function
44
+ (default `None`)
45
+ """
46
+
47
+ ground_truth: Shape
48
+ sampled: Optional[list[NDArray]] = None
49
+ bounding_box: Optional = None
50
+ bounding_sphere: Optional[float] = None
51
+
52
+
53
+ class Grain:
54
+ """
55
+ `Grain`s represent the granular units of a simulation.
56
+
57
+ Keyword arguments:
58
+ shape -- grain shape
59
+ """
60
+
61
+ def __init__(self, shape: Shape) -> None:
62
+ self._identifier = uuid.uuid4()
63
+ self._default_sampling_strategy = EquidistantSampling
64
+ self._default_sampling_context = SamplingContext(
65
+ domain=[0, np.pi],
66
+ num=50,
67
+ endpoint=False
68
+ )
69
+
70
+ # preprocess/analyze shape
71
+ self._representations = Representations(
72
+ ground_truth=shape,
73
+ sampled=[
74
+ self._default_sampling_strategy.sample(
75
+ shape,
76
+ self._default_sampling_context
77
+ )
78
+ ],
79
+ bounding_sphere=shape(
80
+ optimize.fminbound(
81
+ lambda x: -shape(x), 0.0, 2.0*np.pi, xtol=1e-10
82
+ )
83
+ )
84
+ )
85
+
86
+ def sample(
87
+ self,
88
+ strategy: SamplingStrategy = None,
89
+ context: Optional[SamplingContext] = None
90
+ ) -> NDArray:
91
+ """
92
+ Returns an `NDArray` as returned by the provided
93
+ `SamplingStrategy`. The default `SamplingStrategy` corresponds
94
+ to `EquidistantSampling`. The default `SamplingContext` is
95
+ passed into the provided strategy uses a domain of `[0, np.pi]`
96
+ and otherwise the context's default values.
97
+
98
+ Keyword arguments:
99
+ strategy -- a `SamplingStrategy` used to sample the shape
100
+ (default `None`)
101
+ context -- a `SamplingContext` passed to the provided
102
+ `SamplingStrategy`
103
+ (default `None`)
104
+ """
105
+
106
+ if strategy is None and context is None:
107
+ return self._representations.sampled[0]
108
+
109
+ return (strategy or self._default_sampling_strategy).sample(
110
+ self._representations.ground_truth,
111
+ context=context or self._default_sampling_context
112
+ )
113
+
114
+ @property
115
+ def identifier(self) -> str:
116
+ """Getter for property `identifier`."""
117
+ return str(self._identifier)
118
+
119
+ @property
120
+ def shape(self) -> Shape:
121
+ """Getter for property `shape`."""
122
+ return self._representations.ground_truth
123
+
124
+ @property
125
+ def lod0(self) -> NDArray:
126
+ """Getter for property `lod0`."""
127
+ return self._representations.sampled[0]
128
+
129
+ @property
130
+ def radius(self) -> str:
131
+ """Getter for property `radius` (bounding sphere)."""
132
+ return self._representations.bounding_sphere
133
+
134
+ @property
135
+ def diameter(self) -> str:
136
+ """Getter for property `diameter` (bounding sphere)."""
137
+ return 2*self._representations.bounding_sphere
138
+
139
+ def __str__(self):
140
+ return f"<grain {self._identifier}>"
141
+
142
+
143
+ # TODO: derived classes for different types: spherical, super, fourier
144
+ class SphericalGrain(Grain):
145
+ ...
146
+
147
+
148
+ class SuperGrain(Grain):
149
+ ...
150
+
151
+
152
+ class FourierGrain(Grain):
153
+ ...
154
+
155
+
156
+ def duplicate_grain(grain: Grain) -> Grain:
157
+ ...
158
+
159
+
160
+ def generate_batch(grain: Grain) -> list[Grain]:
161
+ ...
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.1
2
+ Name: granular
3
+ Version: 0.0.0
4
+ Summary: python library for the numerical simulation of granular materials
5
+ Home-page: https://pypi.org/project/granular/
6
+ Author: Steffen Richters-Finger
7
+ Author-email: srichters@uni-muenster.de
8
+ License: MIT
9
+ Project-URL: Source, https://github.com/RichtersFinger/granular
10
+ Platform: UNKNOWN
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Topic :: Scientific/Engineering :: Physics
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+
24
+ # granular
25
+ library for the numerical simulation of granular materials
26
+
27
+
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ granular/__init__.py
5
+ granular/geometry.py
6
+ granular/grain.py
7
+ granular.egg-info/PKG-INFO
8
+ granular.egg-info/SOURCES.txt
9
+ granular.egg-info/dependency_links.txt
10
+ granular.egg-info/requires.txt
11
+ granular.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ numpy<2,>=1.26.3
2
+ scipy<2,>=1.11.4
@@ -0,0 +1 @@
1
+ granular
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,45 @@
1
+ from pathlib import Path
2
+ from setuptools import setup
3
+
4
+ # read contents of README
5
+ long_description = \
6
+ (Path(__file__).parent / "README.md").read_text(encoding="utf8")
7
+
8
+ # read contents of requirements.txt
9
+ requirements = \
10
+ (Path(__file__).parent / "requirements.txt") \
11
+ .read_text(encoding="utf8") \
12
+ .strip() \
13
+ .split("\n")
14
+
15
+ setup(
16
+ version="0.0.0",
17
+ name="granular",
18
+ description="python library for the numerical simulation of granular materials",
19
+ long_description=long_description,
20
+ long_description_content_type="text/markdown",
21
+ author="Steffen Richters-Finger",
22
+ author_email="srichters@uni-muenster.de",
23
+ license="MIT",
24
+ license_files=("LICENSE",),
25
+ url="https://pypi.org/project/granular/",
26
+ project_urls={
27
+ "Source": "https://github.com/RichtersFinger/granular"
28
+ },
29
+ python_requires=">=3.10",
30
+ install_requires=requirements,
31
+ packages=[
32
+ "granular",
33
+ ],
34
+ classifiers=[
35
+ "Development Status :: 2 - Pre-Alpha",
36
+ "Intended Audience :: Science/Research",
37
+ "Topic :: Scientific/Engineering :: Physics",
38
+ "License :: OSI Approved :: MIT License",
39
+ "Programming Language :: Python :: 3",
40
+ "Programming Language :: Python :: 3.10",
41
+ "Programming Language :: Python :: 3.11",
42
+ "Programming Language :: Python :: 3.12",
43
+ "Typing :: Typed",
44
+ ],
45
+ )