Reorganized as a Python package

This commit is contained in:
Kenneth Jao 2021-09-18 23:08:49 -04:00
parent 19b3b250b4
commit d397ff93c2
22 changed files with 4051 additions and 4084 deletions

14
.gitignore vendored
View File

@ -1,12 +1,10 @@
.venv .venv/
__pycache__ __pycache__/
src/build build/
src/packsim.c dist/
*.egg-info
src/packsim.c
*.so *.so
figures
simulations
old_simulations
*.json

View File

@ -1,4 +0,0 @@
rm -f packsim_core*
cd src
python3 setup.py build_ext --inplace --quiet
mv *.so ../

20
docs/Makefile Normal file
View File

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

35
docs/make.bat Normal file
View File

@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

7
docs/source/api.rst Normal file
View File

@ -0,0 +1,7 @@
API
===
.. autosummary::
:toctree: generated
lumache

35
docs/source/conf.py Normal file
View File

@ -0,0 +1,35 @@
# Configuration file for the Sphinx documentation builder.
# -- Project information
project = 'Lumache'
copyright = '2021, Graziella'
author = 'Graziella'
release = '0.1'
version = '0.1.0'
# -- General configuration
extensions = [
'sphinx.ext.duration',
'sphinx.ext.doctest',
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'sphinx.ext.intersphinx',
]
intersphinx_mapping = {
'python': ('https://docs.python.org/3/', None),
'sphinx': ('https://www.sphinx-doc.org/en/master/', None),
}
intersphinx_disabled_domains = ['std']
templates_path = ['_templates']
# -- Options for HTML output
html_theme = 'sphinx_rtd_theme'
# -- Options for EPUB output
epub_show_urls = 'footnote'

22
docs/source/index.rst Normal file
View File

