{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Tutorial\n", "\n", "PyKappa works in terms of patterns, mixtures, and rules, and expressions, culminating in systems.\n", "Let's see how the API works at each level." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "nbsphinx": "hidden" }, "outputs": [], "source": [ "import random\n", "random.seed(0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Patterns\n", "\n", "As in Kappa, patterns (subsets of a mixture on which rules can operate) can be broken down into components (a connected set of agents).\n", "Agents are typed objects with named sites, each of which has an internal state and possibly a binding partner -- the site of another agent.\n", "All of these classes of objects can be constructed from Kappa strings using the `from_kappa` classmethod." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pykappa.pattern import Agent, Pattern, Component\n", "\n", "# Parse agents A and B from Kappa strings\n", "a = Agent.from_kappa(\"A(x[.])\")\n", "b = Agent.from_kappa(\"B(x[.])\")\n", "print(\"Agent A:\", a.kappa_str)\n", "print(\"Agent B:\", b.kappa_str)\n", "\n", "# Create an AB complex\n", "complex = Component.from_kappa(\"A(x[1]), B(x[1])\")\n", "# Or equivalently:\n", "complex = Pattern.from_kappa(\"A(x[1]), B(x[1])\").components[0]\n", "print(\"Complex\", complex.kappa_str)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Each agent in a mixture is essentially a node in a graph.\n", "PyKappa implements functions such as the ones below, identifying neighbors of an agent and checking whether a component embeds in another, or is in other words isomorphic to a subset of another." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "root = next(iter(complex))\n", "print(\"Root agent type:\", root.type)\n", "print(\"Neighbors of root:\", [neighbor.type for neighbor in root.neighbors])\n", "\n", "self_embeddings = list(complex.embeddings(complex))\n", "print(\"Embeddings of complex in itself:\", len(self_embeddings))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Mixtures\n", "\n", "A mixture, like components and patterns, is a collection of agents, but one which facilitates the application of rules by efficiently updating embeddings according to changes in the mixture.\n", "Mixtures can be initialized and adjusted programatically, or again initialized from Kappa strings." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pykappa.mixture import Mixture\n", "\n", "mixture = Mixture()\n", "mixture.instantiate(\"A(x[.])\", n_copies=3)\n", "mixture.instantiate(\"B(x[.])\", n_copies=2)\n", "mixture.instantiate(\"A(x[1]), B(x[1])\", n_copies=2)\n", "\n", "print(f\"Mixture as a Kappa string:\\n{mixture.kappa_str}\\n\")\n", "\n", "# Track a component pattern to query embeddings efficiently\n", "mixture.track_component(complex)\n", "print(\"#AB embeddings (cached):\", len(mixture.embeddings(complex)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A `ComponentMixture` is a specialized mixture that explicitly tracks connected components.\n", "This allows for efficient component-level queries, especially important for applying rules that require information about the component contexts of patterns, such as rules with different unimolecular and bimolecular rates.\n", "ComponentMixtures maintain the same interface as regular mixtures but add component-specific functionality." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pykappa.mixture import ComponentMixture\n", "\n", "comp_mixture = ComponentMixture()\n", "comp_mixture.instantiate(\"A(x[.])\", n_copies=2)\n", "comp_mixture.instantiate(\"A(x[1]), B(x[1])\")\n", "\n", "# Iterate over all components\n", "print(\"Components in mixture:\")\n", "for component in comp_mixture:\n", " print(component.kappa_str)\n", "\n", "# Query embeddings within a specific component\n", "comp_mixture.track_component(complex)\n", "mixture_component = list(comp_mixture.components)[-1]\n", "embeddings_in_comp = comp_mixture.embeddings_in_component(complex, mixture_component)\n", "print(f\"\\nEmbeddings in the AB complex: {len(embeddings_in_comp)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Rules\n", "\n", "Rules transform agents matched by their left-hand side into those specified by the right-hand side.\n", "Specifically, `rate(system)` evaluates the stochastic rate (possibly using variables), `n_embeddings(mixture)` counts applicable embeddings, and `select(mixture)` samples a specific embedding and returns a MixtureUpdate, which a mixture can then take to efficiently apply the corresponding update." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's add a rule to bind `A` and `B` into `AB` and see how the mixture applies it." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pykappa.rule import KappaRule\n", "\n", "print(\"AB count before one binding event:\", len(mixture.embeddings(complex)))\n", "\n", "bind = KappaRule.from_kappa(\"A(x[.]), B(x[.]) -> A(x[1]), B(x[1]) @ 1\")\n", "\n", "# Track the components on the left-hand side of the rule\n", "for component in bind.left.components:\n", " mixture.track_component(component)\n", "\n", "update = bind.select(mixture)\n", "mixture.apply_update(update)\n", "\n", "mixture.track_component(complex)\n", "print(\"AB count after one binding event:\", len(mixture.embeddings(complex)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Expressions\n", "\n", "Expressions in PyKappa represent algebraic formulas that can include literals, variables, operators, and component patterns.\n", "They are used for rule rates, observables, and variables.\n", "Expressions can be parsed from Kappa strings and evaluated in the context of a system." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pykappa.algebra import Expression\n", "\n", "literal_expr = Expression.from_kappa(\"42\")\n", "print(\"Literal expression value:\", literal_expr.evaluate())\n", "\n", "math_expr = Expression.from_kappa(\"(2 + 3) * 4\")\n", "print(\"Math expression value:\", math_expr.evaluate())\n", "\n", "pattern_expr = Expression.from_kappa(\"|A(x[1]), B(x[1])|\")\n", "print(\"Pattern expression:\", pattern_expr.kappa_str)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The last expression represents the number of AB complexes; it can be evaluated given a system as `pattern_expr.evaluate(system)`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Systems\n", "\n", "A system bundles a mixture with rules and observables and is used for simulation.\n", "Start with the reversible binding system in the Examples gallery to see how the API works at this highest level." ] } ], "metadata": { "kernelspec": { "display_name": "standard", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.3" } }, "nbformat": 4, "nbformat_minor": 2 }