pyFracAggregate 0.2.0__tar.gz → 0.3.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.
Files changed (77) hide show
  1. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/.gitignore +1 -0
  2. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/CLAUDE.md +1 -1
  3. pyfracaggregate-0.3.0/PKG-INFO +133 -0
  4. pyfracaggregate-0.3.0/README.md +95 -0
  5. pyfracaggregate-0.3.0/docs/_static/.gitkeep +0 -0
  6. pyfracaggregate-0.3.0/docs/_static/tutorial_fracval_render.png +0 -0
  7. pyfracaggregate-0.3.0/docs/_static/tutorial_pca_pcf.png +0 -0
  8. pyfracaggregate-0.3.0/docs/_static/tutorial_pca_render.png +0 -0
  9. pyfracaggregate-0.3.0/docs/api-reference/index.md +116 -0
  10. pyfracaggregate-0.3.0/docs/architecture/index.md +205 -0
  11. pyfracaggregate-0.3.0/docs/background/index.md +375 -0
  12. pyfracaggregate-0.3.0/docs/conf.py +83 -0
  13. pyfracaggregate-0.3.0/docs/contributing.md +75 -0
  14. pyfracaggregate-0.3.0/docs/index.md +17 -0
  15. pyfracaggregate-0.3.0/docs/tutorials/basic_usage.md +289 -0
  16. pyfracaggregate-0.3.0/docs/user-guide/analysis.md +128 -0
  17. pyfracaggregate-0.3.0/docs/user-guide/generators.md +187 -0
  18. pyfracaggregate-0.3.0/docs/user-guide/installation.md +78 -0
  19. pyfracaggregate-0.3.0/docs/user-guide/io.md +106 -0
  20. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/pyproject.toml +32 -1
  21. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/__init__.py +12 -1
  22. pyfracaggregate-0.2.0/PKG-INFO +0 -24
  23. pyfracaggregate-0.2.0/README.md +0 -3
  24. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/LICENSE +0 -0
  25. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/benchmarks/__init__.py +0 -0
  26. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/benchmarks/analyze.py +0 -0
  27. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/benchmarks/grids.py +0 -0
  28. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/benchmarks/metrics.py +0 -0
  29. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/benchmarks/run.py +0 -0
  30. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/benchmarks/tests/__init__.py +0 -0
  31. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/benchmarks/tests/test_analyze.py +0 -0
  32. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/benchmarks/tests/test_grids.py +0 -0
  33. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/benchmarks/tests/test_metrics.py +0 -0
  34. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/benchmarks/tests/test_run.py +0 -0
  35. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/examples/pyFracAggregate_demo.ipynb +0 -0
  36. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/analysis/__init__.py +0 -0
  37. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/analysis/correlation.py +0 -0
  38. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/analysis/morphology.py +0 -0
  39. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/core/__init__.py +0 -0
  40. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/core/aggregate.py +0 -0
  41. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/core/distributions.py +0 -0
  42. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/core/math_utils.py +0 -0
  43. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/generators/__init__.py +0 -0
  44. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/generators/base.py +0 -0
  45. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/generators/cca.py +0 -0
  46. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/generators/factory.py +0 -0
  47. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/generators/fracval.py +0 -0
  48. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/generators/optimizer_flage.py +0 -0
  49. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/generators/pca.py +0 -0
  50. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/generators/placement/__init__.py +0 -0
  51. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/generators/placement/_helpers.py +0 -0
  52. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/generators/placement/algebraic.py +0 -0
  53. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/generators/placement/base.py +0 -0
  54. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/generators/placement/random_.py +0 -0
  55. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/generators/tdcca.py +0 -0
  56. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/io/__init__.py +0 -0
  57. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/io/data.py +0 -0
  58. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/io/visualization.py +0 -0
  59. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/src/pyFracAggregate/io/vtk.py +0 -0
  60. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/fixtures/surface_beta_snapshot.npy +0 -0
  61. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_analysis/test_correlation.py +0 -0
  62. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_analysis/test_morphology.py +0 -0
  63. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_core/test_aggregate.py +0 -0
  64. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_core/test_distributions.py +0 -0
  65. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_core/test_math_utils.py +0 -0
  66. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_density.py +0 -0
  67. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_generators/test_benchmark.py +0 -0
  68. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_generators/test_cca.py +0 -0
  69. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_generators/test_cca_fracval.py +0 -0
  70. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_generators/test_factory.py +0 -0
  71. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_generators/test_optimizer_flage.py +0 -0
  72. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_generators/test_pca.py +0 -0
  73. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_generators/test_placement.py +0 -0
  74. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_generators/test_surface_beta.py +0 -0
  75. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_generators/test_tdcca_thouy.py +0 -0
  76. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_io/test_data.py +0 -0
  77. {pyfracaggregate-0.2.0 → pyfracaggregate-0.3.0}/tests/test_io/test_visualization.py +0 -0