@ -0,0 +1,22 @@
Welcome to Lumache's documentation!
===================================
**Lumache** (/lu'make/) is a Python library for cooks and food lovers
that creates recipes mixing random ingredients.
It pulls data from the `Open Food Facts database <https://world.openfoodfacts.org/>`_
and offers a *simple* and *intuitive* API.
Check out the :doc:`usage` section for further information, including
how to :ref:`installation` the project.
.. note::
This project is under active development.
Contents
--------
.. toctree::
usage
api

34
docs/source/usage.rst Normal file
View File

@ -0,0 +1,34 @@
Usage
=====
.. _installation:
Installation
------------
To use Lumache, first install it using pip:
.. code-block:: console
(.venv) $ pip install lumache
Creating recipes
----------------
To retrieve a list of random ingredients,
you can use the ``lumache.get_random_ingredients()`` function:
.. autofunction:: lumache.get_random_ingredients
The ``kind`` parameter should be either ``"meat"``, ``"fish"``,
or ``"veggies"``. Otherwise, :py:func:`lumache.get_random_ingredients`
will raise an exception.
.. autoexception:: lumache.InvalidKindError
For example:
>>> import lumache
>>> lumache.get_random_ingredients()
['shells', 'gorgonzola', 'parsley']

1
packsim/__init__.py Normal file
View File

@ -0,0 +1 @@
__all__ = ["simulation"]

View File

@ -1,5 +1,3 @@
#!/usr/bin/env python3
from pathlib import Path from pathlib import Path
import sys, numpy as np import sys, numpy as np

View File

@ -5,7 +5,7 @@ from shutil import which
from pathlib import Path from pathlib import Path
from simulation import Diagram, Flow, Search, Shrink from simulation import Diagram, Flow, Search, Shrink
from packsim_core import RadialTEnergy from _packsim import RadialTEnergy
dia_presets = { dia_presets = {
"animate": [["voronoi"]], "animate": [["voronoi"]],
@ -91,6 +91,10 @@ def config_sim(args):
points = None points = None
if "points" in dmn_params: if "points" in dmn_params:
if type(dmn_params["points"]) is str:
with open(Path(dmn_params["points"]), 'rb') as f:
points = np.load(f)
else:
points = np.asarray(dmn_params["points"]) points = np.asarray(dmn_params["points"])
check_params(sim_params, ["mode", "step_size", "threshold", "save_sim"], { check_params(sim_params, ["mode", "step_size", "threshold", "save_sim"], {

View File

@ -122,8 +122,8 @@ class Diagram():
ax.title.set_text('Energy vs. Time') ax.title.set_text('Energy vs. Time')
max_value = round(self.sim[0].energy) max_value = round(self.sim[0].energy)
min_value = round(self.sim[-1].energy) min_value = round(self.sim[-1].energy)
diff = max_value-min_value #diff = max_value-min_value
ax.set_yticks(np.arange(int(min_value-diff/5), int(max_value+diff/5), diff/25)) #ax.set_yticks(np.arange(int(min_value-diff/5), int(max_value+diff/5), diff/25))
ax.set_xlabel("Iterations") ax.set_xlabel("Iterations")
ax.set_ylabel("Energy") ax.set_ylabel("Energy")
ax.grid() ax.grid()
@ -494,7 +494,7 @@ class Simulation:
if filename is None: if filename is None:
path = gen_filepath(self, "sim", "simulations") path = gen_filepath(self, "sim", "simulations")
else: else:
path = f'new_simulations/{filename}.sim' path = f'simulations/{filename}.sim'
all_info = [] all_info = []
for frame in self.frames: for frame in self.frames:
@ -521,8 +521,11 @@ class Simulation:
frames = [] frames = []
with open(filename, 'rb') as data: with open(filename, 'rb') as data:
all_info, sim_class = pickle.load(data) all_info, sim_class = pickle.load(data)
if type(sim_class) == str:
sim_class = {"flow": Flow, "search": Search, "shrink": Shrink}[sim_class] sim_class = {"flow": Flow, "search": Search, "shrink": Shrink}[sim_class]
sim = sim_class(*all_info[0]["params"], all_info[0]["energy"], 0,0,0,0)
sim = sim_class(*all_info[0]["params"], "radial-t", 0,0)
for frame_info in all_info: for frame_info in all_info:
frames.append(sim.energy(*frame_info["params"], frame_info["arr"])) frames.append(sim.energy(*frame_info["params"], frame_info["arr"]))
#frames[-1].stats = frame_info["stats"] #frames[-1].stats = frame_info["stats"]
@ -575,35 +578,37 @@ class Flow(Simulation):
print(f'Find - N = {self.n}, R = {self.r}, {self.w} X {self.h}', flush=True) print(f'Find - N = {self.n}, R = {self.r}, {self.w} X {self.h}', flush=True)
i, grad_norm = 0, float('inf') i, grad_norm = 0, float('inf')
trial = 2 #trial = 2
while grad_norm > self.thres: # Get to threshold. j = 1
# Iterate and generate next frame using RK-3 while i*self.step_size <= 500:
#while grad_norm > self.thres: # Get to threshold.
# Iterate and generate next frame using RK-2
start = timer() start = timer()
change, grad = self.frames[i].iterate(self.step_size) change, grad = self.frames[-1].iterate(self.step_size)
new_frame = self.energy(self.n, self.w, self.h, self.r, new_frame = self.energy(self.n, self.w, self.h, self.r,
self.frames[i].add_sites(change)) self.frames[-1].add_sites(change))
grad_norm = np.sum(np.absolute(grad))/self.n grad_norm = np.linalg.norm(grad)
end = timer() end = timer()
if new_frame.energy < self.frames[i].energy: # If energy decreases. # if new_frame.energy < self.frames[i].energy: # If energy decreases.
if trial < 20: # Try increasing step size for 10 times. # if trial < 20: # Try increasing step size for 10 times.
factor = 1 + .1**trial # factor = 1 + .1**trial
test_frame = self.energy(self.n, self.w, self.h, self.r, # test_frame = self.energy(self.n, self.w, self.h, self.r,
self.frames[i].add_sites(change*factor)) # self.frames[i].add_sites(change*factor))
# If increased step has less energy than original step. # # If increased step has less energy than original step.
if test_frame.energy < new_frame.energy: # if test_frame.energy < new_frame.energy:
self.step_size *= factor # self.step_size *= factor
trial = max(2, trial-1) # trial = max(2, trial-1)
new_frame = test_frame # new_frame = test_frame
else: # Otherwise, increases trials, and use original. # else: # Otherwise, increases trials, and use original.
trial += 1 # trial += 1
else: # Step size too large, decrease and reset trial counter. # else: # Step size too large, decrease and reset trial counter.
trial = 2 # trial = 2
shrink_factor = 1.5 # shrink_factor = 1.5
new_frame = self.energy(self.n, self.w, self.h, self.r, # new_frame = self.energy(self.n, self.w, self.h, self.r,
self.frames[i].add_sites(change/shrink_factor)) # self.frames[i].add_sites(change/shrink_factor))
self.step_size /= shrink_factor # self.step_size /= shrink_factor
#self.step_size *= abs(.01/np.linalg.norm(error))**(1/3) #self.step_size *= abs(.01/np.linalg.norm(error))**(1/3)
#self.step_size = max(10e-4, self.step_size) #self.step_size = max(10e-4, self.step_size)
@ -611,10 +616,17 @@ class Flow(Simulation):
i += 1 i += 1
if(log and i % log_steps == 0): if(log and i % log_steps == 0):
print(f'Iteration: {i:05} | Energy: {self.frames[i].energy: .5f}' + \ print(f'Iteration: {i:05} | Energy: {self.frames[-1].energy: .5f}' + \
f' | Gradient: {grad_norm:.8f} | Step: {self.step_size: .5f} | ' + \ f' | Gradient: {grad_norm:.8f} | Step: {self.step_size: .5f} | ' + \
f'Time: {end-start: .3f}', flush=True) f'Time: {end-start: .3f}', flush=True)
if i % 5000 == 0:
new_frames = [self.frames[-1]]
self.frames = self.frames[:-1]
self.save(f"N200-{self.step_size}-part{j}")
j += 1
self.frames = new_frames
class Search(Simulation): class Search(Simulation):
""" """
@ -677,8 +689,6 @@ class Search(Simulation):
if zero_eigs != 2: if zero_eigs != 2:
print("WARNING, 0 EIGS NOT 2", zero_eigs) print("WARNING, 0 EIGS NOT 2", zero_eigs)
if i == self.iter-1:
break
if len(ns) <= 2: if len(ns) <= 2:
new_sites = dim * np.random.random_sample((self.n, 2)) new_sites = dim * np.random.random_sample((self.n, 2))
@ -687,9 +697,12 @@ class Search(Simulation):
new_sites = self.frames[i].add_sites(self.kernel_step*vec.reshape((self.n, 2))) new_sites = self.frames[i].add_sites(self.kernel_step*vec.reshape((self.n, 2)))
new_sites += (center - new_sites[fixed]) % dim # Offset new_sites += (center - new_sites[fixed]) % dim # Offset
if i < self.iter-1:
self.frames.append(None) self.frames.append(None)
class Shrink(Simulation): class Shrink(Simulation):
""" """
Class for traversing to other equilibriums from an equilbrium. Class for traversing to other equilibriums from an equilbrium.

3
pyproject.toml Normal file
View File

@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools >= 42", "wheel", "Cython", "numpy"]
build-backend = "setuptools.build_meta"

36
setup.cfg Normal file
View File

@ -0,0 +1,36 @@
[metadata]
name = packsim-ksjdragon
version = 0.1
author = Kenneth Jao
author_email = ksjdragon@gmail.com
description = PackSim is a Python package that handles the simulations for Voronoi cells undergoing a gradient flow.
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/ksjdragon/packsim
project_urls =
Bug Tracker = https://github.com/ksjdragon/packsim/issues
classifiers =
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Operating System :: OS Independent
[options]
zip_safe = False
package_dir =
= packsim
packages = find:
python_requires = >= 3.8
install_requires =
numpy == 1.21.2
scipy == 1.7.1
matplotlib == 3.4.3
[options.packages.find]
where = packsim
[options.entry_points]
console_scripts =
simulate = packsim.scripts.simulate:main
shrink_energy_comparison = packsim.scripts.shrink_energy_comparison:main

View File

@ -2,19 +2,17 @@ from setuptools import Extension, setup
from Cython.Build import cythonize from Cython.Build import cythonize
import numpy import numpy
MODULE_NAME = "packsim_core"
ext_modules = [ ext_modules = [
Extension( Extension(
MODULE_NAME, "_packsim",
[f'{MODULE_NAME}.pyx'], ["src/_packsim.pyx"],
extra_compile_args=['-fopenmp'], extra_compile_args=['-fopenmp'],
extra_link_args=['-fopenmp'] extra_link_args=['-fopenmp']
) )
] ]
setup( setup(
name=MODULE_NAME, name="packsim",
ext_modules = cythonize(ext_modules, compiler_directives={ ext_modules = cythonize(ext_modules, compiler_directives={
'language_level': 3, 'boundscheck' : False, 'wraparound': False, 'cdivision' : True 'language_level': 3, 'boundscheck' : False, 'wraparound': False, 'cdivision' : True
}), }),

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ from cpython cimport array
from libc.stdlib cimport malloc, realloc, calloc, free from libc.stdlib cimport malloc, realloc, calloc, free
from libc.math cimport isnan, NAN, pi as PI, M_PI_2 as PI_2, \ from libc.math cimport isnan, NAN, pi as PI, M_PI_2 as PI_2, \
sqrt, log, sin, cos, tan, acos, fabs sqrt, log, sin, cos, tan, acos, fabs
from packsim_core cimport INT_T, FLOAT_T, Init, IArray, FArray, BitSet, Vector2D, Matrix2x2, \ from _packsim cimport INT_T, FLOAT_T, Init, IArray, FArray, BitSet, Vector2D, Matrix2x2, \
VectorSelfOps, VectorCopyOps, MatrixSelfOps, MatrixCopyOps, \ VectorSelfOps, VectorCopyOps, MatrixSelfOps, MatrixCopyOps, \
SiteCacheMap, EdgeCacheMap, VoronoiInfo, Site, HalfEdge SiteCacheMap, EdgeCacheMap, VoronoiInfo, Site, HalfEdge

View File

@ -1,4 +1,4 @@
from packsim_core cimport SiteCacheMap, EdgeCacheMap, VoronoiInfo, Site, HalfEdge from _packsim cimport SiteCacheMap, EdgeCacheMap, VoronoiInfo, Site, HalfEdge
#### Constants #### #### Constants ####
@ -405,12 +405,6 @@ cdef class VoronoiContainer:
self.common_cache() self.common_cache()
self.precompute() self.precompute()
self.calc_grad() self.calc_grad()
self.get_statistics()
# #print(np.asarray(self.site_cache[0]))
# print(np.asarray(self.edges[:6]))
# #print(np.asarray(self.edge_cache[:6]))
# print(self.gradient)
cdef void calculate_voronoi(VoronoiContainer self, cdef void calculate_voronoi(VoronoiContainer self,
@ -622,10 +616,7 @@ cdef class VoronoiContainer:
cache = self.site_cache[:self.n, :] cache = self.site_cache[:self.n, :]
self.stats["site_areas"] = np.asarray(cache[:, SITE_CACHE_MAP.iarea]) self.stats["site_areas"] = np.asarray(cache[:, SITE_CACHE_MAP.iarea])
#edge_count = self.sites[:, 2]np.empty((self.n,)) self.stats["site_edge_count"] = np.asarray(self.sites[:self.n, 2])
# for i in range(self.n):
# edge_count[i] = len(self.vor_data.regions[self.vor_data.point_region[i]])
self.stats["site_edge_count"] = self.sites[:self.n, 2]
self.stats["site_isos"] = np.asarray(cache[:, SITE_CACHE_MAP.iisoparam]) self.stats["site_isos"] = np.asarray(cache[:, SITE_CACHE_MAP.iisoparam])
self.stats["site_energies"] = np.asarray(cache[:, SITE_CACHE_MAP.ienergy]) self.stats["site_energies"] = np.asarray(cache[:, SITE_CACHE_MAP.ienergy])
@ -661,21 +652,29 @@ cdef class VoronoiContainer:
def iterate(self, FLOAT_T step): def iterate(self, FLOAT_T step):
k1 = self.gradient k1 = self.gradient
k2 = self.__class__(self.n, self.w, self.h, self.r, k2 = self.__class__(self.n, self.w, self.h, self.r,
self.add_sites(step*k1/2) self.add_sites(step*k1)
).gradient ).gradient
lower = step*(-k1+ 2*k2) return (step/2)*(k1+k2), k1
k3 = self.__class__(self.n, self.w, self.h, self.r, # k1 = self.gradient
self.add_sites(lower)
).gradient # k2 = self.__class__(self.n, self.w, self.h, self.r,
# self.add_sites(step*k1/2)
# ).gradient
# lower = step*(-k1+ 2*k2)
# k3 = self.__class__(self.n, self.w, self.h, self.r,
# self.add_sites(lower)
# ).gradient
# higher = (step/6)*(k1+2*k2+k3)
higher = (step/6)*(k1+2*k2+k3)
#new_sites = self.add_sites(higher) #new_sites = self.add_sites(higher)
#error = higher - lower #error = higher - lower
return higher, k1 #return higher, k1
def hessian(self, d: float) -> np.ndarray: def hessian(self, d: float) -> np.ndarray:
""" """

View File

@ -1,19 +0,0 @@
{
"domain": {
"n_objects": 100,
"width": 10.0,
"height": 10.0,
"natural_radius": 4.0,
"energy": "radial-t"
},
"simulation": {
"mode": "flow",
"step_size": 0.05,
"threshold": 0.00001,
"save_sim": true
},
"diagram": {
"filetype": "mp4",
"figures": "energy"
}
}