Skip to content

Storage API

Pluggable storage backend for component data.

Overview

AgentECS uses a protocol-based storage architecture, allowing different backend implementations: - LocalStorage: In-memory storage for single-process use - Future: Archetypal storage, distributed backends, persistent storage

Key Features: - Protocol-based: Easy to swap implementations - Generational liveness: Safe entity recycling - Efficient queries: Filter entities by component types - Batch operations: Apply multiple updates atomically


Storage Protocol

Interface that all storage backends must implement.

Storage

Bases: Protocol

Abstract storage interface. Implementations handle actual data.

Source code in src/agentecs/storage/protocol.py
class Storage(Protocol):
    """Abstract storage interface. Implementations handle actual data."""

    def create_entity(self) -> EntityId:
        """Allocate new entity."""
        ...

    def destroy_entity(self, entity: EntityId) -> None:
        """Remove entity and all its components."""
        ...

    def entity_exists(self, entity: EntityId) -> bool:
        """Check if entity is alive."""
        ...

    def all_entities(self) -> Iterator[EntityId]:
        """Iterate all living entities."""
        ...

    def get_component(
        self, entity: EntityId, component_type: type[T], copy: bool = True
    ) -> T | None:
        """Get component from entity."""
        ...

    def set_component(self, entity: EntityId, component: Any) -> None:
        """Set/update component on entity."""
        ...

    def remove_component(self, entity: EntityId, component_type: type) -> bool:
        """Remove component from entity. Returns True if existed."""
        ...

    def remove_component_from_all(self, component_type: type) -> None:
        """Remove a component type from all entities."""
        ...

    def has_component(self, entity: EntityId, component_type: type) -> bool:
        """Check if entity has component."""
        ...

    def get_component_types(self, entity: EntityId) -> frozenset[type]:
        """Get all component types on entity."""
        ...

    def query(
        self,
        *component_types: type,
        copy: bool = True,
    ) -> Iterator[tuple[EntityId, tuple[Any, ...]]]:
        """Find entities with all specified components."""
        ...

    def query_single(
        self, component_type: type[T], copy: bool = True
    ) -> Iterator[tuple[EntityId, T]]:
        """Optimized single-component query."""
        ...

    def snapshot(self) -> bytes:
        """Serialize entire storage state."""
        ...

    def restore(self, data: bytes) -> None:
        """Restore from snapshot."""
        ...

    # Async variants for distributed/remote storage backends

    async def get_component_async(
        self, entity: EntityId, component_type: type[T], copy: bool = True
    ) -> T | None:
        """Get component from entity (async variant for remote storage)."""
        ...

    def query_async(
        self,
        *component_types: type,
        copy: bool = True,
    ) -> AsyncIterator[tuple[EntityId, tuple[Any, ...]]] | Any:
        """Find entities with all specified components (async variant).

        Returns an async iterator (typically via async generator implementation).
        The `| Any` allows both async generators and awaitable-returning implementations.
        """
        ...

create_entity()

Allocate new entity.

Source code in src/agentecs/storage/protocol.py
def create_entity(self) -> EntityId:
    """Allocate new entity."""
    ...

destroy_entity(entity)

Remove entity and all its components.

Source code in src/agentecs/storage/protocol.py
def destroy_entity(self, entity: EntityId) -> None:
    """Remove entity and all its components."""
    ...

entity_exists(entity)

Check if entity is alive.

Source code in src/agentecs/storage/protocol.py
def entity_exists(self, entity: EntityId) -> bool:
    """Check if entity is alive."""
    ...

get_component(entity, component_type, copy=True)

Get component from entity.

Source code in src/agentecs/storage/protocol.py
def get_component(
    self, entity: EntityId, component_type: type[T], copy: bool = True
) -> T | None:
    """Get component from entity."""
    ...

set_component(entity, component)

Set/update component on entity.

Source code in src/agentecs/storage/protocol.py
def set_component(self, entity: EntityId, component: Any) -> None:
    """Set/update component on entity."""
    ...

remove_component(entity, component_type)

Remove component from entity. Returns True if existed.

Source code in src/agentecs/storage/protocol.py
def remove_component(self, entity: EntityId, component_type: type) -> bool:
    """Remove component from entity. Returns True if existed."""
    ...

has_component(entity, component_type)

Check if entity has component.

