diff --git a/404.html b/404.html index 7dede8b..a040bc5 100644 --- a/404.html +++ b/404.html @@ -331,6 +331,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1500,6 +1517,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/artworks/index.html b/artworks/index.html index 9496b54..2ff6847 100644 --- a/artworks/index.html +++ b/artworks/index.html @@ -342,6 +342,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1523,6 +1540,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/bibliography/index.html b/bibliography/index.html new file mode 100644 index 0000000..b6a8533 --- /dev/null +++ b/bibliography/index.html @@ -0,0 +1,1761 @@ + + + + + + + + + + + + + + + + + + + + + Bibliography - Tölvera + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + Skip to content + + +
    +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + + + + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    Bibliography

    +

    Page under construction 🚧. For now, see bibliography.bib.

    + + + + + + + + + + + + + + + + + + +
    +
    + + + +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + + + \ No newline at end of file diff --git a/examples/index.html b/examples/index.html index 2c8c3e3..ccc3f70 100644 --- a/examples/index.html +++ b/examples/index.html @@ -342,6 +342,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1523,6 +1540,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/experiments/index.html b/experiments/index.html index 21230fb..edf41c3 100644 --- a/experiments/index.html +++ b/experiments/index.html @@ -342,6 +342,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1598,6 +1615,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/guide/index.html b/guide/index.html index dfaee52..119d8ba 100644 --- a/guide/index.html +++ b/guide/index.html @@ -342,6 +342,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1730,6 +1747,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/index.html b/index.html index c94b662..e2e54c9 100644 --- a/index.html +++ b/index.html @@ -340,6 +340,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1650,6 +1667,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + @@ -1796,7 +1833,7 @@

    Tölvera

    -

    Tölvera is a Python library designed for composing together and interacting with basal agencies. +

    Tölvera is a Python library designed for composing together and interacting with basal agencies, inspired by fields such as artificial life (ALife) and self-organising systems. It provides creative coding-style APIs that allow users to combine and compose various built-in behaviours, such as flocking, slime mold growth, and swarming, and also author their own. With built-in support for Open Sound Control (OSC) via iipyper and interactive machine learning (IML) via anguilla, Tölvera interfaces with and rapidly maps onto existing creative computing software and hardware, striving to be both an accessible and powerful tool for exploring diverse intelligence in artistic contexts.

    The word Tölvera is an Icelandic kenning based on tölva meaning computer, from tala (number) and völva (prophetess), and vera (being), composed together as number being.

    diff --git a/publications/index.html b/publications/index.html index 86bcf22..53a41b7 100644 --- a/publications/index.html +++ b/publications/index.html @@ -12,6 +12,8 @@ + + @@ -340,6 +342,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + +
    @@ -1521,6 +1540,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/context/index.html b/reference/tolvera/context/index.html index e82d20c..975d4b8 100644 --- a/reference/tolvera/context/index.html +++ b/reference/tolvera/context/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1669,6 +1686,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/cv/index.html b/reference/tolvera/cv/index.html index 767d15d..3dd0450 100644 --- a/reference/tolvera/cv/index.html +++ b/reference/tolvera/cv/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1564,6 +1581,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/iml/index.html b/reference/tolvera/iml/index.html index 623cb7d..0534173 100644 --- a/reference/tolvera/iml/index.html +++ b/reference/tolvera/iml/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -2080,6 +2097,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/mp/face/index.html b/reference/tolvera/mp/face/index.html index f11c435..2d58a9e 100644 --- a/reference/tolvera/mp/face/index.html +++ b/reference/tolvera/mp/face/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1597,6 +1614,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/mp/face_mesh/index.html b/reference/tolvera/mp/face_mesh/index.html index 5c39515..18ac0fd 100644 --- a/reference/tolvera/mp/face_mesh/index.html +++ b/reference/tolvera/mp/face_mesh/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1564,6 +1581,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/mp/face_mesh_connections/index.html b/reference/tolvera/mp/face_mesh_connections/index.html index c159247..0d636cf 100644 --- a/reference/tolvera/mp/face_mesh_connections/index.html +++ b/reference/tolvera/mp/face_mesh_connections/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1564,6 +1581,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/mp/hands/index.html b/reference/tolvera/mp/hands/index.html index 6cccda0..ced3e3c 100644 --- a/reference/tolvera/mp/hands/index.html +++ b/reference/tolvera/mp/hands/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1573,6 +1590,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/mp/pose/index.html b/reference/tolvera/mp/pose/index.html index 34590e6..03a2a23 100644 --- a/reference/tolvera/mp/pose/index.html +++ b/reference/tolvera/mp/pose/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1573,6 +1590,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/npndarray_dict/index.html b/reference/tolvera/npndarray_dict/index.html index a7c1d6a..0d94ac1 100644 --- a/reference/tolvera/npndarray_dict/index.html +++ b/reference/tolvera/npndarray_dict/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1669,6 +1686,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/osc/maxmsp/index.html b/reference/tolvera/osc/maxmsp/index.html index 6589eaa..15b4f7d 100644 --- a/reference/tolvera/osc/maxmsp/index.html +++ b/reference/tolvera/osc/maxmsp/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1615,6 +1632,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/osc/osc/index.html b/reference/tolvera/osc/osc/index.html index a5f7eef..8752302 100644 --- a/reference/tolvera/osc/osc/index.html +++ b/reference/tolvera/osc/osc/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1564,6 +1581,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/osc/oscmap/index.html b/reference/tolvera/osc/oscmap/index.html index 6dc9c2c..19a0c71 100644 --- a/reference/tolvera/osc/oscmap/index.html +++ b/reference/tolvera/osc/oscmap/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1615,6 +1632,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/osc/pd/index.html b/reference/tolvera/osc/pd/index.html index a065da3..b07b86b 100644 --- a/reference/tolvera/osc/pd/index.html +++ b/reference/tolvera/osc/pd/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1615,6 +1632,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/osc/update/index.html b/reference/tolvera/osc/update/index.html index a6e0f0b..dc5f109 100644 --- a/reference/tolvera/osc/update/index.html +++ b/reference/tolvera/osc/update/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1717,6 +1734,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/particles/index.html b/reference/tolvera/particles/index.html index af1bc1f..b0a609d 100644 --- a/reference/tolvera/particles/index.html +++ b/reference/tolvera/particles/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1819,6 +1836,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/patches/index.html b/reference/tolvera/patches/index.html index 811e731..b2aa00b 100644 --- a/reference/tolvera/patches/index.html +++ b/reference/tolvera/patches/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1573,6 +1590,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/pixels/index.html b/reference/tolvera/pixels/index.html index 9a60964..48fd781 100644 --- a/reference/tolvera/pixels/index.html +++ b/reference/tolvera/pixels/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1858,6 +1875,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/sketchbook/index.html b/reference/tolvera/sketchbook/index.html index 1039dc1..6249d28 100644 --- a/reference/tolvera/sketchbook/index.html +++ b/reference/tolvera/sketchbook/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1699,6 +1716,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/species/index.html b/reference/tolvera/species/index.html index c8508c1..05c77b1 100644 --- a/reference/tolvera/species/index.html +++ b/reference/tolvera/species/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1597,6 +1614,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/state/index.html b/reference/tolvera/state/index.html index 9c5ca45..064c988 100644 --- a/reference/tolvera/state/index.html +++ b/reference/tolvera/state/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1837,6 +1854,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/taichi_/index.html b/reference/tolvera/taichi_/index.html index 61b93d6..5419381 100644 --- a/reference/tolvera/taichi_/index.html +++ b/reference/tolvera/taichi_/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1624,6 +1641,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/tolvera_/index.html b/reference/tolvera/tolvera_/index.html index 5865b40..61e1222 100644 --- a/reference/tolvera/tolvera_/index.html +++ b/reference/tolvera/tolvera_/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1651,6 +1668,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/utils/index.html b/reference/tolvera/utils/index.html index 387262a..159ee27 100644 --- a/reference/tolvera/utils/index.html +++ b/reference/tolvera/utils/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1663,6 +1680,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/vera/attractors/index.html b/reference/tolvera/vera/attractors/index.html index 97719a0..f3fcf9a 100644 --- a/reference/tolvera/vera/attractors/index.html +++ b/reference/tolvera/vera/attractors/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1564,6 +1581,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/vera/diffline/index.html b/reference/tolvera/vera/diffline/index.html index 63875b4..ca8f157 100644 --- a/reference/tolvera/vera/diffline/index.html +++ b/reference/tolvera/vera/diffline/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1588,6 +1605,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/vera/flock/index.html b/reference/tolvera/vera/flock/index.html index fba42c0..8067343 100644 --- a/reference/tolvera/vera/flock/index.html +++ b/reference/tolvera/vera/flock/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1615,6 +1632,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/vera/flock2/index.html b/reference/tolvera/vera/flock2/index.html index 6035bf9..1a8cfcc 100644 --- a/reference/tolvera/vera/flock2/index.html +++ b/reference/tolvera/vera/flock2/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1615,6 +1632,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/vera/flock_shiffman/index.html b/reference/tolvera/vera/flock_shiffman/index.html index 8607aa3..d7f4bdb 100644 --- a/reference/tolvera/vera/flock_shiffman/index.html +++ b/reference/tolvera/vera/flock_shiffman/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1564,6 +1581,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/vera/forces/index.html b/reference/tolvera/vera/forces/index.html index aa62a92..deb33ad 100644 --- a/reference/tolvera/vera/forces/index.html +++ b/reference/tolvera/vera/forces/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1681,6 +1698,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/vera/geom/index.html b/reference/tolvera/vera/geom/index.html index fe480c9..abfcdd3 100644 --- a/reference/tolvera/vera/geom/index.html +++ b/reference/tolvera/vera/geom/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1582,6 +1599,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/vera/nca/index.html b/reference/tolvera/vera/nca/index.html index 531c679..6f28e48 100644 --- a/reference/tolvera/vera/nca/index.html +++ b/reference/tolvera/vera/nca/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1564,6 +1581,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/vera/particle_life/index.html b/reference/tolvera/vera/particle_life/index.html index 59699c8..d962c46 100644 --- a/reference/tolvera/vera/particle_life/index.html +++ b/reference/tolvera/vera/particle_life/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1606,6 +1623,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/vera/reaction_diffusion/index.html b/reference/tolvera/vera/reaction_diffusion/index.html index ee820be..0bac676 100644 --- a/reference/tolvera/vera/reaction_diffusion/index.html +++ b/reference/tolvera/vera/reaction_diffusion/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1564,6 +1581,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/vera/slime/index.html b/reference/tolvera/vera/slime/index.html index ef7262a..c0acf79 100644 --- a/reference/tolvera/vera/slime/index.html +++ b/reference/tolvera/vera/slime/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1651,6 +1668,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/vera/swarmalators/index.html b/reference/tolvera/vera/swarmalators/index.html index 394c39a..65f5b00 100644 --- a/reference/tolvera/vera/swarmalators/index.html +++ b/reference/tolvera/vera/swarmalators/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1564,6 +1581,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/reference/tolvera/vera/viscek/index.html b/reference/tolvera/vera/viscek/index.html index b263301..453b753 100644 --- a/reference/tolvera/vera/viscek/index.html +++ b/reference/tolvera/vera/viscek/index.html @@ -344,6 +344,23 @@ + + + + +
  • + + + + + + Bibliography + + +
  • + + + @@ -1597,6 +1614,26 @@ + + + + + + +
  • + + + + + Bibliography + + + + +
  • + + + diff --git a/search/search_index.json b/search/search_index.json index cc5aa9b..7ac50b1 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Intro","text":""},{"location":"#tolvera","title":"T\u00f6lvera","text":"

    T\u00f6lvera is a Python library designed for composing together and interacting with basal agencies. It provides creative coding-style APIs that allow users to combine and compose various built-in behaviours, such as flocking, slime mold growth, and swarming, and also author their own. With built-in support for Open Sound Control (OSC) via iipyper and interactive machine learning (IML) via anguilla, T\u00f6lvera interfaces with and rapidly maps onto existing creative computing software and hardware, striving to be both an accessible and powerful tool for exploring diverse intelligence in artistic contexts.

    The word T\u00f6lvera is an Icelandic kenning based on t\u00f6lva meaning computer, from tala (number) and v\u00f6lva (prophetess), and vera (being), composed together as number being.

    We have employed T\u00f6lvera in various collaborative artistic works, including musical performances, compositions, and multimedia installations (see references.bib for peer-reviewed publications). T\u00f6lvera's role in these pieces has mainly been \"mappable behaviour engine\", where interface inputs can control T\u00f6lvera programs, and T\u00f6lvera runtime data can control interface outputs, in practically any combination. In this way, and to controllable degrees, T\u00f6lvera can contribute to the underlying dynamics of a given interactive scenario. It can also add a visual component, and equally has been used without projection in other works.

    T\u00f6lvera makes use of Taichi, a domain-specific language embedded in Python that enables parallelisation, and is experimental software subject to change.

    We would be happy to have you join us on our Discord server!

    "},{"location":"#showcase-examples","title":"Showcase & Examples","text":"

    Examples can be found at iil-examples/tolvera. See also the guide, reference and experiments pages.

    Visit the YouTube Playlist (if you'd like to add a video, please get in touch).

    "},{"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":"

    For development, we use poetry. 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":"#known-issues-limitations","title":"Known Issues & Limitations","text":""},{"location":"#contribute","title":"Contribute","text":"

    We welcome Pull Requests across all areas of the project:

    "},{"location":"#community","title":"Community","text":"

    To discuss T\u00f6lvera with developers and other users:

    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":"#citation","title":"Citation","text":"

    T\u00f6lvera is being written about and used in a number of contexts (see references.bib). The current canonical citation is our NIME 2024 paper:

    @inproceedings{armitageTolveraComposingBasal2024,\n  title = {T{\\\"o}lvera: {{Composing With Basal Agencies}}},\n  booktitle = {Proc. {{New Interfaces}} for {{Musical Expression}}},\n  author = {Armitage, Jack and Shepardson, Victor and Magnusson, Thor},\n  year = {2024},\n  address = {Utrecht, NL},\n}\n
    "},{"location":"#inspiration","title":"Inspiration","text":""},{"location":"#contact","title":"Contact","text":"

    T\u00f6lvera is developed primarily by Jack Armitage and collaborators at 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":"#acknowledgements","title":"Acknowledgements","text":"

    We thank the Taichi community for their wonderful project, that makes T\u00f6lvera possible.

    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":"artworks/","title":"Artworks","text":""},{"location":"artworks/#artworks","title":"Artworks","text":"

    Page under construction \ud83d\udea7.

    "},{"location":"examples/","title":"Examples","text":""},{"location":"examples/#examples","title":"Examples","text":"

    Page under construction \ud83d\udea7. For now, see iil-examples/tolvera.

    "},{"location":"experiments/","title":"Experiments","text":""},{"location":"experiments/#experiments","title":"Experiments","text":"

    These are ongoing experiments with T\u00f6lvera, Taichi and third-party libraries. You can find more experiments in the examples folder. Most are working examples, but don't be surprised if there is the occasional broken one - please report issues here.

    "},{"location":"experiments/#live-coding","title":"Live Coding","text":"

    T\u00f6lvera can be live coded via Sardine. Note that this only works so far using VSCode's inbuilt Python REPL, and not with the Sardine VSCode extension:

    tv.s.params = {'state':{'evap': (ti.f32, 0., 0.99)}}\n\n@swim\ndef gui_loop(p=0.5, i=0):\n  tv.px.diffuse(tv.s.params.field[0].evap)\n  tv.px.particles(tv.p, tv.s.species())\n  tv.show(tv.px)\n  again(gui_loop, p=1/64, i=i+1)\n\n@swim\ndef control_loop(p=4, i=0):\n  tv.s.params.field[0].evap = P('0.9 0.99 0.1', i)\n  again(control_loop, p=1/2, i=i+1)\n
    "},{"location":"experiments/#sonification","title":"Sonification","text":"

    Sonification of and with T\u00f6lvera can be achieved via SignalFlow. See examples here.

    "},{"location":"experiments/#gpu-audio","title":"GPU Audio","text":"

    GPU audio rendering can be achieved using Taichi alone:

    from iipyper import Audio, run\nimport sounddevice as sd\nimport taichi as ti\nfrom math import pi\n\n@ti.kernel\ndef generate_data(outdata: ti.ext_arr(), \n                  frames: ti.i32, \n                  start_idx: ti.template(), \n                  fs: ti.f32):\n    for I in ti.grouped(ti.ndrange(frames)):\n        i = I[0]\n        t = (start_idx[None] + i) / fs\n        s = ti.sin(2 * pi * 440 * t)\n        start_idx[None] += 1\n        outdata[i,0] = s\n\ndef main():\n    ti.init(arch=ti.vulkan)\n    device = sd.default.device\n    fs = sd.query_devices(sd.default.device, 'output')['default_samplerate']\n    buffer_size = 128\n    start_idx = ti.field(ti.i32, ())\n    def callback(indata, outdata, frames, time, status):\n        if status: print(status, file=sys.stderr)\n        generate_data(outdata, frames, start_idx, fs)\n    Audio(device=device, channels=1, blocksize=buffer_size,\n        samplerate=fs, callback=callback)\n\nif __name__=='__main__':\n    run(main)\n
    "},{"location":"experiments/#3d-printing","title":"3D Printing","text":"

    Exploration of 3D printing with T\u00f6lvera can be achieved via FullControl. See example here.

    "},{"location":"experiments/#sketchbook","title":"Sketchbook","text":"

    T\u00f6lvera has an experimental sketchbook built-in. For example, if the following program exists in a folder called mysketch.py:

    from tolvera import Tolvera\n\ndef mysketch(**kwargs):\n    tv = Tolvera(**kwargs)\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)\n        return tv.px\n

    It can be run with:

    python -m tolvera --sketch \"mysketch\"\n

    Available commands:

    --sketchbook    Set a sketchbook folder\n--sketches      List available sketches\n--sketch        Run a particular sketch (can be a file path or index)\n--random        Run a random sketch from the sketchbook\n
    "},{"location":"guide/","title":"Guide","text":""},{"location":"guide/#tolvera-v010-api-guide","title":"T\u00f6lvera v0.1.0 API Guide","text":"

    This guide provides an overview of the T\u00f6lvera v0.1.0 API:

    The v0.2.0 API will be different, so everything here is subject to change. Please share your feedback on our Discord.

    "},{"location":"guide/#overview-of-features","title":"Overview of Features","text":""},{"location":"guide/#program-structure","title":"Program Structure","text":"

    This example demonstrates the basic usage of T\u00f6lvera when used as a standalone Python script. 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
    "},{"location":"guide/#particles-tvp-species-tvsspecies","title":"Particles (tv.p) & Species (tv.s.species)","text":"

    A central idea of T\u00f6lvera is the particle as a base unit of activity. The particle system is a field of type Particle with these properties:

    @ti.dataclass\nclass Particle:\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

    Particles are divided into species (represented as an integer), and species can have different relationships with each other, creating a matrix of species-species interactions. This idea was inspired by Particle Life, and provides a simple means to mimic ecological complexity, even when using a single behaviour or model. Species are implemented as multi-dimensional state (see below), which means all tv.v behaviour models can make use of the multispecies matrix.

    Multi-species matrix (`tv.s.species`) with N species shown on each axis, with example shown based on `tv.v.flock()`\u2019s rules. Every species has a different relationship with each other, including itself, i.e. cell (0,0) shows the 0th species\u2019 relationship with itself. As species are implemented as state (`tv.s`), OSC endpoints can be automatically created allowing for dynamically updating rules of individual species pairs, or groups of pairs, or indeed the entire set of rules."},{"location":"guide/#state-tvs","title":"State (tv.s)","text":"

    To enable composition of behaviours, T\u00f6lvera features a global state dictionary. State is n-dimensional and can be manipulated in parallel on the GPU. Typical state shapes might be: (species, species) for multispecies rules, (particles) for individual particle states, and \\pyi(particles, particles) for pairwise comparison of particles. Assigning a dictionary to a variable after tv.s causes a new state to be instanced. Here are two examples of state from tv.v.flock, showing the syntax of \"name\": (type, min, max) for each attribute, and some of the additional options which includes built-in randomisation:

    tv.s.flock_s = { \"state\": \n  {\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\": (species, species),\n  \"osc\": (\"set\"),\n  \"randomise\": True\n}\n\ntv.s.flock_dist = {\n  \"state\": {\"dist\": (ti.f32, 0.0, tv.x*2)},\n  \"shape\": (particles, particles),\n  \"randomise\": False\n}\n

    State is useful and interesting to visualise, for example drawing the particle distances that flock uses reveals hidden structure. State can also be reused and combined with state from other models, to compose even more complex behaviour.

    "},{"location":"guide/#vera-tvv","title":"Vera (tv.v)","text":"

    The image below shows some of the available behaviours and models in T\u00f6lvera. Some of these are inspired directly from open source code posted by the Taichi community - thank you!

    Examples of behaviours and models available via `tv.v`. Top row: stateless `tv.v`. Bottom row: stateful `tv.v`.

    Models can be used by calling them and passing particles to them, for example:

    @tv.render\ndef _():\n  tv.px.clear()\n  tv.v.flock(tv.p)\n  tv.v.plife(tv.p)\n  tv.px.particles(tv.p, tv.s.species())\n  return tv.px\n

    In this case, the flock and plife (particle life) models are composed together to create a compound behaviour. Models are also designed to be simple and concise internally to encourage users to understand them and make their own. Simple behaviours like move can be stateless and implemented as single GPU kernels.

    @ti.kernel\ndef move(p: ti.template()):\n  for i in range(p.field.shape[0]):\n    p1 = p.field[i]\n    if p1.field[i].active == 0: continue\n    p.field[i].pos += p1.vel * p1.speed\n

    Models that use state are implemented as classes, that at minimum provide a step() method, where particles can be compared and states updated:

    @ti.data_oriented\nclass MyVera:\n  def __init__(self, tolvera, **kwargs):\n    self.tv, self.kwargs = tolvera, kwargs\n    self.C = CONSTS({...})\n    self.tv.s.vera_s = {...} #\u00a0state\n\n  @ti.kernel\n  def step(self, p):\n    for i in range(p.shape[0]):\n      for j in range(p.shape[1]):\n        # compare p[i] & p[j]\n        # update state, etc.\n      p[i].pos += ... # update p[i]\n\n  def __call__(self, p):\n    self.step(p)\n

    Step function inside tv.v:

    @ti.kernel\ndef step(self, p: ti.template(), W: ti.f32):\n  for i in range(p.shape[0]):\n    p1 = p[i]\n    if p1[i].active == 0: continue\n    for j in range(p.shape[0]):\n      p2 = p[j]\n      if i == j & p2[j].active == 0: continue\n      s = self.tv.s.vera_s[p1.species, p2.species]\n      d = p1.pos - p2.pos\n      if d < s.radius:\n        # p1 & p2 are neighbours\n    p[i].vel += W * ...\n    p[i].pos += W * ...\n
    "},{"location":"guide/#pixels-tvpx","title":"Pixels (tv.px)","text":"

    T\u00f6lvera has a drawing module that is similar in design to p5.js. This example draws a rectangle in the middle of the window:

    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        draw()\n        return tv.px\n\nif __name__ == '__main__':\n    run(main)\n

    It mainly features shape primitives and blend modes. Pixels can also be thought of as fields and used as part of a vera, as tv.v.slime does to deposit particles onto a pheromone trail. Due to this flexibility, drawing and visualisation can impact behaviour and create more mappings and feedback loops between model states.

    "},{"location":"guide/#gpu-programming-tvti","title":"GPU Programming (tv.ti)","text":"

    T\u00f6lvera does most of its work on the GPU thanks to Taichi (tv.ti). Taichi is a domain-specific language (DSL) embedded in Python that supports multiple backends (CUDA, OpenGL, Vulkan, Metal). Taichi programs are distinguished by three levels of scope: regular Python scope, kernel scope (@ti.kernel) and function scope (@ti.func). Python scope can call kernels, and kernels and functions can call functions. In Taichi scope, top-level for loops are automatically parallelised. It can also run headless (without a window), and provides a C runtime and ahead-of-time (AOT) compilation for deployment in non-Python programs. It also interoperates with NumPy and PyTorch for ML integrations.

    "},{"location":"guide/#open-sound-control-tvosc","title":"Open Sound Control (tv.osc)","text":"

    OSC in T\u00f6lvera is handled by iipyper, a package specifically designed for working with artificial intelligence. When creating state, OSC endpoints can be automatically added via the \"osc\" option. For custom OSC endpoints, the tv.osc.map decorators can be used to add senders and receivers of different varieties. Here are two examples, one of receiving two arguments x and y, and another of receiving a vector of length ten:

    @tv.osc.map.receive_args(x=(0.,0.,1.), y=(0.,0.,1.), count=5)\ndef my_args(x: float, y: float):\n  print(f\"Receiving args: {x} {y}\")\n\n@tv.osc.map.receive_list(vector=(0.,0.,1.), length=10, count=5)\ndef my_list(vector: list[float]):\n  print(f\"Received list: {vector}\")\n

    The count decorator argument can be used to rate limit how often an endpoint's Python function runs, relative to the number of frames elapsing. T\u00f6lvera OSC mappings can be exported to JSON and XML, and they can also generate patches for Max/MSP and Pure Data, enabling rapid integration with other software.

    Open Sound Control map (`tv.osc.map`) client generated by T\u00f6lvera for Pure Data (Pd) based on the code example above."},{"location":"guide/#interactive-machine-learning-tviml","title":"Interactive Machine Learning (tv.iml)","text":"

    Interactive machine learning is achieved via the anguilla Python package. T\u00f6lvera features a global dictionary of IML instances at tv.iml, similarly to state (tv.s). IML can be used for a wide range of purposes in T\u00f6lvera, including creating internal feedback loops, for example between tv.v.flock's position and species rules states:

    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}\n

    The above example uses fun2fun, meaning the input and output methods specified in the io option will be run in the background by T\u00f6lvera. These are to_vec and from_vec, built-in methods that serialise and deserialise state to/from 1D arrays making them suitable for use as IML input/output vectors. To facilitate automatic routing of IML inputs and outputs, there are nine types in which the input and output can be either a vector, function or OSC endpoint: vec2vec, vec2fun, vec2osc, fun2vec, fun2fun, fun2osc, osc2vec, osc2fun, and osc2osc. Notably, these IML maps can be trained and updated on-the-fly, providing more variation in prolonged usage.

    The image below demonstrates how T\u00f6lvera's state and drawing capabilities can be used to enable real-time visualisation of IML mappings.

    Interactive machine learning (`tv.iml`) real-time map visualisation for input size two (XY axes) and output size three (pixel RGB). Input-output pairs are shown as white circles. This example demonstrates anguilla's ripple interpolator."},{"location":"guide/#computer-vision-tvcv","title":"Computer Vision (tv.cv)","text":"

    T\u00f6lvera integrates with OpenCV and Mediapipe to enable exploration of computer vision and tracking of hands, face and full body pose. See examples.

    "},{"location":"guide/#command-line-arguments","title":"Command-line arguments","text":"

    When T\u00f6lvera is instanced, a global kwargs dictionary is passed down through the various modules that allows setting of flags and parameters.

    "},{"location":"guide/#tolvera-module-python-m-tolvera","title":"T\u00f6lvera Module (python -m tolvera)","text":"

    All arguments below can be applied, and in addition:

    --demo  Run a tv.v.flock() demo.\n--help  Print a help message.\n

    See also sketchbook in experiments.

    "},{"location":"guide/#tolvera-context-tvctx","title":"T\u00f6lvera Context (tv.ctx)","text":"
    --x             Width in pixels. Defaults to 1920.\n--y             Height in pixels. Defaults to 1080.\n
    "},{"location":"guide/#tolvera-instance-tv","title":"T\u00f6lvera Instance (tv)","text":"
    --name          Name of T\u00f6lvera instance. Defaults to \"T\u00f6lvera\".\n--speed         Global speed scalar. Defaults to 1.\n--particles     Number of particles. Defaults to 1024. Aliased to tv.pn.\n--species       Number of species. Defaults to 4. Aliased to tv.sn.\n--substep       Number of substeps of render function. Defaults to 1.\n
    "},{"location":"guide/#taichi-tvti","title":"Taichi (tv.ti)","text":"
    --gpu        GPU architecture to run on. Defaults to \"vulkan\".\n--cpu        Run on CPU instead of GPU. Defaults to False.\n--fps        Frames per second. Defaults to 120.\n--seed       Random seed. Defaults to int(time.time()).\n--headless   False\n--name       Instance name. Defaults to \"T\u00f6lvera\".\n
    "},{"location":"guide/#pixels-tvpx_1","title":"Pixels (tv.px)","text":"
    --polygon_mode  Polygon drawing mode. Defaults to 'crossing'.\n--brightness    Brightness scalar. Defaults to 1.\n
    "},{"location":"guide/#open-sound-control-tvosc_1","title":"Open Sound Control (tv.osc)","text":"
    --osc               Enable OSC. Defaults to False.\n--host              OSC Host IP. Defaults to \"127.0.0.1\".\n--client            OSC Client IP. Defaults to \"127.0.0.1\".\n--client_name       OSC client name. Defaults to self.ctx.name_clean.\n--receive_port      OSC host port. Defaults to 5001.\n--send_port         OSC client port. Defaults to 5000.\n--osc_trace         Print all OSC messages. Defaults to False.\n--osc_verbose       Verbose printing of iipyper. Defaults to False.\n--create_patch      Create a Max or Pd patch based on tv.osc.map. Defaults to False.\n--patch_type        Type of patch to create. Defaults to \"Pd\".\n--patch_filepath    Filepath of patch. Defaults to self.client_name.\n--export_patch      Export patch schema to XML, JSON or both. Defaults to None.\n
    "},{"location":"guide/#interactive-machine-learning-tviml_1","title":"Interactive Machine Learning (tv.iml)","text":"
    --update_rate           Rate of IML updates relative to FPS. Default to 10.\n--config                anguilla instance configuration. Default to {}.\n--map_kw                anguilla.map kwargs. Default to {}.\n--infun_kw              Input method kwargs. Default to {}.\n--outfun_kw             Output method kwargs. Default to {}.\n--randomise             IML randomisation. Default to False.\n--rand_pairs            IML randomisation. Default to 32.\n--rand_input_weight     IML input randomisation weight. Default to None.\n--rand_output_weight    IML output randomisation weight. Default to None.\n--rand_method           IML randomisation method. Default to \"rand\".\n--rand_kw               IML randomisation kwargs. Default to {}.\n--lag                   Lag value updates. Default to False.\n--lag_coef              Lag coefficient. Default to 0.5.\n--name                  Name of IML instance. Default to None.\n
    "},{"location":"guide/#computer-vision-tvcv_1","title":"Computer Vision (tv.cv)","text":"
    --camera            Enable camera. Defaults to False.\n--device            OpenCV device index. Defaults to 0.\n--substeps          Number of substeps for reading camera frames. Defaults to 2.\n--colormode         Color channels. Defaults to 'rgba'.\n--ggui_fps_limit    FPS limit of Taichi GGUI. Defaults to 120fps.\n--hands             Enable hand tracking. Defaults to False.\n--pose              Enable pose tracking. Defaults to False.\n--face              Enable face tracking. Defaults to False.\n--face_mesh         Enable face mesh tracking. Defaults to False.\n
    "},{"location":"publications/","title":"Publications","text":""},{"location":"publications/#publications","title":"Publications","text":"

    Page under construction \ud83d\udea7. For now, see references.bib.

    "},{"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.x = kwargs.get(\"x\", 1920)\n        self.y = kwargs.get(\"y\", 1080)\n        self.ti = Taichi(self, **kwargs)\n        self.i = ti.field(ti.i32, ())\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        self.pose = kwargs.get(\"pose\", False)\n        self.face = kwargs.get(\"face\", False)\n        self.face_mesh = kwargs.get(\"face_mesh\", 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            if self.hands:\n                self.hands = MPHands(self, **kwargs)\n            if self.pose:\n                self.pose = MPPose(self, **kwargs)\n            if self.face:\n                self.face = MPFace(self, **kwargs)\n            if self.face_mesh:\n                self.face_mesh = MPFaceMesh(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                self.step(f, **kwargs)\n\n    def step(self, f, **kwargs):\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[None] += 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.x = kwargs.get(\"x\", 1920)\n    self.y = kwargs.get(\"y\", 1080)\n    self.ti = Taichi(self, **kwargs)\n    self.i = ti.field(ti.i32, ())\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    self.pose = kwargs.get(\"pose\", False)\n    self.face = kwargs.get(\"face\", False)\n    self.face_mesh = kwargs.get(\"face_mesh\", 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        if self.hands:\n            self.hands = MPHands(self, **kwargs)\n        if self.pose:\n            self.pose = MPPose(self, **kwargs)\n        if self.face:\n            self.face = MPFace(self, **kwargs)\n        if self.face_mesh:\n            self.face_mesh = MPFaceMesh(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            self.step(f, **kwargs)\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 "},{"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[\"embed_input\"] = \"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[\"embed_input\"] = \"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/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    ppos: ti.math.vec2\n    pvel: 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.C = CONSTS({\"COLL_RAD\": (ti.f32, 10.0)})\n        self.tv.s.collisions_p = {\n            'state': {\n                'collision': (ti.i32, 0, 1),\n                'dpos': (ti.math.vec2, 0., 1.),\n                'dvel': (ti.math.vec2, 0., 1.),\n            },\n            'shape': self.n,\n        }\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        j = 0\n        for i in range(self.n):\n            if self.field[i] == 0.0: continue\n            self.toroidal_wrap(i)\n            self.limit_speed(i)\n            self.detect_collisions(i, self.C.COLL_RAD)\n            self.update_prev(i)\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.func\n    def detect_collisions(self, i: ti.i32, radius: ti.f32):\n        \"\"\"Detect collisions between particles.\n\n        TODO: Merge deltas into @ti.dataclass, or reimplement Particle.field as tv.s?\n        TODO: Multiple collision states? Collided, Colliding, etc.\n        TODO: Detect collisions between external objects.\n\n        Args:\n            i (ti.i32): Particle index.\n            radius (ti.f32): Collision radius.\n        \"\"\"\n        for j in range(self.n):\n            p1, p2 = self.tv.p.field[i], self.tv.p.field[j]\n            if p2.active == 0: continue\n            dist = p1.pos - p2.pos\n            if dist.norm() < radius:\n                pdist = p1.ppos - p2.ppos\n                dpos = ti.abs(pdist - dist)\n                dvel = ti.abs((p1.pvel - p2.pvel) - (p1.vel - p2.vel))\n                self.tv.s.collisions_p[i].dpos = dpos\n                self.tv.s.collisions_p[i].dvel = dvel\n                if pdist.norm() > radius:\n                    self.tv.s.collisions_p[i].collision = 1\n                else:\n                    self.tv.s.collisions_p[i].collision = 0\n\n    @ti.func\n    def update_prev(self, i: ti.i32):\n        \"\"\"Update the previous position and velocity of a particle.\n\n        Args:\n            i (ti.i32): Particle index.\n        \"\"\"\n        self.field[i].ppos = self.field[i].pos\n        self.field[i].pvel = self.field[i].vel\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()\n\n    @ti.kernel\n    def set_total_active(self, total: ti.i32):\n        \"\"\"Set the total number of active particles.\n\n        Args:\n            total (ti.i32): Total active particles.\n        \"\"\"\n        for i in range(self.field.shape[0]):\n            if i >= total:\n                self.field[i].active = 0\n            else:\n                self.field[i].active = 1\n\n    @ti.kernel\n    def set_total_active_amount(self, total: ti.i32, amount: ti.f32):\n        \"\"\"Set the total number of active particles.\n\n        Args:\n            total (ti.i32): Total active particles.\n            amount (ti.f32): Amount of activity.\n        \"\"\"\n        for i in range(self.field.shape[0]):\n            if i >= total:\n                self.field[i].active = 0\n            else:\n                self.field[i].active = amount\n\n    @ti.kernel\n    def set_species_total_active(self, i: ti.i32, total: ti.i32):\n        \"\"\"Set the total number of active particles for a species.\n\n        Args:\n            i (ti.i32): Species index.\n            total (ti.i32): Total active particles.\n        \"\"\"\n        for j in range(self.field.shape[0]):\n            if self.field[j].species == i:\n                if j >= total:\n                    self.field[j].active = 0\n                else:\n                    self.field[j].active = 1\n\n    @ti.kernel\n    def set_species_total_active_amount(self, i: ti.i32, total: ti.i32, amount: ti.f32):\n        \"\"\"Set particle activity amount of a species.\n\n        Args:\n            i (ti.i32): Species index.\n            total: (ti.i32): Total number of active particles.\n            amount (ti.i32): Amount of activity.\n        \"\"\"\n        for j in range(self.field.shape[0]):\n            if self.field[j].species == i:\n                if j >= total:\n                    self.field[j].active = 0\n                else:\n                    self.field[j].active = amount\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.C = CONSTS({\"COLL_RAD\": (ti.f32, 10.0)})\n    self.tv.s.collisions_p = {\n        'state': {\n            'collision': (ti.i32, 0, 1),\n            'dpos': (ti.math.vec2, 0., 1.),\n            'dvel': (ti.math.vec2, 0., 1.),\n        },\n        'shape': self.n,\n    }\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.detect_collisions","title":"detect_collisions(i, radius)","text":"

    Detect collisions between particles.

    TODO: Merge deltas into @ti.dataclass, or reimplement Particle.field as tv.s? TODO: Multiple collision states? Collided, Colliding, etc. TODO: Detect collisions between external objects.

    Parameters:

    Name Type Description Default i i32

    Particle index.

    required radius f32

    Collision radius.

    required Source code in src/tolvera/particles.py
    @ti.func\ndef detect_collisions(self, i: ti.i32, radius: ti.f32):\n    \"\"\"Detect collisions between particles.\n\n    TODO: Merge deltas into @ti.dataclass, or reimplement Particle.field as tv.s?\n    TODO: Multiple collision states? Collided, Colliding, etc.\n    TODO: Detect collisions between external objects.\n\n    Args:\n        i (ti.i32): Particle index.\n        radius (ti.f32): Collision radius.\n    \"\"\"\n    for j in range(self.n):\n        p1, p2 = self.tv.p.field[i], self.tv.p.field[j]\n        if p2.active == 0: continue\n        dist = p1.pos - p2.pos\n        if dist.norm() < radius:\n            pdist = p1.ppos - p2.ppos\n            dpos = ti.abs(pdist - dist)\n            dvel = ti.abs((p1.pvel - p2.pvel) - (p1.vel - p2.vel))\n            self.tv.s.collisions_p[i].dpos = dpos\n            self.tv.s.collisions_p[i].dvel = dvel\n            if pdist.norm() > radius:\n                self.tv.s.collisions_p[i].collision = 1\n            else:\n                self.tv.s.collisions_p[i].collision = 0\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()\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_species_total_active","title":"set_species_total_active(i, total)","text":"

    Set the total number of active particles for a species.

    Parameters:

    Name Type Description Default i i32

    Species index.

    required total i32

    Total active particles.

    required Source code in src/tolvera/particles.py
    @ti.kernel\ndef set_species_total_active(self, i: ti.i32, total: ti.i32):\n    \"\"\"Set the total number of active particles for a species.\n\n    Args:\n        i (ti.i32): Species index.\n        total (ti.i32): Total active particles.\n    \"\"\"\n    for j in range(self.field.shape[0]):\n        if self.field[j].species == i:\n            if j >= total:\n                self.field[j].active = 0\n            else:\n                self.field[j].active = 1\n
    "},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_species_total_active_amount","title":"set_species_total_active_amount(i, total, amount)","text":"

    Set particle activity amount of a species.

    Parameters:

    Name Type Description Default i i32

    Species index.

    required total i32

    (ti.i32): Total number of active particles.

    required amount i32

    Amount of activity.

    required Source code in src/tolvera/particles.py
    @ti.kernel\ndef set_species_total_active_amount(self, i: ti.i32, total: ti.i32, amount: ti.f32):\n    \"\"\"Set particle activity amount of a species.\n\n    Args:\n        i (ti.i32): Species index.\n        total: (ti.i32): Total number of active particles.\n        amount (ti.i32): Amount of activity.\n    \"\"\"\n    for j in range(self.field.shape[0]):\n        if self.field[j].species == i:\n            if j >= total:\n                self.field[j].active = 0\n            else:\n                self.field[j].active = amount\n
    "},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_total_active","title":"set_total_active(total)","text":"

    Set the total number of active particles.

    Parameters:

    Name Type Description Default total i32

    Total active particles.

    required Source code in src/tolvera/particles.py
    @ti.kernel\ndef set_total_active(self, total: ti.i32):\n    \"\"\"Set the total number of active particles.\n\n    Args:\n        total (ti.i32): Total active particles.\n    \"\"\"\n    for i in range(self.field.shape[0]):\n        if i >= total:\n            self.field[i].active = 0\n        else:\n            self.field[i].active = 1\n
    "},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_total_active_amount","title":"set_total_active_amount(total, amount)","text":"

    Set the total number of active particles.

    Parameters:

    Name Type Description Default total i32

    Total active particles.

    required amount f32

    Amount of activity.

    required Source code in src/tolvera/particles.py
    @ti.kernel\ndef set_total_active_amount(self, total: ti.i32, amount: ti.f32):\n    \"\"\"Set the total number of active particles.\n\n    Args:\n        total (ti.i32): Total active particles.\n        amount (ti.f32): Amount of activity.\n    \"\"\"\n    for i in range(self.field.shape[0]):\n        if i >= total:\n            self.field[i].active = 0\n        else:\n            self.field[i].active = amount\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    j = 0\n    for i in range(self.n):\n        if self.field[i] == 0.0: continue\n        self.toroidal_wrap(i)\n        self.limit_speed(i)\n        self.detect_collisions(i, self.C.COLL_RAD)\n        self.update_prev(i)\n        self.active_indexes[j] = i\n        j += 1\n    self.active_count[None] = j\n
    "},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.update_prev","title":"update_prev(i)","text":"

    Update the previous position and velocity of a particle.

    Parameters:

    Name Type Description Default i i32

    Particle index.

    required Source code in src/tolvera/particles.py
    @ti.func\ndef update_prev(self, i: ti.i32):\n    \"\"\"Update the previous position and velocity of a particle.\n\n    Args:\n        i (ti.i32): Particle index.\n    \"\"\"\n    self.field[i].ppos = self.field[i].pos\n    self.field[i].pvel = self.field[i].vel\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[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[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[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[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        self.pose = context.pose\n        self.face = context.face\n        self.face_mesh = context.face_mesh\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            if self.hands:\n                self.hands.px = self.px\n            if self.pose:\n                self.pose.px = self.px\n            if self.face:\n                self.face.px = self.px\n            if self.face_mesh:\n                self.face_mesh.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        if self.hands:\n            self.hands.px = self.px\n        if self.pose:\n            self.pose.px = self.px\n        if self.face:\n            self.face.px = self.px\n        if self.face_mesh:\n            self.face_mesh.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    self.pose = context.pose\n    self.face = context.face\n    self.face_mesh = context.face_mesh\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/mp/face/","title":"Face","text":""},{"location":"reference/tolvera/mp/face/#tolvera.mp.face.FaceKeyPoint","title":"FaceKeyPoint","text":"

    Bases: IntEnum

    The enum type of the six face detection key points.

    Source code in src/tolvera/mp/face.py
    class FaceKeyPoint(enum.IntEnum):\n  \"\"\"The enum type of the six face detection key points.\"\"\"\n  RIGHT_EYE = 0\n  LEFT_EYE = 1\n  NOSE_TIP = 2\n  MOUTH_CENTER = 3\n  RIGHT_EAR_TRAGION = 4\n  LEFT_EAR_TRAGION = 5\n
    "},{"location":"reference/tolvera/mp/face/#tolvera.mp.face.MPFace","title":"MPFace","text":"Source code in src/tolvera/mp/face.py
    @ti.data_oriented\nclass MPFace:\n    def __init__(self, context, **kwargs) -> None:\n        self.ctx = context\n        self.kwargs = kwargs\n        self.n_points = 6\n        self.max_faces = kwargs.get('max_faces', 4)\n\n        self.config = {\n            'min_detection_confidence': kwargs.get('detection_con', .5),\n            'model_selection': kwargs.get('model_selection', 0),\n        }\n\n        \"\"\"\n        TODO: add bbox as separate tv.s.faces_bbox?\n            format: RELATIVE_BOUNDING_BOX\n            relative_bounding_box {\n                xmin: 0.482601523\n                ymin: 0.402242899\n                width: 0.162447035\n                height: 0.2887941\n            }\n        \"\"\"\n\n        self.faces_np = {\n            'pxnorm':np.zeros((self.max_faces, self.n_points, 2), np.float32),\n            'px':np.zeros((self.max_faces, self.n_points, 2), np.float32),\n        }\n        self.ctx.s.faces = {\n            'state': {\n                'pxnorm': (ti.math.vec2, 0.0, 1.0),\n                'px': (ti.math.vec2, 0.0, 1.0),\n                # 'metres': (ti.math.vec3, 0.0, 1.0), # face_world_landmarks\n            },\n            'shape': (self.max_faces, self.n_points)\n        }\n\n        self.mpFace = mp.solutions.face_detection\n        self.face = self.mpFace.FaceDetection(**self.config)\n        self.detected = ti.field(ti.i32, shape=())\n\n        self.updater = Updater(self.detect, kwargs.get('face_detect_rate', 10))\n\n    def detect(self, frame=None):\n        if frame is None: return\n        self.results = self.face.process(frame)\n        if self.results.detections is None:\n            self.ctx.s.faces.fill(0.)\n            self.detected[None] = -1\n            return\n\n        if self.results.detections:\n            for i, face in enumerate(self.results.detections):\n                for j, lm in enumerate(face.location_data.relative_keypoints):\n                    pxnorm = np.array([1-lm.x, 1-lm.y])\n                    px = np.array([self.ctx.x*(1-lm.x), self.ctx.y*(1-lm.y)])\n                    self.faces_np['pxnorm'][i, j] = pxnorm\n                    self.faces_np['px'][i, j] = px\n        self.ctx.s.faces.set_from_nddict(self.faces_np)\n\n        self.detected[None] = len(self.results.detections)\n\n    @ti.kernel\n    def draw(self):\n        if self.detected[None] > 0:\n            self.draw_face_lms(5, ti.Vector([1, 1, 1, 1]))\n\n    @ti.func\n    def draw_face_lms(self, r, rgba):\n        for i, lm in ti.ndrange(self.detected[None], self.n_conns):\n            self.draw_lm(i, lm, r, rgba)\n\n    @ti.func\n    def draw_lm(self, face: ti.i32, lm: ti.i32, r: ti.i32, rgba: ti.math.vec4):\n        px = self.ctx.s.faces[face, lm].px\n        cx = ti.cast(px.x, ti.i32)\n        cy = ti.cast(px.y, ti.i32)\n        self.px.circle(cx, cy, r, rgba)\n\n    def landmark_name_from_index(self, index):\n        return FaceKeyPoint(index).name\n\n    def landmark_index_from_name(self, name):\n        return FaceKeyPoint[name].value\n\n    @ti.kernel\n    def get_landmark(self, face: ti.i32, landmark: ti.i32) -> ti.math.vec2:\n        return self.ctx.s.faces[landmark].px\n\n    def __call__(self, frame):\n        self.updater(frame)\n
    "},{"location":"reference/tolvera/mp/face/#tolvera.mp.face.MPFace.config","title":"config = {'min_detection_confidence': kwargs.get('detection_con', 0.5), 'model_selection': kwargs.get('model_selection', 0)} instance-attribute","text":"add bbox as separate tv.s.faces_bbox?

    format: RELATIVE_BOUNDING_BOX relative_bounding_box { xmin: 0.482601523 ymin: 0.402242899 width: 0.162447035 height: 0.2887941 }

    "},{"location":"reference/tolvera/mp/face_mesh/","title":"Face mesh","text":""},{"location":"reference/tolvera/mp/face_mesh_connections/","title":"Face mesh connections","text":"

    MediaPipe FaceMesh connections.

    "},{"location":"reference/tolvera/mp/hands/","title":"Hands","text":""},{"location":"reference/tolvera/mp/hands/#tolvera.mp.hands.HandLandmark","title":"HandLandmark","text":"

    Bases: IntEnum

    The 21 hand landmarks.

    Source code in src/tolvera/mp/hands.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/mp/pose/","title":"Pose","text":""},{"location":"reference/tolvera/mp/pose/#tolvera.mp.pose.PoseLandmark","title":"PoseLandmark","text":"

    Bases: IntEnum

    The 33 pose landmarks.

    Source code in src/tolvera/mp/pose.py
    class PoseLandmark(enum.IntEnum):\n  \"\"\"The 33 pose landmarks.\"\"\"\n  NOSE = 0\n  LEFT_EYE_INNER = 1\n  LEFT_EYE = 2\n  LEFT_EYE_OUTER = 3\n  RIGHT_EYE_INNER = 4\n  RIGHT_EYE = 5\n  RIGHT_EYE_OUTER = 6\n  LEFT_EAR = 7\n  RIGHT_EAR = 8\n  MOUTH_LEFT = 9\n  MOUTH_RIGHT = 10\n  LEFT_SHOULDER = 11\n  RIGHT_SHOULDER = 12\n  LEFT_ELBOW = 13\n  RIGHT_ELBOW = 14\n  LEFT_WRIST = 15\n  RIGHT_WRIST = 16\n  LEFT_PINKY = 17\n  RIGHT_PINKY = 18\n  LEFT_INDEX = 19\n  RIGHT_INDEX = 20\n  LEFT_THUMB = 21\n  RIGHT_THUMB = 22\n  LEFT_HIP = 23\n  RIGHT_HIP = 24\n  LEFT_KNEE = 25\n  RIGHT_KNEE = 26\n  LEFT_ANKLE = 27\n  RIGHT_ANKLE = 28\n  LEFT_HEEL = 29\n  RIGHT_HEEL = 30\n  LEFT_FOOT_INDEX = 31\n  RIGHT_FOOT_INDEX = 32\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        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 pascal_to_camel(self, s):\n        return s[0].lower() + s[1:]\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    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        print('send_args_func',hints, f_p.items())\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 i, (k, p) in enumerate(f_p.items()):\n                p_def, p_min, p_max = f_p[k][0], f_p[k][1], f_p[k][2]\n                print(i, k, p, p_def, p_min, p_max)\n                params.append(\n                    {\n                        \"label\": k,\n                        \"data\": hints[i].__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/diffline/","title":"Diffline","text":"

    Differential line

    "},{"location":"reference/tolvera/vera/diffline/#tolvera.vera.diffline.DifferentialLine","title":"DifferentialLine","text":"Source code in src/tolvera/vera/diffline.py
    @ti.data_oriented\nclass DifferentialLine:\n    def __init__(self, tolvera, **kwargs) -> None:\n        self.tv = tolvera\n        self.kwargs = kwargs\n        self.CONSTS = CONSTS({\"dt\": (ti.f32, 0.1)})\n        \"\"\"\n        state\n            max segments\n            tv.s.segments\n        init\n            starting shape? circle etc.\n        step\n            for s in segments\n                calculate the direction of growth\n                apply angle variation and growth rate\n                maintain minimum distance between points\n        composition\n            want to be able to e.g. flock the segments\n        \"\"\"\n        self.tv.s.diffline_s = {\n            \"state\": {\n                \"sigma\": (ti.f32, 0.0, 100.0),\n            },\n            \"shape\": self.tv.sn,\n            \"randomise\": True,\n        }\n    @ti.kernel\n    def step(self, particles: ti.template(), weight: ti.f32):\n        for i in particles:\n            pass\n    @ti.func\n    def proc(self, x, y, z) -> ti.Vector:\n        pass\n    def randomise(self):\n        self.tv.s.diffline_s.randomise()\n    def __call__(self, particles: ti.template(), weight: ti.f32=1.0):\n        self.step(particles, weight)\n
    "},{"location":"reference/tolvera/vera/diffline/#tolvera.vera.diffline.DifferentialLine.CONSTS","title":"CONSTS = CONSTS({'dt': (ti.f32, 0.1)}) instance-attribute","text":"

    state max segments tv.s.segments init starting shape? circle etc. step for s in segments calculate the direction of growth apply angle variation and growth rate maintain minimum distance between points composition want to be able to e.g. flock the segments

    "},{"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.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 * p1.speed * p1.active \n                particles[i].pos += particles[i].vel\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.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 * p1.speed * p1.active \n            particles[i].pos += particles[i].vel\n        self.tv.s.flock_p[i] = self.tv.s.flock_p.struct(\n            separate, align, cohere, nearby\n        )\n
    "},{"location":"reference/tolvera/vera/flock2/","title":"Flock2","text":"

    Flock behaviour based on the Boids algorithm.

    "},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2","title":"Flock2","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/flock2.py
    @ti.data_oriented\nclass Flock2:\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.flock2_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.flock2_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.0, self.tv.p.n - 1),\n            },\n            \"shape\": self.tv.pn,\n            \"osc\": (\"get\"),\n            \"randomise\": False,\n        }\n        self.tv.s.flock2_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.flock2_s.randomise()\n\n    @ti.func\n    def set_mag(self, v: ti.template(), mag: ti.f32):\n        return (v / v.norm()) * mag\n\n    @ti.func\n    def set_mag2(self, v: ti.template(), mag: ti.f32):\n        return (v / v.norm())# * mag\n\n    @ti.kernel\n    def step(self, particles: ti.template(), weight: ti.f32, algotype: ti.i32, single_species: ti.i32):\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            p1 = particles[i]\n            if p1.active == 0:\n                continue\n            if single_species > -1:\n                if p1.species != single_species:\n                    continue\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.flock2_s.struct()\n            for j in range(n):\n                p2 = particles[j]\n                if i == j and p2.active == 0:\n                    continue\n                # if single_species > -1:\n                #     if p2.species != single_species:\n                #         continue\n                species = self.tv.s.flock2_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.flock2_dist[i, j].dist = p1.dist(p2).norm()\n                self.tv.s.flock2_dist[i, j].dist_wrap = dis_wrap_norm\n            if nearby > 0:\n                vel = ti.Vector([0.0, 0.0])\n                if algotype == 0:\n                    align = (align / nearby) * p1.active * species.align\n                    separate = (\n                        (separate / nearby) * p1.active * ti.math.max(species.separate, 0.2)\n                    )\n                    cohere = ((cohere / nearby) - p1.pos) * p1.active * species.cohere\n                    vel = (separate + align + cohere).normalized()\n                    # vel = separate + align + cohere\n                elif algotype == 1:\n                    align = self.set_mag((align / nearby), p1.speed) * species.align * p1.active\n                    separate = self.set_mag((separate / nearby), p1.speed) * species.separate * p1.active\n                    cohere = self.set_mag(((cohere / nearby) - p1.pos), p1.speed) * species.cohere * p1.active\n                    vel = separate + align + cohere\n                elif algotype == 2:\n                    align = self.set_mag2((align / nearby), p1.speed) * species.align * p1.active\n                    separate = self.set_mag2((separate / nearby), p1.speed) * species.separate * p1.active\n                    cohere = self.set_mag2(((cohere / nearby) - p1.pos), p1.speed) * species.cohere * p1.active\n                    vel = separate + align + cohere\n                particles[i].vel += vel * weight\n                particles[i].pos += particles[i].vel * p1.speed * p1.active * weight\n            self.tv.s.flock2_p[i] = self.tv.s.flock2_p.struct(\n                separate, align, cohere, nearby\n            )\n\n    def __call__(self, particles, weight: ti.f32=1.0, algotype: ti.f32=0, single_species: ti.i32=-1):\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, algotype, single_species)\n
    "},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2.__call__","title":"__call__(particles, weight=1.0, algotype=0, single_species=-1)","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/flock2.py
    def __call__(self, particles, weight: ti.f32=1.0, algotype: ti.f32=0, single_species: ti.i32=-1):\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, algotype, single_species)\n
    "},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2.__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/flock2.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.flock2_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.flock2_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.0, self.tv.p.n - 1),\n        },\n        \"shape\": self.tv.pn,\n        \"osc\": (\"get\"),\n        \"randomise\": False,\n    }\n    self.tv.s.flock2_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/flock2/#tolvera.vera.flock2.Flock2.randomise","title":"randomise()","text":"

    Randomise the Flock behaviour.

    Source code in src/tolvera/vera/flock2.py
    def randomise(self):\n    \"\"\"Randomise the Flock behaviour.\"\"\"\n    self.tv.s.flock2_s.randomise()\n
    "},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2.step","title":"step(particles, weight, algotype, single_species)","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/flock2.py
    @ti.kernel\ndef step(self, particles: ti.template(), weight: ti.f32, algotype: ti.i32, single_species: ti.i32):\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        p1 = particles[i]\n        if p1.active == 0:\n            continue\n        if single_species > -1:\n            if p1.species != single_species:\n                continue\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.flock2_s.struct()\n        for j in range(n):\n            p2 = particles[j]\n            if i == j and p2.active == 0:\n                continue\n            # if single_species > -1:\n            #     if p2.species != single_species:\n            #         continue\n            species = self.tv.s.flock2_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.flock2_dist[i, j].dist = p1.dist(p2).norm()\n            self.tv.s.flock2_dist[i, j].dist_wrap = dis_wrap_norm\n        if nearby > 0:\n            vel = ti.Vector([0.0, 0.0])\n            if algotype == 0:\n                align = (align / nearby) * p1.active * species.align\n                separate = (\n                    (separate / nearby) * p1.active * ti.math.max(species.separate, 0.2)\n                )\n                cohere = ((cohere / nearby) - p1.pos) * p1.active * species.cohere\n                vel = (separate + align + cohere).normalized()\n                # vel = separate + align + cohere\n            elif algotype == 1:\n                align = self.set_mag((align / nearby), p1.speed) * species.align * p1.active\n                separate = self.set_mag((separate / nearby), p1.speed) * species.separate * p1.active\n                cohere = self.set_mag(((cohere / nearby) - p1.pos), p1.speed) * species.cohere * p1.active\n                vel = separate + align + cohere\n            elif algotype == 2:\n                align = self.set_mag2((align / nearby), p1.speed) * species.align * p1.active\n                separate = self.set_mag2((separate / nearby), p1.speed) * species.separate * p1.active\n                cohere = self.set_mag2(((cohere / nearby) - p1.pos), p1.speed) * species.cohere * p1.active\n                vel = separate + align + cohere\n            particles[i].vel += vel * weight\n            particles[i].pos += particles[i].vel * p1.speed * p1.active * weight\n        self.tv.s.flock2_p[i] = self.tv.s.flock2_p.struct(\n            separate, align, cohere, nearby\n        )\n
    "},{"location":"reference/tolvera/vera/flock_shiffman/","title":"Flock shiffman","text":"

    taichi flocking simulation credits: https://www.youtube.com/watch?v=mhjuuHl6qHM

    "},{"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.centripetal","title":"centripetal(particles, centre, direction, weight)","text":"

    Apply a centripetal force to the particles.

    Parameters:

    Name Type Description Default particles template

    Particles.

    required centre vec2

    Centripetal centre.

    required direction i32

    Centripetal direction.

    required weight f32

    Centripetal weight.

    required Source code in src/tolvera/vera/forces.py
    @ti.kernel\ndef centripetal(particles: ti.template(), centre: ti.math.vec2, direction: ti.i32, weight: ti.f32):\n    \"\"\"Apply a centripetal force to the particles.\n\n    Args:\n        particles (ti.template): Particles.\n        centre (ti.math.vec2): Centripetal centre.\n        direction (ti.i32): Centripetal direction.\n        weight (ti.f32): Centripetal 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 += centripetal_particle(p, centre, direction, weight)\n
    "},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.centripetal_particle","title":"centripetal_particle(p, centre, direction, weight)","text":"

    Apply a centripetal force to a particle.

    Parameters:

    Name Type Description Default p Particle

    Individual particle.

    required centre vec2

    Centripetal centre.

    required direction i32

    Centripetal direction.

    required weight f32

    Centripetal weight.

    required

    Returns:

    Type Description vec2

    ti.math.vec2: Centripetal velocity.

    Source code in src/tolvera/vera/forces.py
    @ti.func\ndef centripetal_particle(p: ti.template(), centre: ti.math.vec2, direction: ti.i32, weight: ti.f32) -> ti.math.vec2:\n    \"\"\"Apply a centripetal force to a particle.\n\n    Args:\n        p (Particle): Individual particle.\n        centre (ti.math.vec2): Centripetal centre.\n        direction (ti.i32): Centripetal direction.\n        weight (ti.f32): Centripetal weight.\n\n    Returns:\n        ti.math.vec2: Centripetal velocity.\n    \"\"\"\n    r = p.pos - centre\n    if direction == 0:\n        r = -r\n    v_perp = ti.Vector([-r[1], r[0]])\n    norm = v_perp.norm() + 1e-5\n    v_perp_normalized = v_perp / norm\n    speed = p.vel.norm()\n    new_vel = v_perp_normalized * speed * weight\n    return new_vel\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/geom/","title":"Geom","text":""},{"location":"reference/tolvera/vera/geom/#tolvera.vera.geom.cos","title":"cos(particles, frequency, magnitude, phase, axis)","text":"

    Apply a cosine wave force to particles.

    Parameters:

    Name Type Description Default particles template

    Particles.

    required frequency f32

    Frequency of the cosine wave.

    required magnitude f32

    Magnitude of the cosine wave.

    required phase f32

    Phase of the cosine wave.

    required axis i32

    Axis to apply the cosine wave to.

    required Source code in src/tolvera/vera/geom.py
    @ti.kernel\ndef cos(particles: ti.template(), frequency: ti.f32, magnitude: ti.f32, phase: ti.f32, axis: ti.i32):\n    \"\"\"Apply a cosine wave force to particles.\n\n    Args:\n        particles (ti.template): Particles.\n        frequency (ti.f32): Frequency of the cosine wave.\n        magnitude (ti.f32): Magnitude of the cosine wave.\n        phase (ti.f32): Phase of the cosine wave.\n        axis (ti.i32): Axis to apply the cosine wave to.\n    \"\"\"\n    for i in range(particles.field.shape[0]):\n        p = particles.field[i]\n        if p.active == 0:\n            continue\n        inc = magnitude * ti.cos(1/frequency * p.pos[axis] + phase)\n        particles.field[i].vel[axis] += inc\n
    "},{"location":"reference/tolvera/vera/geom/#tolvera.vera.geom.sin","title":"sin(particles, frequency, magnitude, phase, axis)","text":"

    Apply a sine wave force to particles.

    Parameters:

    Name Type Description Default particles template

    Particles.

    required frequency f32

    Frequency of the sine wave.

    required magnitude f32

    Magnitude of the sine wave.

    required phase f32

    Phase of the sine wave.

    required axis i32

    Axis to apply the sine wave to.

    required Source code in src/tolvera/vera/geom.py
    @ti.kernel\ndef sin(particles: ti.template(), frequency: ti.f32, magnitude: ti.f32, phase: ti.f32, axis: ti.i32):\n    \"\"\"Apply a sine wave force to particles.\n\n    Args:\n        particles (ti.template): Particles.\n        frequency (ti.f32): Frequency of the sine wave.\n        magnitude (ti.f32): Magnitude of the sine wave.\n        phase (ti.f32): Phase of the sine wave.\n        axis (ti.i32): Axis to apply the sine wave to.\n    \"\"\"\n    for i in range(particles.field.shape[0]):\n        p = particles.field[i]\n        if p.active == 0:\n            continue\n        inc = magnitude * ti.sin(1/frequency * p.pos[axis] + phase)\n        particles.field[i].vel[axis] += inc\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            particles[i].vel = (particles[i].vel + ti.Vector([fx, fy])) * self.CONSTS.V * weight * p1.speed * p1.active\n            particles[i].pos += particles[i].vel\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        particles[i].vel = (particles[i].vel + ti.Vector([fx, fy])) * self.CONSTS.V * weight * p1.speed * p1.active\n        particles[i].pos += particles[i].vel\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/

    "},{"location":"reference/tolvera/vera/viscek/","title":"Viscek","text":"

    Flocking behaviour based on the Viscek model.

    "},{"location":"reference/tolvera/vera/viscek/#tolvera.vera.viscek.Viscek","title":"Viscek","text":"

    Viscek flocking model

    Source code in src/tolvera/vera/viscek.py
    @ti.data_oriented\nclass Viscek:\n    \"\"\"Viscek flocking model\"\"\"\n    def __init__(self, tolvera, **kwargs):\n        self.tv = tolvera\n        self.kwargs = kwargs\n        self.CONSTS = CONSTS({\"MAX_RADIUS\": (ti.f32, 300.0)})\n        self.tv.s.viscek_s = {\n            \"state\": {\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.viscek_p = {\n            \"state\": {\n                \"theta\": (ti.f32, -np.pi, np.pi),\n            },\n            \"shape\": self.tv.pn,\n            \"osc\": (\"get\"),\n            \"randomise\": True,\n        }\n        self.tv.s.viscek_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\"\"\"\n        self.tv.s.viscek_s.randomise()\n\n    @ti.kernel\n    def step(self, particles: ti.template(), noise_weight: ti.f32, weight: ti.f32):\n        n = particles.shape[0]\n        for i in range(n):\n            if particles[i].active == 0:\n                continue\n            p1 = particles[i]\n            species = self.tv.s.viscek_s.struct()\n            n = 0\n            n_dir = ti.Vector([0.0, 0.0])\n            for j in range(n):\n                if i == j and particles[j].active == 0:\n                    continue\n                p2 = particles[j]\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                    n_dir += self.tv.s.viscek_p[j].theta\n                    n += 1\n            avg_dir = n_dir / n\n\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/viscek/#tolvera.vera.viscek.Viscek.__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/viscek.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/viscek/#tolvera.vera.viscek.Viscek.randomise","title":"randomise()","text":"

    Randomise

    Source code in src/tolvera/vera/viscek.py
    def randomise(self):\n    \"\"\"Randomise\"\"\"\n    self.tv.s.viscek_s.randomise()\n
    "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Intro","text":""},{"location":"#tolvera","title":"T\u00f6lvera","text":"

    T\u00f6lvera is a Python library designed for composing together and interacting with basal agencies, inspired by fields such as artificial life (ALife) and self-organising systems. It provides creative coding-style APIs that allow users to combine and compose various built-in behaviours, such as flocking, slime mold growth, and swarming, and also author their own. With built-in support for Open Sound Control (OSC) via iipyper and interactive machine learning (IML) via anguilla, T\u00f6lvera interfaces with and rapidly maps onto existing creative computing software and hardware, striving to be both an accessible and powerful tool for exploring diverse intelligence in artistic contexts.

    The word T\u00f6lvera is an Icelandic kenning based on t\u00f6lva meaning computer, from tala (number) and v\u00f6lva (prophetess), and vera (being), composed together as number being.

    We have employed T\u00f6lvera in various collaborative artistic works, including musical performances, compositions, and multimedia installations (see references.bib for peer-reviewed publications). T\u00f6lvera's role in these pieces has mainly been \"mappable behaviour engine\", where interface inputs can control T\u00f6lvera programs, and T\u00f6lvera runtime data can control interface outputs, in practically any combination. In this way, and to controllable degrees, T\u00f6lvera can contribute to the underlying dynamics of a given interactive scenario. It can also add a visual component, and equally has been used without projection in other works.

    T\u00f6lvera makes use of Taichi, a domain-specific language embedded in Python that enables parallelisation, and is experimental software subject to change.

    We would be happy to have you join us on our Discord server!

    "},{"location":"#showcase-examples","title":"Showcase & Examples","text":"

    Examples can be found at iil-examples/tolvera. See also the guide, reference and experiments pages.

    Visit the YouTube Playlist (if you'd like to add a video, please get in touch).

    "},{"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":"

    For development, we use poetry. 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":"#known-issues-limitations","title":"Known Issues & Limitations","text":""},{"location":"#contribute","title":"Contribute","text":"

    We welcome Pull Requests across all areas of the project:

    "},{"location":"#community","title":"Community","text":"

    To discuss T\u00f6lvera with developers and other users:

    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":"#citation","title":"Citation","text":"

    T\u00f6lvera is being written about and used in a number of contexts (see references.bib). The current canonical citation is our NIME 2024 paper:

    @inproceedings{armitageTolveraComposingBasal2024,\n  title = {T{\\\"o}lvera: {{Composing With Basal Agencies}}},\n  booktitle = {Proc. {{New Interfaces}} for {{Musical Expression}}},\n  author = {Armitage, Jack and Shepardson, Victor and Magnusson, Thor},\n  year = {2024},\n  address = {Utrecht, NL},\n}\n
    "},{"location":"#inspiration","title":"Inspiration","text":""},{"location":"#contact","title":"Contact","text":"

    T\u00f6lvera is developed primarily by Jack Armitage and collaborators at 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":"#acknowledgements","title":"Acknowledgements","text":"

    We thank the Taichi community for their wonderful project, that makes T\u00f6lvera possible.

    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":"artworks/","title":"Artworks","text":""},{"location":"artworks/#artworks","title":"Artworks","text":"

    Page under construction \ud83d\udea7.

    "},{"location":"bibliography/","title":"Bibliography","text":""},{"location":"bibliography/#bibliography","title":"Bibliography","text":"

    Page under construction \ud83d\udea7. For now, see bibliography.bib.

    "},{"location":"examples/","title":"Examples","text":""},{"location":"examples/#examples","title":"Examples","text":"

    Page under construction \ud83d\udea7. For now, see iil-examples/tolvera.

    "},{"location":"experiments/","title":"Experiments","text":""},{"location":"experiments/#experiments","title":"Experiments","text":"

    These are ongoing experiments with T\u00f6lvera, Taichi and third-party libraries. You can find more experiments in the examples folder. Most are working examples, but don't be surprised if there is the occasional broken one - please report issues here.

    "},{"location":"experiments/#live-coding","title":"Live Coding","text":"

    T\u00f6lvera can be live coded via Sardine. Note that this only works so far using VSCode's inbuilt Python REPL, and not with the Sardine VSCode extension:

    tv.s.params = {'state':{'evap': (ti.f32, 0., 0.99)}}\n\n@swim\ndef gui_loop(p=0.5, i=0):\n  tv.px.diffuse(tv.s.params.field[0].evap)\n  tv.px.particles(tv.p, tv.s.species())\n  tv.show(tv.px)\n  again(gui_loop, p=1/64, i=i+1)\n\n@swim\ndef control_loop(p=4, i=0):\n  tv.s.params.field[0].evap = P('0.9 0.99 0.1', i)\n  again(control_loop, p=1/2, i=i+1)\n
    "},{"location":"experiments/#sonification","title":"Sonification","text":"

    Sonification of and with T\u00f6lvera can be achieved via SignalFlow. See examples here.

    "},{"location":"experiments/#gpu-audio","title":"GPU Audio","text":"

    GPU audio rendering can be achieved using Taichi alone:

    from iipyper import Audio, run\nimport sounddevice as sd\nimport taichi as ti\nfrom math import pi\n\n@ti.kernel\ndef generate_data(outdata: ti.ext_arr(), \n                  frames: ti.i32, \n                  start_idx: ti.template(), \n                  fs: ti.f32):\n    for I in ti.grouped(ti.ndrange(frames)):\n        i = I[0]\n        t = (start_idx[None] + i) / fs\n        s = ti.sin(2 * pi * 440 * t)\n        start_idx[None] += 1\n        outdata[i,0] = s\n\ndef main():\n    ti.init(arch=ti.vulkan)\n    device = sd.default.device\n    fs = sd.query_devices(sd.default.device, 'output')['default_samplerate']\n    buffer_size = 128\n    start_idx = ti.field(ti.i32, ())\n    def callback(indata, outdata, frames, time, status):\n        if status: print(status, file=sys.stderr)\n        generate_data(outdata, frames, start_idx, fs)\n    Audio(device=device, channels=1, blocksize=buffer_size,\n        samplerate=fs, callback=callback)\n\nif __name__=='__main__':\n    run(main)\n
    "},{"location":"experiments/#3d-printing","title":"3D Printing","text":"

    Exploration of 3D printing with T\u00f6lvera can be achieved via FullControl. See example here.

    "},{"location":"experiments/#sketchbook","title":"Sketchbook","text":"

    T\u00f6lvera has an experimental sketchbook built-in. For example, if the following program exists in a folder called mysketch.py:

    from tolvera import Tolvera\n\ndef mysketch(**kwargs):\n    tv = Tolvera(**kwargs)\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)\n        return tv.px\n

    It can be run with:

    python -m tolvera --sketch \"mysketch\"\n

    Available commands:

    --sketchbook    Set a sketchbook folder\n--sketches      List available sketches\n--sketch        Run a particular sketch (can be a file path or index)\n--random        Run a random sketch from the sketchbook\n
    "},{"location":"guide/","title":"Guide","text":""},{"location":"guide/#tolvera-v010-api-guide","title":"T\u00f6lvera v0.1.0 API Guide","text":"

    This guide provides an overview of the T\u00f6lvera v0.1.0 API:

    The v0.2.0 API will be different, so everything here is subject to change. Please share your feedback on our Discord.

    "},{"location":"guide/#overview-of-features","title":"Overview of Features","text":""},{"location":"guide/#program-structure","title":"Program Structure","text":"

    This example demonstrates the basic usage of T\u00f6lvera when used as a standalone Python script. 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
    "},{"location":"guide/#particles-tvp-species-tvsspecies","title":"Particles (tv.p) & Species (tv.s.species)","text":"

    A central idea of T\u00f6lvera is the particle as a base unit of activity. The particle system is a field of type Particle with these properties:

    @ti.dataclass\nclass Particle:\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

    Particles are divided into species (represented as an integer), and species can have different relationships with each other, creating a matrix of species-species interactions. This idea was inspired by Particle Life, and provides a simple means to mimic ecological complexity, even when using a single behaviour or model. Species are implemented as multi-dimensional state (see below), which means all tv.v behaviour models can make use of the multispecies matrix.

    Multi-species matrix (`tv.s.species`) with N species shown on each axis, with example shown based on `tv.v.flock()`\u2019s rules. Every species has a different relationship with each other, including itself, i.e. cell (0,0) shows the 0th species\u2019 relationship with itself. As species are implemented as state (`tv.s`), OSC endpoints can be automatically created allowing for dynamically updating rules of individual species pairs, or groups of pairs, or indeed the entire set of rules."},{"location":"guide/#state-tvs","title":"State (tv.s)","text":"

    To enable composition of behaviours, T\u00f6lvera features a global state dictionary. State is n-dimensional and can be manipulated in parallel on the GPU. Typical state shapes might be: (species, species) for multispecies rules, (particles) for individual particle states, and \\pyi(particles, particles) for pairwise comparison of particles. Assigning a dictionary to a variable after tv.s causes a new state to be instanced. Here are two examples of state from tv.v.flock, showing the syntax of \"name\": (type, min, max) for each attribute, and some of the additional options which includes built-in randomisation:

    tv.s.flock_s = { \"state\": \n  {\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\": (species, species),\n  \"osc\": (\"set\"),\n  \"randomise\": True\n}\n\ntv.s.flock_dist = {\n  \"state\": {\"dist\": (ti.f32, 0.0, tv.x*2)},\n  \"shape\": (particles, particles),\n  \"randomise\": False\n}\n

    State is useful and interesting to visualise, for example drawing the particle distances that flock uses reveals hidden structure. State can also be reused and combined with state from other models, to compose even more complex behaviour.

    "},{"location":"guide/#vera-tvv","title":"Vera (tv.v)","text":"

    The image below shows some of the available behaviours and models in T\u00f6lvera. Some of these are inspired directly from open source code posted by the Taichi community - thank you!

    Examples of behaviours and models available via `tv.v`. Top row: stateless `tv.v`. Bottom row: stateful `tv.v`.

    Models can be used by calling them and passing particles to them, for example:

    @tv.render\ndef _():\n  tv.px.clear()\n  tv.v.flock(tv.p)\n  tv.v.plife(tv.p)\n  tv.px.particles(tv.p, tv.s.species())\n  return tv.px\n

    In this case, the flock and plife (particle life) models are composed together to create a compound behaviour. Models are also designed to be simple and concise internally to encourage users to understand them and make their own. Simple behaviours like move can be stateless and implemented as single GPU kernels.

    @ti.kernel\ndef move(p: ti.template()):\n  for i in range(p.field.shape[0]):\n    p1 = p.field[i]\n    if p1.field[i].active == 0: continue\n    p.field[i].pos += p1.vel * p1.speed\n

    Models that use state are implemented as classes, that at minimum provide a step() method, where particles can be compared and states updated:

    @ti.data_oriented\nclass MyVera:\n  def __init__(self, tolvera, **kwargs):\n    self.tv, self.kwargs = tolvera, kwargs\n    self.C = CONSTS({...})\n    self.tv.s.vera_s = {...} #\u00a0state\n\n  @ti.kernel\n  def step(self, p):\n    for i in range(p.shape[0]):\n      for j in range(p.shape[1]):\n        # compare p[i] & p[j]\n        # update state, etc.\n      p[i].pos += ... # update p[i]\n\n  def __call__(self, p):\n    self.step(p)\n

    Step function inside tv.v:

    @ti.kernel\ndef step(self, p: ti.template(), W: ti.f32):\n  for i in range(p.shape[0]):\n    p1 = p[i]\n    if p1[i].active == 0: continue\n    for j in range(p.shape[0]):\n      p2 = p[j]\n      if i == j & p2[j].active == 0: continue\n      s = self.tv.s.vera_s[p1.species, p2.species]\n      d = p1.pos - p2.pos\n      if d < s.radius:\n        # p1 & p2 are neighbours\n    p[i].vel += W * ...\n    p[i].pos += W * ...\n
    "},{"location":"guide/#pixels-tvpx","title":"Pixels (tv.px)","text":"

    T\u00f6lvera has a drawing module that is similar in design to p5.js. This example draws a rectangle in the middle of the window:

    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        draw()\n        return tv.px\n\nif __name__ == '__main__':\n    run(main)\n

    It mainly features shape primitives and blend modes. Pixels can also be thought of as fields and used as part of a vera, as tv.v.slime does to deposit particles onto a pheromone trail. Due to this flexibility, drawing and visualisation can impact behaviour and create more mappings and feedback loops between model states.

    "},{"location":"guide/#gpu-programming-tvti","title":"GPU Programming (tv.ti)","text":"

    T\u00f6lvera does most of its work on the GPU thanks to Taichi (tv.ti). Taichi is a domain-specific language (DSL) embedded in Python that supports multiple backends (CUDA, OpenGL, Vulkan, Metal). Taichi programs are distinguished by three levels of scope: regular Python scope, kernel scope (@ti.kernel) and function scope (@ti.func). Python scope can call kernels, and kernels and functions can call functions. In Taichi scope, top-level for loops are automatically parallelised. It can also run headless (without a window), and provides a C runtime and ahead-of-time (AOT) compilation for deployment in non-Python programs. It also interoperates with NumPy and PyTorch for ML integrations.

    "},{"location":"guide/#open-sound-control-tvosc","title":"Open Sound Control (tv.osc)","text":"

    OSC in T\u00f6lvera is handled by iipyper, a package specifically designed for working with artificial intelligence. When creating state, OSC endpoints can be automatically added via the \"osc\" option. For custom OSC endpoints, the tv.osc.map decorators can be used to add senders and receivers of different varieties. Here are two examples, one of receiving two arguments x and y, and another of receiving a vector of length ten:

    @tv.osc.map.receive_args(x=(0.,0.,1.), y=(0.,0.,1.), count=5)\ndef my_args(x: float, y: float):\n  print(f\"Receiving args: {x} {y}\")\n\n@tv.osc.map.receive_list(vector=(0.,0.,1.), length=10, count=5)\ndef my_list(vector: list[float]):\n  print(f\"Received list: {vector}\")\n

    The count decorator argument can be used to rate limit how often an endpoint's Python function runs, relative to the number of frames elapsing. T\u00f6lvera OSC mappings can be exported to JSON and XML, and they can also generate patches for Max/MSP and Pure Data, enabling rapid integration with other software.

    Open Sound Control map (`tv.osc.map`) client generated by T\u00f6lvera for Pure Data (Pd) based on the code example above."},{"location":"guide/#interactive-machine-learning-tviml","title":"Interactive Machine Learning (tv.iml)","text":"

    Interactive machine learning is achieved via the anguilla Python package. T\u00f6lvera features a global dictionary of IML instances at tv.iml, similarly to state (tv.s). IML can be used for a wide range of purposes in T\u00f6lvera, including creating internal feedback loops, for example between tv.v.flock's position and species rules states:

    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}\n

    The above example uses fun2fun, meaning the input and output methods specified in the io option will be run in the background by T\u00f6lvera. These are to_vec and from_vec, built-in methods that serialise and deserialise state to/from 1D arrays making them suitable for use as IML input/output vectors. To facilitate automatic routing of IML inputs and outputs, there are nine types in which the input and output can be either a vector, function or OSC endpoint: vec2vec, vec2fun, vec2osc, fun2vec, fun2fun, fun2osc, osc2vec, osc2fun, and osc2osc. Notably, these IML maps can be trained and updated on-the-fly, providing more variation in prolonged usage.

    The image below demonstrates how T\u00f6lvera's state and drawing capabilities can be used to enable real-time visualisation of IML mappings.

    Interactive machine learning (`tv.iml`) real-time map visualisation for input size two (XY axes) and output size three (pixel RGB). Input-output pairs are shown as white circles. This example demonstrates anguilla's ripple interpolator."},{"location":"guide/#computer-vision-tvcv","title":"Computer Vision (tv.cv)","text":"

    T\u00f6lvera integrates with OpenCV and Mediapipe to enable exploration of computer vision and tracking of hands, face and full body pose. See examples.

    "},{"location":"guide/#command-line-arguments","title":"Command-line arguments","text":"

    When T\u00f6lvera is instanced, a global kwargs dictionary is passed down through the various modules that allows setting of flags and parameters.

    "},{"location":"guide/#tolvera-module-python-m-tolvera","title":"T\u00f6lvera Module (python -m tolvera)","text":"

    All arguments below can be applied, and in addition:

    --demo  Run a tv.v.flock() demo.\n--help  Print a help message.\n

    See also sketchbook in experiments.

    "},{"location":"guide/#tolvera-context-tvctx","title":"T\u00f6lvera Context (tv.ctx)","text":"
    --x             Width in pixels. Defaults to 1920.\n--y             Height in pixels. Defaults to 1080.\n
    "},{"location":"guide/#tolvera-instance-tv","title":"T\u00f6lvera Instance (tv)","text":"
    --name          Name of T\u00f6lvera instance. Defaults to \"T\u00f6lvera\".\n--speed         Global speed scalar. Defaults to 1.\n--particles     Number of particles. Defaults to 1024. Aliased to tv.pn.\n--species       Number of species. Defaults to 4. Aliased to tv.sn.\n--substep       Number of substeps of render function. Defaults to 1.\n
    "},{"location":"guide/#taichi-tvti","title":"Taichi (tv.ti)","text":"
    --gpu        GPU architecture to run on. Defaults to \"vulkan\".\n--cpu        Run on CPU instead of GPU. Defaults to False.\n--fps        Frames per second. Defaults to 120.\n--seed       Random seed. Defaults to int(time.time()).\n--headless   False\n--name       Instance name. Defaults to \"T\u00f6lvera\".\n
    "},{"location":"guide/#pixels-tvpx_1","title":"Pixels (tv.px)","text":"
    --polygon_mode  Polygon drawing mode. Defaults to 'crossing'.\n--brightness    Brightness scalar. Defaults to 1.\n
    "},{"location":"guide/#open-sound-control-tvosc_1","title":"Open Sound Control (tv.osc)","text":"
    --osc               Enable OSC. Defaults to False.\n--host              OSC Host IP. Defaults to \"127.0.0.1\".\n--client            OSC Client IP. Defaults to \"127.0.0.1\".\n--client_name       OSC client name. Defaults to self.ctx.name_clean.\n--receive_port      OSC host port. Defaults to 5001.\n--send_port         OSC client port. Defaults to 5000.\n--osc_trace         Print all OSC messages. Defaults to False.\n--osc_verbose       Verbose printing of iipyper. Defaults to False.\n--create_patch      Create a Max or Pd patch based on tv.osc.map. Defaults to False.\n--patch_type        Type of patch to create. Defaults to \"Pd\".\n--patch_filepath    Filepath of patch. Defaults to self.client_name.\n--export_patch      Export patch schema to XML, JSON or both. Defaults to None.\n
    "},{"location":"guide/#interactive-machine-learning-tviml_1","title":"Interactive Machine Learning (tv.iml)","text":"
    --update_rate           Rate of IML updates relative to FPS. Default to 10.\n--config                anguilla instance configuration. Default to {}.\n--map_kw                anguilla.map kwargs. Default to {}.\n--infun_kw              Input method kwargs. Default to {}.\n--outfun_kw             Output method kwargs. Default to {}.\n--randomise             IML randomisation. Default to False.\n--rand_pairs            IML randomisation. Default to 32.\n--rand_input_weight     IML input randomisation weight. Default to None.\n--rand_output_weight    IML output randomisation weight. Default to None.\n--rand_method           IML randomisation method. Default to \"rand\".\n--rand_kw               IML randomisation kwargs. Default to {}.\n--lag                   Lag value updates. Default to False.\n--lag_coef              Lag coefficient. Default to 0.5.\n--name                  Name of IML instance. Default to None.\n
    "},{"location":"guide/#computer-vision-tvcv_1","title":"Computer Vision (tv.cv)","text":"
    --camera            Enable camera. Defaults to False.\n--device            OpenCV device index. Defaults to 0.\n--substeps          Number of substeps for reading camera frames. Defaults to 2.\n--colormode         Color channels. Defaults to 'rgba'.\n--ggui_fps_limit    FPS limit of Taichi GGUI. Defaults to 120fps.\n--hands             Enable hand tracking. Defaults to False.\n--pose              Enable pose tracking. Defaults to False.\n--face              Enable face tracking. Defaults to False.\n--face_mesh         Enable face mesh tracking. Defaults to False.\n
    "},{"location":"publications/","title":"Publications","text":""},{"location":"publications/#publications","title":"Publications","text":"

    Page under construction \ud83d\udea7. For now, see references.bib.

    "},{"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.x = kwargs.get(\"x\", 1920)\n        self.y = kwargs.get(\"y\", 1080)\n        self.ti = Taichi(self, **kwargs)\n        self.i = ti.field(ti.i32, ())\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        self.pose = kwargs.get(\"pose\", False)\n        self.face = kwargs.get(\"face\", False)\n        self.face_mesh = kwargs.get(\"face_mesh\", 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            if self.hands:\n                self.hands = MPHands(self, **kwargs)\n            if self.pose:\n                self.pose = MPPose(self, **kwargs)\n            if self.face:\n                self.face = MPFace(self, **kwargs)\n            if self.face_mesh:\n                self.face_mesh = MPFaceMesh(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                self.step(f, **kwargs)\n\n    def step(self, f, **kwargs):\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[None] += 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.x = kwargs.get(\"x\", 1920)\n    self.y = kwargs.get(\"y\", 1080)\n    self.ti = Taichi(self, **kwargs)\n    self.i = ti.field(ti.i32, ())\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    self.pose = kwargs.get(\"pose\", False)\n    self.face = kwargs.get(\"face\", False)\n    self.face_mesh = kwargs.get(\"face_mesh\", 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        if self.hands:\n            self.hands = MPHands(self, **kwargs)\n        if self.pose:\n            self.pose = MPPose(self, **kwargs)\n        if self.face:\n            self.face = MPFace(self, **kwargs)\n        if self.face_mesh:\n            self.face_mesh = MPFaceMesh(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            self.step(f, **kwargs)\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 "},{"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[\"embed_input\"] = \"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[\"embed_input\"] = \"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/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    ppos: ti.math.vec2\n    pvel: 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.C = CONSTS({\"COLL_RAD\": (ti.f32, 10.0)})\n        self.tv.s.collisions_p = {\n            'state': {\n                'collision': (ti.i32, 0, 1),\n                'dpos': (ti.math.vec2, 0., 1.),\n                'dvel': (ti.math.vec2, 0., 1.),\n            },\n            'shape': self.n,\n        }\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        j = 0\n        for i in range(self.n):\n            if self.field[i] == 0.0: continue\n            self.toroidal_wrap(i)\n            self.limit_speed(i)\n            self.detect_collisions(i, self.C.COLL_RAD)\n            self.update_prev(i)\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.func\n    def detect_collisions(self, i: ti.i32, radius: ti.f32):\n        \"\"\"Detect collisions between particles.\n\n        TODO: Merge deltas into @ti.dataclass, or reimplement Particle.field as tv.s?\n        TODO: Multiple collision states? Collided, Colliding, etc.\n        TODO: Detect collisions between external objects.\n\n        Args:\n            i (ti.i32): Particle index.\n            radius (ti.f32): Collision radius.\n        \"\"\"\n        for j in range(self.n):\n            p1, p2 = self.tv.p.field[i], self.tv.p.field[j]\n            if p2.active == 0: continue\n            dist = p1.pos - p2.pos\n            if dist.norm() < radius:\n                pdist = p1.ppos - p2.ppos\n                dpos = ti.abs(pdist - dist)\n                dvel = ti.abs((p1.pvel - p2.pvel) - (p1.vel - p2.vel))\n                self.tv.s.collisions_p[i].dpos = dpos\n                self.tv.s.collisions_p[i].dvel = dvel\n                if pdist.norm() > radius:\n                    self.tv.s.collisions_p[i].collision = 1\n                else:\n                    self.tv.s.collisions_p[i].collision = 0\n\n    @ti.func\n    def update_prev(self, i: ti.i32):\n        \"\"\"Update the previous position and velocity of a particle.\n\n        Args:\n            i (ti.i32): Particle index.\n        \"\"\"\n        self.field[i].ppos = self.field[i].pos\n        self.field[i].pvel = self.field[i].vel\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()\n\n    @ti.kernel\n    def set_total_active(self, total: ti.i32):\n        \"\"\"Set the total number of active particles.\n\n        Args:\n            total (ti.i32): Total active particles.\n        \"\"\"\n        for i in range(self.field.shape[0]):\n            if i >= total:\n                self.field[i].active = 0\n            else:\n                self.field[i].active = 1\n\n    @ti.kernel\n    def set_total_active_amount(self, total: ti.i32, amount: ti.f32):\n        \"\"\"Set the total number of active particles.\n\n        Args:\n            total (ti.i32): Total active particles.\n            amount (ti.f32): Amount of activity.\n        \"\"\"\n        for i in range(self.field.shape[0]):\n            if i >= total:\n                self.field[i].active = 0\n            else:\n                self.field[i].active = amount\n\n    @ti.kernel\n    def set_species_total_active(self, i: ti.i32, total: ti.i32):\n        \"\"\"Set the total number of active particles for a species.\n\n        Args:\n            i (ti.i32): Species index.\n            total (ti.i32): Total active particles.\n        \"\"\"\n        for j in range(self.field.shape[0]):\n            if self.field[j].species == i:\n                if j >= total:\n                    self.field[j].active = 0\n                else:\n                    self.field[j].active = 1\n\n    @ti.kernel\n    def set_species_total_active_amount(self, i: ti.i32, total: ti.i32, amount: ti.f32):\n        \"\"\"Set particle activity amount of a species.\n\n        Args:\n            i (ti.i32): Species index.\n            total: (ti.i32): Total number of active particles.\n            amount (ti.i32): Amount of activity.\n        \"\"\"\n        for j in range(self.field.shape[0]):\n            if self.field[j].species == i:\n                if j >= total:\n                    self.field[j].active = 0\n                else:\n                    self.field[j].active = amount\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.C = CONSTS({\"COLL_RAD\": (ti.f32, 10.0)})\n    self.tv.s.collisions_p = {\n        'state': {\n            'collision': (ti.i32, 0, 1),\n            'dpos': (ti.math.vec2, 0., 1.),\n            'dvel': (ti.math.vec2, 0., 1.),\n        },\n        'shape': self.n,\n    }\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.detect_collisions","title":"detect_collisions(i, radius)","text":"

    Detect collisions between particles.

    TODO: Merge deltas into @ti.dataclass, or reimplement Particle.field as tv.s? TODO: Multiple collision states? Collided, Colliding, etc. TODO: Detect collisions between external objects.

    Parameters:

    Name Type Description Default i i32

    Particle index.

    required radius f32

    Collision radius.

    required Source code in src/tolvera/particles.py
    @ti.func\ndef detect_collisions(self, i: ti.i32, radius: ti.f32):\n    \"\"\"Detect collisions between particles.\n\n    TODO: Merge deltas into @ti.dataclass, or reimplement Particle.field as tv.s?\n    TODO: Multiple collision states? Collided, Colliding, etc.\n    TODO: Detect collisions between external objects.\n\n    Args:\n        i (ti.i32): Particle index.\n        radius (ti.f32): Collision radius.\n    \"\"\"\n    for j in range(self.n):\n        p1, p2 = self.tv.p.field[i], self.tv.p.field[j]\n        if p2.active == 0: continue\n        dist = p1.pos - p2.pos\n        if dist.norm() < radius:\n            pdist = p1.ppos - p2.ppos\n            dpos = ti.abs(pdist - dist)\n            dvel = ti.abs((p1.pvel - p2.pvel) - (p1.vel - p2.vel))\n            self.tv.s.collisions_p[i].dpos = dpos\n            self.tv.s.collisions_p[i].dvel = dvel\n            if pdist.norm() > radius:\n                self.tv.s.collisions_p[i].collision = 1\n            else:\n                self.tv.s.collisions_p[i].collision = 0\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()\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_species_total_active","title":"set_species_total_active(i, total)","text":"

    Set the total number of active particles for a species.

    Parameters:

    Name Type Description Default i i32

    Species index.

    required total i32

    Total active particles.

    required Source code in src/tolvera/particles.py
    @ti.kernel\ndef set_species_total_active(self, i: ti.i32, total: ti.i32):\n    \"\"\"Set the total number of active particles for a species.\n\n    Args:\n        i (ti.i32): Species index.\n        total (ti.i32): Total active particles.\n    \"\"\"\n    for j in range(self.field.shape[0]):\n        if self.field[j].species == i:\n            if j >= total:\n                self.field[j].active = 0\n            else:\n                self.field[j].active = 1\n
    "},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_species_total_active_amount","title":"set_species_total_active_amount(i, total, amount)","text":"

    Set particle activity amount of a species.

    Parameters:

    Name Type Description Default i i32

    Species index.

    required total i32

    (ti.i32): Total number of active particles.

    required amount i32

    Amount of activity.

    required Source code in src/tolvera/particles.py
    @ti.kernel\ndef set_species_total_active_amount(self, i: ti.i32, total: ti.i32, amount: ti.f32):\n    \"\"\"Set particle activity amount of a species.\n\n    Args:\n        i (ti.i32): Species index.\n        total: (ti.i32): Total number of active particles.\n        amount (ti.i32): Amount of activity.\n    \"\"\"\n    for j in range(self.field.shape[0]):\n        if self.field[j].species == i:\n            if j >= total:\n                self.field[j].active = 0\n            else:\n                self.field[j].active = amount\n
    "},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_total_active","title":"set_total_active(total)","text":"

    Set the total number of active particles.

    Parameters:

    Name Type Description Default total i32

    Total active particles.

    required Source code in src/tolvera/particles.py
    @ti.kernel\ndef set_total_active(self, total: ti.i32):\n    \"\"\"Set the total number of active particles.\n\n    Args:\n        total (ti.i32): Total active particles.\n    \"\"\"\n    for i in range(self.field.shape[0]):\n        if i >= total:\n            self.field[i].active = 0\n        else:\n            self.field[i].active = 1\n
    "},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.set_total_active_amount","title":"set_total_active_amount(total, amount)","text":"

    Set the total number of active particles.

    Parameters:

    Name Type Description Default total i32

    Total active particles.

    required amount f32

    Amount of activity.

    required Source code in src/tolvera/particles.py
    @ti.kernel\ndef set_total_active_amount(self, total: ti.i32, amount: ti.f32):\n    \"\"\"Set the total number of active particles.\n\n    Args:\n        total (ti.i32): Total active particles.\n        amount (ti.f32): Amount of activity.\n    \"\"\"\n    for i in range(self.field.shape[0]):\n        if i >= total:\n            self.field[i].active = 0\n        else:\n            self.field[i].active = amount\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    j = 0\n    for i in range(self.n):\n        if self.field[i] == 0.0: continue\n        self.toroidal_wrap(i)\n        self.limit_speed(i)\n        self.detect_collisions(i, self.C.COLL_RAD)\n        self.update_prev(i)\n        self.active_indexes[j] = i\n        j += 1\n    self.active_count[None] = j\n
    "},{"location":"reference/tolvera/particles/#tolvera.particles.Particles.update_prev","title":"update_prev(i)","text":"

    Update the previous position and velocity of a particle.

    Parameters:

    Name Type Description Default i i32

    Particle index.

    required Source code in src/tolvera/particles.py
    @ti.func\ndef update_prev(self, i: ti.i32):\n    \"\"\"Update the previous position and velocity of a particle.\n\n    Args:\n        i (ti.i32): Particle index.\n    \"\"\"\n    self.field[i].ppos = self.field[i].pos\n    self.field[i].pvel = self.field[i].vel\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[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[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[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[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        self.pose = context.pose\n        self.face = context.face\n        self.face_mesh = context.face_mesh\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            if self.hands:\n                self.hands.px = self.px\n            if self.pose:\n                self.pose.px = self.px\n            if self.face:\n                self.face.px = self.px\n            if self.face_mesh:\n                self.face_mesh.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        if self.hands:\n            self.hands.px = self.px\n        if self.pose:\n            self.pose.px = self.px\n        if self.face:\n            self.face.px = self.px\n        if self.face_mesh:\n            self.face_mesh.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    self.pose = context.pose\n    self.face = context.face\n    self.face_mesh = context.face_mesh\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/mp/face/","title":"Face","text":""},{"location":"reference/tolvera/mp/face/#tolvera.mp.face.FaceKeyPoint","title":"FaceKeyPoint","text":"

    Bases: IntEnum

    The enum type of the six face detection key points.

    Source code in src/tolvera/mp/face.py
    class FaceKeyPoint(enum.IntEnum):\n  \"\"\"The enum type of the six face detection key points.\"\"\"\n  RIGHT_EYE = 0\n  LEFT_EYE = 1\n  NOSE_TIP = 2\n  MOUTH_CENTER = 3\n  RIGHT_EAR_TRAGION = 4\n  LEFT_EAR_TRAGION = 5\n
    "},{"location":"reference/tolvera/mp/face/#tolvera.mp.face.MPFace","title":"MPFace","text":"Source code in src/tolvera/mp/face.py
    @ti.data_oriented\nclass MPFace:\n    def __init__(self, context, **kwargs) -> None:\n        self.ctx = context\n        self.kwargs = kwargs\n        self.n_points = 6\n        self.max_faces = kwargs.get('max_faces', 4)\n\n        self.config = {\n            'min_detection_confidence': kwargs.get('detection_con', .5),\n            'model_selection': kwargs.get('model_selection', 0),\n        }\n\n        \"\"\"\n        TODO: add bbox as separate tv.s.faces_bbox?\n            format: RELATIVE_BOUNDING_BOX\n            relative_bounding_box {\n                xmin: 0.482601523\n                ymin: 0.402242899\n                width: 0.162447035\n                height: 0.2887941\n            }\n        \"\"\"\n\n        self.faces_np = {\n            'pxnorm':np.zeros((self.max_faces, self.n_points, 2), np.float32),\n            'px':np.zeros((self.max_faces, self.n_points, 2), np.float32),\n        }\n        self.ctx.s.faces = {\n            'state': {\n                'pxnorm': (ti.math.vec2, 0.0, 1.0),\n                'px': (ti.math.vec2, 0.0, 1.0),\n                # 'metres': (ti.math.vec3, 0.0, 1.0), # face_world_landmarks\n            },\n            'shape': (self.max_faces, self.n_points)\n        }\n\n        self.mpFace = mp.solutions.face_detection\n        self.face = self.mpFace.FaceDetection(**self.config)\n        self.detected = ti.field(ti.i32, shape=())\n\n        self.updater = Updater(self.detect, kwargs.get('face_detect_rate', 10))\n\n    def detect(self, frame=None):\n        if frame is None: return\n        self.results = self.face.process(frame)\n        if self.results.detections is None:\n            self.ctx.s.faces.fill(0.)\n            self.detected[None] = -1\n            return\n\n        if self.results.detections:\n            for i, face in enumerate(self.results.detections):\n                for j, lm in enumerate(face.location_data.relative_keypoints):\n                    pxnorm = np.array([1-lm.x, 1-lm.y])\n                    px = np.array([self.ctx.x*(1-lm.x), self.ctx.y*(1-lm.y)])\n                    self.faces_np['pxnorm'][i, j] = pxnorm\n                    self.faces_np['px'][i, j] = px\n        self.ctx.s.faces.set_from_nddict(self.faces_np)\n\n        self.detected[None] = len(self.results.detections)\n\n    @ti.kernel\n    def draw(self):\n        if self.detected[None] > 0:\n            self.draw_face_lms(5, ti.Vector([1, 1, 1, 1]))\n\n    @ti.func\n    def draw_face_lms(self, r, rgba):\n        for i, lm in ti.ndrange(self.detected[None], self.n_conns):\n            self.draw_lm(i, lm, r, rgba)\n\n    @ti.func\n    def draw_lm(self, face: ti.i32, lm: ti.i32, r: ti.i32, rgba: ti.math.vec4):\n        px = self.ctx.s.faces[face, lm].px\n        cx = ti.cast(px.x, ti.i32)\n        cy = ti.cast(px.y, ti.i32)\n        self.px.circle(cx, cy, r, rgba)\n\n    def landmark_name_from_index(self, index):\n        return FaceKeyPoint(index).name\n\n    def landmark_index_from_name(self, name):\n        return FaceKeyPoint[name].value\n\n    @ti.kernel\n    def get_landmark(self, face: ti.i32, landmark: ti.i32) -> ti.math.vec2:\n        return self.ctx.s.faces[landmark].px\n\n    def __call__(self, frame):\n        self.updater(frame)\n
    "},{"location":"reference/tolvera/mp/face/#tolvera.mp.face.MPFace.config","title":"config = {'min_detection_confidence': kwargs.get('detection_con', 0.5), 'model_selection': kwargs.get('model_selection', 0)} instance-attribute","text":"add bbox as separate tv.s.faces_bbox?

    format: RELATIVE_BOUNDING_BOX relative_bounding_box { xmin: 0.482601523 ymin: 0.402242899 width: 0.162447035 height: 0.2887941 }

    "},{"location":"reference/tolvera/mp/face_mesh/","title":"Face mesh","text":""},{"location":"reference/tolvera/mp/face_mesh_connections/","title":"Face mesh connections","text":"

    MediaPipe FaceMesh connections.

    "},{"location":"reference/tolvera/mp/hands/","title":"Hands","text":""},{"location":"reference/tolvera/mp/hands/#tolvera.mp.hands.HandLandmark","title":"HandLandmark","text":"

    Bases: IntEnum

    The 21 hand landmarks.

    Source code in src/tolvera/mp/hands.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/mp/pose/","title":"Pose","text":""},{"location":"reference/tolvera/mp/pose/#tolvera.mp.pose.PoseLandmark","title":"PoseLandmark","text":"

    Bases: IntEnum

    The 33 pose landmarks.

    Source code in src/tolvera/mp/pose.py
    class PoseLandmark(enum.IntEnum):\n  \"\"\"The 33 pose landmarks.\"\"\"\n  NOSE = 0\n  LEFT_EYE_INNER = 1\n  LEFT_EYE = 2\n  LEFT_EYE_OUTER = 3\n  RIGHT_EYE_INNER = 4\n  RIGHT_EYE = 5\n  RIGHT_EYE_OUTER = 6\n  LEFT_EAR = 7\n  RIGHT_EAR = 8\n  MOUTH_LEFT = 9\n  MOUTH_RIGHT = 10\n  LEFT_SHOULDER = 11\n  RIGHT_SHOULDER = 12\n  LEFT_ELBOW = 13\n  RIGHT_ELBOW = 14\n  LEFT_WRIST = 15\n  RIGHT_WRIST = 16\n  LEFT_PINKY = 17\n  RIGHT_PINKY = 18\n  LEFT_INDEX = 19\n  RIGHT_INDEX = 20\n  LEFT_THUMB = 21\n  RIGHT_THUMB = 22\n  LEFT_HIP = 23\n  RIGHT_HIP = 24\n  LEFT_KNEE = 25\n  RIGHT_KNEE = 26\n  LEFT_ANKLE = 27\n  RIGHT_ANKLE = 28\n  LEFT_HEEL = 29\n  RIGHT_HEEL = 30\n  LEFT_FOOT_INDEX = 31\n  RIGHT_FOOT_INDEX = 32\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        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 pascal_to_camel(self, s):\n        return s[0].lower() + s[1:]\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    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        print('send_args_func',hints, f_p.items())\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 i, (k, p) in enumerate(f_p.items()):\n                p_def, p_min, p_max = f_p[k][0], f_p[k][1], f_p[k][2]\n                print(i, k, p, p_def, p_min, p_max)\n                params.append(\n                    {\n                        \"label\": k,\n                        \"data\": hints[i].__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/diffline/","title":"Diffline","text":"

    Differential line

    "},{"location":"reference/tolvera/vera/diffline/#tolvera.vera.diffline.DifferentialLine","title":"DifferentialLine","text":"Source code in src/tolvera/vera/diffline.py
    @ti.data_oriented\nclass DifferentialLine:\n    def __init__(self, tolvera, **kwargs) -> None:\n        self.tv = tolvera\n        self.kwargs = kwargs\n        self.CONSTS = CONSTS({\"dt\": (ti.f32, 0.1)})\n        \"\"\"\n        state\n            max segments\n            tv.s.segments\n        init\n            starting shape? circle etc.\n        step\n            for s in segments\n                calculate the direction of growth\n                apply angle variation and growth rate\n                maintain minimum distance between points\n        composition\n            want to be able to e.g. flock the segments\n        \"\"\"\n        self.tv.s.diffline_s = {\n            \"state\": {\n                \"sigma\": (ti.f32, 0.0, 100.0),\n            },\n            \"shape\": self.tv.sn,\n            \"randomise\": True,\n        }\n    @ti.kernel\n    def step(self, particles: ti.template(), weight: ti.f32):\n        for i in particles:\n            pass\n    @ti.func\n    def proc(self, x, y, z) -> ti.Vector:\n        pass\n    def randomise(self):\n        self.tv.s.diffline_s.randomise()\n    def __call__(self, particles: ti.template(), weight: ti.f32=1.0):\n        self.step(particles, weight)\n
    "},{"location":"reference/tolvera/vera/diffline/#tolvera.vera.diffline.DifferentialLine.CONSTS","title":"CONSTS = CONSTS({'dt': (ti.f32, 0.1)}) instance-attribute","text":"

    state max segments tv.s.segments init starting shape? circle etc. step for s in segments calculate the direction of growth apply angle variation and growth rate maintain minimum distance between points composition want to be able to e.g. flock the segments

    "},{"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.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 * p1.speed * p1.active \n                particles[i].pos += particles[i].vel\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.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 * p1.speed * p1.active \n            particles[i].pos += particles[i].vel\n        self.tv.s.flock_p[i] = self.tv.s.flock_p.struct(\n            separate, align, cohere, nearby\n        )\n
    "},{"location":"reference/tolvera/vera/flock2/","title":"Flock2","text":"

    Flock behaviour based on the Boids algorithm.

    "},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2","title":"Flock2","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/flock2.py
    @ti.data_oriented\nclass Flock2:\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.flock2_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.flock2_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.0, self.tv.p.n - 1),\n            },\n            \"shape\": self.tv.pn,\n            \"osc\": (\"get\"),\n            \"randomise\": False,\n        }\n        self.tv.s.flock2_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.flock2_s.randomise()\n\n    @ti.func\n    def set_mag(self, v: ti.template(), mag: ti.f32):\n        return (v / v.norm()) * mag\n\n    @ti.func\n    def set_mag2(self, v: ti.template(), mag: ti.f32):\n        return (v / v.norm())# * mag\n\n    @ti.kernel\n    def step(self, particles: ti.template(), weight: ti.f32, algotype: ti.i32, single_species: ti.i32):\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            p1 = particles[i]\n            if p1.active == 0:\n                continue\n            if single_species > -1:\n                if p1.species != single_species:\n                    continue\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.flock2_s.struct()\n            for j in range(n):\n                p2 = particles[j]\n                if i == j and p2.active == 0:\n                    continue\n                # if single_species > -1:\n                #     if p2.species != single_species:\n                #         continue\n                species = self.tv.s.flock2_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.flock2_dist[i, j].dist = p1.dist(p2).norm()\n                self.tv.s.flock2_dist[i, j].dist_wrap = dis_wrap_norm\n            if nearby > 0:\n                vel = ti.Vector([0.0, 0.0])\n                if algotype == 0:\n                    align = (align / nearby) * p1.active * species.align\n                    separate = (\n                        (separate / nearby) * p1.active * ti.math.max(species.separate, 0.2)\n                    )\n                    cohere = ((cohere / nearby) - p1.pos) * p1.active * species.cohere\n                    vel = (separate + align + cohere).normalized()\n                    # vel = separate + align + cohere\n                elif algotype == 1:\n                    align = self.set_mag((align / nearby), p1.speed) * species.align * p1.active\n                    separate = self.set_mag((separate / nearby), p1.speed) * species.separate * p1.active\n                    cohere = self.set_mag(((cohere / nearby) - p1.pos), p1.speed) * species.cohere * p1.active\n                    vel = separate + align + cohere\n                elif algotype == 2:\n                    align = self.set_mag2((align / nearby), p1.speed) * species.align * p1.active\n                    separate = self.set_mag2((separate / nearby), p1.speed) * species.separate * p1.active\n                    cohere = self.set_mag2(((cohere / nearby) - p1.pos), p1.speed) * species.cohere * p1.active\n                    vel = separate + align + cohere\n                particles[i].vel += vel * weight\n                particles[i].pos += particles[i].vel * p1.speed * p1.active * weight\n            self.tv.s.flock2_p[i] = self.tv.s.flock2_p.struct(\n                separate, align, cohere, nearby\n            )\n\n    def __call__(self, particles, weight: ti.f32=1.0, algotype: ti.f32=0, single_species: ti.i32=-1):\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, algotype, single_species)\n
    "},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2.__call__","title":"__call__(particles, weight=1.0, algotype=0, single_species=-1)","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/flock2.py
    def __call__(self, particles, weight: ti.f32=1.0, algotype: ti.f32=0, single_species: ti.i32=-1):\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, algotype, single_species)\n
    "},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2.__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/flock2.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.flock2_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.flock2_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.0, self.tv.p.n - 1),\n        },\n        \"shape\": self.tv.pn,\n        \"osc\": (\"get\"),\n        \"randomise\": False,\n    }\n    self.tv.s.flock2_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/flock2/#tolvera.vera.flock2.Flock2.randomise","title":"randomise()","text":"

    Randomise the Flock behaviour.

    Source code in src/tolvera/vera/flock2.py
    def randomise(self):\n    \"\"\"Randomise the Flock behaviour.\"\"\"\n    self.tv.s.flock2_s.randomise()\n
    "},{"location":"reference/tolvera/vera/flock2/#tolvera.vera.flock2.Flock2.step","title":"step(particles, weight, algotype, single_species)","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/flock2.py
    @ti.kernel\ndef step(self, particles: ti.template(), weight: ti.f32, algotype: ti.i32, single_species: ti.i32):\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        p1 = particles[i]\n        if p1.active == 0:\n            continue\n        if single_species > -1:\n            if p1.species != single_species:\n                continue\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.flock2_s.struct()\n        for j in range(n):\n            p2 = particles[j]\n            if i == j and p2.active == 0:\n                continue\n            # if single_species > -1:\n            #     if p2.species != single_species:\n            #         continue\n            species = self.tv.s.flock2_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.flock2_dist[i, j].dist = p1.dist(p2).norm()\n            self.tv.s.flock2_dist[i, j].dist_wrap = dis_wrap_norm\n        if nearby > 0:\n            vel = ti.Vector([0.0, 0.0])\n            if algotype == 0:\n                align = (align / nearby) * p1.active * species.align\n                separate = (\n                    (separate / nearby) * p1.active * ti.math.max(species.separate, 0.2)\n                )\n                cohere = ((cohere / nearby) - p1.pos) * p1.active * species.cohere\n                vel = (separate + align + cohere).normalized()\n                # vel = separate + align + cohere\n            elif algotype == 1:\n                align = self.set_mag((align / nearby), p1.speed) * species.align * p1.active\n                separate = self.set_mag((separate / nearby), p1.speed) * species.separate * p1.active\n                cohere = self.set_mag(((cohere / nearby) - p1.pos), p1.speed) * species.cohere * p1.active\n                vel = separate + align + cohere\n            elif algotype == 2:\n                align = self.set_mag2((align / nearby), p1.speed) * species.align * p1.active\n                separate = self.set_mag2((separate / nearby), p1.speed) * species.separate * p1.active\n                cohere = self.set_mag2(((cohere / nearby) - p1.pos), p1.speed) * species.cohere * p1.active\n                vel = separate + align + cohere\n            particles[i].vel += vel * weight\n            particles[i].pos += particles[i].vel * p1.speed * p1.active * weight\n        self.tv.s.flock2_p[i] = self.tv.s.flock2_p.struct(\n            separate, align, cohere, nearby\n        )\n
    "},{"location":"reference/tolvera/vera/flock_shiffman/","title":"Flock shiffman","text":"

    taichi flocking simulation credits: https://www.youtube.com/watch?v=mhjuuHl6qHM

    "},{"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.centripetal","title":"centripetal(particles, centre, direction, weight)","text":"

    Apply a centripetal force to the particles.

    Parameters:

    Name Type Description Default particles template

    Particles.

    required centre vec2

    Centripetal centre.

    required direction i32

    Centripetal direction.

    required weight f32

    Centripetal weight.

    required Source code in src/tolvera/vera/forces.py
    @ti.kernel\ndef centripetal(particles: ti.template(), centre: ti.math.vec2, direction: ti.i32, weight: ti.f32):\n    \"\"\"Apply a centripetal force to the particles.\n\n    Args:\n        particles (ti.template): Particles.\n        centre (ti.math.vec2): Centripetal centre.\n        direction (ti.i32): Centripetal direction.\n        weight (ti.f32): Centripetal 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 += centripetal_particle(p, centre, direction, weight)\n
    "},{"location":"reference/tolvera/vera/forces/#tolvera.vera.forces.centripetal_particle","title":"centripetal_particle(p, centre, direction, weight)","text":"

    Apply a centripetal force to a particle.

    Parameters:

    Name Type Description Default p Particle

    Individual particle.

    required centre vec2

    Centripetal centre.

    required direction i32

    Centripetal direction.

    required weight f32

    Centripetal weight.

    required

    Returns:

    Type Description vec2

    ti.math.vec2: Centripetal velocity.

    Source code in src/tolvera/vera/forces.py
    @ti.func\ndef centripetal_particle(p: ti.template(), centre: ti.math.vec2, direction: ti.i32, weight: ti.f32) -> ti.math.vec2:\n    \"\"\"Apply a centripetal force to a particle.\n\n    Args:\n        p (Particle): Individual particle.\n        centre (ti.math.vec2): Centripetal centre.\n        direction (ti.i32): Centripetal direction.\n        weight (ti.f32): Centripetal weight.\n\n    Returns:\n        ti.math.vec2: Centripetal velocity.\n    \"\"\"\n    r = p.pos - centre\n    if direction == 0:\n        r = -r\n    v_perp = ti.Vector([-r[1], r[0]])\n    norm = v_perp.norm() + 1e-5\n    v_perp_normalized = v_perp / norm\n    speed = p.vel.norm()\n    new_vel = v_perp_normalized * speed * weight\n    return new_vel\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/geom/","title":"Geom","text":""},{"location":"reference/tolvera/vera/geom/#tolvera.vera.geom.cos","title":"cos(particles, frequency, magnitude, phase, axis)","text":"

    Apply a cosine wave force to particles.

    Parameters:

    Name Type Description Default particles template

    Particles.

    required frequency f32

    Frequency of the cosine wave.

    required magnitude f32

    Magnitude of the cosine wave.

    required phase f32

    Phase of the cosine wave.

    required axis i32

    Axis to apply the cosine wave to.

    required Source code in src/tolvera/vera/geom.py
    @ti.kernel\ndef cos(particles: ti.template(), frequency: ti.f32, magnitude: ti.f32, phase: ti.f32, axis: ti.i32):\n    \"\"\"Apply a cosine wave force to particles.\n\n    Args:\n        particles (ti.template): Particles.\n        frequency (ti.f32): Frequency of the cosine wave.\n        magnitude (ti.f32): Magnitude of the cosine wave.\n        phase (ti.f32): Phase of the cosine wave.\n        axis (ti.i32): Axis to apply the cosine wave to.\n    \"\"\"\n    for i in range(particles.field.shape[0]):\n        p = particles.field[i]\n        if p.active == 0:\n            continue\n        inc = magnitude * ti.cos(1/frequency * p.pos[axis] + phase)\n        particles.field[i].vel[axis] += inc\n
    "},{"location":"reference/tolvera/vera/geom/#tolvera.vera.geom.sin","title":"sin(particles, frequency, magnitude, phase, axis)","text":"

    Apply a sine wave force to particles.

    Parameters:

    Name Type Description Default particles template

    Particles.

    required frequency f32

    Frequency of the sine wave.

    required magnitude f32

    Magnitude of the sine wave.

    required phase f32

    Phase of the sine wave.

    required axis i32

    Axis to apply the sine wave to.

    required Source code in src/tolvera/vera/geom.py
    @ti.kernel\ndef sin(particles: ti.template(), frequency: ti.f32, magnitude: ti.f32, phase: ti.f32, axis: ti.i32):\n    \"\"\"Apply a sine wave force to particles.\n\n    Args:\n        particles (ti.template): Particles.\n        frequency (ti.f32): Frequency of the sine wave.\n        magnitude (ti.f32): Magnitude of the sine wave.\n        phase (ti.f32): Phase of the sine wave.\n        axis (ti.i32): Axis to apply the sine wave to.\n    \"\"\"\n    for i in range(particles.field.shape[0]):\n        p = particles.field[i]\n        if p.active == 0:\n            continue\n        inc = magnitude * ti.sin(1/frequency * p.pos[axis] + phase)\n        particles.field[i].vel[axis] += inc\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            particles[i].vel = (particles[i].vel + ti.Vector([fx, fy])) * self.CONSTS.V * weight * p1.speed * p1.active\n            particles[i].pos += particles[i].vel\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        particles[i].vel = (particles[i].vel + ti.Vector([fx, fy])) * self.CONSTS.V * weight * p1.speed * p1.active\n        particles[i].pos += particles[i].vel\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/

    "},{"location":"reference/tolvera/vera/viscek/","title":"Viscek","text":"

    Flocking behaviour based on the Viscek model.

    "},{"location":"reference/tolvera/vera/viscek/#tolvera.vera.viscek.Viscek","title":"Viscek","text":"

    Viscek flocking model

    Source code in src/tolvera/vera/viscek.py
    @ti.data_oriented\nclass Viscek:\n    \"\"\"Viscek flocking model\"\"\"\n    def __init__(self, tolvera, **kwargs):\n        self.tv = tolvera\n        self.kwargs = kwargs\n        self.CONSTS = CONSTS({\"MAX_RADIUS\": (ti.f32, 300.0)})\n        self.tv.s.viscek_s = {\n            \"state\": {\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.viscek_p = {\n            \"state\": {\n                \"theta\": (ti.f32, -np.pi, np.pi),\n            },\n            \"shape\": self.tv.pn,\n            \"osc\": (\"get\"),\n            \"randomise\": True,\n        }\n        self.tv.s.viscek_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\"\"\"\n        self.tv.s.viscek_s.randomise()\n\n    @ti.kernel\n    def step(self, particles: ti.template(), noise_weight: ti.f32, weight: ti.f32):\n        n = particles.shape[0]\n        for i in range(n):\n            if particles[i].active == 0:\n                continue\n            p1 = particles[i]\n            species = self.tv.s.viscek_s.struct()\n            n = 0\n            n_dir = ti.Vector([0.0, 0.0])\n            for j in range(n):\n                if i == j and particles[j].active == 0:\n                    continue\n                p2 = particles[j]\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                    n_dir += self.tv.s.viscek_p[j].theta\n                    n += 1\n            avg_dir = n_dir / n\n\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/viscek/#tolvera.vera.viscek.Viscek.__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/viscek.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/viscek/#tolvera.vera.viscek.Viscek.randomise","title":"randomise()","text":"

    Randomise

    Source code in src/tolvera/vera/viscek.py
    def randomise(self):\n    \"\"\"Randomise\"\"\"\n    self.tv.s.viscek_s.randomise()\n
    "}]} \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index a6531a2..67a18de 100644 Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