Source code for chipfiring.CFiringScript

from __future__ import annotations
import typing
from typing import Optional
from chipfiring.CFGraph import CFGraph, Vertex


[docs] class CFiringScript: """Represents a chip-firing script for a given graph. A firing script specifies a net number of times each vertex fires. Positive values indicate lending (firing), while negative values indicate borrowing. """
[docs] def __init__(self, graph: CFGraph, script: Optional[typing.Dict[str, int]] = None): """Initialize the firing script. Args: graph: The CFGraph object the script applies to. script: A dictionary mapping vertex names (strings) to integers. Positive integers represent lending moves (firings). Negative integers represent borrowing moves. Vertices not included in the script are assumed to have 0 net firings. If None, an empty script will be created (default: None). Raises: ValueError: If any vertex name in the script is not present in the graph. Example: >>> vertices = {"v1", "v2", "v3"} >>> edges = [("v1", "v2", 1), ("v2", "v3", 2)] >>> graph = CFGraph(vertices, edges) >>> script_dict = {"v1": 2, "v3": -1} # v1 fires twice, v3 borrows once >>> firing_script = CFiringScript(graph, script_dict) >>> firing_script.script {'v1': 2, 'v3': -1, 'v2': 0} # v2 has 0 firings by default >>> # Empty script >>> empty_script = CFiringScript(graph, {}) >>> empty_script.script {'v1': 0, 'v2': 0, 'v3': 0} """ self.graph = graph self._script = {} # Validate and store the script using Vertex objects if script is not None: for vertex_name, firings in script.items(): vertex = Vertex(vertex_name) if vertex not in self.graph.vertices: raise ValueError( f"Vertex '{vertex_name}' in the script is not present in the graph." ) self._script[vertex] = firings
[docs] def get_firings(self, vertex_name: str) -> int: """Get the number of firings for a given vertex. Returns 0 if the vertex is not explicitly mentioned in the script. Args: vertex_name: The name of the vertex. Returns: The net number of firings for the vertex. Raises: ValueError: If the vertex name is not present in the graph. Example: >>> vertices = {"v1", "v2", "v3"} >>> edges = [("v1", "v2", 1), ("v2", "v3", 2)] >>> graph = CFGraph(vertices, edges) >>> script_dict = {"v1": 5, "v2": -3} >>> firing_script = CFiringScript(graph, script_dict) >>> firing_script.get_firings("v1") 5 >>> firing_script.get_firings("v2") -3 >>> firing_script.get_firings("v3") # Not explicitly in script 0 """ vertex = Vertex(vertex_name) if vertex not in self.graph.vertices: raise ValueError(f"Vertex '{vertex_name}' is not present in the graph.") return self._script.get(vertex, 0)
[docs] def set_firings(self, vertex_name: str, firings: int) -> None: """Set the number of firings for a given vertex. Args: vertex_name: The name of the vertex. firings: The net number of firings to set for the vertex. Raises: ValueError: If the vertex name is not present in the graph. Example: >>> vertices = {"v1", "v2", "v3"} >>> edges = [("v1", "v2", 1), ("v2", "v3", 2)] >>> graph = CFGraph(vertices, edges) >>> firing_script = CFiringScript(graph, {}) >>> firing_script.set_firings("v1", 3) >>> firing_script.get_firings("v1") 3 >>> firing_script.set_firings("v2", -2) >>> firing_script.script {'v1': 3, 'v2': -2, 'v3': 0} """ vertex = Vertex(vertex_name) if vertex not in self.graph.vertices: raise ValueError(f"Vertex '{vertex_name}' is not present in the graph.") self._script[vertex] = firings
[docs] def update_firings(self, vertex_name: str, additional_firings: int) -> None: """Update the number of firings for a given vertex by adding to the current value. Args: vertex_name: The name of the vertex. additional_firings: The number of firings to add (can be negative to reduce firings). Raises: ValueError: If the vertex name is not present in the graph. Example: >>> vertices = {"v1", "v2", "v3"} >>> edges = [("v1", "v2", 1), ("v2", "v3", 2)] >>> graph = CFGraph(vertices, edges) >>> script_dict = {"v1": 1} >>> firing_script = CFiringScript(graph, script_dict) >>> firing_script.update_firings("v1", 2) # Add 2 more firings >>> firing_script.get_firings("v1") 3 # 1 + 2 >>> firing_script.update_firings("v2", -1) # Add -1 (borrow once) >>> firing_script.get_firings("v2") -1 # 0 + (-1) """ current_firings = self.get_firings(vertex_name) self.set_firings(vertex_name, current_firings + additional_firings)
[docs] def to_dict(self) -> typing.Dict[str, typing.Any]: """Converts the CFiringScript instance to a dictionary representation. Returns: A dictionary with 'graph' and 'script' (mapping vertex names to firings). """ graph_dict = self.graph.to_dict() script_to_store = {v.name: f for v, f in self._script.items()} # Use internal _script return { "graph": graph_dict, "script": script_to_store }
[docs] @classmethod def from_dict(cls, data: typing.Dict[str, typing.Any]) -> "CFiringScript": """Creates a CFiringScript instance from a dictionary representation. Args: data: A dictionary with 'graph' (CFGraph representation) and 'script' (dictionary mapping vertex names to firings). Returns: A CFiringScript instance. """ graph_data = data.get("graph") if not graph_data: raise ValueError("Graph data is missing in CFiringScript representation") graph = CFGraph.from_dict(graph_data) script_dict = data.get("script", {}) # Script can be empty, defaults to {} in constructor return cls(graph, script_dict)
@property def script(self) -> typing.Dict[str, int]: """Return the script as a dictionary mapping vertex names to firings. Returns: A dictionary where keys are vertex names and values are the number of firings. Example: >>> vertices = {"v1", "v2", "v3"} >>> edges = [("v1", "v2", 1), ("v2", "v3", 2)] >>> graph = CFGraph(vertices, edges) >>> script_dict = {"v2": 10, "v3": -5} >>> firing_script = CFiringScript(graph, script_dict) >>> firing_script.script {'v1': 0, 'v2': 10, 'v3': -5} """ to_return = {} for vertex in self.graph.vertices: to_return[vertex.name] = self.get_firings(vertex.name) return to_return