Source code in src/agentecs/storage/protocol.py
def has_component(self, entity: EntityId, component_type: type) -> bool:
    """Check if entity has component."""
    ...

get_component_types(entity)

Get all component types on entity.

Source code in src/agentecs/storage/protocol.py
def get_component_types(self, entity: EntityId) -> frozenset[type]:
    """Get all component types on entity."""
    ...

query(*component_types, copy=True)

Find entities with all specified components.

Source code in src/agentecs/storage/protocol.py
def query(
    self,
    *component_types: type,
    copy: bool = True,
) -> Iterator[tuple[EntityId, tuple[Any, ...]]]:
    """Find entities with all specified components."""
    ...

snapshot()

Serialize entire storage state.

Source code in src/agentecs/storage/protocol.py
def snapshot(self) -> bytes:
    """Serialize entire storage state."""
    ...

restore(data)

Restore from snapshot.

Source code in src/agentecs/storage/protocol.py
def restore(self, data: bytes) -> None:
    """Restore from snapshot."""
    ...

LocalStorage

In-memory implementation for single-process use.

LocalStorage

Simple in-memory storage using nested dicts.

Structure

_components[entity][component_type] = component_instance

Not cache-efficient - for production, use archetypal storage.

Parameters:

Name Type Description Default
shard int

Shard number for this storage instance (default 0 for local).

