diff --git a/index.html b/index.html index 483b20f..fe5ef6b 100644 --- a/index.html +++ b/index.html @@ -369,6 +369,15 @@ + + +
tv.osc
: Open Sound Control (OSC) via iipyper, including automated export of OSC schemas to JSON, XML, Pure Data (Pd), Max/MSP (SuperCollider TBC).tv.iml
: Interactive Machine Learning via anguilla.tv.ti
: Taichi-based simulation and rendering engine. Can be run "headless" (without graphics).tv.cv
: computer vision integration based on OpenCV.tv.cv
: computer vision integration based on OpenCV and Mediapipe.Examples can be found at iil-examples/tolvera. +When run as a script, Tölvera program looks like this:
+from tolvera import Tolvera, run
+
+def main(**kwargs):
+ tv = Tolvera(**kwargs)
+
+ @tv.render
+ def _():
+ return tv.px
+
+if __name__ == '__main__':
+ run(main)
+
Taichi supports numerous operating systems and backends. If you plan on using Vulkan for graphics (recommended for macOS), you may need to install the Vulkan SDK first and restart your machine.
Tölvera is registered on PyPI and can be installed via a Python package manager such as pip
:
pip install tolvera
+
Develop
Fork/clone this repository and install the package with poetry
:
-git clone https://github.com/Intelligent-Instruments-Lab/tolvera # (or clone your own fork)
-cd tolvera
-poetry install
+git clone https://github.com/Intelligent-Instruments-Lab/tolvera # (or clone your own fork)
+cd tolvera
+poetry install
Contribute
We welcome Pull Requests across all areas of the project:
@@ -1419,33 +1452,34 @@ Community
Use GitHub Discussions to share ideas and ask questions.
Use Discord for further support, sharing your work, and general chat.
-Across the project, we follow the Algorave Code of Conduct. Please get in touch if you experience or witness any conduct issues.
+Across the project, we follow the Berlin Code of Conduct.
+Please get in touch if you experience or witness any conduct issues.
Roadmap
See Discussion.
Citing
Tölvera is being written about and used in a number of contexts (see references.bib), here are a few recent examples:
-@inproceedings{armitageAgentialScoresExploring2023,
- Address = {Boston, Massachusetts, USA},
- Author = { Jack Armitage and Thor Magnusson },
- Title = {Agential Scores: Artificial Life for Emergent, Self-Organising and Entangled Music Notation},
- Booktitle = {Proceedings of the International Conference on Technologies for Music Notation and Representation -- TENOR'2023},
- Pages = {51 - 61},
- Year = {2023},
- Editor = {Anthony Paul De Ritis and Victor Zappi and Jeremy Van Buskirk and John Mallia},
- Publisher = {Northeastern University},
- ISBN = {978-0-6481592-7-8}
-}
-
-@inproceedings{armitageStrengjavera2023,
- title = {Strengjavera},
- booktitle = {{{AI Music Creativity}} 2023},
- author = {Armitage, Jack},
- year = {2023},
- address = {{University of Sussex, Brighton, UK}},
- doi = {10.5281/zenodo.8329855},
- ISBN = {978-0-9957862-9-5},
- url = {https://zenodo.org/records/8329855}
-}
+@inproceedings{armitageAgentialScoresExploring2023,
+ Address = {Boston, Massachusetts, USA},
+ Author = { Jack Armitage and Thor Magnusson },
+ Title = {Agential Scores: Artificial Life for Emergent, Self-Organising and Entangled Music Notation},
+ Booktitle = {Proceedings of the International Conference on Technologies for Music Notation and Representation -- TENOR'2023},
+ Pages = {51 - 61},
+ Year = {2023},
+ Editor = {Anthony Paul De Ritis and Victor Zappi and Jeremy Van Buskirk and John Mallia},
+ Publisher = {Northeastern University},
+ ISBN = {978-0-6481592-7-8}
+}
+
+@inproceedings{armitageStrengjavera2023,
+ title = {Strengjavera},
+ booktitle = {{{AI Music Creativity}} 2023},
+ author = {Armitage, Jack},
+ year = {2023},
+ address = {{University of Sussex, Brighton, UK}},
+ doi = {10.5281/zenodo.8329855},
+ ISBN = {978-0-9957862-9-5},
+ url = {https://zenodo.org/records/8329855}
+}
Inspiration
diff --git a/search/search_index.json b/search/search_index.json
index f241ece..6a04f04 100644
--- a/search/search_index.json
+++ b/search/search_index.json
@@ -1 +1 @@
-{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"T\u00f6lvera","text":""},{"location":"#tolvera","title":"T\u00f6lvera","text":"T\u00f6lvera is a library for exploring musical performance with artificial life (ALife) and self-organising systems. The word is an Icelandic kenning:
- T\u00f6lva = computer, from tala (number) + v\u00f6lva (prophetess)
- Vera = being
- T\u00f6lvera = number being
T\u00f6lvera is written in Taichi, a domain-specific language embedded in Python.
This is experimental software and everything is currently subject to change.
Join us on the T\u00f6lvera Discord.
"},{"location":"#showcase","title":"Showcase","text":"Visit the YouTube Playlist (if you'd like to add a video, please get in touch).
"},{"location":"#features","title":"Features","text":" tv.v
: a collection of \"vera\" (beings) including Move, Flock, Slime and Swarm, with more being continuously added. Vera can be combined and composed in various ways. tv.p
: extensible particle system. Particles are divided into multiple species, where each species has a unique relationship with every other species, including itself tv.s
: n-dimensional state structures that can be used by \"vera\", including built-in OSC and IML creation (see below). tv.px
: drawing library including various shapes and blend modes, styled similarly to p5.js etc. tv.osc
: Open Sound Control (OSC) via iipyper, including automated export of OSC schemas to JSON, XML, Pure Data (Pd), Max/MSP (SuperCollider TBC). tv.iml
: Interactive Machine Learning via anguilla. tv.ti
: Taichi-based simulation and rendering engine. Can be run \"headless\" (without graphics). tv.cv
: computer vision integration based on OpenCV.
"},{"location":"#install","title":"Install","text":"Taichi supports numerous operating systems and backends. If you plan on using Vulkan for graphics (recommended for macOS), you may need to install the Vulkan SDK first and restart your machine.
T\u00f6lvera is registered on PyPI and can be installed via a Python package manager such as pip
:
pip install tolvera\n
"},{"location":"#develop","title":"Develop","text":"Fork/clone this repository and install the package with poetry
:
git clone https://github.com/Intelligent-Instruments-Lab/tolvera # (or clone your own fork)\ncd tolvera\npoetry install\n
"},{"location":"#contribute","title":"Contribute","text":"We welcome Pull Requests across all areas of the project:
- Addressing Issues
- Adding features (see Issues and Discussion)
- Examples
- Tests
- Documentation
"},{"location":"#community","title":"Community","text":"To discuss T\u00f6lvera with developers and other users:
- Use GitHub Issues to report bugs and make specific feature requests.
- Use GitHub Discussions to share ideas and ask questions.
- Use Discord for further support, sharing your work, and general chat.
Across the project, we follow the Algorave Code of Conduct. Please get in touch if you experience or witness any conduct issues.
"},{"location":"#roadmap","title":"Roadmap","text":"See Discussion.
"},{"location":"#citing","title":"Citing","text":"T\u00f6lvera is being written about and used in a number of contexts (see references.bib), here are a few recent examples:
@inproceedings{armitageAgentialScoresExploring2023,\n Address = {Boston, Massachusetts, USA},\n Author = { Jack Armitage and Thor Magnusson },\n Title = {Agential Scores: Artificial Life for Emergent, Self-Organising and Entangled Music Notation},\n Booktitle = {Proceedings of the International Conference on Technologies for Music Notation and Representation -- TENOR'2023},\n Pages = {51 - 61},\n Year = {2023},\n Editor = {Anthony Paul De Ritis and Victor Zappi and Jeremy Van Buskirk and John Mallia},\n Publisher = {Northeastern University},\n ISBN = {978-0-6481592-7-8}\n}\n\n@inproceedings{armitageStrengjavera2023,\n title = {Strengjavera},\n booktitle = {{{AI Music Creativity}} 2023},\n author = {Armitage, Jack},\n year = {2023},\n address = {{University of Sussex, Brighton, UK}},\n doi = {10.5281/zenodo.8329855},\n ISBN = {978-0-9957862-9-5},\n url = {https://zenodo.org/records/8329855}\n}\n
"},{"location":"#inspiration","title":"Inspiration","text":" - SwissGL
- Lenia
- Particle Life (attributed to various, see for example Clusters)
- Journey to the Microcosmos
- Complexity Explorables
"},{"location":"#contact","title":"Contact","text":"tolvera
is developed by the Intelligent Instruments Lab. Get in touch to collaborate:
\u25e6 iil.is \u25e6 Facebook \u25e6 Instagram \u25e6 X (Twitter) \u25e6 YouTube \u25e6 Discord \u25e6 GitHub \u25e6 LinkedIn \u25e6 Email \u25e6
"},{"location":"#funding","title":"Funding","text":"The Intelligent Instruments project (INTENT) is funded by the European Research Council (ERC) under the European Union\u2019s Horizon 2020 research and innovation programme (Grant agreement No. 101001848).
"},{"location":"reference/tolvera/context/","title":"Context","text":"TolveraContext
is a shared context or environment for Tolvera
instances. It is created automatically when a Tolvera
instance is created, if one does not already exist. It manages the integration of packages for graphics, computer vision, communications protocols, and more. If multiple Tolvera
instances are created, they must share the same TolveraContext
.
Example TolveraContext
can be created manually, and shared with multiple Tolvera
instances. Note that only one render
function can be used at a time.
from tolvera import TolveraContext, Tolvera, run\n\ndef main(**kwargs):\n ctx = TolveraContext(**kwargs)\n\n tv1 = Tolvera(ctx=ctx, **kwargs)\n tv2 = Tolvera(ctx=ctx, **kwargs)\n\n @tv1.render\n def _():\n return tv2.px\n\nif __name__ == '__main__':\n run(main)\n
Example TolveraContext
can also be created automatically, and still shared.
from tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv1 = Tolvera(**kwargs)\n tv2 = Tolvera(ctx=tv1.ctx, **kwargs)\n\n @tv1.render\n def _():\n return tv2.px\n\nif __name__ == '__main__':\n run(main)\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext","title":"TolveraContext
","text":"Context for sharing between multiple T\u00f6lvera instances. Context includes Taichi, OSC, IML and CV. All T\u00f6lvera instances share the same context and are managed as a dict.
Attributes:
Name Type Description kwargs
dict
Keyword arguments for context.
name
str
Name of context.
name_clean
str
'Cleaned' name of context.
i
int
Frame counter.
x
int
Width of canvas.
y
int
Height of canvas.
ti
Taichi
Taichi instance.
canvas
Pixels
Pixels instance.
osc
OSC
OSC instance.
iml
IML
IML instance.
cv
CV
CV instance.
_cleanup_fns
list
List of cleanup functions.
tolveras
dict
Dict of T\u00f6lvera instances.
Source code in src/tolvera/context.py
class TolveraContext:\n \"\"\"\n Context for sharing between multiple T\u00f6lvera instances.\n Context includes Taichi, OSC, IML and CV.\n All T\u00f6lvera instances share the same context and are managed as a dict.\n\n Attributes:\n kwargs (dict): Keyword arguments for context.\n name (str): Name of context.\n name_clean (str): 'Cleaned' name of context.\n i (int): Frame counter.\n x (int): Width of canvas.\n y (int): Height of canvas.\n ti (Taichi): Taichi instance.\n canvas (Pixels): Pixels instance.\n osc (OSC): OSC instance.\n iml (IML): IML instance.\n cv (CV): CV instance.\n _cleanup_fns (list): List of cleanup functions.\n tolveras (dict): Dict of T\u00f6lvera instances.\n \"\"\"\n\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise T\u00f6lvera context with given keyword arguments.\"\"\"\n self.kwargs = kwargs\n self.init(**kwargs)\n\n def init(self, **kwargs):\n \"\"\"\n Initialise wrapped external packages with given keyword arguments.\n This only happens once when T\u00f6lvera is first initialised.\n\n Args:\n x (int): Width of canvas. Default: 1920.\n y (int): Height of canvas. Default: 1080.\n osc (bool): Enable OSC. Default: False.\n iml (bool): Enable IML. Default: False.\n cv (bool): Enable CV. Default: False.\n see also kwargs for Taichi, OSC, IMLDict, and CV.\n \"\"\"\n self.name = \"T\u00f6lvera Context\"\n self.name_clean = clean_name(self.name)\n print(f\"[{self.name}] Initializing context...\")\n self.i = 0\n self.x = kwargs.get(\"x\", 1920)\n self.y = kwargs.get(\"y\", 1080)\n self.ti = Taichi(self, **kwargs)\n self.show = self.ti.show\n self.canvas = Pixels(self, **kwargs)\n self.s = StateDict(self)\n self.osc = kwargs.get(\"osc\", False)\n self.iml = kwargs.get(\"iml\", False)\n self.cv = kwargs.get(\"cv\", False)\n self.hands = kwargs.get(\"hands\", False)\n if self.osc:\n self.osc = OSC(self, **kwargs)\n if self.iml:\n self.iml = IMLDict(self)\n if self.cv:\n self.cv = CV(self, **kwargs)\n self.hands = MPHands(self, **kwargs)\n self._cleanup_fns = []\n self.tolveras = {}\n print(f\"[{self.name}] Context initialisation complete.\")\n\n def run(self, f=None, **kwargs):\n \"\"\"\n Run T\u00f6lvera with given render function and keyword arguments.\n This function will run inside a locked thread until KeyboardInterrupt/exit.\n It runs the render function, updates the OSC map (if enabled), and shows the pixels.\n\n Args:\n f: Function to run.\n **kwargs: Keyword arguments for function.\n \"\"\"\n if f is not None:\n print(f\"[{self.name}] Running with render function {f.__name__}...\")\n else:\n print(f\"[{self.name}] Running with no render function...\")\n while self.ti.window.running:\n with _lock:\n [t.p() for t in self.tolveras.values()]\n if f is not None:\n self.canvas = f(**kwargs)\n if self.osc is not False:\n self.osc.map()\n if self.iml is not False:\n self.iml()\n if self.cv is not False:\n self.cv()\n self.ti.show(self.canvas)\n self.i += 1\n\n def stop(self):\n \"\"\"\n Run cleanup functions and exit.\n \"\"\"\n print(f\"\\n[{self.name}] Stopping {self.name}...\")\n for f in self._cleanup_fns:\n print(f\"\\n[{self.name}] Running cleanup function {f.__name__}...\")\n f()\n print(f\"\\n[{self.name}] Exiting {self.name}...\")\n exit(0)\n\n def render(self, f=None, **kwargs):\n \"\"\"Render T\u00f6lvera with given function and keyword arguments.\n\n Args:\n f (function, optional): Function to run. Defaults to None.\n \"\"\"\n try:\n self.run(f, **kwargs)\n except KeyboardInterrupt:\n self.stop()\n\n def cleanup(self, f=None):\n \"\"\"\n Decorator for cleanup functions based on iipyper.\n Make functions run on KeyBoardInterrupt (before exit).\n Cleanup functions must be defined before render is called!\n\n Args:\n f: Function to cleanup.\n\n Returns:\n Decorator function if f is None, else decorated function.\n \"\"\"\n print(f\"\\n[{self.name}] Adding cleanup function {f.__name__}...\")\n\n def decorator(f):\n \"\"\"Decorator that appends function to cleanup functions.\"\"\"\n self._cleanup_fns.append(f)\n return f\n\n if f is None: # return a decorator\n return decorator\n else: # bare decorator case; return decorated function\n return decorator(f)\n\n def add(self, tolvera):\n \"\"\"\n Add T\u00f6lvera to context.\n\n Args:\n tolvera (Tolvera): T\u00f6lvera to add.\n \"\"\"\n print(f\"[{self.name}] Adding tolvera='{tolvera.name}' to context.\")\n self.tolveras[tolvera.name] = tolvera\n\n def get_by_name(self, name):\n \"\"\"\n Get T\u00f6lvera by name.\n\n Args:\n name (str): Name of T\u00f6lvera to get.\n\n Returns:\n T\u00f6lvera: T\u00f6lvera with given name.\n \"\"\"\n return self.tolveras[name]\n\n def get_names(self):\n \"\"\"\n Get names of all T\u00f6lveras in context.\n\n Returns:\n list: List of T\u00f6lvera names.\n \"\"\"\n return list(self.tolveras.keys())\n\n def remove(self, name):\n \"\"\"\n Remove T\u00f6lvera by name.\n\n Args:\n name (str): Name of T\u00f6lvera to delete.\n \"\"\"\n print(f\"[{self.name}] Deleting tolvera='{name}' from context.\")\n del self.tolveras[name]\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.__init__","title":"__init__(**kwargs)
","text":"Initialise T\u00f6lvera context with given keyword arguments.
Source code in src/tolvera/context.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise T\u00f6lvera context with given keyword arguments.\"\"\"\n self.kwargs = kwargs\n self.init(**kwargs)\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.add","title":"add(tolvera)
","text":"Add T\u00f6lvera to context.
Parameters:
Name Type Description Default tolvera
Tolvera
T\u00f6lvera to add.
required Source code in src/tolvera/context.py
def add(self, tolvera):\n \"\"\"\n Add T\u00f6lvera to context.\n\n Args:\n tolvera (Tolvera): T\u00f6lvera to add.\n \"\"\"\n print(f\"[{self.name}] Adding tolvera='{tolvera.name}' to context.\")\n self.tolveras[tolvera.name] = tolvera\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.cleanup","title":"cleanup(f=None)
","text":"Decorator for cleanup functions based on iipyper. Make functions run on KeyBoardInterrupt (before exit). Cleanup functions must be defined before render is called!
Parameters:
Name Type Description Default f
Function to cleanup.
None
Returns:
Type Description Decorator function if f is None, else decorated function.
Source code in src/tolvera/context.py
def cleanup(self, f=None):\n \"\"\"\n Decorator for cleanup functions based on iipyper.\n Make functions run on KeyBoardInterrupt (before exit).\n Cleanup functions must be defined before render is called!\n\n Args:\n f: Function to cleanup.\n\n Returns:\n Decorator function if f is None, else decorated function.\n \"\"\"\n print(f\"\\n[{self.name}] Adding cleanup function {f.__name__}...\")\n\n def decorator(f):\n \"\"\"Decorator that appends function to cleanup functions.\"\"\"\n self._cleanup_fns.append(f)\n return f\n\n if f is None: # return a decorator\n return decorator\n else: # bare decorator case; return decorated function\n return decorator(f)\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.get_by_name","title":"get_by_name(name)
","text":"Get T\u00f6lvera by name.
Parameters:
Name Type Description Default name
str
Name of T\u00f6lvera to get.
required Returns:
Name Type Description T\u00f6lvera
T\u00f6lvera with given name.
Source code in src/tolvera/context.py
def get_by_name(self, name):\n \"\"\"\n Get T\u00f6lvera by name.\n\n Args:\n name (str): Name of T\u00f6lvera to get.\n\n Returns:\n T\u00f6lvera: T\u00f6lvera with given name.\n \"\"\"\n return self.tolveras[name]\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.get_names","title":"get_names()
","text":"Get names of all T\u00f6lveras in context.
Returns:
Name Type Description list
List of T\u00f6lvera names.
Source code in src/tolvera/context.py
def get_names(self):\n \"\"\"\n Get names of all T\u00f6lveras in context.\n\n Returns:\n list: List of T\u00f6lvera names.\n \"\"\"\n return list(self.tolveras.keys())\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.init","title":"init(**kwargs)
","text":"Initialise wrapped external packages with given keyword arguments. This only happens once when T\u00f6lvera is first initialised.
Parameters:
Name Type Description Default x
int
Width of canvas. Default: 1920.
required y
int
Height of canvas. Default: 1080.
required osc
bool
Enable OSC. Default: False.
required iml
bool
Enable IML. Default: False.
required cv
bool
Enable CV. Default: False.
required Source code in src/tolvera/context.py
def init(self, **kwargs):\n \"\"\"\n Initialise wrapped external packages with given keyword arguments.\n This only happens once when T\u00f6lvera is first initialised.\n\n Args:\n x (int): Width of canvas. Default: 1920.\n y (int): Height of canvas. Default: 1080.\n osc (bool): Enable OSC. Default: False.\n iml (bool): Enable IML. Default: False.\n cv (bool): Enable CV. Default: False.\n see also kwargs for Taichi, OSC, IMLDict, and CV.\n \"\"\"\n self.name = \"T\u00f6lvera Context\"\n self.name_clean = clean_name(self.name)\n print(f\"[{self.name}] Initializing context...\")\n self.i = 0\n self.x = kwargs.get(\"x\", 1920)\n self.y = kwargs.get(\"y\", 1080)\n self.ti = Taichi(self, **kwargs)\n self.show = self.ti.show\n self.canvas = Pixels(self, **kwargs)\n self.s = StateDict(self)\n self.osc = kwargs.get(\"osc\", False)\n self.iml = kwargs.get(\"iml\", False)\n self.cv = kwargs.get(\"cv\", False)\n self.hands = kwargs.get(\"hands\", False)\n if self.osc:\n self.osc = OSC(self, **kwargs)\n if self.iml:\n self.iml = IMLDict(self)\n if self.cv:\n self.cv = CV(self, **kwargs)\n self.hands = MPHands(self, **kwargs)\n self._cleanup_fns = []\n self.tolveras = {}\n print(f\"[{self.name}] Context initialisation complete.\")\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.remove","title":"remove(name)
","text":"Remove T\u00f6lvera by name.
Parameters:
Name Type Description Default name
str
Name of T\u00f6lvera to delete.
required Source code in src/tolvera/context.py
def remove(self, name):\n \"\"\"\n Remove T\u00f6lvera by name.\n\n Args:\n name (str): Name of T\u00f6lvera to delete.\n \"\"\"\n print(f\"[{self.name}] Deleting tolvera='{name}' from context.\")\n del self.tolveras[name]\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.render","title":"render(f=None, **kwargs)
","text":"Render T\u00f6lvera with given function and keyword arguments.
Parameters:
Name Type Description Default f
function
Function to run. Defaults to None.
None
Source code in src/tolvera/context.py
def render(self, f=None, **kwargs):\n \"\"\"Render T\u00f6lvera with given function and keyword arguments.\n\n Args:\n f (function, optional): Function to run. Defaults to None.\n \"\"\"\n try:\n self.run(f, **kwargs)\n except KeyboardInterrupt:\n self.stop()\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.run","title":"run(f=None, **kwargs)
","text":"Run T\u00f6lvera with given render function and keyword arguments. This function will run inside a locked thread until KeyboardInterrupt/exit. It runs the render function, updates the OSC map (if enabled), and shows the pixels.
Parameters:
Name Type Description Default f
Function to run.
None
**kwargs
Keyword arguments for function.
{}
Source code in src/tolvera/context.py
def run(self, f=None, **kwargs):\n \"\"\"\n Run T\u00f6lvera with given render function and keyword arguments.\n This function will run inside a locked thread until KeyboardInterrupt/exit.\n It runs the render function, updates the OSC map (if enabled), and shows the pixels.\n\n Args:\n f: Function to run.\n **kwargs: Keyword arguments for function.\n \"\"\"\n if f is not None:\n print(f\"[{self.name}] Running with render function {f.__name__}...\")\n else:\n print(f\"[{self.name}] Running with no render function...\")\n while self.ti.window.running:\n with _lock:\n [t.p() for t in self.tolveras.values()]\n if f is not None:\n self.canvas = f(**kwargs)\n if self.osc is not False:\n self.osc.map()\n if self.iml is not False:\n self.iml()\n if self.cv is not False:\n self.cv()\n self.ti.show(self.canvas)\n self.i += 1\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.stop","title":"stop()
","text":"Run cleanup functions and exit.
Source code in src/tolvera/context.py
def stop(self):\n \"\"\"\n Run cleanup functions and exit.\n \"\"\"\n print(f\"\\n[{self.name}] Stopping {self.name}...\")\n for f in self._cleanup_fns:\n print(f\"\\n[{self.name}] Running cleanup function {f.__name__}...\")\n f()\n print(f\"\\n[{self.name}] Exiting {self.name}...\")\n exit(0)\n
"},{"location":"reference/tolvera/cv/","title":"Cv","text":""},{"location":"reference/tolvera/iml/","title":"Iml","text":"IML stands for Interactive Machine Learning. T\u00f6lvera wraps the anguilla package to provide convenient ways for quickly creating mappings between vectors, functions and OSC routes.
Every T\u00f6lvera instance has an IMLDict, which is a dictionary of IML instances. The IMLDict is accessible via the iml
attribute of a T\u00f6lvera instance, and can be used to create and access IML instances.
There are 9 IML types, which are listed below.
Example Here we create a mapping based on states created by tv.v.flock
, where the per-particle state flock_p
is mapped to the species rule matrix flock_s
. Since this is a fun2fun
mapping (see IML Types below), we provide input and output functions, and T\u00f6lvera updates the mapping automatically every render()
call.
from tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n tv.iml.flock_p2flock_s = {\n 'type': 'fun2fun', \n 'size': (tv.s.flock_p.size, tv.s.flock_s.size), \n 'io': (tv.s.flock_p.to_vec, tv.s.flock_s.from_vec),\n 'randomise': True,\n }\n\n @tv.render\n def _():\n tv.px.diffuse(0.99)\n tv.v.flock(tv.p)\n tv.px.particles(tv.p, tv.s.species, 'circle')\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
IML Types vec2vec
: Vector to vector mapping. vec2fun
: Vector to function mapping. vec2osc
: Vector to OSC mapping. fun2vec
: Function to vector mapping. fun2fun
: Function to function mapping. fun2osc
: Function to OSC mapping. osc2vec
: OSC to vector mapping. osc2fun
: OSC to function mapping. osc2osc
: OSC to OSC mapping.
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase","title":"IMLBase
","text":" Bases: IML
This class inherits from anguilla and adds some functionality. It is not intended to be used directly, but rather to be inherited from.
The base class is initialised with a size tuple (input, output) and a config dict which is passed to anguilla.IML
.
It provides a randomise
method which adds random pairs to the mapping. It also provides methods to remove pairs (remove_oldest
, remove_newest
, remove_random
). It also provides a lag
method which lags the mapped data. Finally, it provides an update
method which is called by the updater
(see .osc.update
).
Source code in src/tolvera/iml.py
class IMLBase(iiIML):\n \"\"\"\n This class inherits from [anguilla](https://intelligent-instruments-lab.github.io/anguilla) \n and adds some functionality. It is not intended to be used directly, but rather \n to be inherited from.\n\n The base class is initialised with a size tuple (input, output) and a config dict\n which is passed to `anguilla.IML`.\n\n It provides a `randomise` method which adds random pairs to the mapping.\n It also provides methods to remove pairs (`remove_oldest`, `remove_newest`, `remove_random`).\n It also provides a `lag` method which lags the mapped data.\n Finally, it provides an `update` method which is called by the `updater` (see `.osc.update`).\n \"\"\"\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLBase\n\n kwargs:\n size (tuple, required): (input, output) sizes.\n io (tuple, optional): (input, output) functions.\n config (dict, optional): {embed_input, embed_output, interpolate, index, verbose}.\n updater (cls, optional): See iipyper.osc.update (Updater, OSCSendUpdater, ...).\n update_rate (int, optional): Updater's update rate (defaults to 1).\n randomise (bool, optional): Randomise mapping on init (defaults to False).\n rand_pairs (int, optional): Number of random pairs to add (defaults to 32).\n rand_input_weight (Any, optional): Random input weight (defaults to None).\n rand_output_weight (Any, optional): Random output weight (defaults to None).\n rand_method (str, optional): rand_method type (see utils).\n rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).\n map_kw (dict, optional): kwargs to use in IML.map().\n infun_kw (dict, optional): kwargs to use in infun() (type 'Fun2*' only).\n outfun_kw (dict, optional): kwargs to use in outfun() (type '*2Fun' only).\n lag (bool, optional): Lag mapped data (defaults to False). Incompatible with '*2Fun' types.\n lag_coef (float, optional): Lag coefficient (defaults to 0.5 if `lag` is True).\n \"\"\"\n assert \"size\" in kwargs, f\"IMLBase requires 'size' kwarg.\"\n self.size = kwargs[\"size\"]\n self.updater = kwargs.get(\n \"updater\", Updater(self.update, kwargs.get(\"update_rate\", 1))\n )\n self.config = kwargs.get(\"config\", {})\n if isinstance(self.size[0], tuple):\n self.config[\"emb\"] = \"ProjectAndSort\"\n print(f\"[tolvera._iml.IMLBase] Initialising IML with config: {self.config}\")\n super().__init__(**self.config)\n self.data = dotdict()\n self.map_kw = kwargs.get(\"map_kw\", {})\n self.infun_kw = kwargs.get(\"infun_kw\", {})\n self.outfun_kw = kwargs.get(\"outfun_kw\", {})\n if kwargs.get(\"randomise\", False):\n self.init_randomise(**kwargs)\n self.lag = kwargs.get(\"lag\", False)\n if self.lag:\n self.init_lag(**kwargs)\n\n def init_randomise(self, **kwargs):\n \"\"\"Initialise randomise() method with kwargs\n\n kwargs: see __init__ kwargs.\n \"\"\"\n self.rand_pairs = kwargs.get(\"rand_pairs\", 32)\n self.rand_input_weight = kwargs.get(\"rand_input_weight\", None)\n self.rand_output_weight = kwargs.get(\"rand_output_weight\", None)\n self.rand_method = kwargs.get(\"rand_method\", \"rand\")\n self.rand_kw = kwargs.get(\"rand_kw\", {})\n self.randomise(\n self.rand_pairs,\n self.rand_input_weight,\n self.rand_output_weight,\n self.rand_method,\n **self.rand_kw,\n )\n\n def init_lag(self, **kwargs):\n \"\"\"Initialise lag() method with kwargs\n\n kwargs: see __init__ kwargs.\n \"\"\"\n self.lag_coef = kwargs.get(\"lag_coef\", 0.5)\n self.lag = Lag(coef=self.lag_coef)\n print(\n f\"[tolvera._iml.IMLBase] Lagging mapped data with coef {self.lag_coef}.\"\n )\n\n def randomise(\n self,\n times: int,\n input_weight=None,\n output_weight=None,\n method: str = \"rand\",\n **kwargs,\n ):\n \"\"\"Randomise mapping.\n\n Args:\n times (int): Number of random pairs to add.\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n method (str, optional): Randomisation method. Defaults to \"rand\".\n \"\"\"\n self.rand = rand_select(method)\n while len(self.pairs) < times:\n self.add_random_pair(input_weight, output_weight, **kwargs)\n\n def set_random_method(self, method: str = \"rand\"):\n \"\"\"Set random method.\n\n Args:\n method (str, optional): Randomisation method. Defaults to \"rand\".\n \"\"\"\n self.rand = rand_select(method)\n\n def add_random_pair(self, input_weight=None, output_weight=None, **kwargs):\n \"\"\"Add random pair to mapping.\n\n Args:\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n **kwargs: see random_pair kwargs.\n \"\"\"\n indata, outdata = self.random_pair(input_weight, output_weight, **kwargs)\n self.add(indata, outdata)\n\n def random_input(self, **kwargs) -> torch.Tensor:\n \"\"\"Random input vector.\n\n Args:\n **kwargs: self.rand kwargs.\n\n Returns:\n torch.Tensor: Random input vector.\n \"\"\"\n return self.rand(self.size[0], **kwargs)\n\n def random_output(self, **kwargs) -> torch.Tensor:\n \"\"\"Random output vector.\n\n Args:\n **kwargs: self.rand kwargs\n\n Returns:\n torch.Tensor: Random output vector.\n \"\"\"\n return self.rand(self.size[1], **kwargs)\n\n def random_pair(self, input_weight=None, output_weight=None, **kwargs):\n \"\"\"Create random pair.\n\n Args:\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n **kwargs:\n rand_method (str, optional): Randomisation method. Defaults to \"rand\".\n rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).\n\n Raises:\n ValueError: Invalid input_weight type.\n ValueError: Invalid output_weight type.\n\n Returns:\n tuple: (input, output) vectors.\n \"\"\"\n if self.rand == None and \"rand_method\" not in kwargs:\n print(f\"[tolvera._iml.IMLBase] No 'rand' method set. Using 'rand'.\")\n self.set_random_method()\n elif \"rand_method\" in kwargs:\n self.set_random_method(kwargs[\"rand_method\"])\n if input_weight is None:\n input_weight = self.rand_input_weight\n if output_weight is None:\n output_weight = self.rand_output_weight\n indata = self.rand(self.size[0], **kwargs)\n outdata = self.rand(self.size[1], **kwargs)\n if input_weight is not None:\n if isinstance(input_weight, np.ndarray):\n indata *= torch.from_numpy(input_weight)\n elif isinstance(input_weight, (torch.Tensor, float, int)):\n indata *= input_weight\n elif isinstance(input_weight, list):\n indata *= torch.Tensor(input_weight)\n else:\n raise ValueError(\n f\"[tolvera._iml.IMLBase] Invalid input_weight type '{type(input_weight)}'.\"\n )\n if output_weight is not None:\n if isinstance(output_weight, np.ndarray):\n outdata *= torch.from_numpy(output_weight)\n elif isinstance(output_weight, (torch.Tensor, float, int)):\n outdata *= output_weight\n elif isinstance(output_weight, list):\n outdata *= torch.Tensor(output_weight)\n else:\n raise ValueError(\n f\"[tolvera._iml.IMLBase] Invalid output_weight type '{type(output_weight)}'.\"\n )\n return indata, outdata\n\n def remove_oldest(self, n: int = 1):\n \"\"\"Remove oldest pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(min(self.pairs.keys())) for _ in range(n)]\n\n def remove_newest(self, n: int = 1):\n \"\"\"Remove newest pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(max(self.pairs.keys())) for _ in range(n)]\n\n def remove_random(self, n: int = 1):\n \"\"\"Remove random pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(np.random.choice(list(self.pairs.keys()))) for _ in range(n)]\n\n def lag_mapped_data(self, lag_coef: float = 0.5):\n \"\"\"Lag mapped data.\n\n Args:\n lag_coef (float, optional): Lag coefficient. Defaults to 0.5.\n \"\"\"\n self.data.mapped = self.lag(self.data.mapped, lag_coef)\n\n def update(self, invec: list|torch.Tensor|np.ndarray) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Args:\n invec (list|torch.Tensor|np.ndarray): Input vector.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.pairs) == 0:\n return None\n self.data.mapped = self.map(invec, **self.map_kw)\n if hasattr(self, \"lag\") and type(self.lag) is Lag:\n self.lag_mapped_data()\n return self.data.mapped\n\n def update_rate(self, rate: int = None):\n \"\"\"Update rate getter/setter.\n\n Args:\n rate (int, optional): Update rate. Defaults to None.\n\n Returns:\n int: Update rate.\n \"\"\"\n if rate is not None:\n self.updater.count = rate\n return self.updater.count\n\n def __call__(self, *args) -> Any:\n \"\"\"Call updater with args.\n\n Args:\n *args: Updater args.\n\n Returns:\n Any: Mapped data.\n \"\"\"\n return self.updater(*args)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.__call__","title":"__call__(*args)
","text":"Call updater with args.
Parameters:
Name Type Description Default *args
Updater args.
()
Returns:
Name Type Description Any
Any
Mapped data.
Source code in src/tolvera/iml.py
def __call__(self, *args) -> Any:\n \"\"\"Call updater with args.\n\n Args:\n *args: Updater args.\n\n Returns:\n Any: Mapped data.\n \"\"\"\n return self.updater(*args)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLBase
kwargs size (tuple, required): (input, output) sizes. io (tuple, optional): (input, output) functions. config (dict, optional): {embed_input, embed_output, interpolate, index, verbose}. updater (cls, optional): See iipyper.osc.update (Updater, OSCSendUpdater, ...). update_rate (int, optional): Updater's update rate (defaults to 1). randomise (bool, optional): Randomise mapping on init (defaults to False). rand_pairs (int, optional): Number of random pairs to add (defaults to 32). rand_input_weight (Any, optional): Random input weight (defaults to None). rand_output_weight (Any, optional): Random output weight (defaults to None). rand_method (str, optional): rand_method type (see utils). rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils). map_kw (dict, optional): kwargs to use in IML.map(). infun_kw (dict, optional): kwargs to use in infun() (type 'Fun2' only). outfun_kw (dict, optional): kwargs to use in outfun() (type '2Fun' only). lag (bool, optional): Lag mapped data (defaults to False). Incompatible with '*2Fun' types. lag_coef (float, optional): Lag coefficient (defaults to 0.5 if lag
is True).
Source code in src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLBase\n\n kwargs:\n size (tuple, required): (input, output) sizes.\n io (tuple, optional): (input, output) functions.\n config (dict, optional): {embed_input, embed_output, interpolate, index, verbose}.\n updater (cls, optional): See iipyper.osc.update (Updater, OSCSendUpdater, ...).\n update_rate (int, optional): Updater's update rate (defaults to 1).\n randomise (bool, optional): Randomise mapping on init (defaults to False).\n rand_pairs (int, optional): Number of random pairs to add (defaults to 32).\n rand_input_weight (Any, optional): Random input weight (defaults to None).\n rand_output_weight (Any, optional): Random output weight (defaults to None).\n rand_method (str, optional): rand_method type (see utils).\n rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).\n map_kw (dict, optional): kwargs to use in IML.map().\n infun_kw (dict, optional): kwargs to use in infun() (type 'Fun2*' only).\n outfun_kw (dict, optional): kwargs to use in outfun() (type '*2Fun' only).\n lag (bool, optional): Lag mapped data (defaults to False). Incompatible with '*2Fun' types.\n lag_coef (float, optional): Lag coefficient (defaults to 0.5 if `lag` is True).\n \"\"\"\n assert \"size\" in kwargs, f\"IMLBase requires 'size' kwarg.\"\n self.size = kwargs[\"size\"]\n self.updater = kwargs.get(\n \"updater\", Updater(self.update, kwargs.get(\"update_rate\", 1))\n )\n self.config = kwargs.get(\"config\", {})\n if isinstance(self.size[0], tuple):\n self.config[\"emb\"] = \"ProjectAndSort\"\n print(f\"[tolvera._iml.IMLBase] Initialising IML with config: {self.config}\")\n super().__init__(**self.config)\n self.data = dotdict()\n self.map_kw = kwargs.get(\"map_kw\", {})\n self.infun_kw = kwargs.get(\"infun_kw\", {})\n self.outfun_kw = kwargs.get(\"outfun_kw\", {})\n if kwargs.get(\"randomise\", False):\n self.init_randomise(**kwargs)\n self.lag = kwargs.get(\"lag\", False)\n if self.lag:\n self.init_lag(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.add_random_pair","title":"add_random_pair(input_weight=None, output_weight=None, **kwargs)
","text":"Add random pair to mapping.
Parameters:
Name Type Description Default input_weight
Any
Weighting for the input vector. Defaults to None.
None
output_weight
Any
Weighting for the output vector. Defaults to None.
None
**kwargs
see random_pair kwargs.
{}
Source code in src/tolvera/iml.py
def add_random_pair(self, input_weight=None, output_weight=None, **kwargs):\n \"\"\"Add random pair to mapping.\n\n Args:\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n **kwargs: see random_pair kwargs.\n \"\"\"\n indata, outdata = self.random_pair(input_weight, output_weight, **kwargs)\n self.add(indata, outdata)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.init_lag","title":"init_lag(**kwargs)
","text":"Initialise lag() method with kwargs
kwargs: see init kwargs.
Source code in src/tolvera/iml.py
def init_lag(self, **kwargs):\n \"\"\"Initialise lag() method with kwargs\n\n kwargs: see __init__ kwargs.\n \"\"\"\n self.lag_coef = kwargs.get(\"lag_coef\", 0.5)\n self.lag = Lag(coef=self.lag_coef)\n print(\n f\"[tolvera._iml.IMLBase] Lagging mapped data with coef {self.lag_coef}.\"\n )\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.init_randomise","title":"init_randomise(**kwargs)
","text":"Initialise randomise() method with kwargs
kwargs: see init kwargs.
Source code in src/tolvera/iml.py
def init_randomise(self, **kwargs):\n \"\"\"Initialise randomise() method with kwargs\n\n kwargs: see __init__ kwargs.\n \"\"\"\n self.rand_pairs = kwargs.get(\"rand_pairs\", 32)\n self.rand_input_weight = kwargs.get(\"rand_input_weight\", None)\n self.rand_output_weight = kwargs.get(\"rand_output_weight\", None)\n self.rand_method = kwargs.get(\"rand_method\", \"rand\")\n self.rand_kw = kwargs.get(\"rand_kw\", {})\n self.randomise(\n self.rand_pairs,\n self.rand_input_weight,\n self.rand_output_weight,\n self.rand_method,\n **self.rand_kw,\n )\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.lag_mapped_data","title":"lag_mapped_data(lag_coef=0.5)
","text":"Lag mapped data.
Parameters:
Name Type Description Default lag_coef
float
Lag coefficient. Defaults to 0.5.
0.5
Source code in src/tolvera/iml.py
def lag_mapped_data(self, lag_coef: float = 0.5):\n \"\"\"Lag mapped data.\n\n Args:\n lag_coef (float, optional): Lag coefficient. Defaults to 0.5.\n \"\"\"\n self.data.mapped = self.lag(self.data.mapped, lag_coef)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.random_input","title":"random_input(**kwargs)
","text":"Random input vector.
Parameters:
Name Type Description Default **kwargs
self.rand kwargs.
{}
Returns:
Type Description Tensor
torch.Tensor: Random input vector.
Source code in src/tolvera/iml.py
def random_input(self, **kwargs) -> torch.Tensor:\n \"\"\"Random input vector.\n\n Args:\n **kwargs: self.rand kwargs.\n\n Returns:\n torch.Tensor: Random input vector.\n \"\"\"\n return self.rand(self.size[0], **kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.random_output","title":"random_output(**kwargs)
","text":"Random output vector.
Parameters:
Name Type Description Default **kwargs
self.rand kwargs
{}
Returns:
Type Description Tensor
torch.Tensor: Random output vector.
Source code in src/tolvera/iml.py
def random_output(self, **kwargs) -> torch.Tensor:\n \"\"\"Random output vector.\n\n Args:\n **kwargs: self.rand kwargs\n\n Returns:\n torch.Tensor: Random output vector.\n \"\"\"\n return self.rand(self.size[1], **kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.random_pair","title":"random_pair(input_weight=None, output_weight=None, **kwargs)
","text":"Create random pair.
Parameters:
Name Type Description Default input_weight
Any
Weighting for the input vector. Defaults to None.
None
output_weight
Any
Weighting for the output vector. Defaults to None.
None
**kwargs
rand_method (str, optional): Randomisation method. Defaults to \"rand\". rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).
{}
Raises:
Type Description ValueError
Invalid input_weight type.
ValueError
Invalid output_weight type.
Returns:
Name Type Description tuple
(input, output) vectors.
Source code in src/tolvera/iml.py
def random_pair(self, input_weight=None, output_weight=None, **kwargs):\n \"\"\"Create random pair.\n\n Args:\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n **kwargs:\n rand_method (str, optional): Randomisation method. Defaults to \"rand\".\n rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).\n\n Raises:\n ValueError: Invalid input_weight type.\n ValueError: Invalid output_weight type.\n\n Returns:\n tuple: (input, output) vectors.\n \"\"\"\n if self.rand == None and \"rand_method\" not in kwargs:\n print(f\"[tolvera._iml.IMLBase] No 'rand' method set. Using 'rand'.\")\n self.set_random_method()\n elif \"rand_method\" in kwargs:\n self.set_random_method(kwargs[\"rand_method\"])\n if input_weight is None:\n input_weight = self.rand_input_weight\n if output_weight is None:\n output_weight = self.rand_output_weight\n indata = self.rand(self.size[0], **kwargs)\n outdata = self.rand(self.size[1], **kwargs)\n if input_weight is not None:\n if isinstance(input_weight, np.ndarray):\n indata *= torch.from_numpy(input_weight)\n elif isinstance(input_weight, (torch.Tensor, float, int)):\n indata *= input_weight\n elif isinstance(input_weight, list):\n indata *= torch.Tensor(input_weight)\n else:\n raise ValueError(\n f\"[tolvera._iml.IMLBase] Invalid input_weight type '{type(input_weight)}'.\"\n )\n if output_weight is not None:\n if isinstance(output_weight, np.ndarray):\n outdata *= torch.from_numpy(output_weight)\n elif isinstance(output_weight, (torch.Tensor, float, int)):\n outdata *= output_weight\n elif isinstance(output_weight, list):\n outdata *= torch.Tensor(output_weight)\n else:\n raise ValueError(\n f\"[tolvera._iml.IMLBase] Invalid output_weight type '{type(output_weight)}'.\"\n )\n return indata, outdata\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.randomise","title":"randomise(times, input_weight=None, output_weight=None, method='rand', **kwargs)
","text":"Randomise mapping.
Parameters:
Name Type Description Default times
int
Number of random pairs to add.
required input_weight
Any
Weighting for the input vector. Defaults to None.
None
output_weight
Any
Weighting for the output vector. Defaults to None.
None
method
str
Randomisation method. Defaults to \"rand\".
'rand'
Source code in src/tolvera/iml.py
def randomise(\n self,\n times: int,\n input_weight=None,\n output_weight=None,\n method: str = \"rand\",\n **kwargs,\n):\n \"\"\"Randomise mapping.\n\n Args:\n times (int): Number of random pairs to add.\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n method (str, optional): Randomisation method. Defaults to \"rand\".\n \"\"\"\n self.rand = rand_select(method)\n while len(self.pairs) < times:\n self.add_random_pair(input_weight, output_weight, **kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.remove_newest","title":"remove_newest(n=1)
","text":"Remove newest pair(s) from mapping.
Parameters:
Name Type Description Default n
int
Number of pairs to remove. Defaults to 1.
1
Source code in src/tolvera/iml.py
def remove_newest(self, n: int = 1):\n \"\"\"Remove newest pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(max(self.pairs.keys())) for _ in range(n)]\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.remove_oldest","title":"remove_oldest(n=1)
","text":"Remove oldest pair(s) from mapping.
Parameters:
Name Type Description Default n
int
Number of pairs to remove. Defaults to 1.
1
Source code in src/tolvera/iml.py
def remove_oldest(self, n: int = 1):\n \"\"\"Remove oldest pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(min(self.pairs.keys())) for _ in range(n)]\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.remove_random","title":"remove_random(n=1)
","text":"Remove random pair(s) from mapping.
Parameters:
Name Type Description Default n
int
Number of pairs to remove. Defaults to 1.
1
Source code in src/tolvera/iml.py
def remove_random(self, n: int = 1):\n \"\"\"Remove random pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(np.random.choice(list(self.pairs.keys()))) for _ in range(n)]\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.set_random_method","title":"set_random_method(method='rand')
","text":"Set random method.
Parameters:
Name Type Description Default method
str
Randomisation method. Defaults to \"rand\".
'rand'
Source code in src/tolvera/iml.py
def set_random_method(self, method: str = \"rand\"):\n \"\"\"Set random method.\n\n Args:\n method (str, optional): Randomisation method. Defaults to \"rand\".\n \"\"\"\n self.rand = rand_select(method)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.update","title":"update(invec)
","text":"Update mapped data.
Parameters:
Name Type Description Default invec
list | Tensor | ndarray
Input vector.
required Returns:
Type Description list | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code in src/tolvera/iml.py
def update(self, invec: list|torch.Tensor|np.ndarray) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Args:\n invec (list|torch.Tensor|np.ndarray): Input vector.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.pairs) == 0:\n return None\n self.data.mapped = self.map(invec, **self.map_kw)\n if hasattr(self, \"lag\") and type(self.lag) is Lag:\n self.lag_mapped_data()\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.update_rate","title":"update_rate(rate=None)
","text":"Update rate getter/setter.
Parameters:
Name Type Description Default rate
int
Update rate. Defaults to None.
None
Returns:
Name Type Description int
Update rate.
Source code in src/tolvera/iml.py
def update_rate(self, rate: int = None):\n \"\"\"Update rate getter/setter.\n\n Args:\n rate (int, optional): Update rate. Defaults to None.\n\n Returns:\n int: Update rate.\n \"\"\"\n if rate is not None:\n self.updater.count = rate\n return self.updater.count\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict","title":"IMLDict
","text":" Bases: dotdict
IML mapping dict
Similarly to StateDict
, this class inherits from dotdict
to enable instantiation via assignment.
Source code in src/tolvera/iml.py
class IMLDict(dotdict):\n \"\"\"IML mapping dict\n\n Similarly to `StateDict`, this class inherits from `dotdict` to enable instantiation\n via assignment.\n \"\"\"\n\n def __init__(self, context) -> None:\n \"\"\"Initialise IMLDict\n\n Args:\n context (TolveraContext): TolveraContext instance.\n \"\"\"\n self.ctx = context\n self.i = {} # input vectors dict\n self.o = {} # output vectors dict\n\n def set(self, name, kwargs: dict) -> Any:\n \"\"\"Set IML instance.\n\n Args:\n name (str): Name of IML instance.\n kwargs (dict): IML instance kwargs.\n\n Raises:\n ValueError: Cannot replace 'tv' IML instance.\n ValueError: Cannot replace 'i' IML instance.\n ValueError: Cannot replace 'o' IML instance.\n NotImplementedError: set() with tuple not implemented yet.\n TypeError: set() requires dict|tuple, not _type_.\n Exception: Other exceptions.\n\n Returns:\n Any: IML instance.\n \"\"\"\n try:\n if name == \"ctx\" and type(kwargs) is not dict and type(kwargs) is not tuple:\n if name in self:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] '{name}' cannot be replaced.\"\n )\n self[name] = kwargs\n elif name == \"i\" or name == \"o\":\n if type(kwargs) is not dict:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] '{name}' is a reserved dict.\"\n )\n self[name] = kwargs\n elif type(kwargs) is dict:\n if \"type\" not in kwargs:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] IMLDict requires 'type' key.\"\n )\n return self.add(name, kwargs[\"type\"], **kwargs)\n elif type(kwargs) is tuple:\n # iml_type = kwargs[0] # TODO: which index is 'iml_type'?\n # return self.add(name, iml_type, *kwargs)\n raise NotImplementedError(\n f\"[tolvera._iml.IMLDict] set() with tuple not implemented yet.\"\n )\n else:\n raise TypeError(\n f\"[tolvera._iml.IMLDict] set() requires dict|tuple, not {type(kwargs)}\"\n )\n except Exception as e:\n raise type(e)(f\"[tolvera._iml.IMLDict] {e}\") from e\n\n def __setattr__(self, __name: str, __value: Any) -> None:\n \"\"\"Set IML instance.\n\n Args:\n __name (str): Name of IML instance.\n __value (Any): IML instance kwargs.\n \"\"\"\n self.set(__name, __value)\n\n def add(self, name: str, iml_type: str, **kwargs) -> Any:\n \"\"\"Add IML instance.\n\n Args:\n name (str): Name of IML instance.\n iml_type (str): IML type.\n\n Raises:\n ValueError: Invalid IML_TYPE.\n\n Returns:\n Any: IML instance.\n \"\"\"\n # TODO: should ^ be kwargs and not **kwargs?\n match iml_type:\n case \"vec2vec\":\n ins = IMLVec2Vec(**kwargs)\n case \"vec2fun\":\n ins = IMLVec2Fun(**kwargs)\n case \"vec2osc\":\n ins = IMLVec2OSC(self.ctx.osc.map, **kwargs)\n case \"fun2vec\":\n ins = IMLFun2Vec(**kwargs)\n case \"fun2fun\":\n ins = IMLFun2Fun(**kwargs)\n case \"fun2osc\":\n ins = IMLFun2OSC(self.ctx.osc.map, **kwargs)\n case \"osc2vec\":\n ins = IMLOSC2Vec(self.ctx.osc.map, self.o, name, **kwargs)\n case \"osc2fun\":\n ins = IMLOSC2Fun(self.ctx.osc.map, **kwargs)\n case \"osc2osc\":\n ins = IMLOSC2OSC(self.ctx.osc.map, self.ctx.osc, **kwargs)\n case _:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] Invalid IML_TYPE '{iml_type}'. Valid IML_TYPES: {IML_TYPES}.\"\n )\n self[name] = ins\n self.o[name] = None\n return ins\n\n def __call__(self, name=None, *args: Any, **kwargs: Any) -> Any:\n \"\"\"Call IML instance or all IML instances.\n\n Args:\n name (str, optional): Name of IML instance to call. Defaults to None.\n\n Raises:\n ValueError: 'name' not in dict.\n\n Returns:\n Any: IML output or dict of IML outputs.\n \"\"\"\n if name is not None:\n if name in self:\n # OSC updaters are handled by tv.osc.map (OSCMap)\n # TODO: Rethink this?\n if \"OSC\" not in type(self[name]).__name__:\n return self[name](*args, **kwargs)\n else:\n raise ValueError(f\"[tolvera._iml.IMLDict] '{name}' not in dict.\")\n else:\n outvecs = {}\n for iml in self:\n if iml == \"ctx\" or iml == \"i\" or iml == \"o\":\n continue\n cls_name = type(self[iml]).__name__\n if \"Vec2OSC\" in cls_name:\n self[iml].invec = self.i[iml]\n elif \"OSC\" in cls_name:\n # Fun2OSC, OSC2Fun, OSC2OSC and OSC2Vec \n # are handled by tv.osc.map (OSCMap)\n continue\n elif \"Vec2\" in cls_name:\n # Vec2Vec, Vec2Fun\n if iml in self.i:\n invec = self.i[iml]\n outvecs[iml] = self[iml](invec, *args, **kwargs)\n else:\n # Fun2Fun, Fun2Vec\n outvecs[iml] = self[iml](*args, **kwargs)\n self.i.clear()\n self.o.update(outvecs)\n return self.o\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.__call__","title":"__call__(name=None, *args, **kwargs)
","text":"Call IML instance or all IML instances.
Parameters:
Name Type Description Default name
str
Name of IML instance to call. Defaults to None.
None
Raises:
Type Description ValueError
'name' not in dict.
Returns:
Name Type Description Any
Any
IML output or dict of IML outputs.
Source code in src/tolvera/iml.py
def __call__(self, name=None, *args: Any, **kwargs: Any) -> Any:\n \"\"\"Call IML instance or all IML instances.\n\n Args:\n name (str, optional): Name of IML instance to call. Defaults to None.\n\n Raises:\n ValueError: 'name' not in dict.\n\n Returns:\n Any: IML output or dict of IML outputs.\n \"\"\"\n if name is not None:\n if name in self:\n # OSC updaters are handled by tv.osc.map (OSCMap)\n # TODO: Rethink this?\n if \"OSC\" not in type(self[name]).__name__:\n return self[name](*args, **kwargs)\n else:\n raise ValueError(f\"[tolvera._iml.IMLDict] '{name}' not in dict.\")\n else:\n outvecs = {}\n for iml in self:\n if iml == \"ctx\" or iml == \"i\" or iml == \"o\":\n continue\n cls_name = type(self[iml]).__name__\n if \"Vec2OSC\" in cls_name:\n self[iml].invec = self.i[iml]\n elif \"OSC\" in cls_name:\n # Fun2OSC, OSC2Fun, OSC2OSC and OSC2Vec \n # are handled by tv.osc.map (OSCMap)\n continue\n elif \"Vec2\" in cls_name:\n # Vec2Vec, Vec2Fun\n if iml in self.i:\n invec = self.i[iml]\n outvecs[iml] = self[iml](invec, *args, **kwargs)\n else:\n # Fun2Fun, Fun2Vec\n outvecs[iml] = self[iml](*args, **kwargs)\n self.i.clear()\n self.o.update(outvecs)\n return self.o\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.__init__","title":"__init__(context)
","text":"Initialise IMLDict
Parameters:
Name Type Description Default context
TolveraContext
TolveraContext instance.
required Source code in src/tolvera/iml.py
def __init__(self, context) -> None:\n \"\"\"Initialise IMLDict\n\n Args:\n context (TolveraContext): TolveraContext instance.\n \"\"\"\n self.ctx = context\n self.i = {} # input vectors dict\n self.o = {} # output vectors dict\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.__setattr__","title":"__setattr__(__name, __value)
","text":"Set IML instance.
Parameters:
Name Type Description Default __name
str
Name of IML instance.
required __value
Any
IML instance kwargs.
required Source code in src/tolvera/iml.py
def __setattr__(self, __name: str, __value: Any) -> None:\n \"\"\"Set IML instance.\n\n Args:\n __name (str): Name of IML instance.\n __value (Any): IML instance kwargs.\n \"\"\"\n self.set(__name, __value)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.add","title":"add(name, iml_type, **kwargs)
","text":"Add IML instance.
Parameters:
Name Type Description Default name
str
Name of IML instance.
required iml_type
str
IML type.
required Raises:
Type Description ValueError
Invalid IML_TYPE.
Returns:
Name Type Description Any
Any
IML instance.
Source code in src/tolvera/iml.py
def add(self, name: str, iml_type: str, **kwargs) -> Any:\n \"\"\"Add IML instance.\n\n Args:\n name (str): Name of IML instance.\n iml_type (str): IML type.\n\n Raises:\n ValueError: Invalid IML_TYPE.\n\n Returns:\n Any: IML instance.\n \"\"\"\n # TODO: should ^ be kwargs and not **kwargs?\n match iml_type:\n case \"vec2vec\":\n ins = IMLVec2Vec(**kwargs)\n case \"vec2fun\":\n ins = IMLVec2Fun(**kwargs)\n case \"vec2osc\":\n ins = IMLVec2OSC(self.ctx.osc.map, **kwargs)\n case \"fun2vec\":\n ins = IMLFun2Vec(**kwargs)\n case \"fun2fun\":\n ins = IMLFun2Fun(**kwargs)\n case \"fun2osc\":\n ins = IMLFun2OSC(self.ctx.osc.map, **kwargs)\n case \"osc2vec\":\n ins = IMLOSC2Vec(self.ctx.osc.map, self.o, name, **kwargs)\n case \"osc2fun\":\n ins = IMLOSC2Fun(self.ctx.osc.map, **kwargs)\n case \"osc2osc\":\n ins = IMLOSC2OSC(self.ctx.osc.map, self.ctx.osc, **kwargs)\n case _:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] Invalid IML_TYPE '{iml_type}'. Valid IML_TYPES: {IML_TYPES}.\"\n )\n self[name] = ins\n self.o[name] = None\n return ins\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.set","title":"set(name, kwargs)
","text":"Set IML instance.
Parameters:
Name Type Description Default name
str
Name of IML instance.
required kwargs
dict
IML instance kwargs.
required Raises:
Type Description ValueError
Cannot replace 'tv' IML instance.
ValueError
Cannot replace 'i' IML instance.
ValueError
Cannot replace 'o' IML instance.
NotImplementedError
set() with tuple not implemented yet.
TypeError
set() requires dict|tuple, not type.
Exception
Other exceptions.
Returns:
Name Type Description Any
Any
IML instance.
Source code in src/tolvera/iml.py
def set(self, name, kwargs: dict) -> Any:\n \"\"\"Set IML instance.\n\n Args:\n name (str): Name of IML instance.\n kwargs (dict): IML instance kwargs.\n\n Raises:\n ValueError: Cannot replace 'tv' IML instance.\n ValueError: Cannot replace 'i' IML instance.\n ValueError: Cannot replace 'o' IML instance.\n NotImplementedError: set() with tuple not implemented yet.\n TypeError: set() requires dict|tuple, not _type_.\n Exception: Other exceptions.\n\n Returns:\n Any: IML instance.\n \"\"\"\n try:\n if name == \"ctx\" and type(kwargs) is not dict and type(kwargs) is not tuple:\n if name in self:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] '{name}' cannot be replaced.\"\n )\n self[name] = kwargs\n elif name == \"i\" or name == \"o\":\n if type(kwargs) is not dict:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] '{name}' is a reserved dict.\"\n )\n self[name] = kwargs\n elif type(kwargs) is dict:\n if \"type\" not in kwargs:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] IMLDict requires 'type' key.\"\n )\n return self.add(name, kwargs[\"type\"], **kwargs)\n elif type(kwargs) is tuple:\n # iml_type = kwargs[0] # TODO: which index is 'iml_type'?\n # return self.add(name, iml_type, *kwargs)\n raise NotImplementedError(\n f\"[tolvera._iml.IMLDict] set() with tuple not implemented yet.\"\n )\n else:\n raise TypeError(\n f\"[tolvera._iml.IMLDict] set() requires dict|tuple, not {type(kwargs)}\"\n )\n except Exception as e:\n raise type(e)(f\"[tolvera._iml.IMLDict] {e}\") from e\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Fun","title":"IMLFun2Fun
","text":" Bases: IMLBase
IML function to function mapping.
Example def infun():\n return [0,0,0,0]\n\ndef outfun(vector):\n print('outvec', vector)\n\ntv.iml.test2test = {\n 'type': 'fun2fun', \n 'size': (4, 8), \n 'io': (infun, outfun),\n}\n
Source code in src/tolvera/iml.py
class IMLFun2Fun(IMLBase):\n \"\"\"IML function to function mapping.\n\n Example:\n ```py\n def infun():\n return [0,0,0,0]\n\n def outfun(vector):\n print('outvec', vector)\n\n tv.iml.test2test = {\n 'type': 'fun2fun', \n 'size': (4, 8), \n 'io': (infun, outfun),\n }\n ```\n \"\"\"\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLFun2Fun\n\n Args:\n kwargs:\n io (tuple, required): (callable, callable) input and output functions.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Fun requires 'io=(callable, callable)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Fun 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLFun2Fun 'io[1]' not callable, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n self.outfun = kwargs[\"io\"][1]\n self.outfun_params = inspect.signature(self.outfun).parameters\n super().__init__(**kwargs)\n\n def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n mapped = self.map(invec, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Fun.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLFun2Fun
Parameters:
Name Type Description Default kwargs
io (tuple, required): (callable, callable) input and output functions. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLFun2Fun\n\n Args:\n kwargs:\n io (tuple, required): (callable, callable) input and output functions.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Fun requires 'io=(callable, callable)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Fun 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLFun2Fun 'io[1]' not callable, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n self.outfun = kwargs[\"io\"][1]\n self.outfun_params = inspect.signature(self.outfun).parameters\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Fun.update","title":"update()
","text":"Update mapped data.
Returns:
Type Description list | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code in src/tolvera/iml.py
def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n mapped = self.map(invec, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2OSC","title":"IMLFun2OSC
","text":" Bases: IMLBase
IML function to OSC mapping
Example This will send the output vector to '/out/vec'.
def infun():\n return [0,0,0,0]\n\ntv.iml.test2osc = {\n 'type': 'fun2osc', \n 'size': (4, 8), \n 'io': (infun, 'out_vec'),\n}\n
Source code in src/tolvera/iml.py
class IMLFun2OSC(IMLBase):\n \"\"\"IML function to OSC mapping\n\n Example:\n This will send the output vector to '/out/vec'.\n\n ```py\n def infun():\n return [0,0,0,0]\n\n tv.iml.test2osc = {\n 'type': 'fun2osc', \n 'size': (4, 8), \n 'io': (infun, 'out_vec'),\n }\n ```\n \"\"\"\n def __init__(self, osc_map: OSCMap, **kwargs) -> None:\n \"\"\"Initialise IMLFun2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (callable, str) input function and output OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Vec requires 'io=(callable, str)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Vec 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert (\n isinstance(kwargs[\"io\"][1], str)\n ), f\"IMLFun2Vec 'io[1]' not str, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n self.osc_map = osc_map\n self.out_osc_route = kwargs[\"io\"][1]\n self.osc_map.send_list_inline(self.out_osc_route, self.update, kwargs[\"size\"][1], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"send\"][self.out_osc_route]['updater']\n super().__init__(**kwargs)\n\n def update(self) -> list[float]:\n \"\"\"Update mapped data.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n self.data.mapped = self.map(invec, **self.map_kw)\n return self.data.mapped.tolist()\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2OSC.__init__","title":"__init__(osc_map, **kwargs)
","text":"Initialise IMLFun2OSC
Parameters:
Name Type Description Default osc_map
(OSCMap, required)
OSCMap instance.
required kwargs
io (tuple, required): (callable, str) input function and output OSC route. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map: OSCMap, **kwargs) -> None:\n \"\"\"Initialise IMLFun2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (callable, str) input function and output OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Vec requires 'io=(callable, str)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Vec 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert (\n isinstance(kwargs[\"io\"][1], str)\n ), f\"IMLFun2Vec 'io[1]' not str, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n self.osc_map = osc_map\n self.out_osc_route = kwargs[\"io\"][1]\n self.osc_map.send_list_inline(self.out_osc_route, self.update, kwargs[\"size\"][1], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"send\"][self.out_osc_route]['updater']\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2OSC.update","title":"update()
","text":"Update mapped data.
Returns:
Type Description list[float]
list[float]: Mapped data.
Source code in src/tolvera/iml.py
def update(self) -> list[float]:\n \"\"\"Update mapped data.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n self.data.mapped = self.map(invec, **self.map_kw)\n return self.data.mapped.tolist()\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Vec","title":"IMLFun2Vec
","text":" Bases: IMLBase
IML function to vector mapping.
Output vector is accessed via tv.iml.o['name']
.
Example tv.iml.flock_p2vec = {\n 'type': 'fun2vec', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (tv.s.flock_p.to_vec, None),\n}\n# ...\nflock_s_outvec = tv.iml.o['flock_p2flock_s']\n
Source code in src/tolvera/iml.py
class IMLFun2Vec(IMLBase):\n \"\"\"IML function to vector mapping.\n\n Output vector is accessed via `tv.iml.o['name']`.\n\n Example:\n ```py\n tv.iml.flock_p2vec = {\n 'type': 'fun2vec', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (tv.s.flock_p.to_vec, None),\n }\n # ...\n flock_s_outvec = tv.iml.o['flock_p2flock_s']\n ```\n \"\"\"\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLFun2Vec\n\n Args:\n kwargs:\n io (tuple, required): (callable, None) input function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Vec requires 'io=(callable, None)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Vec 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert (\n kwargs[\"io\"][1] is None\n ), f\"IMLFun2Vec 'io[1]' not None, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n super().__init__(**kwargs)\n\n def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n self.data.mapped = self.map(invec, **self.map_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Vec.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLFun2Vec
Parameters:
Name Type Description Default kwargs
io (tuple, required): (callable, None) input function. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLFun2Vec\n\n Args:\n kwargs:\n io (tuple, required): (callable, None) input function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Vec requires 'io=(callable, None)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Vec 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert (\n kwargs[\"io\"][1] is None\n ), f\"IMLFun2Vec 'io[1]' not None, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Vec.update","title":"update()
","text":"Update mapped data.
Returns:
Type Description list | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code in src/tolvera/iml.py
def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n self.data.mapped = self.map(invec, **self.map_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Fun","title":"IMLOSC2Fun
","text":" Bases: IMLBase
IML OSC to function mapping
Example def outfun(vector):\n print('outvec', vector)\n\ntv.iml.test2fun = {\n 'type': 'osc2fun', \n 'size': (4, 8), \n 'io': ('in_vec', outfun),\n}\n
Source code in src/tolvera/iml.py
class IMLOSC2Fun(IMLBase):\n \"\"\"IML OSC to function mapping\n\n Example:\n ```py\n def outfun(vector):\n print('outvec', vector)\n\n tv.iml.test2fun = {\n 'type': 'osc2fun', \n 'size': (4, 8), \n 'io': ('in_vec', outfun),\n }\n ```\n \"\"\"\n def __init__(self, osc_map, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2Fun\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (str, callable) input OSC route and output function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2Fun requires 'io=(str, callable)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2Fun 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLOSC2Fun 'io[1]' is not callable, got {type(kwargs['io'][1])}.\"\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.outfun = kwargs[\"io\"][1]\n self.outfun_params = inspect.signature(self.outfun).parameters\n super().__init__(**kwargs)\n\n def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n mapped = self.map(vector, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Fun.__init__","title":"__init__(osc_map, **kwargs)
","text":"Initialise IMLOSC2Fun
Parameters:
Name Type Description Default osc_map
(OSCMap, required)
OSCMap instance.
required kwargs
io (tuple, required): (str, callable) input OSC route and output function. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2Fun\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (str, callable) input OSC route and output function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2Fun requires 'io=(str, callable)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2Fun 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLOSC2Fun 'io[1]' is not callable, got {type(kwargs['io'][1])}.\"\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.outfun = kwargs[\"io\"][1]\n self.outfun_params = inspect.signature(self.outfun).parameters\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Fun.update","title":"update(vector)
","text":"Update mapped data.
Parameters:
Name Type Description Default vector
list[float]
Input vector.
required Returns:
Type Description list[float]
list[float]: Mapped data.
Source code in src/tolvera/iml.py
def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n mapped = self.map(vector, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2OSC","title":"IMLOSC2OSC
","text":" Bases: IMLBase
IML OSC to OSC mapping
Example '/in/vec' is mapped and the output sent to '/out/vec'.
tv.iml.test2fun = {\n 'type': 'osc2osc', \n 'size': (4, 8), \n 'io': ('in_vec', 'out_vec'),\n}\n
Source code in src/tolvera/iml.py
class IMLOSC2OSC(IMLBase):\n \"\"\"IML OSC to OSC mapping\n\n Example:\n '/in/vec' is mapped and the output sent to '/out/vec'.\n\n ```py\n tv.iml.test2fun = {\n 'type': 'osc2osc', \n 'size': (4, 8), \n 'io': ('in_vec', 'out_vec'),\n }\n ```\n \"\"\"\n def __init__(self, osc_map: OSCMap, osc: iiOSC, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n osc (OSC): iipyper OSC instance.\n kwargs:\n io (tuple, required): (str, str) input and output OSC routes.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2OSC requires 'io=(str, str)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2OSC 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert (\n type(kwargs[\"io\"][1]) is str\n ), f\"IMLOSC2OSC 'io[1]' is not str, got {type(kwargs['io'][1])}.\"\n self.osc = osc\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.out_osc_route = kwargs[\"io\"][1]\n super().__init__(**kwargs)\n\n def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n self.data.mapped = self.map(vector, **self.map_kw)\n self.osc.host.send(self.out_osc_route, *self.data.mapped.tolist())\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2OSC.__init__","title":"__init__(osc_map, osc, **kwargs)
","text":"Initialise IMLOSC2OSC
Parameters:
Name Type Description Default osc_map
(OSCMap, required)
OSCMap instance.
required osc
OSC
iipyper OSC instance.
required kwargs
io (tuple, required): (str, str) input and output OSC routes. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map: OSCMap, osc: iiOSC, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n osc (OSC): iipyper OSC instance.\n kwargs:\n io (tuple, required): (str, str) input and output OSC routes.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2OSC requires 'io=(str, str)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2OSC 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert (\n type(kwargs[\"io\"][1]) is str\n ), f\"IMLOSC2OSC 'io[1]' is not str, got {type(kwargs['io'][1])}.\"\n self.osc = osc\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.out_osc_route = kwargs[\"io\"][1]\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2OSC.update","title":"update(vector)
","text":"Update mapped data.
Parameters:
Name Type Description Default vector
list[float]
Input vector.
required Returns:
Type Description list[float]
list[float]: Mapped data.
Source code in src/tolvera/iml.py
def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n self.data.mapped = self.map(vector, **self.map_kw)\n self.osc.host.send(self.out_osc_route, *self.data.mapped.tolist())\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Vec","title":"IMLOSC2Vec
","text":" Bases: IMLBase
IML OSC to vector mapping
Example This will map the OSC input to the output vector and store it in tv.iml.o['name']
.
tv.iml.test2vec = {\n 'type': 'osc2vec', \n 'size': (4, 8), \n 'io': ('in_vec', None),\n}\n# ...\nflock_s_outvec = tv.iml.o['flock_p2flock_s']\n
Source code in src/tolvera/iml.py
class IMLOSC2Vec(IMLBase):\n \"\"\"IML OSC to vector mapping\n\n Example:\n This will map the OSC input to the output vector and store it in `tv.iml.o['name']`.\n\n ```py\n tv.iml.test2vec = {\n 'type': 'osc2vec', \n 'size': (4, 8), \n 'io': ('in_vec', None),\n }\n # ...\n flock_s_outvec = tv.iml.o['flock_p2flock_s']\n ```\n \"\"\"\n def __init__(self, osc_map, outvecs: dict, name: str, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2Vec\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n outvecs (dict): Output vectors dict.\n name (str): Name of output vector.\n kwargs:\n io (tuple, required): (str, None) input OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2Vec requires 'io=(str, None)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2Vec 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert (\n kwargs[\"io\"][1] is None\n ), f\"IMLOSC2Vec 'io[1]' is not None, got {type(kwargs['io'][1])}.\"\n self.name = kwargs.get(\"name\", None)\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.outvecs = outvecs\n self.name = name\n super().__init__(**kwargs)\n\n def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n self.data.mapped = self.map(vector, **self.map_kw)\n if self.name is not None:\n self.outvecs[self.name] = self.data.mapped\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Vec.__init__","title":"__init__(osc_map, outvecs, name, **kwargs)
","text":"Initialise IMLOSC2Vec
Parameters:
Name Type Description Default osc_map
(OSCMap, required)
OSCMap instance.
required outvecs
dict
Output vectors dict.
required name
str
Name of output vector.
required kwargs
io (tuple, required): (str, None) input OSC route. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map, outvecs: dict, name: str, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2Vec\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n outvecs (dict): Output vectors dict.\n name (str): Name of output vector.\n kwargs:\n io (tuple, required): (str, None) input OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2Vec requires 'io=(str, None)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2Vec 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert (\n kwargs[\"io\"][1] is None\n ), f\"IMLOSC2Vec 'io[1]' is not None, got {type(kwargs['io'][1])}.\"\n self.name = kwargs.get(\"name\", None)\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.outvecs = outvecs\n self.name = name\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Vec.update","title":"update(vector)
","text":"Update mapped data.
Parameters:
Name Type Description Default vector
list[float]
Input vector.
required Returns:
Type Description list[float]
list[float]: Mapped data.
Source code in src/tolvera/iml.py
def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n self.data.mapped = self.map(vector, **self.map_kw)\n if self.name is not None:\n self.outvecs[self.name] = self.data.mapped\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Fun","title":"IMLVec2Fun
","text":" Bases: IMLBase
IML vector to function mapping
Example def update(outvec):\n print('outvec', outvec)\n\ntv.iml.flock_p2fun = {\n 'type': 'vec2fun', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (None, update),\n}\n
Source code in src/tolvera/iml.py
class IMLVec2Fun(IMLBase):\n \"\"\"IML vector to function mapping\n\n Example:\n ```py\n def update(outvec):\n print('outvec', outvec)\n\n tv.iml.flock_p2fun = {\n 'type': 'vec2fun', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (None, update),\n }\n ```\n \"\"\"\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLVec2Fun\n\n Args:\n kwargs:\n io (tuple, required): (None, callable) output function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLVec2Fun requires 'io=(None, callable)' kwarg.\"\n assert (\n kwargs[\"io\"][0] is None\n ), f\"IMLVec2Fun 'io[0]' not None, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLVec2Fun 'io[1]' not callable, got {type(kwargs['io'][1])}.\"\n self.outfun = kwargs[\"io\"][1]\n super().__init__(**kwargs)\n\n def update(self, invec: list|torch.Tensor|np.ndarray) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Args:\n invec (list | torch.Tensor | np.ndarray): Input vector.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n mapped = self.map(invec, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Fun.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLVec2Fun
Parameters:
Name Type Description Default kwargs
io (tuple, required): (None, callable) output function. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLVec2Fun\n\n Args:\n kwargs:\n io (tuple, required): (None, callable) output function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLVec2Fun requires 'io=(None, callable)' kwarg.\"\n assert (\n kwargs[\"io\"][0] is None\n ), f\"IMLVec2Fun 'io[0]' not None, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLVec2Fun 'io[1]' not callable, got {type(kwargs['io'][1])}.\"\n self.outfun = kwargs[\"io\"][1]\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Fun.update","title":"update(invec)
","text":"Update mapped data.
Parameters:
Name Type Description Default invec
list | Tensor | ndarray
Input vector.
required Returns:
Type Description list | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code in src/tolvera/iml.py
def update(self, invec: list|torch.Tensor|np.ndarray) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Args:\n invec (list | torch.Tensor | np.ndarray): Input vector.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n mapped = self.map(invec, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2OSC","title":"IMLVec2OSC
","text":" Bases: IMLBase
IML vector to OSC mapping.
Example Sends the output vector to '/tolvera/flock'.
tv.iml.flock_p2osc = {\n 'type': 'vec2osc', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (None, 'tolvera_flock'),\n}\n
Source code in src/tolvera/iml.py
class IMLVec2OSC(IMLBase):\n \"\"\"IML vector to OSC mapping.\n\n Example:\n Sends the output vector to '/tolvera/flock'.\n\n ```py\n tv.iml.flock_p2osc = {\n 'type': 'vec2osc', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (None, 'tolvera_flock'),\n }\n ```\n \"\"\"\n def __init__(self, osc_map: OSCMap, **kwargs) -> None:\n \"\"\"Initialise IMLVec2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (None, str) output OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLVec2OSC requires 'io=(None, str)' kwarg.\"\n assert (\n kwargs[\"io\"][0] is None\n ), f\"IMLVec2OSC 'io[0]' is not None, got {type(kwargs['io'][0])}.\"\n assert (\n type(kwargs[\"io\"][1]) is str\n ), f\"IMLVec2OSC 'io[1]' is not str, got {type(kwargs['io'][1])}.\"\n self.osc_map = osc_map\n self.out_osc_route = kwargs[\"io\"][1]\n self.osc_map.send_list_inline(self.out_osc_route, self.update, kwargs[\"size\"][1], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"send\"][self.out_osc_route]['updater']\n super().__init__(**kwargs)\n\n def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.pairs) == 0:\n return None\n if self.invec is not None:\n self.data.mapped = self.map(self.invec, **self.map_kw)\n if hasattr(self, \"lag\") and type(self.lag) is Lag:\n self.lag_mapped_data()\n return self.data.mapped.tolist()\n else:\n return None\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2OSC.__init__","title":"__init__(osc_map, **kwargs)
","text":"Initialise IMLVec2OSC
Parameters:
Name Type Description Default osc_map
(OSCMap, required)
OSCMap instance.
required kwargs
io (tuple, required): (None, str) output OSC route. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map: OSCMap, **kwargs) -> None:\n \"\"\"Initialise IMLVec2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (None, str) output OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLVec2OSC requires 'io=(None, str)' kwarg.\"\n assert (\n kwargs[\"io\"][0] is None\n ), f\"IMLVec2OSC 'io[0]' is not None, got {type(kwargs['io'][0])}.\"\n assert (\n type(kwargs[\"io\"][1]) is str\n ), f\"IMLVec2OSC 'io[1]' is not str, got {type(kwargs['io'][1])}.\"\n self.osc_map = osc_map\n self.out_osc_route = kwargs[\"io\"][1]\n self.osc_map.send_list_inline(self.out_osc_route, self.update, kwargs[\"size\"][1], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"send\"][self.out_osc_route]['updater']\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2OSC.update","title":"update()
","text":"Update mapped data.
Returns:
Type Description list | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code in src/tolvera/iml.py
def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.pairs) == 0:\n return None\n if self.invec is not None:\n self.data.mapped = self.map(self.invec, **self.map_kw)\n if hasattr(self, \"lag\") and type(self.lag) is Lag:\n self.lag_mapped_data()\n return self.data.mapped.tolist()\n else:\n return None\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Vec","title":"IMLVec2Vec
","text":" Bases: IMLBase
IML vector to vector mapping.
Input vector is accessed via tv.iml.i['name']
. Output vector is accessed via tv.iml.o['name']
.
Example tv.iml.flock_p2flock_s = {\n 'type': 'vec2vec', \n 'size': (tv.s.flock_p.size, tv.s.flock_s.size)\n}\n\ndef update():\n invec = tv.s.flock_p.to_vec()\n tv.iml.i = {'flock_p2flock_s': invec}\n flock_s_outvec = tv.iml.o['flock_p2flock_s']\n if flock_s_outvec is not None:\n tv.s.flock_s.from_vec(flock_s_outvec)\n
Parameters:
Name Type Description Default kwargs
see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
class IMLVec2Vec(IMLBase):\n \"\"\"IML vector to vector mapping.\n\n Input vector is accessed via `tv.iml.i['name']`.\n Output vector is accessed via `tv.iml.o['name']`.\n\n Example:\n ```py\n tv.iml.flock_p2flock_s = {\n 'type': 'vec2vec', \n 'size': (tv.s.flock_p.size, tv.s.flock_s.size)\n }\n\n def update():\n invec = tv.s.flock_p.to_vec()\n tv.iml.i = {'flock_p2flock_s': invec}\n flock_s_outvec = tv.iml.o['flock_p2flock_s']\n if flock_s_outvec is not None:\n tv.s.flock_s.from_vec(flock_s_outvec)\n ```\n\n Args:\n kwargs:\n see IMLBase kwargs.\n \"\"\"\n\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLVec2Vec\"\"\"\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Vec.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLVec2Vec
Source code in src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLVec2Vec\"\"\"\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.rand_select","title":"rand_select(method='rand')
","text":"Select randomisation method.
Parameters:
Name Type Description Default method
str
Randomisation method. Defaults to \"rand\".
'rand'
Raises:
Type Description ValueError
Invalid method.
Returns:
Name Type Description callable
Randomisation method.
Source code in src/tolvera/iml.py
def rand_select(method=\"rand\"):\n \"\"\"Select randomisation method.\n\n Args:\n method (str, optional): Randomisation method. Defaults to \"rand\".\n\n Raises:\n ValueError: Invalid method.\n\n Returns:\n callable: Randomisation method.\n \"\"\"\n match method:\n case \"rand\":\n return rand_n\n case \"uniform\":\n return rand_uniform\n case \"normal\":\n return rand_normal\n case \"exponential\":\n return rand_exponential\n case \"cauchy\":\n return rand_cauchy\n case \"lognormal\":\n return rand_lognormal\n case \"sigmoid\":\n return rand_sigmoid\n case \"beta\":\n return rand_beta\n case _:\n raise ValueError(\n f\"[tolvera._iml.rand_select] Invalid method '{method}'. Valid methods: {RAND_METHODS}.\"\n )\n
"},{"location":"reference/tolvera/mp/","title":"Mp","text":""},{"location":"reference/tolvera/mp/#tolvera.mp.HandLandmark","title":"HandLandmark
","text":" Bases: IntEnum
The 21 hand landmarks.
Source code in src/tolvera/mp.py
class HandLandmark(enum.IntEnum):\n \"\"\"The 21 hand landmarks.\"\"\"\n WRIST = 0\n THUMB_CMC = 1\n THUMB_MCP = 2\n THUMB_IP = 3\n THUMB_TIP = 4\n INDEX_FINGER_MCP = 5\n INDEX_FINGER_PIP = 6\n INDEX_FINGER_DIP = 7\n INDEX_FINGER_TIP = 8\n MIDDLE_FINGER_MCP = 9\n MIDDLE_FINGER_PIP = 10\n MIDDLE_FINGER_DIP = 11\n MIDDLE_FINGER_TIP = 12\n RING_FINGER_MCP = 13\n RING_FINGER_PIP = 14\n RING_FINGER_DIP = 15\n RING_FINGER_TIP = 16\n PINKY_MCP = 17\n PINKY_PIP = 18\n PINKY_DIP = 19\n PINKY_TIP = 20\n
"},{"location":"reference/tolvera/npndarray_dict/","title":"Npndarray dict","text":"Module for working with dictionary of NumPy ndarrays.
Primaril used by State.
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict","title":"NpNdarrayDict
","text":"A class that encapsulates a dictionary of NumPy ndarrays, each associated with a specific data type and a defined min-max range. It provides a structured and efficient way to manage and manipulate multidimensional arrays with constraints on their values.
Attributes:
Name Type Description data
Dict[str, Dict[str, Union[ndarray, Any]]]
A dictionary where each key represents an attribute,
shape
Tuple[int, int]
The shape of the ndarrays, which is consistent across all attributes.
Example state = NpNdarrayDict({ 'i': (np.int32, 2, 10), 'f': (np.float32, 0., 1.), 'v2': (np_vec2, 0., 1.), 'v3': (np_vec3, 0., 1.), 'v4': (np_vec4, 0., 1.), }, (2,2)) state.set_value('i', (0, 0), 5) print(state.get_value('i', (0, 0))) 5
Source code in src/tolvera/npndarray_dict.py
class NpNdarrayDict:\n \"\"\"\n A class that encapsulates a dictionary of NumPy ndarrays, each associated with a specific data type and a defined min-max range.\n It provides a structured and efficient way to manage and manipulate multidimensional arrays with constraints on their values.\n\n Attributes:\n data (Dict[str, Dict[str, Union[np.ndarray, Any]]]): A dictionary where each key represents an attribute,\n and the value is another dictionary with keys 'array', 'min', and 'max', representing the ndarray,\n its minimum value, and its maximum value, respectively.\n shape (Tuple[int, int]): The shape of the ndarrays, which is consistent across all attributes.\n\n Example:\n state = NpNdarrayDict({\n 'i': (np.int32, 2, 10),\n 'f': (np.float32, 0., 1.),\n 'v2': (np_vec2, 0., 1.),\n 'v3': (np_vec3, 0., 1.),\n 'v4': (np_vec4, 0., 1.),\n }, (2,2))\n state.set_value('i', (0, 0), 5)\n print(state.get_value('i', (0, 0)))\n 5\n \"\"\"\n\n def __init__(self, data_dict: dict[str, tuple[Any, Any, Any]], shape: tuple[int]):\n \"\"\"\n Initialize the State class.\n\n Args:\n data_dict: A dictionary where keys are attribute names and values are tuples\n of (dtype, min_value, max_value).\n shape: The shape of the numpy arrays for each attribute.\n\n \"\"\"\n self.shape = shape\n self.init(data_dict, shape)\n\n def init(\n self, data_dict: dict[str, tuple[Any, Any, Any]], shape: tuple[int]\n ) -> None:\n self.dict = {}\n self.data = {}\n self.size = 0\n for key, (dtype, min_val, max_val) in data_dict.items():\n dshape = self.shape\n length = 1\n # handle np_vec2, np_vec3, np_vec4\n if isinstance(dtype, np.ndarray):\n dshape = dshape + dtype.shape\n length = dtype.shape[0]\n dtype = np.float32\n self.dict[key] = {\n \"dtype\": dtype,\n \"min\": min_val,\n \"max\": max_val,\n \"length\": length,\n \"shape\": dshape,\n \"ndims\": len(dshape),\n }\n self.data[key] = np.zeros(dshape, dtype=dtype)\n size = self.data[key].size\n self.dict[key][\"size\"] = size\n self.size += size\n\n \"\"\"\n to|from vec | list (iml)\n \"\"\"\n\n def from_vec(self, vec: list):\n vec_start = 0\n for key in self.data.keys():\n attr_vec_size = self.dict[key][\"size\"]\n attr_vec = vec[vec_start : vec_start + attr_vec_size]\n self.attr_from_vec(key, attr_vec)\n vec_start += attr_vec_size\n\n def to_vec(self) -> list:\n vec = []\n for key in self.data.keys():\n vec += self.attr_to_vec(key).tolist()\n return vec\n\n def attr_from_vec(self, attr: str, vec: list):\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n attr_shape, attr_dtype = self.dict[attr][\"shape\"], self.dict[attr][\"dtype\"]\n if len(vec) != np.prod(attr_shape):\n raise ValueError(\n f\"Length of vec {len(vec)} does not match the shape of {attr} {attr_shape}\"\n )\n nparr = np.array(vec, dtype=attr_dtype)\n if len(attr_shape) > 1:\n nparr = np.reshape(nparr, attr_shape)\n try:\n self.data[attr] = nparr\n except ValueError as e:\n print(f\"ValueError occurred while setting {attr}: {e}\")\n raise\n\n def attr_to_vec(self, attr: str) -> list:\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n vec = self.data[attr].flatten()\n return vec\n\n def slice_from_vec(\n self, slice_args: Union[int, tuple[int, ...], slice], slice_vec: list\n ):\n # TODO: unique slice obj needed per key...\n # slice_obj = create_safe_slice(slice_args)\n raise NotImplementedError(f\"slice_from_vec()\")\n\n def slice_to_vec(self, slice_args: Union[int, tuple[int, ...], slice]) -> list:\n # TODO: unique slice obj needed per key...\n # vec = []\n # for key in self.data.keys():\n # slice_obj = create_safe_slice(slice_args)\n # vec += self.attr_slice_to_vec(key, slice_obj)\n # return vec\n raise NotImplementedError(f\"slice_from_vec()\")\n\n def attr_slice_from_vec(\n self, attr: str, slice_args: Union[int, tuple[int, ...], slice], slice_vec: list\n ):\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n slice_obj = create_safe_slice(slice_args)\n attr_shape, attr_dtype = self.dict[attr][\"shape\"], self.dict[attr][\"dtype\"]\n nparr = np.array(slice_vec, dtype=attr_dtype)\n if len(attr_shape) > 1:\n nparr = np.reshape(nparr, attr_shape)\n try:\n self.data[attr][slice_obj] = nparr\n except ValueError as e:\n print(f\"ValueError occurred while setting slice: {e}\")\n raise\n\n def attr_slice_to_vec(\n self, attr: str, slice_args: Union[int, tuple[int, ...], slice]\n ) -> list:\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n slice_obj = create_safe_slice(slice_args)\n vec = self.data[attr][slice_obj].flatten()\n return vec\n\n \"\"\"\n vec slice helpers\n \"\"\"\n\n def get_slice_size(self, slice_args: Union[int, tuple[int, ...], slice]) -> int:\n slice_obj = create_safe_slice(slice_args)\n return np.sum([self.data[key][slice_obj].size for key in self.data.keys()])\n\n def get_attr_slice_size(\n self, attr: str, slice_args: Union[int, tuple[int, ...], slice]\n ) -> int:\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n slice_obj = create_safe_slice(slice_args)\n return self.data[attr][slice_obj].size\n\n \"\"\"\n to|from vec_args (simple osc)\n \"\"\"\n\n \"\"\"\n to|from ndarray | ndarraydict (serialised formats, complex osc)\n \"\"\"\n\n \"\"\"\n ...\n \"\"\"\n\n def set_slice_from_dict(self, slice_indices: tuple, slice_values: dict):\n for key, values in slice_values.items():\n if key not in self.data:\n raise KeyError(f\"Key {key} not found in data\")\n\n array_slice = self.data[key][slice_indices]\n if array_slice.shape != np.array(values).shape:\n raise ValueError(\n f\"Shape {array_slice.shape} of values for key {key} does not match the shape of the slice {np.array(values).shape}\"\n )\n\n self.data[key][slice_indices] = np.array(\n values, dtype=self.dict[key][\"dtype\"]\n )\n\n # def list_to_dict(self, _list: list) -> dict:\n # \"\"\"\n # Convert a flat list to a dictionary.\n\n # :param _list: The flat list to convert.\n # :return: A dictionary that matches self.dict.\n # \"\"\"\n # pass\n\n # def list_len_to_dict_shape(self, _list: list) -> dict:\n # \"\"\"\n # Convert a flat list to a dictionary of shapes.\n\n # :param _list: The flat list to convert.\n # :return: shape of the dictionary of _list based on self.shape.\n # \"\"\"\n # list_len = len(_list)\n # dict_shape = ()\n # for key in self.data.keys():\n # dict_shape += self.dict[key]['shape'][1:]\n # dict_len = np.prod(dict_shape)\n # if list_len != dict_len:\n # raise ValueError(f\"Length of list {_list} does not match the length of the dictionary {dict_len}\")\n # return dict_shape\n\n def set_slice_from_list(self, slice_indices: tuple, slice_values_list: list):\n list_index = 0\n\n for key in self.data.keys():\n # Determine the total number of elements required for the current key\n num_elements = np.prod(self.dict[key][\"shape\"][1:])\n print(f\"[{key}] num_elements: {num_elements}\")\n\n # Extract the slice from slice_values_list and reshape if necessary\n slice_shape = self.dict[key][\"shape\"][1:]\n slice = slice_values_list[list_index : list_index + num_elements]\n print(f\"[{key}] slice_shape: {slice_shape}, slice: {slice}\")\n\n # Check if the slice has the correct length\n if len(slice) != num_elements:\n raise ValueError(\n f\"Slice length {len(slice)} for key {key} does not match the number of elements {num_elements}\"\n )\n\n # Reshape the slice for ndarrays with more than 2 dimensions\n if len(slice_shape) > 1:\n slice = np.reshape(slice, slice_shape)\n print(f\"[{key}] (reshaping) slice_shape: {slice_shape}, slice: {slice}\")\n\n # Assign the slice to the corresponding key\n self.data[key][slice_indices] = slice\n\n list_index += num_elements\n print(f\"[{key}] list_index: {list_index}, num_elements: {num_elements}\")\n\n print(f\"data: {self.data}\")\n\n # Check if there are extra values in slice_values_list\n if list_index != len(slice_values_list):\n raise ValueError(\n f\"Extra values {slice_values_list[list_index:]} in slice_values_list {slice_values_list} that do not correspond to any array\"\n )\n\n def set_data(self, new_data: dict[str, np.ndarray]) -> None:\n \"\"\"\n Set the data with a new data dictionary.\n\n Args:\n new_data: A dictionary representing the new data, where each key is an\n attribute and the value is a numpy array.\n\n Raises:\n ValueError: If the new data is invalid (e.g., wrong shape, type, or value range).\n \"\"\"\n try:\n self.data = new_data\n except ValueError as e:\n print(f\"ValueError occurred while setting data: {e}\")\n raise\n\n def get_data(self) -> dict[str, np.ndarray]:\n \"\"\"\n Get the entire current data as a dictionary.\n\n Returns:\n A dictionary where each key is an attribute and the value is a numpy array.\n \"\"\"\n return self.data\n\n def validate(self, new_state: dict[str, np.ndarray]) -> bool:\n raise NotImplementedError(\"validate() not implemented\")\n\n def randomise(self) -> None:\n \"\"\"\n Randomize the entire state dictionary based on the datatype, minimum,\n and maximum values for each attribute.\n \"\"\"\n for key in self.data:\n data_type = self.dict[key][\"dtype\"]\n min_val = self.dict[key][\"min\"]\n max_val = self.dict[key][\"max\"]\n shape = self.dict[key][\"shape\"]\n\n if np.issubdtype(data_type, np.integer):\n self.data[key] = np.random.randint(\n min_val, max_val + 1, size=shape, dtype=data_type\n )\n elif np.issubdtype(data_type, np.floating):\n self.data[key] = np.random.uniform(min_val, max_val, size=shape).astype(\n data_type\n )\n # Add more conditions here if you have other data types\n\n def attr_apply(self, key: str, func: Callable[[np.ndarray], np.ndarray]) -> None:\n \"\"\"\n Apply a user-defined function to the array of a specified key.\n\n Args:\n key: The attribute key.\n func: A function that takes a numpy array and returns a numpy array.\n\n Raises:\n KeyError: If the key is not found.\n \"\"\"\n if key not in self.data:\n raise KeyError(f\"Key {key} not found\")\n\n self.data[key] = func(self.data[key])\n\n def attr_broadcast(\n self,\n key: str,\n other: Union[np.ndarray, \"NpNdarrayDict\"],\n op: Callable[[np.ndarray, np.ndarray], np.ndarray],\n ) -> None:\n \"\"\"\n Perform a broadcasting operation between the array of the specified key and another array or NpNdarrayDict.\n\n Args:\n key: The key of the array in the dictionary to operate on.\n other: The other array or NpNdarrayDict to use in the operation.\n op: A function to perform the operation. This should be a NumPy ufunc (like np.add, np.multiply).\n\n Raises:\n KeyError: If the key is not found in the dictionary.\n ValueError: If the operation cannot be broadcasted or if it violates the min-max constraints.\n \"\"\"\n if key not in self.data:\n raise KeyError(f\"Key {key} not found\")\n\n if isinstance(other, NpNdarrayDict):\n if other.shape != self.shape:\n raise ValueError(\"Shapes of NpNdarrayDict objects do not match\")\n other_array = other.data[key] # Assuming we want to operate on the same key\n elif isinstance(other, np.ndarray):\n other_array = other\n else:\n raise ValueError(\n \"The 'other' parameter must be either a NumPy ndarray or NpNdarrayDict\"\n )\n\n result = op(self.data[key], other_array)\n\n # Check if the result is within the allowed min-max range\n if np.any(result < self.dict[key][\"min\"]) or np.any(\n result > self.dict[key][\"max\"]\n ):\n raise ValueError(\"Operation result violates min-max constraints\")\n\n self.data[key] = result\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.__init__","title":"__init__(data_dict, shape)
","text":"Initialize the State class.
Parameters:
Name Type Description Default data_dict
dict[str, tuple[Any, Any, Any]]
A dictionary where keys are attribute names and values are tuples of (dtype, min_value, max_value).
required shape
tuple[int]
The shape of the numpy arrays for each attribute.
required Source code in src/tolvera/npndarray_dict.py
def __init__(self, data_dict: dict[str, tuple[Any, Any, Any]], shape: tuple[int]):\n \"\"\"\n Initialize the State class.\n\n Args:\n data_dict: A dictionary where keys are attribute names and values are tuples\n of (dtype, min_value, max_value).\n shape: The shape of the numpy arrays for each attribute.\n\n \"\"\"\n self.shape = shape\n self.init(data_dict, shape)\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.attr_apply","title":"attr_apply(key, func)
","text":"Apply a user-defined function to the array of a specified key.
Parameters:
Name Type Description Default key
str
The attribute key.
required func
Callable[[ndarray], ndarray]
A function that takes a numpy array and returns a numpy array.
required Raises:
Type Description KeyError
If the key is not found.
Source code in src/tolvera/npndarray_dict.py
def attr_apply(self, key: str, func: Callable[[np.ndarray], np.ndarray]) -> None:\n \"\"\"\n Apply a user-defined function to the array of a specified key.\n\n Args:\n key: The attribute key.\n func: A function that takes a numpy array and returns a numpy array.\n\n Raises:\n KeyError: If the key is not found.\n \"\"\"\n if key not in self.data:\n raise KeyError(f\"Key {key} not found\")\n\n self.data[key] = func(self.data[key])\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.attr_broadcast","title":"attr_broadcast(key, other, op)
","text":"Perform a broadcasting operation between the array of the specified key and another array or NpNdarrayDict.
Parameters:
Name Type Description Default key
str
The key of the array in the dictionary to operate on.
required other
Union[ndarray, NpNdarrayDict]
The other array or NpNdarrayDict to use in the operation.
required op
Callable[[ndarray, ndarray], ndarray]
A function to perform the operation. This should be a NumPy ufunc (like np.add, np.multiply).
required Raises:
Type Description KeyError
If the key is not found in the dictionary.
ValueError
If the operation cannot be broadcasted or if it violates the min-max constraints.
Source code in src/tolvera/npndarray_dict.py
def attr_broadcast(\n self,\n key: str,\n other: Union[np.ndarray, \"NpNdarrayDict\"],\n op: Callable[[np.ndarray, np.ndarray], np.ndarray],\n) -> None:\n \"\"\"\n Perform a broadcasting operation between the array of the specified key and another array or NpNdarrayDict.\n\n Args:\n key: The key of the array in the dictionary to operate on.\n other: The other array or NpNdarrayDict to use in the operation.\n op: A function to perform the operation. This should be a NumPy ufunc (like np.add, np.multiply).\n\n Raises:\n KeyError: If the key is not found in the dictionary.\n ValueError: If the operation cannot be broadcasted or if it violates the min-max constraints.\n \"\"\"\n if key not in self.data:\n raise KeyError(f\"Key {key} not found\")\n\n if isinstance(other, NpNdarrayDict):\n if other.shape != self.shape:\n raise ValueError(\"Shapes of NpNdarrayDict objects do not match\")\n other_array = other.data[key] # Assuming we want to operate on the same key\n elif isinstance(other, np.ndarray):\n other_array = other\n else:\n raise ValueError(\n \"The 'other' parameter must be either a NumPy ndarray or NpNdarrayDict\"\n )\n\n result = op(self.data[key], other_array)\n\n # Check if the result is within the allowed min-max range\n if np.any(result < self.dict[key][\"min\"]) or np.any(\n result > self.dict[key][\"max\"]\n ):\n raise ValueError(\"Operation result violates min-max constraints\")\n\n self.data[key] = result\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.get_data","title":"get_data()
","text":"Get the entire current data as a dictionary.
Returns:
Type Description dict[str, ndarray]
A dictionary where each key is an attribute and the value is a numpy array.
Source code in src/tolvera/npndarray_dict.py
def get_data(self) -> dict[str, np.ndarray]:\n \"\"\"\n Get the entire current data as a dictionary.\n\n Returns:\n A dictionary where each key is an attribute and the value is a numpy array.\n \"\"\"\n return self.data\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.randomise","title":"randomise()
","text":"Randomize the entire state dictionary based on the datatype, minimum, and maximum values for each attribute.
Source code in src/tolvera/npndarray_dict.py
def randomise(self) -> None:\n \"\"\"\n Randomize the entire state dictionary based on the datatype, minimum,\n and maximum values for each attribute.\n \"\"\"\n for key in self.data:\n data_type = self.dict[key][\"dtype\"]\n min_val = self.dict[key][\"min\"]\n max_val = self.dict[key][\"max\"]\n shape = self.dict[key][\"shape\"]\n\n if np.issubdtype(data_type, np.integer):\n self.data[key] = np.random.randint(\n min_val, max_val + 1, size=shape, dtype=data_type\n )\n elif np.issubdtype(data_type, np.floating):\n self.data[key] = np.random.uniform(min_val, max_val, size=shape).astype(\n data_type\n )\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.set_data","title":"set_data(new_data)
","text":"Set the data with a new data dictionary.
Parameters:
Name Type Description Default new_data
dict[str, ndarray]
A dictionary representing the new data, where each key is an attribute and the value is a numpy array.
required Raises:
Type Description ValueError
If the new data is invalid (e.g., wrong shape, type, or value range).
Source code in src/tolvera/npndarray_dict.py
def set_data(self, new_data: dict[str, np.ndarray]) -> None:\n \"\"\"\n Set the data with a new data dictionary.\n\n Args:\n new_data: A dictionary representing the new data, where each key is an\n attribute and the value is a numpy array.\n\n Raises:\n ValueError: If the new data is invalid (e.g., wrong shape, type, or value range).\n \"\"\"\n try:\n self.data = new_data\n except ValueError as e:\n print(f\"ValueError occurred while setting data: {e}\")\n raise\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.dict_from_vector_args","title":"dict_from_vector_args(a, scalars=None)
","text":"Convert a list of arguments to a dictionary.
Args: - a: A list of arguments. - scalars: A list of keys that should be unwrapped from lists.
Returns: - A dictionary of keyword arguments.
Source code in src/tolvera/npndarray_dict.py
def dict_from_vector_args(a: list, scalars=None):\n \"\"\"Convert a list of arguments to a dictionary.\n\n Args:\n - a: A list of arguments.\n - scalars: A list of keys that should be unwrapped from lists.\n\n Returns:\n - A dictionary of keyword arguments.\n \"\"\"\n a = list(a)\n kw = defaultdict(list)\n k = None\n while len(a):\n item = a.pop(0)\n if isinstance(item, str):\n k = item\n else:\n if k is None:\n print(f\"ERROR: bad syntax in {a}\")\n kw[k].append(item)\n # unwrap scalars\n for item in scalars or []:\n if item in kw:\n kw[item] = kw[item][0]\n return kw\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.dict_to_vector_args","title":"dict_to_vector_args(kw)
","text":"Convert a dictionary to a list of arguments.
This function takes a dictionary and returns a list of arguments.
Args: - kw: A dictionary of keyword arguments.
Returns: - A list of arguments.
Source code in src/tolvera/npndarray_dict.py
def dict_to_vector_args(kw):\n \"\"\"Convert a dictionary to a list of arguments.\n\n This function takes a dictionary and returns a list of arguments.\n\n Args:\n - kw: A dictionary of keyword arguments.\n\n Returns:\n - A list of arguments.\n \"\"\"\n args = []\n for key, value in kw.items():\n args.append(key)\n if isinstance(value, (list, np.ndarray)):\n # If it's a numpy array (regardless of its shape), flatten it and extend the list\n if isinstance(value, np.ndarray):\n value = value.flatten()\n args.extend(value)\n else:\n # Append the scalar value associated with the key\n args.append(value)\n return args\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.ndarraydict_from_vector_args","title":"ndarraydict_from_vector_args(lst, shapes)
","text":"Convert a list to a dictionary where each list is turned into a numpy array.
This function takes a list in the format output by dict_from_vector_args
and converts it into a dictionary. Each key's list of values is converted into a numpy array with a specified shape.
Args: - lst: The list to be converted. - shapes: A dictionary where keys correspond to the keys in the original list and values are tuples representing the desired shape of the numpy array.
Returns: - A dictionary with keys mapped to numpy arrays.
Source code in src/tolvera/npndarray_dict.py
def ndarraydict_from_vector_args(lst, shapes):\n \"\"\"Convert a list to a dictionary where each list is turned into a numpy array.\n\n This function takes a list in the format output by `dict_from_vector_args` and converts it\n into a dictionary. Each key's list of values is converted into a numpy array with a\n specified shape.\n\n Args:\n - lst: The list to be converted.\n - shapes: A dictionary where keys correspond to the keys in the original list and\n values are tuples representing the desired shape of the numpy array.\n\n Returns:\n - A dictionary with keys mapped to numpy arrays.\n \"\"\"\n\n def flatten(lst):\n \"\"\"Flatten a nested list or return a non-nested list as is.\"\"\"\n if all(isinstance(el, list) for el in lst):\n # Flatten only if all elements are lists\n return [item for sublist in lst for item in sublist]\n return lst\n\n kw = defaultdict(list)\n k = None\n for item in lst:\n if isinstance(item, str):\n k = item\n else:\n kw[k].append(item)\n\n for key, shape in shapes.items():\n if key in kw:\n values = flatten(kw[key])\n array_size = np.prod(shape)\n if len(values) != array_size:\n raise ValueError(\n f\"Shape mismatch for key '{key}': expected {array_size} elements, got {len(values)}.\"\n )\n kw[key] = np.array(values).reshape(shape)\n\n return dict(kw)\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.shapes_from_ndarray_dict","title":"shapes_from_ndarray_dict(ndarray_dict)
","text":"Return a dictionary of shapes given a dictionary of numpy ndarrays.
This function takes a dictionary where values are numpy ndarrays and returns a new dictionary with the same keys, where each value is the shape of the ndarray.
Args: - ndarray_dict: A dictionary where values are numpy ndarrays.
Returns: - A dictionary where each key maps to the shape of the corresponding ndarray.
Source code in src/tolvera/npndarray_dict.py
def shapes_from_ndarray_dict(ndarray_dict):\n \"\"\"Return a dictionary of shapes given a dictionary of numpy ndarrays.\n\n This function takes a dictionary where values are numpy ndarrays and returns\n a new dictionary with the same keys, where each value is the shape of the ndarray.\n\n Args:\n - ndarray_dict: A dictionary where values are numpy ndarrays.\n\n Returns:\n - A dictionary where each key maps to the shape of the corresponding ndarray.\n \"\"\"\n shapes = {}\n for key, array in ndarray_dict.items():\n shapes[key] = array.shape\n return shapes\n
"},{"location":"reference/tolvera/particles/","title":"Particles","text":"Particle system.
The Tolvera particle system consists of a Particle class and a Particles class. The Particle class is a Taichi dataclass for a single particle, and the Particles class is a Taichi data_oriented class containing a Particle field.
The Particles class also contains methods for processing the particle system, such as updating the particles, and getting and setting particle properties.
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle","title":"Particle
","text":"Particle data structure and methods.
Source code in src/tolvera/particles.py
@ti.dataclass\nclass Particle:\n \"\"\"Particle data structure and methods.\"\"\"\n species: ti.i32\n active: ti.f32\n pos: ti.math.vec2\n vel: ti.math.vec2\n mass: ti.f32\n size: ti.f32\n speed: ti.f32\n\n @ti.func\n def dist(self, other):\n \"\"\"Distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: Distance between the two particles.\n \"\"\"\n return self.pos - other.pos\n\n @ti.func\n def dist_norm(self, other):\n \"\"\"ti.math.norm() distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: ti.math.norm() distance between the two particles.\n \"\"\"\n return self.dist(self.pos - other.pos).norm()\n\n @ti.func\n def dist_normalized(self, other):\n \"\"\"ti.math.normalized() distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: ti.math.normalized() distance between the two particles.\n \"\"\"\n return self.dist(self.pos - other.pos).normalized()\n\n @ti.func\n def dist_wrap(self, other, x, y):\n \"\"\"Wrap around distance between two particles.\n\n Args:\n other (Particle): Other particle.\n x (float): Width.\n y (float): Height.\n\n Returns:\n ti.math.vec2: Wrap around distance between the two particles.\n \"\"\"\n dx = self.pos[0] - other.pos[0]\n dy = self.pos[1] - other.pos[1]\n if abs(dx) > x / 2: # x-axis\n dx = x - abs(dx)\n if self.pos[0] > other.pos[0]:\n dx = -dx\n if abs(dy) > y / 2: # y-axis\n dy = y - abs(dy)\n if self.pos[1] > other.pos[1]:\n dy = -dy\n return ti.Vector([dx, dy])\n\n # @ti.func\n # def dist_wrap(self, other, x, y):\n # dx = self.pos[0] - other.pos[0]\n # dy = self.pos[1] - other.pos[1]\n # # Wrap around for the x-axis\n # if abs(dx) > x / 2:\n # dx = x - abs(dx)\n # if self.pos[0] < other.pos[0]:\n # dx = -dx\n # # Wrap around for the y-axis\n # if abs(dy) > y / 2:\n # dy = y - abs(dy)\n # if self.pos[1] < other.pos[1]:\n # dy = -dy\n # return ti.Vector([dx, dy])\n # @ti.func\n # def dist_wrap(self, other, width, height):\n # # Compute the element-wise absolute difference\n # self_abs = ti.abs(self.pos)\n # other_abs = ti.abs(other.pos)\n # delta = self_abs - other_abs\n # # Check if wrapping around is shorter for both the x and y components\n # if delta[0] > width / 2:\n # delta[0] = width - delta[0]\n # if delta[1] > height / 2:\n # delta[1] = height - delta[1]\n # # Correct the signs if necessary\n # if self.pos[0] > other.pos[0] and delta[0] > 0:\n # delta[0] = -delta[0]\n # if self.pos[1] > other.pos[1] and delta[1] > 0:\n # delta[1] = -delta[1]\n # return delta\n @ti.func\n def randomise(self, x, y):\n \"\"\"Randomise the particle's position and velocity.\n\n Args:\n x (ti.f32): Width.\n y (ti.f32): Height.\n \"\"\"\n self.randomise_pos(x, y)\n self.randomise_vel()\n\n @ti.func\n def randomise_pos(self, x, y):\n \"\"\"Randomise the particle's position.\n\n Args:\n x (ti.f32): Width.\n y (ti.f32): Height.\n \"\"\"\n self.pos = [x * ti.random(ti.f32), y * ti.random(ti.f32)]\n\n @ti.func\n def randomise_vel(self):\n \"\"\"Randomise the particle's velocity.\"\"\"\n self.vel = [2 * (ti.random(ti.f32) - 0.5), 2 * (ti.random(ti.f32) - 0.5)]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.dist","title":"dist(other)
","text":"Distance between two particles.
Parameters:
Name Type Description Default other
Particle
Other particle.
required Returns:
Type Description ti.math.vec2: Distance between the two particles.
Source code in src/tolvera/particles.py
@ti.func\ndef dist(self, other):\n \"\"\"Distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: Distance between the two particles.\n \"\"\"\n return self.pos - other.pos\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.dist_norm","title":"dist_norm(other)
","text":"ti.math.norm() distance between two particles.
Parameters:
Name Type Description Default other
Particle
Other particle.
required Returns:
Type Description ti.math.vec2: ti.math.norm() distance between the two particles.
Source code in src/tolvera/particles.py
@ti.func\ndef dist_norm(self, other):\n \"\"\"ti.math.norm() distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: ti.math.norm() distance between the two particles.\n \"\"\"\n return self.dist(self.pos - other.pos).norm()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.dist_normalized","title":"dist_normalized(other)
","text":"ti.math.normalized() distance between two particles.
Parameters:
Name Type Description Default other
Particle
Other particle.
required Returns:
Type Description ti.math.vec2: ti.math.normalized() distance between the two particles.
Source code in src/tolvera/particles.py
@ti.func\ndef dist_normalized(self, other):\n \"\"\"ti.math.normalized() distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: ti.math.normalized() distance between the two particles.\n \"\"\"\n return self.dist(self.pos - other.pos).normalized()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.dist_wrap","title":"dist_wrap(other, x, y)
","text":"Wrap around distance between two particles.
Parameters:
Name Type Description Default other
Particle
Other particle.
required x
float
Width.
required y
float
Height.
required Returns:
Type Description ti.math.vec2: Wrap around distance between the two particles.
Source code in src/tolvera/particles.py
@ti.func\ndef dist_wrap(self, other, x, y):\n \"\"\"Wrap around distance between two particles.\n\n Args:\n other (Particle): Other particle.\n x (float): Width.\n y (float): Height.\n\n Returns:\n ti.math.vec2: Wrap around distance between the two particles.\n \"\"\"\n dx = self.pos[0] - other.pos[0]\n dy = self.pos[1] - other.pos[1]\n if abs(dx) > x / 2: # x-axis\n dx = x - abs(dx)\n if self.pos[0] > other.pos[0]:\n dx = -dx\n if abs(dy) > y / 2: # y-axis\n dy = y - abs(dy)\n if self.pos[1] > other.pos[1]:\n dy = -dy\n return ti.Vector([dx, dy])\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.randomise","title":"randomise(x, y)
","text":"Randomise the particle's position and velocity.
Parameters:
Name Type Description Default x
f32
Width.
required y
f32
Height.
required Source code in src/tolvera/particles.py
@ti.func\ndef randomise(self, x, y):\n \"\"\"Randomise the particle's position and velocity.\n\n Args:\n x (ti.f32): Width.\n y (ti.f32): Height.\n \"\"\"\n self.randomise_pos(x, y)\n self.randomise_vel()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.randomise_pos","title":"randomise_pos(x, y)
","text":"Randomise the particle's position.
Parameters:
Name Type Description Default x
f32
Width.
required y
f32
Height.
required Source code in src/tolvera/particles.py
@ti.func\ndef randomise_pos(self, x, y):\n \"\"\"Randomise the particle's position.\n\n Args:\n x (ti.f32): Width.\n y (ti.f32): Height.\n \"\"\"\n self.pos = [x * ti.random(ti.f32), y * ti.random(ti.f32)]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.randomise_vel","title":"randomise_vel()
","text":"Randomise the particle's velocity.
Source code in src/tolvera/particles.py
@ti.func\ndef randomise_vel(self):\n \"\"\"Randomise the particle's velocity.\"\"\"\n self.vel = [2 * (ti.random(ti.f32) - 0.5), 2 * (ti.random(ti.f32) - 0.5)]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles","title":"Particles
","text":"Particle system.
Source code in src/tolvera/particles.py
@ti.data_oriented\nclass Particles:\n \"\"\"Particle system.\"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the particle system.\n\n Args:\n tolvera (Tolvera): Tolvera instance.\n **kwargs: Keyword arguments (currently there are none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.n = self.tv.pn\n self.p_per_s = self.tv.p_per_s\n self._speed = ti.field(ti.f32, shape=())\n self._speed[None] = 1.0\n self.substep = self.tv.substep\n self.field = Particle.field(shape=(self.n))\n # TODO: These should be possible with State\n # self.pos = State(self.tv, {\n # 'x': (0., self.tv.x),\n # 'y': (0., self.tv.y),\n # }, shape=(self.n,), osc=('get'), name='particles_pos')\n self.tmp_pos = ti.Vector.field(2, ti.f32, shape=(self.n))\n self.tmp_vel = ti.Vector.field(2, ti.f32, shape=(self.n))\n self.tmp_pos_species = ti.Vector.field(2, ti.f32, shape=(self.p_per_s))\n self.tmp_vel_species = ti.Vector.field(2, ti.f32, shape=(self.p_per_s))\n self.tmp_vel_stats = ti.Vector.field(1, ti.f32, shape=(7))\n self.active_indexes = ti.field(ti.i32, shape=(self.n))\n self.active_count = ti.field(ti.i32, shape=())\n self.init()\n\n def init(self):\n \"\"\"Initialise the particle system.\"\"\"\n self.assign_species()\n self.randomise()\n\n @ti.kernel\n def assign_species(self):\n \"\"\"Assign species to particles.\"\"\"\n for i in range(self.n):\n self.field[i].species = i % self.tv.species\n\n def _randomise(self):\n \"\"\"Randomise the particle system (Python scope).\"\"\"\n self.randomise()\n\n @ti.kernel\n def randomise(self):\n \"\"\"Randomise the particle system (Taichi scope).\"\"\"\n for i in range(self.n):\n si = self.field[i].species\n s = self.tv.s.species[si]\n # FIXME: ugly\n # c = self.tv.species_consts\n species = si\n active = 1.0\n pos = [self.tv.x * ti.random(ti.f32), self.tv.y * ti.random(ti.f32)]\n vel = [2 * (ti.random(ti.f32) - 0.5), 2 * (ti.random(ti.f32) - 0.5)]\n size = (\n ti.random(ti.f32) * s.size * self.tv.species_consts.MAX_SIZE\n + self.tv.species_consts.MIN_SIZE\n )\n speed = (\n ti.random(ti.f32) * s.speed * self.tv.species_consts.MAX_SPEED\n + self.tv.species_consts.MIN_SPEED\n )\n mass = ti.random(ti.f32) * s.mass * self.tv.species_consts.MAX_MASS\n self.field[i] = Particle(\n species=species,\n pos=pos,\n vel=vel,\n active=active,\n mass=mass,\n size=size,\n speed=speed,\n )\n\n @ti.kernel\n def update(self):\n \"\"\"Update the particle system.\"\"\"\n # TODO: collisions\n for i in range(self.n):\n if self.field[i] == 0.0:\n continue\n self.toroidal_wrap(i)\n self.limit_speed(i)\n\n @ti.kernel\n def update_active(self):\n \"\"\"Update the active particles.\"\"\"\n j = 0\n for i in range(self.n):\n p = self.field[i]\n if p.active > 0.0:\n self.active_indexes[j] = i\n j += 1\n self.active_count[None] = j\n\n @ti.func\n def toroidal_wrap(self, i: ti.i32):\n \"\"\"Toroidal wrap a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n p = self.field[i]\n if p.pos[0] > self.tv.x:\n self.field[i].pos[0] = 0.0\n if p.pos[0] < 0.0:\n self.field[i].pos[0] = self.tv.x\n if p.pos[1] > self.tv.y:\n self.field[i].pos[1] = 0.0\n if p.pos[1] < 0.0:\n self.field[i].pos[1] = self.tv.y\n\n @ti.func\n def limit_speed(self, i: ti.i32):\n \"\"\"Limit the speed of a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n p = self.field[i]\n s = self.tv.s.species[p.species]\n # FIXME: ugly\n sp = (\n s.speed * self.tv.species_consts.MAX_SPEED\n + self.tv.species_consts.MIN_SPEED\n )\n if p.vel.norm() > s.speed:\n self.field[i].vel = p.vel.normalized() * sp * self._speed[None]\n\n @ti.kernel\n def activity_decay(self):\n \"\"\"Decay the activity of the particles.\"\"\"\n for i in range(self.active_count[None]):\n idx = self.active_indexes[i]\n self.field[idx].active *= self.field[i].decay\n\n def process(self):\n \"\"\"Process the particle system.\"\"\"\n for i in range(self.substep):\n self.update_active()\n self.update()\n\n @ti.kernel\n def set_active(self, a: ti.i32):\n \"\"\"Set the active particles.\n\n Args:\n a (ti.i32): Amount of active particles.\n \"\"\"\n for i in range(self.field.shape[0]):\n if i > a:\n self.field[i].active = 0\n else:\n self.field[i].active = 1\n\n @ti.kernel\n def set_species_active(self, i: ti.i32, a: ti.i32):\n \"\"\"Set the active particles of a species.\n\n Args:\n i (ti.i32): Species index.\n a (ti.i32): Amount of active particles.\n \"\"\"\n for j in range(self.field.shape[0]):\n if self.field[j].species == i:\n if j > a:\n self.field[j].active = 0\n else:\n self.field[j].active = 1\n\n @ti.kernel\n def set_active_amount(self, a: ti.f32):\n \"\"\"Set particle activity amount.\n\n Args:\n a (ti.i32): Amount of activity.\n \"\"\"\n for i in range(self.field.shape[0]):\n self.field[i].active = a\n\n @ti.kernel\n def set_species_active_amount(self, i: ti.i32, a: ti.f32):\n \"\"\"Set particle activity amount of a species.\n\n Args:\n i (ti.i32): Species index.\n a (ti.i32): Amount of activity.\n \"\"\"\n for j in range(self.field.shape[0]):\n if self.field[j].species == i:\n self.field[j].active = a\n\n def set_pos(self, i, x, y):\n self.field[i].pos = [x, y]\n\n def set_vel(self, i, x, y):\n self.field[i].vel = [x, y]\n\n def set_speed(self, i, s):\n self.field[i].speed = s\n\n def set_size(self, i, s):\n self.field[i].size = s\n\n def get_pos(self, i):\n return self.field[i].pos.to_numpy().tolist()\n\n def get_vel(self, i):\n return self.field[i].vel.to_numpy().tolist()\n\n def get_pos_all_1d(self):\n self._get_pos_all()\n return self.tmp_pos.to_numpy().flatten().tolist()\n\n def get_pos_all_2d(self):\n self._get_pos_all()\n return self.tmp_pos.to_numpy().tolist()\n\n def get_vel_all_1d(self):\n self._get_vel_all()\n return self.tmp_vel.to_numpy().flatten().tolist()\n\n def get_vel_all_2d(self):\n self._get_vel_all()\n return self.tmp_vel.to_numpy().tolist()\n\n @ti.kernel\n def _get_pos_all(self):\n # for i in range(self.active_count[None]):\n # idx = self.active_indexes[i]\n # p = self.field[idx]\n # self.tmp_pos[i] = p.pos / [self.tv.x, self.tv.y]\n # TODO: Only send active particle positions...? Or inactive=-1?\n for i in range(self.n):\n p = self.field[i]\n # if p.active > 0.0: # causes IML shape assertion error\n self.tmp_pos[i] = p.pos / [self.tv.x, self.tv.y]\n # else:\n # self.tmp_pos[i] = [0.0,0.0] # ???\n\n @ti.kernel\n def _get_vel_all(self):\n for i in range(self.n):\n p = self.field[i]\n if p.active > 0.0:\n self.tmp_vel[i] = p.vel\n\n def get_pos_species_1d(self, species: int):\n self._get_pos_species()\n return self.tmp_pos_species.to_numpy().flatten().tolist()\n\n def get_pos_species_2d(self, species: int):\n if species > self.tv.species - 1:\n return\n self._get_pos_species(species)\n return self.tmp_pos_species.to_numpy().tolist()\n\n @ti.kernel\n def _get_pos_species(self, i: ti.i32):\n for j in range(self.n):\n si = j % self.tv.species\n p = self.field[j]\n if i == si and p.active > 0.0:\n species_index = (j - i) // self.tv.species\n pos = p.pos / [self.tv.x, self.tv.y]\n self.tmp_pos_species[species_index] = pos\n\n def get_vel_species_1d(self, species: int):\n self._get_vel_species(species)\n return self.tmp_vel_species.to_numpy().flatten().tolist()\n\n def get_vel_species_2d(self, species: int):\n self._get_vel_species(species)\n return self.tmp_vel_species.to_numpy().tolist()\n\n @ti.kernel\n def _get_vel_species(self, i: ti.i32):\n for j in range(self.n):\n si = j % self.tv.species\n p = self.field[j]\n if i == si and p.active > 0.0:\n species_index = (j - i) // self.tv.species\n vel = p.vel / [self.tv.x, self.tv.y]\n self.tmp_vel_species[species_index] = vel\n\n def get_vel_stats_species_1d(self, species):\n self._species_velocity_statistics(species)\n return self.tmp_vel_stats.to_numpy().flatten().tolist()\n\n @ti.kernel\n def _species_velocity_statistics(self, i: ti.i32):\n \"\"\"\n Centre of Mass Velocity: This is the average velocity of all particles in the species.\n Relative Velocity: This is the average velocity of all particles in the species relative to the centre of mass velocity.\n Angular Momentum: This is the sum of the angular momentum of all particles, which is given by mass * velocity * radius for each particle.\n Kinetic Energy: This is the sum of the kinetic energy of all particles, which is given by 0.5 * mass * velocity^2 for each particle.\n Temperature: In statistical mechanics, the temperature of a system of particles is related to the average kinetic energy of the particles.\n \"\"\"\n centre_of_mass_velocity = ti.Vector([0.0, 0.0])\n relative_velocity = ti.Vector([0.0, 0.0])\n angular_momentum = ti.Vector([0.0])\n kinetic_energy = ti.Vector([0.0])\n for j in range(self.n):\n if self.field[j].species == i:\n v = self.field[j].vel\n p = self.field[j].pos\n m = self.field[j].mass\n centre_of_mass_velocity += v\n relative_velocity += v # - centre_of_mass_velocity\n angular_momentum += m * ti.math.cross(v, p)\n kinetic_energy += 0.5 * m * v.norm_sqr()\n centre_of_mass_velocity = centre_of_mass_velocity / self.n_per_species\n relative_velocity = (\n relative_velocity - centre_of_mass_velocity * self.n_per_species\n ) / self.n_per_species\n temperature = 2.0 * kinetic_energy / (self.particles_per_species * 1.380649e-23)\n self.tmp_vel_stats[0] = centre_of_mass_velocity[0]\n self.tmp_vel_stats[1] = centre_of_mass_velocity[1]\n self.tmp_vel_stats[2] = relative_velocity[0]\n self.tmp_vel_stats[3] = relative_velocity[1]\n self.tmp_vel_stats[4] = angular_momentum[0]\n self.tmp_vel_stats[5] = kinetic_energy[0]\n self.tmp_vel_stats[6] = temperature[0]\n\n def reset(self):\n \"\"\"Reset the particle system.\"\"\"\n self.init()\n\n def speed(self, speed: float = None):\n \"\"\"Get or set the speed of the particle system.\n\n Args:\n speed (float, optional): Speed. Defaults to None.\n\n Returns:\n float: Speed.\n \"\"\"\n if speed is not None:\n self._speed[None] = 1 / (speed + 0.0001)\n else:\n return self._speed[None]\n\n def __call__(self):\n \"\"\"Call will process the particle system.\"\"\"\n self.process()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.__call__","title":"__call__()
","text":"Call will process the particle system.
Source code in src/tolvera/particles.py
def __call__(self):\n \"\"\"Call will process the particle system.\"\"\"\n self.process()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the particle system.
Parameters:
Name Type Description Default tolvera
Tolvera
Tolvera instance.
required **kwargs
Keyword arguments (currently there are none).
{}
Source code in src/tolvera/particles.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the particle system.\n\n Args:\n tolvera (Tolvera): Tolvera instance.\n **kwargs: Keyword arguments (currently there are none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.n = self.tv.pn\n self.p_per_s = self.tv.p_per_s\n self._speed = ti.field(ti.f32, shape=())\n self._speed[None] = 1.0\n self.substep = self.tv.substep\n self.field = Particle.field(shape=(self.n))\n # TODO: These should be possible with State\n # self.pos = State(self.tv, {\n # 'x': (0., self.tv.x),\n # 'y': (0., self.tv.y),\n # }, shape=(self.n,), osc=('get'), name='particles_pos')\n self.tmp_pos = ti.Vector.field(2, ti.f32, shape=(self.n))\n self.tmp_vel = ti.Vector.field(2, ti.f32, shape=(self.n))\n self.tmp_pos_species = ti.Vector.field(2, ti.f32, shape=(self.p_per_s))\n self.tmp_vel_species = ti.Vector.field(2, ti.f32, shape=(self.p_per_s))\n self.tmp_vel_stats = ti.Vector.field(1, ti.f32, shape=(7))\n self.active_indexes = ti.field(ti.i32, shape=(self.n))\n self.active_count = ti.field(ti.i32, shape=())\n self.init()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.activity_decay","title":"activity_decay()
","text":"Decay the activity of the particles.
Source code in src/tolvera/particles.py
@ti.kernel\ndef activity_decay(self):\n \"\"\"Decay the activity of the particles.\"\"\"\n for i in range(self.active_count[None]):\n idx = self.active_indexes[i]\n self.field[idx].active *= self.field[i].decay\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.assign_species","title":"assign_species()
","text":"Assign species to particles.
Source code in src/tolvera/particles.py
@ti.kernel\ndef assign_species(self):\n \"\"\"Assign species to particles.\"\"\"\n for i in range(self.n):\n self.field[i].species = i % self.tv.species\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.init","title":"init()
","text":"Initialise the particle system.
Source code in src/tolvera/particles.py
def init(self):\n \"\"\"Initialise the particle system.\"\"\"\n self.assign_species()\n self.randomise()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.limit_speed","title":"limit_speed(i)
","text":"Limit the speed of a particle.
Parameters:
Name Type Description Default i
i32
Particle index.
required Source code in src/tolvera/particles.py
@ti.func\ndef limit_speed(self, i: ti.i32):\n \"\"\"Limit the speed of a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n p = self.field[i]\n s = self.tv.s.species[p.species]\n # FIXME: ugly\n sp = (\n s.speed * self.tv.species_consts.MAX_SPEED\n + self.tv.species_consts.MIN_SPEED\n )\n if p.vel.norm() > s.speed:\n self.field[i].vel = p.vel.normalized() * sp * self._speed[None]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.process","title":"process()
","text":"Process the particle system.
Source code in src/tolvera/particles.py
def process(self):\n \"\"\"Process the particle system.\"\"\"\n for i in range(self.substep):\n self.update_active()\n self.update()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.randomise","title":"randomise()
","text":"Randomise the particle system (Taichi scope).
Source code in src/tolvera/particles.py
@ti.kernel\ndef randomise(self):\n \"\"\"Randomise the particle system (Taichi scope).\"\"\"\n for i in range(self.n):\n si = self.field[i].species\n s = self.tv.s.species[si]\n # FIXME: ugly\n # c = self.tv.species_consts\n species = si\n active = 1.0\n pos = [self.tv.x * ti.random(ti.f32), self.tv.y * ti.random(ti.f32)]\n vel = [2 * (ti.random(ti.f32) - 0.5), 2 * (ti.random(ti.f32) - 0.5)]\n size = (\n ti.random(ti.f32) * s.size * self.tv.species_consts.MAX_SIZE\n + self.tv.species_consts.MIN_SIZE\n )\n speed = (\n ti.random(ti.f32) * s.speed * self.tv.species_consts.MAX_SPEED\n + self.tv.species_consts.MIN_SPEED\n )\n mass = ti.random(ti.f32) * s.mass * self.tv.species_consts.MAX_MASS\n self.field[i] = Particle(\n species=species,\n pos=pos,\n vel=vel,\n active=active,\n mass=mass,\n size=size,\n speed=speed,\n )\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.reset","title":"reset()
","text":"Reset the particle system.
Source code in src/tolvera/particles.py
def reset(self):\n \"\"\"Reset the particle system.\"\"\"\n self.init()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_active","title":"set_active(a)
","text":"Set the active particles.
Parameters:
Name Type Description Default a
i32
Amount of active particles.
required Source code in src/tolvera/particles.py
@ti.kernel\ndef set_active(self, a: ti.i32):\n \"\"\"Set the active particles.\n\n Args:\n a (ti.i32): Amount of active particles.\n \"\"\"\n for i in range(self.field.shape[0]):\n if i > a:\n self.field[i].active = 0\n else:\n self.field[i].active = 1\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_active_amount","title":"set_active_amount(a)
","text":"Set particle activity amount.
Parameters:
Name Type Description Default a
i32
Amount of activity.
required Source code in src/tolvera/particles.py
@ti.kernel\ndef set_active_amount(self, a: ti.f32):\n \"\"\"Set particle activity amount.\n\n Args:\n a (ti.i32): Amount of activity.\n \"\"\"\n for i in range(self.field.shape[0]):\n self.field[i].active = a\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_species_active","title":"set_species_active(i, a)
","text":"Set the active particles of a species.
Parameters:
Name Type Description Default i
i32
Species index.
required a
i32
Amount of active particles.
required Source code in src/tolvera/particles.py
@ti.kernel\ndef set_species_active(self, i: ti.i32, a: ti.i32):\n \"\"\"Set the active particles of a species.\n\n Args:\n i (ti.i32): Species index.\n a (ti.i32): Amount of active particles.\n \"\"\"\n for j in range(self.field.shape[0]):\n if self.field[j].species == i:\n if j > a:\n self.field[j].active = 0\n else:\n self.field[j].active = 1\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_species_active_amount","title":"set_species_active_amount(i, a)
","text":"Set particle activity amount of a species.
Parameters:
Name Type Description Default i
i32
Species index.
required a
i32
Amount of activity.
required Source code in src/tolvera/particles.py
@ti.kernel\ndef set_species_active_amount(self, i: ti.i32, a: ti.f32):\n \"\"\"Set particle activity amount of a species.\n\n Args:\n i (ti.i32): Species index.\n a (ti.i32): Amount of activity.\n \"\"\"\n for j in range(self.field.shape[0]):\n if self.field[j].species == i:\n self.field[j].active = a\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.speed","title":"speed(speed=None)
","text":"Get or set the speed of the particle system.
Parameters:
Name Type Description Default speed
float
Speed. Defaults to None.
None
Returns:
Name Type Description float
Speed.
Source code in src/tolvera/particles.py
def speed(self, speed: float = None):\n \"\"\"Get or set the speed of the particle system.\n\n Args:\n speed (float, optional): Speed. Defaults to None.\n\n Returns:\n float: Speed.\n \"\"\"\n if speed is not None:\n self._speed[None] = 1 / (speed + 0.0001)\n else:\n return self._speed[None]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.toroidal_wrap","title":"toroidal_wrap(i)
","text":"Toroidal wrap a particle.
Parameters:
Name Type Description Default i
i32
Particle index.
required Source code in src/tolvera/particles.py
@ti.func\ndef toroidal_wrap(self, i: ti.i32):\n \"\"\"Toroidal wrap a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n p = self.field[i]\n if p.pos[0] > self.tv.x:\n self.field[i].pos[0] = 0.0\n if p.pos[0] < 0.0:\n self.field[i].pos[0] = self.tv.x\n if p.pos[1] > self.tv.y:\n self.field[i].pos[1] = 0.0\n if p.pos[1] < 0.0:\n self.field[i].pos[1] = self.tv.y\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.update","title":"update()
","text":"Update the particle system.
Source code in src/tolvera/particles.py
@ti.kernel\ndef update(self):\n \"\"\"Update the particle system.\"\"\"\n # TODO: collisions\n for i in range(self.n):\n if self.field[i] == 0.0:\n continue\n self.toroidal_wrap(i)\n self.limit_speed(i)\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.update_active","title":"update_active()
","text":"Update the active particles.
Source code in src/tolvera/particles.py
@ti.kernel\ndef update_active(self):\n \"\"\"Update the active particles.\"\"\"\n j = 0\n for i in range(self.n):\n p = self.field[i]\n if p.active > 0.0:\n self.active_indexes[j] = i\n j += 1\n self.active_count[None] = j\n
"},{"location":"reference/tolvera/patches/","title":"Patches","text":"Patches for third-party libraries.
Current patches: - 'dill.source.findsource fails when in asyncio REPL' https://github.com/uqfoundation/dill/issues/627
"},{"location":"reference/tolvera/patches/#tolvera.patches.findsource","title":"findsource(object)
","text":"Return the entire source file and starting line number for an object. For interactively-defined objects, the 'file' is the interpreter's history.
The argument may be a module, class, method, function, traceback, frame, or code object. The source code is returned as a list of all the lines in the file and the line number indexes a line in that list. An IOError is raised if the source code cannot be retrieved, while a TypeError is raised for objects where the source code is unavailable (e.g. builtins).
Source code in src/tolvera/patches.py
def findsource(object):\n # print(f\"[dill.source.findsource] PATCHED\")\n\n \"\"\"Return the entire source file and starting line number for an object.\n For interactively-defined objects, the 'file' is the interpreter's history.\n\n The argument may be a module, class, method, function, traceback, frame,\n or code object. The source code is returned as a list of all the lines\n in the file and the line number indexes a line in that list. An IOError\n is raised if the source code cannot be retrieved, while a TypeError is\n raised for objects where the source code is unavailable (e.g. builtins).\"\"\"\n\n def patched_getfile(module):\n # set file = None when module.__package__ == 'asyncio'\n # print(f\"[dill.source.patched_getfile] module={module}\\nmodule.__package__={module.__package__}\\nmodule.__name__={module.__name__}\")\n if module.__package__ == \"asyncio\":\n raise TypeError\n # if module.__package__ == 'sardine':\n # raise TypeError\n ret = getfile(module)\n return ret\n\n module = getmodule(object)\n # try: file = getfile(module)\n try:\n file = patched_getfile(module)\n except TypeError:\n file = None\n # correctly compute `is_module_main` when in asyncio\n is_module_main = module and module.__name__ == \"__main__\" and not file\n # is_module_main = (module and module.__name__ == '__main__' or module.__name__ == 'sardine' and not file)\n print(\n f\"[dill.source.findsource] module: {module}, file: {file}, is_module_main: {is_module_main}\"\n )\n if IS_IPYTHON and is_module_main:\n # FIXME: quick fix for functions and classes in IPython interpreter\n try:\n file = getfile(object)\n sourcefile = getsourcefile(object)\n except TypeError:\n if isclass(object):\n for object_method in filter(isfunction, object.__dict__.values()):\n # look for a method of the class\n file_candidate = getfile(object_method)\n if not file_candidate.startswith(\"<ipython-input-\"):\n continue\n file = file_candidate\n sourcefile = getsourcefile(object_method)\n break\n if file:\n lines = linecache.getlines(file)\n else:\n # fallback to use history\n history = \"\\n\".join(get_ipython().history_manager.input_hist_parsed)\n lines = [line + \"\\n\" for line in history.splitlines()]\n # use readline when working in interpreter (i.e. __main__ and not file)\n elif is_module_main:\n try:\n import readline\n\n err = \"\"\n except ImportError:\n import sys\n\n err = sys.exc_info()[1].args[0]\n if sys.platform[:3] == \"win\":\n err += \", please install 'pyreadline'\"\n if err:\n raise IOError(err)\n lbuf = readline.get_current_history_length()\n lines = [readline.get_history_item(i) + \"\\n\" for i in range(1, lbuf)]\n else:\n try: # special handling for class instances\n if not isclass(object) and isclass(type(object)): # __class__\n file = getfile(module)\n sourcefile = getsourcefile(module)\n else: # builtins fail with a TypeError\n file = getfile(object)\n sourcefile = getsourcefile(object)\n except (TypeError, AttributeError): # fail with better error\n file = getfile(object)\n sourcefile = getsourcefile(object)\n if not sourcefile and file[:1] + file[-1:] != \"<>\":\n raise IOError(\"source code not available\")\n file = sourcefile if sourcefile else file\n\n module = getmodule(object, file)\n if module:\n lines = linecache.getlines(file, module.__dict__)\n else:\n lines = linecache.getlines(file)\n\n if not lines:\n raise IOError(\"could not extract source code\")\n\n # FIXME: all below may fail if exec used (i.e. exec('f = lambda x:x') )\n if ismodule(object):\n return lines, 0\n\n # NOTE: beneficial if search goes from end to start of buffer history\n name = pat1 = obj = \"\"\n pat2 = r\"^(\\s*@)\"\n # pat1b = r'^(\\s*%s\\W*=)' % name #FIXME: finds 'f = decorate(f)', not exec\n if ismethod(object):\n name = object.__name__\n if name == \"<lambda>\":\n pat1 = r\"(.*(?<!\\w)lambda(:|\\s))\"\n else:\n pat1 = r\"^(\\s*def\\s)\"\n object = object.__func__\n if isfunction(object):\n name = object.__name__\n if name == \"<lambda>\":\n pat1 = r\"(.*(?<!\\w)lambda(:|\\s))\"\n obj = object # XXX: better a copy?\n else:\n pat1 = r\"^(\\s*def\\s)\"\n object = object.__code__\n if istraceback(object):\n object = object.tb_frame\n if isframe(object):\n object = object.f_code\n if iscode(object):\n if not hasattr(object, \"co_firstlineno\"):\n raise IOError(\"could not find function definition\")\n # stdin = object.co_filename == '<stdin>'\n stdin = object.co_filename in (\"<console>\", \"<stdin>\")\n # print(f\"[dill.source.findsource] object.co_filename: {object.co_filename}, stdin: {stdin}\")\n if stdin:\n lnum = len(lines) - 1 # can't get lnum easily, so leverage pat\n if not pat1:\n pat1 = r\"^(\\s*def\\s)|(.*(?<!\\w)lambda(:|\\s))|^(\\s*@)\"\n else:\n lnum = object.co_firstlineno - 1\n pat1 = r\"^(\\s*def\\s)|(.*(?<!\\w)lambda(:|\\s))|^(\\s*@)\"\n pat1 = re.compile(pat1)\n pat2 = re.compile(pat2)\n # XXX: candidate_lnum = [n for n in range(lnum) if pat1.match(lines[n])]\n while lnum > 0: # XXX: won't find decorators in <stdin> ?\n line = lines[lnum]\n if pat1.match(line):\n if not stdin:\n break # co_firstlineno does the job\n if name == \"<lambda>\": # hackery needed to confirm a match\n if _matchlambda(obj, line):\n break\n else: # not a lambda, just look for the name\n if name in line: # need to check for decorator...\n hats = 0\n for _lnum in range(lnum - 1, -1, -1):\n if pat2.match(lines[_lnum]):\n hats += 1\n else:\n break\n lnum = lnum - hats\n break\n lnum = lnum - 1\n return lines, lnum\n\n try: # turn instances into classes\n if not isclass(object) and isclass(type(object)): # __class__\n object = object.__class__ # XXX: sometimes type(class) is better?\n # XXX: we don't find how the instance was built\n except AttributeError:\n pass\n if isclass(object):\n name = object.__name__\n pat = re.compile(r\"^(\\s*)class\\s*\" + name + r\"\\b\")\n # make some effort to find the best matching class definition:\n # use the one with the least indentation, which is the one\n # that's most probably not inside a function definition.\n candidates = []\n for i in range(len(lines) - 1, -1, -1):\n match = pat.match(lines[i])\n if match:\n # if it's at toplevel, it's already the best one\n if lines[i][0] == \"c\":\n return lines, i\n # else add whitespace to candidate list\n candidates.append((match.group(1), i))\n if candidates:\n # this will sort by whitespace, and by line number,\n # less whitespace first #XXX: should sort high lnum before low\n candidates.sort()\n return lines, candidates[0][1]\n else:\n raise IOError(\"could not find class definition\")\n raise IOError(\"could not find code object\")\n
"},{"location":"reference/tolvera/pixels/","title":"Pixels","text":"Pixels module.
Example Draw a red rectangle in the centre of the screen.
import taichi as ti\nfrom tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n @ti.kernel\n def draw():\n w = 100\n tv.px.rect(tv.x/2-w/2, tv.y/2-w/2, w, w, ti.Vector([1., 0., 0., 1.]))\n\n @tv.render\n def _():\n tv.p()\n draw()\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels","title":"Pixels
","text":"Pixels class for drawing pixels to the screen.
This class is used to draw pixels to the screen. It contains methods for drawing points, lines, rectangles, circles, triangles, and polygons. It also contains methods for blending pixels together, flipping pixels, inverting pixels, and diffusing, decaying and clearing pixels.
It tries to follow a similar API to the Processing library.
Source code in src/tolvera/pixels.py
@ti.data_oriented\nclass Pixels:\n \"\"\"Pixels class for drawing pixels to the screen.\n\n This class is used to draw pixels to the screen. It contains methods for drawing\n points, lines, rectangles, circles, triangles, and polygons. It also contains\n methods for blending pixels together, flipping pixels, inverting pixels, and\n diffusing, decaying and clearing pixels.\n\n It tries to follow a similar API to the Processing library.\n \"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise Pixels\n\n Args:\n tolvera (Tolvera): T\u00f6lvera instance.\n **kwargs: Keyword arguments.\n polygon_mode (str): Polygon mode. Defaults to \"crossing\".\n brightness (float): Brightness. Defaults to 1.0. \n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.polygon_mode = kwargs.get(\"polygon_mode\", \"crossing\")\n self.x = self.tv.x\n self.y = self.tv.y\n self.px = Pixel.field(shape=(self.x, self.y))\n brightness = kwargs.get(\"brightness\", 1.0)\n self.CONSTS = CONSTS(\n {\n \"BRIGHTNESS\": (ti.f32, brightness),\n }\n )\n self.shape_enum = {\n \"point\": 0,\n \"line\": 1,\n \"rect\": 2,\n \"circle\": 3,\n \"triangle\": 4,\n \"polygon\": 5,\n }\n\n\n def set(self, px: Any):\n \"\"\"Set pixels.\n\n Args:\n px (Any): Pixels to set. Can be Pixels, StructField, MatrixField, etc (see rgba_from_px).\n \"\"\"\n self.px.rgba = self.rgba_from_px(px)\n\n @ti.kernel\n def k_set(self, px: ti.template()):\n for x, y in ti.ndrange(self.x, self.y):\n self.px.rgba[x, y] = px.px.rgba[x, y]\n\n @ti.kernel\n def f_set(self, px: ti.template()):\n for x, y in ti.ndrange(self.x, self.y):\n self.px.rgba[x, y] = px.px.rgba[x, y]\n\n def get(self):\n \"\"\"Get pixels.\"\"\"\n return self.px\n\n @ti.kernel\n def clear(self):\n \"\"\"Clear pixels.\"\"\"\n self.px.rgba.fill(0)\n\n @ti.kernel\n def diffuse(self, evaporate: ti.f32):\n \"\"\"Diffuse pixels.\n\n Args:\n evaporate (float): Evaporation rate.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n d = ti.Vector([0.0, 0.0, 0.0, 0.0]) \n for di in ti.static(range(-1, 2)):\n for dj in ti.static(range(-1, 2)):\n dx = (i + di) % self.x\n dy = (j + dj) % self.y\n d += self.px.rgba[dx, dy]\n d *= 0.99 / 9.0\n self.px.rgba[i, j] = d\n\n @ti.func\n def background(self, r: ti.f32, g: ti.f32, b: ti.f32):\n \"\"\"Set background colour.\n\n Args:\n r (ti.f32): Red.\n g (ti.f32): Green.\n b (ti.f32): Blue.\n \"\"\"\n bg = ti.Vector([r, g, b, 1.0])\n self.rect(0, 0, self.x, self.y, bg)\n\n @ti.func\n def point(self, x: ti.i32, y: ti.i32, rgba: vec4):\n \"\"\"Draw point.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n rgba (vec4): Colour.\n \"\"\"\n self.px.rgba[x, y] = rgba\n\n @ti.func\n def points(self, x: ti.template(), y: ti.template(), rgba: vec4):\n \"\"\"Draw points with the same colour.\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n rgba (vec4): Colour.\n \"\"\"\n for i in ti.static(range(len(x))):\n self.point(x[i], y[i], rgba)\n\n @ti.func\n def rect(self, x: ti.i32, y: ti.i32, w: ti.i32, h: ti.i32, rgba: vec4):\n \"\"\"Draw a filled rectangle.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n w (ti.i32): Width.\n h (ti.i32): Height.\n rgba (vec4): Colour.\n \"\"\"\n # TODO: fill arg\n # TODO: gradients, lerp with ti.math.mix(x, y, a)\n for i, j in ti.ndrange(w, h):\n self.px.rgba[x + i, y + j] = rgba\n\n @ti.func\n def line(self, x0: ti.i32, y0: ti.i32, x1: ti.i32, y1: ti.i32, rgba: vec4):\n \"\"\"Draw a line using Bresenham's algorithm.\n\n Args:\n x0 (ti.i32): X start position.\n y0 (ti.i32): Y start position.\n x1 (ti.i32): X end position.\n y1 (ti.i32): Y end position.\n rgba (vec4): Colour.\n\n TODO: thickness\n TODO: anti-aliasing\n TODO: should lines wrap around (as two lines)?\n \"\"\"\n dx = ti.abs(x1 - x0)\n dy = ti.abs(y1 - y0)\n x, y = x0, y0\n sx = -1 if x0 > x1 else 1\n sy = -1 if y0 > y1 else 1\n if dx > dy:\n err = dx / 2.0\n while x != x1:\n self.px.rgba[x, y] = rgba\n err -= dy\n if err < 0:\n y += sy\n err += dx\n x += sx\n else:\n err = dy / 2.0\n while y != y1:\n self.px.rgba[x, y] = rgba\n err -= dx\n if err < 0:\n x += sx\n err += dy\n y += sy\n self.px.rgba[x, y] = rgba\n\n @ti.func\n def circle(self, x: ti.i32, y: ti.i32, r: ti.i32, rgba: vec4):\n \"\"\"Draw a filled circle.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n r (ti.i32): Radius.\n rgba (vec4): Colour.\n \"\"\"\n for i in range(r + 1):\n d = ti.sqrt(r**2 - i**2)\n d_int = ti.cast(d, ti.i32)\n # TODO: parallelise ?\n for j in range(d_int):\n self.px.rgba[x + i, y + j] = rgba\n self.px.rgba[x + i, y - j] = rgba\n self.px.rgba[x - i, y - j] = rgba\n self.px.rgba[x - i, y + j] = rgba\n\n @ti.func\n def circles(self, x: ti.template(), y: ti.template(), r: ti.template(), rgba: vec4):\n \"\"\"Draw circles with the same colour.\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n r (ti.template): Radii.\n rgba (vec4): Colour.\n \"\"\"\n for i in ti.static(range(len(x))):\n self.circle(x[i], y[i], r[i], rgba)\n\n @ti.func\n def triangle(self, a, b, c, rgba: vec4):\n \"\"\"Draw a filled triangle.\n\n Args:\n a (vec2): Point A.\n b (vec2): Point B.\n c (vec2): Point C.\n rgba (vec4): Colour.\n \"\"\"\n # TODO: fill arg\n x = ti.Vector([a[0], b[0], c[0]])\n y = ti.Vector([a[1], b[1], c[1]])\n self.polygon(x, y, rgba)\n\n @ti.func\n def polygon(self, x: ti.template(), y: ti.template(), rgba: vec4):\n \"\"\"Draw a filled polygon.\n\n Polygons are drawn according to the polygon mode, which can be \"crossing\" \n (default) or \"winding\". First, the bounding box of the polygon is calculated.\n Then, we check if each pixel in the bounding box is inside the polygon. If it\n is, we draw it (along with each neighbour pixel).\n\n Reference for point in polygon inclusion testing:\n http://www.dgp.toronto.edu/~mac/e-stuff/point_in_polygon.py\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n rgba (vec4): Colour.\n\n TODO: fill arg\n \"\"\"\n x_min, x_max = ti.cast(x.min(), ti.i32), ti.cast(x.max(), ti.i32)\n y_min, y_max = ti.cast(y.min(), ti.i32), ti.cast(y.max(), ti.i32)\n l = len(x)\n for i, j in ti.ndrange(x_max - x_min, y_max - y_min):\n p = ti.Vector([x_min + i, y_min + j])\n if self._is_inside(p, x, y, l) != 0:\n # TODO: abstract out, weight?\n \"\"\"\n x-1,y-1 x,y-1 x+1,y-1\n x-1,y x,y x+1,y\n x-1,y+1 x,y+1 x+1,y+1\n \"\"\"\n _x, _y = p[0], p[1]\n self.px.rgba[_x - 1, _y - 1] = rgba\n self.px.rgba[_x - 1, _y] = rgba\n self.px.rgba[_x - 1, _y + 1] = rgba\n\n self.px.rgba[_x, _y - 1] = rgba\n self.px.rgba[_x, _y] = rgba\n self.px.rgba[_x, _y + 1] = rgba\n\n self.px.rgba[_x + 1, _y - 1] = rgba\n self.px.rgba[_x + 1, _y] = rgba\n self.px.rgba[_x + 1, _y + 1] = rgba\n\n @ti.func\n def _is_inside(self, p: vec2, x: ti.template(), y: ti.template(), l: ti.i32):\n \"\"\"Check if point is inside polygon.\n\n Args:\n p (vec2): Point.\n x (ti.template): X positions.\n y (ti.template): Y positions.\n l (ti.i32): Number of points.\n\n Returns:\n int: 1 if inside, 0 if outside.\n \"\"\"\n is_inside = 0\n if self.polygon_mode == \"crossing\":\n is_inside = self._is_inside_crossing(p, x, y, l)\n elif self.polygon_mode == \"winding\":\n is_inside = self._is_inside_winding(p, x, y, l)\n return is_inside\n\n @ti.func\n def _is_inside_crossing(self, p: vec2, x: ti.template(), y: ti.template(), l: ti.i32):\n \"\"\"Check if point is inside polygon using crossing number algorithm.\n\n Args:\n p (vec2): Point.\n x (ti.template): X positions.\n y (ti.template): Y positions.\n l (ti.i32): Number of points.\n\n Returns:\n int: 1 if inside, 0 if outside.\n \"\"\"\n n = 0\n v0, v1 = ti.Vector([0.0, 0.0]), ti.Vector([0.0, 0.0])\n for i in range(l):\n i1 = i + 1 if i < l - 1 else 0\n v0, v1 = [x[i], y[i]], [x[i1], y[i1]]\n if (v0[1] <= p[1] and v1[1] > p[1]) or (v0[1] > p[1] and v1[1] <= p[1]):\n vt = (p[1] - v0[1]) / (v1[1] - v0[1])\n if p[0] < v0[0] + vt * (v1[0] - v0[0]):\n n += 1\n return n % 2\n\n @ti.func\n def _is_inside_winding(self, p: vec2, x: ti.template(), y: ti.template(), l: ti.i32):\n \"\"\"Check if point is inside polygon using winding number algorithm.\n\n Args:\n p (vec2): Point.\n x (ti.template): X positions.\n y (ti.template): Y positions.\n l (ti.i32): Number of points.\n\n Returns:\n int: 1 if inside, 0 if outside.\n \"\"\"\n n = 0\n v0, v1 = ti.Vector([0.0, 0.0]), ti.Vector([0.0, 0.0])\n for i in range(l):\n i1 = i + 1 if i < l - 1 else 0\n v0, v1 = [x[i], y[i]], [x[i1], y[i1]]\n if v0[1] <= p[1] and v1[1] > p[1] and (v0 - v1).cross(p - v1) > 0:\n n += 1\n elif v1[1] <= p[1] and (v0 - v1).cross(p - v1) < 0:\n n -= 1\n return n\n\n @ti.kernel\n def flip_x(self):\n \"\"\"Flip image in x-axis.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba_inv[i, j] = self.px.rgba[self.x - 1 - i, j]\n\n @ti.kernel\n def flip_y(self):\n \"\"\"Flip image in y-axis.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba_inv[i, j] = self.px.rgba[i, self.y - 1 - j]\n\n @ti.kernel\n def invert(self):\n \"\"\"Invert image.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = 1.0 - self.px.rgba[i, j]\n\n @ti.kernel\n def decay(self, rate: ti.f32):\n \"\"\"Decay pixels.\n\n Args:\n rate (ti.f32): decay rate.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] *= rate\n\n def blend_add(self, px: ti.template()):\n \"\"\"Blend by adding pixels together (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_add(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_add(self, rgba: ti.template()):\n \"\"\"Blend by adding pixels together (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] += rgba[i, j]\n\n def blend_sub(self, px: ti.template()):\n \"\"\"Blend by subtracting pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_sub(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_sub(self, rgba: ti.template()):\n \"\"\"Blend by subtracting pixels (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] -= rgba[i, j]\n\n def blend_mul(self, px: ti.template()):\n \"\"\"Blend by multiplying pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_mul(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_mul(self, rgba: ti.template()):\n \"\"\"Blend by multiplying pixels (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] *= rgba[i, j]\n\n def blend_div(self, px: ti.template()):\n \"\"\"Blend by dividing pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_div(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_div(self, rgba: ti.template()):\n \"\"\"Blend by dividing pixels (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] /= rgba[i, j]\n\n def blend_min(self, px: ti.template()):\n \"\"\"Blend by taking the minimum of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_min(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_min(self, rgba: ti.template()):\n \"\"\"Blend by taking the minimum of each pixel (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.min(self.px.rgba[i, j], rgba[i, j])\n\n def blend_max(self, px: ti.template()):\n \"\"\"Blend by taking the maximum of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_max(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_max(self, rgba: ti.template()):\n \"\"\"Blend by taking the maximum of each pixel (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.max(self.px.rgba[i, j], rgba[i, j])\n\n def blend_diff(self, px: ti.template()):\n \"\"\"Blend by taking the difference of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_diff(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_diff(self, rgba: ti.template()):\n \"\"\"Blend by taking the difference of each pixel (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.abs(self.px.rgba[i, j] - rgba[i, j])\n\n def blend_diff_inv(self, px: ti.template()):\n \"\"\"Blend by taking the inverse difference of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_diff_inv(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_diff_inv(self, rgba: ti.template()):\n \"\"\"Blend by taking the inverse difference of each pixel (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.abs(rgba[i, j] - self.px.rgba[i, j])\n\n def blend_mix(self, px: ti.template(), amount: ti.f32):\n \"\"\"Blend by mixing pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n amount (ti.f32): Amount to mix.\n \"\"\"\n self._blend_mix(self.rgba_from_px(px), amount)\n\n @ti.kernel\n def _blend_mix(self, rgba: ti.template(), amount: ti.f32):\n \"\"\"Blend by mixing pixels (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n amount (ti.f32): Amount to mix.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.math.mix(self.px.rgba[i, j], rgba[i, j], amount)\n\n @ti.kernel\n def blur(self, radius: ti.i32):\n \"\"\"Blur pixels.\n\n Args:\n radius (ti.i32): Blur radius.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n d = ti.Vector([0.0, 0.0, 0.0, 0.0])\n for di in range(-radius, radius + 1):\n for dj in range(-radius, radius + 1):\n dx = (i + di) % self.x\n dy = (j + dj) % self.y\n d += self.px.rgba[dx, dy]\n d /= (radius * 2 + 1) ** 2\n self.px.rgba[i, j] = d\n\n def particles(\n self, particles: ti.template(), species: ti.template(), shape=\"circle\"\n ):\n \"\"\"Draw particles.\n\n Args:\n particles (ti.template): Particles.\n species (ti.template): Species.\n shape (str, optional): Shape. Defaults to \"circle\".\n \"\"\"\n shape = self.shape_enum[shape]\n self._particles(particles, species, shape)\n\n @ti.kernel\n def _particles(self, particles: ti.template(), species: ti.template(), shape: int):\n \"\"\"Draw particles.\n\n Args:\n particles (ti.template): Particles.\n species (ti.template): Species.\n shape (int): Shape enum value.\n \"\"\"\n for i in range(self.tv.p.n):\n p = particles.field[i]\n s = species[p.species]\n if p.active == 0.0:\n continue\n px = ti.cast(p.pos[0], ti.i32)\n py = ti.cast(p.pos[1], ti.i32)\n vx = ti.cast(p.pos[0] + p.vel[0] * 20, ti.i32)\n vy = ti.cast(p.pos[1] + p.vel[1] * 20, ti.i32)\n rgba = s.rgba * self.CONSTS.BRIGHTNESS\n if shape == 0:\n self.point(px, py, rgba)\n elif shape == 1:\n self.line(px, py, vx, vy, rgba)\n elif shape == 2:\n side = int(s.size) * 2\n self.rect(px, py, side, side, rgba)\n elif shape == 3:\n self.circle(px, py, p.size, rgba)\n elif shape == 4:\n a = p.pos\n b = p.pos + 1\n c = a + b\n self.triangle(a, b, c, rgba)\n # elif shape == 5:\n # self.polygon(px, py, rgba)\n\n def rgba_from_px(self, px):\n \"\"\"Get rgba from pixels.\n\n Args:\n px (Any): Pixels to get rgba from.\n\n Raises:\n TypeError: If pixel field cannot be found.\n\n Returns:\n MatrixField: RGBA matrix field.\n \"\"\"\n if isinstance(px, Pixels):\n return px.px.rgba\n elif isinstance(px, StructField):\n return px.rgba\n elif isinstance(px, MatrixField):\n return px\n else:\n try:\n return px.px.px.rgba\n except:\n raise TypeError(f\"Cannot find pixel field in {type(px)}\")\n\n def __call__(self):\n \"\"\"Call returns pixels.\"\"\"\n return self.get()\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.__call__","title":"__call__()
","text":"Call returns pixels.
Source code in src/tolvera/pixels.py
def __call__(self):\n \"\"\"Call returns pixels.\"\"\"\n return self.get()\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise Pixels
Parameters:
Name Type Description Default tolvera
Tolvera
T\u00f6lvera instance.
required **kwargs
Keyword arguments. polygon_mode (str): Polygon mode. Defaults to \"crossing\". brightness (float): Brightness. Defaults to 1.0.
{}
Source code in src/tolvera/pixels.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise Pixels\n\n Args:\n tolvera (Tolvera): T\u00f6lvera instance.\n **kwargs: Keyword arguments.\n polygon_mode (str): Polygon mode. Defaults to \"crossing\".\n brightness (float): Brightness. Defaults to 1.0. \n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.polygon_mode = kwargs.get(\"polygon_mode\", \"crossing\")\n self.x = self.tv.x\n self.y = self.tv.y\n self.px = Pixel.field(shape=(self.x, self.y))\n brightness = kwargs.get(\"brightness\", 1.0)\n self.CONSTS = CONSTS(\n {\n \"BRIGHTNESS\": (ti.f32, brightness),\n }\n )\n self.shape_enum = {\n \"point\": 0,\n \"line\": 1,\n \"rect\": 2,\n \"circle\": 3,\n \"triangle\": 4,\n \"polygon\": 5,\n }\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.background","title":"background(r, g, b)
","text":"Set background colour.
Parameters:
Name Type Description Default r
f32
Red.
required g
f32
Green.
required b
f32
Blue.
required Source code in src/tolvera/pixels.py
@ti.func\ndef background(self, r: ti.f32, g: ti.f32, b: ti.f32):\n \"\"\"Set background colour.\n\n Args:\n r (ti.f32): Red.\n g (ti.f32): Green.\n b (ti.f32): Blue.\n \"\"\"\n bg = ti.Vector([r, g, b, 1.0])\n self.rect(0, 0, self.x, self.y, bg)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_add","title":"blend_add(px)
","text":"Blend by adding pixels together (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required Source code in src/tolvera/pixels.py
def blend_add(self, px: ti.template()):\n \"\"\"Blend by adding pixels together (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_add(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_diff","title":"blend_diff(px)
","text":"Blend by taking the difference of each pixel (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required Source code in src/tolvera/pixels.py
def blend_diff(self, px: ti.template()):\n \"\"\"Blend by taking the difference of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_diff(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_diff_inv","title":"blend_diff_inv(px)
","text":"Blend by taking the inverse difference of each pixel (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required Source code in src/tolvera/pixels.py
def blend_diff_inv(self, px: ti.template()):\n \"\"\"Blend by taking the inverse difference of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_diff_inv(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_div","title":"blend_div(px)
","text":"Blend by dividing pixels (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required Source code in src/tolvera/pixels.py
def blend_div(self, px: ti.template()):\n \"\"\"Blend by dividing pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_div(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_max","title":"blend_max(px)
","text":"Blend by taking the maximum of each pixel (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required Source code in src/tolvera/pixels.py
def blend_max(self, px: ti.template()):\n \"\"\"Blend by taking the maximum of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_max(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_min","title":"blend_min(px)
","text":"Blend by taking the minimum of each pixel (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required Source code in src/tolvera/pixels.py
def blend_min(self, px: ti.template()):\n \"\"\"Blend by taking the minimum of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_min(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_mix","title":"blend_mix(px, amount)
","text":"Blend by mixing pixels (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required amount
f32
Amount to mix.
required Source code in src/tolvera/pixels.py
def blend_mix(self, px: ti.template(), amount: ti.f32):\n \"\"\"Blend by mixing pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n amount (ti.f32): Amount to mix.\n \"\"\"\n self._blend_mix(self.rgba_from_px(px), amount)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_mul","title":"blend_mul(px)
","text":"Blend by multiplying pixels (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required Source code in src/tolvera/pixels.py
def blend_mul(self, px: ti.template()):\n \"\"\"Blend by multiplying pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_mul(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_sub","title":"blend_sub(px)
","text":"Blend by subtracting pixels (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required Source code in src/tolvera/pixels.py
def blend_sub(self, px: ti.template()):\n \"\"\"Blend by subtracting pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_sub(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blur","title":"blur(radius)
","text":"Blur pixels.
Parameters:
Name Type Description Default radius
i32
Blur radius.
required Source code in src/tolvera/pixels.py
@ti.kernel\ndef blur(self, radius: ti.i32):\n \"\"\"Blur pixels.\n\n Args:\n radius (ti.i32): Blur radius.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n d = ti.Vector([0.0, 0.0, 0.0, 0.0])\n for di in range(-radius, radius + 1):\n for dj in range(-radius, radius + 1):\n dx = (i + di) % self.x\n dy = (j + dj) % self.y\n d += self.px.rgba[dx, dy]\n d /= (radius * 2 + 1) ** 2\n self.px.rgba[i, j] = d\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.circle","title":"circle(x, y, r, rgba)
","text":"Draw a filled circle.
Parameters:
Name Type Description Default x
i32
X position.
required y
i32
Y position.
required r
i32
Radius.
required rgba
vec4
Colour.
required Source code in src/tolvera/pixels.py
@ti.func\ndef circle(self, x: ti.i32, y: ti.i32, r: ti.i32, rgba: vec4):\n \"\"\"Draw a filled circle.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n r (ti.i32): Radius.\n rgba (vec4): Colour.\n \"\"\"\n for i in range(r + 1):\n d = ti.sqrt(r**2 - i**2)\n d_int = ti.cast(d, ti.i32)\n # TODO: parallelise ?\n for j in range(d_int):\n self.px.rgba[x + i, y + j] = rgba\n self.px.rgba[x + i, y - j] = rgba\n self.px.rgba[x - i, y - j] = rgba\n self.px.rgba[x - i, y + j] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.circles","title":"circles(x, y, r, rgba)
","text":"Draw circles with the same colour.
Parameters:
Name Type Description Default x
template
X positions.
required y
template
Y positions.
required r
template
Radii.
required rgba
vec4
Colour.
required Source code in src/tolvera/pixels.py
@ti.func\ndef circles(self, x: ti.template(), y: ti.template(), r: ti.template(), rgba: vec4):\n \"\"\"Draw circles with the same colour.\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n r (ti.template): Radii.\n rgba (vec4): Colour.\n \"\"\"\n for i in ti.static(range(len(x))):\n self.circle(x[i], y[i], r[i], rgba)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.clear","title":"clear()
","text":"Clear pixels.
Source code in src/tolvera/pixels.py
@ti.kernel\ndef clear(self):\n \"\"\"Clear pixels.\"\"\"\n self.px.rgba.fill(0)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.decay","title":"decay(rate)
","text":"Decay pixels.
Parameters:
Name Type Description Default rate
f32
decay rate.
required Source code in src/tolvera/pixels.py
@ti.kernel\ndef decay(self, rate: ti.f32):\n \"\"\"Decay pixels.\n\n Args:\n rate (ti.f32): decay rate.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] *= rate\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.diffuse","title":"diffuse(evaporate)
","text":"Diffuse pixels.
Parameters:
Name Type Description Default evaporate
float
Evaporation rate.
required Source code in src/tolvera/pixels.py
@ti.kernel\ndef diffuse(self, evaporate: ti.f32):\n \"\"\"Diffuse pixels.\n\n Args:\n evaporate (float): Evaporation rate.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n d = ti.Vector([0.0, 0.0, 0.0, 0.0]) \n for di in ti.static(range(-1, 2)):\n for dj in ti.static(range(-1, 2)):\n dx = (i + di) % self.x\n dy = (j + dj) % self.y\n d += self.px.rgba[dx, dy]\n d *= 0.99 / 9.0\n self.px.rgba[i, j] = d\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.flip_x","title":"flip_x()
","text":"Flip image in x-axis.
Source code in src/tolvera/pixels.py
@ti.kernel\ndef flip_x(self):\n \"\"\"Flip image in x-axis.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba_inv[i, j] = self.px.rgba[self.x - 1 - i, j]\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.flip_y","title":"flip_y()
","text":"Flip image in y-axis.
Source code in src/tolvera/pixels.py
@ti.kernel\ndef flip_y(self):\n \"\"\"Flip image in y-axis.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba_inv[i, j] = self.px.rgba[i, self.y - 1 - j]\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.get","title":"get()
","text":"Get pixels.
Source code in src/tolvera/pixels.py
def get(self):\n \"\"\"Get pixels.\"\"\"\n return self.px\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.invert","title":"invert()
","text":"Invert image.
Source code in src/tolvera/pixels.py
@ti.kernel\ndef invert(self):\n \"\"\"Invert image.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = 1.0 - self.px.rgba[i, j]\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.line","title":"line(x0, y0, x1, y1, rgba)
","text":"Draw a line using Bresenham's algorithm.
Parameters:
Name Type Description Default x0
i32
X start position.
required y0
i32
Y start position.
required x1
i32
X end position.
required y1
i32
Y end position.
required rgba
vec4
Colour.
required TODO: thickness TODO: anti-aliasing TODO: should lines wrap around (as two lines)?
Source code in src/tolvera/pixels.py
@ti.func\ndef line(self, x0: ti.i32, y0: ti.i32, x1: ti.i32, y1: ti.i32, rgba: vec4):\n \"\"\"Draw a line using Bresenham's algorithm.\n\n Args:\n x0 (ti.i32): X start position.\n y0 (ti.i32): Y start position.\n x1 (ti.i32): X end position.\n y1 (ti.i32): Y end position.\n rgba (vec4): Colour.\n\n TODO: thickness\n TODO: anti-aliasing\n TODO: should lines wrap around (as two lines)?\n \"\"\"\n dx = ti.abs(x1 - x0)\n dy = ti.abs(y1 - y0)\n x, y = x0, y0\n sx = -1 if x0 > x1 else 1\n sy = -1 if y0 > y1 else 1\n if dx > dy:\n err = dx / 2.0\n while x != x1:\n self.px.rgba[x, y] = rgba\n err -= dy\n if err < 0:\n y += sy\n err += dx\n x += sx\n else:\n err = dy / 2.0\n while y != y1:\n self.px.rgba[x, y] = rgba\n err -= dx\n if err < 0:\n x += sx\n err += dy\n y += sy\n self.px.rgba[x, y] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.particles","title":"particles(particles, species, shape='circle')
","text":"Draw particles.
Parameters:
Name Type Description Default particles
template
Particles.
required species
template
Species.
required shape
str
Shape. Defaults to \"circle\".
'circle'
Source code in src/tolvera/pixels.py
def particles(\n self, particles: ti.template(), species: ti.template(), shape=\"circle\"\n):\n \"\"\"Draw particles.\n\n Args:\n particles (ti.template): Particles.\n species (ti.template): Species.\n shape (str, optional): Shape. Defaults to \"circle\".\n \"\"\"\n shape = self.shape_enum[shape]\n self._particles(particles, species, shape)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.point","title":"point(x, y, rgba)
","text":"Draw point.
Parameters:
Name Type Description Default x
i32
X position.
required y
i32
Y position.
required rgba
vec4
Colour.
required Source code in src/tolvera/pixels.py
@ti.func\ndef point(self, x: ti.i32, y: ti.i32, rgba: vec4):\n \"\"\"Draw point.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n rgba (vec4): Colour.\n \"\"\"\n self.px.rgba[x, y] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.points","title":"points(x, y, rgba)
","text":"Draw points with the same colour.
Parameters:
Name Type Description Default x
template
X positions.
required y
template
Y positions.
required rgba
vec4
Colour.
required Source code in src/tolvera/pixels.py
@ti.func\ndef points(self, x: ti.template(), y: ti.template(), rgba: vec4):\n \"\"\"Draw points with the same colour.\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n rgba (vec4): Colour.\n \"\"\"\n for i in ti.static(range(len(x))):\n self.point(x[i], y[i], rgba)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.polygon","title":"polygon(x, y, rgba)
","text":"Draw a filled polygon.
Polygons are drawn according to the polygon mode, which can be \"crossing\" (default) or \"winding\". First, the bounding box of the polygon is calculated. Then, we check if each pixel in the bounding box is inside the polygon. If it is, we draw it (along with each neighbour pixel).
Reference for point in polygon inclusion testing: http://www.dgp.toronto.edu/~mac/e-stuff/point_in_polygon.py
Parameters:
Name Type Description Default x
template
X positions.
required y
template
Y positions.
required rgba
vec4
Colour.
required TODO: fill arg
Source code in src/tolvera/pixels.py
@ti.func\ndef polygon(self, x: ti.template(), y: ti.template(), rgba: vec4):\n \"\"\"Draw a filled polygon.\n\n Polygons are drawn according to the polygon mode, which can be \"crossing\" \n (default) or \"winding\". First, the bounding box of the polygon is calculated.\n Then, we check if each pixel in the bounding box is inside the polygon. If it\n is, we draw it (along with each neighbour pixel).\n\n Reference for point in polygon inclusion testing:\n http://www.dgp.toronto.edu/~mac/e-stuff/point_in_polygon.py\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n rgba (vec4): Colour.\n\n TODO: fill arg\n \"\"\"\n x_min, x_max = ti.cast(x.min(), ti.i32), ti.cast(x.max(), ti.i32)\n y_min, y_max = ti.cast(y.min(), ti.i32), ti.cast(y.max(), ti.i32)\n l = len(x)\n for i, j in ti.ndrange(x_max - x_min, y_max - y_min):\n p = ti.Vector([x_min + i, y_min + j])\n if self._is_inside(p, x, y, l) != 0:\n # TODO: abstract out, weight?\n \"\"\"\n x-1,y-1 x,y-1 x+1,y-1\n x-1,y x,y x+1,y\n x-1,y+1 x,y+1 x+1,y+1\n \"\"\"\n _x, _y = p[0], p[1]\n self.px.rgba[_x - 1, _y - 1] = rgba\n self.px.rgba[_x - 1, _y] = rgba\n self.px.rgba[_x - 1, _y + 1] = rgba\n\n self.px.rgba[_x, _y - 1] = rgba\n self.px.rgba[_x, _y] = rgba\n self.px.rgba[_x, _y + 1] = rgba\n\n self.px.rgba[_x + 1, _y - 1] = rgba\n self.px.rgba[_x + 1, _y] = rgba\n self.px.rgba[_x + 1, _y + 1] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.rect","title":"rect(x, y, w, h, rgba)
","text":"Draw a filled rectangle.
Parameters:
Name Type Description Default x
i32
X position.
required y
i32
Y position.
required w
i32
Width.
required h
i32
Height.
required rgba
vec4
Colour.
required Source code in src/tolvera/pixels.py
@ti.func\ndef rect(self, x: ti.i32, y: ti.i32, w: ti.i32, h: ti.i32, rgba: vec4):\n \"\"\"Draw a filled rectangle.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n w (ti.i32): Width.\n h (ti.i32): Height.\n rgba (vec4): Colour.\n \"\"\"\n # TODO: fill arg\n # TODO: gradients, lerp with ti.math.mix(x, y, a)\n for i, j in ti.ndrange(w, h):\n self.px.rgba[x + i, y + j] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.rgba_from_px","title":"rgba_from_px(px)
","text":"Get rgba from pixels.
Parameters:
Name Type Description Default px
Any
Pixels to get rgba from.
required Raises:
Type Description TypeError
If pixel field cannot be found.
Returns:
Name Type Description MatrixField
RGBA matrix field.
Source code in src/tolvera/pixels.py
def rgba_from_px(self, px):\n \"\"\"Get rgba from pixels.\n\n Args:\n px (Any): Pixels to get rgba from.\n\n Raises:\n TypeError: If pixel field cannot be found.\n\n Returns:\n MatrixField: RGBA matrix field.\n \"\"\"\n if isinstance(px, Pixels):\n return px.px.rgba\n elif isinstance(px, StructField):\n return px.rgba\n elif isinstance(px, MatrixField):\n return px\n else:\n try:\n return px.px.px.rgba\n except:\n raise TypeError(f\"Cannot find pixel field in {type(px)}\")\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.set","title":"set(px)
","text":"Set pixels.
Parameters:
Name Type Description Default px
Any
Pixels to set. Can be Pixels, StructField, MatrixField, etc (see rgba_from_px).
required Source code in src/tolvera/pixels.py
def set(self, px: Any):\n \"\"\"Set pixels.\n\n Args:\n px (Any): Pixels to set. Can be Pixels, StructField, MatrixField, etc (see rgba_from_px).\n \"\"\"\n self.px.rgba = self.rgba_from_px(px)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.triangle","title":"triangle(a, b, c, rgba)
","text":"Draw a filled triangle.
Parameters:
Name Type Description Default a
vec2
Point A.
required b
vec2
Point B.
required c
vec2
Point C.
required rgba
vec4
Colour.
required Source code in src/tolvera/pixels.py
@ti.func\ndef triangle(self, a, b, c, rgba: vec4):\n \"\"\"Draw a filled triangle.\n\n Args:\n a (vec2): Point A.\n b (vec2): Point B.\n c (vec2): Point C.\n rgba (vec4): Colour.\n \"\"\"\n # TODO: fill arg\n x = ti.Vector([a[0], b[0], c[0]])\n y = ti.Vector([a[1], b[1], c[1]])\n self.polygon(x, y, rgba)\n
"},{"location":"reference/tolvera/sketchbook/","title":"Sketchbook","text":"Sketchbook module for listing and running sketches from the command line.
WIP.
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.get_sketch_info","title":"get_sketch_info(sketch_file, sketchbook_folder='./')
","text":"Gets information about a specific sketch file.
Parameters:
Name Type Description Default sketch_file
str
Name of the sketch file.
required sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Returns:
Type Description Dict[str, Any]
Dict[str, Any]: Dictionary containing sketch information such as name, path, size, modified and created times.
Source code in src/tolvera/sketchbook.py
def get_sketch_info(sketch_file: str, sketchbook_folder: str = \"./\") -> Dict[str, Any]:\n \"\"\"\n Gets information about a specific sketch file.\n\n Args:\n sketch_file (str): Name of the sketch file.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Returns:\n Dict[str, Any]: Dictionary containing sketch information such as name, path, size, modified and created times.\n \"\"\"\n validate_sketch_file(sketch_file, sketchbook_folder)\n datetimefmt = \"%Y-%m-%d %H:%M:%S\"\n if not sketch_file.endswith(\".py\"):\n sketch_file += \".py\"\n file_path = os.path.join(sketchbook_folder, sketch_file)\n module_name = os.path.splitext(sketch_file)[0]\n file_info = os.stat(file_path)\n size = file_info.st_size\n modified = datetime.datetime.fromtimestamp(file_info.st_mtime).strftime(datetimefmt)\n created = datetime.datetime.fromtimestamp(file_info.st_ctime).strftime(datetimefmt)\n return {\n \"name\": module_name,\n \"path\": file_path,\n \"size\": size,\n \"modified\": modified,\n \"created\": created,\n }\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.get_sketchbook_files","title":"get_sketchbook_files(sketchbook_folder='./')
","text":"Gets all sketch files from the sketchbook folder.
Parameters:
Name Type Description Default sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Returns:
Type Description List[str]
List[str]: List of sketch file names.
Source code in src/tolvera/sketchbook.py
def get_sketchbook_files(sketchbook_folder: str = \"./\") -> List[str]:\n \"\"\"\n Gets all sketch files from the sketchbook folder.\n\n Args:\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Returns:\n List[str]: List of sketch file names.\n \"\"\"\n files = os.listdir(sketchbook_folder)\n exclude = [\"__init__.py\", \"__pycache__\", \".DS_Store\", \"imgui.ini\"]\n return [f for f in files if f not in exclude]\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.get_sketchbook_files_info","title":"get_sketchbook_files_info(sketches, sketchbook_folder='./')
","text":"Gets information about all sketch files in the sketchbook folder.
Parameters:
Name Type Description Default sketches
List[str]
List of sketch file names.
required sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Returns:
Type Description List[Dict[str, Any]]
List[Dict[str, Any]]: List of sketch information dictionaries.
Source code in src/tolvera/sketchbook.py
def get_sketchbook_files_info(\n sketches: List[str], sketchbook_folder: str = \"./\"\n) -> List[Dict[str, Any]]:\n \"\"\"\n Gets information about all sketch files in the sketchbook folder.\n\n Args:\n sketches (List[str]): List of sketch file names.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Returns:\n List[Dict[str, Any]]: List of sketch information dictionaries.\n \"\"\"\n sketch_infos = []\n for sketch in sketches:\n sketch_info = get_sketch_info(sketch, sketchbook_folder)\n sketch_infos.append(sketch_info)\n return sketch_infos\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.import_sketch","title":"import_sketch(module_name, file_path)
","text":"Imports a sketch from a given file.
Parameters:
Name Type Description Default module_name
str
Name of the module.
required file_path
str
Path to the file containing the module.
required Returns:
Name Type Description Any
Any
Imported module.
Source code in src/tolvera/sketchbook.py
def import_sketch(module_name: str, file_path: str) -> Any:\n \"\"\"\n Imports a sketch from a given file.\n\n Args:\n module_name (str): Name of the module.\n file_path (str): Path to the file containing the module.\n\n Returns:\n Any: Imported module.\n \"\"\"\n if not os.path.exists(file_path):\n print(f\"File does not exist: {file_path}\")\n return None\n\n if not module_name:\n print(\"Module name is empty or invalid.\")\n return None\n try:\n print(f\"Importing {module_name} from {file_path}...\")\n spec = importlib.util.spec_from_file_location(module_name, file_path)\n module = importlib.util.module_from_spec(spec)\n spec.loader.exec_module(module)\n return module\n except Exception as e:\n error_type = type(e).__name__\n print(f\"Error importing {module_name} ({error_type}): {str(e)}\")\n return None\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.list_sketches","title":"list_sketches(sketchbook_folder='./', sort='name', direction='ascending')
","text":"Lists all sketches in the given sketchbook folder.
Parameters:
Name Type Description Default sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
sort
str
Sort sketches by name, size, modified or created. Defaults to 'name'.
'name'
direction
str
Sort direction, either 'ascending' or 'descending'. Defaults to 'ascending'.
'ascending'
Source code in src/tolvera/sketchbook.py
def list_sketches(\n sketchbook_folder: str = \"./\", sort: str = \"name\", direction: str = \"ascending\"\n) -> None:\n \"\"\"\n Lists all sketches in the given sketchbook folder.\n\n Args:\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n sort (str): Sort sketches by name, size, modified or created. Defaults to 'name'.\n direction (str): Sort direction, either 'ascending' or 'descending'. Defaults to 'ascending'.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n sketch_files_list = get_sketchbook_files(sketchbook_folder)\n sketches = get_sketchbook_files_info(sketch_files_list, sketchbook_folder)\n sorted_sketches = sort_sketch_files(sketches, sort, direction)\n pretty_print_sketchbook(sorted_sketches, sketchbook_folder)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.main","title":"main(*args, **kwargs)
","text":"Main function for running the sketchbook from the command line.
Source code in src/tolvera/sketchbook.py
def main(*args, **kwargs):\n \"\"\"\n Main function for running the sketchbook from the command line.\n \"\"\"\n if \"sketchbook\" in kwargs:\n sketchbook = kwargs[\"sketchbook\"]\n else:\n sketchbook = \"./\"\n if \"sketch\" in kwargs:\n sketch = kwargs[\"sketch\"]\n if isinstance(sketch, str):\n run_sketch_by_name(sketch, sketchbook, *args, **kwargs)\n elif isinstance(sketch, int):\n print(f\"Running sketch by index: {sketch}\")\n run_sketch_by_index(sketch, sketchbook, *args, **kwargs)\n elif \"sketches\" in kwargs:\n sort = kwargs[\"sort\"] if \"sort\" in kwargs else \"name\"\n direction = kwargs[\"direction\"] if \"direction\" in kwargs else \"ascending\"\n list_sketches(sketchbook, sort, direction)\n exit()\n elif \"random\" in kwargs:\n run_random_sketch(sketchbook)\n else:\n list_sketches(sketchbook)\n exit()\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.pretty_print_sketchbook","title":"pretty_print_sketchbook(sketches, sketchbook_folder='./')
","text":"Pretty prints the sketchbook information.
Parameters:
Name Type Description Default sketches
List[Dict[str, Any]]
List of sketch information dictionaries.
required sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Source code in src/tolvera/sketchbook.py
def pretty_print_sketchbook(\n sketches: List[Dict[str, Any]], sketchbook_folder: str = \"./\"\n) -> None:\n \"\"\"\n Pretty prints the sketchbook information.\n\n Args:\n sketches (List[Dict[str, Any]]): List of sketch information dictionaries.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n print(f\"\\nSketchbook '{sketchbook_folder}':\\n\")\n print(\n f\" {'Index':<5} {'Sketch':<25} {'Size':<10} {'Modified':<20} {'Created':<20}\"\n )\n print(f\" {'-'*5:<5} {'-'*25:<25} {'-'*10:<10} {'-'*20:<20} {'-'*20:<20}\")\n for i, sketch in enumerate(sketches):\n print(\n f\" {i:<5} {sketch['name']:<25} {sketch['size']:<10} {sketch['modified']:<20} {sketch['created']:<20}\"\n )\n print()\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.run_random_sketch","title":"run_random_sketch(sketchbook='./')
","text":"Runs a random sketch from the sketchbook.
Parameters:
Name Type Description Default sketchbook
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Source code in src/tolvera/sketchbook.py
def run_random_sketch(sketchbook=\"./\"):\n \"\"\"\n Runs a random sketch from the sketchbook.\n\n Args:\n sketchbook (str): Path to the sketchbook folder. Defaults to current directory.\n \"\"\"\n validate_sketchbook_path(sketchbook)\n files = get_sketchbook_files(sketchbook)\n sketch_file = files[random.randint(0, len(files) - 1)]\n run_sketch_by_name(sketch_file, sketchbook)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.run_sketch_by_index","title":"run_sketch_by_index(index, sketchbook_folder='./', *args, **kwargs)
","text":"Runs a sketch by its index in the sketchbook.
Parameters:
Name Type Description Default index
int
Index of the sketch to run.
required sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Source code in src/tolvera/sketchbook.py
def run_sketch_by_index(\n index: int, sketchbook_folder: str = \"./\", *args: Any, **kwargs: Any\n) -> None:\n \"\"\"\n Runs a sketch by its index in the sketchbook.\n\n Args:\n index (int): Index of the sketch to run.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n files = get_sketchbook_files(sketchbook_folder)\n for file in files:\n if file.endswith(\".py\"):\n module_name = os.path.splitext(file)[0]\n file_path = os.path.join(sketchbook_folder, file)\n if str(index) == module_name:\n try_import_and_run_sketch(module_name, file_path, *args, **kwargs)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.run_sketch_by_name","title":"run_sketch_by_name(sketch_file, sketchbook_folder='./', *args, **kwargs)
","text":"Runs a sketch by its file name.
Parameters:
Name Type Description Default sketch_file
str
Name of the sketch file.
required sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Source code in src/tolvera/sketchbook.py
def run_sketch_by_name(\n sketch_file: str, sketchbook_folder: str = \"./\", *args: Any, **kwargs: Any\n) -> None:\n \"\"\"\n Runs a sketch by its file name.\n\n Args:\n sketch_file (str): Name of the sketch file.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n if not sketch_file.endswith(\".py\"):\n sketch_file += \".py\"\n file_path = os.path.join(sketchbook_folder, sketch_file)\n module_name = os.path.splitext(sketch_file)[0]\n try_import_and_run_sketch(module_name, file_path, *args, **kwargs)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.run_sketch_function_from_module","title":"run_sketch_function_from_module(module, function_name, file_path, *args, **kwargs)
","text":"Runs a specific function from a given module.
Parameters:
Name Type Description Default module
Any
The imported module.
required function_name
str
Name of the function to run.
required file_path
str
Path to the file containing the module.
required Source code in src/tolvera/sketchbook.py
def run_sketch_function_from_module(\n module: Any, function_name: str, file_path: str, *args: Any, **kwargs: Any\n) -> None:\n \"\"\"\n Runs a specific function from a given module.\n\n Args:\n module (Any): The imported module.\n function_name (str): Name of the function to run.\n file_path (str): Path to the file containing the module.\n \"\"\"\n try:\n if hasattr(module, function_name) and callable(getattr(module, function_name)):\n print(f\"Running {function_name} from {file_path}...\")\n getattr(module, function_name)(*args, **kwargs)\n else:\n print(f\"{module} does not have a '{function_name}' function.\")\n except Exception as e:\n print(f\"Error running {function_name} from {file_path}: {str(e)}\")\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.sort_sketch_files","title":"sort_sketch_files(sketch_files, sort='name', direction='ascending')
","text":"Sorts sketch files by name, size, modified or created.
Parameters:
Name Type Description Default sort
str
Sort sketches by name, size, modified or created. Defaults to 'name'.
'name'
sketch_files
List[Dict[str, Any]]
List of sketch information dictionaries.
required direction
str
Sort direction, either 'ascending' or 'descending'. Defaults to 'ascending'.
'ascending'
Returns:
Type Description List[Dict[str, Any]]
List[Dict[str, Any]]: List of sorted sketch information dictionaries.
Source code in src/tolvera/sketchbook.py
def sort_sketch_files(\n sketch_files: List[Dict[str, Any]], sort: str = \"name\", direction: str = \"ascending\"\n) -> List[Dict[str, Any]]:\n \"\"\"\n Sorts sketch files by name, size, modified or created.\n\n Args:\n sort (str): Sort sketches by name, size, modified or created. Defaults to 'name'.\n sketch_files (List[Dict[str, Any]]): List of sketch information dictionaries.\n direction (str): Sort direction, either 'ascending' or 'descending'. Defaults to 'ascending'.\n\n Returns:\n List[Dict[str, Any]]: List of sorted sketch information dictionaries.\n \"\"\"\n reverse = True if direction == \"descending\" else False\n if sort == \"name\":\n return sorted(sketch_files, key=lambda k: k[\"name\"], reverse=reverse)\n elif sort == \"size\":\n return sorted(sketch_files, key=lambda k: k[\"size\"], reverse=reverse)\n elif sort == \"modified\":\n return sorted(sketch_files, key=lambda k: k[\"modified\"], reverse=reverse)\n elif sort == \"created\":\n return sorted(sketch_files, key=lambda k: k[\"created\"], reverse=reverse)\n else:\n return sketch_files\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.try_import_and_run_sketch","title":"try_import_and_run_sketch(module_name, file_path, *args, **kwargs)
","text":"Tries to import and run a sketch from a given file.
Parameters:
Name Type Description Default module_name
str
Name of the module.
required file_path
str
Path to the file containing the module.
required Source code in src/tolvera/sketchbook.py
def try_import_and_run_sketch(\n module_name: str, file_path: str, *args: Any, **kwargs: Any\n) -> None:\n \"\"\"\n Tries to import and run a sketch from a given file.\n\n Args:\n module_name (str): Name of the module.\n file_path (str): Path to the file containing the module.\n \"\"\"\n try:\n module = import_sketch(module_name, file_path)\n run_sketch_function_from_module(module, \"sketch\", file_path, *args, **kwargs)\n except Exception as e:\n print(f\"Error running {module_name}: {str(e)}\")\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.validate_sketch_file","title":"validate_sketch_file(sketch_file, sketchbook_folder='./')
","text":"Validates if the given sketch file exists in the sketchbook folder.
Parameters:
Name Type Description Default sketch_file
str
Name of the sketch file.
required sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Raises:
Type Description SystemExit
If the sketch file does not exist.
Source code in src/tolvera/sketchbook.py
def validate_sketch_file(sketch_file: str, sketchbook_folder: str = \"./\") -> None:\n \"\"\"\n Validates if the given sketch file exists in the sketchbook folder.\n\n Args:\n sketch_file (str): Name of the sketch file.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Raises:\n SystemExit: If the sketch file does not exist.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n if not sketch_file.endswith(\".py\"):\n sketch_file += \".py\"\n file_path = os.path.join(sketchbook_folder, sketch_file)\n if not os.path.isfile(file_path):\n print(f\"Sketch file '{file_path}' does not exist.\")\n sys.exit(1)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.validate_sketchbook_path","title":"validate_sketchbook_path(sketchbook_folder='./')
","text":"Validates if the given sketchbook folder exists.
Parameters:
Name Type Description Default sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Raises:
Type Description SystemExit
If the sketchbook folder does not exist.
Source code in src/tolvera/sketchbook.py
def validate_sketchbook_path(sketchbook_folder: str = \"./\") -> None:\n \"\"\"\n Validates if the given sketchbook folder exists.\n\n Args:\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Raises:\n SystemExit: If the sketchbook folder does not exist.\n \"\"\"\n if not os.path.isdir(sketchbook_folder):\n print(f\"Sketchbook folder '{sketchbook_folder}' does not exist.\")\n sys.exit(1)\n
"},{"location":"reference/tolvera/species/","title":"Species","text":"Species class.
"},{"location":"reference/tolvera/species/#tolvera.species.Species","title":"Species
","text":"Species in T\u00f6lvera.
Species are implemented as a State with attributes for size
, speed
, mass
and colour
(rgba), and with a length determined by the number of species in the T\u00f6lvera instance (tv.sn
). The attributes are normalised and scaled by species the species_consts
attribute. They are initialised with random values.
Rather than accessing this class directly, access is typically via the State attributes via the T\u00f6lvera instance, via e.g. tv.s.species.field[i].size
.
Source code in src/tolvera/species.py
class Species:\n \"\"\"Species in T\u00f6lvera.\n\n Species are implemented as a State with attributes for `size`, `speed`, `mass` and\n `colour` (rgba), and with a length determined by the number of species in the\n T\u00f6lvera instance (`tv.sn`). The attributes are normalised and scaled by species the \n `species_consts` attribute. They are initialised with random values.\n\n Rather than accessing this class directly, access is typically via the State\n attributes via the T\u00f6lvera instance, via e.g. `tv.s.species.field[i].size`.\n \"\"\"\n def __init__(self, tolvera, **kwargs) -> None:\n \"\"\"Initialise Species\n\n Args:\n tolvera (Tolvera): Tolvera instance.\n **kwargs: Keyword arguments. \n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.n = self.tv.sn\n self.tv.species_consts = CONSTS(\n {\n \"MIN_SIZE\": (ti.f32, 2.0),\n \"MAX_SIZE\": (ti.f32, 5.0),\n \"MIN_SPEED\": (ti.f32, 0.2),\n \"MAX_SPEED\": (ti.f32, 2.0),\n \"MAX_MASS\": (ti.f32, 1.0),\n }\n )\n self.tv.s.species = (\n {\n \"size\": (ti.f32, 0.0, 1.0),\n \"speed\": (ti.f32, 0.0, 1.0),\n \"mass\": (ti.f32, 0.0, 1.0),\n \"rgba\": (ti.math.vec4, 0.0, 1.0),\n },\n self.n,\n \"set\",\n \"set\",\n )\n\n def randomise(self):\n \"\"\"Randomise species.\"\"\"\n self.tv.s.species.randomise()\n
"},{"location":"reference/tolvera/species/#tolvera.species.Species.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise Species
Parameters:
Name Type Description Default tolvera
Tolvera
Tolvera instance.
required **kwargs
Keyword arguments.
{}
Source code in src/tolvera/species.py
def __init__(self, tolvera, **kwargs) -> None:\n \"\"\"Initialise Species\n\n Args:\n tolvera (Tolvera): Tolvera instance.\n **kwargs: Keyword arguments. \n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.n = self.tv.sn\n self.tv.species_consts = CONSTS(\n {\n \"MIN_SIZE\": (ti.f32, 2.0),\n \"MAX_SIZE\": (ti.f32, 5.0),\n \"MIN_SPEED\": (ti.f32, 0.2),\n \"MAX_SPEED\": (ti.f32, 2.0),\n \"MAX_MASS\": (ti.f32, 1.0),\n }\n )\n self.tv.s.species = (\n {\n \"size\": (ti.f32, 0.0, 1.0),\n \"speed\": (ti.f32, 0.0, 1.0),\n \"mass\": (ti.f32, 0.0, 1.0),\n \"rgba\": (ti.math.vec4, 0.0, 1.0),\n },\n self.n,\n \"set\",\n \"set\",\n )\n
"},{"location":"reference/tolvera/species/#tolvera.species.Species.randomise","title":"randomise()
","text":"Randomise species.
Source code in src/tolvera/species.py
def randomise(self):\n \"\"\"Randomise species.\"\"\"\n self.tv.s.species.randomise()\n
"},{"location":"reference/tolvera/state/","title":"State","text":"State and StateDict classes for T\u00f6lvera.
Every T\u00f6lvera instance has a StateDict, which is a dictionary of State instances. The StateDict is accessible via the 's' attribute of a T\u00f6lvera instance, and can be used to create and access states.
Each State instance has a Taichi struct field and a corresponding NpNdarrayDict, which handles OSC accessors and endpoints.
"},{"location":"reference/tolvera/state/#tolvera.state.State","title":"State
","text":"State class for T\u00f6lvera.
This class takes a name, dictionary of state attributes, and a shape, and creates a Taichi struct field and a corresponding dictionary of NumPy arrays (NpNdarrayDict) for a state.
The Taichi struct field can be used in Taichi scope, and the NpNdarrayDict can be used in Python scope, and the two are kept in sync by the from_nddict() and to_nddict() methods.
The State class also handles OSC accessors for the state, which use the NpNdarrayDict to get and set data. A T\u00f6lvera instance is therefore required to initialise a State instance.
State attributes are defined as a dictionary of attribute names and tuples of (Taichi type, min value, max value). The domain of the attribute is used when randomising the data in the state, and by OSCMap endpoints and client patches.
The state is n-dimensional based on the shape argument, and the NpNdarrayDict provides methods for accessing the data in the state in n-dimensional slices.
Example tv.s.flock_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn, # particle count\n \"osc\": (\"get\"),\n \"randomise\": False,\n}\n
Source code in src/tolvera/state.py
@ti.data_oriented\nclass State:\n \"\"\"State class for T\u00f6lvera.\n\n This class takes a name, dictionary of state attributes, and a shape, and\n creates a Taichi struct field and a corresponding dictionary of NumPy arrays \n (NpNdarrayDict) for a state.\n\n The Taichi struct field can be used in Taichi scope, and the NpNdarrayDict\n can be used in Python scope, and the two are kept in sync by the from_nddict()\n and to_nddict() methods.\n\n The State class also handles OSC accessors for the state, which use the\n NpNdarrayDict to get and set data. A T\u00f6lvera instance is therefore required\n to initialise a State instance.\n\n State attributes are defined as a dictionary of attribute names and tuples of\n (Taichi type, min value, max value). The domain of the attribute is used when\n randomising the data in the state, and by OSCMap endpoints and client patches.\n\n The state is n-dimensional based on the shape argument, and the NpNdarrayDict\n provides methods for accessing the data in the state in n-dimensional slices.\n\n Example:\n ```py\n tv.s.flock_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn, # particle count\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n ```\n \"\"\"\n def __init__(\n self,\n tolvera,\n name: str,\n state: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int] = None,\n osc: str | tuple = None, # ('get', 'set', 'stream')\n randomise: bool = True,\n methods: dict[str, Any] = None,\n ):\n \"\"\"Initialise a state for T\u00f6lvera.\n\n Args:\n tolvera (Tolvera): Tolvera instance to which this state belongs.\n name (str): Name of this state.\n state (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int], optional): Shape of the state. Defaults to 1.\n methods (dict[str, Any], optional): Flag for OSC via iipyper. Defaults to False.\n \"\"\"\n self.tv = tolvera\n assert name is not None, \"State must have a name.\"\n self.name = name\n shape = 1 if shape is None else shape\n self.setup_data(state, shape, randomise, methods)\n self.setup_osc(osc)\n\n def setup_data(\n self,\n dict: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int],\n randomise: bool = True,\n methods: dict[str, Any] = None,\n ):\n \"\"\"Setup data structures and data for this state.\n\n Args:\n dict (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int]): Shape of the state.\n randomise (bool, optional): Flag to randomise the data on creation. Defaults to True.\n methods (dict[str, Any], optional): Dict of Taichi field struct methods. Defaults to None.\n \"\"\"\n self.create_struct_field(dict, shape, methods)\n self.create_npndarray_dict()\n if randomise:\n self.randomise()\n\n def create_struct_field(\n self,\n dict: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int],\n methods: dict[str, Any] = None,\n ):\n \"\"\"Create a Taichi struct field for this state.\n\n Args:\n dict (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int]): Shape of the state.\n methods (dict[str, Any], optional): Dict of Taichi field struct methods. Defaults to None.\n \"\"\"\n self.dict = dict\n self.shape = (shape,) if isinstance(shape, int) else shape\n if methods is None:\n self.struct = ti.types.struct(**{k: v[0] for k, v in self.dict.items()})\n else:\n self.methods = methods if methods is not None else {}\n self.struct = ti.types.struct(\n **{k: v[0] for k, v in self.dict.items()}, methods=self.methods\n )\n self.field = self.struct.field(shape=self.shape)\n\n def create_npndarray_dict(self):\n \"\"\"Create a NpNdarrayDict for this state.\n\n Raises:\n NotImplementedError: If no Numpy type is found for a Taichi type.\n \"\"\"\n nddict = {}\n for k, v in self.dict.items():\n titype, min_val, max_val = v\n nptype = TiNpTypeMap.get(titype)\n if nptype is None:\n raise NotImplementedError(f\"no nptype for {titype}\")\n nddict[k] = (nptype, min_val, max_val)\n self.nddict = NpNdarrayDict(nddict, self.shape)\n self.size = self.nddict.size\n\n def randomise(self):\n \"\"\"Randomise the data in this state.\"\"\"\n self.nddict.randomise()\n self.from_nddict()\n\n def setup_osc(self, osc: tuple|str = None):\n \"\"\"Setup OSC for this state.\n\n Args:\n osc (tuple | str, optional): (\"get\", \"set\", \"stream\"). Defaults to None.\n \"\"\"\n self.osc = osc is not None\n if not self.osc: return\n if isinstance(osc, str): osc = (osc,)\n self.osc_set = \"set\" in osc if self.osc else False\n self.osc_get = \"get\" in osc if self.osc else False\n self.osc_stream = \"stream\" in osc if self.osc else False\n self.setter_name = f\"{self.tv.name_clean}_set_{self.name}\"\n self.getter_name = f\"{self.tv.name_clean}_get_{self.name}\"\n self.stream_name = f\"{self.tv.name_clean}_stream_{self.name}\"\n if self.tv.osc is not False and self.osc:\n self.osc = self.tv.osc\n if self.osc_set: self.add_osc_setters()\n # if self.osc_get: self.add_osc_getters()\n # if self.osc_stream: self.add_osc_streams()\n\n def add_osc_setters(self):\n name = self.setter_name\n self.osc.map.receive_args_inline(name + \"_randomise\", self.randomise)\n\n def add_osc_getters(self):\n name = self.getter_name\n for k, v in self.dict.items():\n ranges = (int(v[0]), int(v[0]), int(v[1]))\n kwargs = {\"i\": ranges, \"j\": ranges, \"attr\": (k, k, k)}\n self.osc.map.receive_args_inline(f\"{name}\", self.osc_getter, **kwargs)\n\n # def osc_getter(self, i: int, j: int, attribute: str):\n # ret = self.get((i, j), attribute)\n # if ret is not None:\n # route = self.osc.map.pascal_to_path(self.getter_name) # +'/'+attribute\n # self.osc.host.return_to_sender_by_name(\n # (route, attribute, ret), self.osc.client_name\n # )\n # return ret\n\n # def add_osc_streams(self):\n # # add send in broadcast mode\n # raise NotImplementedError(\"add_osc_streams not implemented\")\n\n def serialize(self) -> str:\n return ti_serialize(self.field)\n\n def deserialize(self, json_str: str):\n ti_deserialize(self.field, json_str)\n\n def save(self, path: str):\n # TODO: path validation, save to path, etc.\n json_str = self.serialize()\n raise NotImplementedError(\"save not implemented\")\n\n def load(self, path: str):\n # TODO: path validation, file ext., etc.\n # TODO: data validation (pydantic?)\n json_str = jsons.load(path)\n self.deserialize(json_str)\n raise NotImplementedError(\"load not implemented\")\n\n def from_nddict(self):\n \"\"\"Copy data from NpNdarrayDict to Taichi field.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n data = self.nddict.get_data()\n self.field.from_numpy(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.from_nddict] {e}\") from e\n\n def to_nddict(self):\n \"\"\"Copy data from Taichi field to NpNdarrayDict.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n data = self.field.to_numpy()\n self.nddict.set_data(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.to_nddict] {e}\") from e\n\n def set_from_nddict(self, data: dict):\n \"\"\"Copy data from NumPy array dict to Taichi field.\n\n Args:\n data (dict): NumPy array dict to copy.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n self.field.from_numpy(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.from_numpy] {e}\") from e\n\n \"\"\"\n npndarray_dict wrappers\n \"\"\"\n\n def from_vec(self, vec: list):\n \"\"\"Wrapper for NpNdarrayDict.from_vec().\"\"\"\n self.to_nddict()\n self.nddict.from_vec(vec)\n self.from_nddict()\n\n def to_vec(self) -> list:\n \"\"\"Wrapper for NpNdarrayDict.to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.to_vec()\n\n def attr_from_vec(self, attr: str, vec: list):\n \"\"\"Wrapper for NpNdarrayDict.attr_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.attr_from_vec(attr, vec)\n self.from_nddict()\n\n def attr_to_vec(self, attr: str) -> list:\n \"\"\"Wrapper for NpNdarrayDict.attr_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.attr_to_vec(attr)\n\n def slice_from_vec(self, slice_args: list, slice_vec: list):\n \"\"\"Wrapper for NpNdarrayDict.slice_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.slice_from_vec(slice_args, slice_vec)\n self.from_nddict()\n\n def slice_to_vec(self, slice_args: list) -> list:\n \"\"\"Wrapper for NpNdarrayDict.slice_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.slice_to_vec(slice_args)\n\n def attr_slice_from_vec(self, attr: str, slice_args: list, slice_vec: list):\n \"\"\"Wrapper for NpNdarrayDict.attr_slice_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.attr_slice_from_vec(attr, slice_args, slice_vec)\n self.from_nddict()\n\n def attr_slice_to_vec(self, attr: str, slice_args: list) -> list:\n \"\"\"Wrapper for NpNdarrayDict.attr_slice_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.attr_slice_to_vec(attr, slice_args)\n\n def attr_size(self, attr: str) -> int:\n \"\"\"Return the size of the attribute.\"\"\"\n return self.nddict.data[attr].size\n\n \"\"\"\n misc\n \"\"\"\n\n def fill(self, value: ti.f32):\n \"\"\"Fill the Taichi field with a value.\"\"\"\n self.field.fill(value)\n\n @ti.func\n def __getitem__(self, index: ti.i32):\n \"\"\"Return the Taichi field attribute.\n\n Args:\n index (ti.i32): Attribute index.\n \"\"\"\n return self.field[index]\n\n def __call__(self, *args: Any, **kwds: Any) -> Any:\n \"\"\"Return the Taichi field.\"\"\"\n return self.field\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.__call__","title":"__call__(*args, **kwds)
","text":"Return the Taichi field.
Source code in src/tolvera/state.py
def __call__(self, *args: Any, **kwds: Any) -> Any:\n \"\"\"Return the Taichi field.\"\"\"\n return self.field\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.__getitem__","title":"__getitem__(index)
","text":"Return the Taichi field attribute.
Parameters:
Name Type Description Default index
i32
Attribute index.
required Source code in src/tolvera/state.py
@ti.func\ndef __getitem__(self, index: ti.i32):\n \"\"\"Return the Taichi field attribute.\n\n Args:\n index (ti.i32): Attribute index.\n \"\"\"\n return self.field[index]\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.__init__","title":"__init__(tolvera, name, state, shape=None, osc=None, randomise=True, methods=None)
","text":"Initialise a state for T\u00f6lvera.
Parameters:
Name Type Description Default tolvera
Tolvera
Tolvera instance to which this state belongs.
required name
str
Name of this state.
required state
dict[str, tuple[DataType, Any, Any]]
Dict of state attributes.
required shape
int | tuple[int]
Shape of the state. Defaults to 1.
None
methods
dict[str, Any]
Flag for OSC via iipyper. Defaults to False.
None
Source code in src/tolvera/state.py
def __init__(\n self,\n tolvera,\n name: str,\n state: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int] = None,\n osc: str | tuple = None, # ('get', 'set', 'stream')\n randomise: bool = True,\n methods: dict[str, Any] = None,\n):\n \"\"\"Initialise a state for T\u00f6lvera.\n\n Args:\n tolvera (Tolvera): Tolvera instance to which this state belongs.\n name (str): Name of this state.\n state (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int], optional): Shape of the state. Defaults to 1.\n methods (dict[str, Any], optional): Flag for OSC via iipyper. Defaults to False.\n \"\"\"\n self.tv = tolvera\n assert name is not None, \"State must have a name.\"\n self.name = name\n shape = 1 if shape is None else shape\n self.setup_data(state, shape, randomise, methods)\n self.setup_osc(osc)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_from_vec","title":"attr_from_vec(attr, vec)
","text":"Wrapper for NpNdarrayDict.attr_from_vec().
Source code in src/tolvera/state.py
def attr_from_vec(self, attr: str, vec: list):\n \"\"\"Wrapper for NpNdarrayDict.attr_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.attr_from_vec(attr, vec)\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_size","title":"attr_size(attr)
","text":"Return the size of the attribute.
Source code in src/tolvera/state.py
def attr_size(self, attr: str) -> int:\n \"\"\"Return the size of the attribute.\"\"\"\n return self.nddict.data[attr].size\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_slice_from_vec","title":"attr_slice_from_vec(attr, slice_args, slice_vec)
","text":"Wrapper for NpNdarrayDict.attr_slice_from_vec().
Source code in src/tolvera/state.py
def attr_slice_from_vec(self, attr: str, slice_args: list, slice_vec: list):\n \"\"\"Wrapper for NpNdarrayDict.attr_slice_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.attr_slice_from_vec(attr, slice_args, slice_vec)\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_slice_to_vec","title":"attr_slice_to_vec(attr, slice_args)
","text":"Wrapper for NpNdarrayDict.attr_slice_to_vec().
Source code in src/tolvera/state.py
def attr_slice_to_vec(self, attr: str, slice_args: list) -> list:\n \"\"\"Wrapper for NpNdarrayDict.attr_slice_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.attr_slice_to_vec(attr, slice_args)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_to_vec","title":"attr_to_vec(attr)
","text":"Wrapper for NpNdarrayDict.attr_to_vec().
Source code in src/tolvera/state.py
def attr_to_vec(self, attr: str) -> list:\n \"\"\"Wrapper for NpNdarrayDict.attr_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.attr_to_vec(attr)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.create_npndarray_dict","title":"create_npndarray_dict()
","text":"Create a NpNdarrayDict for this state.
Raises:
Type Description NotImplementedError
If no Numpy type is found for a Taichi type.
Source code in src/tolvera/state.py
def create_npndarray_dict(self):\n \"\"\"Create a NpNdarrayDict for this state.\n\n Raises:\n NotImplementedError: If no Numpy type is found for a Taichi type.\n \"\"\"\n nddict = {}\n for k, v in self.dict.items():\n titype, min_val, max_val = v\n nptype = TiNpTypeMap.get(titype)\n if nptype is None:\n raise NotImplementedError(f\"no nptype for {titype}\")\n nddict[k] = (nptype, min_val, max_val)\n self.nddict = NpNdarrayDict(nddict, self.shape)\n self.size = self.nddict.size\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.create_struct_field","title":"create_struct_field(dict, shape, methods=None)
","text":"Create a Taichi struct field for this state.
Parameters:
Name Type Description Default dict
dict[str, tuple[DataType, Any, Any]]
Dict of state attributes.
required shape
int | tuple[int]
Shape of the state.
required methods
dict[str, Any]
Dict of Taichi field struct methods. Defaults to None.
None
Source code in src/tolvera/state.py
def create_struct_field(\n self,\n dict: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int],\n methods: dict[str, Any] = None,\n):\n \"\"\"Create a Taichi struct field for this state.\n\n Args:\n dict (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int]): Shape of the state.\n methods (dict[str, Any], optional): Dict of Taichi field struct methods. Defaults to None.\n \"\"\"\n self.dict = dict\n self.shape = (shape,) if isinstance(shape, int) else shape\n if methods is None:\n self.struct = ti.types.struct(**{k: v[0] for k, v in self.dict.items()})\n else:\n self.methods = methods if methods is not None else {}\n self.struct = ti.types.struct(\n **{k: v[0] for k, v in self.dict.items()}, methods=self.methods\n )\n self.field = self.struct.field(shape=self.shape)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.fill","title":"fill(value)
","text":"Fill the Taichi field with a value.
Source code in src/tolvera/state.py
def fill(self, value: ti.f32):\n \"\"\"Fill the Taichi field with a value.\"\"\"\n self.field.fill(value)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.from_nddict","title":"from_nddict()
","text":"Copy data from NpNdarrayDict to Taichi field.
Raises:
Type Description Exception
If data cannot be copied.
Source code in src/tolvera/state.py
def from_nddict(self):\n \"\"\"Copy data from NpNdarrayDict to Taichi field.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n data = self.nddict.get_data()\n self.field.from_numpy(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.from_nddict] {e}\") from e\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.from_vec","title":"from_vec(vec)
","text":"Wrapper for NpNdarrayDict.from_vec().
Source code in src/tolvera/state.py
def from_vec(self, vec: list):\n \"\"\"Wrapper for NpNdarrayDict.from_vec().\"\"\"\n self.to_nddict()\n self.nddict.from_vec(vec)\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.randomise","title":"randomise()
","text":"Randomise the data in this state.
Source code in src/tolvera/state.py
def randomise(self):\n \"\"\"Randomise the data in this state.\"\"\"\n self.nddict.randomise()\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.set_from_nddict","title":"set_from_nddict(data)
","text":"Copy data from NumPy array dict to Taichi field.
Parameters:
Name Type Description Default data
dict
NumPy array dict to copy.
required Raises:
Type Description Exception
If data cannot be copied.
Source code in src/tolvera/state.py
def set_from_nddict(self, data: dict):\n \"\"\"Copy data from NumPy array dict to Taichi field.\n\n Args:\n data (dict): NumPy array dict to copy.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n self.field.from_numpy(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.from_numpy] {e}\") from e\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.setup_data","title":"setup_data(dict, shape, randomise=True, methods=None)
","text":"Setup data structures and data for this state.
Parameters:
Name Type Description Default dict
dict[str, tuple[DataType, Any, Any]]
Dict of state attributes.
required shape
int | tuple[int]
Shape of the state.
required randomise
bool
Flag to randomise the data on creation. Defaults to True.
True
methods
dict[str, Any]
Dict of Taichi field struct methods. Defaults to None.
None
Source code in src/tolvera/state.py
def setup_data(\n self,\n dict: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int],\n randomise: bool = True,\n methods: dict[str, Any] = None,\n):\n \"\"\"Setup data structures and data for this state.\n\n Args:\n dict (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int]): Shape of the state.\n randomise (bool, optional): Flag to randomise the data on creation. Defaults to True.\n methods (dict[str, Any], optional): Dict of Taichi field struct methods. Defaults to None.\n \"\"\"\n self.create_struct_field(dict, shape, methods)\n self.create_npndarray_dict()\n if randomise:\n self.randomise()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.setup_osc","title":"setup_osc(osc=None)
","text":"Setup OSC for this state.
Parameters:
Name Type Description Default osc
tuple | str
(\"get\", \"set\", \"stream\"). Defaults to None.
None
Source code in src/tolvera/state.py
def setup_osc(self, osc: tuple|str = None):\n \"\"\"Setup OSC for this state.\n\n Args:\n osc (tuple | str, optional): (\"get\", \"set\", \"stream\"). Defaults to None.\n \"\"\"\n self.osc = osc is not None\n if not self.osc: return\n if isinstance(osc, str): osc = (osc,)\n self.osc_set = \"set\" in osc if self.osc else False\n self.osc_get = \"get\" in osc if self.osc else False\n self.osc_stream = \"stream\" in osc if self.osc else False\n self.setter_name = f\"{self.tv.name_clean}_set_{self.name}\"\n self.getter_name = f\"{self.tv.name_clean}_get_{self.name}\"\n self.stream_name = f\"{self.tv.name_clean}_stream_{self.name}\"\n if self.tv.osc is not False and self.osc:\n self.osc = self.tv.osc\n if self.osc_set: self.add_osc_setters()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.slice_from_vec","title":"slice_from_vec(slice_args, slice_vec)
","text":"Wrapper for NpNdarrayDict.slice_from_vec().
Source code in src/tolvera/state.py
def slice_from_vec(self, slice_args: list, slice_vec: list):\n \"\"\"Wrapper for NpNdarrayDict.slice_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.slice_from_vec(slice_args, slice_vec)\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.slice_to_vec","title":"slice_to_vec(slice_args)
","text":"Wrapper for NpNdarrayDict.slice_to_vec().
Source code in src/tolvera/state.py
def slice_to_vec(self, slice_args: list) -> list:\n \"\"\"Wrapper for NpNdarrayDict.slice_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.slice_to_vec(slice_args)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.to_nddict","title":"to_nddict()
","text":"Copy data from Taichi field to NpNdarrayDict.
Raises:
Type Description Exception
If data cannot be copied.
Source code in src/tolvera/state.py
def to_nddict(self):\n \"\"\"Copy data from Taichi field to NpNdarrayDict.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n data = self.field.to_numpy()\n self.nddict.set_data(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.to_nddict] {e}\") from e\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.to_vec","title":"to_vec()
","text":"Wrapper for NpNdarrayDict.to_vec().
Source code in src/tolvera/state.py
def to_vec(self) -> list:\n \"\"\"Wrapper for NpNdarrayDict.to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.to_vec()\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict","title":"StateDict
","text":" Bases: dotdict
StateDict class for T\u00f6lvera.
This class is a dictionary of State instances, and is accessible via the 's' attribute of a T\u00f6lvera instance.
States can be created by assigning a dictionary or a tuple to a StateDict key. and can be used in Taichi scope and Python scope respectively.
Example tv = Tolvera(**kwargs)
tv.s.mystate = { \"state\": { \"id\": (ti.i32, 0, tv.pn - 1), \"pos\": (ti.math.vec2, -1.0, 1.0), \"vel\": (ti.math.vec2, -1.0, 1.0), }, \"shape\": (tv.pn, 1), \"osc\": \"get\", \"randomise\": True }
tv.s.mystate.field.pos[0] = 0.5
Source code in src/tolvera/state.py
class StateDict(dotdict):\n \"\"\"StateDict class for T\u00f6lvera.\n\n This class is a dictionary of State instances, and is accessible via the 's'\n attribute of a T\u00f6lvera instance.\n\n States can be created by assigning a dictionary or a tuple to a StateDict key.\n and can be used in Taichi scope and Python scope respectively.\n\n Example:\n tv = Tolvera(**kwargs)\n\n tv.s.mystate = {\n \"state\": {\n \"id\": (ti.i32, 0, tv.pn - 1),\n \"pos\": (ti.math.vec2, -1.0, 1.0),\n \"vel\": (ti.math.vec2, -1.0, 1.0),\n }, \n \"shape\": (tv.pn, 1), \n \"osc\": \"get\", \n \"randomise\": True\n }\n\n tv.s.mystate.field.pos[0] = 0.5\n \"\"\"\n def __init__(self, tolvera) -> None:\n \"\"\"Initialise a StateDict for T\u00f6lvera.\n\n Args:\n tolvera (Tolvera): Tolvera instance to which this StateDict belongs.\n \"\"\"\n self.tv = tolvera\n self.size = 0\n\n def set(self, name, kwargs: Any) -> None:\n \"\"\"Set a state in the StateDict.\n\n Args:\n name (str): Name of the state.\n kwargs (Any): State attributes.\n\n Raises:\n ValueError: If the state is already in the StateDict.\n Exception: If the state cannot be added.\n \"\"\"\n if name in self and name != \"size\":\n raise ValueError(f\"[tolvera.state.StateDict] '{name}' already in dict.\")\n try:\n self.add(name, kwargs)\n except Exception as e:\n raise type(e)(f\"[tolvera.state.StateDict] {e}\") from e\n\n def add(self, name, kwargs: Any):\n \"\"\"Add a state to the StateDict.\n\n Args:\n name (str): Name of the state.\n kwargs (Any): State attributes.\n\n Raises:\n TypeError: If kwargs is not a dict or tuple.\n \"\"\"\n if name == \"tv\" and type(kwargs) is not dict and type(kwargs) is not tuple:\n self[name] = kwargs\n elif name == \"size\" and type(kwargs) is int:\n self[name] = kwargs\n elif type(kwargs) is dict:\n self[name] = State(self.tv, name=name, **kwargs)\n self.size += self[name].size\n elif type(kwargs) is tuple:\n self[name] = State(self.tv, name, *kwargs)\n self.size += self[name].size\n else:\n raise TypeError(\n f\"[tolvera.state.StateDict] set() requires dict|tuple, not {type(kwargs)}\"\n )\n\n def from_vec(self, states: list[str], vector: list[float]):\n \"\"\"Copy data from a vector to states in the StateDict.\n\n Args:\n states (list[str]): List of state names.\n vector (list[float]): Vector of data to copy.\n\n Raises:\n Exception: If the vector is not the correct size.\n \"\"\"\n sizes_sum = self.get_size(states)\n assert sizes_sum == len(\n vector\n ), f\"sizes_sum={sizes_sum} != len(vector)={len(vector)}\"\n vec_start = 0\n for state in states:\n s = self.tv.s[state]\n vec = vector[vec_start : vec_start + s.size]\n s.from_vec(vec)\n vec_start += s.size\n\n def get_size(self, states: str | list[str]) -> int:\n \"\"\"Return the size of the states in the StateDict.\n\n Args:\n states (str | list[str]): State name or list of state names.\n\n Returns:\n int: Size of the states.\n \"\"\"\n if isinstance(states, str):\n states = [states]\n return sum([self.tv.s[state].size for state in states])\n\n def __setattr__(self, __name: str, __value: Any) -> None:\n \"\"\"Set a state in the StateDict.\n\n Args:\n __name (str): Name of the state.\n __value (Any): State attributes.\n \"\"\"\n self.set(__name, __value)\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.__init__","title":"__init__(tolvera)
","text":"Initialise a StateDict for T\u00f6lvera.
Parameters:
Name Type Description Default tolvera
Tolvera
Tolvera instance to which this StateDict belongs.
required Source code in src/tolvera/state.py
def __init__(self, tolvera) -> None:\n \"\"\"Initialise a StateDict for T\u00f6lvera.\n\n Args:\n tolvera (Tolvera): Tolvera instance to which this StateDict belongs.\n \"\"\"\n self.tv = tolvera\n self.size = 0\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.__setattr__","title":"__setattr__(__name, __value)
","text":"Set a state in the StateDict.
Parameters:
Name Type Description Default __name
str
Name of the state.
required __value
Any
State attributes.
required Source code in src/tolvera/state.py
def __setattr__(self, __name: str, __value: Any) -> None:\n \"\"\"Set a state in the StateDict.\n\n Args:\n __name (str): Name of the state.\n __value (Any): State attributes.\n \"\"\"\n self.set(__name, __value)\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.add","title":"add(name, kwargs)
","text":"Add a state to the StateDict.
Parameters:
Name Type Description Default name
str
Name of the state.
required kwargs
Any
State attributes.
required Raises:
Type Description TypeError
If kwargs is not a dict or tuple.
Source code in src/tolvera/state.py
def add(self, name, kwargs: Any):\n \"\"\"Add a state to the StateDict.\n\n Args:\n name (str): Name of the state.\n kwargs (Any): State attributes.\n\n Raises:\n TypeError: If kwargs is not a dict or tuple.\n \"\"\"\n if name == \"tv\" and type(kwargs) is not dict and type(kwargs) is not tuple:\n self[name] = kwargs\n elif name == \"size\" and type(kwargs) is int:\n self[name] = kwargs\n elif type(kwargs) is dict:\n self[name] = State(self.tv, name=name, **kwargs)\n self.size += self[name].size\n elif type(kwargs) is tuple:\n self[name] = State(self.tv, name, *kwargs)\n self.size += self[name].size\n else:\n raise TypeError(\n f\"[tolvera.state.StateDict] set() requires dict|tuple, not {type(kwargs)}\"\n )\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.from_vec","title":"from_vec(states, vector)
","text":"Copy data from a vector to states in the StateDict.
Parameters:
Name Type Description Default states
list[str]
List of state names.
required vector
list[float]
Vector of data to copy.
required Raises:
Type Description Exception
If the vector is not the correct size.
Source code in src/tolvera/state.py
def from_vec(self, states: list[str], vector: list[float]):\n \"\"\"Copy data from a vector to states in the StateDict.\n\n Args:\n states (list[str]): List of state names.\n vector (list[float]): Vector of data to copy.\n\n Raises:\n Exception: If the vector is not the correct size.\n \"\"\"\n sizes_sum = self.get_size(states)\n assert sizes_sum == len(\n vector\n ), f\"sizes_sum={sizes_sum} != len(vector)={len(vector)}\"\n vec_start = 0\n for state in states:\n s = self.tv.s[state]\n vec = vector[vec_start : vec_start + s.size]\n s.from_vec(vec)\n vec_start += s.size\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.get_size","title":"get_size(states)
","text":"Return the size of the states in the StateDict.
Parameters:
Name Type Description Default states
str | list[str]
State name or list of state names.
required Returns:
Name Type Description int
int
Size of the states.
Source code in src/tolvera/state.py
def get_size(self, states: str | list[str]) -> int:\n \"\"\"Return the size of the states in the StateDict.\n\n Args:\n states (str | list[str]): State name or list of state names.\n\n Returns:\n int: Size of the states.\n \"\"\"\n if isinstance(states, str):\n states = [states]\n return sum([self.tv.s[state].size for state in states])\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.set","title":"set(name, kwargs)
","text":"Set a state in the StateDict.
Parameters:
Name Type Description Default name
str
Name of the state.
required kwargs
Any
State attributes.
required Raises:
Type Description ValueError
If the state is already in the StateDict.
Exception
If the state cannot be added.
Source code in src/tolvera/state.py
def set(self, name, kwargs: Any) -> None:\n \"\"\"Set a state in the StateDict.\n\n Args:\n name (str): Name of the state.\n kwargs (Any): State attributes.\n\n Raises:\n ValueError: If the state is already in the StateDict.\n Exception: If the state cannot be added.\n \"\"\"\n if name in self and name != \"size\":\n raise ValueError(f\"[tolvera.state.StateDict] '{name}' already in dict.\")\n try:\n self.add(name, kwargs)\n except Exception as e:\n raise type(e)(f\"[tolvera.state.StateDict] {e}\") from e\n
"},{"location":"reference/tolvera/taichi_/","title":"Taichi","text":"Taichi class for initialising Taichi and UI.
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi","title":"Taichi
","text":"Taichi class for initialising Taichi and UI.
This class provides a show method for showing the Taichi canvas. It is used by the TolveraContext class to display a window.
Source code in src/tolvera/taichi_.py
class Taichi:\n \"\"\"Taichi class for initialising Taichi and UI.\n\n This class provides a show method for showing the Taichi canvas.\n It is used by the TolveraContext class to display a window.\"\"\"\n def __init__(self, context, **kwargs) -> None:\n \"\"\"Initialise Taichi\n\n Args:\n context (TolveraContext): global TolveraContext instance.\n **kwargs: Keyword arguments:\n gpu (str): GPU architecture to run on. Defaults to \"vulkan\".\n cpu (bool): Run on CPU. Defaults to False.\n fps (int): FPS limit. Defaults to 120.\n seed (int): Random seed. Defaults to time.time().\n headless (bool): Run headless. Defaults to False.\n name (str): Window name. Defaults to \"T\u00f6lvera\".\n \"\"\"\n self.ctx = context\n self.kwargs = kwargs\n self.gpu = kwargs.get(\"gpu\", \"vulkan\")\n self.cpu = kwargs.get(\"cpu\", None)\n self.fps = kwargs.get(\"fps\", 120)\n self.seed = kwargs.get(\"seed\", int(time.time()))\n self.headless = kwargs.get(\"headless\", False)\n self.name = kwargs.get(\"name\", \"T\u00f6lvera\")\n self.init_ti()\n self.init_ui()\n print(f\"[T\u00f6lvera.Taichi] Taichi initialised with: {vars(self)}\")\n\n def init_ti(self):\n \"\"\"Initialise Taichi backend on selected architecture.\"\"\"\n if self.cpu:\n ti.init(arch=ti.cpu, random_seed=self.seed)\n self.gpu = None\n print(\"[T\u00f6lvera.Taichi] Running on CPU\")\n else:\n if self.gpu == \"vulkan\":\n ti.init(arch=ti.vulkan, random_seed=self.seed)\n elif self.gpu == \"metal\":\n ti.init(arch=ti.metal, random_seed=self.seed)\n elif self.gpu == \"cuda\":\n ti.init(arch=ti.cuda, random_seed=self.seed)\n else:\n print(f\"[T\u00f6lvera.Taichi] Invalid GPU: {self.gpu}\")\n return False\n print(f\"[T\u00f6lvera.Taichi] Running on {self.gpu}\")\n\n def init_ui(self):\n \"\"\"Initialise Taichi UI window and canvas.\"\"\"\n self.window = ti.ui.Window(\n self.name,\n (self.ctx.x, self.ctx.y),\n fps_limit=self.fps,\n show_window=not self.headless,\n )\n self.canvas = self.window.get_canvas()\n\n def show(self, px):\n \"\"\"Show Taichi canvas and show window.\"\"\"\n self.canvas.set_image(px.px.rgba)\n if not self.headless:\n self.window.show()\n\n def __call__(self, *args: Any, **kwds: Any) -> Any:\n \"\"\"Call Taichi window show.\"\"\"\n self.show(*args, **kwds)\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.__call__","title":"__call__(*args, **kwds)
","text":"Call Taichi window show.
Source code in src/tolvera/taichi_.py
def __call__(self, *args: Any, **kwds: Any) -> Any:\n \"\"\"Call Taichi window show.\"\"\"\n self.show(*args, **kwds)\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.__init__","title":"__init__(context, **kwargs)
","text":"Initialise Taichi
Parameters:
Name Type Description Default context
TolveraContext
global TolveraContext instance.
required **kwargs
Keyword arguments: gpu (str): GPU architecture to run on. Defaults to \"vulkan\". cpu (bool): Run on CPU. Defaults to False. fps (int): FPS limit. Defaults to 120. seed (int): Random seed. Defaults to time.time(). headless (bool): Run headless. Defaults to False. name (str): Window name. Defaults to \"T\u00f6lvera\".
{}
Source code in src/tolvera/taichi_.py
def __init__(self, context, **kwargs) -> None:\n \"\"\"Initialise Taichi\n\n Args:\n context (TolveraContext): global TolveraContext instance.\n **kwargs: Keyword arguments:\n gpu (str): GPU architecture to run on. Defaults to \"vulkan\".\n cpu (bool): Run on CPU. Defaults to False.\n fps (int): FPS limit. Defaults to 120.\n seed (int): Random seed. Defaults to time.time().\n headless (bool): Run headless. Defaults to False.\n name (str): Window name. Defaults to \"T\u00f6lvera\".\n \"\"\"\n self.ctx = context\n self.kwargs = kwargs\n self.gpu = kwargs.get(\"gpu\", \"vulkan\")\n self.cpu = kwargs.get(\"cpu\", None)\n self.fps = kwargs.get(\"fps\", 120)\n self.seed = kwargs.get(\"seed\", int(time.time()))\n self.headless = kwargs.get(\"headless\", False)\n self.name = kwargs.get(\"name\", \"T\u00f6lvera\")\n self.init_ti()\n self.init_ui()\n print(f\"[T\u00f6lvera.Taichi] Taichi initialised with: {vars(self)}\")\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.init_ti","title":"init_ti()
","text":"Initialise Taichi backend on selected architecture.
Source code in src/tolvera/taichi_.py
def init_ti(self):\n \"\"\"Initialise Taichi backend on selected architecture.\"\"\"\n if self.cpu:\n ti.init(arch=ti.cpu, random_seed=self.seed)\n self.gpu = None\n print(\"[T\u00f6lvera.Taichi] Running on CPU\")\n else:\n if self.gpu == \"vulkan\":\n ti.init(arch=ti.vulkan, random_seed=self.seed)\n elif self.gpu == \"metal\":\n ti.init(arch=ti.metal, random_seed=self.seed)\n elif self.gpu == \"cuda\":\n ti.init(arch=ti.cuda, random_seed=self.seed)\n else:\n print(f\"[T\u00f6lvera.Taichi] Invalid GPU: {self.gpu}\")\n return False\n print(f\"[T\u00f6lvera.Taichi] Running on {self.gpu}\")\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.init_ui","title":"init_ui()
","text":"Initialise Taichi UI window and canvas.
Source code in src/tolvera/taichi_.py
def init_ui(self):\n \"\"\"Initialise Taichi UI window and canvas.\"\"\"\n self.window = ti.ui.Window(\n self.name,\n (self.ctx.x, self.ctx.y),\n fps_limit=self.fps,\n show_window=not self.headless,\n )\n self.canvas = self.window.get_canvas()\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.show","title":"show(px)
","text":"Show Taichi canvas and show window.
Source code in src/tolvera/taichi_.py
def show(self, px):\n \"\"\"Show Taichi canvas and show window.\"\"\"\n self.canvas.set_image(px.px.rgba)\n if not self.headless:\n self.window.show()\n
"},{"location":"reference/tolvera/tolvera_/","title":"Tolvera","text":"Example This example demonstrates the basic usage of T\u00f6lvera. It will display a window with a black background.
from tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n @tv.render\n def _():\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
Example Here's an annotated version of the above example:
# First, we import Tolvera and run() from tolvera.\nfrom tolvera import Tolvera, run\n\n# Then, we define a main function which takes in keyword arguments \n# (kwargs) from the command line.\ndef main(**kwargs):\n # Inside the main function, we initialise a Tolvera instance \n # with the given keyword arguments.\n tv = Tolvera(**kwargs)\n\n # We use the render() decorator to render the pixels.\n # This function can be named anything. \n # It will run in a loop until the user exits the program.\n @tv.render\n def _():\n # render() must return Pixels. Often, these pixels will be \n # the pixels of the Tolvera instance, accessed with tv.px.\n return tv.px\n\n# Finally, we call run() with the main function as the argument.\nif __name__ == '__main__':\n run(main)\n
When Tolvera is run, messages will be printed to the console. These messages inform the user of the status of Tolvera, during initialisation, setup, and running.
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera","title":"Tolvera
","text":"Tolvera main class.
Attributes:
Name Type Description `name`
str
Name of T\u00f6lvera instance.
`ctx`
TolveraContext
Shared TolveraContext.
`speed`
float
Global timebase speed.
`pn`
int
Number of particles.
`sn`
int
Number of species.
`p_per_s`
int
Number of particles per species.
`substep`
int
Number of substeps per frame.
`iml`
int
Dict of IML instances via anguilla.
`cv`
int
computer vision integration via OpenCV.
`osc`
int
OSC via iipyper.
`ti`
int
Taichi (graphics backend).
Source code in src/tolvera/tolvera_.py
class Tolvera:\n \"\"\"Tolvera main class.\n\n Attributes:\n `name` (str): Name of T\u00f6lvera instance. \n `ctx` (TolveraContext): Shared TolveraContext.\n `speed` (float): Global timebase speed.\n `pn` (int): Number of particles.\n `sn` (int): Number of species.\n `p_per_s` (int): Number of particles per species.\n `substep` (int): Number of substeps per frame.\n `iml`: Dict of IML instances via anguilla.\n `cv`: computer vision integration via OpenCV.\n `osc`: OSC via iipyper.\n `ti`: Taichi (graphics backend).\n \"\"\"\n\n def __init__(self, **kwargs):\n \"\"\"\n Initialise and setup T\u00f6lvera with given keyword arguments.\n\n Args:\n name (str): Name of T\u00f6lvera instance. Defaults to \"T\u00f6lvera\".\n ctx (TolveraContext): TolveraContext to share. Defaults to None.\n see also kwargs for Tolvera.setup().\n \"\"\"\n self.kwargs = kwargs\n self.name = kwargs.get(\"name\", \"T\u00f6lvera\")\n self.name_clean = clean_name(self.name)\n if \"ctx\" not in kwargs:\n self.init_context(**kwargs)\n else:\n self.share_context(kwargs[\"ctx\"])\n self.setup(**kwargs)\n print(f\"[{self.name}] Initialisation and setup complete.\")\n\n def init_context(self, **kwargs):\n \"\"\"Initiliase T\u00f6lveraContext with given keyword arguments.\n\n Args:\n **kwargs: Keyword arguments for T\u00f6lveraContext.\n \"\"\"\n context = TolveraContext(**kwargs)\n self.share_context(context)\n\n def share_context(self, context):\n \"\"\"Share T\u00f6lveraContext with another T\u00f6lvera instance.\n\n Args:\n context: T\u00f6lveraContext to share.\n \"\"\"\n if len(context.get_names()) == 0:\n print(f\"[{self.name}] Sharing context '{context.name}'.\")\n else:\n print(\n f\"[{self.name}] Sharing context '{context.name}' with {context.get_names()}.\"\n )\n self.ctx = context\n self.x = context.x\n self.y = context.y\n self.ti = context.ti\n self.show = context.show\n self.canvas = context.canvas\n self.osc = context.osc\n self.s = context.s\n self.iml = context.iml\n self.render = context.render\n self.cleanup = context.cleanup\n self.cv = context.cv\n self.hands = context.hands\n\n def setup(self, **kwargs):\n \"\"\"\n Setup T\u00f6lvera with given keyword arguments.\n This can be called multiple throughout the lifetime of a T\u00f6lvera instance.\n\n Args:\n **kwargs: Keyword arguments for setup.\n speed (float): Global timebase speed. Defaults to 1.\n particles (int): Number of particles. Defaults to 1024.\n species (int): Number of species. Defaults to 4.\n substep (int): Number of substeps per frame. Defaults to 1.\n See also kwargs for Pixels, Species, Particles, and Vera.\n \"\"\"\n self._speed = kwargs.get(\"speed\", 1) # global timebase\n self.particles = kwargs.get(\"particles\", 1024)\n self.species = kwargs.get(\"species\", 4)\n if self.particles < self.species:\n self.species = self.particles\n self.pn = self.particles\n self.sn = self.species\n self.p_per_s = self.particles // self.species\n self.substep = kwargs.get(\"substep\", 1)\n self.px = Pixels(self, **kwargs)\n self._species = Species(self, **kwargs)\n self.p = Particles(self, **kwargs)\n self.speed(self._speed)\n self.v = Vera(self, **kwargs)\n if self.osc is not False:\n self.add_to_osc_map()\n if self.cv is not False:\n self.hands.px = self.px\n self.ctx.add(self)\n print(f\"[{self.name}] Setup complete.\")\n\n def randomise(self):\n \"\"\"\n Randomise particles, species, and Vera.\n \"\"\"\n self.p.randomise()\n self.s.species.randomise()\n self.v.randomise()\n\n def reset(self, **kwargs):\n \"\"\"\n Reset T\u00f6lvera with given keyword arguments.\n This will call setup() with given keyword arguments, but not init().\n\n Args:\n **kwargs: Keyword arguments for reset.\n \"\"\"\n print(f\"[{self.name}] Resetting self with kwargs={kwargs}...\")\n if kwargs is not None:\n self.kwargs = kwargs\n self.setup()\n\n def speed(self, speed: float = None):\n \"\"\"Set or get global timebase speed.\"\"\"\n if speed is not None:\n self._speed = speed\n self.p.speed(speed)\n return self._speed\n\n def add_to_osc_map(self):\n \"\"\"\n Add top-level T\u00f6lvera functions to OSCMap.\n \"\"\"\n setter_name = f\"{self.name_clean}_set\"\n getter_name = f\"{self.name_clean}_get\"\n self.osc.map.receive_args_inline(setter_name + \"_randomise\", self.randomise)\n # self.osc.map.receive_args_inline(setter_name+'_reset', self.reset) # TODO: kwargs?\n self.osc.map.receive_args_inline(\n setter_name + \"_particles_randomise\", self.p._randomise\n ) # TODO: move inside Particles\n\n @self.osc.map.receive_args(speed=(1, 0, 100), count=1)\n def tolvera_set_speed(speed: float):\n \"\"\"Set global timebase speed.\"\"\"\n self.speed(speed)\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.__init__","title":"__init__(**kwargs)
","text":"Initialise and setup T\u00f6lvera with given keyword arguments.
Parameters:
Name Type Description Default name
str
Name of T\u00f6lvera instance. Defaults to \"T\u00f6lvera\".
required ctx
TolveraContext
TolveraContext to share. Defaults to None.
required Source code in src/tolvera/tolvera_.py
def __init__(self, **kwargs):\n \"\"\"\n Initialise and setup T\u00f6lvera with given keyword arguments.\n\n Args:\n name (str): Name of T\u00f6lvera instance. Defaults to \"T\u00f6lvera\".\n ctx (TolveraContext): TolveraContext to share. Defaults to None.\n see also kwargs for Tolvera.setup().\n \"\"\"\n self.kwargs = kwargs\n self.name = kwargs.get(\"name\", \"T\u00f6lvera\")\n self.name_clean = clean_name(self.name)\n if \"ctx\" not in kwargs:\n self.init_context(**kwargs)\n else:\n self.share_context(kwargs[\"ctx\"])\n self.setup(**kwargs)\n print(f\"[{self.name}] Initialisation and setup complete.\")\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.add_to_osc_map","title":"add_to_osc_map()
","text":"Add top-level T\u00f6lvera functions to OSCMap.
Source code in src/tolvera/tolvera_.py
def add_to_osc_map(self):\n \"\"\"\n Add top-level T\u00f6lvera functions to OSCMap.\n \"\"\"\n setter_name = f\"{self.name_clean}_set\"\n getter_name = f\"{self.name_clean}_get\"\n self.osc.map.receive_args_inline(setter_name + \"_randomise\", self.randomise)\n # self.osc.map.receive_args_inline(setter_name+'_reset', self.reset) # TODO: kwargs?\n self.osc.map.receive_args_inline(\n setter_name + \"_particles_randomise\", self.p._randomise\n ) # TODO: move inside Particles\n\n @self.osc.map.receive_args(speed=(1, 0, 100), count=1)\n def tolvera_set_speed(speed: float):\n \"\"\"Set global timebase speed.\"\"\"\n self.speed(speed)\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.init_context","title":"init_context(**kwargs)
","text":"Initiliase T\u00f6lveraContext with given keyword arguments.
Parameters:
Name Type Description Default **kwargs
Keyword arguments for T\u00f6lveraContext.
{}
Source code in src/tolvera/tolvera_.py
def init_context(self, **kwargs):\n \"\"\"Initiliase T\u00f6lveraContext with given keyword arguments.\n\n Args:\n **kwargs: Keyword arguments for T\u00f6lveraContext.\n \"\"\"\n context = TolveraContext(**kwargs)\n self.share_context(context)\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.randomise","title":"randomise()
","text":"Randomise particles, species, and Vera.
Source code in src/tolvera/tolvera_.py
def randomise(self):\n \"\"\"\n Randomise particles, species, and Vera.\n \"\"\"\n self.p.randomise()\n self.s.species.randomise()\n self.v.randomise()\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.reset","title":"reset(**kwargs)
","text":"Reset T\u00f6lvera with given keyword arguments. This will call setup() with given keyword arguments, but not init().
Parameters:
Name Type Description Default **kwargs
Keyword arguments for reset.
{}
Source code in src/tolvera/tolvera_.py
def reset(self, **kwargs):\n \"\"\"\n Reset T\u00f6lvera with given keyword arguments.\n This will call setup() with given keyword arguments, but not init().\n\n Args:\n **kwargs: Keyword arguments for reset.\n \"\"\"\n print(f\"[{self.name}] Resetting self with kwargs={kwargs}...\")\n if kwargs is not None:\n self.kwargs = kwargs\n self.setup()\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.setup","title":"setup(**kwargs)
","text":"Setup T\u00f6lvera with given keyword arguments. This can be called multiple throughout the lifetime of a T\u00f6lvera instance.
Parameters:
Name Type Description Default **kwargs
Keyword arguments for setup. speed (float): Global timebase speed. Defaults to 1. particles (int): Number of particles. Defaults to 1024. species (int): Number of species. Defaults to 4. substep (int): Number of substeps per frame. Defaults to 1.
{}
Source code in src/tolvera/tolvera_.py
def setup(self, **kwargs):\n \"\"\"\n Setup T\u00f6lvera with given keyword arguments.\n This can be called multiple throughout the lifetime of a T\u00f6lvera instance.\n\n Args:\n **kwargs: Keyword arguments for setup.\n speed (float): Global timebase speed. Defaults to 1.\n particles (int): Number of particles. Defaults to 1024.\n species (int): Number of species. Defaults to 4.\n substep (int): Number of substeps per frame. Defaults to 1.\n See also kwargs for Pixels, Species, Particles, and Vera.\n \"\"\"\n self._speed = kwargs.get(\"speed\", 1) # global timebase\n self.particles = kwargs.get(\"particles\", 1024)\n self.species = kwargs.get(\"species\", 4)\n if self.particles < self.species:\n self.species = self.particles\n self.pn = self.particles\n self.sn = self.species\n self.p_per_s = self.particles // self.species\n self.substep = kwargs.get(\"substep\", 1)\n self.px = Pixels(self, **kwargs)\n self._species = Species(self, **kwargs)\n self.p = Particles(self, **kwargs)\n self.speed(self._speed)\n self.v = Vera(self, **kwargs)\n if self.osc is not False:\n self.add_to_osc_map()\n if self.cv is not False:\n self.hands.px = self.px\n self.ctx.add(self)\n print(f\"[{self.name}] Setup complete.\")\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.share_context","title":"share_context(context)
","text":"Share T\u00f6lveraContext with another T\u00f6lvera instance.
Parameters:
Name Type Description Default context
T\u00f6lveraContext to share.
required Source code in src/tolvera/tolvera_.py
def share_context(self, context):\n \"\"\"Share T\u00f6lveraContext with another T\u00f6lvera instance.\n\n Args:\n context: T\u00f6lveraContext to share.\n \"\"\"\n if len(context.get_names()) == 0:\n print(f\"[{self.name}] Sharing context '{context.name}'.\")\n else:\n print(\n f\"[{self.name}] Sharing context '{context.name}' with {context.get_names()}.\"\n )\n self.ctx = context\n self.x = context.x\n self.y = context.y\n self.ti = context.ti\n self.show = context.show\n self.canvas = context.canvas\n self.osc = context.osc\n self.s = context.s\n self.iml = context.iml\n self.render = context.render\n self.cleanup = context.cleanup\n self.cv = context.cv\n self.hands = context.hands\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.speed","title":"speed(speed=None)
","text":"Set or get global timebase speed.
Source code in src/tolvera/tolvera_.py
def speed(self, speed: float = None):\n \"\"\"Set or get global timebase speed.\"\"\"\n if speed is not None:\n self._speed = speed\n self.p.speed(speed)\n return self._speed\n
"},{"location":"reference/tolvera/utils/","title":"Utils","text":"Utility functions for Tolvera.
"},{"location":"reference/tolvera/utils/#tolvera.utils.CONSTS","title":"CONSTS
","text":"Dict of CONSTS that can be used in Taichi scope
Source code in src/tolvera/utils.py
class CONSTS:\n \"\"\"\n Dict of CONSTS that can be used in Taichi scope\n \"\"\"\n\n def __init__(self, dict: dict[str, (DataType, Any)]):\n self.struct = ti.types.struct(**{k: v[0] for k, v in dict.items()})\n self.consts = self.struct(**{k: v[1] for k, v in dict.items()})\n\n def __getattr__(self, name):\n try:\n return self.consts[name]\n except:\n raise AttributeError(f\"CONSTS has no attribute {name}\")\n\n def __getitem__(self, name):\n try:\n return self.consts[name]\n except:\n raise AttributeError(f\"CONSTS has no attribute {name}\")\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.dotdict","title":"dotdict
","text":" Bases: dict
dot.notation access to dictionary attributes
Source code in src/tolvera/utils.py
class dotdict(dict):\n \"\"\"dot.notation access to dictionary attributes\"\"\"\n __getattr__ = dict.get\n __setattr__ = dict.__setitem__\n __delattr__ = dict.__delitem__\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.create_and_validate_slice","title":"create_and_validate_slice(arg, target_array)
","text":"Creates and validates a slice object based on the target array.
Source code in src/tolvera/utils.py
def create_and_validate_slice(\n arg: Union[int, tuple[int, ...], slice], target_array: np.ndarray\n) -> slice:\n \"\"\"\n Creates and validates a slice object based on the target array.\n \"\"\"\n try:\n slice_obj = create_safe_slice(arg)\n if not validate_slice(slice_obj, target_array):\n raise ValueError(f\"Invalid slice: {slice_obj}\")\n return slice_obj\n except Exception as e:\n raise type(e)(f\"Error creating slice: {e}\")\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.create_ndslices","title":"create_ndslices(dims)
","text":"Create a multi-dimensional slice from a list of tuples.
Parameters:
Name Type Description Default dims
list[tuple]
A list of tuples containing the slice parameters for each dimension.
required Returns:
Type Description s_
np.s_: A multi-dimensional slice object.
Source code in src/tolvera/utils.py
def create_ndslices(dims: list[tuple]) -> np.s_:\n \"\"\"\n Create a multi-dimensional slice from a list of tuples.\n\n Args:\n dims (list[tuple]): A list of tuples containing the slice parameters for each dimension.\n\n Returns:\n np.s_: A multi-dimensional slice object.\n \"\"\"\n return np.s_[tuple(slice(*dim) if isinstance(dim, tuple) else dim for dim in dims)]\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.create_safe_slice","title":"create_safe_slice(arg)
","text":"Creates a slice object based on the input argument.
Parameters:
Name Type Description Default arg
(int, tuple, slice)
The argument for creating the slice. It can be an integer, a tuple with slice parameters, or a slice object itself.
required Returns:
Name Type Description slice
slice
A slice object created based on the provided argument.
Source code in src/tolvera/utils.py
def create_safe_slice(arg: Union[int, tuple[int, ...], slice]) -> slice:\n \"\"\"\n Creates a slice object based on the input argument.\n\n Args:\n arg (int, tuple, slice): The argument for creating the slice. It can be an integer,\n a tuple with slice parameters, or a slice object itself.\n\n Returns:\n slice: A slice object created based on the provided argument.\n \"\"\"\n try:\n if isinstance(arg, slice):\n return arg\n elif isinstance(arg, tuple):\n return slice(*arg)\n elif isinstance(arg, int):\n return slice(arg, arg + 1)\n else:\n raise TypeError(f\"Invalid slice type: {type(arg)} {arg}\")\n except Exception as e:\n raise type(e)(f\"[create_safe_slice] Error creating slice: {e}\")\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.flatten","title":"flatten(lst)
","text":"Flatten a nested list or return a non-nested list as is.
Source code in src/tolvera/utils.py
def flatten(lst):\n \"\"\"Flatten a nested list or return a non-nested list as is.\"\"\"\n if all(isinstance(el, list) for el in lst):\n return [item for sublist in lst for item in sublist]\n return lst\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.generic_slice","title":"generic_slice(array, slice_params)
","text":"Slices a NumPy array based on a tuple of slice parameters for each dimension.
Parameters:
Name Type Description Default array
ndarray
The array to be sliced.
required slice_params
tuple
A tuple where each item is either an integer, a tuple with slice parameters, or a slice object.
required Returns:
Name Type Description ndarray
ndarray
The sliced array.
Source code in src/tolvera/utils.py
def generic_slice(\n array: np.ndarray,\n slice_params: Union[\n tuple[Union[int, tuple[int, ...], slice], ...],\n Union[int, tuple[int, ...], slice],\n ],\n) -> np.ndarray:\n \"\"\"\n Slices a NumPy array based on a tuple of slice parameters for each dimension.\n\n Args:\n array (np.ndarray): The array to be sliced.\n slice_params (tuple): A tuple where each item is either an integer, a tuple with\n slice parameters, or a slice object.\n\n Returns:\n ndarray: The sliced array.\n \"\"\"\n if not isinstance(slice_params, tuple):\n slice_params = (slice_params,)\n slices = tuple(create_safe_slice(param) for param in slice_params)\n return array.__getitem__(slices)\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.time_function","title":"time_function(func, *args, **kwargs)
","text":"Time how long it takes to run a function and print the result
Source code in src/tolvera/utils.py
def time_function(func, *args, **kwargs):\n \"\"\"Time how long it takes to run a function and print the result\"\"\"\n start = time.time()\n ret = func(*args, **kwargs)\n end = time.time()\n print(f\"[Tolvera.utils] {func.__name__}() ran in {end-start:.4f}s\")\n if ret is not None:\n return (ret, end - start)\n return end - start\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.validate_json_path","title":"validate_json_path(path)
","text":"Validate a JSON file path. It uses validate_path for initial validation.
Parameters:
Name Type Description Default path
str
The JSON file path to be validated.
required Returns:
Name Type Description bool
bool
True if the path is a valid JSON file path, raises an exception otherwise.
Raises:
Type Description ValueError
If the path does not end with '.json'.
Source code in src/tolvera/utils.py
def validate_json_path(path: str) -> bool:\n \"\"\"\n Validate a JSON file path. It uses validate_path for initial validation.\n\n Args:\n path (str): The JSON file path to be validated.\n\n Returns:\n bool: True if the path is a valid JSON file path, raises an exception otherwise.\n\n Raises:\n ValueError: If the path does not end with '.json'.\n \"\"\"\n # Using validate_path for basic path validation\n validate_path(path)\n\n if not path.endswith(\".json\"):\n raise ValueError(\"Path should end with '.json'\")\n\n return True\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.validate_path","title":"validate_path(path)
","text":"Validate a path using os.path and pathlib.
Parameters:
Name Type Description Default path
str
The path to be validated.
required Returns:
Name Type Description bool
bool
True if the path is valid, raises an exception otherwise.
Raises:
Type Description TypeError
If the input is not a string.
FileNotFoundError
If the path does not exist.
PermissionError
If the path is not accessible.
Source code in src/tolvera/utils.py
def validate_path(path: str) -> bool:\n \"\"\"\n Validate a path using os.path and pathlib.\n\n Args:\n path (str): The path to be validated.\n\n Returns:\n bool: True if the path is valid, raises an exception otherwise.\n\n Raises:\n TypeError: If the input is not a string.\n FileNotFoundError: If the path does not exist.\n PermissionError: If the path is not accessible.\n \"\"\"\n if not isinstance(path, str):\n raise TypeError(f\"Expected a string for path, but received {type(path)}\")\n\n path_obj = Path(path)\n if not path_obj.is_file():\n raise FileNotFoundError(f\"The path {path} does not exist or is not a file\")\n\n if not os.access(path, os.R_OK):\n raise PermissionError(f\"The path {path} is not accessible\")\n\n return True\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.validate_slice","title":"validate_slice(slice_obj, target_array)
","text":"Validates if the given slice object is applicable to the target ndarray.
Parameters:
Name Type Description Default slice_obj
tuple[slice]
A tuple containing slice objects for each dimension.
required target_array
ndarray
The array to be sliced.
required Returns:
Name Type Description bool
bool
True if the slice is valid for the given array, False otherwise.
Source code in src/tolvera/utils.py
def validate_slice(slice_obj: tuple[slice], target_array: np.ndarray) -> bool:\n \"\"\"\n Validates if the given slice object is applicable to the target ndarray.\n\n Args:\n slice_obj (tuple[slice]): A tuple containing slice objects for each dimension.\n target_array (np.ndarray): The array to be sliced.\n\n Returns:\n bool: True if the slice is valid for the given array, False otherwise.\n \"\"\"\n if len(slice_obj) != target_array.ndim:\n return False\n\n for sl, size in zip(slice_obj, target_array.shape):\n # Check if slice start and stop are within the dimension size\n start, stop, _ = sl.indices(size)\n if not (0 <= start < size and (0 <= stop <= size or stop == -1)):\n return False\n return True\n
"},{"location":"reference/tolvera/osc/maxmsp/","title":"Maxmsp","text":""},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher","title":"MaxPatcher
","text":"TODO: copy-paste using stdout TODO: add scale objects before send and after receive TODO: add default values via loadbangs TODO: move udpsend/udpreceive to the top left TODO: dict of object ids TODO: add abstraction i/o messages e.g. param names, state save/load/dumps
Source code in src/tolvera/osc/maxmsp.py
class MaxPatcher:\n \"\"\"\n TODO: copy-paste using stdout\n TODO: add scale objects before send and after receive\n TODO: add default values via loadbangs\n TODO: move udpsend/udpreceive to the top left\n TODO: dict of object ids\n TODO: add abstraction i/o messages e.g. param names, state save/load/dumps\n \"\"\"\n\n def __init__(\n self,\n osc,\n client_name=\"client\",\n filepath=\"osc_controls\",\n x=0.0,\n y=0.0,\n w=1600.0,\n h=900.0,\n v=\"8.5.4\",\n ) -> None:\n self.patch = {\n \"patcher\": {\n \"fileversion\": 1,\n \"appversion\": {\n \"major\": v[0],\n \"minor\": v[2],\n \"revision\": v[4],\n \"architecture\": \"x64\",\n \"modernui\": 1,\n },\n \"classnamespace\": \"box\",\n \"rect\": [x, y, w, h],\n \"bglocked\": 0,\n \"openinpresentation\": 0,\n \"default_fontsize\": 12.0,\n \"default_fontface\": 0,\n \"default_fontname\": \"Arial\",\n \"gridonopen\": 1,\n \"gridsize\": [15.0, 15.0],\n \"gridsnaponopen\": 1,\n \"objectsnaponopen\": 1,\n \"statusbarvisible\": 2,\n \"toolbarvisible\": 1,\n \"lefttoolbarpinned\": 0,\n \"toptoolbarpinned\": 0,\n \"righttoolbarpinned\": 0,\n \"bottomtoolbarpinned\": 0,\n \"toolbars_unpinned_last_save\": 0,\n \"tallnewobj\": 0,\n \"boxanimatetime\": 200,\n \"enablehscroll\": 1,\n \"enablevscroll\": 1,\n \"devicewidth\": 0.0,\n \"description\": \"\",\n \"digest\": \"\",\n \"tags\": \"\",\n \"style\": \"\",\n \"subpatcher_template\": \"\",\n \"assistshowspatchername\": 0,\n \"boxes\": [],\n \"lines\": [],\n \"dependency_cache\": [],\n \"autosave\": 0,\n }\n }\n self.types = {\n \"print\": \"print\",\n \"message\": \"message\",\n \"object\": \"newobj\",\n \"comment\": \"comment\",\n \"slider\": \"slider\",\n \"float\": \"flonum\",\n \"int\": \"number\",\n \"bang\": \"button\",\n }\n self.osc = osc\n self.client_name = client_name\n self.client_address, self.client_port = self.osc.client_names[self.client_name]\n self.filepath = filepath\n self.init()\n\n def init(self):\n self.w = 5.5 # default width (scaling factor)\n self.h = 22.0 # default height (pixels)\n self.s_x, self.s_y = 30, 125 # insertion point\n self.r_x, self.r_y = 30, 575 # insertion point\n self.patcher_ids = {}\n self.patcher_ids[\"send_id\"] = self.add_osc_send(\n self.osc.host, self.osc.port, self.s_x, 30, print_label=\"sent\"\n )\n self.patcher_ids[\"receive_id\"] = self.add_osc_receive(\n self.client_port, self.s_x + 150, 30, print_label=\"received\"\n )\n self.add_comment(\"Max \u2192 Python\", self.s_x, self.s_y, 24)\n self.add_comment(\"Python \u2192 Max\", self.r_x, self.r_y, 24)\n self.s_y += 50\n self.r_y += 50\n self.save(self.filepath)\n\n def add_box(self, box_type, inlets, outlets, x, y, w, h=None):\n if h is None:\n h = self.h\n box_id, box = self.create_box(box_type, inlets, outlets, x, y, w, h)\n return self._add_box(box)\n\n def _add_box(self, box):\n self.patch[\"patcher\"][\"boxes\"].append(box)\n return self.id_from_str(box[\"box\"][\"id\"])\n\n def create_box(self, box_type, inlets, outlets, x, y, w, h=None):\n if h is None:\n h = self.h\n box_id = len(self.patch[\"patcher\"][\"boxes\"]) + 1\n box = {\n \"box\": {\n \"id\": \"obj-\" + str(box_id),\n \"maxclass\": self.types[box_type],\n \"numinlets\": inlets,\n \"numoutlets\": outlets,\n \"patching_rect\": [x, y, w, h],\n }\n }\n if outlets > 0:\n if outlets == 1:\n box[\"box\"][\"outlettype\"] = [\"\"]\n match box_type:\n case \"int\" | \"float\" | \"bang\":\n box[\"box\"][\"outlettype\"] = [\"\", \"bang\"]\n return box_id, box\n\n def add_object(self, text, inlets, outlets, x, y):\n box_id, box = self.create_box(\n \"object\", inlets, outlets, x, y, len(text) * self.w\n )\n box[\"box\"][\"text\"] = text\n self._add_box(box)\n return box_id\n\n def add_message(self, text, x, y):\n box_id, box = self.create_box(\"message\", 2, 1, x, y, len(text) * self.w)\n box[\"box\"][\"text\"] = text\n self._add_box(box)\n return box_id\n\n def add_comment(self, text, x, y, fontsize=12):\n box_id, box = self.create_box(\"comment\", 0, 0, x, y, len(text) * self.w)\n box[\"box\"][\"text\"] = text\n box[\"box\"][\"fontsize\"] = fontsize\n self._add_box(box)\n return box_id\n\n def add_bang(self, x, y):\n box_id, box = self.create_box(\"bang\", 1, 1, x, y, 20.0)\n self._add_box(box)\n return box_id\n\n def add_slider(self, x, y, min_val, size, float=False):\n box_id, box = self.create_box(\"slider\", 1, 1, x, y, 20.0, 140.0)\n if float:\n box[\"box\"][\"floatoutput\"] = 1\n box[\"box\"][\"min\"] = min_val\n box[\"box\"][\"size\"] = size\n self._add_box(box)\n return box_id\n\n def connect(self, src, src_outlet, dst, dst_inlet):\n patchline = {\n \"patchline\": {\n \"destination\": [\"obj-\" + str(dst), dst_inlet],\n \"source\": [\"obj-\" + str(src), src_outlet],\n }\n }\n self.patch[\"patcher\"][\"lines\"].append(patchline)\n return patchline\n\n def save(self, name):\n with open(name + \".maxpat\", \"w\") as f:\n f.write(json.dumps(self.patch, indent=2))\n\n def load(self, name):\n with open(name + \".maxpat\", \"r\") as f:\n self.patch = json.loads(f.read())\n\n def get_box_by_id(self, id):\n for box in self.patch[\"patcher\"][\"boxes\"]:\n if self.id_from_str(box[\"box\"][\"id\"]) == id:\n return box\n return None\n\n def str_from_id(self, id):\n return \"obj-\" + str(id)\n\n def id_from_str(self, obj_str):\n return int(obj_str[4:])\n\n def add_osc_send(self, ip, port, x, y, print=True, print_label=None):\n box_id_0 = self.add_object(\"r send\", 0, 1, x, y)\n box_id = self.add_object(\"udpsend \" + ip + \" \" + str(port), 1, 0, x, y + 25)\n if print:\n text = \"print\" if print_label is None else \"print \" + print_label\n print_id = self.add_object(text, 1, 0, x + 50, y)\n self.connect(box_id_0, 0, box_id, 0)\n self.connect(box_id_0, 0, print_id, 0)\n return box_id_0\n return box_id\n\n def add_osc_receive(self, port, x, y, print=True, print_label=None):\n box_id_0 = self.add_object(\"s receive\", 0, 1, x, y + 25)\n box_id = self.add_object(\"udpreceive \" + str(port), 1, 1, x, y)\n if print:\n text = \"print\" if print_label is None else \"print \" + print_label\n print_id = self.add_object(text, 1, 0, x + 60, y + 25)\n self.connect(box_id, 0, print_id, 0)\n self.connect(box_id, 0, box_id_0, 0)\n return box_id_0\n return box_id\n\n def add_osc_route(self, port, x, y, print=True, print_label=None):\n \"\"\"\n [route path]\n [s name] [print]\n [unpack] ?\n [r name]\n \"\"\"\n pass\n\n def add_sliders(self, x, y, sliders):\n \"\"\"\n sliders = [\n { 'label': 'x', data: 'float', min_val: 0.0, size: 0.0 },\n ]\n\n [slider] ...\n |\n [number] ...\n \"\"\"\n slider_ids = []\n float_ids = []\n y_off = 0\n for i, s in enumerate(sliders):\n y_off = 0\n x_i = x + (i * 52.0)\n y_off += self.h\n slider_id = self.add_slider(\n x_i, y + y_off, s[\"min_val\"], s[\"size\"], float=s[\"data\"] == \"float\"\n )\n y_off += 150\n float_id = self.add_box(\"float\", 1, 2, x_i, y + y_off, 50)\n slider_ids.append(slider_id)\n float_ids.append(float_id)\n return slider_ids, float_ids, y_off\n\n def add_param_comments(self, x, y, params):\n comment_ids = []\n y_off = 0\n for i, p in enumerate(params):\n y_off = 0\n x_i = x + (i * 52.0)\n p_max = (\n p[\"min_val\"] + p[\"size\"]\n if p[\"data\"] == \"float\"\n else p[\"min_val\"] + p[\"size\"] - 1\n )\n comment_id1 = self.add_comment(f'{p[\"label\"]}', x_i, y)\n y_off += 15\n comment_id2 = self.add_comment(\n f'{p[\"data\"][0]} {p[\"min_val\"]}-{p_max}', x_i, y + y_off\n )\n comment_ids.append(comment_id1)\n comment_ids.append(comment_id2)\n return comment_ids, y_off\n\n def add_osc_send_msg(self, x, y, path):\n msg_id = self.add_message(path, x, y + 225 + self.h)\n send_id = self.add_object(\"s send\", 1, 0, x, y + 250 + self.h)\n self.connect(msg_id, 0, send_id, 0)\n return msg_id\n\n def add_osc_receive_msg(self, x, y, path):\n receive_id = self.add_object(\"r receive\", 0, 1, x, y + 225 + self.h)\n msg_id = self.add_message(path, x, y + 250 + self.h)\n self.connect(receive_id, 0, msg_id, 0)\n return msg_id\n\n def add_osc_send_with_controls(self, x, y, path, parameters):\n # TODO: add default param value and a loadbang\n \"\"\"\n [comment path]\n [comment args]\n [r path_arg_name]\n sliders\n | |\n [pak $1 $2 $3 ...]\n |\n [msg /path $1 $2 $3 ...]\n |\n [s send]\n \"\"\"\n y_off = 0\n # [comment path]\n path_comment_id = self.add_comment(path, x, y + y_off)\n y_off += 15\n param_comment_ids, _y_off = self.add_param_comments(x, y + y_off, parameters)\n\n # [r path_arg_name]\n y_off += 35\n receive_ids = [\n self.add_object(\n \"r \" + path.replace(\"/\", \"_\")[1:] + \"_\" + p[\"label\"][0:3],\n 1,\n 0,\n x + i * 52.0,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n y_off += 30\n\n # sliders\n slider_ids, slider_float_ids, _y_off = self.add_sliders(\n x, y + y_off, parameters\n )\n y_off += _y_off + 25\n # [pak $1 $2 $3 ...]\n pack_id = self.add_object(\n \"pak \" + self._pack_args(parameters), len(parameters) + 1, 1, x, y + y_off\n )\n pack_width = self.get_box_by_id(pack_id)[\"box\"][\"patching_rect\"][2]\n # [msg /path $1 $2 $3 ...]\n y_off += 25\n msg_id = self.add_message(path + \" \" + self._msg_args(parameters), x, y + y_off)\n # [s send]\n y_off += 25\n send_id = self.add_object(\"s send\", 1, 0, x, y + y_off)\n # connections\n [\n self.connect(receive_ids[i], 0, slider_ids[i], 0)\n for i in range(len(parameters))\n ]\n [\n self.connect(slider_ids[i], 0, slider_float_ids[i], 0)\n for i in range(len(parameters))\n ]\n [\n self.connect(slider_float_ids[i], 0, pack_id, i)\n for i in range(len(parameters))\n ]\n self.connect(pack_id, 0, msg_id, 0)\n self.connect(msg_id, 0, send_id, 0)\n return slider_ids, pack_id, msg_id\n\n def add_osc_receive_with_controls(self, x, y, path, parameters):\n # TODO: add default param value and a loadbang\n \"\"\"\n [comment path]\n [r receive]\n |\n [route /path]\n | |\n [unpack f f f ...] [print /path]\n |\n [slider] ...\n |\n [number] ...\n |\n [s arg_name]\n [comment path_arg_name]\n [comment type min-max]\n \"\"\"\n # [comment path]\n y_off = 0\n path_comment_id = self.add_comment(path, x, y + y_off)\n\n # [r receive]\n y_off += 25\n receive_id = self.add_object(\"r receive\", 0, 1, x, y + y_off)\n\n # [route /path]\n y_off += 25\n route_id = self.add_object(\"route \" + path, 1, 1, x, y + y_off)\n\n # [unpack f f f ...] [print /path]\n y_off += 25\n unpack_id = self.add_object(\n \"unpack \" + self._pack_args(parameters),\n len(parameters) + 1,\n 1,\n x,\n y + y_off,\n )\n unpack_width = self.get_box_by_id(unpack_id)[\"box\"][\"patching_rect\"][2]\n print_id = self.add_object(\n \"print \" + path, 1, 0, x + unpack_width + 10, y + y_off\n )\n\n # sliders\n y_off += 10\n slider_ids, float_ids, _y_off = self.add_sliders(x, y + y_off, parameters)\n\n # [s arg_name]\n y_off += _y_off + 25\n send_ids = [\n self.add_object(\n \"s \" + path.replace(\"/\", \"_\")[1:] + \"_\" + p[\"label\"][0:3],\n 1,\n 0,\n x + i * 52.0,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n\n # [comment params]\n y_off += 50\n param_comment_ids, _y_off = self.add_param_comments(x, y + y_off, parameters)\n\n # connections\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, unpack_id, 0)\n self.connect(route_id, 0, print_id, 0)\n [self.connect(unpack_id, i, slider_ids[i], 0) for i in range(len(parameters))]\n [\n self.connect(slider_ids[i], 0, float_ids[i], 0)\n for i in range(len(parameters))\n ]\n [self.connect(float_ids[i], 0, send_ids[i], 0) for i in range(len(parameters))]\n\n return slider_ids, unpack_id\n\n def add_send_args_func(self, f):\n hints = typing.get_type_hints(f[\"f\"])[\"return\"].__args__\n f_p = f[\"params\"]\n params = []\n if len(f_p) == 0:\n self.add_osc_receive_msg(self.r_x, self.r_y, f[\"address\"])\n else:\n for i, p in enumerate(f_p):\n p_def, p_min, p_max = f_p[p][0], f_p[p][1], f_p[p][2]\n params.append(\n {\n \"label\": p,\n \"data\": hints[i].__name__,\n \"min_val\": p_min,\n \"size\": p_max - p_min,\n }\n )\n self.add_osc_receive_with_controls(self.r_x, self.r_y, f[\"address\"], params)\n self.r_x += max(len(params) * 52.0 + 100.0, len(f[\"address\"]) * 6.0 + 25.0)\n self.save(self.filepath)\n\n def add_send_list_func(self, f):\n raise NotImplementedError(\"add_send_list_func not implemented yet\")\n\n def add_receive_args_func(self, f):\n hints = typing.get_type_hints(f[\"f\"])\n f_p = f[\"params\"]\n params = []\n if len(f_p) == 0:\n self.add_osc_send_msg(self.s_x, self.s_y, f[\"address\"])\n else:\n for p in f_p:\n p_def, p_min, p_max = f_p[p][0], f_p[p][1], f_p[p][2]\n params.append(\n {\n \"label\": p,\n \"data\": hints[p].__name__,\n \"min_val\": p_min,\n \"size\": p_max - p_min,\n }\n )\n self.add_osc_send_with_controls(self.s_x, self.s_y, f[\"address\"], params)\n self.s_x += max(len(params) * 52.0 + 100.0, len(f[\"address\"]) * 6.0 + 25.0)\n self.save(self.filepath)\n\n def add_receive_list_func(self, f):\n raise NotImplementedError(\"add_receive_list_func not implemented yet\")\n\n def _msg_args(self, args):\n return \" \".join([\"$\" + str(i + 1) for i in range(len(args))])\n\n def _pack_args(self, args):\n arg_types = []\n for a in args:\n match a[\"data\"]:\n case \"int\":\n arg_types.append(\"i\")\n case \"float\":\n arg_types.append(\"f\")\n case \"string\":\n arg_types.append(\"s\")\n return \" \".join(arg_types)\n
"},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher.add_osc_receive_with_controls","title":"add_osc_receive_with_controls(x, y, path, parameters)
","text":"[comment path] [r receive] | [route /path] | | [unpack f f f ...] [print /path] | [slider] ... | [number] ... | [s arg_name] [comment path_arg_name] [comment type min-max]
Source code in src/tolvera/osc/maxmsp.py
def add_osc_receive_with_controls(self, x, y, path, parameters):\n # TODO: add default param value and a loadbang\n \"\"\"\n [comment path]\n [r receive]\n |\n [route /path]\n | |\n [unpack f f f ...] [print /path]\n |\n [slider] ...\n |\n [number] ...\n |\n [s arg_name]\n [comment path_arg_name]\n [comment type min-max]\n \"\"\"\n # [comment path]\n y_off = 0\n path_comment_id = self.add_comment(path, x, y + y_off)\n\n # [r receive]\n y_off += 25\n receive_id = self.add_object(\"r receive\", 0, 1, x, y + y_off)\n\n # [route /path]\n y_off += 25\n route_id = self.add_object(\"route \" + path, 1, 1, x, y + y_off)\n\n # [unpack f f f ...] [print /path]\n y_off += 25\n unpack_id = self.add_object(\n \"unpack \" + self._pack_args(parameters),\n len(parameters) + 1,\n 1,\n x,\n y + y_off,\n )\n unpack_width = self.get_box_by_id(unpack_id)[\"box\"][\"patching_rect\"][2]\n print_id = self.add_object(\n \"print \" + path, 1, 0, x + unpack_width + 10, y + y_off\n )\n\n # sliders\n y_off += 10\n slider_ids, float_ids, _y_off = self.add_sliders(x, y + y_off, parameters)\n\n # [s arg_name]\n y_off += _y_off + 25\n send_ids = [\n self.add_object(\n \"s \" + path.replace(\"/\", \"_\")[1:] + \"_\" + p[\"label\"][0:3],\n 1,\n 0,\n x + i * 52.0,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n\n # [comment params]\n y_off += 50\n param_comment_ids, _y_off = self.add_param_comments(x, y + y_off, parameters)\n\n # connections\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, unpack_id, 0)\n self.connect(route_id, 0, print_id, 0)\n [self.connect(unpack_id, i, slider_ids[i], 0) for i in range(len(parameters))]\n [\n self.connect(slider_ids[i], 0, float_ids[i], 0)\n for i in range(len(parameters))\n ]\n [self.connect(float_ids[i], 0, send_ids[i], 0) for i in range(len(parameters))]\n\n return slider_ids, unpack_id\n
"},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher.add_osc_route","title":"add_osc_route(port, x, y, print=True, print_label=None)
","text":"[route path] [s name] print ? [r name]
Source code in src/tolvera/osc/maxmsp.py
def add_osc_route(self, port, x, y, print=True, print_label=None):\n \"\"\"\n [route path]\n [s name] [print]\n [unpack] ?\n [r name]\n \"\"\"\n pass\n
"},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher.add_osc_send_with_controls","title":"add_osc_send_with_controls(x, y, path, parameters)
","text":"[comment path] [comment args] [r path_arg_name] sliders | | [pak $1 $2 $3 ...] | [msg /path $1 $2 $3 ...] | [s send]
Source code in src/tolvera/osc/maxmsp.py
def add_osc_send_with_controls(self, x, y, path, parameters):\n # TODO: add default param value and a loadbang\n \"\"\"\n [comment path]\n [comment args]\n [r path_arg_name]\n sliders\n | |\n [pak $1 $2 $3 ...]\n |\n [msg /path $1 $2 $3 ...]\n |\n [s send]\n \"\"\"\n y_off = 0\n # [comment path]\n path_comment_id = self.add_comment(path, x, y + y_off)\n y_off += 15\n param_comment_ids, _y_off = self.add_param_comments(x, y + y_off, parameters)\n\n # [r path_arg_name]\n y_off += 35\n receive_ids = [\n self.add_object(\n \"r \" + path.replace(\"/\", \"_\")[1:] + \"_\" + p[\"label\"][0:3],\n 1,\n 0,\n x + i * 52.0,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n y_off += 30\n\n # sliders\n slider_ids, slider_float_ids, _y_off = self.add_sliders(\n x, y + y_off, parameters\n )\n y_off += _y_off + 25\n # [pak $1 $2 $3 ...]\n pack_id = self.add_object(\n \"pak \" + self._pack_args(parameters), len(parameters) + 1, 1, x, y + y_off\n )\n pack_width = self.get_box_by_id(pack_id)[\"box\"][\"patching_rect\"][2]\n # [msg /path $1 $2 $3 ...]\n y_off += 25\n msg_id = self.add_message(path + \" \" + self._msg_args(parameters), x, y + y_off)\n # [s send]\n y_off += 25\n send_id = self.add_object(\"s send\", 1, 0, x, y + y_off)\n # connections\n [\n self.connect(receive_ids[i], 0, slider_ids[i], 0)\n for i in range(len(parameters))\n ]\n [\n self.connect(slider_ids[i], 0, slider_float_ids[i], 0)\n for i in range(len(parameters))\n ]\n [\n self.connect(slider_float_ids[i], 0, pack_id, i)\n for i in range(len(parameters))\n ]\n self.connect(pack_id, 0, msg_id, 0)\n self.connect(msg_id, 0, send_id, 0)\n return slider_ids, pack_id, msg_id\n
"},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher.add_sliders","title":"add_sliders(x, y, sliders)
","text":"sliders = [ { 'label': 'x', data: 'float', min_val: 0.0, size: 0.0 }, ]
[slider] ... | [number] ...
Source code in src/tolvera/osc/maxmsp.py
def add_sliders(self, x, y, sliders):\n \"\"\"\n sliders = [\n { 'label': 'x', data: 'float', min_val: 0.0, size: 0.0 },\n ]\n\n [slider] ...\n |\n [number] ...\n \"\"\"\n slider_ids = []\n float_ids = []\n y_off = 0\n for i, s in enumerate(sliders):\n y_off = 0\n x_i = x + (i * 52.0)\n y_off += self.h\n slider_id = self.add_slider(\n x_i, y + y_off, s[\"min_val\"], s[\"size\"], float=s[\"data\"] == \"float\"\n )\n y_off += 150\n float_id = self.add_box(\"float\", 1, 2, x_i, y + y_off, 50)\n slider_ids.append(slider_id)\n float_ids.append(float_id)\n return slider_ids, float_ids, y_off\n
"},{"location":"reference/tolvera/osc/osc/","title":"Osc","text":""},{"location":"reference/tolvera/osc/oscmap/","title":"Oscmap","text":""},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap","title":"OSCMap
","text":"OSCMap maps OSC messages to functions It creates a Max/MSP patcher that can be used to control the OSCMap It uses OSCSendUpdater and OSCReceiveUpdater to decouple incoming messages
Source code in src/tolvera/osc/oscmap.py
class OSCMap:\n \"\"\"\n OSCMap maps OSC messages to functions\n It creates a Max/MSP patcher that can be used to control the OSCMap\n It uses OSCSendUpdater and OSCReceiveUpdater to decouple incoming messages\n \"\"\"\n\n def __init__(\n self,\n osc: iiOSC,\n client_name=\"client\",\n patch_type=\"Max\", # | \"Pd\"\n patch_filepath=\"osc_controls\",\n create_patch=True,\n pd_net_or_udp=\"udp\",\n pd_bela=False,\n export=None, # 'JSON' | 'XML' | True\n ) -> None:\n self.osc = osc\n self.client_name = client_name\n self.client_address, self.client_port = self.osc.client_names[self.client_name]\n self.dict = {\"send\": {}, \"receive\": {}}\n self.create_patch = create_patch\n self.patch_filepath = patch_filepath\n self.patch_type = patch_type\n if create_patch is True:\n self.init_patcher(patch_type, patch_filepath, pd_net_or_udp, pd_bela)\n if export is not None:\n assert (\n export == \"JSON\" or export == \"XML\" or export == True\n ), \"export must be 'JSON', 'XML' or True\"\n self.export = export\n\n def init_patcher(self, patch_type, patch_filepath, pd_net_or_udp, pd_bela):\n # create self.patch_dir if it doesn't exist\n self.patch_dir = \"pd\" if patch_type == \"Pd\" else \"max\"\n if not os.path.exists(self.patch_dir):\n print(f\"Creating {self.patch_dir} directory...\")\n os.makedirs(self.patch_dir)\n self.patch_appendix = \"_local\" if self.osc.host == \"127.0.0.1\" else \"_remote\"\n self.patch_filepath = (\n self.patch_dir + \"/\" + patch_filepath + self.patch_appendix\n )\n if patch_type == \"Max\":\n self.patcher = MaxPatcher(self.osc, self.client_name, self.patch_filepath)\n elif patch_type == \"Pd\":\n if pd_bela is True:\n self.patcher = PdPatcher(\n self.osc,\n self.client_name,\n self.patch_filepath,\n net_or_udp=pd_net_or_udp,\n bela=True,\n )\n else:\n self.patcher = PdPatcher(\n self.osc,\n self.client_name,\n self.patch_filepath,\n net_or_udp=pd_net_or_udp,\n )\n else:\n assert False, \"`patch_type` must be 'Max' or 'Pd'\"\n\n def add(self, **kwargs):\n print(\n \"DeprecationError: OSCMap.add() has been split into separate functions: use `send_args`, `send_list`, `receive_args` or `receive_list` instead!\"\n )\n exit()\n\n def map_func_to_dict(self, func, kwargs):\n if \"name\" not in kwargs:\n n = func.__name__\n address = \"/\" + n.replace(\"_\", \"/\")\n else:\n if isinstance(kwargs[\"name\"], str):\n n = kwargs[\"name\"]\n address = \"/\" + kwargs[\"name\"].replace(\"_\", \"/\")\n else:\n raise TypeError(\n f\"OSC func name must be string, found {str(type(kwargs['name']))}\"\n )\n # TODO: Move this into specific send/receive functions\n params = {\n k: v\n for k, v in kwargs.items()\n if k != \"count\" and k != \"send_mode\" and k != \"length\" and k != \"name\"\n }\n # TODO: turn params into dict with type hints (see export_dict)\n hints = get_type_hints(func)\n f = {\"f\": func, \"name\": n, \"address\": address, \"params\": params, \"hints\": hints}\n return f\n\n \"\"\"\n send args\n \"\"\"\n\n def send_args(self, **kwargs):\n def decorator(func):\n def wrapper(*args):\n self.add_send_args(func, kwargs)\n return func()\n\n default_args = [\n kwargs[a][0]\n for a in kwargs\n if a != \"count\" and a != \"send_mode\" and a != \"name\"\n ]\n wrapper(*default_args)\n return wrapper\n\n return decorator\n\n def add_send_args(self, func, kwargs):\n self.add_send_args_to_osc_map(func, kwargs)\n if self.create_patch is True:\n self.add_send_args_to_patcher(func)\n\n def add_send_args_to_osc_map(self, func, kwargs):\n f = self.map_func_to_dict(func, kwargs)\n if kwargs[\"send_mode\"] == \"broadcast\":\n f[\"updater\"] = OSCSendUpdater(\n self.osc,\n f[\"address\"],\n f=func,\n count=kwargs[\"count\"],\n client=self.client_name,\n )\n else:\n f[\"sender\"] = OSCSend(\n self.osc,\n f[\"address\"],\n f=func,\n count=kwargs[\"count\"],\n client=self.client_name,\n )\n f[\"type\"] = \"args\"\n self.dict[\"send\"][f[\"name\"]] = f\n if self.export is not None:\n self.export_dict()\n\n def add_send_args_to_patcher(self, func):\n f = self.dict[\"send\"][func.__name__]\n self.patcher.send_args_func(f)\n\n \"\"\"\n send list\n \"\"\"\n\n def send_list(self, **kwargs):\n def decorator(func):\n def wrapper(*args):\n self.add_send_list(func, kwargs)\n # TODO: This was originally here to sync defaults with client\n # but it causes init order isses in IMLFun2OSC.update\n # return func()\n\n default_arg = [\n kwargs[a][0]\n for a in kwargs\n if a != \"count\" and a != \"send_mode\" and a != \"length\" and a != \"name\"\n ]\n wrapper(default_arg)\n return wrapper\n\n return decorator\n\n def add_send_list(self, func, kwargs):\n self.add_send_list_to_osc_map(func, kwargs)\n if self.create_patch is True:\n self.add_send_list_to_patcher(func)\n\n def add_send_list_to_osc_map(self, func, kwargs):\n f = self.map_func_to_dict(func, kwargs)\n # TODO: Hack for send_list_inline which doesn't have a return type hint\n if \"return\" in f[\"hints\"]:\n hint = f[\"hints\"][\"return\"]\n else:\n hint = list[float]\n assert hint == list[float], \"send_list can only send list[float], found \" + str(\n hint\n )\n if kwargs[\"send_mode\"] == \"broadcast\":\n f[\"updater\"] = OSCSendUpdater(\n self.osc,\n f[\"address\"],\n f=func,\n count=kwargs[\"count\"],\n client=self.client_name,\n )\n else:\n f[\"sender\"] = OSCSend(\n self.osc,\n f[\"address\"],\n f=func,\n count=kwargs[\"count\"],\n client=self.client_name,\n )\n f[\"type\"] = \"list\"\n f[\"length\"] = kwargs[\"length\"]\n self.dict[\"send\"][f[\"name\"]] = f\n if self.export is not None:\n self.export_dict()\n\n def add_send_list_to_patcher(self, func):\n f = self.dict[\"send\"][func.__name__]\n self.patcher.send_list_func(f)\n\n def send_list_inline(self, name: str, sender_func, length: int, send_mode=\"broadcast\", count=1, **kwargs):\n kwargs = {**kwargs, **{\"name\": name, \"length\": length, \"send_mode\": send_mode, \"count\": count}}\n self.send_list(**kwargs)(sender_func)\n\n \"\"\"\n send kwargs\n \"\"\"\n\n def send_kwargs(self, **kwargs):\n raise NotImplementedError(\"send_kwargs not implemented yet\")\n\n \"\"\"\n receive args\n \"\"\"\n\n def receive_args(self, **kwargs):\n def decorator(func):\n def wrapper(*args):\n self.add_receive_args(func, kwargs)\n return func(*args)\n\n default_args = [\n kwargs[a][0] for a in kwargs if a != \"count\" and a != \"name\"\n ]\n wrapper(*default_args)\n return wrapper\n\n return decorator\n\n def add_receive_args(self, func, kwargs):\n f = self.add_receive_args_to_osc_map(func, kwargs)\n if self.create_patch is True:\n self.add_receive_args_to_patcher(f)\n\n def add_receive_args_to_osc_map(self, func, kwargs):\n f = self.map_func_to_dict(func, kwargs)\n f[\"updater\"] = OSCReceiveUpdater(\n self.osc, f[\"address\"], f=func, count=kwargs[\"count\"]\n )\n f[\"type\"] = \"args\"\n self.dict[\"receive\"][f[\"name\"]] = f\n return f\n\n def add_receive_args_to_patcher(self, func):\n f = self.dict[\"receive\"][func[\"name\"]]\n self.patcher.receive_args_func(f)\n\n def receive_args_inline(self, name: str, receiver_func, **kwargs):\n kwargs = {**kwargs, **{\"count\": 1, \"name\": name}}\n self.receive_args(**kwargs)(receiver_func)\n\n \"\"\"\n receive list\n \"\"\"\n\n def receive_list(self, **kwargs):\n def decorator(func):\n def wrapper(*args):\n self.add_receive_list(func, kwargs)\n # TODO: This was originally here to sync defaults with client\n # but it causes init order isses in IMLOSC2Vec.init\n # return func(*args)\n\n # TODO: This probably shouldn't be here...\n randomised_list = self.randomise_list(\n kwargs[\"length\"], kwargs[\"vector\"][1], kwargs[\"vector\"][2]\n )\n wrapper(randomised_list)\n return wrapper\n\n return decorator\n\n def randomise_list(self, length, min, max):\n return min + (np.random.rand(length).astype(np.float32) * (max - min))\n\n def add_receive_list(self, func, kwargs):\n f = self.add_receive_list_to_osc_map(func, kwargs)\n if self.create_patch is True:\n self.add_receive_list_to_patcher(f)\n\n def add_receive_list_to_osc_map(self, func, kwargs):\n \"\"\"\n TODO: Should this support list[float] only, or list[int] list[str] etc?\n \"\"\"\n f = self.map_func_to_dict(func, kwargs)\n assert (\n len(f[\"params\"]) == 1\n ), \"receive_list can only receive one param (list[float])\"\n print(f)\n hint = f[\"hints\"][list(f[\"params\"].keys())[0]]\n assert (\n hint == list[float]\n ), \"receive_list can only receive list[float], found \" + str(hint)\n f[\"updater\"] = OSCReceiveListUpdater(\n self.osc, f[\"address\"], f=func, count=kwargs[\"count\"]\n )\n f[\"type\"] = \"list\"\n f[\"length\"] = kwargs[\"length\"]\n self.dict[\"receive\"][f[\"name\"]] = f\n if self.export is not None:\n self.export_dict()\n return f\n\n def add_receive_list_to_patcher(self, func):\n f = self.dict[\"receive\"][func[\"name\"]]\n self.patcher.receive_list_func(f)\n\n def receive_list_inline(self, name: str, receiver_func, length: int, count=1, **kwargs):\n kwargs = {**kwargs, **{\"name\": name, \"length\": length, \"count\": count, \"vector\": (0, 0, 1)}}\n self.receive_list(**kwargs)(receiver_func)\n\n def receive_list_with_idx(\n self, name: str, receiver, idx_len: int, vec_len: int, attr=None\n ):\n \"\"\"\n Create an OSC list handler that assumes that the first `idx_len` values are indices into some struct being modified by a receiver function, and the rest are args as a list, i.e.\n /name idx0 idx1 ... idxN arg0 arg1 ... argM\n ...\n receiver((idx0 idx1 ... idxN), args)\n Intended as a utility function to be used by external classes where it's not possible to use a decorator like `receive_list`.\n \"\"\"\n\n def handler(vector: list[float]):\n arg_len = len(vector[idx_len:])\n assert (\n arg_len == vec_len\n ), f\"len(args) != len(list) ({arg_len} != {vec_len})\"\n if idx_len:\n indices = tuple([int(v) for v in vector[:idx_len]])\n if attr is None:\n receiver(indices, vector[idx_len:])\n else:\n receiver(indices, attr, vector[idx_len:])\n else:\n if attr is None:\n receiver(vector)\n else:\n receiver(attr, vector)\n\n kwargs = {\n \"vector\": (0, 0, 1),\n \"length\": vec_len + idx_len,\n \"count\": 1,\n \"name\": name,\n }\n self.receive_list(**kwargs)(handler)\n\n \"\"\"\n receive kwargs\n \"\"\"\n\n def receive_kwargs(self, **kwargs):\n \"\"\"\n Same as receive_args but with named params\n \"\"\"\n raise NotImplementedError(\"receive_kwargs not implemented yet\")\n\n \"\"\"\n xml / json export\n \"\"\"\n\n def export_dict(self):\n \"\"\"\n Save the OSCMap dict as XML\n \"\"\"\n client_ip, client_port = self.osc.client_names[self.client_name]\n # TODO: This should be defined in the OSCMap dict / on init\n metadata = {\n \"HostIP\": self.osc.host,\n \"HostPort\": str(self.osc.port),\n \"ClientName\": self.client_name,\n \"ClientIP\": client_ip,\n \"ClientPort\": str(client_port),\n }\n root = ET.Element(\"OpenSoundControlSchema\")\n metadata_element = ET.SubElement(root, \"Metadata\", **metadata)\n sends = self.dict[\"send\"]\n receives = self.dict[\"receive\"]\n for io in [\"Send\", \"Receive\"]:\n ET.SubElement(root, io)\n for io in [\"send\", \"receive\"]:\n for name in self.dict[io]:\n f = self.dict[io][name]\n if f[\"type\"] == \"args\":\n self.xml_add_args_params(root, name, io, f)\n elif f[\"type\"] == \"list\":\n self.xml_add_list_param(root, name, io, f)\n elif f[\"type\"] == \"kwargs\":\n raise NotImplementedError(\"kwargs not implemented yet\")\n self.export_update(root)\n\n def xml_add_args_params(self, root, name, io, f):\n params = f[\"params\"]\n hints = f[\"hints\"]\n kw = {\n \"Address\": \"/\" + name.replace(\"_\", \"/\"),\n \"Type\": f[\"type\"],\n \"Params\": str(len(params)),\n }\n route = ET.SubElement(root.find(io.capitalize()), \"Route\", **kw)\n for i, p in enumerate(params):\n # TODO: This should already be defined by this point\n if io == \"receive\":\n p_type = hints[p].__name__\n elif io == \"send\":\n p_type = hints[\"return\"].__args__[i].__name__\n kw = {\n \"Name\": p,\n \"Type\": p_type,\n \"Default\": str(params[p][0]),\n \"Min\": str(params[p][1]),\n \"Max\": str(params[p][2]),\n }\n ET.SubElement(route, \"Param\", **kw)\n\n def xml_add_list_param(self, root, name, io, f):\n params = f[\"params\"]\n hints = f[\"hints\"]\n length = f[\"length\"]\n kw = {\n \"Address\": \"/\" + name.replace(\"_\", \"/\"),\n \"Type\": f[\"type\"],\n \"Length\": str(length),\n }\n route = ET.SubElement(root.find(io.capitalize()), \"Route\", **kw)\n p = list(params.keys())[0]\n if io == \"receive\":\n p_type = hints[p].__name__\n elif io == \"send\":\n p_type = hints[\"return\"].__args__[0].__name__\n kw = {\n \"Name\": p,\n \"Type\": p_type,\n \"Default\": str(params[p][0]),\n \"Min\": str(params[p][1]),\n \"Max\": str(params[p][2]),\n }\n ET.SubElement(route, \"ParamList\", **kw)\n\n def export_update(self, root):\n tree = ET.ElementTree(root)\n ET.indent(tree, space=\"\\t\", level=0)\n if self.export == \"XML\":\n self.save_xml(tree, root)\n elif self.export == \"JSON\":\n self.save_json(root)\n elif self.export == True:\n self.save_xml(tree, root)\n self.save_json(root)\n\n def save_xml(self, tree, root):\n tree.write(self.patch_filepath + \".xml\")\n print(f\"Exported OSCMap to {self.patch_filepath}.xml\")\n\n def save_json(self, xml_root):\n # TODO: params should be `params: []` and not `param: {}, param: {}, ...`\n json_dict = self.xml_to_json(\n ET.tostring(xml_root, encoding=\"utf8\", method=\"xml\")\n )\n with open(self.patch_filepath + \".json\", \"w\") as f:\n f.write(json_dict)\n print(f\"Exported OSCMap to {self.patch_filepath}.json\")\n\n def etree_to_dict(self, t):\n tag = self.pascal_to_camel(t.tag)\n d = {tag: {} if t.attrib else None}\n children = list(t)\n if children:\n dd = {}\n for dc in map(self.etree_to_dict, children):\n for k, v in dc.items():\n try:\n dd[k].append(v)\n except KeyError:\n dd[k] = [v]\n d = {tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}}\n if t.attrib:\n d[tag].update((self.pascal_to_camel(k), v) for k, v in t.attrib.items())\n if t.text:\n text = t.text.strip()\n if children or t.attrib:\n if text:\n d[tag][\"#text\"] = text\n else:\n d[tag] = text\n return d\n\n def xml_to_json(self, xml_str):\n e = ET.ElementTree(ET.fromstring(xml_str))\n return json.dumps(self.etree_to_dict(e.getroot()), indent=4)\n\n def update(self):\n for k, v in self.dict[\"send\"].items():\n if \"updater\" in v:\n ret = v[\"updater\"]()\n # v['updater']()\n for k, v in self.dict[\"receive\"].items():\n v[\"updater\"]()\n\n def __call__(self, *args: Any, **kwds: Any) -> Any:\n self.update()\n
"},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap.add_receive_list_to_osc_map","title":"add_receive_list_to_osc_map(func, kwargs)
","text":"TODO: Should this support list[float] only, or list[int] list[str] etc?
Source code in src/tolvera/osc/oscmap.py
def add_receive_list_to_osc_map(self, func, kwargs):\n \"\"\"\n TODO: Should this support list[float] only, or list[int] list[str] etc?\n \"\"\"\n f = self.map_func_to_dict(func, kwargs)\n assert (\n len(f[\"params\"]) == 1\n ), \"receive_list can only receive one param (list[float])\"\n print(f)\n hint = f[\"hints\"][list(f[\"params\"].keys())[0]]\n assert (\n hint == list[float]\n ), \"receive_list can only receive list[float], found \" + str(hint)\n f[\"updater\"] = OSCReceiveListUpdater(\n self.osc, f[\"address\"], f=func, count=kwargs[\"count\"]\n )\n f[\"type\"] = \"list\"\n f[\"length\"] = kwargs[\"length\"]\n self.dict[\"receive\"][f[\"name\"]] = f\n if self.export is not None:\n self.export_dict()\n return f\n
"},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap.export_dict","title":"export_dict()
","text":"Save the OSCMap dict as XML
Source code in src/tolvera/osc/oscmap.py
def export_dict(self):\n \"\"\"\n Save the OSCMap dict as XML\n \"\"\"\n client_ip, client_port = self.osc.client_names[self.client_name]\n # TODO: This should be defined in the OSCMap dict / on init\n metadata = {\n \"HostIP\": self.osc.host,\n \"HostPort\": str(self.osc.port),\n \"ClientName\": self.client_name,\n \"ClientIP\": client_ip,\n \"ClientPort\": str(client_port),\n }\n root = ET.Element(\"OpenSoundControlSchema\")\n metadata_element = ET.SubElement(root, \"Metadata\", **metadata)\n sends = self.dict[\"send\"]\n receives = self.dict[\"receive\"]\n for io in [\"Send\", \"Receive\"]:\n ET.SubElement(root, io)\n for io in [\"send\", \"receive\"]:\n for name in self.dict[io]:\n f = self.dict[io][name]\n if f[\"type\"] == \"args\":\n self.xml_add_args_params(root, name, io, f)\n elif f[\"type\"] == \"list\":\n self.xml_add_list_param(root, name, io, f)\n elif f[\"type\"] == \"kwargs\":\n raise NotImplementedError(\"kwargs not implemented yet\")\n self.export_update(root)\n
"},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap.receive_kwargs","title":"receive_kwargs(**kwargs)
","text":"Same as receive_args but with named params
Source code in src/tolvera/osc/oscmap.py
def receive_kwargs(self, **kwargs):\n \"\"\"\n Same as receive_args but with named params\n \"\"\"\n raise NotImplementedError(\"receive_kwargs not implemented yet\")\n
"},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap.receive_list_with_idx","title":"receive_list_with_idx(name, receiver, idx_len, vec_len, attr=None)
","text":"Create an OSC list handler that assumes that the first idx_len
values are indices into some struct being modified by a receiver function, and the rest are args as a list, i.e. /name idx0 idx1 ... idxN arg0 arg1 ... argM ... receiver((idx0 idx1 ... idxN), args) Intended as a utility function to be used by external classes where it's not possible to use a decorator like receive_list
.
Source code in src/tolvera/osc/oscmap.py
def receive_list_with_idx(\n self, name: str, receiver, idx_len: int, vec_len: int, attr=None\n):\n \"\"\"\n Create an OSC list handler that assumes that the first `idx_len` values are indices into some struct being modified by a receiver function, and the rest are args as a list, i.e.\n /name idx0 idx1 ... idxN arg0 arg1 ... argM\n ...\n receiver((idx0 idx1 ... idxN), args)\n Intended as a utility function to be used by external classes where it's not possible to use a decorator like `receive_list`.\n \"\"\"\n\n def handler(vector: list[float]):\n arg_len = len(vector[idx_len:])\n assert (\n arg_len == vec_len\n ), f\"len(args) != len(list) ({arg_len} != {vec_len})\"\n if idx_len:\n indices = tuple([int(v) for v in vector[:idx_len]])\n if attr is None:\n receiver(indices, vector[idx_len:])\n else:\n receiver(indices, attr, vector[idx_len:])\n else:\n if attr is None:\n receiver(vector)\n else:\n receiver(attr, vector)\n\n kwargs = {\n \"vector\": (0, 0, 1),\n \"length\": vec_len + idx_len,\n \"count\": 1,\n \"name\": name,\n }\n self.receive_list(**kwargs)(handler)\n
"},{"location":"reference/tolvera/osc/pd/","title":"Pd","text":""},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher","title":"PdPatcher
","text":"Source code in src/tolvera/osc/pd.py
class PdPatcher:\n def __init__(\n self,\n osc,\n client_name=\"client\",\n filepath=\"osc_controls\",\n x=0.0,\n y=0.0,\n w=1600.0,\n h=900.0,\n net_or_udp=\"udp\",\n bela=False,\n ) -> None:\n self.x, self.y, self.w, self.h = x, y, w, h\n self.patch_objects = [f\"#N canvas {x} {y} {w} {h} 12;\\n\"]\n self.patch_connections = []\n self.types = {\n \"object\": \"obj\",\n \"message\": \"msg\",\n \"number\": \"floatatom\",\n \"symbol\": \"symbolatom\",\n \"toggle\": \"toggle\",\n \"slider\": \"vslider\",\n \"bang\": \"bng\",\n \"comment\": \"text\",\n }\n self.patch_ids = {}\n self.osc = osc\n self.client_name = client_name\n self.client_address, self.client_port = self.osc.client_names[self.client_name]\n self.filepath = filepath\n self.net_or_udp = net_or_udp\n self.bela = bela\n self.init()\n\n \"\"\"\n init\n \"\"\"\n\n def init(self):\n self.w = 5.5 # default width (scaling factor)\n self.h = 27.0 # default height (pixels)\n self.line = 300 # default [line] (timed ramp generator) time in milliseconds\n self.param_width = 70\n self.s_x, self.s_y = 30, 30 # sends insertion point\n self.r_x, self.r_y = 30, 530 # receives\u00a0insertion point\n self.comment(\"Pd \u2192 Python\", self.s_x, self.s_y)\n self.comment(\"===========\", self.s_x, self.s_y + self.h / 2)\n self.patch_ids[\"send\"] = self.osc_send(\n self.osc.host, self.osc.port, self.s_x, self.s_y + self.h * 2\n )\n self.comment(\"Python \u2192 Pd\", self.r_x, self.r_y)\n self.comment(\"===========\", self.r_x, self.r_y + self.h / 2)\n self.patch_ids[\"receive\"] = self.osc_receive(\n self.client_port, self.r_x, self.r_y + self.h * 2\n )\n self.s_x += 300\n self.r_x += 300\n if self.bela:\n self.create_bela_main()\n self.save(self.filepath)\n\n def create_bela_main(self):\n if self.filepath.startswith(\"pd/\"):\n abstraction = self.filepath[3:]\n with open(\"pd/_main.pd\", \"w\") as f:\n f.write(f\"#N canvas {self.x} {self.y} {self.w} {self.h} 12;\\n\")\n f.write(f\"#X obj {30} {30} {abstraction};\\n\")\n\n \"\"\"\n basic objects\n \"\"\"\n\n def box(self, box_type, x, y, box_text):\n self.patch_objects.append(f\"#X {box_type} {x} {y} {box_text};\\n\")\n return self.get_last_id()\n\n def object(self, obj, x, y):\n return self.box(\"obj\", x, y, obj)\n\n def msg(self, msg, x, y):\n return self.box(\"msg\", x, y, msg)\n\n def comment(self, text, x, y):\n return self.box(\"text\", x, y, text)\n\n def number(self, x, y):\n return self.box(\"floatatom\", x, y, f\"5 0 0 0 - - - 0\")\n\n \"\"\"\n connections\n \"\"\"\n\n def connect(self, a_id, a_outlet, b_id, b_inlet):\n self.patch_connections.append(\n f\"#X connect {a_id} {a_outlet} {b_id} {b_inlet};\\n\"\n )\n\n \"\"\"\n osc send/receive\n \"\"\"\n\n def osc_send(self, host, port, x, y, send_rate_limit=100):\n loadbang_id = self.object(\"loadbang\", x, y)\n y += self.h\n connect_id = self.msg(f\"connect {host} {port}\", x, y)\n y += self.h\n disconnect_id = self.msg(\"disconnect\", x + 10, y)\n metro_id = self.object(f\"metro {send_rate_limit}\", x + 100, y)\n y += self.h\n send_rate_id = self.object(\"s rate\", x + 100, y)\n y += self.h\n receive_id = self.object(\"r send.to.iipyper\", x + 10, y)\n y += self.h\n packOSC_id = self.object(\"packOSC\", x + 10, y)\n y += self.h\n send_type = \"netsend -u\" if self.net_or_udp == \"net\" else \"udpsend\"\n send_id = self.object(send_type, x, y)\n y += self.h\n status_id = self.number(x, y)\n print_id = self.object(\"print reply.from.netreceive\", x + 40, y)\n # loadbang->connect->send->print\n self.connect(loadbang_id, 0, connect_id, 0)\n self.connect(connect_id, 0, send_id, 0)\n self.connect(send_id, 0, status_id, 0)\n self.connect(send_id, 1, print_id, 0)\n # loadbang->metro->send_rate\n self.connect(loadbang_id, 0, metro_id, 0)\n self.connect(metro_id, 0, send_rate_id, 0)\n # disconnect->send\n self.connect(disconnect_id, 0, send_id, 0)\n # receive->packOSC->send\n self.connect(receive_id, 0, packOSC_id, 0)\n self.connect(packOSC_id, 0, send_id, 0)\n return send_id\n\n def osc_receive(self, port, x, y):\n receive_type = (\n f\"netreceive -u {port}\"\n if self.net_or_udp == \"net\"\n else f\"udpreceive {port}\"\n )\n receive_id = self.object(receive_type, x, y)\n y += self.h\n unpackOSC_id = self.object(\"unpackOSC\", x, y)\n y += self.h\n print_id = self.object(\"print receive.from.iipyper\", x + 20, y)\n y += self.h\n s_receive_id = self.object(\"s receive.from.iipyper\", x, y)\n self.connect(receive_id, 0, unpackOSC_id, 0)\n self.connect(unpackOSC_id, 0, s_receive_id, 0)\n self.connect(unpackOSC_id, 0, print_id, 0)\n return self.get_last_id()\n\n \"\"\"\n osc send/receive args/list\n \"\"\"\n\n def send_args_func(self, f):\n hints = typing.get_type_hints(f[\"f\"])[\"return\"].__args__\n f_p = f[\"params\"]\n params = []\n if len(f_p) == 0:\n self.osc_receive_msg(self.r_x, self.r_y, f[\"address\"])\n else:\n for k, p in f_p.items():\n p_def, p_min, p_max = f_p[k][0], f_p[k][1], f_p[k][2]\n params.append(\n {\n \"label\": k,\n \"data\": hints[k].__name__,\n \"min_val\": p_min,\n \"size\": p_max - p_min,\n }\n )\n self.osc_receive_with_controls(self.r_x, self.r_y, f[\"address\"], params)\n self.r_x += max(\n len(params) * self.param_width + 100.0, len(f[\"address\"]) * 15.0 + 25.0\n )\n self.save(self.filepath)\n\n def send_list_func(self, f):\n self.osc_receive_list(self.r_x, self.r_y, f[\"address\"], f[\"params\"])\n self.r_x += len(f[\"address\"]) * 15.0 + 25.0\n self.save(self.filepath)\n\n def receive_args_func(self, f):\n hints = typing.get_type_hints(f[\"f\"])\n f_p = f[\"params\"]\n params = []\n if len(f_p) == 0:\n self.osc_send_msg(self.s_x, self.s_y, f[\"address\"])\n else:\n for k, p in f_p.items():\n # TODO: handle strings\n if isinstance(p, str):\n continue\n p_def, p_min, p_max = f_p[k][0], f_p[k][1], f_p[k][2]\n params.append(\n {\n \"label\": k,\n \"data\": hints[k].__name__,\n \"min_val\": p_min,\n \"size\": p_max - p_min,\n }\n )\n self.osc_send_with_controls(self.s_x, self.s_y, f[\"address\"], params)\n self.s_x += max(\n len(params) * self.param_width + 100.0, len(f[\"address\"]) * 15.0 + 25.0\n )\n self.save(self.filepath)\n\n def receive_list_func(self, f):\n self.osc_send_list(self.s_x, self.s_y, f[\"address\"], f[\"params\"])\n self.s_x += len(f[\"address\"]) * 15.0 + 25.0\n self.save(self.filepath)\n\n \"\"\"\n osc send/receive no args/list (msg)\n \"\"\"\n\n def osc_receive_msg(self, x, y, path):\n \"\"\"\n does this even make sense?\n \"\"\"\n receive_id = self.msg(\"r receive.from.iipyper\", x, y)\n msg_id = self.comment(path, x, y)\n self.connect(receive_id, 0, msg_id, 0)\n return msg_id\n\n def osc_send_msg(self, x, y, path):\n msg_id = self.msg(path, x, y + 225 + self.h)\n send_id = self.object(\"s send.to.iipyper\", x, y + 250 + self.h)\n self.connect(msg_id, 0, send_id, 0)\n return msg_id\n\n \"\"\"\n osc send/receive args with line, slider, rate-limiting, and change detection\n \"\"\"\n\n def osc_receive_with_controls(self, x, y, path, parameters):\n \"\"\"\n TODO: Does [route] need to be broken down into individual subpaths?\n \"\"\"\n\n # [comment path]\n y_off = 0\n path_comment_id = self.comment(path, x, y + y_off)\n\n # [r receive]\n y_off += self.h\n receive_id = self.object(\"r receive.from.iipyper\", x, y + y_off)\n\n # [route /path]\n y_off += self.h\n route_id = self.object(\"routeOSC \" + path, x, y + y_off)\n\n # [unpack f f f ...] [print /path]\n y_off += self.h\n unpack_id = self.object(\"unpack \" + self._pack_args(parameters), x, y + y_off)\n unpack_width = len(parameters) * 7 + 60\n print_id = self.object(\"print \" + path, x + unpack_width + 10, y + y_off)\n\n # sliders\n y_off += 10\n slider_ids, float_ids, int_ids, tbf_ids, _y_off = self.sliders(\n x, y + y_off, parameters, \"receive\"\n )\n y_off += 160\n\n # [s arg_name]\n y_off += _y_off + 75\n send_ids = [\n self.object(\n \"s \" + self.path_to_snakecase(path) + \"_\" + p[\"label\"][0:3],\n x + i * self.param_width,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n\n # [comment params]\n y_off += 50\n param_comment_ids, _y_off = self.param_comments(x, y + y_off, parameters)\n\n # # connections\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, unpack_id, 0)\n self.connect(route_id, 0, print_id, 0)\n [self.connect(unpack_id, i, slider_ids[i], 0) for i in range(len(parameters))]\n [self.connect(float_ids[i], 0, send_ids[i], 0) for i in range(len(parameters))]\n\n return slider_ids, unpack_id\n\n def osc_send_with_controls(self, x, y, path, parameters):\n y_off = 0\n # [comment path]\n path_comment_id = self.comment(path, x, y + y_off)\n y_off += 15\n param_comment_ids, _y_off = self.param_comments(x, y + y_off, parameters)\n\n # [r path_arg_name]\n y_off += 35\n receive_ids = [\n self.object(\n \"r \" + self.path_to_snakecase(path) + \"_\" + p[\"label\"][0:3],\n x + i * self.param_width,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n y_off += 30\n\n # sliders\n slider_ids, slider_float_ids, int_ids, tbf_ids, _y_off = self.sliders(\n x, y + y_off, parameters, \"send\"\n )\n y_off += self.h * 3 # line\n y_off += _y_off + 25\n y_off += 225\n\n pack_id = -1\n out_id = -1\n # [pack $1 $2 $3 ...]\n if len(parameters) > 1:\n pack_id = self.object(\"pack \" + self._pack_args(parameters), x, y + y_off)\n out_id = pack_id\n\n # [msg /path $1 $2 $3 ...]\n y_off += 25\n msg_args = self._msg_args(parameters)\n msg_id = self.msg(path + \" \" + msg_args, x, y + y_off)\n out_id = msg_id if len(parameters) == 1 else out_id\n # [s send]\n y_off += 25\n send_id = self.object(\"s send.to.iipyper\", x, y + y_off)\n\n # connections\n for i in range(len(parameters)):\n rcv = receive_ids[i]\n slider = slider_ids[i]\n slider_float = slider_float_ids[i]\n int_id = int_ids[i]\n tbf_id = tbf_ids[i]\n\n self.connect(rcv, 0, slider[0], 0)\n self.connect(rcv, 0, slider[1], 0)\n if int_id == -1 and tbf_id == -1: # if no int or tbf\n self.connect(slider_float, 0, out_id, 0)\n elif int_id != -1 and tbf_id == -1: # if int but no tbf\n self.connect(slider_float, 0, out_id, 0)\n elif int_id == -1 and tbf_id != -1: # if tbf but no int\n self.connect(tbf_id, 0, out_id, 0)\n self.connect(tbf_id, 1, pack_id, i) if pack_id != -1 else None\n elif int_id != -1 and tbf_id != -1: # if both int and tbf\n self.connect(tbf_id, 0, out_id, 0)\n self.connect(tbf_id, 1, pack_id, i) if pack_id != -1 else None\n\n self.connect(pack_id, 0, msg_id, 0) if pack_id != -1 else None\n self.connect(msg_id, 0, send_id, 0)\n return slider_ids, pack_id, msg_id\n\n \"\"\"\n sliders\n \"\"\"\n\n def sliders(self, x, y, sliders, io=None):\n assert io is not None, 'io must be \"send\" or \"receive\"'\n \"\"\"\n sliders = [\n { 'label': 'x', data: 'float', min_val: 0.0, size: 0.0 },\n ]\n \"\"\"\n slider_ids = []\n float_ids = []\n int_ids = []\n tbf_ids = []\n y_off = 0\n send_rate_id = self.object(\"r rate\", x - 50, y + 155 + self.h * 3)\n for i, s in enumerate(sliders):\n y_off = 0\n x_i = x + (i * self.param_width)\n y_off += self.h\n slider_id, int_id, float_id, tbf_id = self.slider(\n send_rate_id,\n x_i,\n y + y_off,\n s[\"min_val\"],\n s[\"size\"],\n float=s[\"data\"] == \"float\",\n io=io if i > 0 else \"skip\",\n )\n slider_ids.append(slider_id)\n float_ids.append(float_id)\n int_ids.append(int_id)\n tbf_ids.append(tbf_id)\n return slider_ids, float_ids, int_ids, tbf_ids, y_off\n\n def slider(self, send_rate_id, x, y, min_val, size, float=False, io=None):\n assert io is not None, 'io must be \"send\" or \"receive\"'\n bang_id = self.object(\"bng\", x, y)\n y += self.h\n msg_id = self.msg(f\"{self.line}\", x, y)\n y += self.h\n line_id = self.object(f\"line 0 {self.line}\", x, y)\n y += self.h\n slider_id = self.box(\n \"obj\",\n x,\n y,\n f\"vsl 20 120 {min_val} {min_val+size} 0 0 empty empty empty 0 -9 0 12 #fcfcfc #000000 #000000 0 1\",\n )\n self.connect(bang_id, 0, msg_id, 0)\n self.connect(msg_id, 0, line_id, 1)\n self.connect(line_id, 0, slider_id, 0)\n y += 120 + 8\n int_id = -1\n tbf_id = -1\n float_id = -1\n if float == False and io == \"send\":\n y, change_id, tbf_id = self.send_rate_limit_int(\n slider_id, send_rate_id, x, y\n )\n elif float == False and io != \"send\":\n y, change_id = self.receive_rate_limit_int(slider_id, send_rate_id, x, y)\n elif float == True and io == \"send\":\n y, change_id, tbf_id = self.send_rate_limit_float(\n slider_id, send_rate_id, x, y\n )\n elif float == True and io != \"send\":\n y, change_id = self.recieve_rate_limit_float(slider_id, send_rate_id, x, y)\n return (line_id, bang_id), int_id, change_id, tbf_id\n\n def send_rate_limit_int(self, slider_id, send_rate_id, x, y):\n # int -> number -> t b f\n int_id = self.object(\"int\", x, y)\n y += self.h\n float_id = self.number(x, y)\n y += self.h\n zl_id = self.object(\"zl reg\", x, y)\n y += self.h\n change_id = self.object(\"change\", x, y)\n y += self.h\n tbf_id = self.object(\"t b f\", x, y)\n self.connect(slider_id, 0, int_id, 0)\n self.connect(int_id, 0, float_id, 0)\n self.connect(float_id, 0, zl_id, 1)\n self.connect(send_rate_id, 0, zl_id, 0)\n self.connect(zl_id, 0, change_id, 0)\n self.connect(change_id, 0, tbf_id, 0)\n return y, change_id, tbf_id\n\n def receive_rate_limit_int(self, slider_id, send_rate_id, x, y):\n # int -> number\n int_id = self.object(\"int\", x, y)\n y += self.h\n float_id = self.number(x, y)\n y += self.h\n zl_id = self.object(\"zl reg\", x, y)\n y += self.h\n change_id = self.object(\"change\", x, y)\n self.connect(slider_id, 0, int_id, 0)\n self.connect(int_id, 0, float_id, 0)\n self.connect(float_id, 0, zl_id, 1)\n self.connect(send_rate_id, 0, zl_id, 0)\n self.connect(zl_id, 0, change_id, 0)\n return y, change_id\n\n def send_rate_limit_float(self, slider_id, send_rate_id, x, y):\n # number -> t b f\n float_id = self.number(x, y)\n y += self.h\n zl_id = self.object(\"zl reg\", x, y)\n y += self.h\n change_id = self.object(\"change\", x, y)\n y += self.h\n tbf_id = self.object(\"t b f\", x, y)\n self.connect(slider_id, 0, float_id, 0)\n self.connect(float_id, 0, zl_id, 1)\n self.connect(send_rate_id, 0, zl_id, 0)\n self.connect(zl_id, 0, change_id, 0)\n self.connect(change_id, 0, tbf_id, 0)\n return y, change_id, tbf_id\n\n def recieve_rate_limit_float(self, slider_id, send_rate_id, x, y):\n # number\n float_id = self.number(x, y)\n y += self.h\n zl_id = self.object(\"zl reg\", x, y)\n y += self.h\n change_id = self.object(\"change\", x, y)\n self.connect(slider_id, 0, float_id, 0)\n self.connect(float_id, 0, zl_id, 1)\n self.connect(send_rate_id, 0, zl_id, 0)\n self.connect(zl_id, 0, change_id, 0)\n return y, change_id\n\n \"\"\"\n comments\n \"\"\"\n\n def param_comments(self, x, y, params):\n comment_ids = []\n y_off = 0\n for i, p in enumerate(params):\n y_off = 0\n x_i = x + (i * self.param_width)\n p_max = (\n p[\"min_val\"] + p[\"size\"]\n if p[\"data\"] == \"float\"\n else p[\"min_val\"] + p[\"size\"] - 1\n )\n comment_id1 = self.comment(f'{p[\"label\"]}', x_i, y)\n y_off += 15\n comment_id2 = self.comment(\n f'{p[\"data\"][0]} {p[\"min_val\"]} {p_max}', x_i, y + y_off\n )\n comment_ids.append(comment_id1)\n comment_ids.append(comment_id2)\n return comment_ids, y_off\n\n \"\"\"\n lists\n \"\"\"\n\n def osc_send_list(self, x, y, path, params):\n \"\"\"\n [comment] path, list name, params\n [r] path\n [list prepend path]\n [list trim]\n [s send.to.iipyper]\n \"\"\"\n y_off = 0\n self.comment(path, x, y)\n y_off += 15\n l = list(params.items())[0]\n self.comment(f\"{l[0]}\", x, y + y_off)\n y_off += 15\n self.comment(f\"l {l[1][1]} {l[1][2]}\", x, y + y_off)\n y_off += self.h\n receive_id = self.object(f\"r {self.path_to_snakecase(path)}\", x, y + y_off)\n y_off += self.h\n prepend_id = self.object(f\"list prepend {path}\", x, y + y_off)\n y_off += self.h\n trim_id = self.object(f\"list trim\", x, y + y_off)\n y_off += self.h\n send_id = self.object(f\"s send.to.iipyper\", x, y + y_off)\n self.connect(receive_id, 0, prepend_id, 0)\n self.connect(prepend_id, 0, trim_id, 0)\n self.connect(trim_id, 0, send_id, 0)\n\n def osc_receive_list(self, x, y, path, params):\n \"\"\"\n [comment] path\n [r receive.from.iipyper]\n [routeOSC path]\n [s path]\n [comment] params\n \"\"\"\n y_off = 0\n self.comment(path, x, y)\n y_off += self.h\n receive_id = self.object(f\"r receive.from.iipyper\", x, y + y_off)\n y_off += self.h\n route_id = self.object(f\"routeOSC {path}\", x, y + y_off)\n y_off += self.h\n send_id = self.object(f\"s {self.path_to_snakecase(path)}\", x, y + y_off)\n y_off += self.h\n l = list(params.items())[0]\n self.comment(f\"{l[0]}\", x, y + y_off)\n y_off += 15\n self.comment(f\"l {l[1][1]} {l[1][2]}\", x, y + y_off)\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, send_id, 0)\n\n \"\"\"\n utils\n \"\"\"\n\n def get_last_id(self):\n return len(self.patch_objects) - 2\n\n def _pack_args(self, args):\n arg_types = []\n for a in args:\n match a[\"data\"]:\n case \"int\":\n arg_types.append(\"f\")\n case \"float\":\n arg_types.append(\"f\")\n case \"string\":\n arg_types.append(\"s\")\n return \" \".join(arg_types)\n\n def _msg_args(self, args):\n return \" \".join([\"\\$\" + str(i + 1) for i in range(len(args))])\n\n def path_to_snakecase(self, path):\n return path.replace(\"/\", \"_\")[1:] # +'_'+label[0:3]\n\n \"\"\"\n save/load\n \"\"\"\n\n def save(self, name):\n with open(name + \".pd\", \"w\") as f:\n [f.write(o) for o in self.patch_objects]\n [f.write(c) for c in self.patch_connections]\n\n def load(self, name):\n with open(name + \".pd\", \"r\") as f:\n for line in f:\n if f.startswith(\"#X connect\"):\n self.patch_connections.append(f)\n else:\n self.patch_objects.append(f)\n
"},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher.osc_receive_list","title":"osc_receive_list(x, y, path, params)
","text":"[comment] path [r receive.from.iipyper] [routeOSC path] s path params
Source code in src/tolvera/osc/pd.py
def osc_receive_list(self, x, y, path, params):\n \"\"\"\n [comment] path\n [r receive.from.iipyper]\n [routeOSC path]\n [s path]\n [comment] params\n \"\"\"\n y_off = 0\n self.comment(path, x, y)\n y_off += self.h\n receive_id = self.object(f\"r receive.from.iipyper\", x, y + y_off)\n y_off += self.h\n route_id = self.object(f\"routeOSC {path}\", x, y + y_off)\n y_off += self.h\n send_id = self.object(f\"s {self.path_to_snakecase(path)}\", x, y + y_off)\n y_off += self.h\n l = list(params.items())[0]\n self.comment(f\"{l[0]}\", x, y + y_off)\n y_off += 15\n self.comment(f\"l {l[1][1]} {l[1][2]}\", x, y + y_off)\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, send_id, 0)\n
"},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher.osc_receive_msg","title":"osc_receive_msg(x, y, path)
","text":"does this even make sense?
Source code in src/tolvera/osc/pd.py
def osc_receive_msg(self, x, y, path):\n \"\"\"\n does this even make sense?\n \"\"\"\n receive_id = self.msg(\"r receive.from.iipyper\", x, y)\n msg_id = self.comment(path, x, y)\n self.connect(receive_id, 0, msg_id, 0)\n return msg_id\n
"},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher.osc_receive_with_controls","title":"osc_receive_with_controls(x, y, path, parameters)
","text":"TODO: Does [route] need to be broken down into individual subpaths?
Source code in src/tolvera/osc/pd.py
def osc_receive_with_controls(self, x, y, path, parameters):\n \"\"\"\n TODO: Does [route] need to be broken down into individual subpaths?\n \"\"\"\n\n # [comment path]\n y_off = 0\n path_comment_id = self.comment(path, x, y + y_off)\n\n # [r receive]\n y_off += self.h\n receive_id = self.object(\"r receive.from.iipyper\", x, y + y_off)\n\n # [route /path]\n y_off += self.h\n route_id = self.object(\"routeOSC \" + path, x, y + y_off)\n\n # [unpack f f f ...] [print /path]\n y_off += self.h\n unpack_id = self.object(\"unpack \" + self._pack_args(parameters), x, y + y_off)\n unpack_width = len(parameters) * 7 + 60\n print_id = self.object(\"print \" + path, x + unpack_width + 10, y + y_off)\n\n # sliders\n y_off += 10\n slider_ids, float_ids, int_ids, tbf_ids, _y_off = self.sliders(\n x, y + y_off, parameters, \"receive\"\n )\n y_off += 160\n\n # [s arg_name]\n y_off += _y_off + 75\n send_ids = [\n self.object(\n \"s \" + self.path_to_snakecase(path) + \"_\" + p[\"label\"][0:3],\n x + i * self.param_width,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n\n # [comment params]\n y_off += 50\n param_comment_ids, _y_off = self.param_comments(x, y + y_off, parameters)\n\n # # connections\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, unpack_id, 0)\n self.connect(route_id, 0, print_id, 0)\n [self.connect(unpack_id, i, slider_ids[i], 0) for i in range(len(parameters))]\n [self.connect(float_ids[i], 0, send_ids[i], 0) for i in range(len(parameters))]\n\n return slider_ids, unpack_id\n
"},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher.osc_send_list","title":"osc_send_list(x, y, path, params)
","text":"[comment] path, list name, params [r] path [list prepend path] [list trim] [s send.to.iipyper]
Source code in src/tolvera/osc/pd.py
def osc_send_list(self, x, y, path, params):\n \"\"\"\n [comment] path, list name, params\n [r] path\n [list prepend path]\n [list trim]\n [s send.to.iipyper]\n \"\"\"\n y_off = 0\n self.comment(path, x, y)\n y_off += 15\n l = list(params.items())[0]\n self.comment(f\"{l[0]}\", x, y + y_off)\n y_off += 15\n self.comment(f\"l {l[1][1]} {l[1][2]}\", x, y + y_off)\n y_off += self.h\n receive_id = self.object(f\"r {self.path_to_snakecase(path)}\", x, y + y_off)\n y_off += self.h\n prepend_id = self.object(f\"list prepend {path}\", x, y + y_off)\n y_off += self.h\n trim_id = self.object(f\"list trim\", x, y + y_off)\n y_off += self.h\n send_id = self.object(f\"s send.to.iipyper\", x, y + y_off)\n self.connect(receive_id, 0, prepend_id, 0)\n self.connect(prepend_id, 0, trim_id, 0)\n self.connect(trim_id, 0, send_id, 0)\n
"},{"location":"reference/tolvera/osc/update/","title":"Update","text":""},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCReceiveListUpdater","title":"OSCReceiveListUpdater
","text":" Bases: ReceiveListUpdater
ReceiveListUpdater with an OSC handler
Source code in src/tolvera/osc/update.py
class OSCReceiveListUpdater(ReceiveListUpdater):\n \"\"\"\n ReceiveListUpdater with an OSC handler\n \"\"\"\n\n def __init__(self, osc, address: str, f, state=None, count=10, update=False):\n super().__init__(f, state, count, update)\n self.osc = osc\n self.address = address\n osc.add_handler(self.address, self.receive)\n\n def receive(self, address, *args):\n self.set(list(args[1:]))\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCReceiveUpdater","title":"OSCReceiveUpdater
","text":" Bases: ReceiveUpdater
ReceiveUpdater with an OSC handler
Source code in src/tolvera/osc/update.py
class OSCReceiveUpdater(ReceiveUpdater):\n \"\"\"\n ReceiveUpdater with an OSC handler\n \"\"\"\n\n def __init__(self, osc, address: str, f, state=None, count=10, update=False):\n super().__init__(f, state, count, update)\n self.osc = osc\n self.address = address\n osc.add_handler(self.address, self.receive)\n\n def receive(self, address, *args):\n # FIXME: ip:port/args\n \"\"\"\n v: first argument to the handler is the IP:port of the sender\n v: or you can use dispatcher.map directly\n and not set needs_reply_address=True\n j: can I get ip:port from osc itself?\n v: if you know the sender ahead of time yeah,\n but that lets you respond to different senders dynamically\n \"\"\"\n self.set(args[1:])\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCReceiveUpdater.receive","title":"receive(address, *args)
","text":"v: first argument to the handler is the IP:port of the sender v: or you can use dispatcher.map directly and not set needs_reply_address=True j: can I get ip:port from osc itself? v: if you know the sender ahead of time yeah, but that lets you respond to different senders dynamically
Source code in src/tolvera/osc/update.py
def receive(self, address, *args):\n # FIXME: ip:port/args\n \"\"\"\n v: first argument to the handler is the IP:port of the sender\n v: or you can use dispatcher.map directly\n and not set needs_reply_address=True\n j: can I get ip:port from osc itself?\n v: if you know the sender ahead of time yeah,\n but that lets you respond to different senders dynamically\n \"\"\"\n self.set(args[1:])\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCReceiveUpdaters","title":"OSCReceiveUpdaters
","text":"o = OSCReceiveUpdaters(osc, {\"/tolvera/particles/pos\": s.osc_set_pos, \"/tolvera/particles/vel\": s.osc_set_vel})
Source code in src/tolvera/osc/update.py
class OSCReceiveUpdaters:\n \"\"\"\n o = OSCReceiveUpdaters(osc,\n {\"/tolvera/particles/pos\": s.osc_set_pos,\n \"/tolvera/particles/vel\": s.osc_set_vel})\n \"\"\"\n\n def __init__(self, osc, receives=None, count=10):\n self.osc = osc\n self.receives = []\n self.count = count\n if receives is not None:\n self.add_dict(receives, count=self.count)\n\n def add_dict(self, receives, count=None):\n if count is None:\n count = self.count\n {a: self.add(a, f, count=count) for a, f in receives.items()}\n\n def add(self, address, function, state=None, count=None, update=False):\n if count is None:\n count = self.count\n self.receives.append(\n OSCReceiveUpdater(self.osc, address, function, state, count, update)\n )\n\n def __call__(self):\n [r() for r in self.receives]\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCSend","title":"OSCSend
","text":"Non rate-limited OSC send
Source code in src/tolvera/osc/update.py
class OSCSend:\n \"\"\"\n Non rate-limited OSC send\n \"\"\"\n\n def __init__(self, osc, address: str, f, client=None):\n self.osc = osc\n self.address = address\n self.f = f\n self.client = client\n\n def __call__(self, *args):\n self.osc.send(self.address, *self.f(*args), client=self.client)\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCSendUpdater","title":"OSCSendUpdater
","text":"Rate-limited OSC send
Source code in src/tolvera/osc/update.py
class OSCSendUpdater:\n \"\"\"\n Rate-limited OSC send\n \"\"\"\n\n def __init__(self, osc, address: str, f, count=30, client=None):\n self.osc = osc\n self.address = address\n self.f = f\n self.count = count\n self.counter = 0\n self.client = client\n\n def __call__(self):\n self.counter += 1\n if self.counter >= self.count:\n self.osc.send(self.address, *self.f(), client=self.client)\n self.counter = 0\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCSendUpdaters","title":"OSCSendUpdaters
","text":"o = OSCSendUpdaters(osc, client=\"particles\", count=10, sends={ \"/tolvera/particles/get/pos/all\": s.osc_get_pos_all })
Source code in src/tolvera/osc/update.py
class OSCSendUpdaters:\n \"\"\"\n o = OSCSendUpdaters(osc, client=\"particles\", count=10,\n sends={\n \"/tolvera/particles/get/pos/all\": s.osc_get_pos_all\n })\n \"\"\"\n\n def __init__(self, osc, sends=None, count=10, client=None):\n self.osc = osc\n self.sends = []\n self.count = count\n self.client = client\n if sends is not None:\n self.add_dict(sends, self.count, self.client)\n\n def add_dict(self, sends, count=None, client=None):\n if count is None:\n count = self.count\n if client is None:\n client = self.client\n {a: self.add(a, f, count=count, client=client) for a, f in sends.items()}\n\n def add(self, address, function, state=None, count=None, update=False, client=None):\n if count is None:\n count = self.count\n if client is None:\n client = self.client\n self.sends.append(OSCSendUpdater(self.osc, address, function, count, client))\n\n def __call__(self):\n [s() for s in self.sends]\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCUpdaters","title":"OSCUpdaters
","text":"o = OSCUpdaters(osc, client=\"boids\", count=10, receives={ \"/tolvera/boids/pos\": b.osc_set_pos, \"/tolvera/boids/vel\": b.osc_set_vel }, sends={ \"/tolvera/boids/pos/all\": b.osc_get_all_pos } )
Source code in src/tolvera/osc/update.py
class OSCUpdaters:\n \"\"\"\n o = OSCUpdaters(osc, client=\"boids\", count=10,\n receives={\n \"/tolvera/boids/pos\": b.osc_set_pos,\n \"/tolvera/boids/vel\": b.osc_set_vel\n },\n sends={\n \"/tolvera/boids/pos/all\": b.osc_get_all_pos\n }\n )\n \"\"\"\n\n def __init__(\n self,\n osc,\n sends=None,\n receives=None,\n send_count=60,\n receive_count=10,\n client=None,\n ):\n self.osc = osc\n self.client = client\n self.send_count = send_count\n self.receive_count = receive_count\n self.sends = OSCSendUpdaters(\n self.osc, count=self.send_count, client=self.client\n )\n self.receives = OSCReceiveUpdaters(self.osc, count=self.receive_count)\n if sends is not None:\n self.add_sends(sends)\n if receives is not None:\n self.add_receives(receives)\n\n def add_sends(self, sends, count=None, client=None):\n if count is None:\n count = self.send_count\n if client is None:\n client = self.client\n self.sends.add_dict(sends, count, client)\n\n def add_send(self, send, count=None, client=None):\n if count is None:\n count = self.send_count\n if client is None:\n client = self.client\n self.sends.add(send, client=client, count=count)\n\n def add_receives(self, receives, count=None):\n if count is None:\n count = self.receive_count\n self.receives.add_dict(receives, count=count)\n\n def add_receive(self, receive, count=None):\n if count is None:\n count = self.receive_count\n self.receives.add(receive, count=count)\n\n def __call__(self):\n self.sends()\n self.receives()\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveListUpdater","title":"ReceiveListUpdater
","text":"Decouples event handling from updating Updating is rate-limited by a counter Assumes a list[float] instead of *args
Source code in src/tolvera/osc/update.py
class ReceiveListUpdater:\n \"\"\"\n Decouples event handling from updating\n Updating is rate-limited by a counter\n Assumes a list[float] instead of *args\n \"\"\"\n\n def __init__(self, f, state=None, count=5, update=False):\n self.f = f\n self.count = count\n self.counter = 0\n self.update = update\n self.state = state\n\n def set(self, state):\n \"\"\"\n Set the Updater's state\n \"\"\"\n self.state = state\n self.update = True\n\n def __call__(self):\n \"\"\"\n Update the target function with internal state\n \"\"\"\n self.counter += 1\n if not (self.update and self.counter > self.count and self.state is not None):\n return\n self.f(self.state)\n self.counter = 0\n self.update = False\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveListUpdater.__call__","title":"__call__()
","text":"Update the target function with internal state
Source code in src/tolvera/osc/update.py
def __call__(self):\n \"\"\"\n Update the target function with internal state\n \"\"\"\n self.counter += 1\n if not (self.update and self.counter > self.count and self.state is not None):\n return\n self.f(self.state)\n self.counter = 0\n self.update = False\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveListUpdater.set","title":"set(state)
","text":"Set the Updater's state
Source code in src/tolvera/osc/update.py
def set(self, state):\n \"\"\"\n Set the Updater's state\n \"\"\"\n self.state = state\n self.update = True\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveUpdater","title":"ReceiveUpdater
","text":"Decouples event handling from updating Updating is rate-limited by a counter TODO: Rename to ReceiveArgsUpdater?
Source code in src/tolvera/osc/update.py
class ReceiveUpdater:\n \"\"\"\n Decouples event handling from updating\n Updating is rate-limited by a counter\n TODO: Rename to ReceiveArgsUpdater?\n \"\"\"\n\n def __init__(self, f, state=None, count=5, update=False):\n self.f = f\n self.count = count\n self.counter = 0\n self.update = update\n self.state = state\n\n def set(self, state):\n \"\"\"\n Set the Updater's state\n \"\"\"\n self.state = state\n self.update = True\n\n def __call__(self):\n \"\"\"\n Update the target function with internal state\n \"\"\"\n self.counter += 1\n if not (self.update and self.counter > self.count and self.state is not None):\n return\n self.ret = self.f(*self.state)\n \"\"\"\n if ret is not None:\n route = self.pascal_to_path(kwargs['name'])\n print('wrapper', route, ret, self.client_name)\n self.osc.return_to_sender_by_name((route, ret), self.client_name)\n \"\"\"\n self.counter = 0\n self.update = False\n return self.ret\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveUpdater.__call__","title":"__call__()
","text":"Update the target function with internal state
Source code in src/tolvera/osc/update.py
def __call__(self):\n \"\"\"\n Update the target function with internal state\n \"\"\"\n self.counter += 1\n if not (self.update and self.counter > self.count and self.state is not None):\n return\n self.ret = self.f(*self.state)\n \"\"\"\n if ret is not None:\n route = self.pascal_to_path(kwargs['name'])\n print('wrapper', route, ret, self.client_name)\n self.osc.return_to_sender_by_name((route, ret), self.client_name)\n \"\"\"\n self.counter = 0\n self.update = False\n return self.ret\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveUpdater.set","title":"set(state)
","text":"Set the Updater's state
Source code in src/tolvera/osc/update.py
def set(self, state):\n \"\"\"\n Set the Updater's state\n \"\"\"\n self.state = state\n self.update = True\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.Updater","title":"Updater
","text":"Rate-limited function call
Source code in src/tolvera/osc/update.py
class Updater:\n \"\"\"\n Rate-limited function call\n \"\"\"\n\n def __init__(self, f, count: int = 1):\n self.f = f\n self.count = int(count)\n self.counter = 0\n\n def __call__(self, *args, **kwargs):\n self.counter += 1\n if self.counter >= self.count:\n self.counter = 0\n return self.f(*args, **kwargs)\n return None\n
"},{"location":"reference/tolvera/vera/attractors/","title":"Attractors","text":"Inspired by https://github.com/williamgilpin/dysts
"},{"location":"reference/tolvera/vera/flock/","title":"Flock","text":"Flock behaviour based on the Boids algorithm.
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock","title":"Flock
","text":"Flock behaviour.
The flock operates via a species rule matrix, which is a 2D matrix of species rules, such that every species has a separate relationship with every other species including itself. As in the Boids algorithm, the rules are: - separate
: how much a particle should separate from its neighbours. - align
: how much a particle should align (match velocity) with its neighbours. - cohere
: how much a particle should cohere (move towards) its neighbours.
Taichi Boids implementation inspired by: https://forum.taichi-lang.cn/t/homework0-boids/563
Source code in src/tolvera/vera/flock.py
@ti.data_oriented\nclass Flock:\n \"\"\"Flock behaviour.\n\n The flock operates via a species rule matrix, which is a 2D matrix of species \n rules, such that every species has a separate relationship with every other \n species including itself. As in the Boids algorithm, the rules are:\n - `separate`: how much a particle should separate from its neighbours.\n - `align`: how much a particle should align (match velocity) with its neighbours.\n - `cohere`: how much a particle should cohere (move towards) its neighbours.\n\n Taichi Boids implementation inspired by:\n https://forum.taichi-lang.cn/t/homework0-boids/563\n \"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Flock behaviour.\n\n `flock_s` stores the species rule matrix. \n `flock_p` stores the rule values per particle, and the number of neighbours.\n `flock_dist` stores the distance between particles.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\"MAX_RADIUS\": (ti.f32, 300.0)})\n self.tv.s.flock_s = {\n \"state\": {\n \"separate\": (ti.f32, 0.01, 1.0),\n \"align\": (ti.f32, 0.01, 1.0),\n \"cohere\": (ti.f32, 0.01, 1.0),\n \"radius\": (ti.f32, 0.01, 1.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.tv.s.flock_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n self.tv.s.flock_dist = {\n \"state\": {\n \"dist\": (ti.f32, 0.0, self.tv.x * 2),\n \"dist_wrap\": (ti.f32, 0.0, self.tv.x * 2),\n },\n \"shape\": (self.tv.pn, self.tv.pn),\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n\n def randomise(self):\n \"\"\"Randomise the Flock behaviour.\"\"\"\n self.tv.s.flock_s.randomise()\n\n @ti.kernel\n def step(self, particles: ti.template(), weight: ti.f32):\n \"\"\"Step the Flock behaviour.\n\n Pairwise comparison is made and inactive particles are ignored. \n When the distance between two particles is less than the radius \n of the species, the particles are considered neighbours. \n\n The separation, alignment and cohesion are calculated for\n each particle and the velocity is updated accordingly.\n\n State is updated in `flock_p` and `flock_dist`.\n\n Args:\n particles (ti.template()): A template for the particles.\n weight (ti.f32): The weight of the Flock behaviour.\n \"\"\"\n n = particles.shape[0]\n for i in range(n):\n if particles[i].active == 0:\n continue\n p1 = particles[i]\n separate = ti.Vector([0.0, 0.0])\n align = ti.Vector([0.0, 0.0])\n cohere = ti.Vector([0.0, 0.0])\n nearby = 0\n species = self.tv.s.flock_s.struct()\n for j in range(n):\n if i == j and particles[j].active == 0:\n continue\n p2 = particles[j]\n species = self.tv.s.flock_s[p1.species, p2.species]\n dis_wrap = p1.dist_wrap(p2, self.tv.x, self.tv.y)\n dis_wrap_norm = dis_wrap.norm()\n if dis_wrap_norm < species.radius * self.CONSTS.MAX_RADIUS:\n separate += dis_wrap\n align += p2.vel\n cohere += p2.pos\n nearby += 1\n self.tv.s.flock_dist[i, j].dist = p1.dist(p2).norm()\n self.tv.s.flock_dist[i, j].dist_wrap = dis_wrap_norm\n if nearby > 0:\n separate = (\n separate / nearby * p1.active * ti.math.max(species.separate, 0.2)\n )\n align = align / nearby * p1.active * species.align\n cohere = (cohere / nearby - p1.pos) * p1.active * species.cohere\n vel = (separate + align + cohere).normalized()\n particles[i].vel += vel * weight\n particles[i].pos += particles[i].vel * p1.speed * p1.active * weight\n self.tv.s.flock_p[i] = self.tv.s.flock_p.struct(\n separate, align, cohere, nearby\n )\n\n def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Flock behaviour.\n\n Args:\n particles (Particles): Particles to step.\n weight (ti.f32, optional): The weight of the Flock behaviour. Defaults to 1.0.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock.__call__","title":"__call__(particles, weight=1.0)
","text":"Call the Flock behaviour.
Parameters:
Name Type Description Default particles
Particles
Particles to step.
required weight
f32
The weight of the Flock behaviour. Defaults to 1.0.
1.0
Source code in src/tolvera/vera/flock.py
def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Flock behaviour.\n\n Args:\n particles (Particles): Particles to step.\n weight (ti.f32, optional): The weight of the Flock behaviour. Defaults to 1.0.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the Flock behaviour.
flock_s
stores the species rule matrix. flock_p
stores the rule values per particle, and the number of neighbours. flock_dist
stores the distance between particles.
Parameters:
Name Type Description Default tolvera
Tolvera
A Tolvera instance.
required **kwargs
Keyword arguments (currently none).
{}
Source code in src/tolvera/vera/flock.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Flock behaviour.\n\n `flock_s` stores the species rule matrix. \n `flock_p` stores the rule values per particle, and the number of neighbours.\n `flock_dist` stores the distance between particles.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\"MAX_RADIUS\": (ti.f32, 300.0)})\n self.tv.s.flock_s = {\n \"state\": {\n \"separate\": (ti.f32, 0.01, 1.0),\n \"align\": (ti.f32, 0.01, 1.0),\n \"cohere\": (ti.f32, 0.01, 1.0),\n \"radius\": (ti.f32, 0.01, 1.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.tv.s.flock_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n self.tv.s.flock_dist = {\n \"state\": {\n \"dist\": (ti.f32, 0.0, self.tv.x * 2),\n \"dist_wrap\": (ti.f32, 0.0, self.tv.x * 2),\n },\n \"shape\": (self.tv.pn, self.tv.pn),\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock.randomise","title":"randomise()
","text":"Randomise the Flock behaviour.
Source code in src/tolvera/vera/flock.py
def randomise(self):\n \"\"\"Randomise the Flock behaviour.\"\"\"\n self.tv.s.flock_s.randomise()\n
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock.step","title":"step(particles, weight)
","text":"Step the Flock behaviour.
Pairwise comparison is made and inactive particles are ignored. When the distance between two particles is less than the radius of the species, the particles are considered neighbours.
The separation, alignment and cohesion are calculated for each particle and the velocity is updated accordingly.
State is updated in flock_p
and flock_dist
.
Parameters:
Name Type Description Default particles
template
A template for the particles.
required weight
f32
The weight of the Flock behaviour.
required Source code in src/tolvera/vera/flock.py
@ti.kernel\ndef step(self, particles: ti.template(), weight: ti.f32):\n \"\"\"Step the Flock behaviour.\n\n Pairwise comparison is made and inactive particles are ignored. \n When the distance between two particles is less than the radius \n of the species, the particles are considered neighbours. \n\n The separation, alignment and cohesion are calculated for\n each particle and the velocity is updated accordingly.\n\n State is updated in `flock_p` and `flock_dist`.\n\n Args:\n particles (ti.template()): A template for the particles.\n weight (ti.f32): The weight of the Flock behaviour.\n \"\"\"\n n = particles.shape[0]\n for i in range(n):\n if particles[i].active == 0:\n continue\n p1 = particles[i]\n separate = ti.Vector([0.0, 0.0])\n align = ti.Vector([0.0, 0.0])\n cohere = ti.Vector([0.0, 0.0])\n nearby = 0\n species = self.tv.s.flock_s.struct()\n for j in range(n):\n if i == j and particles[j].active == 0:\n continue\n p2 = particles[j]\n species = self.tv.s.flock_s[p1.species, p2.species]\n dis_wrap = p1.dist_wrap(p2, self.tv.x, self.tv.y)\n dis_wrap_norm = dis_wrap.norm()\n if dis_wrap_norm < species.radius * self.CONSTS.MAX_RADIUS:\n separate += dis_wrap\n align += p2.vel\n cohere += p2.pos\n nearby += 1\n self.tv.s.flock_dist[i, j].dist = p1.dist(p2).norm()\n self.tv.s.flock_dist[i, j].dist_wrap = dis_wrap_norm\n if nearby > 0:\n separate = (\n separate / nearby * p1.active * ti.math.max(species.separate, 0.2)\n )\n align = align / nearby * p1.active * species.align\n cohere = (cohere / nearby - p1.pos) * p1.active * species.cohere\n vel = (separate + align + cohere).normalized()\n particles[i].vel += vel * weight\n particles[i].pos += particles[i].vel * p1.speed * p1.active * weight\n self.tv.s.flock_p[i] = self.tv.s.flock_p.struct(\n separate, align, cohere, nearby\n )\n
"},{"location":"reference/tolvera/vera/forces/","title":"Forces","text":"Force functions for particles.
This module contains functions for applying forces to particles. It includes functions for moving, attracting, repelling and gravitating particles. It also includes variations of these functions for specific species of particles.
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.attract","title":"attract(particles, pos, mass, radius)
","text":"Attract the particles to a position.
Parameters:
Name Type Description Default particles
template
Particles.
required pos
vec2
Attraction position.
required mass
f32
Attraction mass.
required radius
f32
Attraction radius.
required Source code in src/tolvera/vera/forces.py
@ti.kernel\ndef attract(particles: ti.template(), pos: ti.math.vec2, mass: ti.f32, radius: ti.f32):\n \"\"\"Attract the particles to a position.\n\n Args:\n particles (ti.template): Particles.\n pos (ti.math.vec2): Attraction position.\n mass (ti.f32): Attraction mass.\n radius (ti.f32): Attraction radius.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n particles.field[i].vel += attract_particle(p, pos, mass, radius)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.attract_particle","title":"attract_particle(p, pos, mass, radius)
","text":"Attract a particle to a position.
Parameters:
Name Type Description Default particles
Particle
Individual particle.
required pos
vec2
Attraction position.
required mass
f32
Attraction mass.
required radius
f32
Attraction radius.
required Returns:
Type Description vec2
ti.math.vec2: Attraction velocity.
Source code in src/tolvera/vera/forces.py
@ti.func\ndef attract_particle(\n p: Particle, pos: ti.math.vec2, mass: ti.f32, radius: ti.f32\n) -> ti.math.vec2:\n \"\"\"Attract a particle to a position.\n\n Args:\n particles (Particle): Individual particle.\n pos (ti.math.vec2): Attraction position.\n mass (ti.f32): Attraction mass.\n radius (ti.f32): Attraction radius.\n\n Returns:\n ti.math.vec2: Attraction velocity.\n \"\"\"\n target_distance = (pos - p.pos).norm()\n vel = ti.Vector([0.0, 0.0])\n if target_distance < radius:\n factor = (radius - target_distance) / radius\n vel = (pos - p.pos).normalized() * mass * factor\n return vel\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.attract_species","title":"attract_species(particles, pos, mass, radius, species)
","text":"Attract the particles of a given species to a position.
Parameters:
Name Type Description Default particles
template
Particles.
required pos
vec2
Attraction position.
required mass
f32
Attraction mass.
required radius
f32
Attraction radius.
required species
i32
Species index.
required Source code in src/tolvera/vera/forces.py
@ti.kernel\ndef attract_species(\n particles: ti.template(),\n pos: ti.math.vec2,\n mass: ti.f32,\n radius: ti.f32,\n species: ti.i32,\n):\n \"\"\"Attract the particles of a given species to a position.\n\n Args:\n particles (ti.template): Particles.\n pos (ti.math.vec2): Attraction position.\n mass (ti.f32): Attraction mass.\n radius (ti.f32): Attraction radius.\n species (ti.i32): Species index.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n if p.species != species:\n continue\n particles.field[i].vel += attract_particle(p, pos, mass, radius)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.gravitate","title":"gravitate(particles, G, radius)
","text":"Gravitate the particles.
Parameters:
Name Type Description Default particles
template
Particles.
required G
f32
Gravitational constant.
required radius
f32
Gravitational radius.
required Source code in src/tolvera/vera/forces.py
@ti.kernel\ndef gravitate(particles: ti.template(), G: ti.f32, radius: ti.f32):\n \"\"\"Gravitate the particles.\n\n Args:\n particles (ti.template): Particles.\n G (ti.f32): Gravitational constant.\n radius (ti.f32): Gravitational radius.\n \"\"\"\n for i, j in ti.ndrange(particles.field.shape[0], particles.field.shape[0]):\n if i == j:\n continue\n p1 = particles.field[i]\n p2 = particles.field[j]\n if (p2.pos - p1.pos).norm() > radius:\n continue\n particles.field[i].vel += gravitation(p1, p2, G)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.gravitate_species","title":"gravitate_species(particles, G, radius, species)
","text":"Gravitate the particles of a given species.
Parameters:
Name Type Description Default particles
template
Particles.
required G
f32
Gravitational constant.
required radius
f32
Gravitational radius.
required species
i32
Species index.
required Source code in src/tolvera/vera/forces.py
@ti.kernel\ndef gravitate_species(\n particles: ti.template(), G: ti.f32, radius: ti.f32, species: ti.i32\n):\n \"\"\"Gravitate the particles of a given species.\n\n Args:\n particles (ti.template): Particles.\n G (ti.f32): Gravitational constant.\n radius (ti.f32): Gravitational radius.\n species (ti.i32): Species index.\n \"\"\"\n for i, j in ti.ndrange(particles.field.shape[0], particles.field.shape[0]):\n if i == j:\n continue\n p1 = particles.field[i]\n p2 = particles.field[j]\n if p1.species != species or p2.species != species:\n continue\n if (p2.pos - p1.pos).norm() > radius:\n continue\n particles.field[i].vel += gravitation(p1, p2, G)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.gravitation","title":"gravitation(p1, p2, G)
","text":"Calculate the gravitational force between two particles.
Parameters:
Name Type Description Default p1
Particle
Particle 1.
required p2
Particle
Particle 2.
required G
f32
Gravitational constant.
required Returns:
Type Description vec2
ti.math.vec2: Gravitational force.
Source code in src/tolvera/vera/forces.py
@ti.func\ndef gravitation(p1: Particle, p2: Particle, G: ti.f32) -> ti.math.vec2:\n \"\"\"Calculate the gravitational force between two particles.\n\n Args:\n p1 (Particle): Particle 1.\n p2 (Particle): Particle 2.\n G (ti.f32): Gravitational constant.\n\n Returns:\n ti.math.vec2: Gravitational force.\n \"\"\"\n r = p2.pos - p1.pos\n distance = r.norm() + 1e-5\n force_direction = r.normalized()\n force_magnitude = G * p1.mass * p2.mass / (distance**2)\n force = force_direction * force_magnitude\n return force / p1.mass\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.move","title":"move(particles)
","text":"Move the particles.
Parameters:
Name Type Description Default particles
template
Particles.
required Source code in src/tolvera/vera/forces.py
@ti.kernel\ndef move(particles: ti.template()):\n \"\"\"Move the particles.\n\n Args:\n particles (ti.template): Particles.\n \"\"\"\n for i in range(particles.field.shape[0]):\n if particles.field[i].active == 0:\n continue\n p1 = particles.field[i]\n particles.field[i].pos += p1.vel * p1.speed * p1.active\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.noise","title":"noise(particles, weight)
","text":"Add noise to the particles.
Parameters:
Name Type Description Default particles
template
Particles.
required weight
f32
Noise weight.
required Source code in src/tolvera/vera/forces.py
@ti.kernel\ndef noise(particles: ti.template(), weight: ti.f32):\n \"\"\"Add noise to the particles.\n\n Args:\n particles (ti.template): Particles.\n weight (ti.f32): Noise weight.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n particles.field[i].vel += (ti.Vector([ti.random() - 0.5, ti.random() - 0.5]) * weight)\n particles.field[i].pos += p.vel * p.speed * p.active\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.repel","title":"repel(particles, pos, mass, radius)
","text":"Repel the particles from a position.
Parameters:
Name Type Description Default particles
template
Particles.
required pos
vec2
Repulsion position.
required mass
f32
Repulsion mass.
required radius
f32
Repulsion radius.
required Source code in src/tolvera/vera/forces.py
@ti.kernel\ndef repel(particles: ti.template(), pos: ti.math.vec2, mass: ti.f32, radius: ti.f32):\n \"\"\"Repel the particles from a position.\n\n Args:\n particles (ti.template): Particles.\n pos (ti.math.vec2): Repulsion position.\n mass (ti.f32): Repulsion mass.\n radius (ti.f32): Repulsion radius.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n particles.field[i].vel += repel_particle(p, pos, mass, radius)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.repel_particle","title":"repel_particle(p, pos, mass, radius)
","text":"Repel a particle from a position.
Parameters:
Name Type Description Default p
Particle
Individual particle.
required pos
vec2
Repulsion position.
required mass
f32
Repulsion mass.
required radius
f32
Repulsion radius.
required Returns:
Type Description vec2
ti.math.vec2: Repulsion velocity.
Source code in src/tolvera/vera/forces.py
@ti.func\ndef repel_particle(\n p: Particle, pos: ti.math.vec2, mass: ti.f32, radius: ti.f32\n) -> ti.math.vec2:\n \"\"\"Repel a particle from a position.\n\n Args:\n p (Particle): Individual particle.\n pos (ti.math.vec2): Repulsion position.\n mass (ti.f32): Repulsion mass.\n radius (ti.f32): Repulsion radius.\n\n Returns:\n ti.math.vec2: Repulsion velocity.\n \"\"\"\n target_distance = (pos - p.pos).norm()\n vel = ti.Vector([0.0, 0.0])\n if target_distance < radius:\n factor = (target_distance - radius) / radius\n vel = (pos - p.pos).normalized() * mass * factor\n return vel\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.repel_species","title":"repel_species(particles, pos, mass, radius, species)
","text":"Repel the particles of a given species from a position.
Parameters:
Name Type Description Default particles
template
Particles.
required pos
vec2
Repulsion position.
required mass
f32
Repulsion mass.
required radius
f32
Repulsion radius.
required species
i32
Species index.
required Source code in src/tolvera/vera/forces.py
@ti.kernel\ndef repel_species(\n particles: ti.template(),\n pos: ti.math.vec2,\n mass: ti.f32,\n radius: ti.f32,\n species: ti.i32,\n):\n \"\"\"Repel the particles of a given species from a position.\n\n Args:\n particles (ti.template): Particles.\n pos (ti.math.vec2): Repulsion position.\n mass (ti.f32): Repulsion mass.\n radius (ti.f32): Repulsion radius.\n species (ti.i32): Species index.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n if p.species != species:\n continue\n particles.field[i].vel += repel_particle(p, pos, mass, radius)\n
"},{"location":"reference/tolvera/vera/nca/","title":"Nca","text":"https://github.com/google/swissgl/blob/main/demo/NeuralCA.js
"},{"location":"reference/tolvera/vera/particle_life/","title":"Particle life","text":"Particle Life model.
"},{"location":"reference/tolvera/vera/particle_life/#tolvera.vera.particle_life.ParticleLife","title":"ParticleLife
","text":"Particle Life model.
The Particle Life model is a simple model of particle behaviour, where particles are either attracted or repelled by other particles, depending on their species. Popularised by Jeffrey Ventrella (Clusters), Tom Mohr and others:
https://www.ventrella.com/Clusters/ https://github.com/tom-mohr/particle-life-app
Source code in src/tolvera/vera/particle_life.py
@ti.data_oriented\nclass ParticleLife():\n \"\"\"Particle Life model.\n\n The Particle Life model is a simple model of particle behaviour, where\n particles are either attracted or repelled by other particles, depending\n on their species. Popularised by Jeffrey Ventrella (Clusters), Tom Mohr\n and others:\n\n https://www.ventrella.com/Clusters/\n https://github.com/tom-mohr/particle-life-app\n\n \"\"\"\n def __init__(self, tolvera, **kwargs) -> None:\n \"\"\"Initialise the Particle Life model.\n\n 'plife' stores the species rule matrix.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\n \"V\": (ti.f32, 0.25),\n })\n self.tv.s.plife = {\n \"state\": {\n \"attract\": (ti.f32, -.5, .5),\n \"radius\": (ti.f32, 100., 300.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"randomise\": True,\n }\n @ti.kernel\n def step(self, particles: ti.template(), weight: ti.f32):\n \"\"\"Step the Particle Life model.\n\n Args:\n particles (Particles.field): The particles to step.\n weight (ti.f32): The weight of the step.\n \"\"\"\n for i in range(particles.shape[0]):\n if particles[i].active == 0.: continue\n p1 = particles[i]\n fx, fy = 0., 0.\n for j in range(particles.shape[0]):\n if particles[j].active == 0.: continue\n p2 = particles[j]\n s = self.tv.s.plife[p1.species, p2.species]\n dx = p1.pos[0] - p2.pos[0]\n dy = p1.pos[1] - p2.pos[1]\n d = ti.sqrt(dx*dx + dy*dy)\n if 0. < d and d < s.radius:\n F = s.attract/d\n fx += F*dx\n fy += F*dy\n particles[i].vel = (particles[i].vel + ti.Vector([fx, fy])) * self.CONSTS.V * weight\n particles[i].pos += (particles[i].vel * p1.speed * p1.active * weight)\n def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Particle Life model.\n\n Args:\n particles (Particles): The particles to step.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/particle_life/#tolvera.vera.particle_life.ParticleLife.__call__","title":"__call__(particles, weight=1.0)
","text":"Call the Particle Life model.
Parameters:
Name Type Description Default particles
Particles
The particles to step.
required Source code in src/tolvera/vera/particle_life.py
def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Particle Life model.\n\n Args:\n particles (Particles): The particles to step.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/particle_life/#tolvera.vera.particle_life.ParticleLife.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the Particle Life model.
'plife' stores the species rule matrix.
Parameters:
Name Type Description Default tolvera
Tolvera
A Tolvera instance.
required **kwargs
Keyword arguments (currently none).
{}
Source code in src/tolvera/vera/particle_life.py
def __init__(self, tolvera, **kwargs) -> None:\n \"\"\"Initialise the Particle Life model.\n\n 'plife' stores the species rule matrix.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\n \"V\": (ti.f32, 0.25),\n })\n self.tv.s.plife = {\n \"state\": {\n \"attract\": (ti.f32, -.5, .5),\n \"radius\": (ti.f32, 100., 300.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"randomise\": True,\n }\n
"},{"location":"reference/tolvera/vera/particle_life/#tolvera.vera.particle_life.ParticleLife.step","title":"step(particles, weight)
","text":"Step the Particle Life model.
Parameters:
Name Type Description Default particles
field
The particles to step.
required weight
f32
The weight of the step.
required Source code in src/tolvera/vera/particle_life.py
@ti.kernel\ndef step(self, particles: ti.template(), weight: ti.f32):\n \"\"\"Step the Particle Life model.\n\n Args:\n particles (Particles.field): The particles to step.\n weight (ti.f32): The weight of the step.\n \"\"\"\n for i in range(particles.shape[0]):\n if particles[i].active == 0.: continue\n p1 = particles[i]\n fx, fy = 0., 0.\n for j in range(particles.shape[0]):\n if particles[j].active == 0.: continue\n p2 = particles[j]\n s = self.tv.s.plife[p1.species, p2.species]\n dx = p1.pos[0] - p2.pos[0]\n dy = p1.pos[1] - p2.pos[1]\n d = ti.sqrt(dx*dx + dy*dy)\n if 0. < d and d < s.radius:\n F = s.attract/d\n fx += F*dx\n fy += F*dy\n particles[i].vel = (particles[i].vel + ti.Vector([fx, fy])) * self.CONSTS.V * weight\n particles[i].pos += (particles[i].vel * p1.speed * p1.active * weight)\n
"},{"location":"reference/tolvera/vera/reaction_diffusion/","title":"Reaction diffusion","text":"Inspired by https://github.com/taichi-dev/faster-python-with-taichi/blob/main/reaction_diffusion_taichi.py
"},{"location":"reference/tolvera/vera/slime/","title":"Slime","text":"Slime behaviour based on the Physarum polycephalum slime mould.
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime","title":"Slime
","text":"Slime behaviour based on the Physarum polycephalum slime mould.
The slime mould is a single-celled organism that exhibits complex behaviour such as foraging, migration, and decision-making. It is a popular model for emergent behaviour in nature-inspired computing.
The slime mould is simulated by a set of particles that move around the simulation space. The particles sense their environment and move in response to the sensed information. The particles leave a \"pheromone trail\" behind them, which evaporates over time. The particles can be of different species, which have different sensing and moving parameters.
Taichi Physarum implementation inspired by: https://github.com/taichi-dev/taichi/blob/master/python/taichi/examples/simulation/physarum.py
Source code in src/tolvera/vera/slime.py
@ti.data_oriented\nclass Slime:\n \"\"\"Slime behaviour based on the Physarum polycephalum slime mould.\n\n The slime mould is a single-celled organism that exhibits complex behaviour\n such as foraging, migration, and decision-making. It is a popular model for\n emergent behaviour in nature-inspired computing.\n\n The slime mould is simulated by a set of particles that move around the\n simulation space. The particles sense their environment and move in response\n to the sensed information. The particles leave a \"pheromone trail\" behind them,\n which evaporates over time. The particles can be of different species, which \n have different sensing and moving parameters.\n\n Taichi Physarum implementation inspired by:\n https://github.com/taichi-dev/taichi/blob/master/python/taichi/examples/simulation/physarum.py\n \"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Slime behaviour.\n\n `slime_p` stores the particle state.\n `slime_s` stores the species state.\n `trail` is a Pixels instance that stores the pheromone trail.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n evaporate (ti.f32, optional): Evaporation rate. Defaults to 0.99.\n **kwargs: Keyword arguments.\n brightness (ti.f32, optional): Brightness of the pheromone trail. Defaults to 1.0.\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n brightness = kwargs.get(\"brightness\", 1.0)\n self.CONSTS = CONSTS(\n {\n \"SENSE_ANGLE\": (ti.f32, ti.math.pi * 0.3),\n \"SENSE_DIST\": (ti.f32, 50.0),\n \"MOVE_ANGLE\": (ti.f32, ti.math.pi * 0.3),\n \"MOVE_DIST\": (ti.f32, 4.0),\n \"SUBSTEP\": (ti.i32, 1),\n \"BRIGHTNESS\": (ti.f32, brightness),\n }\n )\n self.tv.s.slime_p = {\n \"state\": {\n \"sense_angle\": (ti.f32, 0.0, 10.0),\n \"sense_left\": (ti.math.vec4, 0.0, 10.0),\n \"sense_centre\": (ti.math.vec4, 0.0, 10.0),\n \"sense_right\": (ti.math.vec4, 0.0, 10.0),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": True,\n }\n self.tv.s.slime_s = {\n \"state\": {\n \"sense_angle\": (ti.f32, 0.0, 1.0),\n \"sense_dist\": (ti.f32, 0.0, 1.0),\n \"move_angle\": (ti.f32, 0.0, 1.0),\n \"move_dist\": (ti.f32, 0.0, 1.0),\n \"evaporate\": (ti.f32, 0.0, 1.0),\n },\n \"shape\": self.tv.sn, # multi-species: (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.trail = Pixels(self.tv, **kwargs)\n self.evaporate = ti.field(dtype=ti.f32, shape=())\n self.evaporate[None] = kwargs.get(\"evaporate\", 0.99)\n\n def randomise(self):\n \"\"\"Randomise the Slime behaviour.\"\"\"\n self.tv.s.slime_s.randomise()\n self.tv.s.slime_p.randomise()\n\n @ti.kernel\n def move(self, field: ti.template(), weight: ti.f32):\n \"\"\"Move the particles based on the sensed environment.\n\n Each particle senses the trail to its left, centre and right. Depending on the \n strength of the sensed trail in each direction, and the species parameters,\n a movement angle is calculated. The particle moves in this direction by a \n distance proportional to its active state and the weight parameter.\n\n Args:\n field (ti.template): Particle field.\n weight (ti.f32): Weight of the movement.\n \"\"\"\n for i in range(field.shape[0]):\n if field[i].active == 0.0:\n continue\n\n p = field[i]\n ang = self.tv.s.slime_p[i].sense_angle\n species = self.tv.s.slime_s[p.species]\n\n sense_angle = species.sense_angle * self.CONSTS.SENSE_ANGLE\n sense_dist = species.sense_dist * self.CONSTS.SENSE_DIST\n move_angle = species.move_angle * self.CONSTS.MOVE_ANGLE\n move_dist = species.move_dist * self.CONSTS.MOVE_DIST\n\n c = self.sense(p.pos, ang, sense_dist).norm()\n l = self.sense(p.pos, ang - sense_angle, sense_dist).norm()\n r = self.sense(p.pos, ang + sense_angle, sense_dist).norm()\n\n if l < c < r:\n ang += move_angle\n elif l > c > r:\n ang -= move_angle\n elif r > c and c < l:\n # TODO: magic numbers, move to @ti.func inside utils?\n ang += move_angle * (2 * (ti.random() < 0.5) - 1)\n\n p.pos += (\n ti.Vector([ti.cos(ang), ti.sin(ang)]) * move_dist * p.active * weight\n )\n\n self.tv.s.slime_p[i].sense_angle = ang\n self.tv.s.slime_p[i].sense_centre = c\n self.tv.s.slime_p[i].sense_left = l\n self.tv.s.slime_p[i].sense_right = r\n field[i].pos = p.pos\n\n @ti.func\n def sense(self, pos: ti.math.vec2, ang: ti.f32, dist: ti.f32) -> ti.math.vec4:\n \"\"\"Sense the trail at a given position and angle.\n\n Args:\n pos (ti.math.vec2): Position.\n ang (ti.f32): Angle.\n dist (ti.f32): Distance.\n\n Returns:\n ti.math.vec4: RGBA value of the sensed trail point.\n \"\"\"\n ang_cos = ti.cos(ang)\n ang_sin = ti.sin(ang)\n v = ti.Vector([ang_cos, ang_sin])\n p = pos + v * dist\n px = ti.cast(p[0], ti.i32) % self.tv.x\n py = ti.cast(p[1], ti.i32) % self.tv.y\n pixel = self.trail.px.rgba[px, py]\n return pixel\n\n @ti.func\n def sense_rgba(self, pos: ti.math.vec2, ang: ti.f32, dist: ti.f32, rgba: ti.math.vec4) -> ti.math.vec4:\n \"\"\"Sense the trail at a given position and angle and return a weighted RGBA value.\n\n Args:\n pos (ti.math.vec2): Position.\n ang (ti.f32): Angle.\n dist (ti.f32): Distance.\n rgba (ti.math.vec4): RGBA value.\n\n Returns:\n ti.math.vec4: Weighted RGBA value.\n \"\"\"\n p = pos + ti.Vector([ti.cos(ang), ti.sin(ang)]) * dist\n px = ti.cast(p[0], ti.i32) % self.tv.x\n py = ti.cast(p[1], ti.i32) % self.tv.y\n px_rgba = self.trail.px.rgba[px, py]\n px_rgba_weighted = px_rgba * (1.0 - (px_rgba - rgba).norm())\n return px_rgba_weighted\n\n @ti.kernel\n def deposit_particles(self, particles: ti.template(), species: ti.template()):\n \"\"\"Deposit particles onto the trail.\n\n Args:\n particles (ti.template): Particle field.\n species (ti.template): Species field.\n \"\"\"\n for i in range(particles.shape[0]):\n if particles[i].active == 0.0:\n continue\n p, s = particles[i], species[particles[i].species]\n x = ti.cast(p.pos[0], ti.i32) % self.tv.x\n y = ti.cast(p.pos[1], ti.i32) % self.tv.y\n rgba = s.rgba * self.CONSTS.BRIGHTNESS * p.active\n self.trail.circle(x, y, p.size, rgba)\n\n def step(self, particles, species, weight: ti.f32 = 1.0):\n \"\"\"Step the Slime behaviour.\n\n Args:\n particles (Particles): A Particles instance.\n species (Species): A Species instance.\n weight (ti.f32, optional): Weight parameter. Defaults to 1.0.\n \"\"\"\n for i in range(self.CONSTS.SUBSTEP):\n self.move(particles.field, weight)\n self.deposit_particles(particles.field, species)\n self.trail.diffuse(self.evaporate[None])\n\n def __call__(self, particles, species, weight: ti.f32 = 1.0):\n \"\"\"Call the Slime behaviour.\n\n Args:\n particles (Particles): A Particles instance.\n species (Species): A Species instance.\n weight (ti.f32, optional): Weight parameter. Defaults to 1.0.\n\n Returns:\n Pixels: A Pixels instance containing the pheromone trail.\n \"\"\"\n self.step(particles, species, weight)\n return self.trail\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.__call__","title":"__call__(particles, species, weight=1.0)
","text":"Call the Slime behaviour.
Parameters:
Name Type Description Default particles
Particles
A Particles instance.
required species
Species
A Species instance.
required weight
f32
Weight parameter. Defaults to 1.0.
1.0
Returns:
Name Type Description Pixels
A Pixels instance containing the pheromone trail.
Source code in src/tolvera/vera/slime.py
def __call__(self, particles, species, weight: ti.f32 = 1.0):\n \"\"\"Call the Slime behaviour.\n\n Args:\n particles (Particles): A Particles instance.\n species (Species): A Species instance.\n weight (ti.f32, optional): Weight parameter. Defaults to 1.0.\n\n Returns:\n Pixels: A Pixels instance containing the pheromone trail.\n \"\"\"\n self.step(particles, species, weight)\n return self.trail\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the Slime behaviour.
slime_p
stores the particle state. slime_s
stores the species state. trail
is a Pixels instance that stores the pheromone trail.
Parameters:
Name Type Description Default tolvera
Tolvera
A Tolvera instance.
required evaporate
f32
Evaporation rate. Defaults to 0.99.
required **kwargs
Keyword arguments. brightness (ti.f32, optional): Brightness of the pheromone trail. Defaults to 1.0.
{}
Source code in src/tolvera/vera/slime.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Slime behaviour.\n\n `slime_p` stores the particle state.\n `slime_s` stores the species state.\n `trail` is a Pixels instance that stores the pheromone trail.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n evaporate (ti.f32, optional): Evaporation rate. Defaults to 0.99.\n **kwargs: Keyword arguments.\n brightness (ti.f32, optional): Brightness of the pheromone trail. Defaults to 1.0.\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n brightness = kwargs.get(\"brightness\", 1.0)\n self.CONSTS = CONSTS(\n {\n \"SENSE_ANGLE\": (ti.f32, ti.math.pi * 0.3),\n \"SENSE_DIST\": (ti.f32, 50.0),\n \"MOVE_ANGLE\": (ti.f32, ti.math.pi * 0.3),\n \"MOVE_DIST\": (ti.f32, 4.0),\n \"SUBSTEP\": (ti.i32, 1),\n \"BRIGHTNESS\": (ti.f32, brightness),\n }\n )\n self.tv.s.slime_p = {\n \"state\": {\n \"sense_angle\": (ti.f32, 0.0, 10.0),\n \"sense_left\": (ti.math.vec4, 0.0, 10.0),\n \"sense_centre\": (ti.math.vec4, 0.0, 10.0),\n \"sense_right\": (ti.math.vec4, 0.0, 10.0),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": True,\n }\n self.tv.s.slime_s = {\n \"state\": {\n \"sense_angle\": (ti.f32, 0.0, 1.0),\n \"sense_dist\": (ti.f32, 0.0, 1.0),\n \"move_angle\": (ti.f32, 0.0, 1.0),\n \"move_dist\": (ti.f32, 0.0, 1.0),\n \"evaporate\": (ti.f32, 0.0, 1.0),\n },\n \"shape\": self.tv.sn, # multi-species: (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.trail = Pixels(self.tv, **kwargs)\n self.evaporate = ti.field(dtype=ti.f32, shape=())\n self.evaporate[None] = kwargs.get(\"evaporate\", 0.99)\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.deposit_particles","title":"deposit_particles(particles, species)
","text":"Deposit particles onto the trail.
Parameters:
Name Type Description Default particles
template
Particle field.
required species
template
Species field.
required Source code in src/tolvera/vera/slime.py
@ti.kernel\ndef deposit_particles(self, particles: ti.template(), species: ti.template()):\n \"\"\"Deposit particles onto the trail.\n\n Args:\n particles (ti.template): Particle field.\n species (ti.template): Species field.\n \"\"\"\n for i in range(particles.shape[0]):\n if particles[i].active == 0.0:\n continue\n p, s = particles[i], species[particles[i].species]\n x = ti.cast(p.pos[0], ti.i32) % self.tv.x\n y = ti.cast(p.pos[1], ti.i32) % self.tv.y\n rgba = s.rgba * self.CONSTS.BRIGHTNESS * p.active\n self.trail.circle(x, y, p.size, rgba)\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.move","title":"move(field, weight)
","text":"Move the particles based on the sensed environment.
Each particle senses the trail to its left, centre and right. Depending on the strength of the sensed trail in each direction, and the species parameters, a movement angle is calculated. The particle moves in this direction by a distance proportional to its active state and the weight parameter.
Parameters:
Name Type Description Default field
template
Particle field.
required weight
f32
Weight of the movement.
required Source code in src/tolvera/vera/slime.py
@ti.kernel\ndef move(self, field: ti.template(), weight: ti.f32):\n \"\"\"Move the particles based on the sensed environment.\n\n Each particle senses the trail to its left, centre and right. Depending on the \n strength of the sensed trail in each direction, and the species parameters,\n a movement angle is calculated. The particle moves in this direction by a \n distance proportional to its active state and the weight parameter.\n\n Args:\n field (ti.template): Particle field.\n weight (ti.f32): Weight of the movement.\n \"\"\"\n for i in range(field.shape[0]):\n if field[i].active == 0.0:\n continue\n\n p = field[i]\n ang = self.tv.s.slime_p[i].sense_angle\n species = self.tv.s.slime_s[p.species]\n\n sense_angle = species.sense_angle * self.CONSTS.SENSE_ANGLE\n sense_dist = species.sense_dist * self.CONSTS.SENSE_DIST\n move_angle = species.move_angle * self.CONSTS.MOVE_ANGLE\n move_dist = species.move_dist * self.CONSTS.MOVE_DIST\n\n c = self.sense(p.pos, ang, sense_dist).norm()\n l = self.sense(p.pos, ang - sense_angle, sense_dist).norm()\n r = self.sense(p.pos, ang + sense_angle, sense_dist).norm()\n\n if l < c < r:\n ang += move_angle\n elif l > c > r:\n ang -= move_angle\n elif r > c and c < l:\n # TODO: magic numbers, move to @ti.func inside utils?\n ang += move_angle * (2 * (ti.random() < 0.5) - 1)\n\n p.pos += (\n ti.Vector([ti.cos(ang), ti.sin(ang)]) * move_dist * p.active * weight\n )\n\n self.tv.s.slime_p[i].sense_angle = ang\n self.tv.s.slime_p[i].sense_centre = c\n self.tv.s.slime_p[i].sense_left = l\n self.tv.s.slime_p[i].sense_right = r\n field[i].pos = p.pos\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.randomise","title":"randomise()
","text":"Randomise the Slime behaviour.
Source code in src/tolvera/vera/slime.py
def randomise(self):\n \"\"\"Randomise the Slime behaviour.\"\"\"\n self.tv.s.slime_s.randomise()\n self.tv.s.slime_p.randomise()\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.sense","title":"sense(pos, ang, dist)
","text":"Sense the trail at a given position and angle.
Parameters:
Name Type Description Default pos
vec2
Position.
required ang
f32
Angle.
required dist
f32
Distance.
required Returns:
Type Description vec4
ti.math.vec4: RGBA value of the sensed trail point.
Source code in src/tolvera/vera/slime.py
@ti.func\ndef sense(self, pos: ti.math.vec2, ang: ti.f32, dist: ti.f32) -> ti.math.vec4:\n \"\"\"Sense the trail at a given position and angle.\n\n Args:\n pos (ti.math.vec2): Position.\n ang (ti.f32): Angle.\n dist (ti.f32): Distance.\n\n Returns:\n ti.math.vec4: RGBA value of the sensed trail point.\n \"\"\"\n ang_cos = ti.cos(ang)\n ang_sin = ti.sin(ang)\n v = ti.Vector([ang_cos, ang_sin])\n p = pos + v * dist\n px = ti.cast(p[0], ti.i32) % self.tv.x\n py = ti.cast(p[1], ti.i32) % self.tv.y\n pixel = self.trail.px.rgba[px, py]\n return pixel\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.sense_rgba","title":"sense_rgba(pos, ang, dist, rgba)
","text":"Sense the trail at a given position and angle and return a weighted RGBA value.
Parameters:
Name Type Description Default pos
vec2
Position.
required ang
f32
Angle.
required dist
f32
Distance.
required rgba
vec4
RGBA value.
required Returns:
Type Description vec4
ti.math.vec4: Weighted RGBA value.
Source code in src/tolvera/vera/slime.py
@ti.func\ndef sense_rgba(self, pos: ti.math.vec2, ang: ti.f32, dist: ti.f32, rgba: ti.math.vec4) -> ti.math.vec4:\n \"\"\"Sense the trail at a given position and angle and return a weighted RGBA value.\n\n Args:\n pos (ti.math.vec2): Position.\n ang (ti.f32): Angle.\n dist (ti.f32): Distance.\n rgba (ti.math.vec4): RGBA value.\n\n Returns:\n ti.math.vec4: Weighted RGBA value.\n \"\"\"\n p = pos + ti.Vector([ti.cos(ang), ti.sin(ang)]) * dist\n px = ti.cast(p[0], ti.i32) % self.tv.x\n py = ti.cast(p[1], ti.i32) % self.tv.y\n px_rgba = self.trail.px.rgba[px, py]\n px_rgba_weighted = px_rgba * (1.0 - (px_rgba - rgba).norm())\n return px_rgba_weighted\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.step","title":"step(particles, species, weight=1.0)
","text":"Step the Slime behaviour.
Parameters:
Name Type Description Default particles
Particles
A Particles instance.
required species
Species
A Species instance.
required weight
f32
Weight parameter. Defaults to 1.0.
1.0
Source code in src/tolvera/vera/slime.py
def step(self, particles, species, weight: ti.f32 = 1.0):\n \"\"\"Step the Slime behaviour.\n\n Args:\n particles (Particles): A Particles instance.\n species (Species): A Species instance.\n weight (ti.f32, optional): Weight parameter. Defaults to 1.0.\n \"\"\"\n for i in range(self.CONSTS.SUBSTEP):\n self.move(particles.field, weight)\n self.deposit_particles(particles.field, species)\n self.trail.diffuse(self.evaporate[None])\n
"},{"location":"reference/tolvera/vera/swarmalators/","title":"Swarmalators","text":"Based on https://www.complexity-explorables.org/explorables/swarmalators/
"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"T\u00f6lvera","text":""},{"location":"#tolvera","title":"T\u00f6lvera","text":"T\u00f6lvera is a library for exploring musical performance with artificial life (ALife) and self-organising systems. The word is an Icelandic kenning:
- T\u00f6lva = computer, from tala (number) + v\u00f6lva (prophetess)
- Vera = being
- T\u00f6lvera = number being
T\u00f6lvera is written in Taichi, a domain-specific language embedded in Python.
This is experimental software and everything is currently subject to change.
Join us on the T\u00f6lvera Discord.
"},{"location":"#showcase","title":"Showcase","text":"Visit the YouTube Playlist (if you'd like to add a video, please get in touch).
"},{"location":"#features","title":"Features","text":" tv.v
: a collection of \"vera\" (beings) including Move, Flock, Slime and Swarm, with more being continuously added. Vera can be combined and composed in various ways. tv.p
: extensible particle system. Particles are divided into multiple species, where each species has a unique relationship with every other species, including itself tv.s
: n-dimensional state structures that can be used by \"vera\", including built-in OSC and IML creation (see below). tv.px
: drawing library including various shapes and blend modes, styled similarly to p5.js etc. tv.osc
: Open Sound Control (OSC) via iipyper, including automated export of OSC schemas to JSON, XML, Pure Data (Pd), Max/MSP (SuperCollider TBC). tv.iml
: Interactive Machine Learning via anguilla. tv.ti
: Taichi-based simulation and rendering engine. Can be run \"headless\" (without graphics). tv.cv
: computer vision integration based on OpenCV and Mediapipe.
"},{"location":"#examples","title":"Examples","text":"Examples can be found at iil-examples/tolvera. When run as a script, T\u00f6lvera program looks like this:
from tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n @tv.render\n def _():\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
"},{"location":"#install","title":"Install","text":"Taichi supports numerous operating systems and backends. If you plan on using Vulkan for graphics (recommended for macOS), you may need to install the Vulkan SDK first and restart your machine.
T\u00f6lvera is registered on PyPI and can be installed via a Python package manager such as pip
:
pip install tolvera\n
"},{"location":"#develop","title":"Develop","text":"Fork/clone this repository and install the package with poetry
:
git clone https://github.com/Intelligent-Instruments-Lab/tolvera # (or clone your own fork)\ncd tolvera\npoetry install\n
"},{"location":"#contribute","title":"Contribute","text":"We welcome Pull Requests across all areas of the project:
- Addressing Issues
- Adding features (see Issues and Discussion)
- Examples
- Tests
- Documentation
"},{"location":"#community","title":"Community","text":"To discuss T\u00f6lvera with developers and other users:
- Use GitHub Issues to report bugs and make specific feature requests.
- Use GitHub Discussions to share ideas and ask questions.
- Use Discord for further support, sharing your work, and general chat.
Across the project, we follow the Berlin Code of Conduct. Please get in touch if you experience or witness any conduct issues.
"},{"location":"#roadmap","title":"Roadmap","text":"See Discussion.
"},{"location":"#citing","title":"Citing","text":"T\u00f6lvera is being written about and used in a number of contexts (see references.bib), here are a few recent examples:
@inproceedings{armitageAgentialScoresExploring2023,\n Address = {Boston, Massachusetts, USA},\n Author = { Jack Armitage and Thor Magnusson },\n Title = {Agential Scores: Artificial Life for Emergent, Self-Organising and Entangled Music Notation},\n Booktitle = {Proceedings of the International Conference on Technologies for Music Notation and Representation -- TENOR'2023},\n Pages = {51 - 61},\n Year = {2023},\n Editor = {Anthony Paul De Ritis and Victor Zappi and Jeremy Van Buskirk and John Mallia},\n Publisher = {Northeastern University},\n ISBN = {978-0-6481592-7-8}\n}\n\n@inproceedings{armitageStrengjavera2023,\n title = {Strengjavera},\n booktitle = {{{AI Music Creativity}} 2023},\n author = {Armitage, Jack},\n year = {2023},\n address = {{University of Sussex, Brighton, UK}},\n doi = {10.5281/zenodo.8329855},\n ISBN = {978-0-9957862-9-5},\n url = {https://zenodo.org/records/8329855}\n}\n
"},{"location":"#inspiration","title":"Inspiration","text":" - SwissGL
- Lenia
- Particle Life (attributed to various, see for example Clusters)
- Journey to the Microcosmos
- Complexity Explorables
"},{"location":"#contact","title":"Contact","text":"tolvera
is developed by the Intelligent Instruments Lab. Get in touch to collaborate:
\u25e6 iil.is \u25e6 Facebook \u25e6 Instagram \u25e6 X (Twitter) \u25e6 YouTube \u25e6 Discord \u25e6 GitHub \u25e6 LinkedIn \u25e6 Email \u25e6
"},{"location":"#funding","title":"Funding","text":"The Intelligent Instruments project (INTENT) is funded by the European Research Council (ERC) under the European Union\u2019s Horizon 2020 research and innovation programme (Grant agreement No. 101001848).
"},{"location":"reference/tolvera/context/","title":"Context","text":"TolveraContext
is a shared context or environment for Tolvera
instances. It is created automatically when a Tolvera
instance is created, if one does not already exist. It manages the integration of packages for graphics, computer vision, communications protocols, and more. If multiple Tolvera
instances are created, they must share the same TolveraContext
.
Example TolveraContext
can be created manually, and shared with multiple Tolvera
instances. Note that only one render
function can be used at a time.
from tolvera import TolveraContext, Tolvera, run\n\ndef main(**kwargs):\n ctx = TolveraContext(**kwargs)\n\n tv1 = Tolvera(ctx=ctx, **kwargs)\n tv2 = Tolvera(ctx=ctx, **kwargs)\n\n @tv1.render\n def _():\n return tv2.px\n\nif __name__ == '__main__':\n run(main)\n
Example TolveraContext
can also be created automatically, and still shared.
from tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv1 = Tolvera(**kwargs)\n tv2 = Tolvera(ctx=tv1.ctx, **kwargs)\n\n @tv1.render\n def _():\n return tv2.px\n\nif __name__ == '__main__':\n run(main)\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext","title":"TolveraContext
","text":"Context for sharing between multiple T\u00f6lvera instances. Context includes Taichi, OSC, IML and CV. All T\u00f6lvera instances share the same context and are managed as a dict.
Attributes:
Name Type Description kwargs
dict
Keyword arguments for context.
name
str
Name of context.
name_clean
str
'Cleaned' name of context.
i
int
Frame counter.
x
int
Width of canvas.
y
int
Height of canvas.
ti
Taichi
Taichi instance.
canvas
Pixels
Pixels instance.
osc
OSC
OSC instance.
iml
IML
IML instance.
cv
CV
CV instance.
_cleanup_fns
list
List of cleanup functions.
tolveras
dict
Dict of T\u00f6lvera instances.
Source code in src/tolvera/context.py
class TolveraContext:\n \"\"\"\n Context for sharing between multiple T\u00f6lvera instances.\n Context includes Taichi, OSC, IML and CV.\n All T\u00f6lvera instances share the same context and are managed as a dict.\n\n Attributes:\n kwargs (dict): Keyword arguments for context.\n name (str): Name of context.\n name_clean (str): 'Cleaned' name of context.\n i (int): Frame counter.\n x (int): Width of canvas.\n y (int): Height of canvas.\n ti (Taichi): Taichi instance.\n canvas (Pixels): Pixels instance.\n osc (OSC): OSC instance.\n iml (IML): IML instance.\n cv (CV): CV instance.\n _cleanup_fns (list): List of cleanup functions.\n tolveras (dict): Dict of T\u00f6lvera instances.\n \"\"\"\n\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise T\u00f6lvera context with given keyword arguments.\"\"\"\n self.kwargs = kwargs\n self.init(**kwargs)\n\n def init(self, **kwargs):\n \"\"\"\n Initialise wrapped external packages with given keyword arguments.\n This only happens once when T\u00f6lvera is first initialised.\n\n Args:\n x (int): Width of canvas. Default: 1920.\n y (int): Height of canvas. Default: 1080.\n osc (bool): Enable OSC. Default: False.\n iml (bool): Enable IML. Default: False.\n cv (bool): Enable CV. Default: False.\n see also kwargs for Taichi, OSC, IMLDict, and CV.\n \"\"\"\n self.name = \"T\u00f6lvera Context\"\n self.name_clean = clean_name(self.name)\n print(f\"[{self.name}] Initializing context...\")\n self.i = 0\n self.x = kwargs.get(\"x\", 1920)\n self.y = kwargs.get(\"y\", 1080)\n self.ti = Taichi(self, **kwargs)\n self.show = self.ti.show\n self.canvas = Pixels(self, **kwargs)\n self.s = StateDict(self)\n self.osc = kwargs.get(\"osc\", False)\n self.iml = kwargs.get(\"iml\", False)\n self.cv = kwargs.get(\"cv\", False)\n self.hands = kwargs.get(\"hands\", False)\n if self.osc:\n self.osc = OSC(self, **kwargs)\n if self.iml:\n self.iml = IMLDict(self)\n if self.cv:\n self.cv = CV(self, **kwargs)\n self.hands = MPHands(self, **kwargs)\n self._cleanup_fns = []\n self.tolveras = {}\n print(f\"[{self.name}] Context initialisation complete.\")\n\n def run(self, f=None, **kwargs):\n \"\"\"\n Run T\u00f6lvera with given render function and keyword arguments.\n This function will run inside a locked thread until KeyboardInterrupt/exit.\n It runs the render function, updates the OSC map (if enabled), and shows the pixels.\n\n Args:\n f: Function to run.\n **kwargs: Keyword arguments for function.\n \"\"\"\n if f is not None:\n print(f\"[{self.name}] Running with render function {f.__name__}...\")\n else:\n print(f\"[{self.name}] Running with no render function...\")\n while self.ti.window.running:\n with _lock:\n [t.p() for t in self.tolveras.values()]\n if f is not None:\n self.canvas = f(**kwargs)\n if self.osc is not False:\n self.osc.map()\n if self.iml is not False:\n self.iml()\n if self.cv is not False:\n self.cv()\n self.ti.show(self.canvas)\n self.i += 1\n\n def stop(self):\n \"\"\"\n Run cleanup functions and exit.\n \"\"\"\n print(f\"\\n[{self.name}] Stopping {self.name}...\")\n for f in self._cleanup_fns:\n print(f\"\\n[{self.name}] Running cleanup function {f.__name__}...\")\n f()\n print(f\"\\n[{self.name}] Exiting {self.name}...\")\n exit(0)\n\n def render(self, f=None, **kwargs):\n \"\"\"Render T\u00f6lvera with given function and keyword arguments.\n\n Args:\n f (function, optional): Function to run. Defaults to None.\n \"\"\"\n try:\n self.run(f, **kwargs)\n except KeyboardInterrupt:\n self.stop()\n\n def cleanup(self, f=None):\n \"\"\"\n Decorator for cleanup functions based on iipyper.\n Make functions run on KeyBoardInterrupt (before exit).\n Cleanup functions must be defined before render is called!\n\n Args:\n f: Function to cleanup.\n\n Returns:\n Decorator function if f is None, else decorated function.\n \"\"\"\n print(f\"\\n[{self.name}] Adding cleanup function {f.__name__}...\")\n\n def decorator(f):\n \"\"\"Decorator that appends function to cleanup functions.\"\"\"\n self._cleanup_fns.append(f)\n return f\n\n if f is None: # return a decorator\n return decorator\n else: # bare decorator case; return decorated function\n return decorator(f)\n\n def add(self, tolvera):\n \"\"\"\n Add T\u00f6lvera to context.\n\n Args:\n tolvera (Tolvera): T\u00f6lvera to add.\n \"\"\"\n print(f\"[{self.name}] Adding tolvera='{tolvera.name}' to context.\")\n self.tolveras[tolvera.name] = tolvera\n\n def get_by_name(self, name):\n \"\"\"\n Get T\u00f6lvera by name.\n\n Args:\n name (str): Name of T\u00f6lvera to get.\n\n Returns:\n T\u00f6lvera: T\u00f6lvera with given name.\n \"\"\"\n return self.tolveras[name]\n\n def get_names(self):\n \"\"\"\n Get names of all T\u00f6lveras in context.\n\n Returns:\n list: List of T\u00f6lvera names.\n \"\"\"\n return list(self.tolveras.keys())\n\n def remove(self, name):\n \"\"\"\n Remove T\u00f6lvera by name.\n\n Args:\n name (str): Name of T\u00f6lvera to delete.\n \"\"\"\n print(f\"[{self.name}] Deleting tolvera='{name}' from context.\")\n del self.tolveras[name]\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.__init__","title":"__init__(**kwargs)
","text":"Initialise T\u00f6lvera context with given keyword arguments.
Source code in src/tolvera/context.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise T\u00f6lvera context with given keyword arguments.\"\"\"\n self.kwargs = kwargs\n self.init(**kwargs)\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.add","title":"add(tolvera)
","text":"Add T\u00f6lvera to context.
Parameters:
Name Type Description Default tolvera
Tolvera
T\u00f6lvera to add.
required Source code in src/tolvera/context.py
def add(self, tolvera):\n \"\"\"\n Add T\u00f6lvera to context.\n\n Args:\n tolvera (Tolvera): T\u00f6lvera to add.\n \"\"\"\n print(f\"[{self.name}] Adding tolvera='{tolvera.name}' to context.\")\n self.tolveras[tolvera.name] = tolvera\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.cleanup","title":"cleanup(f=None)
","text":"Decorator for cleanup functions based on iipyper. Make functions run on KeyBoardInterrupt (before exit). Cleanup functions must be defined before render is called!
Parameters:
Name Type Description Default f
Function to cleanup.
None
Returns:
Type Description Decorator function if f is None, else decorated function.
Source code in src/tolvera/context.py
def cleanup(self, f=None):\n \"\"\"\n Decorator for cleanup functions based on iipyper.\n Make functions run on KeyBoardInterrupt (before exit).\n Cleanup functions must be defined before render is called!\n\n Args:\n f: Function to cleanup.\n\n Returns:\n Decorator function if f is None, else decorated function.\n \"\"\"\n print(f\"\\n[{self.name}] Adding cleanup function {f.__name__}...\")\n\n def decorator(f):\n \"\"\"Decorator that appends function to cleanup functions.\"\"\"\n self._cleanup_fns.append(f)\n return f\n\n if f is None: # return a decorator\n return decorator\n else: # bare decorator case; return decorated function\n return decorator(f)\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.get_by_name","title":"get_by_name(name)
","text":"Get T\u00f6lvera by name.
Parameters:
Name Type Description Default name
str
Name of T\u00f6lvera to get.
required Returns:
Name Type Description T\u00f6lvera
T\u00f6lvera with given name.
Source code in src/tolvera/context.py
def get_by_name(self, name):\n \"\"\"\n Get T\u00f6lvera by name.\n\n Args:\n name (str): Name of T\u00f6lvera to get.\n\n Returns:\n T\u00f6lvera: T\u00f6lvera with given name.\n \"\"\"\n return self.tolveras[name]\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.get_names","title":"get_names()
","text":"Get names of all T\u00f6lveras in context.
Returns:
Name Type Description list
List of T\u00f6lvera names.
Source code in src/tolvera/context.py
def get_names(self):\n \"\"\"\n Get names of all T\u00f6lveras in context.\n\n Returns:\n list: List of T\u00f6lvera names.\n \"\"\"\n return list(self.tolveras.keys())\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.init","title":"init(**kwargs)
","text":"Initialise wrapped external packages with given keyword arguments. This only happens once when T\u00f6lvera is first initialised.
Parameters:
Name Type Description Default x
int
Width of canvas. Default: 1920.
required y
int
Height of canvas. Default: 1080.
required osc
bool
Enable OSC. Default: False.
required iml
bool
Enable IML. Default: False.
required cv
bool
Enable CV. Default: False.
required Source code in src/tolvera/context.py
def init(self, **kwargs):\n \"\"\"\n Initialise wrapped external packages with given keyword arguments.\n This only happens once when T\u00f6lvera is first initialised.\n\n Args:\n x (int): Width of canvas. Default: 1920.\n y (int): Height of canvas. Default: 1080.\n osc (bool): Enable OSC. Default: False.\n iml (bool): Enable IML. Default: False.\n cv (bool): Enable CV. Default: False.\n see also kwargs for Taichi, OSC, IMLDict, and CV.\n \"\"\"\n self.name = \"T\u00f6lvera Context\"\n self.name_clean = clean_name(self.name)\n print(f\"[{self.name}] Initializing context...\")\n self.i = 0\n self.x = kwargs.get(\"x\", 1920)\n self.y = kwargs.get(\"y\", 1080)\n self.ti = Taichi(self, **kwargs)\n self.show = self.ti.show\n self.canvas = Pixels(self, **kwargs)\n self.s = StateDict(self)\n self.osc = kwargs.get(\"osc\", False)\n self.iml = kwargs.get(\"iml\", False)\n self.cv = kwargs.get(\"cv\", False)\n self.hands = kwargs.get(\"hands\", False)\n if self.osc:\n self.osc = OSC(self, **kwargs)\n if self.iml:\n self.iml = IMLDict(self)\n if self.cv:\n self.cv = CV(self, **kwargs)\n self.hands = MPHands(self, **kwargs)\n self._cleanup_fns = []\n self.tolveras = {}\n print(f\"[{self.name}] Context initialisation complete.\")\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.remove","title":"remove(name)
","text":"Remove T\u00f6lvera by name.
Parameters:
Name Type Description Default name
str
Name of T\u00f6lvera to delete.
required Source code in src/tolvera/context.py
def remove(self, name):\n \"\"\"\n Remove T\u00f6lvera by name.\n\n Args:\n name (str): Name of T\u00f6lvera to delete.\n \"\"\"\n print(f\"[{self.name}] Deleting tolvera='{name}' from context.\")\n del self.tolveras[name]\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.render","title":"render(f=None, **kwargs)
","text":"Render T\u00f6lvera with given function and keyword arguments.
Parameters:
Name Type Description Default f
function
Function to run. Defaults to None.
None
Source code in src/tolvera/context.py
def render(self, f=None, **kwargs):\n \"\"\"Render T\u00f6lvera with given function and keyword arguments.\n\n Args:\n f (function, optional): Function to run. Defaults to None.\n \"\"\"\n try:\n self.run(f, **kwargs)\n except KeyboardInterrupt:\n self.stop()\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.run","title":"run(f=None, **kwargs)
","text":"Run T\u00f6lvera with given render function and keyword arguments. This function will run inside a locked thread until KeyboardInterrupt/exit. It runs the render function, updates the OSC map (if enabled), and shows the pixels.
Parameters:
Name Type Description Default f
Function to run.
None
**kwargs
Keyword arguments for function.
{}
Source code in src/tolvera/context.py
def run(self, f=None, **kwargs):\n \"\"\"\n Run T\u00f6lvera with given render function and keyword arguments.\n This function will run inside a locked thread until KeyboardInterrupt/exit.\n It runs the render function, updates the OSC map (if enabled), and shows the pixels.\n\n Args:\n f: Function to run.\n **kwargs: Keyword arguments for function.\n \"\"\"\n if f is not None:\n print(f\"[{self.name}] Running with render function {f.__name__}...\")\n else:\n print(f\"[{self.name}] Running with no render function...\")\n while self.ti.window.running:\n with _lock:\n [t.p() for t in self.tolveras.values()]\n if f is not None:\n self.canvas = f(**kwargs)\n if self.osc is not False:\n self.osc.map()\n if self.iml is not False:\n self.iml()\n if self.cv is not False:\n self.cv()\n self.ti.show(self.canvas)\n self.i += 1\n
"},{"location":"reference/tolvera/context/#tolvera.context.TolveraContext.stop","title":"stop()
","text":"Run cleanup functions and exit.
Source code in src/tolvera/context.py
def stop(self):\n \"\"\"\n Run cleanup functions and exit.\n \"\"\"\n print(f\"\\n[{self.name}] Stopping {self.name}...\")\n for f in self._cleanup_fns:\n print(f\"\\n[{self.name}] Running cleanup function {f.__name__}...\")\n f()\n print(f\"\\n[{self.name}] Exiting {self.name}...\")\n exit(0)\n
"},{"location":"reference/tolvera/cv/","title":"Cv","text":""},{"location":"reference/tolvera/iml/","title":"Iml","text":"IML stands for Interactive Machine Learning. T\u00f6lvera wraps the anguilla package to provide convenient ways for quickly creating mappings between vectors, functions and OSC routes.
Every T\u00f6lvera instance has an IMLDict, which is a dictionary of IML instances. The IMLDict is accessible via the iml
attribute of a T\u00f6lvera instance, and can be used to create and access IML instances.
There are 9 IML types, which are listed below.
Example Here we create a mapping based on states created by tv.v.flock
, where the per-particle state flock_p
is mapped to the species rule matrix flock_s
. Since this is a fun2fun
mapping (see IML Types below), we provide input and output functions, and T\u00f6lvera updates the mapping automatically every render()
call.
from tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n tv.iml.flock_p2flock_s = {\n 'type': 'fun2fun', \n 'size': (tv.s.flock_p.size, tv.s.flock_s.size), \n 'io': (tv.s.flock_p.to_vec, tv.s.flock_s.from_vec),\n 'randomise': True,\n }\n\n @tv.render\n def _():\n tv.px.diffuse(0.99)\n tv.v.flock(tv.p)\n tv.px.particles(tv.p, tv.s.species, 'circle')\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
IML Types vec2vec
: Vector to vector mapping. vec2fun
: Vector to function mapping. vec2osc
: Vector to OSC mapping. fun2vec
: Function to vector mapping. fun2fun
: Function to function mapping. fun2osc
: Function to OSC mapping. osc2vec
: OSC to vector mapping. osc2fun
: OSC to function mapping. osc2osc
: OSC to OSC mapping.
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase","title":"IMLBase
","text":" Bases: IML
This class inherits from anguilla and adds some functionality. It is not intended to be used directly, but rather to be inherited from.
The base class is initialised with a size tuple (input, output) and a config dict which is passed to anguilla.IML
.
It provides a randomise
method which adds random pairs to the mapping. It also provides methods to remove pairs (remove_oldest
, remove_newest
, remove_random
). It also provides a lag
method which lags the mapped data. Finally, it provides an update
method which is called by the updater
(see .osc.update
).
Source code in src/tolvera/iml.py
class IMLBase(iiIML):\n \"\"\"\n This class inherits from [anguilla](https://intelligent-instruments-lab.github.io/anguilla) \n and adds some functionality. It is not intended to be used directly, but rather \n to be inherited from.\n\n The base class is initialised with a size tuple (input, output) and a config dict\n which is passed to `anguilla.IML`.\n\n It provides a `randomise` method which adds random pairs to the mapping.\n It also provides methods to remove pairs (`remove_oldest`, `remove_newest`, `remove_random`).\n It also provides a `lag` method which lags the mapped data.\n Finally, it provides an `update` method which is called by the `updater` (see `.osc.update`).\n \"\"\"\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLBase\n\n kwargs:\n size (tuple, required): (input, output) sizes.\n io (tuple, optional): (input, output) functions.\n config (dict, optional): {embed_input, embed_output, interpolate, index, verbose}.\n updater (cls, optional): See iipyper.osc.update (Updater, OSCSendUpdater, ...).\n update_rate (int, optional): Updater's update rate (defaults to 1).\n randomise (bool, optional): Randomise mapping on init (defaults to False).\n rand_pairs (int, optional): Number of random pairs to add (defaults to 32).\n rand_input_weight (Any, optional): Random input weight (defaults to None).\n rand_output_weight (Any, optional): Random output weight (defaults to None).\n rand_method (str, optional): rand_method type (see utils).\n rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).\n map_kw (dict, optional): kwargs to use in IML.map().\n infun_kw (dict, optional): kwargs to use in infun() (type 'Fun2*' only).\n outfun_kw (dict, optional): kwargs to use in outfun() (type '*2Fun' only).\n lag (bool, optional): Lag mapped data (defaults to False). Incompatible with '*2Fun' types.\n lag_coef (float, optional): Lag coefficient (defaults to 0.5 if `lag` is True).\n \"\"\"\n assert \"size\" in kwargs, f\"IMLBase requires 'size' kwarg.\"\n self.size = kwargs[\"size\"]\n self.updater = kwargs.get(\n \"updater\", Updater(self.update, kwargs.get(\"update_rate\", 1))\n )\n self.config = kwargs.get(\"config\", {})\n if isinstance(self.size[0], tuple):\n self.config[\"emb\"] = \"ProjectAndSort\"\n print(f\"[tolvera._iml.IMLBase] Initialising IML with config: {self.config}\")\n super().__init__(**self.config)\n self.data = dotdict()\n self.map_kw = kwargs.get(\"map_kw\", {})\n self.infun_kw = kwargs.get(\"infun_kw\", {})\n self.outfun_kw = kwargs.get(\"outfun_kw\", {})\n if kwargs.get(\"randomise\", False):\n self.init_randomise(**kwargs)\n self.lag = kwargs.get(\"lag\", False)\n if self.lag:\n self.init_lag(**kwargs)\n\n def init_randomise(self, **kwargs):\n \"\"\"Initialise randomise() method with kwargs\n\n kwargs: see __init__ kwargs.\n \"\"\"\n self.rand_pairs = kwargs.get(\"rand_pairs\", 32)\n self.rand_input_weight = kwargs.get(\"rand_input_weight\", None)\n self.rand_output_weight = kwargs.get(\"rand_output_weight\", None)\n self.rand_method = kwargs.get(\"rand_method\", \"rand\")\n self.rand_kw = kwargs.get(\"rand_kw\", {})\n self.randomise(\n self.rand_pairs,\n self.rand_input_weight,\n self.rand_output_weight,\n self.rand_method,\n **self.rand_kw,\n )\n\n def init_lag(self, **kwargs):\n \"\"\"Initialise lag() method with kwargs\n\n kwargs: see __init__ kwargs.\n \"\"\"\n self.lag_coef = kwargs.get(\"lag_coef\", 0.5)\n self.lag = Lag(coef=self.lag_coef)\n print(\n f\"[tolvera._iml.IMLBase] Lagging mapped data with coef {self.lag_coef}.\"\n )\n\n def randomise(\n self,\n times: int,\n input_weight=None,\n output_weight=None,\n method: str = \"rand\",\n **kwargs,\n ):\n \"\"\"Randomise mapping.\n\n Args:\n times (int): Number of random pairs to add.\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n method (str, optional): Randomisation method. Defaults to \"rand\".\n \"\"\"\n self.rand = rand_select(method)\n while len(self.pairs) < times:\n self.add_random_pair(input_weight, output_weight, **kwargs)\n\n def set_random_method(self, method: str = \"rand\"):\n \"\"\"Set random method.\n\n Args:\n method (str, optional): Randomisation method. Defaults to \"rand\".\n \"\"\"\n self.rand = rand_select(method)\n\n def add_random_pair(self, input_weight=None, output_weight=None, **kwargs):\n \"\"\"Add random pair to mapping.\n\n Args:\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n **kwargs: see random_pair kwargs.\n \"\"\"\n indata, outdata = self.random_pair(input_weight, output_weight, **kwargs)\n self.add(indata, outdata)\n\n def random_input(self, **kwargs) -> torch.Tensor:\n \"\"\"Random input vector.\n\n Args:\n **kwargs: self.rand kwargs.\n\n Returns:\n torch.Tensor: Random input vector.\n \"\"\"\n return self.rand(self.size[0], **kwargs)\n\n def random_output(self, **kwargs) -> torch.Tensor:\n \"\"\"Random output vector.\n\n Args:\n **kwargs: self.rand kwargs\n\n Returns:\n torch.Tensor: Random output vector.\n \"\"\"\n return self.rand(self.size[1], **kwargs)\n\n def random_pair(self, input_weight=None, output_weight=None, **kwargs):\n \"\"\"Create random pair.\n\n Args:\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n **kwargs:\n rand_method (str, optional): Randomisation method. Defaults to \"rand\".\n rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).\n\n Raises:\n ValueError: Invalid input_weight type.\n ValueError: Invalid output_weight type.\n\n Returns:\n tuple: (input, output) vectors.\n \"\"\"\n if self.rand == None and \"rand_method\" not in kwargs:\n print(f\"[tolvera._iml.IMLBase] No 'rand' method set. Using 'rand'.\")\n self.set_random_method()\n elif \"rand_method\" in kwargs:\n self.set_random_method(kwargs[\"rand_method\"])\n if input_weight is None:\n input_weight = self.rand_input_weight\n if output_weight is None:\n output_weight = self.rand_output_weight\n indata = self.rand(self.size[0], **kwargs)\n outdata = self.rand(self.size[1], **kwargs)\n if input_weight is not None:\n if isinstance(input_weight, np.ndarray):\n indata *= torch.from_numpy(input_weight)\n elif isinstance(input_weight, (torch.Tensor, float, int)):\n indata *= input_weight\n elif isinstance(input_weight, list):\n indata *= torch.Tensor(input_weight)\n else:\n raise ValueError(\n f\"[tolvera._iml.IMLBase] Invalid input_weight type '{type(input_weight)}'.\"\n )\n if output_weight is not None:\n if isinstance(output_weight, np.ndarray):\n outdata *= torch.from_numpy(output_weight)\n elif isinstance(output_weight, (torch.Tensor, float, int)):\n outdata *= output_weight\n elif isinstance(output_weight, list):\n outdata *= torch.Tensor(output_weight)\n else:\n raise ValueError(\n f\"[tolvera._iml.IMLBase] Invalid output_weight type '{type(output_weight)}'.\"\n )\n return indata, outdata\n\n def remove_oldest(self, n: int = 1):\n \"\"\"Remove oldest pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(min(self.pairs.keys())) for _ in range(n)]\n\n def remove_newest(self, n: int = 1):\n \"\"\"Remove newest pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(max(self.pairs.keys())) for _ in range(n)]\n\n def remove_random(self, n: int = 1):\n \"\"\"Remove random pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(np.random.choice(list(self.pairs.keys()))) for _ in range(n)]\n\n def lag_mapped_data(self, lag_coef: float = 0.5):\n \"\"\"Lag mapped data.\n\n Args:\n lag_coef (float, optional): Lag coefficient. Defaults to 0.5.\n \"\"\"\n self.data.mapped = self.lag(self.data.mapped, lag_coef)\n\n def update(self, invec: list|torch.Tensor|np.ndarray) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Args:\n invec (list|torch.Tensor|np.ndarray): Input vector.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.pairs) == 0:\n return None\n self.data.mapped = self.map(invec, **self.map_kw)\n if hasattr(self, \"lag\") and type(self.lag) is Lag:\n self.lag_mapped_data()\n return self.data.mapped\n\n def update_rate(self, rate: int = None):\n \"\"\"Update rate getter/setter.\n\n Args:\n rate (int, optional): Update rate. Defaults to None.\n\n Returns:\n int: Update rate.\n \"\"\"\n if rate is not None:\n self.updater.count = rate\n return self.updater.count\n\n def __call__(self, *args) -> Any:\n \"\"\"Call updater with args.\n\n Args:\n *args: Updater args.\n\n Returns:\n Any: Mapped data.\n \"\"\"\n return self.updater(*args)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.__call__","title":"__call__(*args)
","text":"Call updater with args.
Parameters:
Name Type Description Default *args
Updater args.
()
Returns:
Name Type Description Any
Any
Mapped data.
Source code in src/tolvera/iml.py
def __call__(self, *args) -> Any:\n \"\"\"Call updater with args.\n\n Args:\n *args: Updater args.\n\n Returns:\n Any: Mapped data.\n \"\"\"\n return self.updater(*args)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLBase
kwargs size (tuple, required): (input, output) sizes. io (tuple, optional): (input, output) functions. config (dict, optional): {embed_input, embed_output, interpolate, index, verbose}. updater (cls, optional): See iipyper.osc.update (Updater, OSCSendUpdater, ...). update_rate (int, optional): Updater's update rate (defaults to 1). randomise (bool, optional): Randomise mapping on init (defaults to False). rand_pairs (int, optional): Number of random pairs to add (defaults to 32). rand_input_weight (Any, optional): Random input weight (defaults to None). rand_output_weight (Any, optional): Random output weight (defaults to None). rand_method (str, optional): rand_method type (see utils). rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils). map_kw (dict, optional): kwargs to use in IML.map(). infun_kw (dict, optional): kwargs to use in infun() (type 'Fun2' only). outfun_kw (dict, optional): kwargs to use in outfun() (type '2Fun' only). lag (bool, optional): Lag mapped data (defaults to False). Incompatible with '*2Fun' types. lag_coef (float, optional): Lag coefficient (defaults to 0.5 if lag
is True).
Source code in src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLBase\n\n kwargs:\n size (tuple, required): (input, output) sizes.\n io (tuple, optional): (input, output) functions.\n config (dict, optional): {embed_input, embed_output, interpolate, index, verbose}.\n updater (cls, optional): See iipyper.osc.update (Updater, OSCSendUpdater, ...).\n update_rate (int, optional): Updater's update rate (defaults to 1).\n randomise (bool, optional): Randomise mapping on init (defaults to False).\n rand_pairs (int, optional): Number of random pairs to add (defaults to 32).\n rand_input_weight (Any, optional): Random input weight (defaults to None).\n rand_output_weight (Any, optional): Random output weight (defaults to None).\n rand_method (str, optional): rand_method type (see utils).\n rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).\n map_kw (dict, optional): kwargs to use in IML.map().\n infun_kw (dict, optional): kwargs to use in infun() (type 'Fun2*' only).\n outfun_kw (dict, optional): kwargs to use in outfun() (type '*2Fun' only).\n lag (bool, optional): Lag mapped data (defaults to False). Incompatible with '*2Fun' types.\n lag_coef (float, optional): Lag coefficient (defaults to 0.5 if `lag` is True).\n \"\"\"\n assert \"size\" in kwargs, f\"IMLBase requires 'size' kwarg.\"\n self.size = kwargs[\"size\"]\n self.updater = kwargs.get(\n \"updater\", Updater(self.update, kwargs.get(\"update_rate\", 1))\n )\n self.config = kwargs.get(\"config\", {})\n if isinstance(self.size[0], tuple):\n self.config[\"emb\"] = \"ProjectAndSort\"\n print(f\"[tolvera._iml.IMLBase] Initialising IML with config: {self.config}\")\n super().__init__(**self.config)\n self.data = dotdict()\n self.map_kw = kwargs.get(\"map_kw\", {})\n self.infun_kw = kwargs.get(\"infun_kw\", {})\n self.outfun_kw = kwargs.get(\"outfun_kw\", {})\n if kwargs.get(\"randomise\", False):\n self.init_randomise(**kwargs)\n self.lag = kwargs.get(\"lag\", False)\n if self.lag:\n self.init_lag(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.add_random_pair","title":"add_random_pair(input_weight=None, output_weight=None, **kwargs)
","text":"Add random pair to mapping.
Parameters:
Name Type Description Default input_weight
Any
Weighting for the input vector. Defaults to None.
None
output_weight
Any
Weighting for the output vector. Defaults to None.
None
**kwargs
see random_pair kwargs.
{}
Source code in src/tolvera/iml.py
def add_random_pair(self, input_weight=None, output_weight=None, **kwargs):\n \"\"\"Add random pair to mapping.\n\n Args:\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n **kwargs: see random_pair kwargs.\n \"\"\"\n indata, outdata = self.random_pair(input_weight, output_weight, **kwargs)\n self.add(indata, outdata)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.init_lag","title":"init_lag(**kwargs)
","text":"Initialise lag() method with kwargs
kwargs: see init kwargs.
Source code in src/tolvera/iml.py
def init_lag(self, **kwargs):\n \"\"\"Initialise lag() method with kwargs\n\n kwargs: see __init__ kwargs.\n \"\"\"\n self.lag_coef = kwargs.get(\"lag_coef\", 0.5)\n self.lag = Lag(coef=self.lag_coef)\n print(\n f\"[tolvera._iml.IMLBase] Lagging mapped data with coef {self.lag_coef}.\"\n )\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.init_randomise","title":"init_randomise(**kwargs)
","text":"Initialise randomise() method with kwargs
kwargs: see init kwargs.
Source code in src/tolvera/iml.py
def init_randomise(self, **kwargs):\n \"\"\"Initialise randomise() method with kwargs\n\n kwargs: see __init__ kwargs.\n \"\"\"\n self.rand_pairs = kwargs.get(\"rand_pairs\", 32)\n self.rand_input_weight = kwargs.get(\"rand_input_weight\", None)\n self.rand_output_weight = kwargs.get(\"rand_output_weight\", None)\n self.rand_method = kwargs.get(\"rand_method\", \"rand\")\n self.rand_kw = kwargs.get(\"rand_kw\", {})\n self.randomise(\n self.rand_pairs,\n self.rand_input_weight,\n self.rand_output_weight,\n self.rand_method,\n **self.rand_kw,\n )\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.lag_mapped_data","title":"lag_mapped_data(lag_coef=0.5)
","text":"Lag mapped data.
Parameters:
Name Type Description Default lag_coef
float
Lag coefficient. Defaults to 0.5.
0.5
Source code in src/tolvera/iml.py
def lag_mapped_data(self, lag_coef: float = 0.5):\n \"\"\"Lag mapped data.\n\n Args:\n lag_coef (float, optional): Lag coefficient. Defaults to 0.5.\n \"\"\"\n self.data.mapped = self.lag(self.data.mapped, lag_coef)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.random_input","title":"random_input(**kwargs)
","text":"Random input vector.
Parameters:
Name Type Description Default **kwargs
self.rand kwargs.
{}
Returns:
Type Description Tensor
torch.Tensor: Random input vector.
Source code in src/tolvera/iml.py
def random_input(self, **kwargs) -> torch.Tensor:\n \"\"\"Random input vector.\n\n Args:\n **kwargs: self.rand kwargs.\n\n Returns:\n torch.Tensor: Random input vector.\n \"\"\"\n return self.rand(self.size[0], **kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.random_output","title":"random_output(**kwargs)
","text":"Random output vector.
Parameters:
Name Type Description Default **kwargs
self.rand kwargs
{}
Returns:
Type Description Tensor
torch.Tensor: Random output vector.
Source code in src/tolvera/iml.py
def random_output(self, **kwargs) -> torch.Tensor:\n \"\"\"Random output vector.\n\n Args:\n **kwargs: self.rand kwargs\n\n Returns:\n torch.Tensor: Random output vector.\n \"\"\"\n return self.rand(self.size[1], **kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.random_pair","title":"random_pair(input_weight=None, output_weight=None, **kwargs)
","text":"Create random pair.
Parameters:
Name Type Description Default input_weight
Any
Weighting for the input vector. Defaults to None.
None
output_weight
Any
Weighting for the output vector. Defaults to None.
None
**kwargs
rand_method (str, optional): Randomisation method. Defaults to \"rand\". rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).
{}
Raises:
Type Description ValueError
Invalid input_weight type.
ValueError
Invalid output_weight type.
Returns:
Name Type Description tuple
(input, output) vectors.
Source code in src/tolvera/iml.py
def random_pair(self, input_weight=None, output_weight=None, **kwargs):\n \"\"\"Create random pair.\n\n Args:\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n **kwargs:\n rand_method (str, optional): Randomisation method. Defaults to \"rand\".\n rand_kw (dict, optional): Random kwargs to pass to rand_method (see utils).\n\n Raises:\n ValueError: Invalid input_weight type.\n ValueError: Invalid output_weight type.\n\n Returns:\n tuple: (input, output) vectors.\n \"\"\"\n if self.rand == None and \"rand_method\" not in kwargs:\n print(f\"[tolvera._iml.IMLBase] No 'rand' method set. Using 'rand'.\")\n self.set_random_method()\n elif \"rand_method\" in kwargs:\n self.set_random_method(kwargs[\"rand_method\"])\n if input_weight is None:\n input_weight = self.rand_input_weight\n if output_weight is None:\n output_weight = self.rand_output_weight\n indata = self.rand(self.size[0], **kwargs)\n outdata = self.rand(self.size[1], **kwargs)\n if input_weight is not None:\n if isinstance(input_weight, np.ndarray):\n indata *= torch.from_numpy(input_weight)\n elif isinstance(input_weight, (torch.Tensor, float, int)):\n indata *= input_weight\n elif isinstance(input_weight, list):\n indata *= torch.Tensor(input_weight)\n else:\n raise ValueError(\n f\"[tolvera._iml.IMLBase] Invalid input_weight type '{type(input_weight)}'.\"\n )\n if output_weight is not None:\n if isinstance(output_weight, np.ndarray):\n outdata *= torch.from_numpy(output_weight)\n elif isinstance(output_weight, (torch.Tensor, float, int)):\n outdata *= output_weight\n elif isinstance(output_weight, list):\n outdata *= torch.Tensor(output_weight)\n else:\n raise ValueError(\n f\"[tolvera._iml.IMLBase] Invalid output_weight type '{type(output_weight)}'.\"\n )\n return indata, outdata\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.randomise","title":"randomise(times, input_weight=None, output_weight=None, method='rand', **kwargs)
","text":"Randomise mapping.
Parameters:
Name Type Description Default times
int
Number of random pairs to add.
required input_weight
Any
Weighting for the input vector. Defaults to None.
None
output_weight
Any
Weighting for the output vector. Defaults to None.
None
method
str
Randomisation method. Defaults to \"rand\".
'rand'
Source code in src/tolvera/iml.py
def randomise(\n self,\n times: int,\n input_weight=None,\n output_weight=None,\n method: str = \"rand\",\n **kwargs,\n):\n \"\"\"Randomise mapping.\n\n Args:\n times (int): Number of random pairs to add.\n input_weight (Any, optional): Weighting for the input vector. Defaults to None.\n output_weight (Any, optional): Weighting for the output vector. Defaults to None.\n method (str, optional): Randomisation method. Defaults to \"rand\".\n \"\"\"\n self.rand = rand_select(method)\n while len(self.pairs) < times:\n self.add_random_pair(input_weight, output_weight, **kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.remove_newest","title":"remove_newest(n=1)
","text":"Remove newest pair(s) from mapping.
Parameters:
Name Type Description Default n
int
Number of pairs to remove. Defaults to 1.
1
Source code in src/tolvera/iml.py
def remove_newest(self, n: int = 1):\n \"\"\"Remove newest pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(max(self.pairs.keys())) for _ in range(n)]\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.remove_oldest","title":"remove_oldest(n=1)
","text":"Remove oldest pair(s) from mapping.
Parameters:
Name Type Description Default n
int
Number of pairs to remove. Defaults to 1.
1
Source code in src/tolvera/iml.py
def remove_oldest(self, n: int = 1):\n \"\"\"Remove oldest pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(min(self.pairs.keys())) for _ in range(n)]\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.remove_random","title":"remove_random(n=1)
","text":"Remove random pair(s) from mapping.
Parameters:
Name Type Description Default n
int
Number of pairs to remove. Defaults to 1.
1
Source code in src/tolvera/iml.py
def remove_random(self, n: int = 1):\n \"\"\"Remove random pair(s) from mapping.\n\n Args:\n n (int, optional): Number of pairs to remove. Defaults to 1.\n \"\"\"\n if len(self.pairs) > n - 1:\n [self.remove(np.random.choice(list(self.pairs.keys()))) for _ in range(n)]\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.set_random_method","title":"set_random_method(method='rand')
","text":"Set random method.
Parameters:
Name Type Description Default method
str
Randomisation method. Defaults to \"rand\".
'rand'
Source code in src/tolvera/iml.py
def set_random_method(self, method: str = \"rand\"):\n \"\"\"Set random method.\n\n Args:\n method (str, optional): Randomisation method. Defaults to \"rand\".\n \"\"\"\n self.rand = rand_select(method)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.update","title":"update(invec)
","text":"Update mapped data.
Parameters:
Name Type Description Default invec
list | Tensor | ndarray
Input vector.
required Returns:
Type Description list | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code in src/tolvera/iml.py
def update(self, invec: list|torch.Tensor|np.ndarray) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Args:\n invec (list|torch.Tensor|np.ndarray): Input vector.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.pairs) == 0:\n return None\n self.data.mapped = self.map(invec, **self.map_kw)\n if hasattr(self, \"lag\") and type(self.lag) is Lag:\n self.lag_mapped_data()\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLBase.update_rate","title":"update_rate(rate=None)
","text":"Update rate getter/setter.
Parameters:
Name Type Description Default rate
int
Update rate. Defaults to None.
None
Returns:
Name Type Description int
Update rate.
Source code in src/tolvera/iml.py
def update_rate(self, rate: int = None):\n \"\"\"Update rate getter/setter.\n\n Args:\n rate (int, optional): Update rate. Defaults to None.\n\n Returns:\n int: Update rate.\n \"\"\"\n if rate is not None:\n self.updater.count = rate\n return self.updater.count\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict","title":"IMLDict
","text":" Bases: dotdict
IML mapping dict
Similarly to StateDict
, this class inherits from dotdict
to enable instantiation via assignment.
Source code in src/tolvera/iml.py
class IMLDict(dotdict):\n \"\"\"IML mapping dict\n\n Similarly to `StateDict`, this class inherits from `dotdict` to enable instantiation\n via assignment.\n \"\"\"\n\n def __init__(self, context) -> None:\n \"\"\"Initialise IMLDict\n\n Args:\n context (TolveraContext): TolveraContext instance.\n \"\"\"\n self.ctx = context\n self.i = {} # input vectors dict\n self.o = {} # output vectors dict\n\n def set(self, name, kwargs: dict) -> Any:\n \"\"\"Set IML instance.\n\n Args:\n name (str): Name of IML instance.\n kwargs (dict): IML instance kwargs.\n\n Raises:\n ValueError: Cannot replace 'tv' IML instance.\n ValueError: Cannot replace 'i' IML instance.\n ValueError: Cannot replace 'o' IML instance.\n NotImplementedError: set() with tuple not implemented yet.\n TypeError: set() requires dict|tuple, not _type_.\n Exception: Other exceptions.\n\n Returns:\n Any: IML instance.\n \"\"\"\n try:\n if name == \"ctx\" and type(kwargs) is not dict and type(kwargs) is not tuple:\n if name in self:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] '{name}' cannot be replaced.\"\n )\n self[name] = kwargs\n elif name == \"i\" or name == \"o\":\n if type(kwargs) is not dict:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] '{name}' is a reserved dict.\"\n )\n self[name] = kwargs\n elif type(kwargs) is dict:\n if \"type\" not in kwargs:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] IMLDict requires 'type' key.\"\n )\n return self.add(name, kwargs[\"type\"], **kwargs)\n elif type(kwargs) is tuple:\n # iml_type = kwargs[0] # TODO: which index is 'iml_type'?\n # return self.add(name, iml_type, *kwargs)\n raise NotImplementedError(\n f\"[tolvera._iml.IMLDict] set() with tuple not implemented yet.\"\n )\n else:\n raise TypeError(\n f\"[tolvera._iml.IMLDict] set() requires dict|tuple, not {type(kwargs)}\"\n )\n except Exception as e:\n raise type(e)(f\"[tolvera._iml.IMLDict] {e}\") from e\n\n def __setattr__(self, __name: str, __value: Any) -> None:\n \"\"\"Set IML instance.\n\n Args:\n __name (str): Name of IML instance.\n __value (Any): IML instance kwargs.\n \"\"\"\n self.set(__name, __value)\n\n def add(self, name: str, iml_type: str, **kwargs) -> Any:\n \"\"\"Add IML instance.\n\n Args:\n name (str): Name of IML instance.\n iml_type (str): IML type.\n\n Raises:\n ValueError: Invalid IML_TYPE.\n\n Returns:\n Any: IML instance.\n \"\"\"\n # TODO: should ^ be kwargs and not **kwargs?\n match iml_type:\n case \"vec2vec\":\n ins = IMLVec2Vec(**kwargs)\n case \"vec2fun\":\n ins = IMLVec2Fun(**kwargs)\n case \"vec2osc\":\n ins = IMLVec2OSC(self.ctx.osc.map, **kwargs)\n case \"fun2vec\":\n ins = IMLFun2Vec(**kwargs)\n case \"fun2fun\":\n ins = IMLFun2Fun(**kwargs)\n case \"fun2osc\":\n ins = IMLFun2OSC(self.ctx.osc.map, **kwargs)\n case \"osc2vec\":\n ins = IMLOSC2Vec(self.ctx.osc.map, self.o, name, **kwargs)\n case \"osc2fun\":\n ins = IMLOSC2Fun(self.ctx.osc.map, **kwargs)\n case \"osc2osc\":\n ins = IMLOSC2OSC(self.ctx.osc.map, self.ctx.osc, **kwargs)\n case _:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] Invalid IML_TYPE '{iml_type}'. Valid IML_TYPES: {IML_TYPES}.\"\n )\n self[name] = ins\n self.o[name] = None\n return ins\n\n def __call__(self, name=None, *args: Any, **kwargs: Any) -> Any:\n \"\"\"Call IML instance or all IML instances.\n\n Args:\n name (str, optional): Name of IML instance to call. Defaults to None.\n\n Raises:\n ValueError: 'name' not in dict.\n\n Returns:\n Any: IML output or dict of IML outputs.\n \"\"\"\n if name is not None:\n if name in self:\n # OSC updaters are handled by tv.osc.map (OSCMap)\n # TODO: Rethink this?\n if \"OSC\" not in type(self[name]).__name__:\n return self[name](*args, **kwargs)\n else:\n raise ValueError(f\"[tolvera._iml.IMLDict] '{name}' not in dict.\")\n else:\n outvecs = {}\n for iml in self:\n if iml == \"ctx\" or iml == \"i\" or iml == \"o\":\n continue\n cls_name = type(self[iml]).__name__\n if \"Vec2OSC\" in cls_name:\n self[iml].invec = self.i[iml]\n elif \"OSC\" in cls_name:\n # Fun2OSC, OSC2Fun, OSC2OSC and OSC2Vec \n # are handled by tv.osc.map (OSCMap)\n continue\n elif \"Vec2\" in cls_name:\n # Vec2Vec, Vec2Fun\n if iml in self.i:\n invec = self.i[iml]\n outvecs[iml] = self[iml](invec, *args, **kwargs)\n else:\n # Fun2Fun, Fun2Vec\n outvecs[iml] = self[iml](*args, **kwargs)\n self.i.clear()\n self.o.update(outvecs)\n return self.o\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.__call__","title":"__call__(name=None, *args, **kwargs)
","text":"Call IML instance or all IML instances.
Parameters:
Name Type Description Default name
str
Name of IML instance to call. Defaults to None.
None
Raises:
Type Description ValueError
'name' not in dict.
Returns:
Name Type Description Any
Any
IML output or dict of IML outputs.
Source code in src/tolvera/iml.py
def __call__(self, name=None, *args: Any, **kwargs: Any) -> Any:\n \"\"\"Call IML instance or all IML instances.\n\n Args:\n name (str, optional): Name of IML instance to call. Defaults to None.\n\n Raises:\n ValueError: 'name' not in dict.\n\n Returns:\n Any: IML output or dict of IML outputs.\n \"\"\"\n if name is not None:\n if name in self:\n # OSC updaters are handled by tv.osc.map (OSCMap)\n # TODO: Rethink this?\n if \"OSC\" not in type(self[name]).__name__:\n return self[name](*args, **kwargs)\n else:\n raise ValueError(f\"[tolvera._iml.IMLDict] '{name}' not in dict.\")\n else:\n outvecs = {}\n for iml in self:\n if iml == \"ctx\" or iml == \"i\" or iml == \"o\":\n continue\n cls_name = type(self[iml]).__name__\n if \"Vec2OSC\" in cls_name:\n self[iml].invec = self.i[iml]\n elif \"OSC\" in cls_name:\n # Fun2OSC, OSC2Fun, OSC2OSC and OSC2Vec \n # are handled by tv.osc.map (OSCMap)\n continue\n elif \"Vec2\" in cls_name:\n # Vec2Vec, Vec2Fun\n if iml in self.i:\n invec = self.i[iml]\n outvecs[iml] = self[iml](invec, *args, **kwargs)\n else:\n # Fun2Fun, Fun2Vec\n outvecs[iml] = self[iml](*args, **kwargs)\n self.i.clear()\n self.o.update(outvecs)\n return self.o\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.__init__","title":"__init__(context)
","text":"Initialise IMLDict
Parameters:
Name Type Description Default context
TolveraContext
TolveraContext instance.
required Source code in src/tolvera/iml.py
def __init__(self, context) -> None:\n \"\"\"Initialise IMLDict\n\n Args:\n context (TolveraContext): TolveraContext instance.\n \"\"\"\n self.ctx = context\n self.i = {} # input vectors dict\n self.o = {} # output vectors dict\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.__setattr__","title":"__setattr__(__name, __value)
","text":"Set IML instance.
Parameters:
Name Type Description Default __name
str
Name of IML instance.
required __value
Any
IML instance kwargs.
required Source code in src/tolvera/iml.py
def __setattr__(self, __name: str, __value: Any) -> None:\n \"\"\"Set IML instance.\n\n Args:\n __name (str): Name of IML instance.\n __value (Any): IML instance kwargs.\n \"\"\"\n self.set(__name, __value)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.add","title":"add(name, iml_type, **kwargs)
","text":"Add IML instance.
Parameters:
Name Type Description Default name
str
Name of IML instance.
required iml_type
str
IML type.
required Raises:
Type Description ValueError
Invalid IML_TYPE.
Returns:
Name Type Description Any
Any
IML instance.
Source code in src/tolvera/iml.py
def add(self, name: str, iml_type: str, **kwargs) -> Any:\n \"\"\"Add IML instance.\n\n Args:\n name (str): Name of IML instance.\n iml_type (str): IML type.\n\n Raises:\n ValueError: Invalid IML_TYPE.\n\n Returns:\n Any: IML instance.\n \"\"\"\n # TODO: should ^ be kwargs and not **kwargs?\n match iml_type:\n case \"vec2vec\":\n ins = IMLVec2Vec(**kwargs)\n case \"vec2fun\":\n ins = IMLVec2Fun(**kwargs)\n case \"vec2osc\":\n ins = IMLVec2OSC(self.ctx.osc.map, **kwargs)\n case \"fun2vec\":\n ins = IMLFun2Vec(**kwargs)\n case \"fun2fun\":\n ins = IMLFun2Fun(**kwargs)\n case \"fun2osc\":\n ins = IMLFun2OSC(self.ctx.osc.map, **kwargs)\n case \"osc2vec\":\n ins = IMLOSC2Vec(self.ctx.osc.map, self.o, name, **kwargs)\n case \"osc2fun\":\n ins = IMLOSC2Fun(self.ctx.osc.map, **kwargs)\n case \"osc2osc\":\n ins = IMLOSC2OSC(self.ctx.osc.map, self.ctx.osc, **kwargs)\n case _:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] Invalid IML_TYPE '{iml_type}'. Valid IML_TYPES: {IML_TYPES}.\"\n )\n self[name] = ins\n self.o[name] = None\n return ins\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLDict.set","title":"set(name, kwargs)
","text":"Set IML instance.
Parameters:
Name Type Description Default name
str
Name of IML instance.
required kwargs
dict
IML instance kwargs.
required Raises:
Type Description ValueError
Cannot replace 'tv' IML instance.
ValueError
Cannot replace 'i' IML instance.
ValueError
Cannot replace 'o' IML instance.
NotImplementedError
set() with tuple not implemented yet.
TypeError
set() requires dict|tuple, not type.
Exception
Other exceptions.
Returns:
Name Type Description Any
Any
IML instance.
Source code in src/tolvera/iml.py
def set(self, name, kwargs: dict) -> Any:\n \"\"\"Set IML instance.\n\n Args:\n name (str): Name of IML instance.\n kwargs (dict): IML instance kwargs.\n\n Raises:\n ValueError: Cannot replace 'tv' IML instance.\n ValueError: Cannot replace 'i' IML instance.\n ValueError: Cannot replace 'o' IML instance.\n NotImplementedError: set() with tuple not implemented yet.\n TypeError: set() requires dict|tuple, not _type_.\n Exception: Other exceptions.\n\n Returns:\n Any: IML instance.\n \"\"\"\n try:\n if name == \"ctx\" and type(kwargs) is not dict and type(kwargs) is not tuple:\n if name in self:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] '{name}' cannot be replaced.\"\n )\n self[name] = kwargs\n elif name == \"i\" or name == \"o\":\n if type(kwargs) is not dict:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] '{name}' is a reserved dict.\"\n )\n self[name] = kwargs\n elif type(kwargs) is dict:\n if \"type\" not in kwargs:\n raise ValueError(\n f\"[tolvera._iml.IMLDict] IMLDict requires 'type' key.\"\n )\n return self.add(name, kwargs[\"type\"], **kwargs)\n elif type(kwargs) is tuple:\n # iml_type = kwargs[0] # TODO: which index is 'iml_type'?\n # return self.add(name, iml_type, *kwargs)\n raise NotImplementedError(\n f\"[tolvera._iml.IMLDict] set() with tuple not implemented yet.\"\n )\n else:\n raise TypeError(\n f\"[tolvera._iml.IMLDict] set() requires dict|tuple, not {type(kwargs)}\"\n )\n except Exception as e:\n raise type(e)(f\"[tolvera._iml.IMLDict] {e}\") from e\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Fun","title":"IMLFun2Fun
","text":" Bases: IMLBase
IML function to function mapping.
Example def infun():\n return [0,0,0,0]\n\ndef outfun(vector):\n print('outvec', vector)\n\ntv.iml.test2test = {\n 'type': 'fun2fun', \n 'size': (4, 8), \n 'io': (infun, outfun),\n}\n
Source code in src/tolvera/iml.py
class IMLFun2Fun(IMLBase):\n \"\"\"IML function to function mapping.\n\n Example:\n ```py\n def infun():\n return [0,0,0,0]\n\n def outfun(vector):\n print('outvec', vector)\n\n tv.iml.test2test = {\n 'type': 'fun2fun', \n 'size': (4, 8), \n 'io': (infun, outfun),\n }\n ```\n \"\"\"\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLFun2Fun\n\n Args:\n kwargs:\n io (tuple, required): (callable, callable) input and output functions.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Fun requires 'io=(callable, callable)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Fun 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLFun2Fun 'io[1]' not callable, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n self.outfun = kwargs[\"io\"][1]\n self.outfun_params = inspect.signature(self.outfun).parameters\n super().__init__(**kwargs)\n\n def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n mapped = self.map(invec, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Fun.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLFun2Fun
Parameters:
Name Type Description Default kwargs
io (tuple, required): (callable, callable) input and output functions. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLFun2Fun\n\n Args:\n kwargs:\n io (tuple, required): (callable, callable) input and output functions.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Fun requires 'io=(callable, callable)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Fun 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLFun2Fun 'io[1]' not callable, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n self.outfun = kwargs[\"io\"][1]\n self.outfun_params = inspect.signature(self.outfun).parameters\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Fun.update","title":"update()
","text":"Update mapped data.
Returns:
Type Description list | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code in src/tolvera/iml.py
def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n mapped = self.map(invec, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2OSC","title":"IMLFun2OSC
","text":" Bases: IMLBase
IML function to OSC mapping
Example This will send the output vector to '/out/vec'.
def infun():\n return [0,0,0,0]\n\ntv.iml.test2osc = {\n 'type': 'fun2osc', \n 'size': (4, 8), \n 'io': (infun, 'out_vec'),\n}\n
Source code in src/tolvera/iml.py
class IMLFun2OSC(IMLBase):\n \"\"\"IML function to OSC mapping\n\n Example:\n This will send the output vector to '/out/vec'.\n\n ```py\n def infun():\n return [0,0,0,0]\n\n tv.iml.test2osc = {\n 'type': 'fun2osc', \n 'size': (4, 8), \n 'io': (infun, 'out_vec'),\n }\n ```\n \"\"\"\n def __init__(self, osc_map: OSCMap, **kwargs) -> None:\n \"\"\"Initialise IMLFun2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (callable, str) input function and output OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Vec requires 'io=(callable, str)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Vec 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert (\n isinstance(kwargs[\"io\"][1], str)\n ), f\"IMLFun2Vec 'io[1]' not str, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n self.osc_map = osc_map\n self.out_osc_route = kwargs[\"io\"][1]\n self.osc_map.send_list_inline(self.out_osc_route, self.update, kwargs[\"size\"][1], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"send\"][self.out_osc_route]['updater']\n super().__init__(**kwargs)\n\n def update(self) -> list[float]:\n \"\"\"Update mapped data.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n self.data.mapped = self.map(invec, **self.map_kw)\n return self.data.mapped.tolist()\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2OSC.__init__","title":"__init__(osc_map, **kwargs)
","text":"Initialise IMLFun2OSC
Parameters:
Name Type Description Default osc_map
(OSCMap, required)
OSCMap instance.
required kwargs
io (tuple, required): (callable, str) input function and output OSC route. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map: OSCMap, **kwargs) -> None:\n \"\"\"Initialise IMLFun2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (callable, str) input function and output OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Vec requires 'io=(callable, str)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Vec 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert (\n isinstance(kwargs[\"io\"][1], str)\n ), f\"IMLFun2Vec 'io[1]' not str, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n self.osc_map = osc_map\n self.out_osc_route = kwargs[\"io\"][1]\n self.osc_map.send_list_inline(self.out_osc_route, self.update, kwargs[\"size\"][1], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"send\"][self.out_osc_route]['updater']\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2OSC.update","title":"update()
","text":"Update mapped data.
Returns:
Type Description list[float]
list[float]: Mapped data.
Source code in src/tolvera/iml.py
def update(self) -> list[float]:\n \"\"\"Update mapped data.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n self.data.mapped = self.map(invec, **self.map_kw)\n return self.data.mapped.tolist()\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Vec","title":"IMLFun2Vec
","text":" Bases: IMLBase
IML function to vector mapping.
Output vector is accessed via tv.iml.o['name']
.
Example tv.iml.flock_p2vec = {\n 'type': 'fun2vec', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (tv.s.flock_p.to_vec, None),\n}\n# ...\nflock_s_outvec = tv.iml.o['flock_p2flock_s']\n
Source code in src/tolvera/iml.py
class IMLFun2Vec(IMLBase):\n \"\"\"IML function to vector mapping.\n\n Output vector is accessed via `tv.iml.o['name']`.\n\n Example:\n ```py\n tv.iml.flock_p2vec = {\n 'type': 'fun2vec', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (tv.s.flock_p.to_vec, None),\n }\n # ...\n flock_s_outvec = tv.iml.o['flock_p2flock_s']\n ```\n \"\"\"\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLFun2Vec\n\n Args:\n kwargs:\n io (tuple, required): (callable, None) input function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Vec requires 'io=(callable, None)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Vec 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert (\n kwargs[\"io\"][1] is None\n ), f\"IMLFun2Vec 'io[1]' not None, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n super().__init__(**kwargs)\n\n def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n self.data.mapped = self.map(invec, **self.map_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Vec.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLFun2Vec
Parameters:
Name Type Description Default kwargs
io (tuple, required): (callable, None) input function. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLFun2Vec\n\n Args:\n kwargs:\n io (tuple, required): (callable, None) input function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLFun2Vec requires 'io=(callable, None)' kwarg.\"\n assert callable(\n kwargs[\"io\"][0]\n ), f\"IMLFun2Vec 'io[0]' not callable, got {type(kwargs['io'][0])}.\"\n assert (\n kwargs[\"io\"][1] is None\n ), f\"IMLFun2Vec 'io[1]' not None, got {type(kwargs['io'][1])}.\"\n self.infun = kwargs[\"io\"][0]\n self.infun_params = inspect.signature(self.infun).parameters\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLFun2Vec.update","title":"update()
","text":"Update mapped data.
Returns:
Type Description list | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code in src/tolvera/iml.py
def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.infun_params) > 0:\n invec = self.infun(**self.infun_kw)\n else:\n invec = self.infun()\n self.data.mapped = self.map(invec, **self.map_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Fun","title":"IMLOSC2Fun
","text":" Bases: IMLBase
IML OSC to function mapping
Example def outfun(vector):\n print('outvec', vector)\n\ntv.iml.test2fun = {\n 'type': 'osc2fun', \n 'size': (4, 8), \n 'io': ('in_vec', outfun),\n}\n
Source code in src/tolvera/iml.py
class IMLOSC2Fun(IMLBase):\n \"\"\"IML OSC to function mapping\n\n Example:\n ```py\n def outfun(vector):\n print('outvec', vector)\n\n tv.iml.test2fun = {\n 'type': 'osc2fun', \n 'size': (4, 8), \n 'io': ('in_vec', outfun),\n }\n ```\n \"\"\"\n def __init__(self, osc_map, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2Fun\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (str, callable) input OSC route and output function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2Fun requires 'io=(str, callable)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2Fun 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLOSC2Fun 'io[1]' is not callable, got {type(kwargs['io'][1])}.\"\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.outfun = kwargs[\"io\"][1]\n self.outfun_params = inspect.signature(self.outfun).parameters\n super().__init__(**kwargs)\n\n def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n mapped = self.map(vector, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Fun.__init__","title":"__init__(osc_map, **kwargs)
","text":"Initialise IMLOSC2Fun
Parameters:
Name Type Description Default osc_map
(OSCMap, required)
OSCMap instance.
required kwargs
io (tuple, required): (str, callable) input OSC route and output function. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2Fun\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (str, callable) input OSC route and output function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2Fun requires 'io=(str, callable)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2Fun 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLOSC2Fun 'io[1]' is not callable, got {type(kwargs['io'][1])}.\"\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.outfun = kwargs[\"io\"][1]\n self.outfun_params = inspect.signature(self.outfun).parameters\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Fun.update","title":"update(vector)
","text":"Update mapped data.
Parameters:
Name Type Description Default vector
list[float]
Input vector.
required Returns:
Type Description list[float]
list[float]: Mapped data.
Source code in src/tolvera/iml.py
def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n mapped = self.map(vector, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2OSC","title":"IMLOSC2OSC
","text":" Bases: IMLBase
IML OSC to OSC mapping
Example '/in/vec' is mapped and the output sent to '/out/vec'.
tv.iml.test2fun = {\n 'type': 'osc2osc', \n 'size': (4, 8), \n 'io': ('in_vec', 'out_vec'),\n}\n
Source code in src/tolvera/iml.py
class IMLOSC2OSC(IMLBase):\n \"\"\"IML OSC to OSC mapping\n\n Example:\n '/in/vec' is mapped and the output sent to '/out/vec'.\n\n ```py\n tv.iml.test2fun = {\n 'type': 'osc2osc', \n 'size': (4, 8), \n 'io': ('in_vec', 'out_vec'),\n }\n ```\n \"\"\"\n def __init__(self, osc_map: OSCMap, osc: iiOSC, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n osc (OSC): iipyper OSC instance.\n kwargs:\n io (tuple, required): (str, str) input and output OSC routes.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2OSC requires 'io=(str, str)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2OSC 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert (\n type(kwargs[\"io\"][1]) is str\n ), f\"IMLOSC2OSC 'io[1]' is not str, got {type(kwargs['io'][1])}.\"\n self.osc = osc\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.out_osc_route = kwargs[\"io\"][1]\n super().__init__(**kwargs)\n\n def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n self.data.mapped = self.map(vector, **self.map_kw)\n self.osc.host.send(self.out_osc_route, *self.data.mapped.tolist())\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2OSC.__init__","title":"__init__(osc_map, osc, **kwargs)
","text":"Initialise IMLOSC2OSC
Parameters:
Name Type Description Default osc_map
(OSCMap, required)
OSCMap instance.
required osc
OSC
iipyper OSC instance.
required kwargs
io (tuple, required): (str, str) input and output OSC routes. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map: OSCMap, osc: iiOSC, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n osc (OSC): iipyper OSC instance.\n kwargs:\n io (tuple, required): (str, str) input and output OSC routes.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2OSC requires 'io=(str, str)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2OSC 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert (\n type(kwargs[\"io\"][1]) is str\n ), f\"IMLOSC2OSC 'io[1]' is not str, got {type(kwargs['io'][1])}.\"\n self.osc = osc\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.out_osc_route = kwargs[\"io\"][1]\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2OSC.update","title":"update(vector)
","text":"Update mapped data.
Parameters:
Name Type Description Default vector
list[float]
Input vector.
required Returns:
Type Description list[float]
list[float]: Mapped data.
Source code in src/tolvera/iml.py
def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n self.data.mapped = self.map(vector, **self.map_kw)\n self.osc.host.send(self.out_osc_route, *self.data.mapped.tolist())\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Vec","title":"IMLOSC2Vec
","text":" Bases: IMLBase
IML OSC to vector mapping
Example This will map the OSC input to the output vector and store it in tv.iml.o['name']
.
tv.iml.test2vec = {\n 'type': 'osc2vec', \n 'size': (4, 8), \n 'io': ('in_vec', None),\n}\n# ...\nflock_s_outvec = tv.iml.o['flock_p2flock_s']\n
Source code in src/tolvera/iml.py
class IMLOSC2Vec(IMLBase):\n \"\"\"IML OSC to vector mapping\n\n Example:\n This will map the OSC input to the output vector and store it in `tv.iml.o['name']`.\n\n ```py\n tv.iml.test2vec = {\n 'type': 'osc2vec', \n 'size': (4, 8), \n 'io': ('in_vec', None),\n }\n # ...\n flock_s_outvec = tv.iml.o['flock_p2flock_s']\n ```\n \"\"\"\n def __init__(self, osc_map, outvecs: dict, name: str, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2Vec\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n outvecs (dict): Output vectors dict.\n name (str): Name of output vector.\n kwargs:\n io (tuple, required): (str, None) input OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2Vec requires 'io=(str, None)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2Vec 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert (\n kwargs[\"io\"][1] is None\n ), f\"IMLOSC2Vec 'io[1]' is not None, got {type(kwargs['io'][1])}.\"\n self.name = kwargs.get(\"name\", None)\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.outvecs = outvecs\n self.name = name\n super().__init__(**kwargs)\n\n def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n self.data.mapped = self.map(vector, **self.map_kw)\n if self.name is not None:\n self.outvecs[self.name] = self.data.mapped\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Vec.__init__","title":"__init__(osc_map, outvecs, name, **kwargs)
","text":"Initialise IMLOSC2Vec
Parameters:
Name Type Description Default osc_map
(OSCMap, required)
OSCMap instance.
required outvecs
dict
Output vectors dict.
required name
str
Name of output vector.
required kwargs
io (tuple, required): (str, None) input OSC route. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map, outvecs: dict, name: str, **kwargs) -> None:\n \"\"\"Initialise IMLOSC2Vec\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n outvecs (dict): Output vectors dict.\n name (str): Name of output vector.\n kwargs:\n io (tuple, required): (str, None) input OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLOSC2Vec requires 'io=(str, None)' kwarg.\"\n assert (\n type(kwargs[\"io\"][0]) is str\n ), f\"IMLOSC2Vec 'io[0]' not str, got {type(kwargs['io'][0])}.\"\n assert (\n kwargs[\"io\"][1] is None\n ), f\"IMLOSC2Vec 'io[1]' is not None, got {type(kwargs['io'][1])}.\"\n self.name = kwargs.get(\"name\", None)\n self.osc_map = osc_map\n self.osc_in_route = kwargs[\"io\"][0]\n self.osc_map.receive_list_inline(self.osc_in_route, self.update, kwargs[\"size\"][0], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"receive\"][self.osc_in_route]['updater']\n self.outvecs = outvecs\n self.name = name\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLOSC2Vec.update","title":"update(vector)
","text":"Update mapped data.
Parameters:
Name Type Description Default vector
list[float]
Input vector.
required Returns:
Type Description list[float]
list[float]: Mapped data.
Source code in src/tolvera/iml.py
def update(self, vector: list[float]) -> list[float]:\n \"\"\"Update mapped data.\n\n Args:\n vector (list[float]): Input vector.\n\n Returns:\n list[float]: Mapped data.\n \"\"\"\n self.data.mapped = self.map(vector, **self.map_kw)\n if self.name is not None:\n self.outvecs[self.name] = self.data.mapped\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Fun","title":"IMLVec2Fun
","text":" Bases: IMLBase
IML vector to function mapping
Example def update(outvec):\n print('outvec', outvec)\n\ntv.iml.flock_p2fun = {\n 'type': 'vec2fun', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (None, update),\n}\n
Source code in src/tolvera/iml.py
class IMLVec2Fun(IMLBase):\n \"\"\"IML vector to function mapping\n\n Example:\n ```py\n def update(outvec):\n print('outvec', outvec)\n\n tv.iml.flock_p2fun = {\n 'type': 'vec2fun', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (None, update),\n }\n ```\n \"\"\"\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLVec2Fun\n\n Args:\n kwargs:\n io (tuple, required): (None, callable) output function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLVec2Fun requires 'io=(None, callable)' kwarg.\"\n assert (\n kwargs[\"io\"][0] is None\n ), f\"IMLVec2Fun 'io[0]' not None, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLVec2Fun 'io[1]' not callable, got {type(kwargs['io'][1])}.\"\n self.outfun = kwargs[\"io\"][1]\n super().__init__(**kwargs)\n\n def update(self, invec: list|torch.Tensor|np.ndarray) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Args:\n invec (list | torch.Tensor | np.ndarray): Input vector.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n mapped = self.map(invec, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Fun.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLVec2Fun
Parameters:
Name Type Description Default kwargs
io (tuple, required): (None, callable) output function. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLVec2Fun\n\n Args:\n kwargs:\n io (tuple, required): (None, callable) output function.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLVec2Fun requires 'io=(None, callable)' kwarg.\"\n assert (\n kwargs[\"io\"][0] is None\n ), f\"IMLVec2Fun 'io[0]' not None, got {type(kwargs['io'][0])}.\"\n assert callable(\n kwargs[\"io\"][1]\n ), f\"IMLVec2Fun 'io[1]' not callable, got {type(kwargs['io'][1])}.\"\n self.outfun = kwargs[\"io\"][1]\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Fun.update","title":"update(invec)
","text":"Update mapped data.
Parameters:
Name Type Description Default invec
list | Tensor | ndarray
Input vector.
required Returns:
Type Description list | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code in src/tolvera/iml.py
def update(self, invec: list|torch.Tensor|np.ndarray) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Args:\n invec (list | torch.Tensor | np.ndarray): Input vector.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n mapped = self.map(invec, **self.map_kw)\n self.data.mapped = self.outfun(mapped, **self.outfun_kw)\n return self.data.mapped\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2OSC","title":"IMLVec2OSC
","text":" Bases: IMLBase
IML vector to OSC mapping.
Example Sends the output vector to '/tolvera/flock'.
tv.iml.flock_p2osc = {\n 'type': 'vec2osc', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (None, 'tolvera_flock'),\n}\n
Source code in src/tolvera/iml.py
class IMLVec2OSC(IMLBase):\n \"\"\"IML vector to OSC mapping.\n\n Example:\n Sends the output vector to '/tolvera/flock'.\n\n ```py\n tv.iml.flock_p2osc = {\n 'type': 'vec2osc', \n 'size': (tv.s.flock_p.size, 8), \n 'io': (None, 'tolvera_flock'),\n }\n ```\n \"\"\"\n def __init__(self, osc_map: OSCMap, **kwargs) -> None:\n \"\"\"Initialise IMLVec2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (None, str) output OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLVec2OSC requires 'io=(None, str)' kwarg.\"\n assert (\n kwargs[\"io\"][0] is None\n ), f\"IMLVec2OSC 'io[0]' is not None, got {type(kwargs['io'][0])}.\"\n assert (\n type(kwargs[\"io\"][1]) is str\n ), f\"IMLVec2OSC 'io[1]' is not str, got {type(kwargs['io'][1])}.\"\n self.osc_map = osc_map\n self.out_osc_route = kwargs[\"io\"][1]\n self.osc_map.send_list_inline(self.out_osc_route, self.update, kwargs[\"size\"][1], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"send\"][self.out_osc_route]['updater']\n super().__init__(**kwargs)\n\n def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.pairs) == 0:\n return None\n if self.invec is not None:\n self.data.mapped = self.map(self.invec, **self.map_kw)\n if hasattr(self, \"lag\") and type(self.lag) is Lag:\n self.lag_mapped_data()\n return self.data.mapped.tolist()\n else:\n return None\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2OSC.__init__","title":"__init__(osc_map, **kwargs)
","text":"Initialise IMLVec2OSC
Parameters:
Name Type Description Default osc_map
(OSCMap, required)
OSCMap instance.
required kwargs
io (tuple, required): (None, str) output OSC route. see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
def __init__(self, osc_map: OSCMap, **kwargs) -> None:\n \"\"\"Initialise IMLVec2OSC\n\n Args:\n osc_map (OSCMap, required): OSCMap instance.\n kwargs:\n io (tuple, required): (None, str) output OSC route.\n see IMLBase kwargs.\n \"\"\"\n assert \"io\" in kwargs, f\"IMLVec2OSC requires 'io=(None, str)' kwarg.\"\n assert (\n kwargs[\"io\"][0] is None\n ), f\"IMLVec2OSC 'io[0]' is not None, got {type(kwargs['io'][0])}.\"\n assert (\n type(kwargs[\"io\"][1]) is str\n ), f\"IMLVec2OSC 'io[1]' is not str, got {type(kwargs['io'][1])}.\"\n self.osc_map = osc_map\n self.out_osc_route = kwargs[\"io\"][1]\n self.osc_map.send_list_inline(self.out_osc_route, self.update, kwargs[\"size\"][1], count=kwargs.get(\"update_rate\", 10))\n kwargs[\"updater\"] = self.osc_map.dict[\"send\"][self.out_osc_route]['updater']\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2OSC.update","title":"update()
","text":"Update mapped data.
Returns:
Type Description list | Tensor | ndarray
list|torch.Tensor|np.ndarray: Mapped data.
Source code in src/tolvera/iml.py
def update(self) -> list|torch.Tensor|np.ndarray:\n \"\"\"Update mapped data.\n\n Returns:\n list|torch.Tensor|np.ndarray: Mapped data.\n \"\"\"\n if len(self.pairs) == 0:\n return None\n if self.invec is not None:\n self.data.mapped = self.map(self.invec, **self.map_kw)\n if hasattr(self, \"lag\") and type(self.lag) is Lag:\n self.lag_mapped_data()\n return self.data.mapped.tolist()\n else:\n return None\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Vec","title":"IMLVec2Vec
","text":" Bases: IMLBase
IML vector to vector mapping.
Input vector is accessed via tv.iml.i['name']
. Output vector is accessed via tv.iml.o['name']
.
Example tv.iml.flock_p2flock_s = {\n 'type': 'vec2vec', \n 'size': (tv.s.flock_p.size, tv.s.flock_s.size)\n}\n\ndef update():\n invec = tv.s.flock_p.to_vec()\n tv.iml.i = {'flock_p2flock_s': invec}\n flock_s_outvec = tv.iml.o['flock_p2flock_s']\n if flock_s_outvec is not None:\n tv.s.flock_s.from_vec(flock_s_outvec)\n
Parameters:
Name Type Description Default kwargs
see IMLBase kwargs.
{}
Source code in src/tolvera/iml.py
class IMLVec2Vec(IMLBase):\n \"\"\"IML vector to vector mapping.\n\n Input vector is accessed via `tv.iml.i['name']`.\n Output vector is accessed via `tv.iml.o['name']`.\n\n Example:\n ```py\n tv.iml.flock_p2flock_s = {\n 'type': 'vec2vec', \n 'size': (tv.s.flock_p.size, tv.s.flock_s.size)\n }\n\n def update():\n invec = tv.s.flock_p.to_vec()\n tv.iml.i = {'flock_p2flock_s': invec}\n flock_s_outvec = tv.iml.o['flock_p2flock_s']\n if flock_s_outvec is not None:\n tv.s.flock_s.from_vec(flock_s_outvec)\n ```\n\n Args:\n kwargs:\n see IMLBase kwargs.\n \"\"\"\n\n def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLVec2Vec\"\"\"\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.IMLVec2Vec.__init__","title":"__init__(**kwargs)
","text":"Initialise IMLVec2Vec
Source code in src/tolvera/iml.py
def __init__(self, **kwargs) -> None:\n \"\"\"Initialise IMLVec2Vec\"\"\"\n super().__init__(**kwargs)\n
"},{"location":"reference/tolvera/iml/#tolvera.iml.rand_select","title":"rand_select(method='rand')
","text":"Select randomisation method.
Parameters:
Name Type Description Default method
str
Randomisation method. Defaults to \"rand\".
'rand'
Raises:
Type Description ValueError
Invalid method.
Returns:
Name Type Description callable
Randomisation method.
Source code in src/tolvera/iml.py
def rand_select(method=\"rand\"):\n \"\"\"Select randomisation method.\n\n Args:\n method (str, optional): Randomisation method. Defaults to \"rand\".\n\n Raises:\n ValueError: Invalid method.\n\n Returns:\n callable: Randomisation method.\n \"\"\"\n match method:\n case \"rand\":\n return rand_n\n case \"uniform\":\n return rand_uniform\n case \"normal\":\n return rand_normal\n case \"exponential\":\n return rand_exponential\n case \"cauchy\":\n return rand_cauchy\n case \"lognormal\":\n return rand_lognormal\n case \"sigmoid\":\n return rand_sigmoid\n case \"beta\":\n return rand_beta\n case _:\n raise ValueError(\n f\"[tolvera._iml.rand_select] Invalid method '{method}'. Valid methods: {RAND_METHODS}.\"\n )\n
"},{"location":"reference/tolvera/mp/","title":"Mp","text":""},{"location":"reference/tolvera/mp/#tolvera.mp.HandLandmark","title":"HandLandmark
","text":" Bases: IntEnum
The 21 hand landmarks.
Source code in src/tolvera/mp.py
class HandLandmark(enum.IntEnum):\n \"\"\"The 21 hand landmarks.\"\"\"\n WRIST = 0\n THUMB_CMC = 1\n THUMB_MCP = 2\n THUMB_IP = 3\n THUMB_TIP = 4\n INDEX_FINGER_MCP = 5\n INDEX_FINGER_PIP = 6\n INDEX_FINGER_DIP = 7\n INDEX_FINGER_TIP = 8\n MIDDLE_FINGER_MCP = 9\n MIDDLE_FINGER_PIP = 10\n MIDDLE_FINGER_DIP = 11\n MIDDLE_FINGER_TIP = 12\n RING_FINGER_MCP = 13\n RING_FINGER_PIP = 14\n RING_FINGER_DIP = 15\n RING_FINGER_TIP = 16\n PINKY_MCP = 17\n PINKY_PIP = 18\n PINKY_DIP = 19\n PINKY_TIP = 20\n
"},{"location":"reference/tolvera/npndarray_dict/","title":"Npndarray dict","text":"Module for working with dictionary of NumPy ndarrays.
Primaril used by State.
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict","title":"NpNdarrayDict
","text":"A class that encapsulates a dictionary of NumPy ndarrays, each associated with a specific data type and a defined min-max range. It provides a structured and efficient way to manage and manipulate multidimensional arrays with constraints on their values.
Attributes:
Name Type Description data
Dict[str, Dict[str, Union[ndarray, Any]]]
A dictionary where each key represents an attribute,
shape
Tuple[int, int]
The shape of the ndarrays, which is consistent across all attributes.
Example state = NpNdarrayDict({ 'i': (np.int32, 2, 10), 'f': (np.float32, 0., 1.), 'v2': (np_vec2, 0., 1.), 'v3': (np_vec3, 0., 1.), 'v4': (np_vec4, 0., 1.), }, (2,2)) state.set_value('i', (0, 0), 5) print(state.get_value('i', (0, 0))) 5
Source code in src/tolvera/npndarray_dict.py
class NpNdarrayDict:\n \"\"\"\n A class that encapsulates a dictionary of NumPy ndarrays, each associated with a specific data type and a defined min-max range.\n It provides a structured and efficient way to manage and manipulate multidimensional arrays with constraints on their values.\n\n Attributes:\n data (Dict[str, Dict[str, Union[np.ndarray, Any]]]): A dictionary where each key represents an attribute,\n and the value is another dictionary with keys 'array', 'min', and 'max', representing the ndarray,\n its minimum value, and its maximum value, respectively.\n shape (Tuple[int, int]): The shape of the ndarrays, which is consistent across all attributes.\n\n Example:\n state = NpNdarrayDict({\n 'i': (np.int32, 2, 10),\n 'f': (np.float32, 0., 1.),\n 'v2': (np_vec2, 0., 1.),\n 'v3': (np_vec3, 0., 1.),\n 'v4': (np_vec4, 0., 1.),\n }, (2,2))\n state.set_value('i', (0, 0), 5)\n print(state.get_value('i', (0, 0)))\n 5\n \"\"\"\n\n def __init__(self, data_dict: dict[str, tuple[Any, Any, Any]], shape: tuple[int]):\n \"\"\"\n Initialize the State class.\n\n Args:\n data_dict: A dictionary where keys are attribute names and values are tuples\n of (dtype, min_value, max_value).\n shape: The shape of the numpy arrays for each attribute.\n\n \"\"\"\n self.shape = shape\n self.init(data_dict, shape)\n\n def init(\n self, data_dict: dict[str, tuple[Any, Any, Any]], shape: tuple[int]\n ) -> None:\n self.dict = {}\n self.data = {}\n self.size = 0\n for key, (dtype, min_val, max_val) in data_dict.items():\n dshape = self.shape\n length = 1\n # handle np_vec2, np_vec3, np_vec4\n if isinstance(dtype, np.ndarray):\n dshape = dshape + dtype.shape\n length = dtype.shape[0]\n dtype = np.float32\n self.dict[key] = {\n \"dtype\": dtype,\n \"min\": min_val,\n \"max\": max_val,\n \"length\": length,\n \"shape\": dshape,\n \"ndims\": len(dshape),\n }\n self.data[key] = np.zeros(dshape, dtype=dtype)\n size = self.data[key].size\n self.dict[key][\"size\"] = size\n self.size += size\n\n \"\"\"\n to|from vec | list (iml)\n \"\"\"\n\n def from_vec(self, vec: list):\n vec_start = 0\n for key in self.data.keys():\n attr_vec_size = self.dict[key][\"size\"]\n attr_vec = vec[vec_start : vec_start + attr_vec_size]\n self.attr_from_vec(key, attr_vec)\n vec_start += attr_vec_size\n\n def to_vec(self) -> list:\n vec = []\n for key in self.data.keys():\n vec += self.attr_to_vec(key).tolist()\n return vec\n\n def attr_from_vec(self, attr: str, vec: list):\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n attr_shape, attr_dtype = self.dict[attr][\"shape\"], self.dict[attr][\"dtype\"]\n if len(vec) != np.prod(attr_shape):\n raise ValueError(\n f\"Length of vec {len(vec)} does not match the shape of {attr} {attr_shape}\"\n )\n nparr = np.array(vec, dtype=attr_dtype)\n if len(attr_shape) > 1:\n nparr = np.reshape(nparr, attr_shape)\n try:\n self.data[attr] = nparr\n except ValueError as e:\n print(f\"ValueError occurred while setting {attr}: {e}\")\n raise\n\n def attr_to_vec(self, attr: str) -> list:\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n vec = self.data[attr].flatten()\n return vec\n\n def slice_from_vec(\n self, slice_args: Union[int, tuple[int, ...], slice], slice_vec: list\n ):\n # TODO: unique slice obj needed per key...\n # slice_obj = create_safe_slice(slice_args)\n raise NotImplementedError(f\"slice_from_vec()\")\n\n def slice_to_vec(self, slice_args: Union[int, tuple[int, ...], slice]) -> list:\n # TODO: unique slice obj needed per key...\n # vec = []\n # for key in self.data.keys():\n # slice_obj = create_safe_slice(slice_args)\n # vec += self.attr_slice_to_vec(key, slice_obj)\n # return vec\n raise NotImplementedError(f\"slice_from_vec()\")\n\n def attr_slice_from_vec(\n self, attr: str, slice_args: Union[int, tuple[int, ...], slice], slice_vec: list\n ):\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n slice_obj = create_safe_slice(slice_args)\n attr_shape, attr_dtype = self.dict[attr][\"shape\"], self.dict[attr][\"dtype\"]\n nparr = np.array(slice_vec, dtype=attr_dtype)\n if len(attr_shape) > 1:\n nparr = np.reshape(nparr, attr_shape)\n try:\n self.data[attr][slice_obj] = nparr\n except ValueError as e:\n print(f\"ValueError occurred while setting slice: {e}\")\n raise\n\n def attr_slice_to_vec(\n self, attr: str, slice_args: Union[int, tuple[int, ...], slice]\n ) -> list:\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n slice_obj = create_safe_slice(slice_args)\n vec = self.data[attr][slice_obj].flatten()\n return vec\n\n \"\"\"\n vec slice helpers\n \"\"\"\n\n def get_slice_size(self, slice_args: Union[int, tuple[int, ...], slice]) -> int:\n slice_obj = create_safe_slice(slice_args)\n return np.sum([self.data[key][slice_obj].size for key in self.data.keys()])\n\n def get_attr_slice_size(\n self, attr: str, slice_args: Union[int, tuple[int, ...], slice]\n ) -> int:\n if attr not in self.data:\n raise KeyError(f\"Key {attr} not in {self.data.keys()}\")\n slice_obj = create_safe_slice(slice_args)\n return self.data[attr][slice_obj].size\n\n \"\"\"\n to|from vec_args (simple osc)\n \"\"\"\n\n \"\"\"\n to|from ndarray | ndarraydict (serialised formats, complex osc)\n \"\"\"\n\n \"\"\"\n ...\n \"\"\"\n\n def set_slice_from_dict(self, slice_indices: tuple, slice_values: dict):\n for key, values in slice_values.items():\n if key not in self.data:\n raise KeyError(f\"Key {key} not found in data\")\n\n array_slice = self.data[key][slice_indices]\n if array_slice.shape != np.array(values).shape:\n raise ValueError(\n f\"Shape {array_slice.shape} of values for key {key} does not match the shape of the slice {np.array(values).shape}\"\n )\n\n self.data[key][slice_indices] = np.array(\n values, dtype=self.dict[key][\"dtype\"]\n )\n\n # def list_to_dict(self, _list: list) -> dict:\n # \"\"\"\n # Convert a flat list to a dictionary.\n\n # :param _list: The flat list to convert.\n # :return: A dictionary that matches self.dict.\n # \"\"\"\n # pass\n\n # def list_len_to_dict_shape(self, _list: list) -> dict:\n # \"\"\"\n # Convert a flat list to a dictionary of shapes.\n\n # :param _list: The flat list to convert.\n # :return: shape of the dictionary of _list based on self.shape.\n # \"\"\"\n # list_len = len(_list)\n # dict_shape = ()\n # for key in self.data.keys():\n # dict_shape += self.dict[key]['shape'][1:]\n # dict_len = np.prod(dict_shape)\n # if list_len != dict_len:\n # raise ValueError(f\"Length of list {_list} does not match the length of the dictionary {dict_len}\")\n # return dict_shape\n\n def set_slice_from_list(self, slice_indices: tuple, slice_values_list: list):\n list_index = 0\n\n for key in self.data.keys():\n # Determine the total number of elements required for the current key\n num_elements = np.prod(self.dict[key][\"shape\"][1:])\n print(f\"[{key}] num_elements: {num_elements}\")\n\n # Extract the slice from slice_values_list and reshape if necessary\n slice_shape = self.dict[key][\"shape\"][1:]\n slice = slice_values_list[list_index : list_index + num_elements]\n print(f\"[{key}] slice_shape: {slice_shape}, slice: {slice}\")\n\n # Check if the slice has the correct length\n if len(slice) != num_elements:\n raise ValueError(\n f\"Slice length {len(slice)} for key {key} does not match the number of elements {num_elements}\"\n )\n\n # Reshape the slice for ndarrays with more than 2 dimensions\n if len(slice_shape) > 1:\n slice = np.reshape(slice, slice_shape)\n print(f\"[{key}] (reshaping) slice_shape: {slice_shape}, slice: {slice}\")\n\n # Assign the slice to the corresponding key\n self.data[key][slice_indices] = slice\n\n list_index += num_elements\n print(f\"[{key}] list_index: {list_index}, num_elements: {num_elements}\")\n\n print(f\"data: {self.data}\")\n\n # Check if there are extra values in slice_values_list\n if list_index != len(slice_values_list):\n raise ValueError(\n f\"Extra values {slice_values_list[list_index:]} in slice_values_list {slice_values_list} that do not correspond to any array\"\n )\n\n def set_data(self, new_data: dict[str, np.ndarray]) -> None:\n \"\"\"\n Set the data with a new data dictionary.\n\n Args:\n new_data: A dictionary representing the new data, where each key is an\n attribute and the value is a numpy array.\n\n Raises:\n ValueError: If the new data is invalid (e.g., wrong shape, type, or value range).\n \"\"\"\n try:\n self.data = new_data\n except ValueError as e:\n print(f\"ValueError occurred while setting data: {e}\")\n raise\n\n def get_data(self) -> dict[str, np.ndarray]:\n \"\"\"\n Get the entire current data as a dictionary.\n\n Returns:\n A dictionary where each key is an attribute and the value is a numpy array.\n \"\"\"\n return self.data\n\n def validate(self, new_state: dict[str, np.ndarray]) -> bool:\n raise NotImplementedError(\"validate() not implemented\")\n\n def randomise(self) -> None:\n \"\"\"\n Randomize the entire state dictionary based on the datatype, minimum,\n and maximum values for each attribute.\n \"\"\"\n for key in self.data:\n data_type = self.dict[key][\"dtype\"]\n min_val = self.dict[key][\"min\"]\n max_val = self.dict[key][\"max\"]\n shape = self.dict[key][\"shape\"]\n\n if np.issubdtype(data_type, np.integer):\n self.data[key] = np.random.randint(\n min_val, max_val + 1, size=shape, dtype=data_type\n )\n elif np.issubdtype(data_type, np.floating):\n self.data[key] = np.random.uniform(min_val, max_val, size=shape).astype(\n data_type\n )\n # Add more conditions here if you have other data types\n\n def attr_apply(self, key: str, func: Callable[[np.ndarray], np.ndarray]) -> None:\n \"\"\"\n Apply a user-defined function to the array of a specified key.\n\n Args:\n key: The attribute key.\n func: A function that takes a numpy array and returns a numpy array.\n\n Raises:\n KeyError: If the key is not found.\n \"\"\"\n if key not in self.data:\n raise KeyError(f\"Key {key} not found\")\n\n self.data[key] = func(self.data[key])\n\n def attr_broadcast(\n self,\n key: str,\n other: Union[np.ndarray, \"NpNdarrayDict\"],\n op: Callable[[np.ndarray, np.ndarray], np.ndarray],\n ) -> None:\n \"\"\"\n Perform a broadcasting operation between the array of the specified key and another array or NpNdarrayDict.\n\n Args:\n key: The key of the array in the dictionary to operate on.\n other: The other array or NpNdarrayDict to use in the operation.\n op: A function to perform the operation. This should be a NumPy ufunc (like np.add, np.multiply).\n\n Raises:\n KeyError: If the key is not found in the dictionary.\n ValueError: If the operation cannot be broadcasted or if it violates the min-max constraints.\n \"\"\"\n if key not in self.data:\n raise KeyError(f\"Key {key} not found\")\n\n if isinstance(other, NpNdarrayDict):\n if other.shape != self.shape:\n raise ValueError(\"Shapes of NpNdarrayDict objects do not match\")\n other_array = other.data[key] # Assuming we want to operate on the same key\n elif isinstance(other, np.ndarray):\n other_array = other\n else:\n raise ValueError(\n \"The 'other' parameter must be either a NumPy ndarray or NpNdarrayDict\"\n )\n\n result = op(self.data[key], other_array)\n\n # Check if the result is within the allowed min-max range\n if np.any(result < self.dict[key][\"min\"]) or np.any(\n result > self.dict[key][\"max\"]\n ):\n raise ValueError(\"Operation result violates min-max constraints\")\n\n self.data[key] = result\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.__init__","title":"__init__(data_dict, shape)
","text":"Initialize the State class.
Parameters:
Name Type Description Default data_dict
dict[str, tuple[Any, Any, Any]]
A dictionary where keys are attribute names and values are tuples of (dtype, min_value, max_value).
required shape
tuple[int]
The shape of the numpy arrays for each attribute.
required Source code in src/tolvera/npndarray_dict.py
def __init__(self, data_dict: dict[str, tuple[Any, Any, Any]], shape: tuple[int]):\n \"\"\"\n Initialize the State class.\n\n Args:\n data_dict: A dictionary where keys are attribute names and values are tuples\n of (dtype, min_value, max_value).\n shape: The shape of the numpy arrays for each attribute.\n\n \"\"\"\n self.shape = shape\n self.init(data_dict, shape)\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.attr_apply","title":"attr_apply(key, func)
","text":"Apply a user-defined function to the array of a specified key.
Parameters:
Name Type Description Default key
str
The attribute key.
required func
Callable[[ndarray], ndarray]
A function that takes a numpy array and returns a numpy array.
required Raises:
Type Description KeyError
If the key is not found.
Source code in src/tolvera/npndarray_dict.py
def attr_apply(self, key: str, func: Callable[[np.ndarray], np.ndarray]) -> None:\n \"\"\"\n Apply a user-defined function to the array of a specified key.\n\n Args:\n key: The attribute key.\n func: A function that takes a numpy array and returns a numpy array.\n\n Raises:\n KeyError: If the key is not found.\n \"\"\"\n if key not in self.data:\n raise KeyError(f\"Key {key} not found\")\n\n self.data[key] = func(self.data[key])\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.attr_broadcast","title":"attr_broadcast(key, other, op)
","text":"Perform a broadcasting operation between the array of the specified key and another array or NpNdarrayDict.
Parameters:
Name Type Description Default key
str
The key of the array in the dictionary to operate on.
required other
Union[ndarray, NpNdarrayDict]
The other array or NpNdarrayDict to use in the operation.
required op
Callable[[ndarray, ndarray], ndarray]
A function to perform the operation. This should be a NumPy ufunc (like np.add, np.multiply).
required Raises:
Type Description KeyError
If the key is not found in the dictionary.
ValueError
If the operation cannot be broadcasted or if it violates the min-max constraints.
Source code in src/tolvera/npndarray_dict.py
def attr_broadcast(\n self,\n key: str,\n other: Union[np.ndarray, \"NpNdarrayDict\"],\n op: Callable[[np.ndarray, np.ndarray], np.ndarray],\n) -> None:\n \"\"\"\n Perform a broadcasting operation between the array of the specified key and another array or NpNdarrayDict.\n\n Args:\n key: The key of the array in the dictionary to operate on.\n other: The other array or NpNdarrayDict to use in the operation.\n op: A function to perform the operation. This should be a NumPy ufunc (like np.add, np.multiply).\n\n Raises:\n KeyError: If the key is not found in the dictionary.\n ValueError: If the operation cannot be broadcasted or if it violates the min-max constraints.\n \"\"\"\n if key not in self.data:\n raise KeyError(f\"Key {key} not found\")\n\n if isinstance(other, NpNdarrayDict):\n if other.shape != self.shape:\n raise ValueError(\"Shapes of NpNdarrayDict objects do not match\")\n other_array = other.data[key] # Assuming we want to operate on the same key\n elif isinstance(other, np.ndarray):\n other_array = other\n else:\n raise ValueError(\n \"The 'other' parameter must be either a NumPy ndarray or NpNdarrayDict\"\n )\n\n result = op(self.data[key], other_array)\n\n # Check if the result is within the allowed min-max range\n if np.any(result < self.dict[key][\"min\"]) or np.any(\n result > self.dict[key][\"max\"]\n ):\n raise ValueError(\"Operation result violates min-max constraints\")\n\n self.data[key] = result\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.get_data","title":"get_data()
","text":"Get the entire current data as a dictionary.
Returns:
Type Description dict[str, ndarray]
A dictionary where each key is an attribute and the value is a numpy array.
Source code in src/tolvera/npndarray_dict.py
def get_data(self) -> dict[str, np.ndarray]:\n \"\"\"\n Get the entire current data as a dictionary.\n\n Returns:\n A dictionary where each key is an attribute and the value is a numpy array.\n \"\"\"\n return self.data\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.randomise","title":"randomise()
","text":"Randomize the entire state dictionary based on the datatype, minimum, and maximum values for each attribute.
Source code in src/tolvera/npndarray_dict.py
def randomise(self) -> None:\n \"\"\"\n Randomize the entire state dictionary based on the datatype, minimum,\n and maximum values for each attribute.\n \"\"\"\n for key in self.data:\n data_type = self.dict[key][\"dtype\"]\n min_val = self.dict[key][\"min\"]\n max_val = self.dict[key][\"max\"]\n shape = self.dict[key][\"shape\"]\n\n if np.issubdtype(data_type, np.integer):\n self.data[key] = np.random.randint(\n min_val, max_val + 1, size=shape, dtype=data_type\n )\n elif np.issubdtype(data_type, np.floating):\n self.data[key] = np.random.uniform(min_val, max_val, size=shape).astype(\n data_type\n )\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.NpNdarrayDict.set_data","title":"set_data(new_data)
","text":"Set the data with a new data dictionary.
Parameters:
Name Type Description Default new_data
dict[str, ndarray]
A dictionary representing the new data, where each key is an attribute and the value is a numpy array.
required Raises:
Type Description ValueError
If the new data is invalid (e.g., wrong shape, type, or value range).
Source code in src/tolvera/npndarray_dict.py
def set_data(self, new_data: dict[str, np.ndarray]) -> None:\n \"\"\"\n Set the data with a new data dictionary.\n\n Args:\n new_data: A dictionary representing the new data, where each key is an\n attribute and the value is a numpy array.\n\n Raises:\n ValueError: If the new data is invalid (e.g., wrong shape, type, or value range).\n \"\"\"\n try:\n self.data = new_data\n except ValueError as e:\n print(f\"ValueError occurred while setting data: {e}\")\n raise\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.dict_from_vector_args","title":"dict_from_vector_args(a, scalars=None)
","text":"Convert a list of arguments to a dictionary.
Args: - a: A list of arguments. - scalars: A list of keys that should be unwrapped from lists.
Returns: - A dictionary of keyword arguments.
Source code in src/tolvera/npndarray_dict.py
def dict_from_vector_args(a: list, scalars=None):\n \"\"\"Convert a list of arguments to a dictionary.\n\n Args:\n - a: A list of arguments.\n - scalars: A list of keys that should be unwrapped from lists.\n\n Returns:\n - A dictionary of keyword arguments.\n \"\"\"\n a = list(a)\n kw = defaultdict(list)\n k = None\n while len(a):\n item = a.pop(0)\n if isinstance(item, str):\n k = item\n else:\n if k is None:\n print(f\"ERROR: bad syntax in {a}\")\n kw[k].append(item)\n # unwrap scalars\n for item in scalars or []:\n if item in kw:\n kw[item] = kw[item][0]\n return kw\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.dict_to_vector_args","title":"dict_to_vector_args(kw)
","text":"Convert a dictionary to a list of arguments.
This function takes a dictionary and returns a list of arguments.
Args: - kw: A dictionary of keyword arguments.
Returns: - A list of arguments.
Source code in src/tolvera/npndarray_dict.py
def dict_to_vector_args(kw):\n \"\"\"Convert a dictionary to a list of arguments.\n\n This function takes a dictionary and returns a list of arguments.\n\n Args:\n - kw: A dictionary of keyword arguments.\n\n Returns:\n - A list of arguments.\n \"\"\"\n args = []\n for key, value in kw.items():\n args.append(key)\n if isinstance(value, (list, np.ndarray)):\n # If it's a numpy array (regardless of its shape), flatten it and extend the list\n if isinstance(value, np.ndarray):\n value = value.flatten()\n args.extend(value)\n else:\n # Append the scalar value associated with the key\n args.append(value)\n return args\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.ndarraydict_from_vector_args","title":"ndarraydict_from_vector_args(lst, shapes)
","text":"Convert a list to a dictionary where each list is turned into a numpy array.
This function takes a list in the format output by dict_from_vector_args
and converts it into a dictionary. Each key's list of values is converted into a numpy array with a specified shape.
Args: - lst: The list to be converted. - shapes: A dictionary where keys correspond to the keys in the original list and values are tuples representing the desired shape of the numpy array.
Returns: - A dictionary with keys mapped to numpy arrays.
Source code in src/tolvera/npndarray_dict.py
def ndarraydict_from_vector_args(lst, shapes):\n \"\"\"Convert a list to a dictionary where each list is turned into a numpy array.\n\n This function takes a list in the format output by `dict_from_vector_args` and converts it\n into a dictionary. Each key's list of values is converted into a numpy array with a\n specified shape.\n\n Args:\n - lst: The list to be converted.\n - shapes: A dictionary where keys correspond to the keys in the original list and\n values are tuples representing the desired shape of the numpy array.\n\n Returns:\n - A dictionary with keys mapped to numpy arrays.\n \"\"\"\n\n def flatten(lst):\n \"\"\"Flatten a nested list or return a non-nested list as is.\"\"\"\n if all(isinstance(el, list) for el in lst):\n # Flatten only if all elements are lists\n return [item for sublist in lst for item in sublist]\n return lst\n\n kw = defaultdict(list)\n k = None\n for item in lst:\n if isinstance(item, str):\n k = item\n else:\n kw[k].append(item)\n\n for key, shape in shapes.items():\n if key in kw:\n values = flatten(kw[key])\n array_size = np.prod(shape)\n if len(values) != array_size:\n raise ValueError(\n f\"Shape mismatch for key '{key}': expected {array_size} elements, got {len(values)}.\"\n )\n kw[key] = np.array(values).reshape(shape)\n\n return dict(kw)\n
"},{"location":"reference/tolvera/npndarray_dict/#tolvera.npndarray_dict.shapes_from_ndarray_dict","title":"shapes_from_ndarray_dict(ndarray_dict)
","text":"Return a dictionary of shapes given a dictionary of numpy ndarrays.
This function takes a dictionary where values are numpy ndarrays and returns a new dictionary with the same keys, where each value is the shape of the ndarray.
Args: - ndarray_dict: A dictionary where values are numpy ndarrays.
Returns: - A dictionary where each key maps to the shape of the corresponding ndarray.
Source code in src/tolvera/npndarray_dict.py
def shapes_from_ndarray_dict(ndarray_dict):\n \"\"\"Return a dictionary of shapes given a dictionary of numpy ndarrays.\n\n This function takes a dictionary where values are numpy ndarrays and returns\n a new dictionary with the same keys, where each value is the shape of the ndarray.\n\n Args:\n - ndarray_dict: A dictionary where values are numpy ndarrays.\n\n Returns:\n - A dictionary where each key maps to the shape of the corresponding ndarray.\n \"\"\"\n shapes = {}\n for key, array in ndarray_dict.items():\n shapes[key] = array.shape\n return shapes\n
"},{"location":"reference/tolvera/particles/","title":"Particles","text":"Particle system.
The Tolvera particle system consists of a Particle class and a Particles class. The Particle class is a Taichi dataclass for a single particle, and the Particles class is a Taichi data_oriented class containing a Particle field.
The Particles class also contains methods for processing the particle system, such as updating the particles, and getting and setting particle properties.
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle","title":"Particle
","text":"Particle data structure and methods.
Source code in src/tolvera/particles.py
@ti.dataclass\nclass Particle:\n \"\"\"Particle data structure and methods.\"\"\"\n species: ti.i32\n active: ti.f32\n pos: ti.math.vec2\n vel: ti.math.vec2\n mass: ti.f32\n size: ti.f32\n speed: ti.f32\n\n @ti.func\n def dist(self, other):\n \"\"\"Distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: Distance between the two particles.\n \"\"\"\n return self.pos - other.pos\n\n @ti.func\n def dist_norm(self, other):\n \"\"\"ti.math.norm() distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: ti.math.norm() distance between the two particles.\n \"\"\"\n return self.dist(self.pos - other.pos).norm()\n\n @ti.func\n def dist_normalized(self, other):\n \"\"\"ti.math.normalized() distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: ti.math.normalized() distance between the two particles.\n \"\"\"\n return self.dist(self.pos - other.pos).normalized()\n\n @ti.func\n def dist_wrap(self, other, x, y):\n \"\"\"Wrap around distance between two particles.\n\n Args:\n other (Particle): Other particle.\n x (float): Width.\n y (float): Height.\n\n Returns:\n ti.math.vec2: Wrap around distance between the two particles.\n \"\"\"\n dx = self.pos[0] - other.pos[0]\n dy = self.pos[1] - other.pos[1]\n if abs(dx) > x / 2: # x-axis\n dx = x - abs(dx)\n if self.pos[0] > other.pos[0]:\n dx = -dx\n if abs(dy) > y / 2: # y-axis\n dy = y - abs(dy)\n if self.pos[1] > other.pos[1]:\n dy = -dy\n return ti.Vector([dx, dy])\n\n # @ti.func\n # def dist_wrap(self, other, x, y):\n # dx = self.pos[0] - other.pos[0]\n # dy = self.pos[1] - other.pos[1]\n # # Wrap around for the x-axis\n # if abs(dx) > x / 2:\n # dx = x - abs(dx)\n # if self.pos[0] < other.pos[0]:\n # dx = -dx\n # # Wrap around for the y-axis\n # if abs(dy) > y / 2:\n # dy = y - abs(dy)\n # if self.pos[1] < other.pos[1]:\n # dy = -dy\n # return ti.Vector([dx, dy])\n # @ti.func\n # def dist_wrap(self, other, width, height):\n # # Compute the element-wise absolute difference\n # self_abs = ti.abs(self.pos)\n # other_abs = ti.abs(other.pos)\n # delta = self_abs - other_abs\n # # Check if wrapping around is shorter for both the x and y components\n # if delta[0] > width / 2:\n # delta[0] = width - delta[0]\n # if delta[1] > height / 2:\n # delta[1] = height - delta[1]\n # # Correct the signs if necessary\n # if self.pos[0] > other.pos[0] and delta[0] > 0:\n # delta[0] = -delta[0]\n # if self.pos[1] > other.pos[1] and delta[1] > 0:\n # delta[1] = -delta[1]\n # return delta\n @ti.func\n def randomise(self, x, y):\n \"\"\"Randomise the particle's position and velocity.\n\n Args:\n x (ti.f32): Width.\n y (ti.f32): Height.\n \"\"\"\n self.randomise_pos(x, y)\n self.randomise_vel()\n\n @ti.func\n def randomise_pos(self, x, y):\n \"\"\"Randomise the particle's position.\n\n Args:\n x (ti.f32): Width.\n y (ti.f32): Height.\n \"\"\"\n self.pos = [x * ti.random(ti.f32), y * ti.random(ti.f32)]\n\n @ti.func\n def randomise_vel(self):\n \"\"\"Randomise the particle's velocity.\"\"\"\n self.vel = [2 * (ti.random(ti.f32) - 0.5), 2 * (ti.random(ti.f32) - 0.5)]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.dist","title":"dist(other)
","text":"Distance between two particles.
Parameters:
Name Type Description Default other
Particle
Other particle.
required Returns:
Type Description ti.math.vec2: Distance between the two particles.
Source code in src/tolvera/particles.py
@ti.func\ndef dist(self, other):\n \"\"\"Distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: Distance between the two particles.\n \"\"\"\n return self.pos - other.pos\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.dist_norm","title":"dist_norm(other)
","text":"ti.math.norm() distance between two particles.
Parameters:
Name Type Description Default other
Particle
Other particle.
required Returns:
Type Description ti.math.vec2: ti.math.norm() distance between the two particles.
Source code in src/tolvera/particles.py
@ti.func\ndef dist_norm(self, other):\n \"\"\"ti.math.norm() distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: ti.math.norm() distance between the two particles.\n \"\"\"\n return self.dist(self.pos - other.pos).norm()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.dist_normalized","title":"dist_normalized(other)
","text":"ti.math.normalized() distance between two particles.
Parameters:
Name Type Description Default other
Particle
Other particle.
required Returns:
Type Description ti.math.vec2: ti.math.normalized() distance between the two particles.
Source code in src/tolvera/particles.py
@ti.func\ndef dist_normalized(self, other):\n \"\"\"ti.math.normalized() distance between two particles.\n\n Args:\n other (Particle): Other particle.\n\n Returns:\n ti.math.vec2: ti.math.normalized() distance between the two particles.\n \"\"\"\n return self.dist(self.pos - other.pos).normalized()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.dist_wrap","title":"dist_wrap(other, x, y)
","text":"Wrap around distance between two particles.
Parameters:
Name Type Description Default other
Particle
Other particle.
required x
float
Width.
required y
float
Height.
required Returns:
Type Description ti.math.vec2: Wrap around distance between the two particles.
Source code in src/tolvera/particles.py
@ti.func\ndef dist_wrap(self, other, x, y):\n \"\"\"Wrap around distance between two particles.\n\n Args:\n other (Particle): Other particle.\n x (float): Width.\n y (float): Height.\n\n Returns:\n ti.math.vec2: Wrap around distance between the two particles.\n \"\"\"\n dx = self.pos[0] - other.pos[0]\n dy = self.pos[1] - other.pos[1]\n if abs(dx) > x / 2: # x-axis\n dx = x - abs(dx)\n if self.pos[0] > other.pos[0]:\n dx = -dx\n if abs(dy) > y / 2: # y-axis\n dy = y - abs(dy)\n if self.pos[1] > other.pos[1]:\n dy = -dy\n return ti.Vector([dx, dy])\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.randomise","title":"randomise(x, y)
","text":"Randomise the particle's position and velocity.
Parameters:
Name Type Description Default x
f32
Width.
required y
f32
Height.
required Source code in src/tolvera/particles.py
@ti.func\ndef randomise(self, x, y):\n \"\"\"Randomise the particle's position and velocity.\n\n Args:\n x (ti.f32): Width.\n y (ti.f32): Height.\n \"\"\"\n self.randomise_pos(x, y)\n self.randomise_vel()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.randomise_pos","title":"randomise_pos(x, y)
","text":"Randomise the particle's position.
Parameters:
Name Type Description Default x
f32
Width.
required y
f32
Height.
required Source code in src/tolvera/particles.py
@ti.func\ndef randomise_pos(self, x, y):\n \"\"\"Randomise the particle's position.\n\n Args:\n x (ti.f32): Width.\n y (ti.f32): Height.\n \"\"\"\n self.pos = [x * ti.random(ti.f32), y * ti.random(ti.f32)]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particle.randomise_vel","title":"randomise_vel()
","text":"Randomise the particle's velocity.
Source code in src/tolvera/particles.py
@ti.func\ndef randomise_vel(self):\n \"\"\"Randomise the particle's velocity.\"\"\"\n self.vel = [2 * (ti.random(ti.f32) - 0.5), 2 * (ti.random(ti.f32) - 0.5)]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles","title":"Particles
","text":"Particle system.
Source code in src/tolvera/particles.py
@ti.data_oriented\nclass Particles:\n \"\"\"Particle system.\"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the particle system.\n\n Args:\n tolvera (Tolvera): Tolvera instance.\n **kwargs: Keyword arguments (currently there are none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.n = self.tv.pn\n self.p_per_s = self.tv.p_per_s\n self._speed = ti.field(ti.f32, shape=())\n self._speed[None] = 1.0\n self.substep = self.tv.substep\n self.field = Particle.field(shape=(self.n))\n # TODO: These should be possible with State\n # self.pos = State(self.tv, {\n # 'x': (0., self.tv.x),\n # 'y': (0., self.tv.y),\n # }, shape=(self.n,), osc=('get'), name='particles_pos')\n self.tmp_pos = ti.Vector.field(2, ti.f32, shape=(self.n))\n self.tmp_vel = ti.Vector.field(2, ti.f32, shape=(self.n))\n self.tmp_pos_species = ti.Vector.field(2, ti.f32, shape=(self.p_per_s))\n self.tmp_vel_species = ti.Vector.field(2, ti.f32, shape=(self.p_per_s))\n self.tmp_vel_stats = ti.Vector.field(1, ti.f32, shape=(7))\n self.active_indexes = ti.field(ti.i32, shape=(self.n))\n self.active_count = ti.field(ti.i32, shape=())\n self.init()\n\n def init(self):\n \"\"\"Initialise the particle system.\"\"\"\n self.assign_species()\n self.randomise()\n\n @ti.kernel\n def assign_species(self):\n \"\"\"Assign species to particles.\"\"\"\n for i in range(self.n):\n self.field[i].species = i % self.tv.species\n\n def _randomise(self):\n \"\"\"Randomise the particle system (Python scope).\"\"\"\n self.randomise()\n\n @ti.kernel\n def randomise(self):\n \"\"\"Randomise the particle system (Taichi scope).\"\"\"\n for i in range(self.n):\n si = self.field[i].species\n s = self.tv.s.species[si]\n # FIXME: ugly\n # c = self.tv.species_consts\n species = si\n active = 1.0\n pos = [self.tv.x * ti.random(ti.f32), self.tv.y * ti.random(ti.f32)]\n vel = [2 * (ti.random(ti.f32) - 0.5), 2 * (ti.random(ti.f32) - 0.5)]\n size = (\n ti.random(ti.f32) * s.size * self.tv.species_consts.MAX_SIZE\n + self.tv.species_consts.MIN_SIZE\n )\n speed = (\n ti.random(ti.f32) * s.speed * self.tv.species_consts.MAX_SPEED\n + self.tv.species_consts.MIN_SPEED\n )\n mass = ti.random(ti.f32) * s.mass * self.tv.species_consts.MAX_MASS\n self.field[i] = Particle(\n species=species,\n pos=pos,\n vel=vel,\n active=active,\n mass=mass,\n size=size,\n speed=speed,\n )\n\n @ti.kernel\n def update(self):\n \"\"\"Update the particle system.\"\"\"\n # TODO: collisions\n for i in range(self.n):\n if self.field[i] == 0.0:\n continue\n self.toroidal_wrap(i)\n self.limit_speed(i)\n\n @ti.kernel\n def update_active(self):\n \"\"\"Update the active particles.\"\"\"\n j = 0\n for i in range(self.n):\n p = self.field[i]\n if p.active > 0.0:\n self.active_indexes[j] = i\n j += 1\n self.active_count[None] = j\n\n @ti.func\n def toroidal_wrap(self, i: ti.i32):\n \"\"\"Toroidal wrap a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n p = self.field[i]\n if p.pos[0] > self.tv.x:\n self.field[i].pos[0] = 0.0\n if p.pos[0] < 0.0:\n self.field[i].pos[0] = self.tv.x\n if p.pos[1] > self.tv.y:\n self.field[i].pos[1] = 0.0\n if p.pos[1] < 0.0:\n self.field[i].pos[1] = self.tv.y\n\n @ti.func\n def limit_speed(self, i: ti.i32):\n \"\"\"Limit the speed of a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n p = self.field[i]\n s = self.tv.s.species[p.species]\n # FIXME: ugly\n sp = (\n s.speed * self.tv.species_consts.MAX_SPEED\n + self.tv.species_consts.MIN_SPEED\n )\n if p.vel.norm() > s.speed:\n self.field[i].vel = p.vel.normalized() * sp * self._speed[None]\n\n @ti.kernel\n def activity_decay(self):\n \"\"\"Decay the activity of the particles.\"\"\"\n for i in range(self.active_count[None]):\n idx = self.active_indexes[i]\n self.field[idx].active *= self.field[i].decay\n\n def process(self):\n \"\"\"Process the particle system.\"\"\"\n for i in range(self.substep):\n self.update_active()\n self.update()\n\n @ti.kernel\n def set_active(self, a: ti.i32):\n \"\"\"Set the active particles.\n\n Args:\n a (ti.i32): Amount of active particles.\n \"\"\"\n for i in range(self.field.shape[0]):\n if i > a:\n self.field[i].active = 0\n else:\n self.field[i].active = 1\n\n @ti.kernel\n def set_species_active(self, i: ti.i32, a: ti.i32):\n \"\"\"Set the active particles of a species.\n\n Args:\n i (ti.i32): Species index.\n a (ti.i32): Amount of active particles.\n \"\"\"\n for j in range(self.field.shape[0]):\n if self.field[j].species == i:\n if j > a:\n self.field[j].active = 0\n else:\n self.field[j].active = 1\n\n @ti.kernel\n def set_active_amount(self, a: ti.f32):\n \"\"\"Set particle activity amount.\n\n Args:\n a (ti.i32): Amount of activity.\n \"\"\"\n for i in range(self.field.shape[0]):\n self.field[i].active = a\n\n @ti.kernel\n def set_species_active_amount(self, i: ti.i32, a: ti.f32):\n \"\"\"Set particle activity amount of a species.\n\n Args:\n i (ti.i32): Species index.\n a (ti.i32): Amount of activity.\n \"\"\"\n for j in range(self.field.shape[0]):\n if self.field[j].species == i:\n self.field[j].active = a\n\n def set_pos(self, i, x, y):\n self.field[i].pos = [x, y]\n\n def set_vel(self, i, x, y):\n self.field[i].vel = [x, y]\n\n def set_speed(self, i, s):\n self.field[i].speed = s\n\n def set_size(self, i, s):\n self.field[i].size = s\n\n def get_pos(self, i):\n return self.field[i].pos.to_numpy().tolist()\n\n def get_vel(self, i):\n return self.field[i].vel.to_numpy().tolist()\n\n def get_pos_all_1d(self):\n self._get_pos_all()\n return self.tmp_pos.to_numpy().flatten().tolist()\n\n def get_pos_all_2d(self):\n self._get_pos_all()\n return self.tmp_pos.to_numpy().tolist()\n\n def get_vel_all_1d(self):\n self._get_vel_all()\n return self.tmp_vel.to_numpy().flatten().tolist()\n\n def get_vel_all_2d(self):\n self._get_vel_all()\n return self.tmp_vel.to_numpy().tolist()\n\n @ti.kernel\n def _get_pos_all(self):\n # for i in range(self.active_count[None]):\n # idx = self.active_indexes[i]\n # p = self.field[idx]\n # self.tmp_pos[i] = p.pos / [self.tv.x, self.tv.y]\n # TODO: Only send active particle positions...? Or inactive=-1?\n for i in range(self.n):\n p = self.field[i]\n # if p.active > 0.0: # causes IML shape assertion error\n self.tmp_pos[i] = p.pos / [self.tv.x, self.tv.y]\n # else:\n # self.tmp_pos[i] = [0.0,0.0] # ???\n\n @ti.kernel\n def _get_vel_all(self):\n for i in range(self.n):\n p = self.field[i]\n if p.active > 0.0:\n self.tmp_vel[i] = p.vel\n\n def get_pos_species_1d(self, species: int):\n self._get_pos_species()\n return self.tmp_pos_species.to_numpy().flatten().tolist()\n\n def get_pos_species_2d(self, species: int):\n if species > self.tv.species - 1:\n return\n self._get_pos_species(species)\n return self.tmp_pos_species.to_numpy().tolist()\n\n @ti.kernel\n def _get_pos_species(self, i: ti.i32):\n for j in range(self.n):\n si = j % self.tv.species\n p = self.field[j]\n if i == si and p.active > 0.0:\n species_index = (j - i) // self.tv.species\n pos = p.pos / [self.tv.x, self.tv.y]\n self.tmp_pos_species[species_index] = pos\n\n def get_vel_species_1d(self, species: int):\n self._get_vel_species(species)\n return self.tmp_vel_species.to_numpy().flatten().tolist()\n\n def get_vel_species_2d(self, species: int):\n self._get_vel_species(species)\n return self.tmp_vel_species.to_numpy().tolist()\n\n @ti.kernel\n def _get_vel_species(self, i: ti.i32):\n for j in range(self.n):\n si = j % self.tv.species\n p = self.field[j]\n if i == si and p.active > 0.0:\n species_index = (j - i) // self.tv.species\n vel = p.vel / [self.tv.x, self.tv.y]\n self.tmp_vel_species[species_index] = vel\n\n def get_vel_stats_species_1d(self, species):\n self._species_velocity_statistics(species)\n return self.tmp_vel_stats.to_numpy().flatten().tolist()\n\n @ti.kernel\n def _species_velocity_statistics(self, i: ti.i32):\n \"\"\"\n Centre of Mass Velocity: This is the average velocity of all particles in the species.\n Relative Velocity: This is the average velocity of all particles in the species relative to the centre of mass velocity.\n Angular Momentum: This is the sum of the angular momentum of all particles, which is given by mass * velocity * radius for each particle.\n Kinetic Energy: This is the sum of the kinetic energy of all particles, which is given by 0.5 * mass * velocity^2 for each particle.\n Temperature: In statistical mechanics, the temperature of a system of particles is related to the average kinetic energy of the particles.\n \"\"\"\n centre_of_mass_velocity = ti.Vector([0.0, 0.0])\n relative_velocity = ti.Vector([0.0, 0.0])\n angular_momentum = ti.Vector([0.0])\n kinetic_energy = ti.Vector([0.0])\n for j in range(self.n):\n if self.field[j].species == i:\n v = self.field[j].vel\n p = self.field[j].pos\n m = self.field[j].mass\n centre_of_mass_velocity += v\n relative_velocity += v # - centre_of_mass_velocity\n angular_momentum += m * ti.math.cross(v, p)\n kinetic_energy += 0.5 * m * v.norm_sqr()\n centre_of_mass_velocity = centre_of_mass_velocity / self.n_per_species\n relative_velocity = (\n relative_velocity - centre_of_mass_velocity * self.n_per_species\n ) / self.n_per_species\n temperature = 2.0 * kinetic_energy / (self.particles_per_species * 1.380649e-23)\n self.tmp_vel_stats[0] = centre_of_mass_velocity[0]\n self.tmp_vel_stats[1] = centre_of_mass_velocity[1]\n self.tmp_vel_stats[2] = relative_velocity[0]\n self.tmp_vel_stats[3] = relative_velocity[1]\n self.tmp_vel_stats[4] = angular_momentum[0]\n self.tmp_vel_stats[5] = kinetic_energy[0]\n self.tmp_vel_stats[6] = temperature[0]\n\n def reset(self):\n \"\"\"Reset the particle system.\"\"\"\n self.init()\n\n def speed(self, speed: float = None):\n \"\"\"Get or set the speed of the particle system.\n\n Args:\n speed (float, optional): Speed. Defaults to None.\n\n Returns:\n float: Speed.\n \"\"\"\n if speed is not None:\n self._speed[None] = 1 / (speed + 0.0001)\n else:\n return self._speed[None]\n\n def __call__(self):\n \"\"\"Call will process the particle system.\"\"\"\n self.process()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.__call__","title":"__call__()
","text":"Call will process the particle system.
Source code in src/tolvera/particles.py
def __call__(self):\n \"\"\"Call will process the particle system.\"\"\"\n self.process()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the particle system.
Parameters:
Name Type Description Default tolvera
Tolvera
Tolvera instance.
required **kwargs
Keyword arguments (currently there are none).
{}
Source code in src/tolvera/particles.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the particle system.\n\n Args:\n tolvera (Tolvera): Tolvera instance.\n **kwargs: Keyword arguments (currently there are none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.n = self.tv.pn\n self.p_per_s = self.tv.p_per_s\n self._speed = ti.field(ti.f32, shape=())\n self._speed[None] = 1.0\n self.substep = self.tv.substep\n self.field = Particle.field(shape=(self.n))\n # TODO: These should be possible with State\n # self.pos = State(self.tv, {\n # 'x': (0., self.tv.x),\n # 'y': (0., self.tv.y),\n # }, shape=(self.n,), osc=('get'), name='particles_pos')\n self.tmp_pos = ti.Vector.field(2, ti.f32, shape=(self.n))\n self.tmp_vel = ti.Vector.field(2, ti.f32, shape=(self.n))\n self.tmp_pos_species = ti.Vector.field(2, ti.f32, shape=(self.p_per_s))\n self.tmp_vel_species = ti.Vector.field(2, ti.f32, shape=(self.p_per_s))\n self.tmp_vel_stats = ti.Vector.field(1, ti.f32, shape=(7))\n self.active_indexes = ti.field(ti.i32, shape=(self.n))\n self.active_count = ti.field(ti.i32, shape=())\n self.init()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.activity_decay","title":"activity_decay()
","text":"Decay the activity of the particles.
Source code in src/tolvera/particles.py
@ti.kernel\ndef activity_decay(self):\n \"\"\"Decay the activity of the particles.\"\"\"\n for i in range(self.active_count[None]):\n idx = self.active_indexes[i]\n self.field[idx].active *= self.field[i].decay\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.assign_species","title":"assign_species()
","text":"Assign species to particles.
Source code in src/tolvera/particles.py
@ti.kernel\ndef assign_species(self):\n \"\"\"Assign species to particles.\"\"\"\n for i in range(self.n):\n self.field[i].species = i % self.tv.species\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.init","title":"init()
","text":"Initialise the particle system.
Source code in src/tolvera/particles.py
def init(self):\n \"\"\"Initialise the particle system.\"\"\"\n self.assign_species()\n self.randomise()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.limit_speed","title":"limit_speed(i)
","text":"Limit the speed of a particle.
Parameters:
Name Type Description Default i
i32
Particle index.
required Source code in src/tolvera/particles.py
@ti.func\ndef limit_speed(self, i: ti.i32):\n \"\"\"Limit the speed of a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n p = self.field[i]\n s = self.tv.s.species[p.species]\n # FIXME: ugly\n sp = (\n s.speed * self.tv.species_consts.MAX_SPEED\n + self.tv.species_consts.MIN_SPEED\n )\n if p.vel.norm() > s.speed:\n self.field[i].vel = p.vel.normalized() * sp * self._speed[None]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.process","title":"process()
","text":"Process the particle system.
Source code in src/tolvera/particles.py
def process(self):\n \"\"\"Process the particle system.\"\"\"\n for i in range(self.substep):\n self.update_active()\n self.update()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.randomise","title":"randomise()
","text":"Randomise the particle system (Taichi scope).
Source code in src/tolvera/particles.py
@ti.kernel\ndef randomise(self):\n \"\"\"Randomise the particle system (Taichi scope).\"\"\"\n for i in range(self.n):\n si = self.field[i].species\n s = self.tv.s.species[si]\n # FIXME: ugly\n # c = self.tv.species_consts\n species = si\n active = 1.0\n pos = [self.tv.x * ti.random(ti.f32), self.tv.y * ti.random(ti.f32)]\n vel = [2 * (ti.random(ti.f32) - 0.5), 2 * (ti.random(ti.f32) - 0.5)]\n size = (\n ti.random(ti.f32) * s.size * self.tv.species_consts.MAX_SIZE\n + self.tv.species_consts.MIN_SIZE\n )\n speed = (\n ti.random(ti.f32) * s.speed * self.tv.species_consts.MAX_SPEED\n + self.tv.species_consts.MIN_SPEED\n )\n mass = ti.random(ti.f32) * s.mass * self.tv.species_consts.MAX_MASS\n self.field[i] = Particle(\n species=species,\n pos=pos,\n vel=vel,\n active=active,\n mass=mass,\n size=size,\n speed=speed,\n )\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.reset","title":"reset()
","text":"Reset the particle system.
Source code in src/tolvera/particles.py
def reset(self):\n \"\"\"Reset the particle system.\"\"\"\n self.init()\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_active","title":"set_active(a)
","text":"Set the active particles.
Parameters:
Name Type Description Default a
i32
Amount of active particles.
required Source code in src/tolvera/particles.py
@ti.kernel\ndef set_active(self, a: ti.i32):\n \"\"\"Set the active particles.\n\n Args:\n a (ti.i32): Amount of active particles.\n \"\"\"\n for i in range(self.field.shape[0]):\n if i > a:\n self.field[i].active = 0\n else:\n self.field[i].active = 1\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_active_amount","title":"set_active_amount(a)
","text":"Set particle activity amount.
Parameters:
Name Type Description Default a
i32
Amount of activity.
required Source code in src/tolvera/particles.py
@ti.kernel\ndef set_active_amount(self, a: ti.f32):\n \"\"\"Set particle activity amount.\n\n Args:\n a (ti.i32): Amount of activity.\n \"\"\"\n for i in range(self.field.shape[0]):\n self.field[i].active = a\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_species_active","title":"set_species_active(i, a)
","text":"Set the active particles of a species.
Parameters:
Name Type Description Default i
i32
Species index.
required a
i32
Amount of active particles.
required Source code in src/tolvera/particles.py
@ti.kernel\ndef set_species_active(self, i: ti.i32, a: ti.i32):\n \"\"\"Set the active particles of a species.\n\n Args:\n i (ti.i32): Species index.\n a (ti.i32): Amount of active particles.\n \"\"\"\n for j in range(self.field.shape[0]):\n if self.field[j].species == i:\n if j > a:\n self.field[j].active = 0\n else:\n self.field[j].active = 1\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_species_active_amount","title":"set_species_active_amount(i, a)
","text":"Set particle activity amount of a species.
Parameters:
Name Type Description Default i
i32
Species index.
required a
i32
Amount of activity.
required Source code in src/tolvera/particles.py
@ti.kernel\ndef set_species_active_amount(self, i: ti.i32, a: ti.f32):\n \"\"\"Set particle activity amount of a species.\n\n Args:\n i (ti.i32): Species index.\n a (ti.i32): Amount of activity.\n \"\"\"\n for j in range(self.field.shape[0]):\n if self.field[j].species == i:\n self.field[j].active = a\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.speed","title":"speed(speed=None)
","text":"Get or set the speed of the particle system.
Parameters:
Name Type Description Default speed
float
Speed. Defaults to None.
None
Returns:
Name Type Description float
Speed.
Source code in src/tolvera/particles.py
def speed(self, speed: float = None):\n \"\"\"Get or set the speed of the particle system.\n\n Args:\n speed (float, optional): Speed. Defaults to None.\n\n Returns:\n float: Speed.\n \"\"\"\n if speed is not None:\n self._speed[None] = 1 / (speed + 0.0001)\n else:\n return self._speed[None]\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.toroidal_wrap","title":"toroidal_wrap(i)
","text":"Toroidal wrap a particle.
Parameters:
Name Type Description Default i
i32
Particle index.
required Source code in src/tolvera/particles.py
@ti.func\ndef toroidal_wrap(self, i: ti.i32):\n \"\"\"Toroidal wrap a particle.\n\n Args:\n i (ti.i32): Particle index.\n \"\"\"\n p = self.field[i]\n if p.pos[0] > self.tv.x:\n self.field[i].pos[0] = 0.0\n if p.pos[0] < 0.0:\n self.field[i].pos[0] = self.tv.x\n if p.pos[1] > self.tv.y:\n self.field[i].pos[1] = 0.0\n if p.pos[1] < 0.0:\n self.field[i].pos[1] = self.tv.y\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.update","title":"update()
","text":"Update the particle system.
Source code in src/tolvera/particles.py
@ti.kernel\ndef update(self):\n \"\"\"Update the particle system.\"\"\"\n # TODO: collisions\n for i in range(self.n):\n if self.field[i] == 0.0:\n continue\n self.toroidal_wrap(i)\n self.limit_speed(i)\n
"},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.update_active","title":"update_active()
","text":"Update the active particles.
Source code in src/tolvera/particles.py
@ti.kernel\ndef update_active(self):\n \"\"\"Update the active particles.\"\"\"\n j = 0\n for i in range(self.n):\n p = self.field[i]\n if p.active > 0.0:\n self.active_indexes[j] = i\n j += 1\n self.active_count[None] = j\n
"},{"location":"reference/tolvera/patches/","title":"Patches","text":"Patches for third-party libraries.
Current patches: - 'dill.source.findsource fails when in asyncio REPL' https://github.com/uqfoundation/dill/issues/627
"},{"location":"reference/tolvera/patches/#tolvera.patches.findsource","title":"findsource(object)
","text":"Return the entire source file and starting line number for an object. For interactively-defined objects, the 'file' is the interpreter's history.
The argument may be a module, class, method, function, traceback, frame, or code object. The source code is returned as a list of all the lines in the file and the line number indexes a line in that list. An IOError is raised if the source code cannot be retrieved, while a TypeError is raised for objects where the source code is unavailable (e.g. builtins).
Source code in src/tolvera/patches.py
def findsource(object):\n # print(f\"[dill.source.findsource] PATCHED\")\n\n \"\"\"Return the entire source file and starting line number for an object.\n For interactively-defined objects, the 'file' is the interpreter's history.\n\n The argument may be a module, class, method, function, traceback, frame,\n or code object. The source code is returned as a list of all the lines\n in the file and the line number indexes a line in that list. An IOError\n is raised if the source code cannot be retrieved, while a TypeError is\n raised for objects where the source code is unavailable (e.g. builtins).\"\"\"\n\n def patched_getfile(module):\n # set file = None when module.__package__ == 'asyncio'\n # print(f\"[dill.source.patched_getfile] module={module}\\nmodule.__package__={module.__package__}\\nmodule.__name__={module.__name__}\")\n if module.__package__ == \"asyncio\":\n raise TypeError\n # if module.__package__ == 'sardine':\n # raise TypeError\n ret = getfile(module)\n return ret\n\n module = getmodule(object)\n # try: file = getfile(module)\n try:\n file = patched_getfile(module)\n except TypeError:\n file = None\n # correctly compute `is_module_main` when in asyncio\n is_module_main = module and module.__name__ == \"__main__\" and not file\n # is_module_main = (module and module.__name__ == '__main__' or module.__name__ == 'sardine' and not file)\n print(\n f\"[dill.source.findsource] module: {module}, file: {file}, is_module_main: {is_module_main}\"\n )\n if IS_IPYTHON and is_module_main:\n # FIXME: quick fix for functions and classes in IPython interpreter\n try:\n file = getfile(object)\n sourcefile = getsourcefile(object)\n except TypeError:\n if isclass(object):\n for object_method in filter(isfunction, object.__dict__.values()):\n # look for a method of the class\n file_candidate = getfile(object_method)\n if not file_candidate.startswith(\"<ipython-input-\"):\n continue\n file = file_candidate\n sourcefile = getsourcefile(object_method)\n break\n if file:\n lines = linecache.getlines(file)\n else:\n # fallback to use history\n history = \"\\n\".join(get_ipython().history_manager.input_hist_parsed)\n lines = [line + \"\\n\" for line in history.splitlines()]\n # use readline when working in interpreter (i.e. __main__ and not file)\n elif is_module_main:\n try:\n import readline\n\n err = \"\"\n except ImportError:\n import sys\n\n err = sys.exc_info()[1].args[0]\n if sys.platform[:3] == \"win\":\n err += \", please install 'pyreadline'\"\n if err:\n raise IOError(err)\n lbuf = readline.get_current_history_length()\n lines = [readline.get_history_item(i) + \"\\n\" for i in range(1, lbuf)]\n else:\n try: # special handling for class instances\n if not isclass(object) and isclass(type(object)): # __class__\n file = getfile(module)\n sourcefile = getsourcefile(module)\n else: # builtins fail with a TypeError\n file = getfile(object)\n sourcefile = getsourcefile(object)\n except (TypeError, AttributeError): # fail with better error\n file = getfile(object)\n sourcefile = getsourcefile(object)\n if not sourcefile and file[:1] + file[-1:] != \"<>\":\n raise IOError(\"source code not available\")\n file = sourcefile if sourcefile else file\n\n module = getmodule(object, file)\n if module:\n lines = linecache.getlines(file, module.__dict__)\n else:\n lines = linecache.getlines(file)\n\n if not lines:\n raise IOError(\"could not extract source code\")\n\n # FIXME: all below may fail if exec used (i.e. exec('f = lambda x:x') )\n if ismodule(object):\n return lines, 0\n\n # NOTE: beneficial if search goes from end to start of buffer history\n name = pat1 = obj = \"\"\n pat2 = r\"^(\\s*@)\"\n # pat1b = r'^(\\s*%s\\W*=)' % name #FIXME: finds 'f = decorate(f)', not exec\n if ismethod(object):\n name = object.__name__\n if name == \"<lambda>\":\n pat1 = r\"(.*(?<!\\w)lambda(:|\\s))\"\n else:\n pat1 = r\"^(\\s*def\\s)\"\n object = object.__func__\n if isfunction(object):\n name = object.__name__\n if name == \"<lambda>\":\n pat1 = r\"(.*(?<!\\w)lambda(:|\\s))\"\n obj = object # XXX: better a copy?\n else:\n pat1 = r\"^(\\s*def\\s)\"\n object = object.__code__\n if istraceback(object):\n object = object.tb_frame\n if isframe(object):\n object = object.f_code\n if iscode(object):\n if not hasattr(object, \"co_firstlineno\"):\n raise IOError(\"could not find function definition\")\n # stdin = object.co_filename == '<stdin>'\n stdin = object.co_filename in (\"<console>\", \"<stdin>\")\n # print(f\"[dill.source.findsource] object.co_filename: {object.co_filename}, stdin: {stdin}\")\n if stdin:\n lnum = len(lines) - 1 # can't get lnum easily, so leverage pat\n if not pat1:\n pat1 = r\"^(\\s*def\\s)|(.*(?<!\\w)lambda(:|\\s))|^(\\s*@)\"\n else:\n lnum = object.co_firstlineno - 1\n pat1 = r\"^(\\s*def\\s)|(.*(?<!\\w)lambda(:|\\s))|^(\\s*@)\"\n pat1 = re.compile(pat1)\n pat2 = re.compile(pat2)\n # XXX: candidate_lnum = [n for n in range(lnum) if pat1.match(lines[n])]\n while lnum > 0: # XXX: won't find decorators in <stdin> ?\n line = lines[lnum]\n if pat1.match(line):\n if not stdin:\n break # co_firstlineno does the job\n if name == \"<lambda>\": # hackery needed to confirm a match\n if _matchlambda(obj, line):\n break\n else: # not a lambda, just look for the name\n if name in line: # need to check for decorator...\n hats = 0\n for _lnum in range(lnum - 1, -1, -1):\n if pat2.match(lines[_lnum]):\n hats += 1\n else:\n break\n lnum = lnum - hats\n break\n lnum = lnum - 1\n return lines, lnum\n\n try: # turn instances into classes\n if not isclass(object) and isclass(type(object)): # __class__\n object = object.__class__ # XXX: sometimes type(class) is better?\n # XXX: we don't find how the instance was built\n except AttributeError:\n pass\n if isclass(object):\n name = object.__name__\n pat = re.compile(r\"^(\\s*)class\\s*\" + name + r\"\\b\")\n # make some effort to find the best matching class definition:\n # use the one with the least indentation, which is the one\n # that's most probably not inside a function definition.\n candidates = []\n for i in range(len(lines) - 1, -1, -1):\n match = pat.match(lines[i])\n if match:\n # if it's at toplevel, it's already the best one\n if lines[i][0] == \"c\":\n return lines, i\n # else add whitespace to candidate list\n candidates.append((match.group(1), i))\n if candidates:\n # this will sort by whitespace, and by line number,\n # less whitespace first #XXX: should sort high lnum before low\n candidates.sort()\n return lines, candidates[0][1]\n else:\n raise IOError(\"could not find class definition\")\n raise IOError(\"could not find code object\")\n
"},{"location":"reference/tolvera/pixels/","title":"Pixels","text":"Pixels module.
Example Draw a red rectangle in the centre of the screen.
import taichi as ti\nfrom tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n @ti.kernel\n def draw():\n w = 100\n tv.px.rect(tv.x/2-w/2, tv.y/2-w/2, w, w, ti.Vector([1., 0., 0., 1.]))\n\n @tv.render\n def _():\n tv.p()\n draw()\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels","title":"Pixels
","text":"Pixels class for drawing pixels to the screen.
This class is used to draw pixels to the screen. It contains methods for drawing points, lines, rectangles, circles, triangles, and polygons. It also contains methods for blending pixels together, flipping pixels, inverting pixels, and diffusing, decaying and clearing pixels.
It tries to follow a similar API to the Processing library.
Source code in src/tolvera/pixels.py
@ti.data_oriented\nclass Pixels:\n \"\"\"Pixels class for drawing pixels to the screen.\n\n This class is used to draw pixels to the screen. It contains methods for drawing\n points, lines, rectangles, circles, triangles, and polygons. It also contains\n methods for blending pixels together, flipping pixels, inverting pixels, and\n diffusing, decaying and clearing pixels.\n\n It tries to follow a similar API to the Processing library.\n \"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise Pixels\n\n Args:\n tolvera (Tolvera): T\u00f6lvera instance.\n **kwargs: Keyword arguments.\n polygon_mode (str): Polygon mode. Defaults to \"crossing\".\n brightness (float): Brightness. Defaults to 1.0. \n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.polygon_mode = kwargs.get(\"polygon_mode\", \"crossing\")\n self.x = self.tv.x\n self.y = self.tv.y\n self.px = Pixel.field(shape=(self.x, self.y))\n brightness = kwargs.get(\"brightness\", 1.0)\n self.CONSTS = CONSTS(\n {\n \"BRIGHTNESS\": (ti.f32, brightness),\n }\n )\n self.shape_enum = {\n \"point\": 0,\n \"line\": 1,\n \"rect\": 2,\n \"circle\": 3,\n \"triangle\": 4,\n \"polygon\": 5,\n }\n\n\n def set(self, px: Any):\n \"\"\"Set pixels.\n\n Args:\n px (Any): Pixels to set. Can be Pixels, StructField, MatrixField, etc (see rgba_from_px).\n \"\"\"\n self.px.rgba = self.rgba_from_px(px)\n\n @ti.kernel\n def k_set(self, px: ti.template()):\n for x, y in ti.ndrange(self.x, self.y):\n self.px.rgba[x, y] = px.px.rgba[x, y]\n\n @ti.kernel\n def f_set(self, px: ti.template()):\n for x, y in ti.ndrange(self.x, self.y):\n self.px.rgba[x, y] = px.px.rgba[x, y]\n\n def get(self):\n \"\"\"Get pixels.\"\"\"\n return self.px\n\n @ti.kernel\n def clear(self):\n \"\"\"Clear pixels.\"\"\"\n self.px.rgba.fill(0)\n\n @ti.kernel\n def diffuse(self, evaporate: ti.f32):\n \"\"\"Diffuse pixels.\n\n Args:\n evaporate (float): Evaporation rate.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n d = ti.Vector([0.0, 0.0, 0.0, 0.0]) \n for di in ti.static(range(-1, 2)):\n for dj in ti.static(range(-1, 2)):\n dx = (i + di) % self.x\n dy = (j + dj) % self.y\n d += self.px.rgba[dx, dy]\n d *= 0.99 / 9.0\n self.px.rgba[i, j] = d\n\n @ti.func\n def background(self, r: ti.f32, g: ti.f32, b: ti.f32):\n \"\"\"Set background colour.\n\n Args:\n r (ti.f32): Red.\n g (ti.f32): Green.\n b (ti.f32): Blue.\n \"\"\"\n bg = ti.Vector([r, g, b, 1.0])\n self.rect(0, 0, self.x, self.y, bg)\n\n @ti.func\n def point(self, x: ti.i32, y: ti.i32, rgba: vec4):\n \"\"\"Draw point.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n rgba (vec4): Colour.\n \"\"\"\n self.px.rgba[x, y] = rgba\n\n @ti.func\n def points(self, x: ti.template(), y: ti.template(), rgba: vec4):\n \"\"\"Draw points with the same colour.\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n rgba (vec4): Colour.\n \"\"\"\n for i in ti.static(range(len(x))):\n self.point(x[i], y[i], rgba)\n\n @ti.func\n def rect(self, x: ti.i32, y: ti.i32, w: ti.i32, h: ti.i32, rgba: vec4):\n \"\"\"Draw a filled rectangle.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n w (ti.i32): Width.\n h (ti.i32): Height.\n rgba (vec4): Colour.\n \"\"\"\n # TODO: fill arg\n # TODO: gradients, lerp with ti.math.mix(x, y, a)\n for i, j in ti.ndrange(w, h):\n self.px.rgba[x + i, y + j] = rgba\n\n @ti.func\n def line(self, x0: ti.i32, y0: ti.i32, x1: ti.i32, y1: ti.i32, rgba: vec4):\n \"\"\"Draw a line using Bresenham's algorithm.\n\n Args:\n x0 (ti.i32): X start position.\n y0 (ti.i32): Y start position.\n x1 (ti.i32): X end position.\n y1 (ti.i32): Y end position.\n rgba (vec4): Colour.\n\n TODO: thickness\n TODO: anti-aliasing\n TODO: should lines wrap around (as two lines)?\n \"\"\"\n dx = ti.abs(x1 - x0)\n dy = ti.abs(y1 - y0)\n x, y = x0, y0\n sx = -1 if x0 > x1 else 1\n sy = -1 if y0 > y1 else 1\n if dx > dy:\n err = dx / 2.0\n while x != x1:\n self.px.rgba[x, y] = rgba\n err -= dy\n if err < 0:\n y += sy\n err += dx\n x += sx\n else:\n err = dy / 2.0\n while y != y1:\n self.px.rgba[x, y] = rgba\n err -= dx\n if err < 0:\n x += sx\n err += dy\n y += sy\n self.px.rgba[x, y] = rgba\n\n @ti.func\n def circle(self, x: ti.i32, y: ti.i32, r: ti.i32, rgba: vec4):\n \"\"\"Draw a filled circle.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n r (ti.i32): Radius.\n rgba (vec4): Colour.\n \"\"\"\n for i in range(r + 1):\n d = ti.sqrt(r**2 - i**2)\n d_int = ti.cast(d, ti.i32)\n # TODO: parallelise ?\n for j in range(d_int):\n self.px.rgba[x + i, y + j] = rgba\n self.px.rgba[x + i, y - j] = rgba\n self.px.rgba[x - i, y - j] = rgba\n self.px.rgba[x - i, y + j] = rgba\n\n @ti.func\n def circles(self, x: ti.template(), y: ti.template(), r: ti.template(), rgba: vec4):\n \"\"\"Draw circles with the same colour.\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n r (ti.template): Radii.\n rgba (vec4): Colour.\n \"\"\"\n for i in ti.static(range(len(x))):\n self.circle(x[i], y[i], r[i], rgba)\n\n @ti.func\n def triangle(self, a, b, c, rgba: vec4):\n \"\"\"Draw a filled triangle.\n\n Args:\n a (vec2): Point A.\n b (vec2): Point B.\n c (vec2): Point C.\n rgba (vec4): Colour.\n \"\"\"\n # TODO: fill arg\n x = ti.Vector([a[0], b[0], c[0]])\n y = ti.Vector([a[1], b[1], c[1]])\n self.polygon(x, y, rgba)\n\n @ti.func\n def polygon(self, x: ti.template(), y: ti.template(), rgba: vec4):\n \"\"\"Draw a filled polygon.\n\n Polygons are drawn according to the polygon mode, which can be \"crossing\" \n (default) or \"winding\". First, the bounding box of the polygon is calculated.\n Then, we check if each pixel in the bounding box is inside the polygon. If it\n is, we draw it (along with each neighbour pixel).\n\n Reference for point in polygon inclusion testing:\n http://www.dgp.toronto.edu/~mac/e-stuff/point_in_polygon.py\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n rgba (vec4): Colour.\n\n TODO: fill arg\n \"\"\"\n x_min, x_max = ti.cast(x.min(), ti.i32), ti.cast(x.max(), ti.i32)\n y_min, y_max = ti.cast(y.min(), ti.i32), ti.cast(y.max(), ti.i32)\n l = len(x)\n for i, j in ti.ndrange(x_max - x_min, y_max - y_min):\n p = ti.Vector([x_min + i, y_min + j])\n if self._is_inside(p, x, y, l) != 0:\n # TODO: abstract out, weight?\n \"\"\"\n x-1,y-1 x,y-1 x+1,y-1\n x-1,y x,y x+1,y\n x-1,y+1 x,y+1 x+1,y+1\n \"\"\"\n _x, _y = p[0], p[1]\n self.px.rgba[_x - 1, _y - 1] = rgba\n self.px.rgba[_x - 1, _y] = rgba\n self.px.rgba[_x - 1, _y + 1] = rgba\n\n self.px.rgba[_x, _y - 1] = rgba\n self.px.rgba[_x, _y] = rgba\n self.px.rgba[_x, _y + 1] = rgba\n\n self.px.rgba[_x + 1, _y - 1] = rgba\n self.px.rgba[_x + 1, _y] = rgba\n self.px.rgba[_x + 1, _y + 1] = rgba\n\n @ti.func\n def _is_inside(self, p: vec2, x: ti.template(), y: ti.template(), l: ti.i32):\n \"\"\"Check if point is inside polygon.\n\n Args:\n p (vec2): Point.\n x (ti.template): X positions.\n y (ti.template): Y positions.\n l (ti.i32): Number of points.\n\n Returns:\n int: 1 if inside, 0 if outside.\n \"\"\"\n is_inside = 0\n if self.polygon_mode == \"crossing\":\n is_inside = self._is_inside_crossing(p, x, y, l)\n elif self.polygon_mode == \"winding\":\n is_inside = self._is_inside_winding(p, x, y, l)\n return is_inside\n\n @ti.func\n def _is_inside_crossing(self, p: vec2, x: ti.template(), y: ti.template(), l: ti.i32):\n \"\"\"Check if point is inside polygon using crossing number algorithm.\n\n Args:\n p (vec2): Point.\n x (ti.template): X positions.\n y (ti.template): Y positions.\n l (ti.i32): Number of points.\n\n Returns:\n int: 1 if inside, 0 if outside.\n \"\"\"\n n = 0\n v0, v1 = ti.Vector([0.0, 0.0]), ti.Vector([0.0, 0.0])\n for i in range(l):\n i1 = i + 1 if i < l - 1 else 0\n v0, v1 = [x[i], y[i]], [x[i1], y[i1]]\n if (v0[1] <= p[1] and v1[1] > p[1]) or (v0[1] > p[1] and v1[1] <= p[1]):\n vt = (p[1] - v0[1]) / (v1[1] - v0[1])\n if p[0] < v0[0] + vt * (v1[0] - v0[0]):\n n += 1\n return n % 2\n\n @ti.func\n def _is_inside_winding(self, p: vec2, x: ti.template(), y: ti.template(), l: ti.i32):\n \"\"\"Check if point is inside polygon using winding number algorithm.\n\n Args:\n p (vec2): Point.\n x (ti.template): X positions.\n y (ti.template): Y positions.\n l (ti.i32): Number of points.\n\n Returns:\n int: 1 if inside, 0 if outside.\n \"\"\"\n n = 0\n v0, v1 = ti.Vector([0.0, 0.0]), ti.Vector([0.0, 0.0])\n for i in range(l):\n i1 = i + 1 if i < l - 1 else 0\n v0, v1 = [x[i], y[i]], [x[i1], y[i1]]\n if v0[1] <= p[1] and v1[1] > p[1] and (v0 - v1).cross(p - v1) > 0:\n n += 1\n elif v1[1] <= p[1] and (v0 - v1).cross(p - v1) < 0:\n n -= 1\n return n\n\n @ti.kernel\n def flip_x(self):\n \"\"\"Flip image in x-axis.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba_inv[i, j] = self.px.rgba[self.x - 1 - i, j]\n\n @ti.kernel\n def flip_y(self):\n \"\"\"Flip image in y-axis.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba_inv[i, j] = self.px.rgba[i, self.y - 1 - j]\n\n @ti.kernel\n def invert(self):\n \"\"\"Invert image.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = 1.0 - self.px.rgba[i, j]\n\n @ti.kernel\n def decay(self, rate: ti.f32):\n \"\"\"Decay pixels.\n\n Args:\n rate (ti.f32): decay rate.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] *= rate\n\n def blend_add(self, px: ti.template()):\n \"\"\"Blend by adding pixels together (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_add(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_add(self, rgba: ti.template()):\n \"\"\"Blend by adding pixels together (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] += rgba[i, j]\n\n def blend_sub(self, px: ti.template()):\n \"\"\"Blend by subtracting pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_sub(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_sub(self, rgba: ti.template()):\n \"\"\"Blend by subtracting pixels (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] -= rgba[i, j]\n\n def blend_mul(self, px: ti.template()):\n \"\"\"Blend by multiplying pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_mul(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_mul(self, rgba: ti.template()):\n \"\"\"Blend by multiplying pixels (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] *= rgba[i, j]\n\n def blend_div(self, px: ti.template()):\n \"\"\"Blend by dividing pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_div(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_div(self, rgba: ti.template()):\n \"\"\"Blend by dividing pixels (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] /= rgba[i, j]\n\n def blend_min(self, px: ti.template()):\n \"\"\"Blend by taking the minimum of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_min(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_min(self, rgba: ti.template()):\n \"\"\"Blend by taking the minimum of each pixel (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.min(self.px.rgba[i, j], rgba[i, j])\n\n def blend_max(self, px: ti.template()):\n \"\"\"Blend by taking the maximum of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_max(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_max(self, rgba: ti.template()):\n \"\"\"Blend by taking the maximum of each pixel (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.max(self.px.rgba[i, j], rgba[i, j])\n\n def blend_diff(self, px: ti.template()):\n \"\"\"Blend by taking the difference of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_diff(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_diff(self, rgba: ti.template()):\n \"\"\"Blend by taking the difference of each pixel (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.abs(self.px.rgba[i, j] - rgba[i, j])\n\n def blend_diff_inv(self, px: ti.template()):\n \"\"\"Blend by taking the inverse difference of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_diff_inv(self.rgba_from_px(px))\n\n @ti.kernel\n def _blend_diff_inv(self, rgba: ti.template()):\n \"\"\"Blend by taking the inverse difference of each pixel (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.abs(rgba[i, j] - self.px.rgba[i, j])\n\n def blend_mix(self, px: ti.template(), amount: ti.f32):\n \"\"\"Blend by mixing pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n amount (ti.f32): Amount to mix.\n \"\"\"\n self._blend_mix(self.rgba_from_px(px), amount)\n\n @ti.kernel\n def _blend_mix(self, rgba: ti.template(), amount: ti.f32):\n \"\"\"Blend by mixing pixels (Taichi scope).\n\n Args:\n rgba (ti.template): Pixels to blend with.\n amount (ti.f32): Amount to mix.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = ti.math.mix(self.px.rgba[i, j], rgba[i, j], amount)\n\n @ti.kernel\n def blur(self, radius: ti.i32):\n \"\"\"Blur pixels.\n\n Args:\n radius (ti.i32): Blur radius.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n d = ti.Vector([0.0, 0.0, 0.0, 0.0])\n for di in range(-radius, radius + 1):\n for dj in range(-radius, radius + 1):\n dx = (i + di) % self.x\n dy = (j + dj) % self.y\n d += self.px.rgba[dx, dy]\n d /= (radius * 2 + 1) ** 2\n self.px.rgba[i, j] = d\n\n def particles(\n self, particles: ti.template(), species: ti.template(), shape=\"circle\"\n ):\n \"\"\"Draw particles.\n\n Args:\n particles (ti.template): Particles.\n species (ti.template): Species.\n shape (str, optional): Shape. Defaults to \"circle\".\n \"\"\"\n shape = self.shape_enum[shape]\n self._particles(particles, species, shape)\n\n @ti.kernel\n def _particles(self, particles: ti.template(), species: ti.template(), shape: int):\n \"\"\"Draw particles.\n\n Args:\n particles (ti.template): Particles.\n species (ti.template): Species.\n shape (int): Shape enum value.\n \"\"\"\n for i in range(self.tv.p.n):\n p = particles.field[i]\n s = species[p.species]\n if p.active == 0.0:\n continue\n px = ti.cast(p.pos[0], ti.i32)\n py = ti.cast(p.pos[1], ti.i32)\n vx = ti.cast(p.pos[0] + p.vel[0] * 20, ti.i32)\n vy = ti.cast(p.pos[1] + p.vel[1] * 20, ti.i32)\n rgba = s.rgba * self.CONSTS.BRIGHTNESS\n if shape == 0:\n self.point(px, py, rgba)\n elif shape == 1:\n self.line(px, py, vx, vy, rgba)\n elif shape == 2:\n side = int(s.size) * 2\n self.rect(px, py, side, side, rgba)\n elif shape == 3:\n self.circle(px, py, p.size, rgba)\n elif shape == 4:\n a = p.pos\n b = p.pos + 1\n c = a + b\n self.triangle(a, b, c, rgba)\n # elif shape == 5:\n # self.polygon(px, py, rgba)\n\n def rgba_from_px(self, px):\n \"\"\"Get rgba from pixels.\n\n Args:\n px (Any): Pixels to get rgba from.\n\n Raises:\n TypeError: If pixel field cannot be found.\n\n Returns:\n MatrixField: RGBA matrix field.\n \"\"\"\n if isinstance(px, Pixels):\n return px.px.rgba\n elif isinstance(px, StructField):\n return px.rgba\n elif isinstance(px, MatrixField):\n return px\n else:\n try:\n return px.px.px.rgba\n except:\n raise TypeError(f\"Cannot find pixel field in {type(px)}\")\n\n def __call__(self):\n \"\"\"Call returns pixels.\"\"\"\n return self.get()\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.__call__","title":"__call__()
","text":"Call returns pixels.
Source code in src/tolvera/pixels.py
def __call__(self):\n \"\"\"Call returns pixels.\"\"\"\n return self.get()\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise Pixels
Parameters:
Name Type Description Default tolvera
Tolvera
T\u00f6lvera instance.
required **kwargs
Keyword arguments. polygon_mode (str): Polygon mode. Defaults to \"crossing\". brightness (float): Brightness. Defaults to 1.0.
{}
Source code in src/tolvera/pixels.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise Pixels\n\n Args:\n tolvera (Tolvera): T\u00f6lvera instance.\n **kwargs: Keyword arguments.\n polygon_mode (str): Polygon mode. Defaults to \"crossing\".\n brightness (float): Brightness. Defaults to 1.0. \n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.polygon_mode = kwargs.get(\"polygon_mode\", \"crossing\")\n self.x = self.tv.x\n self.y = self.tv.y\n self.px = Pixel.field(shape=(self.x, self.y))\n brightness = kwargs.get(\"brightness\", 1.0)\n self.CONSTS = CONSTS(\n {\n \"BRIGHTNESS\": (ti.f32, brightness),\n }\n )\n self.shape_enum = {\n \"point\": 0,\n \"line\": 1,\n \"rect\": 2,\n \"circle\": 3,\n \"triangle\": 4,\n \"polygon\": 5,\n }\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.background","title":"background(r, g, b)
","text":"Set background colour.
Parameters:
Name Type Description Default r
f32
Red.
required g
f32
Green.
required b
f32
Blue.
required Source code in src/tolvera/pixels.py
@ti.func\ndef background(self, r: ti.f32, g: ti.f32, b: ti.f32):\n \"\"\"Set background colour.\n\n Args:\n r (ti.f32): Red.\n g (ti.f32): Green.\n b (ti.f32): Blue.\n \"\"\"\n bg = ti.Vector([r, g, b, 1.0])\n self.rect(0, 0, self.x, self.y, bg)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_add","title":"blend_add(px)
","text":"Blend by adding pixels together (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required Source code in src/tolvera/pixels.py
def blend_add(self, px: ti.template()):\n \"\"\"Blend by adding pixels together (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_add(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_diff","title":"blend_diff(px)
","text":"Blend by taking the difference of each pixel (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required Source code in src/tolvera/pixels.py
def blend_diff(self, px: ti.template()):\n \"\"\"Blend by taking the difference of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_diff(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_diff_inv","title":"blend_diff_inv(px)
","text":"Blend by taking the inverse difference of each pixel (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required Source code in src/tolvera/pixels.py
def blend_diff_inv(self, px: ti.template()):\n \"\"\"Blend by taking the inverse difference of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_diff_inv(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_div","title":"blend_div(px)
","text":"Blend by dividing pixels (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required Source code in src/tolvera/pixels.py
def blend_div(self, px: ti.template()):\n \"\"\"Blend by dividing pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_div(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_max","title":"blend_max(px)
","text":"Blend by taking the maximum of each pixel (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required Source code in src/tolvera/pixels.py
def blend_max(self, px: ti.template()):\n \"\"\"Blend by taking the maximum of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_max(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_min","title":"blend_min(px)
","text":"Blend by taking the minimum of each pixel (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required Source code in src/tolvera/pixels.py
def blend_min(self, px: ti.template()):\n \"\"\"Blend by taking the minimum of each pixel (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_min(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_mix","title":"blend_mix(px, amount)
","text":"Blend by mixing pixels (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required amount
f32
Amount to mix.
required Source code in src/tolvera/pixels.py
def blend_mix(self, px: ti.template(), amount: ti.f32):\n \"\"\"Blend by mixing pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n amount (ti.f32): Amount to mix.\n \"\"\"\n self._blend_mix(self.rgba_from_px(px), amount)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_mul","title":"blend_mul(px)
","text":"Blend by multiplying pixels (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required Source code in src/tolvera/pixels.py
def blend_mul(self, px: ti.template()):\n \"\"\"Blend by multiplying pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_mul(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blend_sub","title":"blend_sub(px)
","text":"Blend by subtracting pixels (Python scope).
Parameters:
Name Type Description Default px
template
Pixels to blend with.
required Source code in src/tolvera/pixels.py
def blend_sub(self, px: ti.template()):\n \"\"\"Blend by subtracting pixels (Python scope).\n\n Args:\n px (ti.template): Pixels to blend with.\n \"\"\"\n self._blend_sub(self.rgba_from_px(px))\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.blur","title":"blur(radius)
","text":"Blur pixels.
Parameters:
Name Type Description Default radius
i32
Blur radius.
required Source code in src/tolvera/pixels.py
@ti.kernel\ndef blur(self, radius: ti.i32):\n \"\"\"Blur pixels.\n\n Args:\n radius (ti.i32): Blur radius.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n d = ti.Vector([0.0, 0.0, 0.0, 0.0])\n for di in range(-radius, radius + 1):\n for dj in range(-radius, radius + 1):\n dx = (i + di) % self.x\n dy = (j + dj) % self.y\n d += self.px.rgba[dx, dy]\n d /= (radius * 2 + 1) ** 2\n self.px.rgba[i, j] = d\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.circle","title":"circle(x, y, r, rgba)
","text":"Draw a filled circle.
Parameters:
Name Type Description Default x
i32
X position.
required y
i32
Y position.
required r
i32
Radius.
required rgba
vec4
Colour.
required Source code in src/tolvera/pixels.py
@ti.func\ndef circle(self, x: ti.i32, y: ti.i32, r: ti.i32, rgba: vec4):\n \"\"\"Draw a filled circle.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n r (ti.i32): Radius.\n rgba (vec4): Colour.\n \"\"\"\n for i in range(r + 1):\n d = ti.sqrt(r**2 - i**2)\n d_int = ti.cast(d, ti.i32)\n # TODO: parallelise ?\n for j in range(d_int):\n self.px.rgba[x + i, y + j] = rgba\n self.px.rgba[x + i, y - j] = rgba\n self.px.rgba[x - i, y - j] = rgba\n self.px.rgba[x - i, y + j] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.circles","title":"circles(x, y, r, rgba)
","text":"Draw circles with the same colour.
Parameters:
Name Type Description Default x
template
X positions.
required y
template
Y positions.
required r
template
Radii.
required rgba
vec4
Colour.
required Source code in src/tolvera/pixels.py
@ti.func\ndef circles(self, x: ti.template(), y: ti.template(), r: ti.template(), rgba: vec4):\n \"\"\"Draw circles with the same colour.\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n r (ti.template): Radii.\n rgba (vec4): Colour.\n \"\"\"\n for i in ti.static(range(len(x))):\n self.circle(x[i], y[i], r[i], rgba)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.clear","title":"clear()
","text":"Clear pixels.
Source code in src/tolvera/pixels.py
@ti.kernel\ndef clear(self):\n \"\"\"Clear pixels.\"\"\"\n self.px.rgba.fill(0)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.decay","title":"decay(rate)
","text":"Decay pixels.
Parameters:
Name Type Description Default rate
f32
decay rate.
required Source code in src/tolvera/pixels.py
@ti.kernel\ndef decay(self, rate: ti.f32):\n \"\"\"Decay pixels.\n\n Args:\n rate (ti.f32): decay rate.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] *= rate\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.diffuse","title":"diffuse(evaporate)
","text":"Diffuse pixels.
Parameters:
Name Type Description Default evaporate
float
Evaporation rate.
required Source code in src/tolvera/pixels.py
@ti.kernel\ndef diffuse(self, evaporate: ti.f32):\n \"\"\"Diffuse pixels.\n\n Args:\n evaporate (float): Evaporation rate.\n \"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n d = ti.Vector([0.0, 0.0, 0.0, 0.0]) \n for di in ti.static(range(-1, 2)):\n for dj in ti.static(range(-1, 2)):\n dx = (i + di) % self.x\n dy = (j + dj) % self.y\n d += self.px.rgba[dx, dy]\n d *= 0.99 / 9.0\n self.px.rgba[i, j] = d\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.flip_x","title":"flip_x()
","text":"Flip image in x-axis.
Source code in src/tolvera/pixels.py
@ti.kernel\ndef flip_x(self):\n \"\"\"Flip image in x-axis.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba_inv[i, j] = self.px.rgba[self.x - 1 - i, j]\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.flip_y","title":"flip_y()
","text":"Flip image in y-axis.
Source code in src/tolvera/pixels.py
@ti.kernel\ndef flip_y(self):\n \"\"\"Flip image in y-axis.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba_inv[i, j] = self.px.rgba[i, self.y - 1 - j]\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.get","title":"get()
","text":"Get pixels.
Source code in src/tolvera/pixels.py
def get(self):\n \"\"\"Get pixels.\"\"\"\n return self.px\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.invert","title":"invert()
","text":"Invert image.
Source code in src/tolvera/pixels.py
@ti.kernel\ndef invert(self):\n \"\"\"Invert image.\"\"\"\n for i, j in ti.ndrange(self.x, self.y):\n self.px.rgba[i, j] = 1.0 - self.px.rgba[i, j]\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.line","title":"line(x0, y0, x1, y1, rgba)
","text":"Draw a line using Bresenham's algorithm.
Parameters:
Name Type Description Default x0
i32
X start position.
required y0
i32
Y start position.
required x1
i32
X end position.
required y1
i32
Y end position.
required rgba
vec4
Colour.
required TODO: thickness TODO: anti-aliasing TODO: should lines wrap around (as two lines)?
Source code in src/tolvera/pixels.py
@ti.func\ndef line(self, x0: ti.i32, y0: ti.i32, x1: ti.i32, y1: ti.i32, rgba: vec4):\n \"\"\"Draw a line using Bresenham's algorithm.\n\n Args:\n x0 (ti.i32): X start position.\n y0 (ti.i32): Y start position.\n x1 (ti.i32): X end position.\n y1 (ti.i32): Y end position.\n rgba (vec4): Colour.\n\n TODO: thickness\n TODO: anti-aliasing\n TODO: should lines wrap around (as two lines)?\n \"\"\"\n dx = ti.abs(x1 - x0)\n dy = ti.abs(y1 - y0)\n x, y = x0, y0\n sx = -1 if x0 > x1 else 1\n sy = -1 if y0 > y1 else 1\n if dx > dy:\n err = dx / 2.0\n while x != x1:\n self.px.rgba[x, y] = rgba\n err -= dy\n if err < 0:\n y += sy\n err += dx\n x += sx\n else:\n err = dy / 2.0\n while y != y1:\n self.px.rgba[x, y] = rgba\n err -= dx\n if err < 0:\n x += sx\n err += dy\n y += sy\n self.px.rgba[x, y] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.particles","title":"particles(particles, species, shape='circle')
","text":"Draw particles.
Parameters:
Name Type Description Default particles
template
Particles.
required species
template
Species.
required shape
str
Shape. Defaults to \"circle\".
'circle'
Source code in src/tolvera/pixels.py
def particles(\n self, particles: ti.template(), species: ti.template(), shape=\"circle\"\n):\n \"\"\"Draw particles.\n\n Args:\n particles (ti.template): Particles.\n species (ti.template): Species.\n shape (str, optional): Shape. Defaults to \"circle\".\n \"\"\"\n shape = self.shape_enum[shape]\n self._particles(particles, species, shape)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.point","title":"point(x, y, rgba)
","text":"Draw point.
Parameters:
Name Type Description Default x
i32
X position.
required y
i32
Y position.
required rgba
vec4
Colour.
required Source code in src/tolvera/pixels.py
@ti.func\ndef point(self, x: ti.i32, y: ti.i32, rgba: vec4):\n \"\"\"Draw point.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n rgba (vec4): Colour.\n \"\"\"\n self.px.rgba[x, y] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.points","title":"points(x, y, rgba)
","text":"Draw points with the same colour.
Parameters:
Name Type Description Default x
template
X positions.
required y
template
Y positions.
required rgba
vec4
Colour.
required Source code in src/tolvera/pixels.py
@ti.func\ndef points(self, x: ti.template(), y: ti.template(), rgba: vec4):\n \"\"\"Draw points with the same colour.\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n rgba (vec4): Colour.\n \"\"\"\n for i in ti.static(range(len(x))):\n self.point(x[i], y[i], rgba)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.polygon","title":"polygon(x, y, rgba)
","text":"Draw a filled polygon.
Polygons are drawn according to the polygon mode, which can be \"crossing\" (default) or \"winding\". First, the bounding box of the polygon is calculated. Then, we check if each pixel in the bounding box is inside the polygon. If it is, we draw it (along with each neighbour pixel).
Reference for point in polygon inclusion testing: http://www.dgp.toronto.edu/~mac/e-stuff/point_in_polygon.py
Parameters:
Name Type Description Default x
template
X positions.
required y
template
Y positions.
required rgba
vec4
Colour.
required TODO: fill arg
Source code in src/tolvera/pixels.py
@ti.func\ndef polygon(self, x: ti.template(), y: ti.template(), rgba: vec4):\n \"\"\"Draw a filled polygon.\n\n Polygons are drawn according to the polygon mode, which can be \"crossing\" \n (default) or \"winding\". First, the bounding box of the polygon is calculated.\n Then, we check if each pixel in the bounding box is inside the polygon. If it\n is, we draw it (along with each neighbour pixel).\n\n Reference for point in polygon inclusion testing:\n http://www.dgp.toronto.edu/~mac/e-stuff/point_in_polygon.py\n\n Args:\n x (ti.template): X positions.\n y (ti.template): Y positions.\n rgba (vec4): Colour.\n\n TODO: fill arg\n \"\"\"\n x_min, x_max = ti.cast(x.min(), ti.i32), ti.cast(x.max(), ti.i32)\n y_min, y_max = ti.cast(y.min(), ti.i32), ti.cast(y.max(), ti.i32)\n l = len(x)\n for i, j in ti.ndrange(x_max - x_min, y_max - y_min):\n p = ti.Vector([x_min + i, y_min + j])\n if self._is_inside(p, x, y, l) != 0:\n # TODO: abstract out, weight?\n \"\"\"\n x-1,y-1 x,y-1 x+1,y-1\n x-1,y x,y x+1,y\n x-1,y+1 x,y+1 x+1,y+1\n \"\"\"\n _x, _y = p[0], p[1]\n self.px.rgba[_x - 1, _y - 1] = rgba\n self.px.rgba[_x - 1, _y] = rgba\n self.px.rgba[_x - 1, _y + 1] = rgba\n\n self.px.rgba[_x, _y - 1] = rgba\n self.px.rgba[_x, _y] = rgba\n self.px.rgba[_x, _y + 1] = rgba\n\n self.px.rgba[_x + 1, _y - 1] = rgba\n self.px.rgba[_x + 1, _y] = rgba\n self.px.rgba[_x + 1, _y + 1] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.rect","title":"rect(x, y, w, h, rgba)
","text":"Draw a filled rectangle.
Parameters:
Name Type Description Default x
i32
X position.
required y
i32
Y position.
required w
i32
Width.
required h
i32
Height.
required rgba
vec4
Colour.
required Source code in src/tolvera/pixels.py
@ti.func\ndef rect(self, x: ti.i32, y: ti.i32, w: ti.i32, h: ti.i32, rgba: vec4):\n \"\"\"Draw a filled rectangle.\n\n Args:\n x (ti.i32): X position.\n y (ti.i32): Y position.\n w (ti.i32): Width.\n h (ti.i32): Height.\n rgba (vec4): Colour.\n \"\"\"\n # TODO: fill arg\n # TODO: gradients, lerp with ti.math.mix(x, y, a)\n for i, j in ti.ndrange(w, h):\n self.px.rgba[x + i, y + j] = rgba\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.rgba_from_px","title":"rgba_from_px(px)
","text":"Get rgba from pixels.
Parameters:
Name Type Description Default px
Any
Pixels to get rgba from.
required Raises:
Type Description TypeError
If pixel field cannot be found.
Returns:
Name Type Description MatrixField
RGBA matrix field.
Source code in src/tolvera/pixels.py
def rgba_from_px(self, px):\n \"\"\"Get rgba from pixels.\n\n Args:\n px (Any): Pixels to get rgba from.\n\n Raises:\n TypeError: If pixel field cannot be found.\n\n Returns:\n MatrixField: RGBA matrix field.\n \"\"\"\n if isinstance(px, Pixels):\n return px.px.rgba\n elif isinstance(px, StructField):\n return px.rgba\n elif isinstance(px, MatrixField):\n return px\n else:\n try:\n return px.px.px.rgba\n except:\n raise TypeError(f\"Cannot find pixel field in {type(px)}\")\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.set","title":"set(px)
","text":"Set pixels.
Parameters:
Name Type Description Default px
Any
Pixels to set. Can be Pixels, StructField, MatrixField, etc (see rgba_from_px).
required Source code in src/tolvera/pixels.py
def set(self, px: Any):\n \"\"\"Set pixels.\n\n Args:\n px (Any): Pixels to set. Can be Pixels, StructField, MatrixField, etc (see rgba_from_px).\n \"\"\"\n self.px.rgba = self.rgba_from_px(px)\n
"},{"location":"reference/tolvera/pixels/#tolvera.pixels.Pixels.triangle","title":"triangle(a, b, c, rgba)
","text":"Draw a filled triangle.
Parameters:
Name Type Description Default a
vec2
Point A.
required b
vec2
Point B.
required c
vec2
Point C.
required rgba
vec4
Colour.
required Source code in src/tolvera/pixels.py
@ti.func\ndef triangle(self, a, b, c, rgba: vec4):\n \"\"\"Draw a filled triangle.\n\n Args:\n a (vec2): Point A.\n b (vec2): Point B.\n c (vec2): Point C.\n rgba (vec4): Colour.\n \"\"\"\n # TODO: fill arg\n x = ti.Vector([a[0], b[0], c[0]])\n y = ti.Vector([a[1], b[1], c[1]])\n self.polygon(x, y, rgba)\n
"},{"location":"reference/tolvera/sketchbook/","title":"Sketchbook","text":"Sketchbook module for listing and running sketches from the command line.
WIP.
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.get_sketch_info","title":"get_sketch_info(sketch_file, sketchbook_folder='./')
","text":"Gets information about a specific sketch file.
Parameters:
Name Type Description Default sketch_file
str
Name of the sketch file.
required sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Returns:
Type Description Dict[str, Any]
Dict[str, Any]: Dictionary containing sketch information such as name, path, size, modified and created times.
Source code in src/tolvera/sketchbook.py
def get_sketch_info(sketch_file: str, sketchbook_folder: str = \"./\") -> Dict[str, Any]:\n \"\"\"\n Gets information about a specific sketch file.\n\n Args:\n sketch_file (str): Name of the sketch file.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Returns:\n Dict[str, Any]: Dictionary containing sketch information such as name, path, size, modified and created times.\n \"\"\"\n validate_sketch_file(sketch_file, sketchbook_folder)\n datetimefmt = \"%Y-%m-%d %H:%M:%S\"\n if not sketch_file.endswith(\".py\"):\n sketch_file += \".py\"\n file_path = os.path.join(sketchbook_folder, sketch_file)\n module_name = os.path.splitext(sketch_file)[0]\n file_info = os.stat(file_path)\n size = file_info.st_size\n modified = datetime.datetime.fromtimestamp(file_info.st_mtime).strftime(datetimefmt)\n created = datetime.datetime.fromtimestamp(file_info.st_ctime).strftime(datetimefmt)\n return {\n \"name\": module_name,\n \"path\": file_path,\n \"size\": size,\n \"modified\": modified,\n \"created\": created,\n }\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.get_sketchbook_files","title":"get_sketchbook_files(sketchbook_folder='./')
","text":"Gets all sketch files from the sketchbook folder.
Parameters:
Name Type Description Default sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Returns:
Type Description List[str]
List[str]: List of sketch file names.
Source code in src/tolvera/sketchbook.py
def get_sketchbook_files(sketchbook_folder: str = \"./\") -> List[str]:\n \"\"\"\n Gets all sketch files from the sketchbook folder.\n\n Args:\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Returns:\n List[str]: List of sketch file names.\n \"\"\"\n files = os.listdir(sketchbook_folder)\n exclude = [\"__init__.py\", \"__pycache__\", \".DS_Store\", \"imgui.ini\"]\n return [f for f in files if f not in exclude]\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.get_sketchbook_files_info","title":"get_sketchbook_files_info(sketches, sketchbook_folder='./')
","text":"Gets information about all sketch files in the sketchbook folder.
Parameters:
Name Type Description Default sketches
List[str]
List of sketch file names.
required sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Returns:
Type Description List[Dict[str, Any]]
List[Dict[str, Any]]: List of sketch information dictionaries.
Source code in src/tolvera/sketchbook.py
def get_sketchbook_files_info(\n sketches: List[str], sketchbook_folder: str = \"./\"\n) -> List[Dict[str, Any]]:\n \"\"\"\n Gets information about all sketch files in the sketchbook folder.\n\n Args:\n sketches (List[str]): List of sketch file names.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Returns:\n List[Dict[str, Any]]: List of sketch information dictionaries.\n \"\"\"\n sketch_infos = []\n for sketch in sketches:\n sketch_info = get_sketch_info(sketch, sketchbook_folder)\n sketch_infos.append(sketch_info)\n return sketch_infos\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.import_sketch","title":"import_sketch(module_name, file_path)
","text":"Imports a sketch from a given file.
Parameters:
Name Type Description Default module_name
str
Name of the module.
required file_path
str
Path to the file containing the module.
required Returns:
Name Type Description Any
Any
Imported module.
Source code in src/tolvera/sketchbook.py
def import_sketch(module_name: str, file_path: str) -> Any:\n \"\"\"\n Imports a sketch from a given file.\n\n Args:\n module_name (str): Name of the module.\n file_path (str): Path to the file containing the module.\n\n Returns:\n Any: Imported module.\n \"\"\"\n if not os.path.exists(file_path):\n print(f\"File does not exist: {file_path}\")\n return None\n\n if not module_name:\n print(\"Module name is empty or invalid.\")\n return None\n try:\n print(f\"Importing {module_name} from {file_path}...\")\n spec = importlib.util.spec_from_file_location(module_name, file_path)\n module = importlib.util.module_from_spec(spec)\n spec.loader.exec_module(module)\n return module\n except Exception as e:\n error_type = type(e).__name__\n print(f\"Error importing {module_name} ({error_type}): {str(e)}\")\n return None\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.list_sketches","title":"list_sketches(sketchbook_folder='./', sort='name', direction='ascending')
","text":"Lists all sketches in the given sketchbook folder.
Parameters:
Name Type Description Default sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
sort
str
Sort sketches by name, size, modified or created. Defaults to 'name'.
'name'
direction
str
Sort direction, either 'ascending' or 'descending'. Defaults to 'ascending'.
'ascending'
Source code in src/tolvera/sketchbook.py
def list_sketches(\n sketchbook_folder: str = \"./\", sort: str = \"name\", direction: str = \"ascending\"\n) -> None:\n \"\"\"\n Lists all sketches in the given sketchbook folder.\n\n Args:\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n sort (str): Sort sketches by name, size, modified or created. Defaults to 'name'.\n direction (str): Sort direction, either 'ascending' or 'descending'. Defaults to 'ascending'.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n sketch_files_list = get_sketchbook_files(sketchbook_folder)\n sketches = get_sketchbook_files_info(sketch_files_list, sketchbook_folder)\n sorted_sketches = sort_sketch_files(sketches, sort, direction)\n pretty_print_sketchbook(sorted_sketches, sketchbook_folder)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.main","title":"main(*args, **kwargs)
","text":"Main function for running the sketchbook from the command line.
Source code in src/tolvera/sketchbook.py
def main(*args, **kwargs):\n \"\"\"\n Main function for running the sketchbook from the command line.\n \"\"\"\n if \"sketchbook\" in kwargs:\n sketchbook = kwargs[\"sketchbook\"]\n else:\n sketchbook = \"./\"\n if \"sketch\" in kwargs:\n sketch = kwargs[\"sketch\"]\n if isinstance(sketch, str):\n run_sketch_by_name(sketch, sketchbook, *args, **kwargs)\n elif isinstance(sketch, int):\n print(f\"Running sketch by index: {sketch}\")\n run_sketch_by_index(sketch, sketchbook, *args, **kwargs)\n elif \"sketches\" in kwargs:\n sort = kwargs[\"sort\"] if \"sort\" in kwargs else \"name\"\n direction = kwargs[\"direction\"] if \"direction\" in kwargs else \"ascending\"\n list_sketches(sketchbook, sort, direction)\n exit()\n elif \"random\" in kwargs:\n run_random_sketch(sketchbook)\n else:\n list_sketches(sketchbook)\n exit()\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.pretty_print_sketchbook","title":"pretty_print_sketchbook(sketches, sketchbook_folder='./')
","text":"Pretty prints the sketchbook information.
Parameters:
Name Type Description Default sketches
List[Dict[str, Any]]
List of sketch information dictionaries.
required sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Source code in src/tolvera/sketchbook.py
def pretty_print_sketchbook(\n sketches: List[Dict[str, Any]], sketchbook_folder: str = \"./\"\n) -> None:\n \"\"\"\n Pretty prints the sketchbook information.\n\n Args:\n sketches (List[Dict[str, Any]]): List of sketch information dictionaries.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n print(f\"\\nSketchbook '{sketchbook_folder}':\\n\")\n print(\n f\" {'Index':<5} {'Sketch':<25} {'Size':<10} {'Modified':<20} {'Created':<20}\"\n )\n print(f\" {'-'*5:<5} {'-'*25:<25} {'-'*10:<10} {'-'*20:<20} {'-'*20:<20}\")\n for i, sketch in enumerate(sketches):\n print(\n f\" {i:<5} {sketch['name']:<25} {sketch['size']:<10} {sketch['modified']:<20} {sketch['created']:<20}\"\n )\n print()\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.run_random_sketch","title":"run_random_sketch(sketchbook='./')
","text":"Runs a random sketch from the sketchbook.
Parameters:
Name Type Description Default sketchbook
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Source code in src/tolvera/sketchbook.py
def run_random_sketch(sketchbook=\"./\"):\n \"\"\"\n Runs a random sketch from the sketchbook.\n\n Args:\n sketchbook (str): Path to the sketchbook folder. Defaults to current directory.\n \"\"\"\n validate_sketchbook_path(sketchbook)\n files = get_sketchbook_files(sketchbook)\n sketch_file = files[random.randint(0, len(files) - 1)]\n run_sketch_by_name(sketch_file, sketchbook)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.run_sketch_by_index","title":"run_sketch_by_index(index, sketchbook_folder='./', *args, **kwargs)
","text":"Runs a sketch by its index in the sketchbook.
Parameters:
Name Type Description Default index
int
Index of the sketch to run.
required sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Source code in src/tolvera/sketchbook.py
def run_sketch_by_index(\n index: int, sketchbook_folder: str = \"./\", *args: Any, **kwargs: Any\n) -> None:\n \"\"\"\n Runs a sketch by its index in the sketchbook.\n\n Args:\n index (int): Index of the sketch to run.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n files = get_sketchbook_files(sketchbook_folder)\n for file in files:\n if file.endswith(\".py\"):\n module_name = os.path.splitext(file)[0]\n file_path = os.path.join(sketchbook_folder, file)\n if str(index) == module_name:\n try_import_and_run_sketch(module_name, file_path, *args, **kwargs)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.run_sketch_by_name","title":"run_sketch_by_name(sketch_file, sketchbook_folder='./', *args, **kwargs)
","text":"Runs a sketch by its file name.
Parameters:
Name Type Description Default sketch_file
str
Name of the sketch file.
required sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Source code in src/tolvera/sketchbook.py
def run_sketch_by_name(\n sketch_file: str, sketchbook_folder: str = \"./\", *args: Any, **kwargs: Any\n) -> None:\n \"\"\"\n Runs a sketch by its file name.\n\n Args:\n sketch_file (str): Name of the sketch file.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n if not sketch_file.endswith(\".py\"):\n sketch_file += \".py\"\n file_path = os.path.join(sketchbook_folder, sketch_file)\n module_name = os.path.splitext(sketch_file)[0]\n try_import_and_run_sketch(module_name, file_path, *args, **kwargs)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.run_sketch_function_from_module","title":"run_sketch_function_from_module(module, function_name, file_path, *args, **kwargs)
","text":"Runs a specific function from a given module.
Parameters:
Name Type Description Default module
Any
The imported module.
required function_name
str
Name of the function to run.
required file_path
str
Path to the file containing the module.
required Source code in src/tolvera/sketchbook.py
def run_sketch_function_from_module(\n module: Any, function_name: str, file_path: str, *args: Any, **kwargs: Any\n) -> None:\n \"\"\"\n Runs a specific function from a given module.\n\n Args:\n module (Any): The imported module.\n function_name (str): Name of the function to run.\n file_path (str): Path to the file containing the module.\n \"\"\"\n try:\n if hasattr(module, function_name) and callable(getattr(module, function_name)):\n print(f\"Running {function_name} from {file_path}...\")\n getattr(module, function_name)(*args, **kwargs)\n else:\n print(f\"{module} does not have a '{function_name}' function.\")\n except Exception as e:\n print(f\"Error running {function_name} from {file_path}: {str(e)}\")\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.sort_sketch_files","title":"sort_sketch_files(sketch_files, sort='name', direction='ascending')
","text":"Sorts sketch files by name, size, modified or created.
Parameters:
Name Type Description Default sort
str
Sort sketches by name, size, modified or created. Defaults to 'name'.
'name'
sketch_files
List[Dict[str, Any]]
List of sketch information dictionaries.
required direction
str
Sort direction, either 'ascending' or 'descending'. Defaults to 'ascending'.
'ascending'
Returns:
Type Description List[Dict[str, Any]]
List[Dict[str, Any]]: List of sorted sketch information dictionaries.
Source code in src/tolvera/sketchbook.py
def sort_sketch_files(\n sketch_files: List[Dict[str, Any]], sort: str = \"name\", direction: str = \"ascending\"\n) -> List[Dict[str, Any]]:\n \"\"\"\n Sorts sketch files by name, size, modified or created.\n\n Args:\n sort (str): Sort sketches by name, size, modified or created. Defaults to 'name'.\n sketch_files (List[Dict[str, Any]]): List of sketch information dictionaries.\n direction (str): Sort direction, either 'ascending' or 'descending'. Defaults to 'ascending'.\n\n Returns:\n List[Dict[str, Any]]: List of sorted sketch information dictionaries.\n \"\"\"\n reverse = True if direction == \"descending\" else False\n if sort == \"name\":\n return sorted(sketch_files, key=lambda k: k[\"name\"], reverse=reverse)\n elif sort == \"size\":\n return sorted(sketch_files, key=lambda k: k[\"size\"], reverse=reverse)\n elif sort == \"modified\":\n return sorted(sketch_files, key=lambda k: k[\"modified\"], reverse=reverse)\n elif sort == \"created\":\n return sorted(sketch_files, key=lambda k: k[\"created\"], reverse=reverse)\n else:\n return sketch_files\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.try_import_and_run_sketch","title":"try_import_and_run_sketch(module_name, file_path, *args, **kwargs)
","text":"Tries to import and run a sketch from a given file.
Parameters:
Name Type Description Default module_name
str
Name of the module.
required file_path
str
Path to the file containing the module.
required Source code in src/tolvera/sketchbook.py
def try_import_and_run_sketch(\n module_name: str, file_path: str, *args: Any, **kwargs: Any\n) -> None:\n \"\"\"\n Tries to import and run a sketch from a given file.\n\n Args:\n module_name (str): Name of the module.\n file_path (str): Path to the file containing the module.\n \"\"\"\n try:\n module = import_sketch(module_name, file_path)\n run_sketch_function_from_module(module, \"sketch\", file_path, *args, **kwargs)\n except Exception as e:\n print(f\"Error running {module_name}: {str(e)}\")\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.validate_sketch_file","title":"validate_sketch_file(sketch_file, sketchbook_folder='./')
","text":"Validates if the given sketch file exists in the sketchbook folder.
Parameters:
Name Type Description Default sketch_file
str
Name of the sketch file.
required sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Raises:
Type Description SystemExit
If the sketch file does not exist.
Source code in src/tolvera/sketchbook.py
def validate_sketch_file(sketch_file: str, sketchbook_folder: str = \"./\") -> None:\n \"\"\"\n Validates if the given sketch file exists in the sketchbook folder.\n\n Args:\n sketch_file (str): Name of the sketch file.\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Raises:\n SystemExit: If the sketch file does not exist.\n \"\"\"\n validate_sketchbook_path(sketchbook_folder)\n if not sketch_file.endswith(\".py\"):\n sketch_file += \".py\"\n file_path = os.path.join(sketchbook_folder, sketch_file)\n if not os.path.isfile(file_path):\n print(f\"Sketch file '{file_path}' does not exist.\")\n sys.exit(1)\n
"},{"location":"reference/tolvera/sketchbook/#tolvera.sketchbook.validate_sketchbook_path","title":"validate_sketchbook_path(sketchbook_folder='./')
","text":"Validates if the given sketchbook folder exists.
Parameters:
Name Type Description Default sketchbook_folder
str
Path to the sketchbook folder. Defaults to current directory.
'./'
Raises:
Type Description SystemExit
If the sketchbook folder does not exist.
Source code in src/tolvera/sketchbook.py
def validate_sketchbook_path(sketchbook_folder: str = \"./\") -> None:\n \"\"\"\n Validates if the given sketchbook folder exists.\n\n Args:\n sketchbook_folder (str): Path to the sketchbook folder. Defaults to current directory.\n\n Raises:\n SystemExit: If the sketchbook folder does not exist.\n \"\"\"\n if not os.path.isdir(sketchbook_folder):\n print(f\"Sketchbook folder '{sketchbook_folder}' does not exist.\")\n sys.exit(1)\n
"},{"location":"reference/tolvera/species/","title":"Species","text":"Species class.
"},{"location":"reference/tolvera/species/#tolvera.species.Species","title":"Species
","text":"Species in T\u00f6lvera.
Species are implemented as a State with attributes for size
, speed
, mass
and colour
(rgba), and with a length determined by the number of species in the T\u00f6lvera instance (tv.sn
). The attributes are normalised and scaled by species the species_consts
attribute. They are initialised with random values.
Rather than accessing this class directly, access is typically via the State attributes via the T\u00f6lvera instance, via e.g. tv.s.species.field[i].size
.
Source code in src/tolvera/species.py
class Species:\n \"\"\"Species in T\u00f6lvera.\n\n Species are implemented as a State with attributes for `size`, `speed`, `mass` and\n `colour` (rgba), and with a length determined by the number of species in the\n T\u00f6lvera instance (`tv.sn`). The attributes are normalised and scaled by species the \n `species_consts` attribute. They are initialised with random values.\n\n Rather than accessing this class directly, access is typically via the State\n attributes via the T\u00f6lvera instance, via e.g. `tv.s.species.field[i].size`.\n \"\"\"\n def __init__(self, tolvera, **kwargs) -> None:\n \"\"\"Initialise Species\n\n Args:\n tolvera (Tolvera): Tolvera instance.\n **kwargs: Keyword arguments. \n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.n = self.tv.sn\n self.tv.species_consts = CONSTS(\n {\n \"MIN_SIZE\": (ti.f32, 2.0),\n \"MAX_SIZE\": (ti.f32, 5.0),\n \"MIN_SPEED\": (ti.f32, 0.2),\n \"MAX_SPEED\": (ti.f32, 2.0),\n \"MAX_MASS\": (ti.f32, 1.0),\n }\n )\n self.tv.s.species = (\n {\n \"size\": (ti.f32, 0.0, 1.0),\n \"speed\": (ti.f32, 0.0, 1.0),\n \"mass\": (ti.f32, 0.0, 1.0),\n \"rgba\": (ti.math.vec4, 0.0, 1.0),\n },\n self.n,\n \"set\",\n \"set\",\n )\n\n def randomise(self):\n \"\"\"Randomise species.\"\"\"\n self.tv.s.species.randomise()\n
"},{"location":"reference/tolvera/species/#tolvera.species.Species.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise Species
Parameters:
Name Type Description Default tolvera
Tolvera
Tolvera instance.
required **kwargs
Keyword arguments.
{}
Source code in src/tolvera/species.py
def __init__(self, tolvera, **kwargs) -> None:\n \"\"\"Initialise Species\n\n Args:\n tolvera (Tolvera): Tolvera instance.\n **kwargs: Keyword arguments. \n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.n = self.tv.sn\n self.tv.species_consts = CONSTS(\n {\n \"MIN_SIZE\": (ti.f32, 2.0),\n \"MAX_SIZE\": (ti.f32, 5.0),\n \"MIN_SPEED\": (ti.f32, 0.2),\n \"MAX_SPEED\": (ti.f32, 2.0),\n \"MAX_MASS\": (ti.f32, 1.0),\n }\n )\n self.tv.s.species = (\n {\n \"size\": (ti.f32, 0.0, 1.0),\n \"speed\": (ti.f32, 0.0, 1.0),\n \"mass\": (ti.f32, 0.0, 1.0),\n \"rgba\": (ti.math.vec4, 0.0, 1.0),\n },\n self.n,\n \"set\",\n \"set\",\n )\n
"},{"location":"reference/tolvera/species/#tolvera.species.Species.randomise","title":"randomise()
","text":"Randomise species.
Source code in src/tolvera/species.py
def randomise(self):\n \"\"\"Randomise species.\"\"\"\n self.tv.s.species.randomise()\n
"},{"location":"reference/tolvera/state/","title":"State","text":"State and StateDict classes for T\u00f6lvera.
Every T\u00f6lvera instance has a StateDict, which is a dictionary of State instances. The StateDict is accessible via the 's' attribute of a T\u00f6lvera instance, and can be used to create and access states.
Each State instance has a Taichi struct field and a corresponding NpNdarrayDict, which handles OSC accessors and endpoints.
"},{"location":"reference/tolvera/state/#tolvera.state.State","title":"State
","text":"State class for T\u00f6lvera.
This class takes a name, dictionary of state attributes, and a shape, and creates a Taichi struct field and a corresponding dictionary of NumPy arrays (NpNdarrayDict) for a state.
The Taichi struct field can be used in Taichi scope, and the NpNdarrayDict can be used in Python scope, and the two are kept in sync by the from_nddict() and to_nddict() methods.
The State class also handles OSC accessors for the state, which use the NpNdarrayDict to get and set data. A T\u00f6lvera instance is therefore required to initialise a State instance.
State attributes are defined as a dictionary of attribute names and tuples of (Taichi type, min value, max value). The domain of the attribute is used when randomising the data in the state, and by OSCMap endpoints and client patches.
The state is n-dimensional based on the shape argument, and the NpNdarrayDict provides methods for accessing the data in the state in n-dimensional slices.
Example tv.s.flock_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn, # particle count\n \"osc\": (\"get\"),\n \"randomise\": False,\n}\n
Source code in src/tolvera/state.py
@ti.data_oriented\nclass State:\n \"\"\"State class for T\u00f6lvera.\n\n This class takes a name, dictionary of state attributes, and a shape, and\n creates a Taichi struct field and a corresponding dictionary of NumPy arrays \n (NpNdarrayDict) for a state.\n\n The Taichi struct field can be used in Taichi scope, and the NpNdarrayDict\n can be used in Python scope, and the two are kept in sync by the from_nddict()\n and to_nddict() methods.\n\n The State class also handles OSC accessors for the state, which use the\n NpNdarrayDict to get and set data. A T\u00f6lvera instance is therefore required\n to initialise a State instance.\n\n State attributes are defined as a dictionary of attribute names and tuples of\n (Taichi type, min value, max value). The domain of the attribute is used when\n randomising the data in the state, and by OSCMap endpoints and client patches.\n\n The state is n-dimensional based on the shape argument, and the NpNdarrayDict\n provides methods for accessing the data in the state in n-dimensional slices.\n\n Example:\n ```py\n tv.s.flock_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn, # particle count\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n ```\n \"\"\"\n def __init__(\n self,\n tolvera,\n name: str,\n state: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int] = None,\n osc: str | tuple = None, # ('get', 'set', 'stream')\n randomise: bool = True,\n methods: dict[str, Any] = None,\n ):\n \"\"\"Initialise a state for T\u00f6lvera.\n\n Args:\n tolvera (Tolvera): Tolvera instance to which this state belongs.\n name (str): Name of this state.\n state (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int], optional): Shape of the state. Defaults to 1.\n methods (dict[str, Any], optional): Flag for OSC via iipyper. Defaults to False.\n \"\"\"\n self.tv = tolvera\n assert name is not None, \"State must have a name.\"\n self.name = name\n shape = 1 if shape is None else shape\n self.setup_data(state, shape, randomise, methods)\n self.setup_osc(osc)\n\n def setup_data(\n self,\n dict: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int],\n randomise: bool = True,\n methods: dict[str, Any] = None,\n ):\n \"\"\"Setup data structures and data for this state.\n\n Args:\n dict (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int]): Shape of the state.\n randomise (bool, optional): Flag to randomise the data on creation. Defaults to True.\n methods (dict[str, Any], optional): Dict of Taichi field struct methods. Defaults to None.\n \"\"\"\n self.create_struct_field(dict, shape, methods)\n self.create_npndarray_dict()\n if randomise:\n self.randomise()\n\n def create_struct_field(\n self,\n dict: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int],\n methods: dict[str, Any] = None,\n ):\n \"\"\"Create a Taichi struct field for this state.\n\n Args:\n dict (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int]): Shape of the state.\n methods (dict[str, Any], optional): Dict of Taichi field struct methods. Defaults to None.\n \"\"\"\n self.dict = dict\n self.shape = (shape,) if isinstance(shape, int) else shape\n if methods is None:\n self.struct = ti.types.struct(**{k: v[0] for k, v in self.dict.items()})\n else:\n self.methods = methods if methods is not None else {}\n self.struct = ti.types.struct(\n **{k: v[0] for k, v in self.dict.items()}, methods=self.methods\n )\n self.field = self.struct.field(shape=self.shape)\n\n def create_npndarray_dict(self):\n \"\"\"Create a NpNdarrayDict for this state.\n\n Raises:\n NotImplementedError: If no Numpy type is found for a Taichi type.\n \"\"\"\n nddict = {}\n for k, v in self.dict.items():\n titype, min_val, max_val = v\n nptype = TiNpTypeMap.get(titype)\n if nptype is None:\n raise NotImplementedError(f\"no nptype for {titype}\")\n nddict[k] = (nptype, min_val, max_val)\n self.nddict = NpNdarrayDict(nddict, self.shape)\n self.size = self.nddict.size\n\n def randomise(self):\n \"\"\"Randomise the data in this state.\"\"\"\n self.nddict.randomise()\n self.from_nddict()\n\n def setup_osc(self, osc: tuple|str = None):\n \"\"\"Setup OSC for this state.\n\n Args:\n osc (tuple | str, optional): (\"get\", \"set\", \"stream\"). Defaults to None.\n \"\"\"\n self.osc = osc is not None\n if not self.osc: return\n if isinstance(osc, str): osc = (osc,)\n self.osc_set = \"set\" in osc if self.osc else False\n self.osc_get = \"get\" in osc if self.osc else False\n self.osc_stream = \"stream\" in osc if self.osc else False\n self.setter_name = f\"{self.tv.name_clean}_set_{self.name}\"\n self.getter_name = f\"{self.tv.name_clean}_get_{self.name}\"\n self.stream_name = f\"{self.tv.name_clean}_stream_{self.name}\"\n if self.tv.osc is not False and self.osc:\n self.osc = self.tv.osc\n if self.osc_set: self.add_osc_setters()\n # if self.osc_get: self.add_osc_getters()\n # if self.osc_stream: self.add_osc_streams()\n\n def add_osc_setters(self):\n name = self.setter_name\n self.osc.map.receive_args_inline(name + \"_randomise\", self.randomise)\n\n def add_osc_getters(self):\n name = self.getter_name\n for k, v in self.dict.items():\n ranges = (int(v[0]), int(v[0]), int(v[1]))\n kwargs = {\"i\": ranges, \"j\": ranges, \"attr\": (k, k, k)}\n self.osc.map.receive_args_inline(f\"{name}\", self.osc_getter, **kwargs)\n\n # def osc_getter(self, i: int, j: int, attribute: str):\n # ret = self.get((i, j), attribute)\n # if ret is not None:\n # route = self.osc.map.pascal_to_path(self.getter_name) # +'/'+attribute\n # self.osc.host.return_to_sender_by_name(\n # (route, attribute, ret), self.osc.client_name\n # )\n # return ret\n\n # def add_osc_streams(self):\n # # add send in broadcast mode\n # raise NotImplementedError(\"add_osc_streams not implemented\")\n\n def serialize(self) -> str:\n return ti_serialize(self.field)\n\n def deserialize(self, json_str: str):\n ti_deserialize(self.field, json_str)\n\n def save(self, path: str):\n # TODO: path validation, save to path, etc.\n json_str = self.serialize()\n raise NotImplementedError(\"save not implemented\")\n\n def load(self, path: str):\n # TODO: path validation, file ext., etc.\n # TODO: data validation (pydantic?)\n json_str = jsons.load(path)\n self.deserialize(json_str)\n raise NotImplementedError(\"load not implemented\")\n\n def from_nddict(self):\n \"\"\"Copy data from NpNdarrayDict to Taichi field.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n data = self.nddict.get_data()\n self.field.from_numpy(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.from_nddict] {e}\") from e\n\n def to_nddict(self):\n \"\"\"Copy data from Taichi field to NpNdarrayDict.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n data = self.field.to_numpy()\n self.nddict.set_data(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.to_nddict] {e}\") from e\n\n def set_from_nddict(self, data: dict):\n \"\"\"Copy data from NumPy array dict to Taichi field.\n\n Args:\n data (dict): NumPy array dict to copy.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n self.field.from_numpy(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.from_numpy] {e}\") from e\n\n \"\"\"\n npndarray_dict wrappers\n \"\"\"\n\n def from_vec(self, vec: list):\n \"\"\"Wrapper for NpNdarrayDict.from_vec().\"\"\"\n self.to_nddict()\n self.nddict.from_vec(vec)\n self.from_nddict()\n\n def to_vec(self) -> list:\n \"\"\"Wrapper for NpNdarrayDict.to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.to_vec()\n\n def attr_from_vec(self, attr: str, vec: list):\n \"\"\"Wrapper for NpNdarrayDict.attr_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.attr_from_vec(attr, vec)\n self.from_nddict()\n\n def attr_to_vec(self, attr: str) -> list:\n \"\"\"Wrapper for NpNdarrayDict.attr_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.attr_to_vec(attr)\n\n def slice_from_vec(self, slice_args: list, slice_vec: list):\n \"\"\"Wrapper for NpNdarrayDict.slice_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.slice_from_vec(slice_args, slice_vec)\n self.from_nddict()\n\n def slice_to_vec(self, slice_args: list) -> list:\n \"\"\"Wrapper for NpNdarrayDict.slice_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.slice_to_vec(slice_args)\n\n def attr_slice_from_vec(self, attr: str, slice_args: list, slice_vec: list):\n \"\"\"Wrapper for NpNdarrayDict.attr_slice_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.attr_slice_from_vec(attr, slice_args, slice_vec)\n self.from_nddict()\n\n def attr_slice_to_vec(self, attr: str, slice_args: list) -> list:\n \"\"\"Wrapper for NpNdarrayDict.attr_slice_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.attr_slice_to_vec(attr, slice_args)\n\n def attr_size(self, attr: str) -> int:\n \"\"\"Return the size of the attribute.\"\"\"\n return self.nddict.data[attr].size\n\n \"\"\"\n misc\n \"\"\"\n\n def fill(self, value: ti.f32):\n \"\"\"Fill the Taichi field with a value.\"\"\"\n self.field.fill(value)\n\n @ti.func\n def __getitem__(self, index: ti.i32):\n \"\"\"Return the Taichi field attribute.\n\n Args:\n index (ti.i32): Attribute index.\n \"\"\"\n return self.field[index]\n\n def __call__(self, *args: Any, **kwds: Any) -> Any:\n \"\"\"Return the Taichi field.\"\"\"\n return self.field\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.__call__","title":"__call__(*args, **kwds)
","text":"Return the Taichi field.
Source code in src/tolvera/state.py
def __call__(self, *args: Any, **kwds: Any) -> Any:\n \"\"\"Return the Taichi field.\"\"\"\n return self.field\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.__getitem__","title":"__getitem__(index)
","text":"Return the Taichi field attribute.
Parameters:
Name Type Description Default index
i32
Attribute index.
required Source code in src/tolvera/state.py
@ti.func\ndef __getitem__(self, index: ti.i32):\n \"\"\"Return the Taichi field attribute.\n\n Args:\n index (ti.i32): Attribute index.\n \"\"\"\n return self.field[index]\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.__init__","title":"__init__(tolvera, name, state, shape=None, osc=None, randomise=True, methods=None)
","text":"Initialise a state for T\u00f6lvera.
Parameters:
Name Type Description Default tolvera
Tolvera
Tolvera instance to which this state belongs.
required name
str
Name of this state.
required state
dict[str, tuple[DataType, Any, Any]]
Dict of state attributes.
required shape
int | tuple[int]
Shape of the state. Defaults to 1.
None
methods
dict[str, Any]
Flag for OSC via iipyper. Defaults to False.
None
Source code in src/tolvera/state.py
def __init__(\n self,\n tolvera,\n name: str,\n state: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int] = None,\n osc: str | tuple = None, # ('get', 'set', 'stream')\n randomise: bool = True,\n methods: dict[str, Any] = None,\n):\n \"\"\"Initialise a state for T\u00f6lvera.\n\n Args:\n tolvera (Tolvera): Tolvera instance to which this state belongs.\n name (str): Name of this state.\n state (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int], optional): Shape of the state. Defaults to 1.\n methods (dict[str, Any], optional): Flag for OSC via iipyper. Defaults to False.\n \"\"\"\n self.tv = tolvera\n assert name is not None, \"State must have a name.\"\n self.name = name\n shape = 1 if shape is None else shape\n self.setup_data(state, shape, randomise, methods)\n self.setup_osc(osc)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_from_vec","title":"attr_from_vec(attr, vec)
","text":"Wrapper for NpNdarrayDict.attr_from_vec().
Source code in src/tolvera/state.py
def attr_from_vec(self, attr: str, vec: list):\n \"\"\"Wrapper for NpNdarrayDict.attr_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.attr_from_vec(attr, vec)\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_size","title":"attr_size(attr)
","text":"Return the size of the attribute.
Source code in src/tolvera/state.py
def attr_size(self, attr: str) -> int:\n \"\"\"Return the size of the attribute.\"\"\"\n return self.nddict.data[attr].size\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_slice_from_vec","title":"attr_slice_from_vec(attr, slice_args, slice_vec)
","text":"Wrapper for NpNdarrayDict.attr_slice_from_vec().
Source code in src/tolvera/state.py
def attr_slice_from_vec(self, attr: str, slice_args: list, slice_vec: list):\n \"\"\"Wrapper for NpNdarrayDict.attr_slice_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.attr_slice_from_vec(attr, slice_args, slice_vec)\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_slice_to_vec","title":"attr_slice_to_vec(attr, slice_args)
","text":"Wrapper for NpNdarrayDict.attr_slice_to_vec().
Source code in src/tolvera/state.py
def attr_slice_to_vec(self, attr: str, slice_args: list) -> list:\n \"\"\"Wrapper for NpNdarrayDict.attr_slice_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.attr_slice_to_vec(attr, slice_args)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.attr_to_vec","title":"attr_to_vec(attr)
","text":"Wrapper for NpNdarrayDict.attr_to_vec().
Source code in src/tolvera/state.py
def attr_to_vec(self, attr: str) -> list:\n \"\"\"Wrapper for NpNdarrayDict.attr_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.attr_to_vec(attr)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.create_npndarray_dict","title":"create_npndarray_dict()
","text":"Create a NpNdarrayDict for this state.
Raises:
Type Description NotImplementedError
If no Numpy type is found for a Taichi type.
Source code in src/tolvera/state.py
def create_npndarray_dict(self):\n \"\"\"Create a NpNdarrayDict for this state.\n\n Raises:\n NotImplementedError: If no Numpy type is found for a Taichi type.\n \"\"\"\n nddict = {}\n for k, v in self.dict.items():\n titype, min_val, max_val = v\n nptype = TiNpTypeMap.get(titype)\n if nptype is None:\n raise NotImplementedError(f\"no nptype for {titype}\")\n nddict[k] = (nptype, min_val, max_val)\n self.nddict = NpNdarrayDict(nddict, self.shape)\n self.size = self.nddict.size\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.create_struct_field","title":"create_struct_field(dict, shape, methods=None)
","text":"Create a Taichi struct field for this state.
Parameters:
Name Type Description Default dict
dict[str, tuple[DataType, Any, Any]]
Dict of state attributes.
required shape
int | tuple[int]
Shape of the state.
required methods
dict[str, Any]
Dict of Taichi field struct methods. Defaults to None.
None
Source code in src/tolvera/state.py
def create_struct_field(\n self,\n dict: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int],\n methods: dict[str, Any] = None,\n):\n \"\"\"Create a Taichi struct field for this state.\n\n Args:\n dict (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int]): Shape of the state.\n methods (dict[str, Any], optional): Dict of Taichi field struct methods. Defaults to None.\n \"\"\"\n self.dict = dict\n self.shape = (shape,) if isinstance(shape, int) else shape\n if methods is None:\n self.struct = ti.types.struct(**{k: v[0] for k, v in self.dict.items()})\n else:\n self.methods = methods if methods is not None else {}\n self.struct = ti.types.struct(\n **{k: v[0] for k, v in self.dict.items()}, methods=self.methods\n )\n self.field = self.struct.field(shape=self.shape)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.fill","title":"fill(value)
","text":"Fill the Taichi field with a value.
Source code in src/tolvera/state.py
def fill(self, value: ti.f32):\n \"\"\"Fill the Taichi field with a value.\"\"\"\n self.field.fill(value)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.from_nddict","title":"from_nddict()
","text":"Copy data from NpNdarrayDict to Taichi field.
Raises:
Type Description Exception
If data cannot be copied.
Source code in src/tolvera/state.py
def from_nddict(self):\n \"\"\"Copy data from NpNdarrayDict to Taichi field.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n data = self.nddict.get_data()\n self.field.from_numpy(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.from_nddict] {e}\") from e\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.from_vec","title":"from_vec(vec)
","text":"Wrapper for NpNdarrayDict.from_vec().
Source code in src/tolvera/state.py
def from_vec(self, vec: list):\n \"\"\"Wrapper for NpNdarrayDict.from_vec().\"\"\"\n self.to_nddict()\n self.nddict.from_vec(vec)\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.randomise","title":"randomise()
","text":"Randomise the data in this state.
Source code in src/tolvera/state.py
def randomise(self):\n \"\"\"Randomise the data in this state.\"\"\"\n self.nddict.randomise()\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.set_from_nddict","title":"set_from_nddict(data)
","text":"Copy data from NumPy array dict to Taichi field.
Parameters:
Name Type Description Default data
dict
NumPy array dict to copy.
required Raises:
Type Description Exception
If data cannot be copied.
Source code in src/tolvera/state.py
def set_from_nddict(self, data: dict):\n \"\"\"Copy data from NumPy array dict to Taichi field.\n\n Args:\n data (dict): NumPy array dict to copy.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n self.field.from_numpy(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.from_numpy] {e}\") from e\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.setup_data","title":"setup_data(dict, shape, randomise=True, methods=None)
","text":"Setup data structures and data for this state.
Parameters:
Name Type Description Default dict
dict[str, tuple[DataType, Any, Any]]
Dict of state attributes.
required shape
int | tuple[int]
Shape of the state.
required randomise
bool
Flag to randomise the data on creation. Defaults to True.
True
methods
dict[str, Any]
Dict of Taichi field struct methods. Defaults to None.
None
Source code in src/tolvera/state.py
def setup_data(\n self,\n dict: dict[str, tuple[DataType, Any, Any]],\n shape: int | tuple[int],\n randomise: bool = True,\n methods: dict[str, Any] = None,\n):\n \"\"\"Setup data structures and data for this state.\n\n Args:\n dict (dict[str, tuple[DataType, Any, Any]]): Dict of state attributes.\n shape (int | tuple[int]): Shape of the state.\n randomise (bool, optional): Flag to randomise the data on creation. Defaults to True.\n methods (dict[str, Any], optional): Dict of Taichi field struct methods. Defaults to None.\n \"\"\"\n self.create_struct_field(dict, shape, methods)\n self.create_npndarray_dict()\n if randomise:\n self.randomise()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.setup_osc","title":"setup_osc(osc=None)
","text":"Setup OSC for this state.
Parameters:
Name Type Description Default osc
tuple | str
(\"get\", \"set\", \"stream\"). Defaults to None.
None
Source code in src/tolvera/state.py
def setup_osc(self, osc: tuple|str = None):\n \"\"\"Setup OSC for this state.\n\n Args:\n osc (tuple | str, optional): (\"get\", \"set\", \"stream\"). Defaults to None.\n \"\"\"\n self.osc = osc is not None\n if not self.osc: return\n if isinstance(osc, str): osc = (osc,)\n self.osc_set = \"set\" in osc if self.osc else False\n self.osc_get = \"get\" in osc if self.osc else False\n self.osc_stream = \"stream\" in osc if self.osc else False\n self.setter_name = f\"{self.tv.name_clean}_set_{self.name}\"\n self.getter_name = f\"{self.tv.name_clean}_get_{self.name}\"\n self.stream_name = f\"{self.tv.name_clean}_stream_{self.name}\"\n if self.tv.osc is not False and self.osc:\n self.osc = self.tv.osc\n if self.osc_set: self.add_osc_setters()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.slice_from_vec","title":"slice_from_vec(slice_args, slice_vec)
","text":"Wrapper for NpNdarrayDict.slice_from_vec().
Source code in src/tolvera/state.py
def slice_from_vec(self, slice_args: list, slice_vec: list):\n \"\"\"Wrapper for NpNdarrayDict.slice_from_vec().\"\"\"\n self.to_nddict()\n self.nddict.slice_from_vec(slice_args, slice_vec)\n self.from_nddict()\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.slice_to_vec","title":"slice_to_vec(slice_args)
","text":"Wrapper for NpNdarrayDict.slice_to_vec().
Source code in src/tolvera/state.py
def slice_to_vec(self, slice_args: list) -> list:\n \"\"\"Wrapper for NpNdarrayDict.slice_to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.slice_to_vec(slice_args)\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.to_nddict","title":"to_nddict()
","text":"Copy data from Taichi field to NpNdarrayDict.
Raises:
Type Description Exception
If data cannot be copied.
Source code in src/tolvera/state.py
def to_nddict(self):\n \"\"\"Copy data from Taichi field to NpNdarrayDict.\n\n Raises:\n Exception: If data cannot be copied.\n \"\"\"\n try:\n data = self.field.to_numpy()\n self.nddict.set_data(data)\n except Exception as e:\n raise Exception(f\"[tolvera.state.to_nddict] {e}\") from e\n
"},{"location":"reference/tolvera/state/#tolvera.state.State.to_vec","title":"to_vec()
","text":"Wrapper for NpNdarrayDict.to_vec().
Source code in src/tolvera/state.py
def to_vec(self) -> list:\n \"\"\"Wrapper for NpNdarrayDict.to_vec().\"\"\"\n self.to_nddict()\n return self.nddict.to_vec()\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict","title":"StateDict
","text":" Bases: dotdict
StateDict class for T\u00f6lvera.
This class is a dictionary of State instances, and is accessible via the 's' attribute of a T\u00f6lvera instance.
States can be created by assigning a dictionary or a tuple to a StateDict key. and can be used in Taichi scope and Python scope respectively.
Example tv = Tolvera(**kwargs)
tv.s.mystate = { \"state\": { \"id\": (ti.i32, 0, tv.pn - 1), \"pos\": (ti.math.vec2, -1.0, 1.0), \"vel\": (ti.math.vec2, -1.0, 1.0), }, \"shape\": (tv.pn, 1), \"osc\": \"get\", \"randomise\": True }
tv.s.mystate.field.pos[0] = 0.5
Source code in src/tolvera/state.py
class StateDict(dotdict):\n \"\"\"StateDict class for T\u00f6lvera.\n\n This class is a dictionary of State instances, and is accessible via the 's'\n attribute of a T\u00f6lvera instance.\n\n States can be created by assigning a dictionary or a tuple to a StateDict key.\n and can be used in Taichi scope and Python scope respectively.\n\n Example:\n tv = Tolvera(**kwargs)\n\n tv.s.mystate = {\n \"state\": {\n \"id\": (ti.i32, 0, tv.pn - 1),\n \"pos\": (ti.math.vec2, -1.0, 1.0),\n \"vel\": (ti.math.vec2, -1.0, 1.0),\n }, \n \"shape\": (tv.pn, 1), \n \"osc\": \"get\", \n \"randomise\": True\n }\n\n tv.s.mystate.field.pos[0] = 0.5\n \"\"\"\n def __init__(self, tolvera) -> None:\n \"\"\"Initialise a StateDict for T\u00f6lvera.\n\n Args:\n tolvera (Tolvera): Tolvera instance to which this StateDict belongs.\n \"\"\"\n self.tv = tolvera\n self.size = 0\n\n def set(self, name, kwargs: Any) -> None:\n \"\"\"Set a state in the StateDict.\n\n Args:\n name (str): Name of the state.\n kwargs (Any): State attributes.\n\n Raises:\n ValueError: If the state is already in the StateDict.\n Exception: If the state cannot be added.\n \"\"\"\n if name in self and name != \"size\":\n raise ValueError(f\"[tolvera.state.StateDict] '{name}' already in dict.\")\n try:\n self.add(name, kwargs)\n except Exception as e:\n raise type(e)(f\"[tolvera.state.StateDict] {e}\") from e\n\n def add(self, name, kwargs: Any):\n \"\"\"Add a state to the StateDict.\n\n Args:\n name (str): Name of the state.\n kwargs (Any): State attributes.\n\n Raises:\n TypeError: If kwargs is not a dict or tuple.\n \"\"\"\n if name == \"tv\" and type(kwargs) is not dict and type(kwargs) is not tuple:\n self[name] = kwargs\n elif name == \"size\" and type(kwargs) is int:\n self[name] = kwargs\n elif type(kwargs) is dict:\n self[name] = State(self.tv, name=name, **kwargs)\n self.size += self[name].size\n elif type(kwargs) is tuple:\n self[name] = State(self.tv, name, *kwargs)\n self.size += self[name].size\n else:\n raise TypeError(\n f\"[tolvera.state.StateDict] set() requires dict|tuple, not {type(kwargs)}\"\n )\n\n def from_vec(self, states: list[str], vector: list[float]):\n \"\"\"Copy data from a vector to states in the StateDict.\n\n Args:\n states (list[str]): List of state names.\n vector (list[float]): Vector of data to copy.\n\n Raises:\n Exception: If the vector is not the correct size.\n \"\"\"\n sizes_sum = self.get_size(states)\n assert sizes_sum == len(\n vector\n ), f\"sizes_sum={sizes_sum} != len(vector)={len(vector)}\"\n vec_start = 0\n for state in states:\n s = self.tv.s[state]\n vec = vector[vec_start : vec_start + s.size]\n s.from_vec(vec)\n vec_start += s.size\n\n def get_size(self, states: str | list[str]) -> int:\n \"\"\"Return the size of the states in the StateDict.\n\n Args:\n states (str | list[str]): State name or list of state names.\n\n Returns:\n int: Size of the states.\n \"\"\"\n if isinstance(states, str):\n states = [states]\n return sum([self.tv.s[state].size for state in states])\n\n def __setattr__(self, __name: str, __value: Any) -> None:\n \"\"\"Set a state in the StateDict.\n\n Args:\n __name (str): Name of the state.\n __value (Any): State attributes.\n \"\"\"\n self.set(__name, __value)\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.__init__","title":"__init__(tolvera)
","text":"Initialise a StateDict for T\u00f6lvera.
Parameters:
Name Type Description Default tolvera
Tolvera
Tolvera instance to which this StateDict belongs.
required Source code in src/tolvera/state.py
def __init__(self, tolvera) -> None:\n \"\"\"Initialise a StateDict for T\u00f6lvera.\n\n Args:\n tolvera (Tolvera): Tolvera instance to which this StateDict belongs.\n \"\"\"\n self.tv = tolvera\n self.size = 0\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.__setattr__","title":"__setattr__(__name, __value)
","text":"Set a state in the StateDict.
Parameters:
Name Type Description Default __name
str
Name of the state.
required __value
Any
State attributes.
required Source code in src/tolvera/state.py
def __setattr__(self, __name: str, __value: Any) -> None:\n \"\"\"Set a state in the StateDict.\n\n Args:\n __name (str): Name of the state.\n __value (Any): State attributes.\n \"\"\"\n self.set(__name, __value)\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.add","title":"add(name, kwargs)
","text":"Add a state to the StateDict.
Parameters:
Name Type Description Default name
str
Name of the state.
required kwargs
Any
State attributes.
required Raises:
Type Description TypeError
If kwargs is not a dict or tuple.
Source code in src/tolvera/state.py
def add(self, name, kwargs: Any):\n \"\"\"Add a state to the StateDict.\n\n Args:\n name (str): Name of the state.\n kwargs (Any): State attributes.\n\n Raises:\n TypeError: If kwargs is not a dict or tuple.\n \"\"\"\n if name == \"tv\" and type(kwargs) is not dict and type(kwargs) is not tuple:\n self[name] = kwargs\n elif name == \"size\" and type(kwargs) is int:\n self[name] = kwargs\n elif type(kwargs) is dict:\n self[name] = State(self.tv, name=name, **kwargs)\n self.size += self[name].size\n elif type(kwargs) is tuple:\n self[name] = State(self.tv, name, *kwargs)\n self.size += self[name].size\n else:\n raise TypeError(\n f\"[tolvera.state.StateDict] set() requires dict|tuple, not {type(kwargs)}\"\n )\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.from_vec","title":"from_vec(states, vector)
","text":"Copy data from a vector to states in the StateDict.
Parameters:
Name Type Description Default states
list[str]
List of state names.
required vector
list[float]
Vector of data to copy.
required Raises:
Type Description Exception
If the vector is not the correct size.
Source code in src/tolvera/state.py
def from_vec(self, states: list[str], vector: list[float]):\n \"\"\"Copy data from a vector to states in the StateDict.\n\n Args:\n states (list[str]): List of state names.\n vector (list[float]): Vector of data to copy.\n\n Raises:\n Exception: If the vector is not the correct size.\n \"\"\"\n sizes_sum = self.get_size(states)\n assert sizes_sum == len(\n vector\n ), f\"sizes_sum={sizes_sum} != len(vector)={len(vector)}\"\n vec_start = 0\n for state in states:\n s = self.tv.s[state]\n vec = vector[vec_start : vec_start + s.size]\n s.from_vec(vec)\n vec_start += s.size\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.get_size","title":"get_size(states)
","text":"Return the size of the states in the StateDict.
Parameters:
Name Type Description Default states
str | list[str]
State name or list of state names.
required Returns:
Name Type Description int
int
Size of the states.
Source code in src/tolvera/state.py
def get_size(self, states: str | list[str]) -> int:\n \"\"\"Return the size of the states in the StateDict.\n\n Args:\n states (str | list[str]): State name or list of state names.\n\n Returns:\n int: Size of the states.\n \"\"\"\n if isinstance(states, str):\n states = [states]\n return sum([self.tv.s[state].size for state in states])\n
"},{"location":"reference/tolvera/state/#tolvera.state.StateDict.set","title":"set(name, kwargs)
","text":"Set a state in the StateDict.
Parameters:
Name Type Description Default name
str
Name of the state.
required kwargs
Any
State attributes.
required Raises:
Type Description ValueError
If the state is already in the StateDict.
Exception
If the state cannot be added.
Source code in src/tolvera/state.py
def set(self, name, kwargs: Any) -> None:\n \"\"\"Set a state in the StateDict.\n\n Args:\n name (str): Name of the state.\n kwargs (Any): State attributes.\n\n Raises:\n ValueError: If the state is already in the StateDict.\n Exception: If the state cannot be added.\n \"\"\"\n if name in self and name != \"size\":\n raise ValueError(f\"[tolvera.state.StateDict] '{name}' already in dict.\")\n try:\n self.add(name, kwargs)\n except Exception as e:\n raise type(e)(f\"[tolvera.state.StateDict] {e}\") from e\n
"},{"location":"reference/tolvera/taichi_/","title":"Taichi","text":"Taichi class for initialising Taichi and UI.
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi","title":"Taichi
","text":"Taichi class for initialising Taichi and UI.
This class provides a show method for showing the Taichi canvas. It is used by the TolveraContext class to display a window.
Source code in src/tolvera/taichi_.py
class Taichi:\n \"\"\"Taichi class for initialising Taichi and UI.\n\n This class provides a show method for showing the Taichi canvas.\n It is used by the TolveraContext class to display a window.\"\"\"\n def __init__(self, context, **kwargs) -> None:\n \"\"\"Initialise Taichi\n\n Args:\n context (TolveraContext): global TolveraContext instance.\n **kwargs: Keyword arguments:\n gpu (str): GPU architecture to run on. Defaults to \"vulkan\".\n cpu (bool): Run on CPU. Defaults to False.\n fps (int): FPS limit. Defaults to 120.\n seed (int): Random seed. Defaults to time.time().\n headless (bool): Run headless. Defaults to False.\n name (str): Window name. Defaults to \"T\u00f6lvera\".\n \"\"\"\n self.ctx = context\n self.kwargs = kwargs\n self.gpu = kwargs.get(\"gpu\", \"vulkan\")\n self.cpu = kwargs.get(\"cpu\", None)\n self.fps = kwargs.get(\"fps\", 120)\n self.seed = kwargs.get(\"seed\", int(time.time()))\n self.headless = kwargs.get(\"headless\", False)\n self.name = kwargs.get(\"name\", \"T\u00f6lvera\")\n self.init_ti()\n self.init_ui()\n print(f\"[T\u00f6lvera.Taichi] Taichi initialised with: {vars(self)}\")\n\n def init_ti(self):\n \"\"\"Initialise Taichi backend on selected architecture.\"\"\"\n if self.cpu:\n ti.init(arch=ti.cpu, random_seed=self.seed)\n self.gpu = None\n print(\"[T\u00f6lvera.Taichi] Running on CPU\")\n else:\n if self.gpu == \"vulkan\":\n ti.init(arch=ti.vulkan, random_seed=self.seed)\n elif self.gpu == \"metal\":\n ti.init(arch=ti.metal, random_seed=self.seed)\n elif self.gpu == \"cuda\":\n ti.init(arch=ti.cuda, random_seed=self.seed)\n else:\n print(f\"[T\u00f6lvera.Taichi] Invalid GPU: {self.gpu}\")\n return False\n print(f\"[T\u00f6lvera.Taichi] Running on {self.gpu}\")\n\n def init_ui(self):\n \"\"\"Initialise Taichi UI window and canvas.\"\"\"\n self.window = ti.ui.Window(\n self.name,\n (self.ctx.x, self.ctx.y),\n fps_limit=self.fps,\n show_window=not self.headless,\n )\n self.canvas = self.window.get_canvas()\n\n def show(self, px):\n \"\"\"Show Taichi canvas and show window.\"\"\"\n self.canvas.set_image(px.px.rgba)\n if not self.headless:\n self.window.show()\n\n def __call__(self, *args: Any, **kwds: Any) -> Any:\n \"\"\"Call Taichi window show.\"\"\"\n self.show(*args, **kwds)\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.__call__","title":"__call__(*args, **kwds)
","text":"Call Taichi window show.
Source code in src/tolvera/taichi_.py
def __call__(self, *args: Any, **kwds: Any) -> Any:\n \"\"\"Call Taichi window show.\"\"\"\n self.show(*args, **kwds)\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.__init__","title":"__init__(context, **kwargs)
","text":"Initialise Taichi
Parameters:
Name Type Description Default context
TolveraContext
global TolveraContext instance.
required **kwargs
Keyword arguments: gpu (str): GPU architecture to run on. Defaults to \"vulkan\". cpu (bool): Run on CPU. Defaults to False. fps (int): FPS limit. Defaults to 120. seed (int): Random seed. Defaults to time.time(). headless (bool): Run headless. Defaults to False. name (str): Window name. Defaults to \"T\u00f6lvera\".
{}
Source code in src/tolvera/taichi_.py
def __init__(self, context, **kwargs) -> None:\n \"\"\"Initialise Taichi\n\n Args:\n context (TolveraContext): global TolveraContext instance.\n **kwargs: Keyword arguments:\n gpu (str): GPU architecture to run on. Defaults to \"vulkan\".\n cpu (bool): Run on CPU. Defaults to False.\n fps (int): FPS limit. Defaults to 120.\n seed (int): Random seed. Defaults to time.time().\n headless (bool): Run headless. Defaults to False.\n name (str): Window name. Defaults to \"T\u00f6lvera\".\n \"\"\"\n self.ctx = context\n self.kwargs = kwargs\n self.gpu = kwargs.get(\"gpu\", \"vulkan\")\n self.cpu = kwargs.get(\"cpu\", None)\n self.fps = kwargs.get(\"fps\", 120)\n self.seed = kwargs.get(\"seed\", int(time.time()))\n self.headless = kwargs.get(\"headless\", False)\n self.name = kwargs.get(\"name\", \"T\u00f6lvera\")\n self.init_ti()\n self.init_ui()\n print(f\"[T\u00f6lvera.Taichi] Taichi initialised with: {vars(self)}\")\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.init_ti","title":"init_ti()
","text":"Initialise Taichi backend on selected architecture.
Source code in src/tolvera/taichi_.py
def init_ti(self):\n \"\"\"Initialise Taichi backend on selected architecture.\"\"\"\n if self.cpu:\n ti.init(arch=ti.cpu, random_seed=self.seed)\n self.gpu = None\n print(\"[T\u00f6lvera.Taichi] Running on CPU\")\n else:\n if self.gpu == \"vulkan\":\n ti.init(arch=ti.vulkan, random_seed=self.seed)\n elif self.gpu == \"metal\":\n ti.init(arch=ti.metal, random_seed=self.seed)\n elif self.gpu == \"cuda\":\n ti.init(arch=ti.cuda, random_seed=self.seed)\n else:\n print(f\"[T\u00f6lvera.Taichi] Invalid GPU: {self.gpu}\")\n return False\n print(f\"[T\u00f6lvera.Taichi] Running on {self.gpu}\")\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.init_ui","title":"init_ui()
","text":"Initialise Taichi UI window and canvas.
Source code in src/tolvera/taichi_.py
def init_ui(self):\n \"\"\"Initialise Taichi UI window and canvas.\"\"\"\n self.window = ti.ui.Window(\n self.name,\n (self.ctx.x, self.ctx.y),\n fps_limit=self.fps,\n show_window=not self.headless,\n )\n self.canvas = self.window.get_canvas()\n
"},{"location":"reference/tolvera/taichi_/#tolvera.taichi_.Taichi.show","title":"show(px)
","text":"Show Taichi canvas and show window.
Source code in src/tolvera/taichi_.py
def show(self, px):\n \"\"\"Show Taichi canvas and show window.\"\"\"\n self.canvas.set_image(px.px.rgba)\n if not self.headless:\n self.window.show()\n
"},{"location":"reference/tolvera/tolvera_/","title":"Tolvera","text":"Example This example demonstrates the basic usage of T\u00f6lvera. It will display a window with a black background.
from tolvera import Tolvera, run\n\ndef main(**kwargs):\n tv = Tolvera(**kwargs)\n\n @tv.render\n def _():\n return tv.px\n\nif __name__ == '__main__':\n run(main)\n
Example Here's an annotated version of the above example:
# First, we import Tolvera and run() from tolvera.\nfrom tolvera import Tolvera, run\n\n# Then, we define a main function which takes in keyword arguments \n# (kwargs) from the command line.\ndef main(**kwargs):\n # Inside the main function, we initialise a Tolvera instance \n # with the given keyword arguments.\n tv = Tolvera(**kwargs)\n\n # We use the render() decorator to render the pixels.\n # This function can be named anything. \n # It will run in a loop until the user exits the program.\n @tv.render\n def _():\n # render() must return Pixels. Often, these pixels will be \n # the pixels of the Tolvera instance, accessed with tv.px.\n return tv.px\n\n# Finally, we call run() with the main function as the argument.\nif __name__ == '__main__':\n run(main)\n
When Tolvera is run, messages will be printed to the console. These messages inform the user of the status of Tolvera, during initialisation, setup, and running.
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera","title":"Tolvera
","text":"Tolvera main class.
Attributes:
Name Type Description `name`
str
Name of T\u00f6lvera instance.
`ctx`
TolveraContext
Shared TolveraContext.
`speed`
float
Global timebase speed.
`pn`
int
Number of particles.
`sn`
int
Number of species.
`p_per_s`
int
Number of particles per species.
`substep`
int
Number of substeps per frame.
`iml`
int
Dict of IML instances via anguilla.
`cv`
int
computer vision integration via OpenCV.
`osc`
int
OSC via iipyper.
`ti`
int
Taichi (graphics backend).
Source code in src/tolvera/tolvera_.py
class Tolvera:\n \"\"\"Tolvera main class.\n\n Attributes:\n `name` (str): Name of T\u00f6lvera instance. \n `ctx` (TolveraContext): Shared TolveraContext.\n `speed` (float): Global timebase speed.\n `pn` (int): Number of particles.\n `sn` (int): Number of species.\n `p_per_s` (int): Number of particles per species.\n `substep` (int): Number of substeps per frame.\n `iml`: Dict of IML instances via anguilla.\n `cv`: computer vision integration via OpenCV.\n `osc`: OSC via iipyper.\n `ti`: Taichi (graphics backend).\n \"\"\"\n\n def __init__(self, **kwargs):\n \"\"\"\n Initialise and setup T\u00f6lvera with given keyword arguments.\n\n Args:\n name (str): Name of T\u00f6lvera instance. Defaults to \"T\u00f6lvera\".\n ctx (TolveraContext): TolveraContext to share. Defaults to None.\n see also kwargs for Tolvera.setup().\n \"\"\"\n self.kwargs = kwargs\n self.name = kwargs.get(\"name\", \"T\u00f6lvera\")\n self.name_clean = clean_name(self.name)\n if \"ctx\" not in kwargs:\n self.init_context(**kwargs)\n else:\n self.share_context(kwargs[\"ctx\"])\n self.setup(**kwargs)\n print(f\"[{self.name}] Initialisation and setup complete.\")\n\n def init_context(self, **kwargs):\n \"\"\"Initiliase T\u00f6lveraContext with given keyword arguments.\n\n Args:\n **kwargs: Keyword arguments for T\u00f6lveraContext.\n \"\"\"\n context = TolveraContext(**kwargs)\n self.share_context(context)\n\n def share_context(self, context):\n \"\"\"Share T\u00f6lveraContext with another T\u00f6lvera instance.\n\n Args:\n context: T\u00f6lveraContext to share.\n \"\"\"\n if len(context.get_names()) == 0:\n print(f\"[{self.name}] Sharing context '{context.name}'.\")\n else:\n print(\n f\"[{self.name}] Sharing context '{context.name}' with {context.get_names()}.\"\n )\n self.ctx = context\n self.x = context.x\n self.y = context.y\n self.ti = context.ti\n self.show = context.show\n self.canvas = context.canvas\n self.osc = context.osc\n self.s = context.s\n self.iml = context.iml\n self.render = context.render\n self.cleanup = context.cleanup\n self.cv = context.cv\n self.hands = context.hands\n\n def setup(self, **kwargs):\n \"\"\"\n Setup T\u00f6lvera with given keyword arguments.\n This can be called multiple throughout the lifetime of a T\u00f6lvera instance.\n\n Args:\n **kwargs: Keyword arguments for setup.\n speed (float): Global timebase speed. Defaults to 1.\n particles (int): Number of particles. Defaults to 1024.\n species (int): Number of species. Defaults to 4.\n substep (int): Number of substeps per frame. Defaults to 1.\n See also kwargs for Pixels, Species, Particles, and Vera.\n \"\"\"\n self._speed = kwargs.get(\"speed\", 1) # global timebase\n self.particles = kwargs.get(\"particles\", 1024)\n self.species = kwargs.get(\"species\", 4)\n if self.particles < self.species:\n self.species = self.particles\n self.pn = self.particles\n self.sn = self.species\n self.p_per_s = self.particles // self.species\n self.substep = kwargs.get(\"substep\", 1)\n self.px = Pixels(self, **kwargs)\n self._species = Species(self, **kwargs)\n self.p = Particles(self, **kwargs)\n self.speed(self._speed)\n self.v = Vera(self, **kwargs)\n if self.osc is not False:\n self.add_to_osc_map()\n if self.cv is not False:\n self.hands.px = self.px\n self.ctx.add(self)\n print(f\"[{self.name}] Setup complete.\")\n\n def randomise(self):\n \"\"\"\n Randomise particles, species, and Vera.\n \"\"\"\n self.p.randomise()\n self.s.species.randomise()\n self.v.randomise()\n\n def reset(self, **kwargs):\n \"\"\"\n Reset T\u00f6lvera with given keyword arguments.\n This will call setup() with given keyword arguments, but not init().\n\n Args:\n **kwargs: Keyword arguments for reset.\n \"\"\"\n print(f\"[{self.name}] Resetting self with kwargs={kwargs}...\")\n if kwargs is not None:\n self.kwargs = kwargs\n self.setup()\n\n def speed(self, speed: float = None):\n \"\"\"Set or get global timebase speed.\"\"\"\n if speed is not None:\n self._speed = speed\n self.p.speed(speed)\n return self._speed\n\n def add_to_osc_map(self):\n \"\"\"\n Add top-level T\u00f6lvera functions to OSCMap.\n \"\"\"\n setter_name = f\"{self.name_clean}_set\"\n getter_name = f\"{self.name_clean}_get\"\n self.osc.map.receive_args_inline(setter_name + \"_randomise\", self.randomise)\n # self.osc.map.receive_args_inline(setter_name+'_reset', self.reset) # TODO: kwargs?\n self.osc.map.receive_args_inline(\n setter_name + \"_particles_randomise\", self.p._randomise\n ) # TODO: move inside Particles\n\n @self.osc.map.receive_args(speed=(1, 0, 100), count=1)\n def tolvera_set_speed(speed: float):\n \"\"\"Set global timebase speed.\"\"\"\n self.speed(speed)\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.__init__","title":"__init__(**kwargs)
","text":"Initialise and setup T\u00f6lvera with given keyword arguments.
Parameters:
Name Type Description Default name
str
Name of T\u00f6lvera instance. Defaults to \"T\u00f6lvera\".
required ctx
TolveraContext
TolveraContext to share. Defaults to None.
required Source code in src/tolvera/tolvera_.py
def __init__(self, **kwargs):\n \"\"\"\n Initialise and setup T\u00f6lvera with given keyword arguments.\n\n Args:\n name (str): Name of T\u00f6lvera instance. Defaults to \"T\u00f6lvera\".\n ctx (TolveraContext): TolveraContext to share. Defaults to None.\n see also kwargs for Tolvera.setup().\n \"\"\"\n self.kwargs = kwargs\n self.name = kwargs.get(\"name\", \"T\u00f6lvera\")\n self.name_clean = clean_name(self.name)\n if \"ctx\" not in kwargs:\n self.init_context(**kwargs)\n else:\n self.share_context(kwargs[\"ctx\"])\n self.setup(**kwargs)\n print(f\"[{self.name}] Initialisation and setup complete.\")\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.add_to_osc_map","title":"add_to_osc_map()
","text":"Add top-level T\u00f6lvera functions to OSCMap.
Source code in src/tolvera/tolvera_.py
def add_to_osc_map(self):\n \"\"\"\n Add top-level T\u00f6lvera functions to OSCMap.\n \"\"\"\n setter_name = f\"{self.name_clean}_set\"\n getter_name = f\"{self.name_clean}_get\"\n self.osc.map.receive_args_inline(setter_name + \"_randomise\", self.randomise)\n # self.osc.map.receive_args_inline(setter_name+'_reset', self.reset) # TODO: kwargs?\n self.osc.map.receive_args_inline(\n setter_name + \"_particles_randomise\", self.p._randomise\n ) # TODO: move inside Particles\n\n @self.osc.map.receive_args(speed=(1, 0, 100), count=1)\n def tolvera_set_speed(speed: float):\n \"\"\"Set global timebase speed.\"\"\"\n self.speed(speed)\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.init_context","title":"init_context(**kwargs)
","text":"Initiliase T\u00f6lveraContext with given keyword arguments.
Parameters:
Name Type Description Default **kwargs
Keyword arguments for T\u00f6lveraContext.
{}
Source code in src/tolvera/tolvera_.py
def init_context(self, **kwargs):\n \"\"\"Initiliase T\u00f6lveraContext with given keyword arguments.\n\n Args:\n **kwargs: Keyword arguments for T\u00f6lveraContext.\n \"\"\"\n context = TolveraContext(**kwargs)\n self.share_context(context)\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.randomise","title":"randomise()
","text":"Randomise particles, species, and Vera.
Source code in src/tolvera/tolvera_.py
def randomise(self):\n \"\"\"\n Randomise particles, species, and Vera.\n \"\"\"\n self.p.randomise()\n self.s.species.randomise()\n self.v.randomise()\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.reset","title":"reset(**kwargs)
","text":"Reset T\u00f6lvera with given keyword arguments. This will call setup() with given keyword arguments, but not init().
Parameters:
Name Type Description Default **kwargs
Keyword arguments for reset.
{}
Source code in src/tolvera/tolvera_.py
def reset(self, **kwargs):\n \"\"\"\n Reset T\u00f6lvera with given keyword arguments.\n This will call setup() with given keyword arguments, but not init().\n\n Args:\n **kwargs: Keyword arguments for reset.\n \"\"\"\n print(f\"[{self.name}] Resetting self with kwargs={kwargs}...\")\n if kwargs is not None:\n self.kwargs = kwargs\n self.setup()\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.setup","title":"setup(**kwargs)
","text":"Setup T\u00f6lvera with given keyword arguments. This can be called multiple throughout the lifetime of a T\u00f6lvera instance.
Parameters:
Name Type Description Default **kwargs
Keyword arguments for setup. speed (float): Global timebase speed. Defaults to 1. particles (int): Number of particles. Defaults to 1024. species (int): Number of species. Defaults to 4. substep (int): Number of substeps per frame. Defaults to 1.
{}
Source code in src/tolvera/tolvera_.py
def setup(self, **kwargs):\n \"\"\"\n Setup T\u00f6lvera with given keyword arguments.\n This can be called multiple throughout the lifetime of a T\u00f6lvera instance.\n\n Args:\n **kwargs: Keyword arguments for setup.\n speed (float): Global timebase speed. Defaults to 1.\n particles (int): Number of particles. Defaults to 1024.\n species (int): Number of species. Defaults to 4.\n substep (int): Number of substeps per frame. Defaults to 1.\n See also kwargs for Pixels, Species, Particles, and Vera.\n \"\"\"\n self._speed = kwargs.get(\"speed\", 1) # global timebase\n self.particles = kwargs.get(\"particles\", 1024)\n self.species = kwargs.get(\"species\", 4)\n if self.particles < self.species:\n self.species = self.particles\n self.pn = self.particles\n self.sn = self.species\n self.p_per_s = self.particles // self.species\n self.substep = kwargs.get(\"substep\", 1)\n self.px = Pixels(self, **kwargs)\n self._species = Species(self, **kwargs)\n self.p = Particles(self, **kwargs)\n self.speed(self._speed)\n self.v = Vera(self, **kwargs)\n if self.osc is not False:\n self.add_to_osc_map()\n if self.cv is not False:\n self.hands.px = self.px\n self.ctx.add(self)\n print(f\"[{self.name}] Setup complete.\")\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.share_context","title":"share_context(context)
","text":"Share T\u00f6lveraContext with another T\u00f6lvera instance.
Parameters:
Name Type Description Default context
T\u00f6lveraContext to share.
required Source code in src/tolvera/tolvera_.py
def share_context(self, context):\n \"\"\"Share T\u00f6lveraContext with another T\u00f6lvera instance.\n\n Args:\n context: T\u00f6lveraContext to share.\n \"\"\"\n if len(context.get_names()) == 0:\n print(f\"[{self.name}] Sharing context '{context.name}'.\")\n else:\n print(\n f\"[{self.name}] Sharing context '{context.name}' with {context.get_names()}.\"\n )\n self.ctx = context\n self.x = context.x\n self.y = context.y\n self.ti = context.ti\n self.show = context.show\n self.canvas = context.canvas\n self.osc = context.osc\n self.s = context.s\n self.iml = context.iml\n self.render = context.render\n self.cleanup = context.cleanup\n self.cv = context.cv\n self.hands = context.hands\n
"},{"location":"reference/tolvera/tolvera_/#tolvera.tolvera_.Tolvera.speed","title":"speed(speed=None)
","text":"Set or get global timebase speed.
Source code in src/tolvera/tolvera_.py
def speed(self, speed: float = None):\n \"\"\"Set or get global timebase speed.\"\"\"\n if speed is not None:\n self._speed = speed\n self.p.speed(speed)\n return self._speed\n
"},{"location":"reference/tolvera/utils/","title":"Utils","text":"Utility functions for Tolvera.
"},{"location":"reference/tolvera/utils/#tolvera.utils.CONSTS","title":"CONSTS
","text":"Dict of CONSTS that can be used in Taichi scope
Source code in src/tolvera/utils.py
class CONSTS:\n \"\"\"\n Dict of CONSTS that can be used in Taichi scope\n \"\"\"\n\n def __init__(self, dict: dict[str, (DataType, Any)]):\n self.struct = ti.types.struct(**{k: v[0] for k, v in dict.items()})\n self.consts = self.struct(**{k: v[1] for k, v in dict.items()})\n\n def __getattr__(self, name):\n try:\n return self.consts[name]\n except:\n raise AttributeError(f\"CONSTS has no attribute {name}\")\n\n def __getitem__(self, name):\n try:\n return self.consts[name]\n except:\n raise AttributeError(f\"CONSTS has no attribute {name}\")\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.dotdict","title":"dotdict
","text":" Bases: dict
dot.notation access to dictionary attributes
Source code in src/tolvera/utils.py
class dotdict(dict):\n \"\"\"dot.notation access to dictionary attributes\"\"\"\n __getattr__ = dict.get\n __setattr__ = dict.__setitem__\n __delattr__ = dict.__delitem__\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.create_and_validate_slice","title":"create_and_validate_slice(arg, target_array)
","text":"Creates and validates a slice object based on the target array.
Source code in src/tolvera/utils.py
def create_and_validate_slice(\n arg: Union[int, tuple[int, ...], slice], target_array: np.ndarray\n) -> slice:\n \"\"\"\n Creates and validates a slice object based on the target array.\n \"\"\"\n try:\n slice_obj = create_safe_slice(arg)\n if not validate_slice(slice_obj, target_array):\n raise ValueError(f\"Invalid slice: {slice_obj}\")\n return slice_obj\n except Exception as e:\n raise type(e)(f\"Error creating slice: {e}\")\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.create_ndslices","title":"create_ndslices(dims)
","text":"Create a multi-dimensional slice from a list of tuples.
Parameters:
Name Type Description Default dims
list[tuple]
A list of tuples containing the slice parameters for each dimension.
required Returns:
Type Description s_
np.s_: A multi-dimensional slice object.
Source code in src/tolvera/utils.py
def create_ndslices(dims: list[tuple]) -> np.s_:\n \"\"\"\n Create a multi-dimensional slice from a list of tuples.\n\n Args:\n dims (list[tuple]): A list of tuples containing the slice parameters for each dimension.\n\n Returns:\n np.s_: A multi-dimensional slice object.\n \"\"\"\n return np.s_[tuple(slice(*dim) if isinstance(dim, tuple) else dim for dim in dims)]\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.create_safe_slice","title":"create_safe_slice(arg)
","text":"Creates a slice object based on the input argument.
Parameters:
Name Type Description Default arg
(int, tuple, slice)
The argument for creating the slice. It can be an integer, a tuple with slice parameters, or a slice object itself.
required Returns:
Name Type Description slice
slice
A slice object created based on the provided argument.
Source code in src/tolvera/utils.py
def create_safe_slice(arg: Union[int, tuple[int, ...], slice]) -> slice:\n \"\"\"\n Creates a slice object based on the input argument.\n\n Args:\n arg (int, tuple, slice): The argument for creating the slice. It can be an integer,\n a tuple with slice parameters, or a slice object itself.\n\n Returns:\n slice: A slice object created based on the provided argument.\n \"\"\"\n try:\n if isinstance(arg, slice):\n return arg\n elif isinstance(arg, tuple):\n return slice(*arg)\n elif isinstance(arg, int):\n return slice(arg, arg + 1)\n else:\n raise TypeError(f\"Invalid slice type: {type(arg)} {arg}\")\n except Exception as e:\n raise type(e)(f\"[create_safe_slice] Error creating slice: {e}\")\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.flatten","title":"flatten(lst)
","text":"Flatten a nested list or return a non-nested list as is.
Source code in src/tolvera/utils.py
def flatten(lst):\n \"\"\"Flatten a nested list or return a non-nested list as is.\"\"\"\n if all(isinstance(el, list) for el in lst):\n return [item for sublist in lst for item in sublist]\n return lst\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.generic_slice","title":"generic_slice(array, slice_params)
","text":"Slices a NumPy array based on a tuple of slice parameters for each dimension.
Parameters:
Name Type Description Default array
ndarray
The array to be sliced.
required slice_params
tuple
A tuple where each item is either an integer, a tuple with slice parameters, or a slice object.
required Returns:
Name Type Description ndarray
ndarray
The sliced array.
Source code in src/tolvera/utils.py
def generic_slice(\n array: np.ndarray,\n slice_params: Union[\n tuple[Union[int, tuple[int, ...], slice], ...],\n Union[int, tuple[int, ...], slice],\n ],\n) -> np.ndarray:\n \"\"\"\n Slices a NumPy array based on a tuple of slice parameters for each dimension.\n\n Args:\n array (np.ndarray): The array to be sliced.\n slice_params (tuple): A tuple where each item is either an integer, a tuple with\n slice parameters, or a slice object.\n\n Returns:\n ndarray: The sliced array.\n \"\"\"\n if not isinstance(slice_params, tuple):\n slice_params = (slice_params,)\n slices = tuple(create_safe_slice(param) for param in slice_params)\n return array.__getitem__(slices)\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.time_function","title":"time_function(func, *args, **kwargs)
","text":"Time how long it takes to run a function and print the result
Source code in src/tolvera/utils.py
def time_function(func, *args, **kwargs):\n \"\"\"Time how long it takes to run a function and print the result\"\"\"\n start = time.time()\n ret = func(*args, **kwargs)\n end = time.time()\n print(f\"[Tolvera.utils] {func.__name__}() ran in {end-start:.4f}s\")\n if ret is not None:\n return (ret, end - start)\n return end - start\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.validate_json_path","title":"validate_json_path(path)
","text":"Validate a JSON file path. It uses validate_path for initial validation.
Parameters:
Name Type Description Default path
str
The JSON file path to be validated.
required Returns:
Name Type Description bool
bool
True if the path is a valid JSON file path, raises an exception otherwise.
Raises:
Type Description ValueError
If the path does not end with '.json'.
Source code in src/tolvera/utils.py
def validate_json_path(path: str) -> bool:\n \"\"\"\n Validate a JSON file path. It uses validate_path for initial validation.\n\n Args:\n path (str): The JSON file path to be validated.\n\n Returns:\n bool: True if the path is a valid JSON file path, raises an exception otherwise.\n\n Raises:\n ValueError: If the path does not end with '.json'.\n \"\"\"\n # Using validate_path for basic path validation\n validate_path(path)\n\n if not path.endswith(\".json\"):\n raise ValueError(\"Path should end with '.json'\")\n\n return True\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.validate_path","title":"validate_path(path)
","text":"Validate a path using os.path and pathlib.
Parameters:
Name Type Description Default path
str
The path to be validated.
required Returns:
Name Type Description bool
bool
True if the path is valid, raises an exception otherwise.
Raises:
Type Description TypeError
If the input is not a string.
FileNotFoundError
If the path does not exist.
PermissionError
If the path is not accessible.
Source code in src/tolvera/utils.py
def validate_path(path: str) -> bool:\n \"\"\"\n Validate a path using os.path and pathlib.\n\n Args:\n path (str): The path to be validated.\n\n Returns:\n bool: True if the path is valid, raises an exception otherwise.\n\n Raises:\n TypeError: If the input is not a string.\n FileNotFoundError: If the path does not exist.\n PermissionError: If the path is not accessible.\n \"\"\"\n if not isinstance(path, str):\n raise TypeError(f\"Expected a string for path, but received {type(path)}\")\n\n path_obj = Path(path)\n if not path_obj.is_file():\n raise FileNotFoundError(f\"The path {path} does not exist or is not a file\")\n\n if not os.access(path, os.R_OK):\n raise PermissionError(f\"The path {path} is not accessible\")\n\n return True\n
"},{"location":"reference/tolvera/utils/#tolvera.utils.validate_slice","title":"validate_slice(slice_obj, target_array)
","text":"Validates if the given slice object is applicable to the target ndarray.
Parameters:
Name Type Description Default slice_obj
tuple[slice]
A tuple containing slice objects for each dimension.
required target_array
ndarray
The array to be sliced.
required Returns:
Name Type Description bool
bool
True if the slice is valid for the given array, False otherwise.
Source code in src/tolvera/utils.py
def validate_slice(slice_obj: tuple[slice], target_array: np.ndarray) -> bool:\n \"\"\"\n Validates if the given slice object is applicable to the target ndarray.\n\n Args:\n slice_obj (tuple[slice]): A tuple containing slice objects for each dimension.\n target_array (np.ndarray): The array to be sliced.\n\n Returns:\n bool: True if the slice is valid for the given array, False otherwise.\n \"\"\"\n if len(slice_obj) != target_array.ndim:\n return False\n\n for sl, size in zip(slice_obj, target_array.shape):\n # Check if slice start and stop are within the dimension size\n start, stop, _ = sl.indices(size)\n if not (0 <= start < size and (0 <= stop <= size or stop == -1)):\n return False\n return True\n
"},{"location":"reference/tolvera/osc/maxmsp/","title":"Maxmsp","text":""},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher","title":"MaxPatcher
","text":"TODO: copy-paste using stdout TODO: add scale objects before send and after receive TODO: add default values via loadbangs TODO: move udpsend/udpreceive to the top left TODO: dict of object ids TODO: add abstraction i/o messages e.g. param names, state save/load/dumps
Source code in src/tolvera/osc/maxmsp.py
class MaxPatcher:\n \"\"\"\n TODO: copy-paste using stdout\n TODO: add scale objects before send and after receive\n TODO: add default values via loadbangs\n TODO: move udpsend/udpreceive to the top left\n TODO: dict of object ids\n TODO: add abstraction i/o messages e.g. param names, state save/load/dumps\n \"\"\"\n\n def __init__(\n self,\n osc,\n client_name=\"client\",\n filepath=\"osc_controls\",\n x=0.0,\n y=0.0,\n w=1600.0,\n h=900.0,\n v=\"8.5.4\",\n ) -> None:\n self.patch = {\n \"patcher\": {\n \"fileversion\": 1,\n \"appversion\": {\n \"major\": v[0],\n \"minor\": v[2],\n \"revision\": v[4],\n \"architecture\": \"x64\",\n \"modernui\": 1,\n },\n \"classnamespace\": \"box\",\n \"rect\": [x, y, w, h],\n \"bglocked\": 0,\n \"openinpresentation\": 0,\n \"default_fontsize\": 12.0,\n \"default_fontface\": 0,\n \"default_fontname\": \"Arial\",\n \"gridonopen\": 1,\n \"gridsize\": [15.0, 15.0],\n \"gridsnaponopen\": 1,\n \"objectsnaponopen\": 1,\n \"statusbarvisible\": 2,\n \"toolbarvisible\": 1,\n \"lefttoolbarpinned\": 0,\n \"toptoolbarpinned\": 0,\n \"righttoolbarpinned\": 0,\n \"bottomtoolbarpinned\": 0,\n \"toolbars_unpinned_last_save\": 0,\n \"tallnewobj\": 0,\n \"boxanimatetime\": 200,\n \"enablehscroll\": 1,\n \"enablevscroll\": 1,\n \"devicewidth\": 0.0,\n \"description\": \"\",\n \"digest\": \"\",\n \"tags\": \"\",\n \"style\": \"\",\n \"subpatcher_template\": \"\",\n \"assistshowspatchername\": 0,\n \"boxes\": [],\n \"lines\": [],\n \"dependency_cache\": [],\n \"autosave\": 0,\n }\n }\n self.types = {\n \"print\": \"print\",\n \"message\": \"message\",\n \"object\": \"newobj\",\n \"comment\": \"comment\",\n \"slider\": \"slider\",\n \"float\": \"flonum\",\n \"int\": \"number\",\n \"bang\": \"button\",\n }\n self.osc = osc\n self.client_name = client_name\n self.client_address, self.client_port = self.osc.client_names[self.client_name]\n self.filepath = filepath\n self.init()\n\n def init(self):\n self.w = 5.5 # default width (scaling factor)\n self.h = 22.0 # default height (pixels)\n self.s_x, self.s_y = 30, 125 # insertion point\n self.r_x, self.r_y = 30, 575 # insertion point\n self.patcher_ids = {}\n self.patcher_ids[\"send_id\"] = self.add_osc_send(\n self.osc.host, self.osc.port, self.s_x, 30, print_label=\"sent\"\n )\n self.patcher_ids[\"receive_id\"] = self.add_osc_receive(\n self.client_port, self.s_x + 150, 30, print_label=\"received\"\n )\n self.add_comment(\"Max \u2192 Python\", self.s_x, self.s_y, 24)\n self.add_comment(\"Python \u2192 Max\", self.r_x, self.r_y, 24)\n self.s_y += 50\n self.r_y += 50\n self.save(self.filepath)\n\n def add_box(self, box_type, inlets, outlets, x, y, w, h=None):\n if h is None:\n h = self.h\n box_id, box = self.create_box(box_type, inlets, outlets, x, y, w, h)\n return self._add_box(box)\n\n def _add_box(self, box):\n self.patch[\"patcher\"][\"boxes\"].append(box)\n return self.id_from_str(box[\"box\"][\"id\"])\n\n def create_box(self, box_type, inlets, outlets, x, y, w, h=None):\n if h is None:\n h = self.h\n box_id = len(self.patch[\"patcher\"][\"boxes\"]) + 1\n box = {\n \"box\": {\n \"id\": \"obj-\" + str(box_id),\n \"maxclass\": self.types[box_type],\n \"numinlets\": inlets,\n \"numoutlets\": outlets,\n \"patching_rect\": [x, y, w, h],\n }\n }\n if outlets > 0:\n if outlets == 1:\n box[\"box\"][\"outlettype\"] = [\"\"]\n match box_type:\n case \"int\" | \"float\" | \"bang\":\n box[\"box\"][\"outlettype\"] = [\"\", \"bang\"]\n return box_id, box\n\n def add_object(self, text, inlets, outlets, x, y):\n box_id, box = self.create_box(\n \"object\", inlets, outlets, x, y, len(text) * self.w\n )\n box[\"box\"][\"text\"] = text\n self._add_box(box)\n return box_id\n\n def add_message(self, text, x, y):\n box_id, box = self.create_box(\"message\", 2, 1, x, y, len(text) * self.w)\n box[\"box\"][\"text\"] = text\n self._add_box(box)\n return box_id\n\n def add_comment(self, text, x, y, fontsize=12):\n box_id, box = self.create_box(\"comment\", 0, 0, x, y, len(text) * self.w)\n box[\"box\"][\"text\"] = text\n box[\"box\"][\"fontsize\"] = fontsize\n self._add_box(box)\n return box_id\n\n def add_bang(self, x, y):\n box_id, box = self.create_box(\"bang\", 1, 1, x, y, 20.0)\n self._add_box(box)\n return box_id\n\n def add_slider(self, x, y, min_val, size, float=False):\n box_id, box = self.create_box(\"slider\", 1, 1, x, y, 20.0, 140.0)\n if float:\n box[\"box\"][\"floatoutput\"] = 1\n box[\"box\"][\"min\"] = min_val\n box[\"box\"][\"size\"] = size\n self._add_box(box)\n return box_id\n\n def connect(self, src, src_outlet, dst, dst_inlet):\n patchline = {\n \"patchline\": {\n \"destination\": [\"obj-\" + str(dst), dst_inlet],\n \"source\": [\"obj-\" + str(src), src_outlet],\n }\n }\n self.patch[\"patcher\"][\"lines\"].append(patchline)\n return patchline\n\n def save(self, name):\n with open(name + \".maxpat\", \"w\") as f:\n f.write(json.dumps(self.patch, indent=2))\n\n def load(self, name):\n with open(name + \".maxpat\", \"r\") as f:\n self.patch = json.loads(f.read())\n\n def get_box_by_id(self, id):\n for box in self.patch[\"patcher\"][\"boxes\"]:\n if self.id_from_str(box[\"box\"][\"id\"]) == id:\n return box\n return None\n\n def str_from_id(self, id):\n return \"obj-\" + str(id)\n\n def id_from_str(self, obj_str):\n return int(obj_str[4:])\n\n def add_osc_send(self, ip, port, x, y, print=True, print_label=None):\n box_id_0 = self.add_object(\"r send\", 0, 1, x, y)\n box_id = self.add_object(\"udpsend \" + ip + \" \" + str(port), 1, 0, x, y + 25)\n if print:\n text = \"print\" if print_label is None else \"print \" + print_label\n print_id = self.add_object(text, 1, 0, x + 50, y)\n self.connect(box_id_0, 0, box_id, 0)\n self.connect(box_id_0, 0, print_id, 0)\n return box_id_0\n return box_id\n\n def add_osc_receive(self, port, x, y, print=True, print_label=None):\n box_id_0 = self.add_object(\"s receive\", 0, 1, x, y + 25)\n box_id = self.add_object(\"udpreceive \" + str(port), 1, 1, x, y)\n if print:\n text = \"print\" if print_label is None else \"print \" + print_label\n print_id = self.add_object(text, 1, 0, x + 60, y + 25)\n self.connect(box_id, 0, print_id, 0)\n self.connect(box_id, 0, box_id_0, 0)\n return box_id_0\n return box_id\n\n def add_osc_route(self, port, x, y, print=True, print_label=None):\n \"\"\"\n [route path]\n [s name] [print]\n [unpack] ?\n [r name]\n \"\"\"\n pass\n\n def add_sliders(self, x, y, sliders):\n \"\"\"\n sliders = [\n { 'label': 'x', data: 'float', min_val: 0.0, size: 0.0 },\n ]\n\n [slider] ...\n |\n [number] ...\n \"\"\"\n slider_ids = []\n float_ids = []\n y_off = 0\n for i, s in enumerate(sliders):\n y_off = 0\n x_i = x + (i * 52.0)\n y_off += self.h\n slider_id = self.add_slider(\n x_i, y + y_off, s[\"min_val\"], s[\"size\"], float=s[\"data\"] == \"float\"\n )\n y_off += 150\n float_id = self.add_box(\"float\", 1, 2, x_i, y + y_off, 50)\n slider_ids.append(slider_id)\n float_ids.append(float_id)\n return slider_ids, float_ids, y_off\n\n def add_param_comments(self, x, y, params):\n comment_ids = []\n y_off = 0\n for i, p in enumerate(params):\n y_off = 0\n x_i = x + (i * 52.0)\n p_max = (\n p[\"min_val\"] + p[\"size\"]\n if p[\"data\"] == \"float\"\n else p[\"min_val\"] + p[\"size\"] - 1\n )\n comment_id1 = self.add_comment(f'{p[\"label\"]}', x_i, y)\n y_off += 15\n comment_id2 = self.add_comment(\n f'{p[\"data\"][0]} {p[\"min_val\"]}-{p_max}', x_i, y + y_off\n )\n comment_ids.append(comment_id1)\n comment_ids.append(comment_id2)\n return comment_ids, y_off\n\n def add_osc_send_msg(self, x, y, path):\n msg_id = self.add_message(path, x, y + 225 + self.h)\n send_id = self.add_object(\"s send\", 1, 0, x, y + 250 + self.h)\n self.connect(msg_id, 0, send_id, 0)\n return msg_id\n\n def add_osc_receive_msg(self, x, y, path):\n receive_id = self.add_object(\"r receive\", 0, 1, x, y + 225 + self.h)\n msg_id = self.add_message(path, x, y + 250 + self.h)\n self.connect(receive_id, 0, msg_id, 0)\n return msg_id\n\n def add_osc_send_with_controls(self, x, y, path, parameters):\n # TODO: add default param value and a loadbang\n \"\"\"\n [comment path]\n [comment args]\n [r path_arg_name]\n sliders\n | |\n [pak $1 $2 $3 ...]\n |\n [msg /path $1 $2 $3 ...]\n |\n [s send]\n \"\"\"\n y_off = 0\n # [comment path]\n path_comment_id = self.add_comment(path, x, y + y_off)\n y_off += 15\n param_comment_ids, _y_off = self.add_param_comments(x, y + y_off, parameters)\n\n # [r path_arg_name]\n y_off += 35\n receive_ids = [\n self.add_object(\n \"r \" + path.replace(\"/\", \"_\")[1:] + \"_\" + p[\"label\"][0:3],\n 1,\n 0,\n x + i * 52.0,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n y_off += 30\n\n # sliders\n slider_ids, slider_float_ids, _y_off = self.add_sliders(\n x, y + y_off, parameters\n )\n y_off += _y_off + 25\n # [pak $1 $2 $3 ...]\n pack_id = self.add_object(\n \"pak \" + self._pack_args(parameters), len(parameters) + 1, 1, x, y + y_off\n )\n pack_width = self.get_box_by_id(pack_id)[\"box\"][\"patching_rect\"][2]\n # [msg /path $1 $2 $3 ...]\n y_off += 25\n msg_id = self.add_message(path + \" \" + self._msg_args(parameters), x, y + y_off)\n # [s send]\n y_off += 25\n send_id = self.add_object(\"s send\", 1, 0, x, y + y_off)\n # connections\n [\n self.connect(receive_ids[i], 0, slider_ids[i], 0)\n for i in range(len(parameters))\n ]\n [\n self.connect(slider_ids[i], 0, slider_float_ids[i], 0)\n for i in range(len(parameters))\n ]\n [\n self.connect(slider_float_ids[i], 0, pack_id, i)\n for i in range(len(parameters))\n ]\n self.connect(pack_id, 0, msg_id, 0)\n self.connect(msg_id, 0, send_id, 0)\n return slider_ids, pack_id, msg_id\n\n def add_osc_receive_with_controls(self, x, y, path, parameters):\n # TODO: add default param value and a loadbang\n \"\"\"\n [comment path]\n [r receive]\n |\n [route /path]\n | |\n [unpack f f f ...] [print /path]\n |\n [slider] ...\n |\n [number] ...\n |\n [s arg_name]\n [comment path_arg_name]\n [comment type min-max]\n \"\"\"\n # [comment path]\n y_off = 0\n path_comment_id = self.add_comment(path, x, y + y_off)\n\n # [r receive]\n y_off += 25\n receive_id = self.add_object(\"r receive\", 0, 1, x, y + y_off)\n\n # [route /path]\n y_off += 25\n route_id = self.add_object(\"route \" + path, 1, 1, x, y + y_off)\n\n # [unpack f f f ...] [print /path]\n y_off += 25\n unpack_id = self.add_object(\n \"unpack \" + self._pack_args(parameters),\n len(parameters) + 1,\n 1,\n x,\n y + y_off,\n )\n unpack_width = self.get_box_by_id(unpack_id)[\"box\"][\"patching_rect\"][2]\n print_id = self.add_object(\n \"print \" + path, 1, 0, x + unpack_width + 10, y + y_off\n )\n\n # sliders\n y_off += 10\n slider_ids, float_ids, _y_off = self.add_sliders(x, y + y_off, parameters)\n\n # [s arg_name]\n y_off += _y_off + 25\n send_ids = [\n self.add_object(\n \"s \" + path.replace(\"/\", \"_\")[1:] + \"_\" + p[\"label\"][0:3],\n 1,\n 0,\n x + i * 52.0,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n\n # [comment params]\n y_off += 50\n param_comment_ids, _y_off = self.add_param_comments(x, y + y_off, parameters)\n\n # connections\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, unpack_id, 0)\n self.connect(route_id, 0, print_id, 0)\n [self.connect(unpack_id, i, slider_ids[i], 0) for i in range(len(parameters))]\n [\n self.connect(slider_ids[i], 0, float_ids[i], 0)\n for i in range(len(parameters))\n ]\n [self.connect(float_ids[i], 0, send_ids[i], 0) for i in range(len(parameters))]\n\n return slider_ids, unpack_id\n\n def add_send_args_func(self, f):\n hints = typing.get_type_hints(f[\"f\"])[\"return\"].__args__\n f_p = f[\"params\"]\n params = []\n if len(f_p) == 0:\n self.add_osc_receive_msg(self.r_x, self.r_y, f[\"address\"])\n else:\n for i, p in enumerate(f_p):\n p_def, p_min, p_max = f_p[p][0], f_p[p][1], f_p[p][2]\n params.append(\n {\n \"label\": p,\n \"data\": hints[i].__name__,\n \"min_val\": p_min,\n \"size\": p_max - p_min,\n }\n )\n self.add_osc_receive_with_controls(self.r_x, self.r_y, f[\"address\"], params)\n self.r_x += max(len(params) * 52.0 + 100.0, len(f[\"address\"]) * 6.0 + 25.0)\n self.save(self.filepath)\n\n def add_send_list_func(self, f):\n raise NotImplementedError(\"add_send_list_func not implemented yet\")\n\n def add_receive_args_func(self, f):\n hints = typing.get_type_hints(f[\"f\"])\n f_p = f[\"params\"]\n params = []\n if len(f_p) == 0:\n self.add_osc_send_msg(self.s_x, self.s_y, f[\"address\"])\n else:\n for p in f_p:\n p_def, p_min, p_max = f_p[p][0], f_p[p][1], f_p[p][2]\n params.append(\n {\n \"label\": p,\n \"data\": hints[p].__name__,\n \"min_val\": p_min,\n \"size\": p_max - p_min,\n }\n )\n self.add_osc_send_with_controls(self.s_x, self.s_y, f[\"address\"], params)\n self.s_x += max(len(params) * 52.0 + 100.0, len(f[\"address\"]) * 6.0 + 25.0)\n self.save(self.filepath)\n\n def add_receive_list_func(self, f):\n raise NotImplementedError(\"add_receive_list_func not implemented yet\")\n\n def _msg_args(self, args):\n return \" \".join([\"$\" + str(i + 1) for i in range(len(args))])\n\n def _pack_args(self, args):\n arg_types = []\n for a in args:\n match a[\"data\"]:\n case \"int\":\n arg_types.append(\"i\")\n case \"float\":\n arg_types.append(\"f\")\n case \"string\":\n arg_types.append(\"s\")\n return \" \".join(arg_types)\n
"},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher.add_osc_receive_with_controls","title":"add_osc_receive_with_controls(x, y, path, parameters)
","text":"[comment path] [r receive] | [route /path] | | [unpack f f f ...] [print /path] | [slider] ... | [number] ... | [s arg_name] [comment path_arg_name] [comment type min-max]
Source code in src/tolvera/osc/maxmsp.py
def add_osc_receive_with_controls(self, x, y, path, parameters):\n # TODO: add default param value and a loadbang\n \"\"\"\n [comment path]\n [r receive]\n |\n [route /path]\n | |\n [unpack f f f ...] [print /path]\n |\n [slider] ...\n |\n [number] ...\n |\n [s arg_name]\n [comment path_arg_name]\n [comment type min-max]\n \"\"\"\n # [comment path]\n y_off = 0\n path_comment_id = self.add_comment(path, x, y + y_off)\n\n # [r receive]\n y_off += 25\n receive_id = self.add_object(\"r receive\", 0, 1, x, y + y_off)\n\n # [route /path]\n y_off += 25\n route_id = self.add_object(\"route \" + path, 1, 1, x, y + y_off)\n\n # [unpack f f f ...] [print /path]\n y_off += 25\n unpack_id = self.add_object(\n \"unpack \" + self._pack_args(parameters),\n len(parameters) + 1,\n 1,\n x,\n y + y_off,\n )\n unpack_width = self.get_box_by_id(unpack_id)[\"box\"][\"patching_rect\"][2]\n print_id = self.add_object(\n \"print \" + path, 1, 0, x + unpack_width + 10, y + y_off\n )\n\n # sliders\n y_off += 10\n slider_ids, float_ids, _y_off = self.add_sliders(x, y + y_off, parameters)\n\n # [s arg_name]\n y_off += _y_off + 25\n send_ids = [\n self.add_object(\n \"s \" + path.replace(\"/\", \"_\")[1:] + \"_\" + p[\"label\"][0:3],\n 1,\n 0,\n x + i * 52.0,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n\n # [comment params]\n y_off += 50\n param_comment_ids, _y_off = self.add_param_comments(x, y + y_off, parameters)\n\n # connections\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, unpack_id, 0)\n self.connect(route_id, 0, print_id, 0)\n [self.connect(unpack_id, i, slider_ids[i], 0) for i in range(len(parameters))]\n [\n self.connect(slider_ids[i], 0, float_ids[i], 0)\n for i in range(len(parameters))\n ]\n [self.connect(float_ids[i], 0, send_ids[i], 0) for i in range(len(parameters))]\n\n return slider_ids, unpack_id\n
"},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher.add_osc_route","title":"add_osc_route(port, x, y, print=True, print_label=None)
","text":"[route path] [s name] print ? [r name]
Source code in src/tolvera/osc/maxmsp.py
def add_osc_route(self, port, x, y, print=True, print_label=None):\n \"\"\"\n [route path]\n [s name] [print]\n [unpack] ?\n [r name]\n \"\"\"\n pass\n
"},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher.add_osc_send_with_controls","title":"add_osc_send_with_controls(x, y, path, parameters)
","text":"[comment path] [comment args] [r path_arg_name] sliders | | [pak $1 $2 $3 ...] | [msg /path $1 $2 $3 ...] | [s send]
Source code in src/tolvera/osc/maxmsp.py
def add_osc_send_with_controls(self, x, y, path, parameters):\n # TODO: add default param value and a loadbang\n \"\"\"\n [comment path]\n [comment args]\n [r path_arg_name]\n sliders\n | |\n [pak $1 $2 $3 ...]\n |\n [msg /path $1 $2 $3 ...]\n |\n [s send]\n \"\"\"\n y_off = 0\n # [comment path]\n path_comment_id = self.add_comment(path, x, y + y_off)\n y_off += 15\n param_comment_ids, _y_off = self.add_param_comments(x, y + y_off, parameters)\n\n # [r path_arg_name]\n y_off += 35\n receive_ids = [\n self.add_object(\n \"r \" + path.replace(\"/\", \"_\")[1:] + \"_\" + p[\"label\"][0:3],\n 1,\n 0,\n x + i * 52.0,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n y_off += 30\n\n # sliders\n slider_ids, slider_float_ids, _y_off = self.add_sliders(\n x, y + y_off, parameters\n )\n y_off += _y_off + 25\n # [pak $1 $2 $3 ...]\n pack_id = self.add_object(\n \"pak \" + self._pack_args(parameters), len(parameters) + 1, 1, x, y + y_off\n )\n pack_width = self.get_box_by_id(pack_id)[\"box\"][\"patching_rect\"][2]\n # [msg /path $1 $2 $3 ...]\n y_off += 25\n msg_id = self.add_message(path + \" \" + self._msg_args(parameters), x, y + y_off)\n # [s send]\n y_off += 25\n send_id = self.add_object(\"s send\", 1, 0, x, y + y_off)\n # connections\n [\n self.connect(receive_ids[i], 0, slider_ids[i], 0)\n for i in range(len(parameters))\n ]\n [\n self.connect(slider_ids[i], 0, slider_float_ids[i], 0)\n for i in range(len(parameters))\n ]\n [\n self.connect(slider_float_ids[i], 0, pack_id, i)\n for i in range(len(parameters))\n ]\n self.connect(pack_id, 0, msg_id, 0)\n self.connect(msg_id, 0, send_id, 0)\n return slider_ids, pack_id, msg_id\n
"},{"location":"reference/tolvera/osc/maxmsp/#tolvera.osc.maxmsp.MaxPatcher.add_sliders","title":"add_sliders(x, y, sliders)
","text":"sliders = [ { 'label': 'x', data: 'float', min_val: 0.0, size: 0.0 }, ]
[slider] ... | [number] ...
Source code in src/tolvera/osc/maxmsp.py
def add_sliders(self, x, y, sliders):\n \"\"\"\n sliders = [\n { 'label': 'x', data: 'float', min_val: 0.0, size: 0.0 },\n ]\n\n [slider] ...\n |\n [number] ...\n \"\"\"\n slider_ids = []\n float_ids = []\n y_off = 0\n for i, s in enumerate(sliders):\n y_off = 0\n x_i = x + (i * 52.0)\n y_off += self.h\n slider_id = self.add_slider(\n x_i, y + y_off, s[\"min_val\"], s[\"size\"], float=s[\"data\"] == \"float\"\n )\n y_off += 150\n float_id = self.add_box(\"float\", 1, 2, x_i, y + y_off, 50)\n slider_ids.append(slider_id)\n float_ids.append(float_id)\n return slider_ids, float_ids, y_off\n
"},{"location":"reference/tolvera/osc/osc/","title":"Osc","text":""},{"location":"reference/tolvera/osc/oscmap/","title":"Oscmap","text":""},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap","title":"OSCMap
","text":"OSCMap maps OSC messages to functions It creates a Max/MSP patcher that can be used to control the OSCMap It uses OSCSendUpdater and OSCReceiveUpdater to decouple incoming messages
Source code in src/tolvera/osc/oscmap.py
class OSCMap:\n \"\"\"\n OSCMap maps OSC messages to functions\n It creates a Max/MSP patcher that can be used to control the OSCMap\n It uses OSCSendUpdater and OSCReceiveUpdater to decouple incoming messages\n \"\"\"\n\n def __init__(\n self,\n osc: iiOSC,\n client_name=\"client\",\n patch_type=\"Max\", # | \"Pd\"\n patch_filepath=\"osc_controls\",\n create_patch=True,\n pd_net_or_udp=\"udp\",\n pd_bela=False,\n export=None, # 'JSON' | 'XML' | True\n ) -> None:\n self.osc = osc\n self.client_name = client_name\n self.client_address, self.client_port = self.osc.client_names[self.client_name]\n self.dict = {\"send\": {}, \"receive\": {}}\n self.create_patch = create_patch\n self.patch_filepath = patch_filepath\n self.patch_type = patch_type\n if create_patch is True:\n self.init_patcher(patch_type, patch_filepath, pd_net_or_udp, pd_bela)\n if export is not None:\n assert (\n export == \"JSON\" or export == \"XML\" or export == True\n ), \"export must be 'JSON', 'XML' or True\"\n self.export = export\n\n def init_patcher(self, patch_type, patch_filepath, pd_net_or_udp, pd_bela):\n # create self.patch_dir if it doesn't exist\n self.patch_dir = \"pd\" if patch_type == \"Pd\" else \"max\"\n if not os.path.exists(self.patch_dir):\n print(f\"Creating {self.patch_dir} directory...\")\n os.makedirs(self.patch_dir)\n self.patch_appendix = \"_local\" if self.osc.host == \"127.0.0.1\" else \"_remote\"\n self.patch_filepath = (\n self.patch_dir + \"/\" + patch_filepath + self.patch_appendix\n )\n if patch_type == \"Max\":\n self.patcher = MaxPatcher(self.osc, self.client_name, self.patch_filepath)\n elif patch_type == \"Pd\":\n if pd_bela is True:\n self.patcher = PdPatcher(\n self.osc,\n self.client_name,\n self.patch_filepath,\n net_or_udp=pd_net_or_udp,\n bela=True,\n )\n else:\n self.patcher = PdPatcher(\n self.osc,\n self.client_name,\n self.patch_filepath,\n net_or_udp=pd_net_or_udp,\n )\n else:\n assert False, \"`patch_type` must be 'Max' or 'Pd'\"\n\n def add(self, **kwargs):\n print(\n \"DeprecationError: OSCMap.add() has been split into separate functions: use `send_args`, `send_list`, `receive_args` or `receive_list` instead!\"\n )\n exit()\n\n def map_func_to_dict(self, func, kwargs):\n if \"name\" not in kwargs:\n n = func.__name__\n address = \"/\" + n.replace(\"_\", \"/\")\n else:\n if isinstance(kwargs[\"name\"], str):\n n = kwargs[\"name\"]\n address = \"/\" + kwargs[\"name\"].replace(\"_\", \"/\")\n else:\n raise TypeError(\n f\"OSC func name must be string, found {str(type(kwargs['name']))}\"\n )\n # TODO: Move this into specific send/receive functions\n params = {\n k: v\n for k, v in kwargs.items()\n if k != \"count\" and k != \"send_mode\" and k != \"length\" and k != \"name\"\n }\n # TODO: turn params into dict with type hints (see export_dict)\n hints = get_type_hints(func)\n f = {\"f\": func, \"name\": n, \"address\": address, \"params\": params, \"hints\": hints}\n return f\n\n \"\"\"\n send args\n \"\"\"\n\n def send_args(self, **kwargs):\n def decorator(func):\n def wrapper(*args):\n self.add_send_args(func, kwargs)\n return func()\n\n default_args = [\n kwargs[a][0]\n for a in kwargs\n if a != \"count\" and a != \"send_mode\" and a != \"name\"\n ]\n wrapper(*default_args)\n return wrapper\n\n return decorator\n\n def add_send_args(self, func, kwargs):\n self.add_send_args_to_osc_map(func, kwargs)\n if self.create_patch is True:\n self.add_send_args_to_patcher(func)\n\n def add_send_args_to_osc_map(self, func, kwargs):\n f = self.map_func_to_dict(func, kwargs)\n if kwargs[\"send_mode\"] == \"broadcast\":\n f[\"updater\"] = OSCSendUpdater(\n self.osc,\n f[\"address\"],\n f=func,\n count=kwargs[\"count\"],\n client=self.client_name,\n )\n else:\n f[\"sender\"] = OSCSend(\n self.osc,\n f[\"address\"],\n f=func,\n count=kwargs[\"count\"],\n client=self.client_name,\n )\n f[\"type\"] = \"args\"\n self.dict[\"send\"][f[\"name\"]] = f\n if self.export is not None:\n self.export_dict()\n\n def add_send_args_to_patcher(self, func):\n f = self.dict[\"send\"][func.__name__]\n self.patcher.send_args_func(f)\n\n \"\"\"\n send list\n \"\"\"\n\n def send_list(self, **kwargs):\n def decorator(func):\n def wrapper(*args):\n self.add_send_list(func, kwargs)\n # TODO: This was originally here to sync defaults with client\n # but it causes init order isses in IMLFun2OSC.update\n # return func()\n\n default_arg = [\n kwargs[a][0]\n for a in kwargs\n if a != \"count\" and a != \"send_mode\" and a != \"length\" and a != \"name\"\n ]\n wrapper(default_arg)\n return wrapper\n\n return decorator\n\n def add_send_list(self, func, kwargs):\n self.add_send_list_to_osc_map(func, kwargs)\n if self.create_patch is True:\n self.add_send_list_to_patcher(func)\n\n def add_send_list_to_osc_map(self, func, kwargs):\n f = self.map_func_to_dict(func, kwargs)\n # TODO: Hack for send_list_inline which doesn't have a return type hint\n if \"return\" in f[\"hints\"]:\n hint = f[\"hints\"][\"return\"]\n else:\n hint = list[float]\n assert hint == list[float], \"send_list can only send list[float], found \" + str(\n hint\n )\n if kwargs[\"send_mode\"] == \"broadcast\":\n f[\"updater\"] = OSCSendUpdater(\n self.osc,\n f[\"address\"],\n f=func,\n count=kwargs[\"count\"],\n client=self.client_name,\n )\n else:\n f[\"sender\"] = OSCSend(\n self.osc,\n f[\"address\"],\n f=func,\n count=kwargs[\"count\"],\n client=self.client_name,\n )\n f[\"type\"] = \"list\"\n f[\"length\"] = kwargs[\"length\"]\n self.dict[\"send\"][f[\"name\"]] = f\n if self.export is not None:\n self.export_dict()\n\n def add_send_list_to_patcher(self, func):\n f = self.dict[\"send\"][func.__name__]\n self.patcher.send_list_func(f)\n\n def send_list_inline(self, name: str, sender_func, length: int, send_mode=\"broadcast\", count=1, **kwargs):\n kwargs = {**kwargs, **{\"name\": name, \"length\": length, \"send_mode\": send_mode, \"count\": count}}\n self.send_list(**kwargs)(sender_func)\n\n \"\"\"\n send kwargs\n \"\"\"\n\n def send_kwargs(self, **kwargs):\n raise NotImplementedError(\"send_kwargs not implemented yet\")\n\n \"\"\"\n receive args\n \"\"\"\n\n def receive_args(self, **kwargs):\n def decorator(func):\n def wrapper(*args):\n self.add_receive_args(func, kwargs)\n return func(*args)\n\n default_args = [\n kwargs[a][0] for a in kwargs if a != \"count\" and a != \"name\"\n ]\n wrapper(*default_args)\n return wrapper\n\n return decorator\n\n def add_receive_args(self, func, kwargs):\n f = self.add_receive_args_to_osc_map(func, kwargs)\n if self.create_patch is True:\n self.add_receive_args_to_patcher(f)\n\n def add_receive_args_to_osc_map(self, func, kwargs):\n f = self.map_func_to_dict(func, kwargs)\n f[\"updater\"] = OSCReceiveUpdater(\n self.osc, f[\"address\"], f=func, count=kwargs[\"count\"]\n )\n f[\"type\"] = \"args\"\n self.dict[\"receive\"][f[\"name\"]] = f\n return f\n\n def add_receive_args_to_patcher(self, func):\n f = self.dict[\"receive\"][func[\"name\"]]\n self.patcher.receive_args_func(f)\n\n def receive_args_inline(self, name: str, receiver_func, **kwargs):\n kwargs = {**kwargs, **{\"count\": 1, \"name\": name}}\n self.receive_args(**kwargs)(receiver_func)\n\n \"\"\"\n receive list\n \"\"\"\n\n def receive_list(self, **kwargs):\n def decorator(func):\n def wrapper(*args):\n self.add_receive_list(func, kwargs)\n # TODO: This was originally here to sync defaults with client\n # but it causes init order isses in IMLOSC2Vec.init\n # return func(*args)\n\n # TODO: This probably shouldn't be here...\n randomised_list = self.randomise_list(\n kwargs[\"length\"], kwargs[\"vector\"][1], kwargs[\"vector\"][2]\n )\n wrapper(randomised_list)\n return wrapper\n\n return decorator\n\n def randomise_list(self, length, min, max):\n return min + (np.random.rand(length).astype(np.float32) * (max - min))\n\n def add_receive_list(self, func, kwargs):\n f = self.add_receive_list_to_osc_map(func, kwargs)\n if self.create_patch is True:\n self.add_receive_list_to_patcher(f)\n\n def add_receive_list_to_osc_map(self, func, kwargs):\n \"\"\"\n TODO: Should this support list[float] only, or list[int] list[str] etc?\n \"\"\"\n f = self.map_func_to_dict(func, kwargs)\n assert (\n len(f[\"params\"]) == 1\n ), \"receive_list can only receive one param (list[float])\"\n print(f)\n hint = f[\"hints\"][list(f[\"params\"].keys())[0]]\n assert (\n hint == list[float]\n ), \"receive_list can only receive list[float], found \" + str(hint)\n f[\"updater\"] = OSCReceiveListUpdater(\n self.osc, f[\"address\"], f=func, count=kwargs[\"count\"]\n )\n f[\"type\"] = \"list\"\n f[\"length\"] = kwargs[\"length\"]\n self.dict[\"receive\"][f[\"name\"]] = f\n if self.export is not None:\n self.export_dict()\n return f\n\n def add_receive_list_to_patcher(self, func):\n f = self.dict[\"receive\"][func[\"name\"]]\n self.patcher.receive_list_func(f)\n\n def receive_list_inline(self, name: str, receiver_func, length: int, count=1, **kwargs):\n kwargs = {**kwargs, **{\"name\": name, \"length\": length, \"count\": count, \"vector\": (0, 0, 1)}}\n self.receive_list(**kwargs)(receiver_func)\n\n def receive_list_with_idx(\n self, name: str, receiver, idx_len: int, vec_len: int, attr=None\n ):\n \"\"\"\n Create an OSC list handler that assumes that the first `idx_len` values are indices into some struct being modified by a receiver function, and the rest are args as a list, i.e.\n /name idx0 idx1 ... idxN arg0 arg1 ... argM\n ...\n receiver((idx0 idx1 ... idxN), args)\n Intended as a utility function to be used by external classes where it's not possible to use a decorator like `receive_list`.\n \"\"\"\n\n def handler(vector: list[float]):\n arg_len = len(vector[idx_len:])\n assert (\n arg_len == vec_len\n ), f\"len(args) != len(list) ({arg_len} != {vec_len})\"\n if idx_len:\n indices = tuple([int(v) for v in vector[:idx_len]])\n if attr is None:\n receiver(indices, vector[idx_len:])\n else:\n receiver(indices, attr, vector[idx_len:])\n else:\n if attr is None:\n receiver(vector)\n else:\n receiver(attr, vector)\n\n kwargs = {\n \"vector\": (0, 0, 1),\n \"length\": vec_len + idx_len,\n \"count\": 1,\n \"name\": name,\n }\n self.receive_list(**kwargs)(handler)\n\n \"\"\"\n receive kwargs\n \"\"\"\n\n def receive_kwargs(self, **kwargs):\n \"\"\"\n Same as receive_args but with named params\n \"\"\"\n raise NotImplementedError(\"receive_kwargs not implemented yet\")\n\n \"\"\"\n xml / json export\n \"\"\"\n\n def export_dict(self):\n \"\"\"\n Save the OSCMap dict as XML\n \"\"\"\n client_ip, client_port = self.osc.client_names[self.client_name]\n # TODO: This should be defined in the OSCMap dict / on init\n metadata = {\n \"HostIP\": self.osc.host,\n \"HostPort\": str(self.osc.port),\n \"ClientName\": self.client_name,\n \"ClientIP\": client_ip,\n \"ClientPort\": str(client_port),\n }\n root = ET.Element(\"OpenSoundControlSchema\")\n metadata_element = ET.SubElement(root, \"Metadata\", **metadata)\n sends = self.dict[\"send\"]\n receives = self.dict[\"receive\"]\n for io in [\"Send\", \"Receive\"]:\n ET.SubElement(root, io)\n for io in [\"send\", \"receive\"]:\n for name in self.dict[io]:\n f = self.dict[io][name]\n if f[\"type\"] == \"args\":\n self.xml_add_args_params(root, name, io, f)\n elif f[\"type\"] == \"list\":\n self.xml_add_list_param(root, name, io, f)\n elif f[\"type\"] == \"kwargs\":\n raise NotImplementedError(\"kwargs not implemented yet\")\n self.export_update(root)\n\n def xml_add_args_params(self, root, name, io, f):\n params = f[\"params\"]\n hints = f[\"hints\"]\n kw = {\n \"Address\": \"/\" + name.replace(\"_\", \"/\"),\n \"Type\": f[\"type\"],\n \"Params\": str(len(params)),\n }\n route = ET.SubElement(root.find(io.capitalize()), \"Route\", **kw)\n for i, p in enumerate(params):\n # TODO: This should already be defined by this point\n if io == \"receive\":\n p_type = hints[p].__name__\n elif io == \"send\":\n p_type = hints[\"return\"].__args__[i].__name__\n kw = {\n \"Name\": p,\n \"Type\": p_type,\n \"Default\": str(params[p][0]),\n \"Min\": str(params[p][1]),\n \"Max\": str(params[p][2]),\n }\n ET.SubElement(route, \"Param\", **kw)\n\n def xml_add_list_param(self, root, name, io, f):\n params = f[\"params\"]\n hints = f[\"hints\"]\n length = f[\"length\"]\n kw = {\n \"Address\": \"/\" + name.replace(\"_\", \"/\"),\n \"Type\": f[\"type\"],\n \"Length\": str(length),\n }\n route = ET.SubElement(root.find(io.capitalize()), \"Route\", **kw)\n p = list(params.keys())[0]\n if io == \"receive\":\n p_type = hints[p].__name__\n elif io == \"send\":\n p_type = hints[\"return\"].__args__[0].__name__\n kw = {\n \"Name\": p,\n \"Type\": p_type,\n \"Default\": str(params[p][0]),\n \"Min\": str(params[p][1]),\n \"Max\": str(params[p][2]),\n }\n ET.SubElement(route, \"ParamList\", **kw)\n\n def export_update(self, root):\n tree = ET.ElementTree(root)\n ET.indent(tree, space=\"\\t\", level=0)\n if self.export == \"XML\":\n self.save_xml(tree, root)\n elif self.export == \"JSON\":\n self.save_json(root)\n elif self.export == True:\n self.save_xml(tree, root)\n self.save_json(root)\n\n def save_xml(self, tree, root):\n tree.write(self.patch_filepath + \".xml\")\n print(f\"Exported OSCMap to {self.patch_filepath}.xml\")\n\n def save_json(self, xml_root):\n # TODO: params should be `params: []` and not `param: {}, param: {}, ...`\n json_dict = self.xml_to_json(\n ET.tostring(xml_root, encoding=\"utf8\", method=\"xml\")\n )\n with open(self.patch_filepath + \".json\", \"w\") as f:\n f.write(json_dict)\n print(f\"Exported OSCMap to {self.patch_filepath}.json\")\n\n def etree_to_dict(self, t):\n tag = self.pascal_to_camel(t.tag)\n d = {tag: {} if t.attrib else None}\n children = list(t)\n if children:\n dd = {}\n for dc in map(self.etree_to_dict, children):\n for k, v in dc.items():\n try:\n dd[k].append(v)\n except KeyError:\n dd[k] = [v]\n d = {tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}}\n if t.attrib:\n d[tag].update((self.pascal_to_camel(k), v) for k, v in t.attrib.items())\n if t.text:\n text = t.text.strip()\n if children or t.attrib:\n if text:\n d[tag][\"#text\"] = text\n else:\n d[tag] = text\n return d\n\n def xml_to_json(self, xml_str):\n e = ET.ElementTree(ET.fromstring(xml_str))\n return json.dumps(self.etree_to_dict(e.getroot()), indent=4)\n\n def update(self):\n for k, v in self.dict[\"send\"].items():\n if \"updater\" in v:\n ret = v[\"updater\"]()\n # v['updater']()\n for k, v in self.dict[\"receive\"].items():\n v[\"updater\"]()\n\n def __call__(self, *args: Any, **kwds: Any) -> Any:\n self.update()\n
"},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap.add_receive_list_to_osc_map","title":"add_receive_list_to_osc_map(func, kwargs)
","text":"TODO: Should this support list[float] only, or list[int] list[str] etc?
Source code in src/tolvera/osc/oscmap.py
def add_receive_list_to_osc_map(self, func, kwargs):\n \"\"\"\n TODO: Should this support list[float] only, or list[int] list[str] etc?\n \"\"\"\n f = self.map_func_to_dict(func, kwargs)\n assert (\n len(f[\"params\"]) == 1\n ), \"receive_list can only receive one param (list[float])\"\n print(f)\n hint = f[\"hints\"][list(f[\"params\"].keys())[0]]\n assert (\n hint == list[float]\n ), \"receive_list can only receive list[float], found \" + str(hint)\n f[\"updater\"] = OSCReceiveListUpdater(\n self.osc, f[\"address\"], f=func, count=kwargs[\"count\"]\n )\n f[\"type\"] = \"list\"\n f[\"length\"] = kwargs[\"length\"]\n self.dict[\"receive\"][f[\"name\"]] = f\n if self.export is not None:\n self.export_dict()\n return f\n
"},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap.export_dict","title":"export_dict()
","text":"Save the OSCMap dict as XML
Source code in src/tolvera/osc/oscmap.py
def export_dict(self):\n \"\"\"\n Save the OSCMap dict as XML\n \"\"\"\n client_ip, client_port = self.osc.client_names[self.client_name]\n # TODO: This should be defined in the OSCMap dict / on init\n metadata = {\n \"HostIP\": self.osc.host,\n \"HostPort\": str(self.osc.port),\n \"ClientName\": self.client_name,\n \"ClientIP\": client_ip,\n \"ClientPort\": str(client_port),\n }\n root = ET.Element(\"OpenSoundControlSchema\")\n metadata_element = ET.SubElement(root, \"Metadata\", **metadata)\n sends = self.dict[\"send\"]\n receives = self.dict[\"receive\"]\n for io in [\"Send\", \"Receive\"]:\n ET.SubElement(root, io)\n for io in [\"send\", \"receive\"]:\n for name in self.dict[io]:\n f = self.dict[io][name]\n if f[\"type\"] == \"args\":\n self.xml_add_args_params(root, name, io, f)\n elif f[\"type\"] == \"list\":\n self.xml_add_list_param(root, name, io, f)\n elif f[\"type\"] == \"kwargs\":\n raise NotImplementedError(\"kwargs not implemented yet\")\n self.export_update(root)\n
"},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap.receive_kwargs","title":"receive_kwargs(**kwargs)
","text":"Same as receive_args but with named params
Source code in src/tolvera/osc/oscmap.py
def receive_kwargs(self, **kwargs):\n \"\"\"\n Same as receive_args but with named params\n \"\"\"\n raise NotImplementedError(\"receive_kwargs not implemented yet\")\n
"},{"location":"reference/tolvera/osc/oscmap/#tolvera.osc.oscmap.OSCMap.receive_list_with_idx","title":"receive_list_with_idx(name, receiver, idx_len, vec_len, attr=None)
","text":"Create an OSC list handler that assumes that the first idx_len
values are indices into some struct being modified by a receiver function, and the rest are args as a list, i.e. /name idx0 idx1 ... idxN arg0 arg1 ... argM ... receiver((idx0 idx1 ... idxN), args) Intended as a utility function to be used by external classes where it's not possible to use a decorator like receive_list
.
Source code in src/tolvera/osc/oscmap.py
def receive_list_with_idx(\n self, name: str, receiver, idx_len: int, vec_len: int, attr=None\n):\n \"\"\"\n Create an OSC list handler that assumes that the first `idx_len` values are indices into some struct being modified by a receiver function, and the rest are args as a list, i.e.\n /name idx0 idx1 ... idxN arg0 arg1 ... argM\n ...\n receiver((idx0 idx1 ... idxN), args)\n Intended as a utility function to be used by external classes where it's not possible to use a decorator like `receive_list`.\n \"\"\"\n\n def handler(vector: list[float]):\n arg_len = len(vector[idx_len:])\n assert (\n arg_len == vec_len\n ), f\"len(args) != len(list) ({arg_len} != {vec_len})\"\n if idx_len:\n indices = tuple([int(v) for v in vector[:idx_len]])\n if attr is None:\n receiver(indices, vector[idx_len:])\n else:\n receiver(indices, attr, vector[idx_len:])\n else:\n if attr is None:\n receiver(vector)\n else:\n receiver(attr, vector)\n\n kwargs = {\n \"vector\": (0, 0, 1),\n \"length\": vec_len + idx_len,\n \"count\": 1,\n \"name\": name,\n }\n self.receive_list(**kwargs)(handler)\n
"},{"location":"reference/tolvera/osc/pd/","title":"Pd","text":""},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher","title":"PdPatcher
","text":"Source code in src/tolvera/osc/pd.py
class PdPatcher:\n def __init__(\n self,\n osc,\n client_name=\"client\",\n filepath=\"osc_controls\",\n x=0.0,\n y=0.0,\n w=1600.0,\n h=900.0,\n net_or_udp=\"udp\",\n bela=False,\n ) -> None:\n self.x, self.y, self.w, self.h = x, y, w, h\n self.patch_objects = [f\"#N canvas {x} {y} {w} {h} 12;\\n\"]\n self.patch_connections = []\n self.types = {\n \"object\": \"obj\",\n \"message\": \"msg\",\n \"number\": \"floatatom\",\n \"symbol\": \"symbolatom\",\n \"toggle\": \"toggle\",\n \"slider\": \"vslider\",\n \"bang\": \"bng\",\n \"comment\": \"text\",\n }\n self.patch_ids = {}\n self.osc = osc\n self.client_name = client_name\n self.client_address, self.client_port = self.osc.client_names[self.client_name]\n self.filepath = filepath\n self.net_or_udp = net_or_udp\n self.bela = bela\n self.init()\n\n \"\"\"\n init\n \"\"\"\n\n def init(self):\n self.w = 5.5 # default width (scaling factor)\n self.h = 27.0 # default height (pixels)\n self.line = 300 # default [line] (timed ramp generator) time in milliseconds\n self.param_width = 70\n self.s_x, self.s_y = 30, 30 # sends insertion point\n self.r_x, self.r_y = 30, 530 # receives\u00a0insertion point\n self.comment(\"Pd \u2192 Python\", self.s_x, self.s_y)\n self.comment(\"===========\", self.s_x, self.s_y + self.h / 2)\n self.patch_ids[\"send\"] = self.osc_send(\n self.osc.host, self.osc.port, self.s_x, self.s_y + self.h * 2\n )\n self.comment(\"Python \u2192 Pd\", self.r_x, self.r_y)\n self.comment(\"===========\", self.r_x, self.r_y + self.h / 2)\n self.patch_ids[\"receive\"] = self.osc_receive(\n self.client_port, self.r_x, self.r_y + self.h * 2\n )\n self.s_x += 300\n self.r_x += 300\n if self.bela:\n self.create_bela_main()\n self.save(self.filepath)\n\n def create_bela_main(self):\n if self.filepath.startswith(\"pd/\"):\n abstraction = self.filepath[3:]\n with open(\"pd/_main.pd\", \"w\") as f:\n f.write(f\"#N canvas {self.x} {self.y} {self.w} {self.h} 12;\\n\")\n f.write(f\"#X obj {30} {30} {abstraction};\\n\")\n\n \"\"\"\n basic objects\n \"\"\"\n\n def box(self, box_type, x, y, box_text):\n self.patch_objects.append(f\"#X {box_type} {x} {y} {box_text};\\n\")\n return self.get_last_id()\n\n def object(self, obj, x, y):\n return self.box(\"obj\", x, y, obj)\n\n def msg(self, msg, x, y):\n return self.box(\"msg\", x, y, msg)\n\n def comment(self, text, x, y):\n return self.box(\"text\", x, y, text)\n\n def number(self, x, y):\n return self.box(\"floatatom\", x, y, f\"5 0 0 0 - - - 0\")\n\n \"\"\"\n connections\n \"\"\"\n\n def connect(self, a_id, a_outlet, b_id, b_inlet):\n self.patch_connections.append(\n f\"#X connect {a_id} {a_outlet} {b_id} {b_inlet};\\n\"\n )\n\n \"\"\"\n osc send/receive\n \"\"\"\n\n def osc_send(self, host, port, x, y, send_rate_limit=100):\n loadbang_id = self.object(\"loadbang\", x, y)\n y += self.h\n connect_id = self.msg(f\"connect {host} {port}\", x, y)\n y += self.h\n disconnect_id = self.msg(\"disconnect\", x + 10, y)\n metro_id = self.object(f\"metro {send_rate_limit}\", x + 100, y)\n y += self.h\n send_rate_id = self.object(\"s rate\", x + 100, y)\n y += self.h\n receive_id = self.object(\"r send.to.iipyper\", x + 10, y)\n y += self.h\n packOSC_id = self.object(\"packOSC\", x + 10, y)\n y += self.h\n send_type = \"netsend -u\" if self.net_or_udp == \"net\" else \"udpsend\"\n send_id = self.object(send_type, x, y)\n y += self.h\n status_id = self.number(x, y)\n print_id = self.object(\"print reply.from.netreceive\", x + 40, y)\n # loadbang->connect->send->print\n self.connect(loadbang_id, 0, connect_id, 0)\n self.connect(connect_id, 0, send_id, 0)\n self.connect(send_id, 0, status_id, 0)\n self.connect(send_id, 1, print_id, 0)\n # loadbang->metro->send_rate\n self.connect(loadbang_id, 0, metro_id, 0)\n self.connect(metro_id, 0, send_rate_id, 0)\n # disconnect->send\n self.connect(disconnect_id, 0, send_id, 0)\n # receive->packOSC->send\n self.connect(receive_id, 0, packOSC_id, 0)\n self.connect(packOSC_id, 0, send_id, 0)\n return send_id\n\n def osc_receive(self, port, x, y):\n receive_type = (\n f\"netreceive -u {port}\"\n if self.net_or_udp == \"net\"\n else f\"udpreceive {port}\"\n )\n receive_id = self.object(receive_type, x, y)\n y += self.h\n unpackOSC_id = self.object(\"unpackOSC\", x, y)\n y += self.h\n print_id = self.object(\"print receive.from.iipyper\", x + 20, y)\n y += self.h\n s_receive_id = self.object(\"s receive.from.iipyper\", x, y)\n self.connect(receive_id, 0, unpackOSC_id, 0)\n self.connect(unpackOSC_id, 0, s_receive_id, 0)\n self.connect(unpackOSC_id, 0, print_id, 0)\n return self.get_last_id()\n\n \"\"\"\n osc send/receive args/list\n \"\"\"\n\n def send_args_func(self, f):\n hints = typing.get_type_hints(f[\"f\"])[\"return\"].__args__\n f_p = f[\"params\"]\n params = []\n if len(f_p) == 0:\n self.osc_receive_msg(self.r_x, self.r_y, f[\"address\"])\n else:\n for k, p in f_p.items():\n p_def, p_min, p_max = f_p[k][0], f_p[k][1], f_p[k][2]\n params.append(\n {\n \"label\": k,\n \"data\": hints[k].__name__,\n \"min_val\": p_min,\n \"size\": p_max - p_min,\n }\n )\n self.osc_receive_with_controls(self.r_x, self.r_y, f[\"address\"], params)\n self.r_x += max(\n len(params) * self.param_width + 100.0, len(f[\"address\"]) * 15.0 + 25.0\n )\n self.save(self.filepath)\n\n def send_list_func(self, f):\n self.osc_receive_list(self.r_x, self.r_y, f[\"address\"], f[\"params\"])\n self.r_x += len(f[\"address\"]) * 15.0 + 25.0\n self.save(self.filepath)\n\n def receive_args_func(self, f):\n hints = typing.get_type_hints(f[\"f\"])\n f_p = f[\"params\"]\n params = []\n if len(f_p) == 0:\n self.osc_send_msg(self.s_x, self.s_y, f[\"address\"])\n else:\n for k, p in f_p.items():\n # TODO: handle strings\n if isinstance(p, str):\n continue\n p_def, p_min, p_max = f_p[k][0], f_p[k][1], f_p[k][2]\n params.append(\n {\n \"label\": k,\n \"data\": hints[k].__name__,\n \"min_val\": p_min,\n \"size\": p_max - p_min,\n }\n )\n self.osc_send_with_controls(self.s_x, self.s_y, f[\"address\"], params)\n self.s_x += max(\n len(params) * self.param_width + 100.0, len(f[\"address\"]) * 15.0 + 25.0\n )\n self.save(self.filepath)\n\n def receive_list_func(self, f):\n self.osc_send_list(self.s_x, self.s_y, f[\"address\"], f[\"params\"])\n self.s_x += len(f[\"address\"]) * 15.0 + 25.0\n self.save(self.filepath)\n\n \"\"\"\n osc send/receive no args/list (msg)\n \"\"\"\n\n def osc_receive_msg(self, x, y, path):\n \"\"\"\n does this even make sense?\n \"\"\"\n receive_id = self.msg(\"r receive.from.iipyper\", x, y)\n msg_id = self.comment(path, x, y)\n self.connect(receive_id, 0, msg_id, 0)\n return msg_id\n\n def osc_send_msg(self, x, y, path):\n msg_id = self.msg(path, x, y + 225 + self.h)\n send_id = self.object(\"s send.to.iipyper\", x, y + 250 + self.h)\n self.connect(msg_id, 0, send_id, 0)\n return msg_id\n\n \"\"\"\n osc send/receive args with line, slider, rate-limiting, and change detection\n \"\"\"\n\n def osc_receive_with_controls(self, x, y, path, parameters):\n \"\"\"\n TODO: Does [route] need to be broken down into individual subpaths?\n \"\"\"\n\n # [comment path]\n y_off = 0\n path_comment_id = self.comment(path, x, y + y_off)\n\n # [r receive]\n y_off += self.h\n receive_id = self.object(\"r receive.from.iipyper\", x, y + y_off)\n\n # [route /path]\n y_off += self.h\n route_id = self.object(\"routeOSC \" + path, x, y + y_off)\n\n # [unpack f f f ...] [print /path]\n y_off += self.h\n unpack_id = self.object(\"unpack \" + self._pack_args(parameters), x, y + y_off)\n unpack_width = len(parameters) * 7 + 60\n print_id = self.object(\"print \" + path, x + unpack_width + 10, y + y_off)\n\n # sliders\n y_off += 10\n slider_ids, float_ids, int_ids, tbf_ids, _y_off = self.sliders(\n x, y + y_off, parameters, \"receive\"\n )\n y_off += 160\n\n # [s arg_name]\n y_off += _y_off + 75\n send_ids = [\n self.object(\n \"s \" + self.path_to_snakecase(path) + \"_\" + p[\"label\"][0:3],\n x + i * self.param_width,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n\n # [comment params]\n y_off += 50\n param_comment_ids, _y_off = self.param_comments(x, y + y_off, parameters)\n\n # # connections\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, unpack_id, 0)\n self.connect(route_id, 0, print_id, 0)\n [self.connect(unpack_id, i, slider_ids[i], 0) for i in range(len(parameters))]\n [self.connect(float_ids[i], 0, send_ids[i], 0) for i in range(len(parameters))]\n\n return slider_ids, unpack_id\n\n def osc_send_with_controls(self, x, y, path, parameters):\n y_off = 0\n # [comment path]\n path_comment_id = self.comment(path, x, y + y_off)\n y_off += 15\n param_comment_ids, _y_off = self.param_comments(x, y + y_off, parameters)\n\n # [r path_arg_name]\n y_off += 35\n receive_ids = [\n self.object(\n \"r \" + self.path_to_snakecase(path) + \"_\" + p[\"label\"][0:3],\n x + i * self.param_width,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n y_off += 30\n\n # sliders\n slider_ids, slider_float_ids, int_ids, tbf_ids, _y_off = self.sliders(\n x, y + y_off, parameters, \"send\"\n )\n y_off += self.h * 3 # line\n y_off += _y_off + 25\n y_off += 225\n\n pack_id = -1\n out_id = -1\n # [pack $1 $2 $3 ...]\n if len(parameters) > 1:\n pack_id = self.object(\"pack \" + self._pack_args(parameters), x, y + y_off)\n out_id = pack_id\n\n # [msg /path $1 $2 $3 ...]\n y_off += 25\n msg_args = self._msg_args(parameters)\n msg_id = self.msg(path + \" \" + msg_args, x, y + y_off)\n out_id = msg_id if len(parameters) == 1 else out_id\n # [s send]\n y_off += 25\n send_id = self.object(\"s send.to.iipyper\", x, y + y_off)\n\n # connections\n for i in range(len(parameters)):\n rcv = receive_ids[i]\n slider = slider_ids[i]\n slider_float = slider_float_ids[i]\n int_id = int_ids[i]\n tbf_id = tbf_ids[i]\n\n self.connect(rcv, 0, slider[0], 0)\n self.connect(rcv, 0, slider[1], 0)\n if int_id == -1 and tbf_id == -1: # if no int or tbf\n self.connect(slider_float, 0, out_id, 0)\n elif int_id != -1 and tbf_id == -1: # if int but no tbf\n self.connect(slider_float, 0, out_id, 0)\n elif int_id == -1 and tbf_id != -1: # if tbf but no int\n self.connect(tbf_id, 0, out_id, 0)\n self.connect(tbf_id, 1, pack_id, i) if pack_id != -1 else None\n elif int_id != -1 and tbf_id != -1: # if both int and tbf\n self.connect(tbf_id, 0, out_id, 0)\n self.connect(tbf_id, 1, pack_id, i) if pack_id != -1 else None\n\n self.connect(pack_id, 0, msg_id, 0) if pack_id != -1 else None\n self.connect(msg_id, 0, send_id, 0)\n return slider_ids, pack_id, msg_id\n\n \"\"\"\n sliders\n \"\"\"\n\n def sliders(self, x, y, sliders, io=None):\n assert io is not None, 'io must be \"send\" or \"receive\"'\n \"\"\"\n sliders = [\n { 'label': 'x', data: 'float', min_val: 0.0, size: 0.0 },\n ]\n \"\"\"\n slider_ids = []\n float_ids = []\n int_ids = []\n tbf_ids = []\n y_off = 0\n send_rate_id = self.object(\"r rate\", x - 50, y + 155 + self.h * 3)\n for i, s in enumerate(sliders):\n y_off = 0\n x_i = x + (i * self.param_width)\n y_off += self.h\n slider_id, int_id, float_id, tbf_id = self.slider(\n send_rate_id,\n x_i,\n y + y_off,\n s[\"min_val\"],\n s[\"size\"],\n float=s[\"data\"] == \"float\",\n io=io if i > 0 else \"skip\",\n )\n slider_ids.append(slider_id)\n float_ids.append(float_id)\n int_ids.append(int_id)\n tbf_ids.append(tbf_id)\n return slider_ids, float_ids, int_ids, tbf_ids, y_off\n\n def slider(self, send_rate_id, x, y, min_val, size, float=False, io=None):\n assert io is not None, 'io must be \"send\" or \"receive\"'\n bang_id = self.object(\"bng\", x, y)\n y += self.h\n msg_id = self.msg(f\"{self.line}\", x, y)\n y += self.h\n line_id = self.object(f\"line 0 {self.line}\", x, y)\n y += self.h\n slider_id = self.box(\n \"obj\",\n x,\n y,\n f\"vsl 20 120 {min_val} {min_val+size} 0 0 empty empty empty 0 -9 0 12 #fcfcfc #000000 #000000 0 1\",\n )\n self.connect(bang_id, 0, msg_id, 0)\n self.connect(msg_id, 0, line_id, 1)\n self.connect(line_id, 0, slider_id, 0)\n y += 120 + 8\n int_id = -1\n tbf_id = -1\n float_id = -1\n if float == False and io == \"send\":\n y, change_id, tbf_id = self.send_rate_limit_int(\n slider_id, send_rate_id, x, y\n )\n elif float == False and io != \"send\":\n y, change_id = self.receive_rate_limit_int(slider_id, send_rate_id, x, y)\n elif float == True and io == \"send\":\n y, change_id, tbf_id = self.send_rate_limit_float(\n slider_id, send_rate_id, x, y\n )\n elif float == True and io != \"send\":\n y, change_id = self.recieve_rate_limit_float(slider_id, send_rate_id, x, y)\n return (line_id, bang_id), int_id, change_id, tbf_id\n\n def send_rate_limit_int(self, slider_id, send_rate_id, x, y):\n # int -> number -> t b f\n int_id = self.object(\"int\", x, y)\n y += self.h\n float_id = self.number(x, y)\n y += self.h\n zl_id = self.object(\"zl reg\", x, y)\n y += self.h\n change_id = self.object(\"change\", x, y)\n y += self.h\n tbf_id = self.object(\"t b f\", x, y)\n self.connect(slider_id, 0, int_id, 0)\n self.connect(int_id, 0, float_id, 0)\n self.connect(float_id, 0, zl_id, 1)\n self.connect(send_rate_id, 0, zl_id, 0)\n self.connect(zl_id, 0, change_id, 0)\n self.connect(change_id, 0, tbf_id, 0)\n return y, change_id, tbf_id\n\n def receive_rate_limit_int(self, slider_id, send_rate_id, x, y):\n # int -> number\n int_id = self.object(\"int\", x, y)\n y += self.h\n float_id = self.number(x, y)\n y += self.h\n zl_id = self.object(\"zl reg\", x, y)\n y += self.h\n change_id = self.object(\"change\", x, y)\n self.connect(slider_id, 0, int_id, 0)\n self.connect(int_id, 0, float_id, 0)\n self.connect(float_id, 0, zl_id, 1)\n self.connect(send_rate_id, 0, zl_id, 0)\n self.connect(zl_id, 0, change_id, 0)\n return y, change_id\n\n def send_rate_limit_float(self, slider_id, send_rate_id, x, y):\n # number -> t b f\n float_id = self.number(x, y)\n y += self.h\n zl_id = self.object(\"zl reg\", x, y)\n y += self.h\n change_id = self.object(\"change\", x, y)\n y += self.h\n tbf_id = self.object(\"t b f\", x, y)\n self.connect(slider_id, 0, float_id, 0)\n self.connect(float_id, 0, zl_id, 1)\n self.connect(send_rate_id, 0, zl_id, 0)\n self.connect(zl_id, 0, change_id, 0)\n self.connect(change_id, 0, tbf_id, 0)\n return y, change_id, tbf_id\n\n def recieve_rate_limit_float(self, slider_id, send_rate_id, x, y):\n # number\n float_id = self.number(x, y)\n y += self.h\n zl_id = self.object(\"zl reg\", x, y)\n y += self.h\n change_id = self.object(\"change\", x, y)\n self.connect(slider_id, 0, float_id, 0)\n self.connect(float_id, 0, zl_id, 1)\n self.connect(send_rate_id, 0, zl_id, 0)\n self.connect(zl_id, 0, change_id, 0)\n return y, change_id\n\n \"\"\"\n comments\n \"\"\"\n\n def param_comments(self, x, y, params):\n comment_ids = []\n y_off = 0\n for i, p in enumerate(params):\n y_off = 0\n x_i = x + (i * self.param_width)\n p_max = (\n p[\"min_val\"] + p[\"size\"]\n if p[\"data\"] == \"float\"\n else p[\"min_val\"] + p[\"size\"] - 1\n )\n comment_id1 = self.comment(f'{p[\"label\"]}', x_i, y)\n y_off += 15\n comment_id2 = self.comment(\n f'{p[\"data\"][0]} {p[\"min_val\"]} {p_max}', x_i, y + y_off\n )\n comment_ids.append(comment_id1)\n comment_ids.append(comment_id2)\n return comment_ids, y_off\n\n \"\"\"\n lists\n \"\"\"\n\n def osc_send_list(self, x, y, path, params):\n \"\"\"\n [comment] path, list name, params\n [r] path\n [list prepend path]\n [list trim]\n [s send.to.iipyper]\n \"\"\"\n y_off = 0\n self.comment(path, x, y)\n y_off += 15\n l = list(params.items())[0]\n self.comment(f\"{l[0]}\", x, y + y_off)\n y_off += 15\n self.comment(f\"l {l[1][1]} {l[1][2]}\", x, y + y_off)\n y_off += self.h\n receive_id = self.object(f\"r {self.path_to_snakecase(path)}\", x, y + y_off)\n y_off += self.h\n prepend_id = self.object(f\"list prepend {path}\", x, y + y_off)\n y_off += self.h\n trim_id = self.object(f\"list trim\", x, y + y_off)\n y_off += self.h\n send_id = self.object(f\"s send.to.iipyper\", x, y + y_off)\n self.connect(receive_id, 0, prepend_id, 0)\n self.connect(prepend_id, 0, trim_id, 0)\n self.connect(trim_id, 0, send_id, 0)\n\n def osc_receive_list(self, x, y, path, params):\n \"\"\"\n [comment] path\n [r receive.from.iipyper]\n [routeOSC path]\n [s path]\n [comment] params\n \"\"\"\n y_off = 0\n self.comment(path, x, y)\n y_off += self.h\n receive_id = self.object(f\"r receive.from.iipyper\", x, y + y_off)\n y_off += self.h\n route_id = self.object(f\"routeOSC {path}\", x, y + y_off)\n y_off += self.h\n send_id = self.object(f\"s {self.path_to_snakecase(path)}\", x, y + y_off)\n y_off += self.h\n l = list(params.items())[0]\n self.comment(f\"{l[0]}\", x, y + y_off)\n y_off += 15\n self.comment(f\"l {l[1][1]} {l[1][2]}\", x, y + y_off)\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, send_id, 0)\n\n \"\"\"\n utils\n \"\"\"\n\n def get_last_id(self):\n return len(self.patch_objects) - 2\n\n def _pack_args(self, args):\n arg_types = []\n for a in args:\n match a[\"data\"]:\n case \"int\":\n arg_types.append(\"f\")\n case \"float\":\n arg_types.append(\"f\")\n case \"string\":\n arg_types.append(\"s\")\n return \" \".join(arg_types)\n\n def _msg_args(self, args):\n return \" \".join([\"\\$\" + str(i + 1) for i in range(len(args))])\n\n def path_to_snakecase(self, path):\n return path.replace(\"/\", \"_\")[1:] # +'_'+label[0:3]\n\n \"\"\"\n save/load\n \"\"\"\n\n def save(self, name):\n with open(name + \".pd\", \"w\") as f:\n [f.write(o) for o in self.patch_objects]\n [f.write(c) for c in self.patch_connections]\n\n def load(self, name):\n with open(name + \".pd\", \"r\") as f:\n for line in f:\n if f.startswith(\"#X connect\"):\n self.patch_connections.append(f)\n else:\n self.patch_objects.append(f)\n
"},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher.osc_receive_list","title":"osc_receive_list(x, y, path, params)
","text":"[comment] path [r receive.from.iipyper] [routeOSC path] s path params
Source code in src/tolvera/osc/pd.py
def osc_receive_list(self, x, y, path, params):\n \"\"\"\n [comment] path\n [r receive.from.iipyper]\n [routeOSC path]\n [s path]\n [comment] params\n \"\"\"\n y_off = 0\n self.comment(path, x, y)\n y_off += self.h\n receive_id = self.object(f\"r receive.from.iipyper\", x, y + y_off)\n y_off += self.h\n route_id = self.object(f\"routeOSC {path}\", x, y + y_off)\n y_off += self.h\n send_id = self.object(f\"s {self.path_to_snakecase(path)}\", x, y + y_off)\n y_off += self.h\n l = list(params.items())[0]\n self.comment(f\"{l[0]}\", x, y + y_off)\n y_off += 15\n self.comment(f\"l {l[1][1]} {l[1][2]}\", x, y + y_off)\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, send_id, 0)\n
"},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher.osc_receive_msg","title":"osc_receive_msg(x, y, path)
","text":"does this even make sense?
Source code in src/tolvera/osc/pd.py
def osc_receive_msg(self, x, y, path):\n \"\"\"\n does this even make sense?\n \"\"\"\n receive_id = self.msg(\"r receive.from.iipyper\", x, y)\n msg_id = self.comment(path, x, y)\n self.connect(receive_id, 0, msg_id, 0)\n return msg_id\n
"},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher.osc_receive_with_controls","title":"osc_receive_with_controls(x, y, path, parameters)
","text":"TODO: Does [route] need to be broken down into individual subpaths?
Source code in src/tolvera/osc/pd.py
def osc_receive_with_controls(self, x, y, path, parameters):\n \"\"\"\n TODO: Does [route] need to be broken down into individual subpaths?\n \"\"\"\n\n # [comment path]\n y_off = 0\n path_comment_id = self.comment(path, x, y + y_off)\n\n # [r receive]\n y_off += self.h\n receive_id = self.object(\"r receive.from.iipyper\", x, y + y_off)\n\n # [route /path]\n y_off += self.h\n route_id = self.object(\"routeOSC \" + path, x, y + y_off)\n\n # [unpack f f f ...] [print /path]\n y_off += self.h\n unpack_id = self.object(\"unpack \" + self._pack_args(parameters), x, y + y_off)\n unpack_width = len(parameters) * 7 + 60\n print_id = self.object(\"print \" + path, x + unpack_width + 10, y + y_off)\n\n # sliders\n y_off += 10\n slider_ids, float_ids, int_ids, tbf_ids, _y_off = self.sliders(\n x, y + y_off, parameters, \"receive\"\n )\n y_off += 160\n\n # [s arg_name]\n y_off += _y_off + 75\n send_ids = [\n self.object(\n \"s \" + self.path_to_snakecase(path) + \"_\" + p[\"label\"][0:3],\n x + i * self.param_width,\n y + y_off + (0 if i % 2 == 0 else 25),\n )\n for i, p in enumerate(parameters)\n ]\n\n # [comment params]\n y_off += 50\n param_comment_ids, _y_off = self.param_comments(x, y + y_off, parameters)\n\n # # connections\n self.connect(receive_id, 0, route_id, 0)\n self.connect(route_id, 0, unpack_id, 0)\n self.connect(route_id, 0, print_id, 0)\n [self.connect(unpack_id, i, slider_ids[i], 0) for i in range(len(parameters))]\n [self.connect(float_ids[i], 0, send_ids[i], 0) for i in range(len(parameters))]\n\n return slider_ids, unpack_id\n
"},{"location":"reference/tolvera/osc/pd/#tolvera.osc.pd.PdPatcher.osc_send_list","title":"osc_send_list(x, y, path, params)
","text":"[comment] path, list name, params [r] path [list prepend path] [list trim] [s send.to.iipyper]
Source code in src/tolvera/osc/pd.py
def osc_send_list(self, x, y, path, params):\n \"\"\"\n [comment] path, list name, params\n [r] path\n [list prepend path]\n [list trim]\n [s send.to.iipyper]\n \"\"\"\n y_off = 0\n self.comment(path, x, y)\n y_off += 15\n l = list(params.items())[0]\n self.comment(f\"{l[0]}\", x, y + y_off)\n y_off += 15\n self.comment(f\"l {l[1][1]} {l[1][2]}\", x, y + y_off)\n y_off += self.h\n receive_id = self.object(f\"r {self.path_to_snakecase(path)}\", x, y + y_off)\n y_off += self.h\n prepend_id = self.object(f\"list prepend {path}\", x, y + y_off)\n y_off += self.h\n trim_id = self.object(f\"list trim\", x, y + y_off)\n y_off += self.h\n send_id = self.object(f\"s send.to.iipyper\", x, y + y_off)\n self.connect(receive_id, 0, prepend_id, 0)\n self.connect(prepend_id, 0, trim_id, 0)\n self.connect(trim_id, 0, send_id, 0)\n
"},{"location":"reference/tolvera/osc/update/","title":"Update","text":""},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCReceiveListUpdater","title":"OSCReceiveListUpdater
","text":" Bases: ReceiveListUpdater
ReceiveListUpdater with an OSC handler
Source code in src/tolvera/osc/update.py
class OSCReceiveListUpdater(ReceiveListUpdater):\n \"\"\"\n ReceiveListUpdater with an OSC handler\n \"\"\"\n\n def __init__(self, osc, address: str, f, state=None, count=10, update=False):\n super().__init__(f, state, count, update)\n self.osc = osc\n self.address = address\n osc.add_handler(self.address, self.receive)\n\n def receive(self, address, *args):\n self.set(list(args[1:]))\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCReceiveUpdater","title":"OSCReceiveUpdater
","text":" Bases: ReceiveUpdater
ReceiveUpdater with an OSC handler
Source code in src/tolvera/osc/update.py
class OSCReceiveUpdater(ReceiveUpdater):\n \"\"\"\n ReceiveUpdater with an OSC handler\n \"\"\"\n\n def __init__(self, osc, address: str, f, state=None, count=10, update=False):\n super().__init__(f, state, count, update)\n self.osc = osc\n self.address = address\n osc.add_handler(self.address, self.receive)\n\n def receive(self, address, *args):\n # FIXME: ip:port/args\n \"\"\"\n v: first argument to the handler is the IP:port of the sender\n v: or you can use dispatcher.map directly\n and not set needs_reply_address=True\n j: can I get ip:port from osc itself?\n v: if you know the sender ahead of time yeah,\n but that lets you respond to different senders dynamically\n \"\"\"\n self.set(args[1:])\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCReceiveUpdater.receive","title":"receive(address, *args)
","text":"v: first argument to the handler is the IP:port of the sender v: or you can use dispatcher.map directly and not set needs_reply_address=True j: can I get ip:port from osc itself? v: if you know the sender ahead of time yeah, but that lets you respond to different senders dynamically
Source code in src/tolvera/osc/update.py
def receive(self, address, *args):\n # FIXME: ip:port/args\n \"\"\"\n v: first argument to the handler is the IP:port of the sender\n v: or you can use dispatcher.map directly\n and not set needs_reply_address=True\n j: can I get ip:port from osc itself?\n v: if you know the sender ahead of time yeah,\n but that lets you respond to different senders dynamically\n \"\"\"\n self.set(args[1:])\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCReceiveUpdaters","title":"OSCReceiveUpdaters
","text":"o = OSCReceiveUpdaters(osc, {\"/tolvera/particles/pos\": s.osc_set_pos, \"/tolvera/particles/vel\": s.osc_set_vel})
Source code in src/tolvera/osc/update.py
class OSCReceiveUpdaters:\n \"\"\"\n o = OSCReceiveUpdaters(osc,\n {\"/tolvera/particles/pos\": s.osc_set_pos,\n \"/tolvera/particles/vel\": s.osc_set_vel})\n \"\"\"\n\n def __init__(self, osc, receives=None, count=10):\n self.osc = osc\n self.receives = []\n self.count = count\n if receives is not None:\n self.add_dict(receives, count=self.count)\n\n def add_dict(self, receives, count=None):\n if count is None:\n count = self.count\n {a: self.add(a, f, count=count) for a, f in receives.items()}\n\n def add(self, address, function, state=None, count=None, update=False):\n if count is None:\n count = self.count\n self.receives.append(\n OSCReceiveUpdater(self.osc, address, function, state, count, update)\n )\n\n def __call__(self):\n [r() for r in self.receives]\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCSend","title":"OSCSend
","text":"Non rate-limited OSC send
Source code in src/tolvera/osc/update.py
class OSCSend:\n \"\"\"\n Non rate-limited OSC send\n \"\"\"\n\n def __init__(self, osc, address: str, f, client=None):\n self.osc = osc\n self.address = address\n self.f = f\n self.client = client\n\n def __call__(self, *args):\n self.osc.send(self.address, *self.f(*args), client=self.client)\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCSendUpdater","title":"OSCSendUpdater
","text":"Rate-limited OSC send
Source code in src/tolvera/osc/update.py
class OSCSendUpdater:\n \"\"\"\n Rate-limited OSC send\n \"\"\"\n\n def __init__(self, osc, address: str, f, count=30, client=None):\n self.osc = osc\n self.address = address\n self.f = f\n self.count = count\n self.counter = 0\n self.client = client\n\n def __call__(self):\n self.counter += 1\n if self.counter >= self.count:\n self.osc.send(self.address, *self.f(), client=self.client)\n self.counter = 0\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCSendUpdaters","title":"OSCSendUpdaters
","text":"o = OSCSendUpdaters(osc, client=\"particles\", count=10, sends={ \"/tolvera/particles/get/pos/all\": s.osc_get_pos_all })
Source code in src/tolvera/osc/update.py
class OSCSendUpdaters:\n \"\"\"\n o = OSCSendUpdaters(osc, client=\"particles\", count=10,\n sends={\n \"/tolvera/particles/get/pos/all\": s.osc_get_pos_all\n })\n \"\"\"\n\n def __init__(self, osc, sends=None, count=10, client=None):\n self.osc = osc\n self.sends = []\n self.count = count\n self.client = client\n if sends is not None:\n self.add_dict(sends, self.count, self.client)\n\n def add_dict(self, sends, count=None, client=None):\n if count is None:\n count = self.count\n if client is None:\n client = self.client\n {a: self.add(a, f, count=count, client=client) for a, f in sends.items()}\n\n def add(self, address, function, state=None, count=None, update=False, client=None):\n if count is None:\n count = self.count\n if client is None:\n client = self.client\n self.sends.append(OSCSendUpdater(self.osc, address, function, count, client))\n\n def __call__(self):\n [s() for s in self.sends]\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.OSCUpdaters","title":"OSCUpdaters
","text":"o = OSCUpdaters(osc, client=\"boids\", count=10, receives={ \"/tolvera/boids/pos\": b.osc_set_pos, \"/tolvera/boids/vel\": b.osc_set_vel }, sends={ \"/tolvera/boids/pos/all\": b.osc_get_all_pos } )
Source code in src/tolvera/osc/update.py
class OSCUpdaters:\n \"\"\"\n o = OSCUpdaters(osc, client=\"boids\", count=10,\n receives={\n \"/tolvera/boids/pos\": b.osc_set_pos,\n \"/tolvera/boids/vel\": b.osc_set_vel\n },\n sends={\n \"/tolvera/boids/pos/all\": b.osc_get_all_pos\n }\n )\n \"\"\"\n\n def __init__(\n self,\n osc,\n sends=None,\n receives=None,\n send_count=60,\n receive_count=10,\n client=None,\n ):\n self.osc = osc\n self.client = client\n self.send_count = send_count\n self.receive_count = receive_count\n self.sends = OSCSendUpdaters(\n self.osc, count=self.send_count, client=self.client\n )\n self.receives = OSCReceiveUpdaters(self.osc, count=self.receive_count)\n if sends is not None:\n self.add_sends(sends)\n if receives is not None:\n self.add_receives(receives)\n\n def add_sends(self, sends, count=None, client=None):\n if count is None:\n count = self.send_count\n if client is None:\n client = self.client\n self.sends.add_dict(sends, count, client)\n\n def add_send(self, send, count=None, client=None):\n if count is None:\n count = self.send_count\n if client is None:\n client = self.client\n self.sends.add(send, client=client, count=count)\n\n def add_receives(self, receives, count=None):\n if count is None:\n count = self.receive_count\n self.receives.add_dict(receives, count=count)\n\n def add_receive(self, receive, count=None):\n if count is None:\n count = self.receive_count\n self.receives.add(receive, count=count)\n\n def __call__(self):\n self.sends()\n self.receives()\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveListUpdater","title":"ReceiveListUpdater
","text":"Decouples event handling from updating Updating is rate-limited by a counter Assumes a list[float] instead of *args
Source code in src/tolvera/osc/update.py
class ReceiveListUpdater:\n \"\"\"\n Decouples event handling from updating\n Updating is rate-limited by a counter\n Assumes a list[float] instead of *args\n \"\"\"\n\n def __init__(self, f, state=None, count=5, update=False):\n self.f = f\n self.count = count\n self.counter = 0\n self.update = update\n self.state = state\n\n def set(self, state):\n \"\"\"\n Set the Updater's state\n \"\"\"\n self.state = state\n self.update = True\n\n def __call__(self):\n \"\"\"\n Update the target function with internal state\n \"\"\"\n self.counter += 1\n if not (self.update and self.counter > self.count and self.state is not None):\n return\n self.f(self.state)\n self.counter = 0\n self.update = False\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveListUpdater.__call__","title":"__call__()
","text":"Update the target function with internal state
Source code in src/tolvera/osc/update.py
def __call__(self):\n \"\"\"\n Update the target function with internal state\n \"\"\"\n self.counter += 1\n if not (self.update and self.counter > self.count and self.state is not None):\n return\n self.f(self.state)\n self.counter = 0\n self.update = False\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveListUpdater.set","title":"set(state)
","text":"Set the Updater's state
Source code in src/tolvera/osc/update.py
def set(self, state):\n \"\"\"\n Set the Updater's state\n \"\"\"\n self.state = state\n self.update = True\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveUpdater","title":"ReceiveUpdater
","text":"Decouples event handling from updating Updating is rate-limited by a counter TODO: Rename to ReceiveArgsUpdater?
Source code in src/tolvera/osc/update.py
class ReceiveUpdater:\n \"\"\"\n Decouples event handling from updating\n Updating is rate-limited by a counter\n TODO: Rename to ReceiveArgsUpdater?\n \"\"\"\n\n def __init__(self, f, state=None, count=5, update=False):\n self.f = f\n self.count = count\n self.counter = 0\n self.update = update\n self.state = state\n\n def set(self, state):\n \"\"\"\n Set the Updater's state\n \"\"\"\n self.state = state\n self.update = True\n\n def __call__(self):\n \"\"\"\n Update the target function with internal state\n \"\"\"\n self.counter += 1\n if not (self.update and self.counter > self.count and self.state is not None):\n return\n self.ret = self.f(*self.state)\n \"\"\"\n if ret is not None:\n route = self.pascal_to_path(kwargs['name'])\n print('wrapper', route, ret, self.client_name)\n self.osc.return_to_sender_by_name((route, ret), self.client_name)\n \"\"\"\n self.counter = 0\n self.update = False\n return self.ret\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveUpdater.__call__","title":"__call__()
","text":"Update the target function with internal state
Source code in src/tolvera/osc/update.py
def __call__(self):\n \"\"\"\n Update the target function with internal state\n \"\"\"\n self.counter += 1\n if not (self.update and self.counter > self.count and self.state is not None):\n return\n self.ret = self.f(*self.state)\n \"\"\"\n if ret is not None:\n route = self.pascal_to_path(kwargs['name'])\n print('wrapper', route, ret, self.client_name)\n self.osc.return_to_sender_by_name((route, ret), self.client_name)\n \"\"\"\n self.counter = 0\n self.update = False\n return self.ret\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.ReceiveUpdater.set","title":"set(state)
","text":"Set the Updater's state
Source code in src/tolvera/osc/update.py
def set(self, state):\n \"\"\"\n Set the Updater's state\n \"\"\"\n self.state = state\n self.update = True\n
"},{"location":"reference/tolvera/osc/update/#tolvera.osc.update.Updater","title":"Updater
","text":"Rate-limited function call
Source code in src/tolvera/osc/update.py
class Updater:\n \"\"\"\n Rate-limited function call\n \"\"\"\n\n def __init__(self, f, count: int = 1):\n self.f = f\n self.count = int(count)\n self.counter = 0\n\n def __call__(self, *args, **kwargs):\n self.counter += 1\n if self.counter >= self.count:\n self.counter = 0\n return self.f(*args, **kwargs)\n return None\n
"},{"location":"reference/tolvera/vera/attractors/","title":"Attractors","text":"Inspired by https://github.com/williamgilpin/dysts
"},{"location":"reference/tolvera/vera/flock/","title":"Flock","text":"Flock behaviour based on the Boids algorithm.
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock","title":"Flock
","text":"Flock behaviour.
The flock operates via a species rule matrix, which is a 2D matrix of species rules, such that every species has a separate relationship with every other species including itself. As in the Boids algorithm, the rules are: - separate
: how much a particle should separate from its neighbours. - align
: how much a particle should align (match velocity) with its neighbours. - cohere
: how much a particle should cohere (move towards) its neighbours.
Taichi Boids implementation inspired by: https://forum.taichi-lang.cn/t/homework0-boids/563
Source code in src/tolvera/vera/flock.py
@ti.data_oriented\nclass Flock:\n \"\"\"Flock behaviour.\n\n The flock operates via a species rule matrix, which is a 2D matrix of species \n rules, such that every species has a separate relationship with every other \n species including itself. As in the Boids algorithm, the rules are:\n - `separate`: how much a particle should separate from its neighbours.\n - `align`: how much a particle should align (match velocity) with its neighbours.\n - `cohere`: how much a particle should cohere (move towards) its neighbours.\n\n Taichi Boids implementation inspired by:\n https://forum.taichi-lang.cn/t/homework0-boids/563\n \"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Flock behaviour.\n\n `flock_s` stores the species rule matrix. \n `flock_p` stores the rule values per particle, and the number of neighbours.\n `flock_dist` stores the distance between particles.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\"MAX_RADIUS\": (ti.f32, 300.0)})\n self.tv.s.flock_s = {\n \"state\": {\n \"separate\": (ti.f32, 0.01, 1.0),\n \"align\": (ti.f32, 0.01, 1.0),\n \"cohere\": (ti.f32, 0.01, 1.0),\n \"radius\": (ti.f32, 0.01, 1.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.tv.s.flock_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n self.tv.s.flock_dist = {\n \"state\": {\n \"dist\": (ti.f32, 0.0, self.tv.x * 2),\n \"dist_wrap\": (ti.f32, 0.0, self.tv.x * 2),\n },\n \"shape\": (self.tv.pn, self.tv.pn),\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n\n def randomise(self):\n \"\"\"Randomise the Flock behaviour.\"\"\"\n self.tv.s.flock_s.randomise()\n\n @ti.kernel\n def step(self, particles: ti.template(), weight: ti.f32):\n \"\"\"Step the Flock behaviour.\n\n Pairwise comparison is made and inactive particles are ignored. \n When the distance between two particles is less than the radius \n of the species, the particles are considered neighbours. \n\n The separation, alignment and cohesion are calculated for\n each particle and the velocity is updated accordingly.\n\n State is updated in `flock_p` and `flock_dist`.\n\n Args:\n particles (ti.template()): A template for the particles.\n weight (ti.f32): The weight of the Flock behaviour.\n \"\"\"\n n = particles.shape[0]\n for i in range(n):\n if particles[i].active == 0:\n continue\n p1 = particles[i]\n separate = ti.Vector([0.0, 0.0])\n align = ti.Vector([0.0, 0.0])\n cohere = ti.Vector([0.0, 0.0])\n nearby = 0\n species = self.tv.s.flock_s.struct()\n for j in range(n):\n if i == j and particles[j].active == 0:\n continue\n p2 = particles[j]\n species = self.tv.s.flock_s[p1.species, p2.species]\n dis_wrap = p1.dist_wrap(p2, self.tv.x, self.tv.y)\n dis_wrap_norm = dis_wrap.norm()\n if dis_wrap_norm < species.radius * self.CONSTS.MAX_RADIUS:\n separate += dis_wrap\n align += p2.vel\n cohere += p2.pos\n nearby += 1\n self.tv.s.flock_dist[i, j].dist = p1.dist(p2).norm()\n self.tv.s.flock_dist[i, j].dist_wrap = dis_wrap_norm\n if nearby > 0:\n separate = (\n separate / nearby * p1.active * ti.math.max(species.separate, 0.2)\n )\n align = align / nearby * p1.active * species.align\n cohere = (cohere / nearby - p1.pos) * p1.active * species.cohere\n vel = (separate + align + cohere).normalized()\n particles[i].vel += vel * weight\n particles[i].pos += particles[i].vel * p1.speed * p1.active * weight\n self.tv.s.flock_p[i] = self.tv.s.flock_p.struct(\n separate, align, cohere, nearby\n )\n\n def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Flock behaviour.\n\n Args:\n particles (Particles): Particles to step.\n weight (ti.f32, optional): The weight of the Flock behaviour. Defaults to 1.0.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock.__call__","title":"__call__(particles, weight=1.0)
","text":"Call the Flock behaviour.
Parameters:
Name Type Description Default particles
Particles
Particles to step.
required weight
f32
The weight of the Flock behaviour. Defaults to 1.0.
1.0
Source code in src/tolvera/vera/flock.py
def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Flock behaviour.\n\n Args:\n particles (Particles): Particles to step.\n weight (ti.f32, optional): The weight of the Flock behaviour. Defaults to 1.0.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the Flock behaviour.
flock_s
stores the species rule matrix. flock_p
stores the rule values per particle, and the number of neighbours. flock_dist
stores the distance between particles.
Parameters:
Name Type Description Default tolvera
Tolvera
A Tolvera instance.
required **kwargs
Keyword arguments (currently none).
{}
Source code in src/tolvera/vera/flock.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Flock behaviour.\n\n `flock_s` stores the species rule matrix. \n `flock_p` stores the rule values per particle, and the number of neighbours.\n `flock_dist` stores the distance between particles.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\"MAX_RADIUS\": (ti.f32, 300.0)})\n self.tv.s.flock_s = {\n \"state\": {\n \"separate\": (ti.f32, 0.01, 1.0),\n \"align\": (ti.f32, 0.01, 1.0),\n \"cohere\": (ti.f32, 0.01, 1.0),\n \"radius\": (ti.f32, 0.01, 1.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.tv.s.flock_p = {\n \"state\": {\n \"separate\": (ti.math.vec2, 0.0, 1.0),\n \"align\": (ti.math.vec2, 0.0, 1.0),\n \"cohere\": (ti.math.vec2, 0.0, 1.0),\n \"nearby\": (ti.i32, 0, self.tv.p.n - 1),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n self.tv.s.flock_dist = {\n \"state\": {\n \"dist\": (ti.f32, 0.0, self.tv.x * 2),\n \"dist_wrap\": (ti.f32, 0.0, self.tv.x * 2),\n },\n \"shape\": (self.tv.pn, self.tv.pn),\n \"osc\": (\"get\"),\n \"randomise\": False,\n }\n
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock.randomise","title":"randomise()
","text":"Randomise the Flock behaviour.
Source code in src/tolvera/vera/flock.py
def randomise(self):\n \"\"\"Randomise the Flock behaviour.\"\"\"\n self.tv.s.flock_s.randomise()\n
"},{"location":"reference/tolvera/vera/flock/#tolvera.vera.flock.Flock.step","title":"step(particles, weight)
","text":"Step the Flock behaviour.
Pairwise comparison is made and inactive particles are ignored. When the distance between two particles is less than the radius of the species, the particles are considered neighbours.
The separation, alignment and cohesion are calculated for each particle and the velocity is updated accordingly.
State is updated in flock_p
and flock_dist
.
Parameters:
Name Type Description Default particles
template
A template for the particles.
required weight
f32
The weight of the Flock behaviour.
required Source code in src/tolvera/vera/flock.py
@ti.kernel\ndef step(self, particles: ti.template(), weight: ti.f32):\n \"\"\"Step the Flock behaviour.\n\n Pairwise comparison is made and inactive particles are ignored. \n When the distance between two particles is less than the radius \n of the species, the particles are considered neighbours. \n\n The separation, alignment and cohesion are calculated for\n each particle and the velocity is updated accordingly.\n\n State is updated in `flock_p` and `flock_dist`.\n\n Args:\n particles (ti.template()): A template for the particles.\n weight (ti.f32): The weight of the Flock behaviour.\n \"\"\"\n n = particles.shape[0]\n for i in range(n):\n if particles[i].active == 0:\n continue\n p1 = particles[i]\n separate = ti.Vector([0.0, 0.0])\n align = ti.Vector([0.0, 0.0])\n cohere = ti.Vector([0.0, 0.0])\n nearby = 0\n species = self.tv.s.flock_s.struct()\n for j in range(n):\n if i == j and particles[j].active == 0:\n continue\n p2 = particles[j]\n species = self.tv.s.flock_s[p1.species, p2.species]\n dis_wrap = p1.dist_wrap(p2, self.tv.x, self.tv.y)\n dis_wrap_norm = dis_wrap.norm()\n if dis_wrap_norm < species.radius * self.CONSTS.MAX_RADIUS:\n separate += dis_wrap\n align += p2.vel\n cohere += p2.pos\n nearby += 1\n self.tv.s.flock_dist[i, j].dist = p1.dist(p2).norm()\n self.tv.s.flock_dist[i, j].dist_wrap = dis_wrap_norm\n if nearby > 0:\n separate = (\n separate / nearby * p1.active * ti.math.max(species.separate, 0.2)\n )\n align = align / nearby * p1.active * species.align\n cohere = (cohere / nearby - p1.pos) * p1.active * species.cohere\n vel = (separate + align + cohere).normalized()\n particles[i].vel += vel * weight\n particles[i].pos += particles[i].vel * p1.speed * p1.active * weight\n self.tv.s.flock_p[i] = self.tv.s.flock_p.struct(\n separate, align, cohere, nearby\n )\n
"},{"location":"reference/tolvera/vera/forces/","title":"Forces","text":"Force functions for particles.
This module contains functions for applying forces to particles. It includes functions for moving, attracting, repelling and gravitating particles. It also includes variations of these functions for specific species of particles.
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.attract","title":"attract(particles, pos, mass, radius)
","text":"Attract the particles to a position.
Parameters:
Name Type Description Default particles
template
Particles.
required pos
vec2
Attraction position.
required mass
f32
Attraction mass.
required radius
f32
Attraction radius.
required Source code in src/tolvera/vera/forces.py
@ti.kernel\ndef attract(particles: ti.template(), pos: ti.math.vec2, mass: ti.f32, radius: ti.f32):\n \"\"\"Attract the particles to a position.\n\n Args:\n particles (ti.template): Particles.\n pos (ti.math.vec2): Attraction position.\n mass (ti.f32): Attraction mass.\n radius (ti.f32): Attraction radius.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n particles.field[i].vel += attract_particle(p, pos, mass, radius)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.attract_particle","title":"attract_particle(p, pos, mass, radius)
","text":"Attract a particle to a position.
Parameters:
Name Type Description Default particles
Particle
Individual particle.
required pos
vec2
Attraction position.
required mass
f32
Attraction mass.
required radius
f32
Attraction radius.
required Returns:
Type Description vec2
ti.math.vec2: Attraction velocity.
Source code in src/tolvera/vera/forces.py
@ti.func\ndef attract_particle(\n p: Particle, pos: ti.math.vec2, mass: ti.f32, radius: ti.f32\n) -> ti.math.vec2:\n \"\"\"Attract a particle to a position.\n\n Args:\n particles (Particle): Individual particle.\n pos (ti.math.vec2): Attraction position.\n mass (ti.f32): Attraction mass.\n radius (ti.f32): Attraction radius.\n\n Returns:\n ti.math.vec2: Attraction velocity.\n \"\"\"\n target_distance = (pos - p.pos).norm()\n vel = ti.Vector([0.0, 0.0])\n if target_distance < radius:\n factor = (radius - target_distance) / radius\n vel = (pos - p.pos).normalized() * mass * factor\n return vel\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.attract_species","title":"attract_species(particles, pos, mass, radius, species)
","text":"Attract the particles of a given species to a position.
Parameters:
Name Type Description Default particles
template
Particles.
required pos
vec2
Attraction position.
required mass
f32
Attraction mass.
required radius
f32
Attraction radius.
required species
i32
Species index.
required Source code in src/tolvera/vera/forces.py
@ti.kernel\ndef attract_species(\n particles: ti.template(),\n pos: ti.math.vec2,\n mass: ti.f32,\n radius: ti.f32,\n species: ti.i32,\n):\n \"\"\"Attract the particles of a given species to a position.\n\n Args:\n particles (ti.template): Particles.\n pos (ti.math.vec2): Attraction position.\n mass (ti.f32): Attraction mass.\n radius (ti.f32): Attraction radius.\n species (ti.i32): Species index.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n if p.species != species:\n continue\n particles.field[i].vel += attract_particle(p, pos, mass, radius)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.gravitate","title":"gravitate(particles, G, radius)
","text":"Gravitate the particles.
Parameters:
Name Type Description Default particles
template
Particles.
required G
f32
Gravitational constant.
required radius
f32
Gravitational radius.
required Source code in src/tolvera/vera/forces.py
@ti.kernel\ndef gravitate(particles: ti.template(), G: ti.f32, radius: ti.f32):\n \"\"\"Gravitate the particles.\n\n Args:\n particles (ti.template): Particles.\n G (ti.f32): Gravitational constant.\n radius (ti.f32): Gravitational radius.\n \"\"\"\n for i, j in ti.ndrange(particles.field.shape[0], particles.field.shape[0]):\n if i == j:\n continue\n p1 = particles.field[i]\n p2 = particles.field[j]\n if (p2.pos - p1.pos).norm() > radius:\n continue\n particles.field[i].vel += gravitation(p1, p2, G)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.gravitate_species","title":"gravitate_species(particles, G, radius, species)
","text":"Gravitate the particles of a given species.
Parameters:
Name Type Description Default particles
template
Particles.
required G
f32
Gravitational constant.
required radius
f32
Gravitational radius.
required species
i32
Species index.
required Source code in src/tolvera/vera/forces.py
@ti.kernel\ndef gravitate_species(\n particles: ti.template(), G: ti.f32, radius: ti.f32, species: ti.i32\n):\n \"\"\"Gravitate the particles of a given species.\n\n Args:\n particles (ti.template): Particles.\n G (ti.f32): Gravitational constant.\n radius (ti.f32): Gravitational radius.\n species (ti.i32): Species index.\n \"\"\"\n for i, j in ti.ndrange(particles.field.shape[0], particles.field.shape[0]):\n if i == j:\n continue\n p1 = particles.field[i]\n p2 = particles.field[j]\n if p1.species != species or p2.species != species:\n continue\n if (p2.pos - p1.pos).norm() > radius:\n continue\n particles.field[i].vel += gravitation(p1, p2, G)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.gravitation","title":"gravitation(p1, p2, G)
","text":"Calculate the gravitational force between two particles.
Parameters:
Name Type Description Default p1
Particle
Particle 1.
required p2
Particle
Particle 2.
required G
f32
Gravitational constant.
required Returns:
Type Description vec2
ti.math.vec2: Gravitational force.
Source code in src/tolvera/vera/forces.py
@ti.func\ndef gravitation(p1: Particle, p2: Particle, G: ti.f32) -> ti.math.vec2:\n \"\"\"Calculate the gravitational force between two particles.\n\n Args:\n p1 (Particle): Particle 1.\n p2 (Particle): Particle 2.\n G (ti.f32): Gravitational constant.\n\n Returns:\n ti.math.vec2: Gravitational force.\n \"\"\"\n r = p2.pos - p1.pos\n distance = r.norm() + 1e-5\n force_direction = r.normalized()\n force_magnitude = G * p1.mass * p2.mass / (distance**2)\n force = force_direction * force_magnitude\n return force / p1.mass\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.move","title":"move(particles)
","text":"Move the particles.
Parameters:
Name Type Description Default particles
template
Particles.
required Source code in src/tolvera/vera/forces.py
@ti.kernel\ndef move(particles: ti.template()):\n \"\"\"Move the particles.\n\n Args:\n particles (ti.template): Particles.\n \"\"\"\n for i in range(particles.field.shape[0]):\n if particles.field[i].active == 0:\n continue\n p1 = particles.field[i]\n particles.field[i].pos += p1.vel * p1.speed * p1.active\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.noise","title":"noise(particles, weight)
","text":"Add noise to the particles.
Parameters:
Name Type Description Default particles
template
Particles.
required weight
f32
Noise weight.
required Source code in src/tolvera/vera/forces.py
@ti.kernel\ndef noise(particles: ti.template(), weight: ti.f32):\n \"\"\"Add noise to the particles.\n\n Args:\n particles (ti.template): Particles.\n weight (ti.f32): Noise weight.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n particles.field[i].vel += (ti.Vector([ti.random() - 0.5, ti.random() - 0.5]) * weight)\n particles.field[i].pos += p.vel * p.speed * p.active\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.repel","title":"repel(particles, pos, mass, radius)
","text":"Repel the particles from a position.
Parameters:
Name Type Description Default particles
template
Particles.
required pos
vec2
Repulsion position.
required mass
f32
Repulsion mass.
required radius
f32
Repulsion radius.
required Source code in src/tolvera/vera/forces.py
@ti.kernel\ndef repel(particles: ti.template(), pos: ti.math.vec2, mass: ti.f32, radius: ti.f32):\n \"\"\"Repel the particles from a position.\n\n Args:\n particles (ti.template): Particles.\n pos (ti.math.vec2): Repulsion position.\n mass (ti.f32): Repulsion mass.\n radius (ti.f32): Repulsion radius.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n particles.field[i].vel += repel_particle(p, pos, mass, radius)\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.repel_particle","title":"repel_particle(p, pos, mass, radius)
","text":"Repel a particle from a position.
Parameters:
Name Type Description Default p
Particle
Individual particle.
required pos
vec2
Repulsion position.
required mass
f32
Repulsion mass.
required radius
f32
Repulsion radius.
required Returns:
Type Description vec2
ti.math.vec2: Repulsion velocity.
Source code in src/tolvera/vera/forces.py
@ti.func\ndef repel_particle(\n p: Particle, pos: ti.math.vec2, mass: ti.f32, radius: ti.f32\n) -> ti.math.vec2:\n \"\"\"Repel a particle from a position.\n\n Args:\n p (Particle): Individual particle.\n pos (ti.math.vec2): Repulsion position.\n mass (ti.f32): Repulsion mass.\n radius (ti.f32): Repulsion radius.\n\n Returns:\n ti.math.vec2: Repulsion velocity.\n \"\"\"\n target_distance = (pos - p.pos).norm()\n vel = ti.Vector([0.0, 0.0])\n if target_distance < radius:\n factor = (target_distance - radius) / radius\n vel = (pos - p.pos).normalized() * mass * factor\n return vel\n
"},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.repel_species","title":"repel_species(particles, pos, mass, radius, species)
","text":"Repel the particles of a given species from a position.
Parameters:
Name Type Description Default particles
template
Particles.
required pos
vec2
Repulsion position.
required mass
f32
Repulsion mass.
required radius
f32
Repulsion radius.
required species
i32
Species index.
required Source code in src/tolvera/vera/forces.py
@ti.kernel\ndef repel_species(\n particles: ti.template(),\n pos: ti.math.vec2,\n mass: ti.f32,\n radius: ti.f32,\n species: ti.i32,\n):\n \"\"\"Repel the particles of a given species from a position.\n\n Args:\n particles (ti.template): Particles.\n pos (ti.math.vec2): Repulsion position.\n mass (ti.f32): Repulsion mass.\n radius (ti.f32): Repulsion radius.\n species (ti.i32): Species index.\n \"\"\"\n for i in range(particles.field.shape[0]):\n p = particles.field[i]\n if p.active == 0:\n continue\n if p.species != species:\n continue\n particles.field[i].vel += repel_particle(p, pos, mass, radius)\n
"},{"location":"reference/tolvera/vera/nca/","title":"Nca","text":"https://github.com/google/swissgl/blob/main/demo/NeuralCA.js
"},{"location":"reference/tolvera/vera/particle_life/","title":"Particle life","text":"Particle Life model.
"},{"location":"reference/tolvera/vera/particle_life/#tolvera.vera.particle_life.ParticleLife","title":"ParticleLife
","text":"Particle Life model.
The Particle Life model is a simple model of particle behaviour, where particles are either attracted or repelled by other particles, depending on their species. Popularised by Jeffrey Ventrella (Clusters), Tom Mohr and others:
https://www.ventrella.com/Clusters/ https://github.com/tom-mohr/particle-life-app
Source code in src/tolvera/vera/particle_life.py
@ti.data_oriented\nclass ParticleLife():\n \"\"\"Particle Life model.\n\n The Particle Life model is a simple model of particle behaviour, where\n particles are either attracted or repelled by other particles, depending\n on their species. Popularised by Jeffrey Ventrella (Clusters), Tom Mohr\n and others:\n\n https://www.ventrella.com/Clusters/\n https://github.com/tom-mohr/particle-life-app\n\n \"\"\"\n def __init__(self, tolvera, **kwargs) -> None:\n \"\"\"Initialise the Particle Life model.\n\n 'plife' stores the species rule matrix.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\n \"V\": (ti.f32, 0.25),\n })\n self.tv.s.plife = {\n \"state\": {\n \"attract\": (ti.f32, -.5, .5),\n \"radius\": (ti.f32, 100., 300.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"randomise\": True,\n }\n @ti.kernel\n def step(self, particles: ti.template(), weight: ti.f32):\n \"\"\"Step the Particle Life model.\n\n Args:\n particles (Particles.field): The particles to step.\n weight (ti.f32): The weight of the step.\n \"\"\"\n for i in range(particles.shape[0]):\n if particles[i].active == 0.: continue\n p1 = particles[i]\n fx, fy = 0., 0.\n for j in range(particles.shape[0]):\n if particles[j].active == 0.: continue\n p2 = particles[j]\n s = self.tv.s.plife[p1.species, p2.species]\n dx = p1.pos[0] - p2.pos[0]\n dy = p1.pos[1] - p2.pos[1]\n d = ti.sqrt(dx*dx + dy*dy)\n if 0. < d and d < s.radius:\n F = s.attract/d\n fx += F*dx\n fy += F*dy\n particles[i].vel = (particles[i].vel + ti.Vector([fx, fy])) * self.CONSTS.V * weight\n particles[i].pos += (particles[i].vel * p1.speed * p1.active * weight)\n def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Particle Life model.\n\n Args:\n particles (Particles): The particles to step.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/particle_life/#tolvera.vera.particle_life.ParticleLife.__call__","title":"__call__(particles, weight=1.0)
","text":"Call the Particle Life model.
Parameters:
Name Type Description Default particles
Particles
The particles to step.
required Source code in src/tolvera/vera/particle_life.py
def __call__(self, particles, weight: ti.f32 = 1.0):\n \"\"\"Call the Particle Life model.\n\n Args:\n particles (Particles): The particles to step.\n \"\"\"\n self.step(particles.field, weight)\n
"},{"location":"reference/tolvera/vera/particle_life/#tolvera.vera.particle_life.ParticleLife.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the Particle Life model.
'plife' stores the species rule matrix.
Parameters:
Name Type Description Default tolvera
Tolvera
A Tolvera instance.
required **kwargs
Keyword arguments (currently none).
{}
Source code in src/tolvera/vera/particle_life.py
def __init__(self, tolvera, **kwargs) -> None:\n \"\"\"Initialise the Particle Life model.\n\n 'plife' stores the species rule matrix.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n **kwargs: Keyword arguments (currently none).\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n self.CONSTS = CONSTS({\n \"V\": (ti.f32, 0.25),\n })\n self.tv.s.plife = {\n \"state\": {\n \"attract\": (ti.f32, -.5, .5),\n \"radius\": (ti.f32, 100., 300.0),\n },\n \"shape\": (self.tv.sn, self.tv.sn),\n \"randomise\": True,\n }\n
"},{"location":"reference/tolvera/vera/particle_life/#tolvera.vera.particle_life.ParticleLife.step","title":"step(particles, weight)
","text":"Step the Particle Life model.
Parameters:
Name Type Description Default particles
field
The particles to step.
required weight
f32
The weight of the step.
required Source code in src/tolvera/vera/particle_life.py
@ti.kernel\ndef step(self, particles: ti.template(), weight: ti.f32):\n \"\"\"Step the Particle Life model.\n\n Args:\n particles (Particles.field): The particles to step.\n weight (ti.f32): The weight of the step.\n \"\"\"\n for i in range(particles.shape[0]):\n if particles[i].active == 0.: continue\n p1 = particles[i]\n fx, fy = 0., 0.\n for j in range(particles.shape[0]):\n if particles[j].active == 0.: continue\n p2 = particles[j]\n s = self.tv.s.plife[p1.species, p2.species]\n dx = p1.pos[0] - p2.pos[0]\n dy = p1.pos[1] - p2.pos[1]\n d = ti.sqrt(dx*dx + dy*dy)\n if 0. < d and d < s.radius:\n F = s.attract/d\n fx += F*dx\n fy += F*dy\n particles[i].vel = (particles[i].vel + ti.Vector([fx, fy])) * self.CONSTS.V * weight\n particles[i].pos += (particles[i].vel * p1.speed * p1.active * weight)\n
"},{"location":"reference/tolvera/vera/reaction_diffusion/","title":"Reaction diffusion","text":"Inspired by https://github.com/taichi-dev/faster-python-with-taichi/blob/main/reaction_diffusion_taichi.py
"},{"location":"reference/tolvera/vera/slime/","title":"Slime","text":"Slime behaviour based on the Physarum polycephalum slime mould.
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime","title":"Slime
","text":"Slime behaviour based on the Physarum polycephalum slime mould.
The slime mould is a single-celled organism that exhibits complex behaviour such as foraging, migration, and decision-making. It is a popular model for emergent behaviour in nature-inspired computing.
The slime mould is simulated by a set of particles that move around the simulation space. The particles sense their environment and move in response to the sensed information. The particles leave a \"pheromone trail\" behind them, which evaporates over time. The particles can be of different species, which have different sensing and moving parameters.
Taichi Physarum implementation inspired by: https://github.com/taichi-dev/taichi/blob/master/python/taichi/examples/simulation/physarum.py
Source code in src/tolvera/vera/slime.py
@ti.data_oriented\nclass Slime:\n \"\"\"Slime behaviour based on the Physarum polycephalum slime mould.\n\n The slime mould is a single-celled organism that exhibits complex behaviour\n such as foraging, migration, and decision-making. It is a popular model for\n emergent behaviour in nature-inspired computing.\n\n The slime mould is simulated by a set of particles that move around the\n simulation space. The particles sense their environment and move in response\n to the sensed information. The particles leave a \"pheromone trail\" behind them,\n which evaporates over time. The particles can be of different species, which \n have different sensing and moving parameters.\n\n Taichi Physarum implementation inspired by:\n https://github.com/taichi-dev/taichi/blob/master/python/taichi/examples/simulation/physarum.py\n \"\"\"\n def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Slime behaviour.\n\n `slime_p` stores the particle state.\n `slime_s` stores the species state.\n `trail` is a Pixels instance that stores the pheromone trail.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n evaporate (ti.f32, optional): Evaporation rate. Defaults to 0.99.\n **kwargs: Keyword arguments.\n brightness (ti.f32, optional): Brightness of the pheromone trail. Defaults to 1.0.\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n brightness = kwargs.get(\"brightness\", 1.0)\n self.CONSTS = CONSTS(\n {\n \"SENSE_ANGLE\": (ti.f32, ti.math.pi * 0.3),\n \"SENSE_DIST\": (ti.f32, 50.0),\n \"MOVE_ANGLE\": (ti.f32, ti.math.pi * 0.3),\n \"MOVE_DIST\": (ti.f32, 4.0),\n \"SUBSTEP\": (ti.i32, 1),\n \"BRIGHTNESS\": (ti.f32, brightness),\n }\n )\n self.tv.s.slime_p = {\n \"state\": {\n \"sense_angle\": (ti.f32, 0.0, 10.0),\n \"sense_left\": (ti.math.vec4, 0.0, 10.0),\n \"sense_centre\": (ti.math.vec4, 0.0, 10.0),\n \"sense_right\": (ti.math.vec4, 0.0, 10.0),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": True,\n }\n self.tv.s.slime_s = {\n \"state\": {\n \"sense_angle\": (ti.f32, 0.0, 1.0),\n \"sense_dist\": (ti.f32, 0.0, 1.0),\n \"move_angle\": (ti.f32, 0.0, 1.0),\n \"move_dist\": (ti.f32, 0.0, 1.0),\n \"evaporate\": (ti.f32, 0.0, 1.0),\n },\n \"shape\": self.tv.sn, # multi-species: (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.trail = Pixels(self.tv, **kwargs)\n self.evaporate = ti.field(dtype=ti.f32, shape=())\n self.evaporate[None] = kwargs.get(\"evaporate\", 0.99)\n\n def randomise(self):\n \"\"\"Randomise the Slime behaviour.\"\"\"\n self.tv.s.slime_s.randomise()\n self.tv.s.slime_p.randomise()\n\n @ti.kernel\n def move(self, field: ti.template(), weight: ti.f32):\n \"\"\"Move the particles based on the sensed environment.\n\n Each particle senses the trail to its left, centre and right. Depending on the \n strength of the sensed trail in each direction, and the species parameters,\n a movement angle is calculated. The particle moves in this direction by a \n distance proportional to its active state and the weight parameter.\n\n Args:\n field (ti.template): Particle field.\n weight (ti.f32): Weight of the movement.\n \"\"\"\n for i in range(field.shape[0]):\n if field[i].active == 0.0:\n continue\n\n p = field[i]\n ang = self.tv.s.slime_p[i].sense_angle\n species = self.tv.s.slime_s[p.species]\n\n sense_angle = species.sense_angle * self.CONSTS.SENSE_ANGLE\n sense_dist = species.sense_dist * self.CONSTS.SENSE_DIST\n move_angle = species.move_angle * self.CONSTS.MOVE_ANGLE\n move_dist = species.move_dist * self.CONSTS.MOVE_DIST\n\n c = self.sense(p.pos, ang, sense_dist).norm()\n l = self.sense(p.pos, ang - sense_angle, sense_dist).norm()\n r = self.sense(p.pos, ang + sense_angle, sense_dist).norm()\n\n if l < c < r:\n ang += move_angle\n elif l > c > r:\n ang -= move_angle\n elif r > c and c < l:\n # TODO: magic numbers, move to @ti.func inside utils?\n ang += move_angle * (2 * (ti.random() < 0.5) - 1)\n\n p.pos += (\n ti.Vector([ti.cos(ang), ti.sin(ang)]) * move_dist * p.active * weight\n )\n\n self.tv.s.slime_p[i].sense_angle = ang\n self.tv.s.slime_p[i].sense_centre = c\n self.tv.s.slime_p[i].sense_left = l\n self.tv.s.slime_p[i].sense_right = r\n field[i].pos = p.pos\n\n @ti.func\n def sense(self, pos: ti.math.vec2, ang: ti.f32, dist: ti.f32) -> ti.math.vec4:\n \"\"\"Sense the trail at a given position and angle.\n\n Args:\n pos (ti.math.vec2): Position.\n ang (ti.f32): Angle.\n dist (ti.f32): Distance.\n\n Returns:\n ti.math.vec4: RGBA value of the sensed trail point.\n \"\"\"\n ang_cos = ti.cos(ang)\n ang_sin = ti.sin(ang)\n v = ti.Vector([ang_cos, ang_sin])\n p = pos + v * dist\n px = ti.cast(p[0], ti.i32) % self.tv.x\n py = ti.cast(p[1], ti.i32) % self.tv.y\n pixel = self.trail.px.rgba[px, py]\n return pixel\n\n @ti.func\n def sense_rgba(self, pos: ti.math.vec2, ang: ti.f32, dist: ti.f32, rgba: ti.math.vec4) -> ti.math.vec4:\n \"\"\"Sense the trail at a given position and angle and return a weighted RGBA value.\n\n Args:\n pos (ti.math.vec2): Position.\n ang (ti.f32): Angle.\n dist (ti.f32): Distance.\n rgba (ti.math.vec4): RGBA value.\n\n Returns:\n ti.math.vec4: Weighted RGBA value.\n \"\"\"\n p = pos + ti.Vector([ti.cos(ang), ti.sin(ang)]) * dist\n px = ti.cast(p[0], ti.i32) % self.tv.x\n py = ti.cast(p[1], ti.i32) % self.tv.y\n px_rgba = self.trail.px.rgba[px, py]\n px_rgba_weighted = px_rgba * (1.0 - (px_rgba - rgba).norm())\n return px_rgba_weighted\n\n @ti.kernel\n def deposit_particles(self, particles: ti.template(), species: ti.template()):\n \"\"\"Deposit particles onto the trail.\n\n Args:\n particles (ti.template): Particle field.\n species (ti.template): Species field.\n \"\"\"\n for i in range(particles.shape[0]):\n if particles[i].active == 0.0:\n continue\n p, s = particles[i], species[particles[i].species]\n x = ti.cast(p.pos[0], ti.i32) % self.tv.x\n y = ti.cast(p.pos[1], ti.i32) % self.tv.y\n rgba = s.rgba * self.CONSTS.BRIGHTNESS * p.active\n self.trail.circle(x, y, p.size, rgba)\n\n def step(self, particles, species, weight: ti.f32 = 1.0):\n \"\"\"Step the Slime behaviour.\n\n Args:\n particles (Particles): A Particles instance.\n species (Species): A Species instance.\n weight (ti.f32, optional): Weight parameter. Defaults to 1.0.\n \"\"\"\n for i in range(self.CONSTS.SUBSTEP):\n self.move(particles.field, weight)\n self.deposit_particles(particles.field, species)\n self.trail.diffuse(self.evaporate[None])\n\n def __call__(self, particles, species, weight: ti.f32 = 1.0):\n \"\"\"Call the Slime behaviour.\n\n Args:\n particles (Particles): A Particles instance.\n species (Species): A Species instance.\n weight (ti.f32, optional): Weight parameter. Defaults to 1.0.\n\n Returns:\n Pixels: A Pixels instance containing the pheromone trail.\n \"\"\"\n self.step(particles, species, weight)\n return self.trail\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.__call__","title":"__call__(particles, species, weight=1.0)
","text":"Call the Slime behaviour.
Parameters:
Name Type Description Default particles
Particles
A Particles instance.
required species
Species
A Species instance.
required weight
f32
Weight parameter. Defaults to 1.0.
1.0
Returns:
Name Type Description Pixels
A Pixels instance containing the pheromone trail.
Source code in src/tolvera/vera/slime.py
def __call__(self, particles, species, weight: ti.f32 = 1.0):\n \"\"\"Call the Slime behaviour.\n\n Args:\n particles (Particles): A Particles instance.\n species (Species): A Species instance.\n weight (ti.f32, optional): Weight parameter. Defaults to 1.0.\n\n Returns:\n Pixels: A Pixels instance containing the pheromone trail.\n \"\"\"\n self.step(particles, species, weight)\n return self.trail\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.__init__","title":"__init__(tolvera, **kwargs)
","text":"Initialise the Slime behaviour.
slime_p
stores the particle state. slime_s
stores the species state. trail
is a Pixels instance that stores the pheromone trail.
Parameters:
Name Type Description Default tolvera
Tolvera
A Tolvera instance.
required evaporate
f32
Evaporation rate. Defaults to 0.99.
required **kwargs
Keyword arguments. brightness (ti.f32, optional): Brightness of the pheromone trail. Defaults to 1.0.
{}
Source code in src/tolvera/vera/slime.py
def __init__(self, tolvera, **kwargs):\n \"\"\"Initialise the Slime behaviour.\n\n `slime_p` stores the particle state.\n `slime_s` stores the species state.\n `trail` is a Pixels instance that stores the pheromone trail.\n\n Args:\n tolvera (Tolvera): A Tolvera instance.\n evaporate (ti.f32, optional): Evaporation rate. Defaults to 0.99.\n **kwargs: Keyword arguments.\n brightness (ti.f32, optional): Brightness of the pheromone trail. Defaults to 1.0.\n \"\"\"\n self.tv = tolvera\n self.kwargs = kwargs\n brightness = kwargs.get(\"brightness\", 1.0)\n self.CONSTS = CONSTS(\n {\n \"SENSE_ANGLE\": (ti.f32, ti.math.pi * 0.3),\n \"SENSE_DIST\": (ti.f32, 50.0),\n \"MOVE_ANGLE\": (ti.f32, ti.math.pi * 0.3),\n \"MOVE_DIST\": (ti.f32, 4.0),\n \"SUBSTEP\": (ti.i32, 1),\n \"BRIGHTNESS\": (ti.f32, brightness),\n }\n )\n self.tv.s.slime_p = {\n \"state\": {\n \"sense_angle\": (ti.f32, 0.0, 10.0),\n \"sense_left\": (ti.math.vec4, 0.0, 10.0),\n \"sense_centre\": (ti.math.vec4, 0.0, 10.0),\n \"sense_right\": (ti.math.vec4, 0.0, 10.0),\n },\n \"shape\": self.tv.pn,\n \"osc\": (\"get\"),\n \"randomise\": True,\n }\n self.tv.s.slime_s = {\n \"state\": {\n \"sense_angle\": (ti.f32, 0.0, 1.0),\n \"sense_dist\": (ti.f32, 0.0, 1.0),\n \"move_angle\": (ti.f32, 0.0, 1.0),\n \"move_dist\": (ti.f32, 0.0, 1.0),\n \"evaporate\": (ti.f32, 0.0, 1.0),\n },\n \"shape\": self.tv.sn, # multi-species: (self.tv.sn, self.tv.sn),\n \"osc\": (\"set\"),\n \"randomise\": True,\n }\n self.trail = Pixels(self.tv, **kwargs)\n self.evaporate = ti.field(dtype=ti.f32, shape=())\n self.evaporate[None] = kwargs.get(\"evaporate\", 0.99)\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.deposit_particles","title":"deposit_particles(particles, species)
","text":"Deposit particles onto the trail.
Parameters:
Name Type Description Default particles
template
Particle field.
required species
template
Species field.
required Source code in src/tolvera/vera/slime.py
@ti.kernel\ndef deposit_particles(self, particles: ti.template(), species: ti.template()):\n \"\"\"Deposit particles onto the trail.\n\n Args:\n particles (ti.template): Particle field.\n species (ti.template): Species field.\n \"\"\"\n for i in range(particles.shape[0]):\n if particles[i].active == 0.0:\n continue\n p, s = particles[i], species[particles[i].species]\n x = ti.cast(p.pos[0], ti.i32) % self.tv.x\n y = ti.cast(p.pos[1], ti.i32) % self.tv.y\n rgba = s.rgba * self.CONSTS.BRIGHTNESS * p.active\n self.trail.circle(x, y, p.size, rgba)\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.move","title":"move(field, weight)
","text":"Move the particles based on the sensed environment.
Each particle senses the trail to its left, centre and right. Depending on the strength of the sensed trail in each direction, and the species parameters, a movement angle is calculated. The particle moves in this direction by a distance proportional to its active state and the weight parameter.
Parameters:
Name Type Description Default field
template
Particle field.
required weight
f32
Weight of the movement.
required Source code in src/tolvera/vera/slime.py
@ti.kernel\ndef move(self, field: ti.template(), weight: ti.f32):\n \"\"\"Move the particles based on the sensed environment.\n\n Each particle senses the trail to its left, centre and right. Depending on the \n strength of the sensed trail in each direction, and the species parameters,\n a movement angle is calculated. The particle moves in this direction by a \n distance proportional to its active state and the weight parameter.\n\n Args:\n field (ti.template): Particle field.\n weight (ti.f32): Weight of the movement.\n \"\"\"\n for i in range(field.shape[0]):\n if field[i].active == 0.0:\n continue\n\n p = field[i]\n ang = self.tv.s.slime_p[i].sense_angle\n species = self.tv.s.slime_s[p.species]\n\n sense_angle = species.sense_angle * self.CONSTS.SENSE_ANGLE\n sense_dist = species.sense_dist * self.CONSTS.SENSE_DIST\n move_angle = species.move_angle * self.CONSTS.MOVE_ANGLE\n move_dist = species.move_dist * self.CONSTS.MOVE_DIST\n\n c = self.sense(p.pos, ang, sense_dist).norm()\n l = self.sense(p.pos, ang - sense_angle, sense_dist).norm()\n r = self.sense(p.pos, ang + sense_angle, sense_dist).norm()\n\n if l < c < r:\n ang += move_angle\n elif l > c > r:\n ang -= move_angle\n elif r > c and c < l:\n # TODO: magic numbers, move to @ti.func inside utils?\n ang += move_angle * (2 * (ti.random() < 0.5) - 1)\n\n p.pos += (\n ti.Vector([ti.cos(ang), ti.sin(ang)]) * move_dist * p.active * weight\n )\n\n self.tv.s.slime_p[i].sense_angle = ang\n self.tv.s.slime_p[i].sense_centre = c\n self.tv.s.slime_p[i].sense_left = l\n self.tv.s.slime_p[i].sense_right = r\n field[i].pos = p.pos\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.randomise","title":"randomise()
","text":"Randomise the Slime behaviour.
Source code in src/tolvera/vera/slime.py
def randomise(self):\n \"\"\"Randomise the Slime behaviour.\"\"\"\n self.tv.s.slime_s.randomise()\n self.tv.s.slime_p.randomise()\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.sense","title":"sense(pos, ang, dist)
","text":"Sense the trail at a given position and angle.
Parameters:
Name Type Description Default pos
vec2
Position.
required ang
f32
Angle.
required dist
f32
Distance.
required Returns:
Type Description vec4
ti.math.vec4: RGBA value of the sensed trail point.
Source code in src/tolvera/vera/slime.py
@ti.func\ndef sense(self, pos: ti.math.vec2, ang: ti.f32, dist: ti.f32) -> ti.math.vec4:\n \"\"\"Sense the trail at a given position and angle.\n\n Args:\n pos (ti.math.vec2): Position.\n ang (ti.f32): Angle.\n dist (ti.f32): Distance.\n\n Returns:\n ti.math.vec4: RGBA value of the sensed trail point.\n \"\"\"\n ang_cos = ti.cos(ang)\n ang_sin = ti.sin(ang)\n v = ti.Vector([ang_cos, ang_sin])\n p = pos + v * dist\n px = ti.cast(p[0], ti.i32) % self.tv.x\n py = ti.cast(p[1], ti.i32) % self.tv.y\n pixel = self.trail.px.rgba[px, py]\n return pixel\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.sense_rgba","title":"sense_rgba(pos, ang, dist, rgba)
","text":"Sense the trail at a given position and angle and return a weighted RGBA value.
Parameters:
Name Type Description Default pos
vec2
Position.
required ang
f32
Angle.
required dist
f32
Distance.
required rgba
vec4
RGBA value.
required Returns:
Type Description vec4
ti.math.vec4: Weighted RGBA value.
Source code in src/tolvera/vera/slime.py
@ti.func\ndef sense_rgba(self, pos: ti.math.vec2, ang: ti.f32, dist: ti.f32, rgba: ti.math.vec4) -> ti.math.vec4:\n \"\"\"Sense the trail at a given position and angle and return a weighted RGBA value.\n\n Args:\n pos (ti.math.vec2): Position.\n ang (ti.f32): Angle.\n dist (ti.f32): Distance.\n rgba (ti.math.vec4): RGBA value.\n\n Returns:\n ti.math.vec4: Weighted RGBA value.\n \"\"\"\n p = pos + ti.Vector([ti.cos(ang), ti.sin(ang)]) * dist\n px = ti.cast(p[0], ti.i32) % self.tv.x\n py = ti.cast(p[1], ti.i32) % self.tv.y\n px_rgba = self.trail.px.rgba[px, py]\n px_rgba_weighted = px_rgba * (1.0 - (px_rgba - rgba).norm())\n return px_rgba_weighted\n
"},{"location":"reference/tolvera/vera/slime/#tolvera.vera.slime.Slime.step","title":"step(particles, species, weight=1.0)
","text":"Step the Slime behaviour.
Parameters:
Name Type Description Default particles
Particles
A Particles instance.
required species
Species
A Species instance.
required weight
f32
Weight parameter. Defaults to 1.0.
1.0
Source code in src/tolvera/vera/slime.py
def step(self, particles, species, weight: ti.f32 = 1.0):\n \"\"\"Step the Slime behaviour.\n\n Args:\n particles (Particles): A Particles instance.\n species (Species): A Species instance.\n weight (ti.f32, optional): Weight parameter. Defaults to 1.0.\n \"\"\"\n for i in range(self.CONSTS.SUBSTEP):\n self.move(particles.field, weight)\n self.deposit_particles(particles.field, species)\n self.trail.diffuse(self.evaporate[None])\n
"},{"location":"reference/tolvera/vera/swarmalators/","title":"Swarmalators","text":"Based on https://www.complexity-explorables.org/explorables/swarmalators/
"}]}
\ No newline at end of file
diff --git a/sitemap.xml.gz b/sitemap.xml.gz
index c1fbb51..970346a 100644
Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