# elsciRL Adapters: Abstract Form Documentation ## Overview Adapters in `elsciRL` provide a standardized interface for transforming raw environment states into a form suitable for reinforcement learning (RL) agents. This abstraction allows for flexible integration of various state representations, including numeric, symbolic, or language-based forms (e.g., via LLMs). ## Abstract Base Class: `StateAdapter` All adapters inherit from the abstract base class `StateAdapter`, which defines the core interface and expected methods for any state adapter implementation. ### Key Methods - **`__init__(self, raw_state)`** Initializes the adapter with a raw state and processes it using the `_read` method. - **`_read(raw_state) -> list`** Abstract method. Reads and processes the raw state, returning a list of features. *Must be implemented by subclasses.* - **`adapter(self, state, legal_moves=None, episode_action_history=None, encode=True, indexed=False)`** Returns the adapted state. May include options for encoded or non-encoded output, and for indexed representations. *Should be overridden by subclasses.* - **`sample(self)`** Returns a sample adapted state, typically representing an initial environment state. *Should be overridden by subclasses.* ### Example Abstract Adapter ```python from abc import ABC, abstractmethod class Adapter(ABC): def __init__(self, raw_state): super().__init__() self.state: list = self._read(raw_state) @abstractmethod def _read(raw_state) -> list: raise NotImplementedError def adapter(self, state, legal_moves=None, episode_action_history=None, encode=True, indexed=False): # Return the adapted state (to be implemented in subclass) pass def sample(self): # Return a sample adapted state (to be implemented in subclass) pass ``` --- ## Practical Adapter Implementations Below are examples of how the abstract adapter interface is implemented for different environments in elsciRL. ### 1. Tabular State Adapter (TextWorldExpress Example) ```python from elsciRL.encoders.poss_state_encoded import StateEncoder import torch class Adapter: _cached_state_idx = dict() def __init__(self, setup_info={}): all_possible_states = [i for i in range(4*4)] self.encoder = StateEncoder(all_possible_states) def adapter(self, state, legal_moves=None, episode_action_history=None, encode=True, indexed=False): if encode: state_encoded = self.encoder.encode(state=state) else: state_encoded = state if indexed: state_indexed = [] for sent in state: if sent not in Adapter._cached_state_idx: Adapter._cached_state_idx[sent] = len(Adapter._cached_state_idx) state_indexed.append(Adapter._cached_state_idx[sent]) state_encoded = torch.tensor(state_indexed) return state_encoded ``` ### 2. Grid State Adapter (Classroom Example) ```python from elsciRL.encoders.poss_state_encoded import StateEncoder from gymnasium.spaces import Discrete import torch class Adapter: _cached_state_idx = dict() def __init__(self, setup_info={}): self.x_range = [0,1,2,3,4,5] self.y_range = [0,1,2,3,4,5] possible_states = [str(x)+'_'+str(y) for x in self.x_range for y in self.y_range] self.encoder = StateEncoder(len(possible_states)) self.observation_space = Discrete(len(possible_states)) def adapter(self, state, legal_moves=None, episode_action_history=None, encode=True, indexed=False): if encode: state_encoded = self.encoder.encode(state=state) else: state_encoded = state if indexed: state_indexed = [] for sent in state: if sent not in Adapter._cached_state_idx: Adapter._cached_state_idx[sent] = len(Adapter._cached_state_idx) state_indexed.append(Adapter._cached_state_idx[sent]) state_encoded = torch.tensor(state_indexed) return state_encoded ``` ### 3. Continuous/Discretized State Adapter (Sailing Example) ```python from elsciRL.encoders.poss_state_encoded import StateEncoder from gymnasium.spaces import Discrete import numpy as np import torch class Adapter: @staticmethod def angle_to_state(angle): return int(30 * ((angle + np.pi) / (2 * np.pi) % 1)) @staticmethod def x_to_state(x): return int(40 * ((x + -10) / 20)) @staticmethod def state_discretizer(state): x = float(state.split('_')[0]) x_state = Adapter.x_to_state(x) angle = float(state.split('_')[1]) angle_state = Adapter.angle_to_state(angle) return str(x_state)+'_'+str(angle_state) _cached_state_idx = dict() def __init__(self, setup_info={}): num_x_states = 10*2 num_angle_states = 30 self.num_states = num_x_states*(10**setup_info['obs_precision']) * num_angle_states self.observation_space = Discrete(2000*30) self.one_hot_encoder = StateEncoder(self.num_states) self.index_encoder = {} self.encoder_idx = 0 def adapter(self, state, legal_moves=None, episode_action_history=None, encode=True, indexed=False): #state = Adapter.state_discretizer(state) if encode: if indexed: state_encoded = self.one_hot_encoder.encode(state=state, legal_actions=legal_moves, episode_action_history=episode_action_history, indexed=indexed) else: if state not in self.index_encoder: state_encoded = torch.tensor([self.encoder_idx]).float() self.index_encoder[state] = state_encoded self.encoder_idx += 1 else: state_encoded = self.index_encoder[state] else: state_encoded = state return state_encoded ``` --- ## Summary - **Adapters** in elsciRL provide a consistent interface for transforming environment states for RL agents. - The **abstract base class** defines the required methods and structure. - **Concrete implementations** adapt the interface for specific environments, handling encoding, indexing, and feature extraction as needed. - This design supports both simple tabular and complex, high-dimensional or language-based state representations.