0
Source code in src/agentecs/storage/local.py
class LocalStorage:
    """Simple in-memory storage using nested dicts.

    Structure:
        _components[entity][component_type] = component_instance

    Not cache-efficient - for production, use archetypal storage.

    Args:
        shard: Shard number for this storage instance (default 0 for local).
    """

    def __init__(self, shard: int = 0):
        """Initialize local storage.

        Args:
            shard: Shard number for this storage instance (default 0).
        """
        self._shard = shard
        self._allocator = EntityAllocator(shard=shard)
        self._components: dict[EntityId, dict[type, Any]] = {}

        self._shared_refs: dict[tuple[EntityId, type], int] = {}
        self._shared_components: dict[int, Any] = {}

    def _locate_component(self, entity: EntityId, component_type: type) -> tuple[int | None, bool]:
        """Find where a component lives for an entity.

        Returns:
            (instance_id, in_regular) where instance_id is set if in shared storage,
            and in_regular is True if in _components[entity].
        """
        instance_id = self._shared_refs.get((entity, component_type))
        in_regular = component_type in self._components.get(entity, {})
        return instance_id, in_regular

    def _gc_shared(self, instance_id: int) -> None:
        """Remove shared component if no entity references it."""
        if not any(sid == instance_id for sid in self._shared_refs.values()):
            self._shared_components.pop(instance_id, None)

    def _get_component_raw(self, entity: EntityId, component_type: type[T]) -> T | None:
        """Get component without copy (internal use). Unwraps shared wrappers."""
        instance_id, _ = self._locate_component(entity, component_type)
        if instance_id is not None:
            component = self._shared_components.get(instance_id)
            if isinstance(component, WrappedComponent):
                return cast(T, component.unwrap())
            return cast(T, component)
        return self._components.get(entity, {}).get(component_type)

    def create_entity(self) -> EntityId:
        """Create a new entity and return its ID.

        Returns:
            Newly allocated EntityId.
        """
        entity = self._allocator.allocate()
        self._components[entity] = {}
        return entity

    def destroy_entity(self, entity: EntityId) -> None:
        """Destroy an entity and remove all its components.

        Args:
            entity: Entity to destroy.
        """
        if entity in self._components:
            refs = [
                (key, instance_id)
                for key, instance_id in self._shared_refs.items()
                if key[0] == entity
            ]
            for key, instance_id in refs:
                del self._shared_refs[key]
                self._gc_shared(instance_id)
            del self._components[entity]
            self._allocator.deallocate(entity)

    def entity_exists(self, entity: EntityId) -> bool:
        """Check if an entity exists and is alive.

        Args:
            entity: Entity to check.

        Returns:
            True if entity exists and is alive, False otherwise.
        """
        return entity in self._components and self._allocator.is_alive(entity)

    def all_entities(self) -> Iterator[EntityId]:
        """Iterate over all alive entities.

        Yields:
            EntityId for each alive entity.
        """
        for entity in self._components:
            if self._allocator.is_alive(entity):
                yield entity

    def get_component(
        self, entity: EntityId, component_type: type[T], copy: bool = True
    ) -> Copy[T] | T | None:
        """Get a component from an entity.

        Args:
            entity: Entity to query.
            component_type: Type of component to retrieve.
            copy: Whether to return a copy of the component (default True).

        Returns:
            Component instance or None if not present.
        """
        if entity not in self._components:
            return None
        component = self._get_component_raw(entity, component_type)
        if component is None:
            return None
        return cp.deepcopy(component) if copy else component

    def set_component(self, entity: EntityId, component: Any) -> None:
        """Set or update a component on an entity.

        If the component is wrapped in Shared, it is stored as a shared instance
        and mapped to the entity via instance_id. Otherwise, stored directly.

        Args:
            entity: Entity to modify.
            component: Component instance to set (type inferred).
        """
        if entity not in self._components:
            self._components[entity] = {}

        def _set_shared(component: Shared[Any], prior_instance_id: int | None) -> None:
            """Helper to correctly set a shared component."""
            instance_id = component.ref_id
            self._shared_refs[(entity, get_type(component))] = instance_id
            self._shared_components[instance_id] = component.unwrap()
            if prior_instance_id is not None and prior_instance_id != instance_id:
                self._gc_shared(prior_instance_id)

        existing_id, _ = self._locate_component(entity, get_type(component))
        if existing_id is not None:
            if isinstance(component, Shared):
                _set_shared(component, existing_id)
            else:
                # Component type was previously shared but now regular - remove old shared ref
                del self._shared_refs[(entity, get_type(component))]
                self._components[entity][get_type(component)] = get_component(component)
                self._gc_shared(existing_id)
        else:
            if isinstance(component, Shared):
                if self.has_component(entity, get_type(component)):
                    self.remove_component(entity, get_type(component))
                _set_shared(component, None)

            else:
                self._components[entity][get_type(component)] = component

    def remove_component(self, entity: EntityId, component_type: type) -> bool:
        """Remove a component from an entity.

        Args:
            entity: Entity to modify.
            component_type: Type of component to remove.

        Returns:
            True if component was removed, False if not present.
        """
        if entity not in self._components:
            return False
        instance_id, in_regular = self._locate_component(entity, component_type)
        if instance_id is not None:
            del self._shared_refs[(entity, component_type)]
            self._gc_shared(instance_id)
            return True
        if in_regular:
            del self._components[entity][component_type]
            return True
        return False

    def remove_component_from_all(self, component_type: type) -> None:
        """Remove a component type from all entities.

        This completely removes shared components.

        Args:
            component_type: Type of component to remove from all entities.
        """
        # Clean per-instance shared refs for this type
        to_remove_inst = [
            (key, iid) for key, iid in self._shared_refs.items() if key[1] == component_type
        ]
        for key, iid in to_remove_inst:
            del self._shared_refs[key]
            self._gc_shared(iid)

        # Clean regular storage
        for comps in self._components.values():
            comps.pop(component_type, None)

    def has_component(self, entity: EntityId, component_type: type) -> bool:
        """Check if an entity has a specific component type.

        Args:
            entity: Entity to check.
            component_type: Component type to look for.

        Returns:
            True if entity has component, False otherwise.
        """
        if entity not in self._components:
            return False
        instance_id, in_regular = self._locate_component(entity, component_type)
        return instance_id is not None or in_regular

    def get_component_types(self, entity: EntityId) -> frozenset[type]:
        """Get all component types present on an entity.

        Args:
            entity: Entity to query.

        Returns:
            Frozenset of component types on entity.
        """
        if entity not in self._components:
            return frozenset()
        types = set(self._components[entity].keys())
        # Add shared types
        for (e, t), _ in self._shared_refs.items():
            if e == entity:
                types.add(t)
        return frozenset(types)

    def query(
        self,
        *component_types: type,
        copy: bool = True,
    ) -> Iterator[tuple[EntityId, tuple[Any, ...]]]:
        """Find entities with all specified components.

        O(n) scan - archetypal storage would be O(matched).

        Args:
            *component_types: Component types to query for.
            copy: Whether to return copies of components (default True).

        Yields:
            Tuples of (entity, (component1, component2, ...)) for each match.
        """
        type_set = set(component_types)
        for entity, components in self._components.items():
            if not self._allocator.is_alive(entity):
                continue
            entity_types = set(components.keys())
            for (e, t), _ in self._shared_refs.items():
                if e == entity:
                    entity_types.add(t)
            if type_set.issubset(entity_types):
                if copy:
                    result = tuple(
                        cp.deepcopy(self._get_component_raw(entity, t)) for t in component_types
                    )
                else:
                    result = tuple(self._get_component_raw(entity, t) for t in component_types)
                yield entity, result

    def query_single(
        self, component_type: type[T], copy: bool = True
    ) -> Iterator[tuple[EntityId, T]]:
        """Optimized single-component query.

        Args:
            component_type: Component type to query for.
            copy: Whether to return copies of components (default True).

        Yields:
            Tuples of (entity, component) for each match.
        """
        # Since this is local, no need to optimize here.
        iterator = self.query(component_type, copy=copy)
        for entity, components in iterator:
            yield entity, components[0]

    def snapshot(self) -> bytes:
        """Pickle entire state for serialization.

        Not efficient - use only for testing/prototyping, not production.

        Returns:
            Pickled bytes of storage state.
        """
        return pickle.dumps(
            {
                "shard": self._shard,
                "components": self._components,
                "allocator_next": self._allocator._next_index,
                "shared_refs": self._shared_refs,
                "shared_components": self._shared_components,
            }
        )

    def restore(self, data: bytes) -> None:
        """Restore from pickle snapshot.

        Args:
            data: Pickled bytes from previous snapshot() call.
        """
        state = pickle.loads(data)  # nosec B301 - Used only for local testing, not production
        self._shard = state["shard"]
        self._components = state["components"]
        self._allocator._next_index = state["allocator_next"]
        self._shared_refs = state["shared_refs"]
        self._shared_components = state["shared_components"]

    # Async variants - for LocalStorage these just wrap sync methods
    # Future distributed storage backends can implement truly async versions

    async def get_component_async(
        self, entity: EntityId, component_type: type[T], copy: bool = True
    ) -> T | None:
        """Get component from entity (async wrapper for sync implementation).

        Args:
            entity: Entity to query.
            component_type: Type of component to retrieve.
            copy: Whether to return a copy of the component (default True).

        Returns:
            Component instance or None if not present.
        """
        return self.get_component(entity, component_type, copy)

    async def query_async(
        self, *component_types: type, copy: bool = True
    ) -> AsyncIterator[tuple[EntityId, tuple[Any, ...]]]:
        """Find entities with all specified components (async wrapper).

        Args:
            *component_types: Component types to query for.
            copy: Whether to return copies of components (default True).

        Yields:
            Tuples of (entity, (component1, component2, ...)) for each match.
        """
        for entity, components in self.query(*component_types, copy=copy):
            yield entity, components