@@ -168,3 +168,4 @@ debug_pca.py
168
168
  # Output data and artifacts
169
169
  output/
170
170
  benchmarks/results/
171
+ .superpowers/
@@ -30,7 +30,7 @@ No CLI entry point exists — the package is a library used via `import pyFracAg
30
30
  - `'pca'` → `PCAGenerator` — particle-cluster aggregation
31
31
  - `'cca'` → `CCAGenerator` — cluster-cluster aggregation
32
32
  - `'fracval'` → `FracVALGenerator` — FracVAL algorithm
33
- - `'tdcca'` → `ThouyJullienGenerator` — Thouy & Jullien (2004) algorithm
33
+ - `'tdcca'` → `ThouyJullienGenerator` — Thouy & Jullien (1994) algorithm
34
34
  - All generators share the same constructor signature from `BaseGenerator`.
35
35
 
36
36
  #### Placement Strategy Layer (`src/pyFracAggregate/generators/placement/`)
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.5
2
+ Name: pyFracAggregate
3
+ Version: 0.3.0
4
+ Summary: 通用分形团簇生成框架 (General Fractal Aggregate Generation Framework)
5
+ Project-URL: Homepage, https://github.com/vanvonzhang/pyFracAggregate
6
+ Project-URL: Documentation, https://vanvonzhang.github.io/pyFracAggregate/
7
+ Project-URL: Repository, https://github.com/vanvonzhang/pyFracAggregate
8
+ Project-URL: Issues, https://github.com/vanvonzhang/pyFracAggregate/issues
9
+ Author-email: Fan Zhang <vanvonzhang@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: DLCA,aerosol,cluster-cluster aggregation,fractal aggregate,fractal dimension,nanoparticles,soot
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
17
+ Classifier: Topic :: Scientific/Engineering :: Physics
18
+ Requires-Python: >=3.13
19
+ Requires-Dist: imageio[ffmpeg]>=2.9.0
20
+ Requires-Dist: mathutils>=3.0.0
21
+ Requires-Dist: numpy>=1.21.0
22
+ Requires-Dist: pyvista
23
+ Requires-Dist: pyyaml
24
+ Requires-Dist: scipy>=1.7.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: mypy; extra == 'dev'
27
+ Requires-Dist: pytest; extra == 'dev'
28
+ Requires-Dist: ruff; extra == 'dev'
29
+ Provides-Extra: docs
30
+ Requires-Dist: myst-parser>=2.0; extra == 'docs'
31
+ Requires-Dist: sphinx-copybutton>=0.5.0; extra == 'docs'
32
+ Requires-Dist: sphinx-rtd-theme>=2.0; extra == 'docs'
33
+ Requires-Dist: sphinx>=7.0; extra == 'docs'
34
+ Requires-Dist: sphinxcontrib-mermaid>=1.0; extra == 'docs'
35
+ Provides-Extra: plot
36
+ Requires-Dist: matplotlib>=3.5.0; extra == 'plot'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # pyFracAggregate
40
+
41
+ [![CI](https://github.com/vanvonzhang/pyFracAggregate/actions/workflows/test.yml/badge.svg)](https://github.com/vanvonzhang/pyFracAggregate/actions/workflows/test.yml)
42
+ [![PyPI version](https://img.shields.io/pypi/v/pyFracAggregate)](https://pypi.org/project/pyFracAggregate/)
43
+ [![Python versions](https://img.shields.io/pypi/pyversions/pyFracAggregate)](https://pypi.org/project/pyFracAggregate/)
44
+ [![Docs](https://img.shields.io/badge/docs-online-blue)](https://vanvonzhang.github.io/pyFracAggregate/)
45
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
46
+
47
+ A Python library for generating synthetic fractal aggregates — clusters of
48
+ spherical primary particles with a tunable morphology, such as soot and other
49
+ aerosols — unified across four classical generation algorithms behind one API,
50
+ with built-in morphological analysis and export to common scientific formats.
51
+
52
+ ## Features
53
+
54
+ - **Four generation algorithms, one API** — particle-cluster aggregation
55
+ (`'pca'`), cluster-cluster aggregation (`'cca'`), FracVAL (`'fracval'`), and
56
+ the Thouy & Jullien tunable CCA (`'tdcca'`), all selected with a single
57
+ `method=` keyword.
58
+ - **Two placement strategies** — FLAGE-style algebraic touching-point
59
+ computation (default) or Monte Carlo random placement with tolerance
60
+ relaxation.
61
+ - **Monodisperse and lognormal primary particles** — `Monodisperse` and
62
+ `LognormalDistribution` size distributions feed any generator.
63
+ - **Built-in morphology analysis** — radius of gyration, center of mass, pair
64
+ correlation function, and fractal-dimension estimation with fit quality
65
+ (`pfa.analyze`).
66
+ - **Rich exports** — YAML snapshot, VTK point cloud and VTM multiblock (via
67
+ pyvista, ready for ParaView), off-screen static render, and rotation video.
68
+ - **Fully typed library with tests** — type hints throughout the source, and a
69
+ pytest suite mirroring the package layout.
70
+
71
+ ## Installation
72
+
73
+ ```console
74
+ $ pip install pyFracAggregate
75
+ ```
76
+
77
+ > **Requires Python ≥ 3.13.** The 3D math dependency `mathutils` only has
78
+ > usable wheels for the 3.13 ABI on several platforms; older interpreters can
79
+ > fail at compile time. See the
80
+ > [installation guide](https://vanvonzhang.github.io/pyFracAggregate/user-guide/installation.html)
81
+ > for details and platform notes.
82
+
83
+ To install from source for development:
84
+
85
+ ```console
86
+ $ pip install -e ".[dev]"
87
+ ```
88
+
89
+ ## Quick start
90
+
91
+ ```python
92
+ import numpy as np
93
+ import pyFracAggregate as pfa
94
+
95
+ np.random.seed(0)
96
+ agg = pfa.generate(200, 1.8, 1.9, method='pca') # N=200, Df=1.8, kf=1.9
97
+
98
+ summary = pfa.analyze(agg) # Rg=13.274 nm, Df_estimated=1.714, R2=0.964
99
+ print(agg.current_size, summary['Df_estimated']) # 200 1.714241520287105
100
+
101
+ pfa.export_yaml(agg, 'aggregate.yaml')
102
+ pfa.export_vtk(agg, 'aggregate.vtk')
103
+ ```
104
+
105
+ Generation is stochastic and draws from NumPy's global legacy random state:
106
+ call `np.random.seed(...)` immediately before `pfa.generate(...)` for
107
+ reproducible aggregates. The single-realization `Df_estimated` scatters
108
+ around the requested `df`; average over realizations for ensemble statements.
109
+
110
+ ## Methods
111
+
112
+ | Keyword | Algorithm | Family | Polydispersity | Reference |
113
+ |---|---|---|---|---|
114
+ | [`pca`](https://vanvonzhang.github.io/pyFracAggregate/background/index.html#pca-particle-cluster-aggregation) | Particle-cluster aggregation | particle-cluster | approximate (mean radius) | Skorupski et al., 2014 |
115
+ | [`cca`](https://vanvonzhang.github.io/pyFracAggregate/background/index.html#cca-cluster-cluster-aggregation) | Cluster-cluster aggregation | cluster-cluster | approximate (number-weighted) | Filippov et al., 2000 |
116
+ | [`fracval`](https://vanvonzhang.github.io/pyFracAggregate/background/index.html#fracval-tunable-cca-for-polydisperse-primaries) | FracVAL tunable CCA | cluster-cluster | native (mass-weighted) | Morán et al., 2019 |
117
+ | [`tdcca`](https://vanvonzhang.github.io/pyFracAggregate/background/index.html#tdcca-thouy-jullien-tunable-cca) | Thouy & Jullien tunable CCA | cluster-cluster | supported (mass-weighted Rg) | Thouy & Jullien, 1994 |
118
+
119
+ Each keyword links to the corresponding section of the
120
+ [background chapter](https://vanvonzhang.github.io/pyFracAggregate/background/index.html)
121
+ on the documentation site, which derives each algorithm's principle,
122
+ guarantees, and limits.
123
+
124
+ ## Documentation
125
+
126
+ Full documentation — background theory, user guide, tutorial, API reference,
127
+ architecture notes, and contributing instructions — is hosted at:
128
+
129
+ [Documentation](https://vanvonzhang.github.io/pyFracAggregate/)
130
+
131
+ ## License
132
+
133
+ [MIT](LICENSE)
@@ -0,0 +1,95 @@
1
+ # pyFracAggregate
2
+
3
+ [![CI](https://github.com/vanvonzhang/pyFracAggregate/actions/workflows/test.yml/badge.svg)](https://github.com/vanvonzhang/pyFracAggregate/actions/workflows/test.yml)
4
+ [![PyPI version](https://img.shields.io/pypi/v/pyFracAggregate)](https://pypi.org/project/pyFracAggregate/)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/pyFracAggregate)](https://pypi.org/project/pyFracAggregate/)
6
+ [![Docs](https://img.shields.io/badge/docs-online-blue)](https://vanvonzhang.github.io/pyFracAggregate/)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
8
+
9
+ A Python library for generating synthetic fractal aggregates — clusters of
10
+ spherical primary particles with a tunable morphology, such as soot and other
11
+ aerosols — unified across four classical generation algorithms behind one API,
12
+ with built-in morphological analysis and export to common scientific formats.
13
+
14
+ ## Features
15
+
16
+ - **Four generation algorithms, one API** — particle-cluster aggregation
17
+ (`'pca'`), cluster-cluster aggregation (`'cca'`), FracVAL (`'fracval'`), and
18
+ the Thouy & Jullien tunable CCA (`'tdcca'`), all selected with a single
19
+ `method=` keyword.
20
+ - **Two placement strategies** — FLAGE-style algebraic touching-point
21
+ computation (default) or Monte Carlo random placement with tolerance
22
+ relaxation.
23
+ - **Monodisperse and lognormal primary particles** — `Monodisperse` and
24
+ `LognormalDistribution` size distributions feed any generator.
25
+ - **Built-in morphology analysis** — radius of gyration, center of mass, pair
26
+ correlation function, and fractal-dimension estimation with fit quality
27
+ (`pfa.analyze`).
28
+ - **Rich exports** — YAML snapshot, VTK point cloud and VTM multiblock (via
29
+ pyvista, ready for ParaView), off-screen static render, and rotation video.
30
+ - **Fully typed library with tests** — type hints throughout the source, and a
31
+ pytest suite mirroring the package layout.
32
+
33
+ ## Installation
34
+
35
+ ```console
36
+ $ pip install pyFracAggregate
37
+ ```
38
+
39
+ > **Requires Python ≥ 3.13.** The 3D math dependency `mathutils` only has
40
+ > usable wheels for the 3.13 ABI on several platforms; older interpreters can
41
+ > fail at compile time. See the
42
+ > [installation guide](https://vanvonzhang.github.io/pyFracAggregate/user-guide/installation.html)
43
+ > for details and platform notes.
44
+
45
+ To install from source for development:
46
+
47
+ ```console
48
+ $ pip install -e ".[dev]"
49
+ ```
50
+
51
+ ## Quick start
52
+
53
+ ```python
54
+ import numpy as np
55
+ import pyFracAggregate as pfa
56
+
57
+ np.random.seed(0)
58
+ agg = pfa.generate(200, 1.8, 1.9, method='pca') # N=200, Df=1.8, kf=1.9
59
+
60
+ summary = pfa.analyze(agg) # Rg=13.274 nm, Df_estimated=1.714, R2=0.964
61
+ print(agg.current_size, summary['Df_estimated']) # 200 1.714241520287105
62
+
63
+ pfa.export_yaml(agg, 'aggregate.yaml')
64
+ pfa.export_vtk(agg, 'aggregate.vtk')
65
+ ```
66
+
67
+ Generation is stochastic and draws from NumPy's global legacy random state:
68
+ call `np.random.seed(...)` immediately before `pfa.generate(...)` for
69
+ reproducible aggregates. The single-realization `Df_estimated` scatters
70
+ around the requested `df`; average over realizations for ensemble statements.
71
+
72
+ ## Methods
73
+
74
+ | Keyword | Algorithm | Family | Polydispersity | Reference |
75
+ |---|---|---|---|---|
76
+ | [`pca`](https://vanvonzhang.github.io/pyFracAggregate/background/index.html#pca-particle-cluster-aggregation) | Particle-cluster aggregation | particle-cluster | approximate (mean radius) | Skorupski et al., 2014 |
77
+ | [`cca`](https://vanvonzhang.github.io/pyFracAggregate/background/index.html#cca-cluster-cluster-aggregation) | Cluster-cluster aggregation | cluster-cluster | approximate (number-weighted) | Filippov et al., 2000 |
78
+ | [`fracval`](https://vanvonzhang.github.io/pyFracAggregate/background/index.html#fracval-tunable-cca-for-polydisperse-primaries) | FracVAL tunable CCA | cluster-cluster | native (mass-weighted) | Morán et al., 2019 |
79
+ | [`tdcca`](https://vanvonzhang.github.io/pyFracAggregate/background/index.html#tdcca-thouy-jullien-tunable-cca) | Thouy & Jullien tunable CCA | cluster-cluster | supported (mass-weighted Rg) | Thouy & Jullien, 1994 |
80
+
81
+ Each keyword links to the corresponding section of the
82
+ [background chapter](https://vanvonzhang.github.io/pyFracAggregate/background/index.html)
83
+ on the documentation site, which derives each algorithm's principle,
84
+ guarantees, and limits.
85
+
86
+ ## Documentation
87
+
88
+ Full documentation — background theory, user guide, tutorial, API reference,
89
+ architecture notes, and contributing instructions — is hosted at:
90
+
91
+ [Documentation](https://vanvonzhang.github.io/pyFracAggregate/)
92
+
93
+ ## License
94
+
95
+ [MIT](LICENSE)
File without changes
@@ -0,0 +1,116 @@
1
+ # API Reference
2
+
3
+ Complete reference for the 21 public symbols exported by `pyFracAggregate`
4
+ (the package's `__all__`), grouped by layer: the top-level facade, the core
5
+ data structures, the four generation algorithms, the analysis functions, the
6
+ I/O exporters, and the placement strategies.
7
+
8
+ ## Top-level API
9
+
10
+ The facade functions cover the common workflow: build an aggregate with
11
+ `generate()` (which dispatches to one of the four generation methods), then
12
+ summarize its morphology with `analyze()`.
13
+
14
+ ```{eval-rst}
15
+ .. autofunction:: pyFracAggregate.generate
16
+
17
+ .. autofunction:: pyFracAggregate.analyze
18
+ ```
19
+
20
+ ## Core
21
+
22
+ `Aggregate` is the central data structure every other layer produces or
23
+ consumes: a pre-allocated `(max_particles, 5)` NumPy array of
24
+ `[x, y, z, radius, mass]` rows whose `positions`, `radii`, and `masses`
25
+ properties are zero-copy views. The two distribution classes describe
26
+ primary-particle sizes and are passed to generators via the `particle_dist`
27
+ argument.
28
+
29
+ ```{eval-rst}
30
+ .. autoclass:: pyFracAggregate.core.aggregate.Aggregate
31
+ :members:
32
+
33
+ .. autoclass:: pyFracAggregate.core.distributions.Monodisperse
34
+ :members:
35
+
36
+ .. autoclass:: pyFracAggregate.core.distributions.LognormalDistribution
37
+ :members:
38
+ ```
39
+
40
+ ## Generators
41
+
42
+ Four algorithms share the `BaseGenerator` constructor contract
43
+ `(n_particles, df, kf, particle_dist, overlap_tolerance, placement)` and each
44
+ returns an `Aggregate` from its `generate()` method. Users normally reach them
45
+ through `generate(method=...)`; the classes are public for direct use and
46
+ subclassing.
47
+
48
+ ```{eval-rst}
49
+ .. autoclass:: pyFracAggregate.generators.pca.PCAGenerator
50
+ :members:
51
+
52
+ .. autoclass:: pyFracAggregate.generators.cca.CCAGenerator
53
+ :members:
54
+
55
+ .. autoclass:: pyFracAggregate.generators.fracval.FracVALGenerator
56
+ :members:
57
+
58
+ .. autoclass:: pyFracAggregate.generators.tdcca.ThouyJullienGenerator
59
+ :members:
60
+ ```
61
+
62
+ ## Analysis
63
+
64
+ Morphological descriptors computed from an `Aggregate`: global quantities
65
+ (radius of gyration, center of mass) and the two-point pair correlation
66
+ function `C(r)`, from which the fractal dimension is estimated by log-log
67
+ regression. `analyze()` bundles the main ones into a summary dict, and
68
+ `plot_pair_correlation()` visualizes the fit (requires matplotlib).
69
+
70
+ ```{eval-rst}
71
+ .. autofunction:: pyFracAggregate.analysis.morphology.radius_of_gyration
72
+
73
+ .. autofunction:: pyFracAggregate.analysis.morphology.center_of_mass
74
+
75
+ .. autofunction:: pyFracAggregate.analysis.correlation.pair_correlation_function
76
+
77
+ .. autofunction:: pyFracAggregate.analysis.correlation.estimate_fractal_dimension
78
+
79
+ .. autofunction:: pyFracAggregate.analysis.correlation.plot_pair_correlation
80
+ ```
81
+
82
+ ## I/O
83
+
84
+ Export an `Aggregate` for downstream use: a YAML snapshot bundling the particle
85
+ data with generation parameters and analysis results, VTK/VTM files built with
86
+ pyvista for ParaView and other tools, and off-screen rendered PNG images or
87
+ MP4 rotation videos. The render and video exporters require a working pyvista
88
+ 3D backend (see the user guide for headless-environment notes).
89
+
90
+ ```{eval-rst}
91
+ .. autofunction:: pyFracAggregate.io.data.export_yaml
92
+
93
+ .. autofunction:: pyFracAggregate.io.visualization.export_render
94
+
95
+ .. autofunction:: pyFracAggregate.io.visualization.export_rotation_video
96
+
97
+ .. autofunction:: pyFracAggregate.io.vtk.export_vtm
98
+
99
+ .. autofunction:: pyFracAggregate.io.vtk.export_vtk
100
+ ```
101
+
102
+ ## Placement
103
+
104
+ Placement strategies decide where a new particle or cluster touches the
105
+ existing structure while respecting the overlap tolerance; every generator
106
+ selects one via `placement=`. Both classes implement the same two entry
107
+ points: `place_particle()` for particle-cluster stages and `merge_clusters()`
108
+ for cluster-cluster stages.
109
+
110
+ ```{eval-rst}
111
+ .. autoclass:: pyFracAggregate.generators.placement.algebraic.AlgebraicPlacement
112
+ :members:
113
+
114
+ .. autoclass:: pyFracAggregate.generators.placement.random_.RandomPlacement
115
+ :members:
116
+ ```
@@ -0,0 +1,205 @@
1
+ # Architecture
2
+
3
+ pyFracAggregate is a four-layer library: a **core** data structure
4
+ (`Aggregate`, primary-particle distributions), a **generators** layer holding
5
+ the four aggregation algorithms and their shared placement sublayer, an
6
+ **analysis** layer computing morphological descriptors, and an **io** layer
7
+ exporting aggregates. A thin top-level facade (`pfa.generate` /
8
+ `pfa.analyze`) ties the layers together, and a factory function dispatches
9
+ generation requests to one of the four algorithm classes.
10
+
11
+ ```{mermaid}
12
+ flowchart TB
13
+ user(["user code"])
14
+
15
+ facade["top-level facade<br/>generate() / analyze()"]
16
+ factory["generators/factory.py<br/>get_generator(method, ...)"]
17
+
18
+ subgraph GENLAYER ["generators/"]
19
+ BASE["BaseGenerator (ABC)"]
20
+ PCA["PCAGenerator"]
21
+ CCA["CCAGenerator"]
22
+ FRACVAL["FracVALGenerator"]
23
+ TJ["ThouyJullienGenerator"]
24
+ subgraph PLACE ["generators/placement/"]
25
+ PABC["PlacementStrategy (ABC)"]
26
+ ALG["AlgebraicPlacement<br/>(FLAGE, default)"]
27
+ RND["RandomPlacement<br/>(Monte Carlo)"]
28
+ MC["_helpers.py<br/>shared Monte Carlo"]
29
+ end
30
+ end
31
+
32
+ subgraph CORELAYER ["core/"]
33
+ AGG["Aggregate<br/>pre-allocated (max_particles, 5)<br/>[x, y, z, radius, mass]"]
34
+ DIST["ParticleDistribution<br/>Monodisperse / LognormalDistribution"]
35
+ end
36
+
37
+ subgraph ANALYSIS ["analysis/"]
38
+ MORPH["morphology.py<br/>radius_of_gyration / center_of_mass"]
39
+ CORR["correlation.py<br/>pair correlation / Df estimation"]
40
+ end
41
+
42
+ subgraph IOLAYER ["io/"]
43
+ YAMLIO["data.py<br/>export_yaml"]
44
+ VTKIO["vtk.py<br/>export_vtk / export_vtm"]
45
+ VISIO["visualization.py<br/>export_render / export_rotation_video"]
46
+ end
47
+
48
+ user --> facade
49
+ facade -->|"'method' keyword"| factory
50
+ factory --> PCA
51
+ factory --> CCA
52
+ factory --> FRACVAL
53
+ factory --> TJ
54
+
55
+ PCA & CCA & FRACVAL & TJ -.->|"subclass"| BASE
56
+ DIST -->|"sample() radii"| BASE
57
+ PCA -.->|"place_particle()"| PABC
58
+ CCA -.->|"merge_clusters()"| PABC
59
+ PABC --> ALG
60
+ PABC --> RND
61
+ ALG --> MC
62
+ RND --> MC
63
+
64
+ PCA & CCA & FRACVAL & TJ -->|"generate() returns"| AGG
65
+
66
+ AGG --> MORPH
67
+ AGG --> CORR
68
+ facade -.->|"analyze()"| MORPH
69
+ facade -.->|"analyze()"| CORR
70
+
71
+ AGG --> YAMLIO
72
+ AGG --> VTKIO
73
+ AGG --> VISIO
74
+ ```
75
+
76
+ Only `pca` and `cca` route through the placement sublayer; `fracval` and
77
+ `tdcca` embed their own contact logic (see below).
78
+
79
+ ## The `Aggregate` data structure
80
+
81
+ Every layer produces or consumes one central type,
82
+ [`Aggregate`](/api-reference/index.md#core). It stores all particle data in a
83
+ **single pre-allocated NumPy array** of shape `(max_particles, 5)`, one row
84
+ per particle holding `[x, y, z, radius, mass]`:
85
+
86
+ - **Why pre-allocation.** The array is allocated once, contiguously, at
87
+ construction. Growing the cluster is a row write plus a counter increment
88
+ (`add_particle` is O(1)); there are no per-particle Python objects and no
89
+ list reallocations or copies during generation. Data locality keeps the
90
+ tight numeric loops in the generators and analysis fast.
91
+ - **Zero-copy views.** The properties `positions` (`(N, 3)`), `radii`
92
+ (`(N,)`), and `masses` (`(N,)`) return NumPy *views* slicing that one
93
+ backing array, not copies — mutating them mutates the aggregate.
94
+ `to_numpy()` returns a copy of the valid `(N, 5)` block when ownership of
95
+ the data must leave the aggregate.
96
+ - **`.current_size` semantics.** The backing array is over-provisioned to
97
+ `max_particles` rows; `.current_size` counts how many are valid. The views
98
+ always expose exactly the valid prefix, so `.current_size` is "the N of
99
+ the cluster" (the analysis helper `pfa.analyze` returns it as `"N"`). The
100
+ capacity is readable as `.max_size`.
101
+ - **Units.** `length_unit`, `mass_unit`, and `density` travel with the
102
+ aggregate (set by the generator; defaults `'nm'`, `'g'`, `1.0`) so exports
103
+ and analysis can label quantities.
104
+
105
+ Primary-particle sizes are supplied by a `ParticleDistribution` —
106
+ `Monodisperse(radius)` or `LognormalDistribution(mean, std)` — whose
107
+ `sample(n)` the generators call to draw radii.
108
+
109
+ ## The generator contract
110
+
111
+ All four algorithms implement the abstract
112
+ [`BaseGenerator`](/api-reference/index.md#generators) with a single
113
+ constructor signature:
114
+
115
+ ```python
116
+ BaseGenerator(
117
+ n_particles, # target number of primary particles
118
+ df, kf, # fractal dimension and prefactor
119
+ particle_dist, # ParticleDistribution for primary radii
120
+ overlap_tolerance=0.0,
121
+ placement='algebraic',
122
+ # plus length_unit / mass_unit / density
123
+ )
124
+ ```
125
+
126
+ `generate()` then returns a populated `Aggregate`. Because the contract is
127
+ identical, the factory `get_generator(method, ...)` (in
128
+ `generators/factory.py`) can dispatch on a string — `'pca'`, `'cca'`,
129
+ `'fracval'`, `'tdcca'` — and that factory is exactly what the top-level
130
+ `pfa.generate()` wraps. Keyword-only extras (`surface_beta`) are validated
131
+ there and rejected for methods that do not support them.
132
+
133
+ `pfa.analyze()` is the facade's read-side: it bundles
134
+ `radius_of_gyration`, `center_of_mass`, and the pair-correlation fit into
135
+ one summary dict (`Rg`, `CoM`, `N`, `Df_estimated`, `R2`).
136
+
137
+ ## The placement strategy layer
138
+
139
+ The scaling law fixes *where* the center of each added particle or cluster
140
+ must sit (distance `L` or `Γ` from the cluster center) but not *which*
141
+ particles touch. Resolving that contact problem is delegated to a strategy
142
+ object, selected by name:
143
+
144
+ - [`PlacementStrategy`](/api-reference/index.md#placement) — the ABC. Its two
145
+ abstract methods mirror the two aggregation stages: `place_particle()`
146
+ (single particle onto a cluster, the PCA stage) and `merge_clusters()`
147
+ (two clusters onto a common `Γ`, the CCA stage).
148
+ - `AlgebraicPlacement` (default) — FLAGE, Skorupski et al. (2014). The
149
+ analytical solver in `generators/optimizer_flage.py` intersects the target
150
+ sphere with a reference particle's contact sphere to get exact touching
151
+ points; candidates are overlap-filtered, with a Monte Carlo fallback.
152
+ - `RandomPlacement` — Filippov et al. (2000). Pure Monte Carlo sampling on
153
+ the target sphere with gradual tolerance relaxation until a candidate is
154
+ accepted (typically several times slower).
155
+ - `get_placement(name)` — the factory. `BaseGenerator.__init__` resolves the
156
+ `placement` string through it and stores the strategy instance, injecting
157
+ the generator's `overlap_tolerance` into it.
158
+
159
+ Both implementations share their Monte Carlo machinery
160
+ (`random_monte_carlo_place`, `random_monte_carlo_merge`) through
161
+ `generators/placement/_helpers.py`, so the strategies differ only in their
162
+ deterministic precomputation, not in their fallbacks.
163
+
164
+ `FracVALGenerator` and `ThouyJullienGenerator` do **not** route through the
165
+ placement layer: FracVAL's merge has its own deterministic contact search
166
+ (sphere-sphere intersection with overlap-resolving rotations), and Thouy &
167
+ Jullien selects among lattice-seeded orientations directly. The `placement`
168
+ argument is accepted for constructor uniformity but ignored by both.
169
+
170
+ ## Analysis
171
+
172
+ The analysis layer is a set of pure functions over an `Aggregate`:
173
+ `morphology.py` provides `radius_of_gyration` (mass-weighted, including each
174
+ sphere's intrinsic gyration, per Morán et al. 2019 Eq. (3)) and
175
+ `center_of_mass`; `correlation.py` provides `pair_correlation_function`,
176
+ `estimate_fractal_dimension` (log-log regression of `C(r)` over the fractal
177
+ regime), and the matplotlib-based `plot_pair_correlation` for diagnosing the
178
+ fit. No function mutates the aggregate.
179
+
180
+ ## I/O
181
+
182
+ The io layer serializes an `Aggregate` for downstream use: `data.py` writes
183
+ a YAML snapshot bundling particle data with generation parameters and
184
+ analysis results; `vtk.py` builds the pyvista point cloud (`export_vtk`) and
185
+ MultiBlock dataset (`export_vtm`) for ParaView; `visualization.py` performs
186
+ off-screen pyvista rendering (`export_render`) and assembles MP4 rotation
187
+ videos (`export_rotation_video`). Rendering exporters need a working 3D
188
+ backend — see the [io guide](/user-guide/io.md) for headless-environment
189
+ notes.
190
+
191
+ ## Tests
192
+
193
+ The test suite mirrors the source layout:
194
+
195
+ - `tests/test_core/` — `Aggregate`, distributions, 3D math helpers
196
+ - `tests/test_generators/` — the four algorithms, factory, placement
197
+ strategies, FLAGE optimizer
198
+ - `tests/test_analysis/` — morphology and correlation functions
199
+ - `tests/test_io/` — YAML and visualization exports
200
+ - `tests/test_density.py` — root-level density test
201
+ - `tests/fixtures/` — shared fixture data (`surface_beta_snapshot.npy`)
202
+
203
+ Slow performance tests carry the `benchmark` pytest marker and can be
204
+ deselected with `-m "not benchmark"` (see
205
+ [Contributing](/contributing.md#running-the-tests)).