__init__(shard=0)

Initialize local storage.

Parameters:

Name Type Description Default
shard int

Shard number for this storage instance (default 0).

0
Source code in src/agentecs/storage/local.py
def __init__(self, shard: int = 0):
    """Initialize local storage.

    Args:
        shard: Shard number for this storage instance (default 0).
    """
    self._shard = shard
    self._allocator = EntityAllocator(shard=shard)
    self._components: dict[EntityId, dict[type, Any]] = {}

    self._shared_refs: dict[tuple[EntityId, type], int] = {}
    self._shared_components: dict[int, Any] = {}

create_entity()

Create a new entity and return its ID.

Returns:

Type Description
EntityId

Newly allocated EntityId.

Source code in src/agentecs/storage/local.py
def create_entity(self) -> EntityId:
    """Create a new entity and return its ID.

    Returns:
        Newly allocated EntityId.
    """
    entity = self._allocator.allocate()
    self._components[entity] = {}
    return entity

destroy_entity(entity)

Destroy an entity and remove all its components.

Parameters:

Name Type Description Default
entity EntityId

Entity to destroy.

required
Source code in src/agentecs/storage/local.py
def destroy_entity(self, entity: EntityId) -> None:
    """Destroy an entity and remove all its components.

    Args:
        entity: Entity to destroy.
    """
    if entity in self._components:
        refs = [
            (key, instance_id)
            for key, instance_id in self._shared_refs.items()
            if key[0] == entity
        ]
        for key, instance_id in refs:
            del self._shared_refs[key]
            self._gc_shared(instance_id)
        del self._components[entity]
        self._allocator.deallocate(entity)

entity_exists(entity)

Check if an entity exists and is alive.

Parameters:

Name Type Description Default
entity EntityId

Entity to check.

required

Returns:

Type Description
bool

True if entity exists and is alive, False otherwise.

Source code in src/agentecs/storage/local.py
def entity_exists(self, entity: EntityId) -> bool:
    """Check if an entity exists and is alive.

    Args:
        entity: Entity to check.

    Returns:
        True if entity exists and is alive, False otherwise.
    """
    return entity in self._components and self._allocator.is_alive(entity)

get_component(entity, component_type, copy=True)

Get a component from an entity.

Parameters:

Name Type Description Default
entity EntityId

Entity to query.

required
component_type type[T]

Type of component to retrieve.

required
copy bool

Whether to return a copy of the component (default True).

True

Returns:

Type Description
Copy[T] | T | None

Component instance or None if not present.

Source code in src/agentecs/storage/local.py
def get_component(
    self, entity: EntityId, component_type: type[T], copy: bool = True
) -> Copy[T] | T | None:
    """Get a component from an entity.

    Args:
        entity: Entity to query.
        component_type: Type of component to retrieve.
        copy: Whether to return a copy of the component (default True).

    Returns:
        Component instance or None if not present.
    """
    if entity not in self._components:
        return None
    component = self._get_component_raw(entity, component_type)
    if component is None:
        return None
    return cp.deepcopy(component) if copy else component

set_component(entity, component)

Set or update a component on an entity.

If the component is wrapped in Shared, it is stored as a shared instance and mapped to the entity via instance_id. Otherwise, stored directly.

Parameters:

Name Type Description Default
entity EntityId

Entity to modify.

required
component Any

Component instance to set (type inferred).

required
Source code in src/agentecs/storage/local.py
def set_component(self, entity: EntityId, component: Any) -> None:
    """Set or update a component on an entity.

    If the component is wrapped in Shared, it is stored as a shared instance
    and mapped to the entity via instance_id. Otherwise, stored directly.

    Args:
        entity: Entity to modify.
        component: Component instance to set (type inferred).
    """
    if entity not in self._components:
        self._components[entity] = {}

    def _set_shared(component: Shared[Any], prior_instance_id: int | None) -> None:
        """Helper to correctly set a shared component."""
        instance_id = component.ref_id
        self._shared_refs[(entity, get_type(component))] = instance_id
        self._shared_components[instance_id] = component.unwrap()
        if prior_instance_id is not None and prior_instance_id != instance_id:
            self._gc_shared(prior_instance_id)

    existing_id, _ = self._locate_component(entity, get_type(component))
    if existing_id is not None:
        if isinstance(component, Shared):
            _set_shared(component, existing_id)
        else:
            # Component type was previously shared but now regular - remove old shared ref
            del self._shared_refs[(entity, get_type(component))]
            self._components[entity][get_type(component)] = get_component(component)
            self._gc_shared(existing_id)
    else:
        if isinstance(component, Shared):
            if self.has_component(entity, get_type(component)):
                self.remove_component(entity, get_type(component))
            _set_shared(component, None)

        else:
            self._components[entity][get_type(component)] = component

remove_component(entity, component_type)

Remove a component from an entity.

Parameters:

Name Type Description Default
entity EntityId

Entity to modify.

required
component_type type

Type of component to remove.

required

Returns:

Type Description
bool

True if component was removed, False if not present.

Source code in src/agentecs/storage/local.py
def remove_component(self, entity: EntityId, component_type: type) -> bool:
    """Remove a component from an entity.

    Args:
        entity: Entity to modify.
        component_type: Type of component to remove.

    Returns:
        True if component was removed, False if not present.
    """
    if entity not in self._components:
        return False
    instance_id, in_regular = self._locate_component(entity, component_type)
    if instance_id is not None:
        del self._shared_refs[(entity, component_type)]
        self._gc_shared(instance_id)
        return True
    if in_regular:
        del self._components[entity][component_type]
        return True
    return False

has_component(entity, component_type)

Check if an entity has a specific component type.

Parameters:

Name Type Description Default
entity EntityId

Entity to check.

required
component_type type

Component type to look for.

required

Returns:

Type Description
bool

True if entity has component, False otherwise.

Source code in src/agentecs/storage/local.py
def has_component(self, entity: EntityId, component_type: type) -> bool:
    """Check if an entity has a specific component type.

    Args:
        entity: Entity to check.
        component_type: Component type to look for.

    Returns:
        True if entity has component, False otherwise.
    """
    if entity not in self._components:
        return False
    instance_id, in_regular = self._locate_component(entity, component_type)
    return instance_id is not None or in_regular

get_component_types(entity)

Get all component types present on an entity.

Parameters:

Name Type Description Default
entity EntityId

Entity to query.

required

Returns:

Type Description
frozenset[type]

Frozenset of component types on entity.

Source code in src/agentecs/storage/local.py
def get_component_types(self, entity: EntityId) -> frozenset[type]:
    """Get all component types present on an entity.

    Args:
        entity: Entity to query.

    Returns:
        Frozenset of component types on entity.
    """
    if entity not in self._components:
        return frozenset()
    types = set(self._components[entity].keys())
    # Add shared types
    for (e, t), _ in self._shared_refs.items():
        if e == entity:
            types.add(t)
    return frozenset(types)

query(*component_types, copy=True)

Find entities with all specified components.

O(n) scan - archetypal storage would be O(matched).

Parameters:

Name Type Description Default
*component_types type

Component types to query for.

()
copy bool

Whether to return copies of components (default True).

True

Yields:

Type Description
tuple[EntityId, tuple[Any, ...]]

Tuples of (entity, (component1, component2, ...)) for each match.

Source code in src/agentecs/storage/local.py
def query(
    self,
    *component_types: type,
    copy: bool = True,
) -> Iterator[tuple[EntityId, tuple[Any, ...]]]:
    """Find entities with all specified components.

    O(n) scan - archetypal storage would be O(matched).

    Args:
        *component_types: Component types to query for.
        copy: Whether to return copies of components (default True).

    Yields:
        Tuples of (entity, (component1, component2, ...)) for each match.
    """
    type_set = set(component_types)
    for entity, components in self._components.items():
        if not self._allocator.is_alive(entity):
            continue
        entity_types = set(components.keys())
        for (e, t), _ in self._shared_refs.items():
            if e == entity:
                entity_types.add(t)
        if type_set.issubset(entity_types):
            if copy:
                result = tuple(
                    cp.deepcopy(self._get_component_raw(entity, t)) for t in component_types
                )
            else:
                result = tuple(self._get_component_raw(entity, t) for t in component_types)
            yield entity, result

snapshot()

Pickle entire state for serialization.

Not efficient - use only for testing/prototyping, not production.

Returns:

Type Description
bytes

Pickled bytes of storage state.

Source code in src/agentecs/storage/local.py
def snapshot(self) -> bytes:
    """Pickle entire state for serialization.

    Not efficient - use only for testing/prototyping, not production.

    Returns:
        Pickled bytes of storage state.
    """
    return pickle.dumps(
        {
            "shard": self._shard,
            "components": self._components,
            "allocator_next": self._allocator._next_index,
            "shared_refs": self._shared_refs,
            "shared_components": self._shared_components,
        }
    )

restore(data)

Restore from pickle snapshot.

Parameters:

Name Type Description Default
data bytes

Pickled bytes from previous snapshot() call.

required
Source code in src/agentecs/storage/local.py
def restore(self, data: bytes) -> None:
    """Restore from pickle snapshot.

    Args:
        data: Pickled bytes from previous snapshot() call.
    """
    state = pickle.loads(data)  # nosec B301 - Used only for local testing, not production
    self._shard = state["shard"]
    self._components = state["components"]
    self._allocator._next_index = state["allocator_next"]
    self._shared_refs = state["shared_refs"]
    self._shared_components = state["shared_components"]

Usage Example

from agentecs import World
from agentecs.storage import LocalStorage

# Use default LocalStorage
world = World()

# Or provide custom storage
custom_storage = LocalStorage()
world = World(storage=custom_storage)

Future Storage Backends

ArchetypalStorage (Planned): - Cache-friendly memory layout - O(matched) query performance - Optimized for iteration over large entity sets

Distributed Storage (Research): - Cross-shard queries - Eventual consistency models - Network-aware optimization