def test_class_docstring(): @add_metaclass(DocInheritMeta(style="numpy")) class Parent(object): """ Parent class. Returns ------- foo """ class Mixin(object): """ This is mixin which does something. """ class Child(Mixin, Parent): """ Attributes ---------- bar """ assert getdoc(Child) == \ 'This is mixin which does something.\n\nAttributes\n----------\nbar\n\nReturns\n-------\nfoo'
class JobDefinition(metaclass=DocInheritMeta( style="google", abstract_base_class=True) # type: ignore ): """A Batch job definition.""" def __new__(cls, *args: str, **kwargs: str) -> 'JobDefinition': """Create a new Batch job definition.""" this: JobDefinition = super().__new__(cls) this._revision = '0' return this @property @abstractmethod def name(self) -> str: """Return the name of the job definition.""" @property def parameters(self) -> Tuple[str]: """Return the parameters of the job definition.""" return tuple(inspect.signature( self.__init__).parameters.keys()) # type: ignore @property def revision(self) -> str: """Return the revision of the job definition.""" return self._revision @abstractmethod def validate(self) -> None: """Validate this job definition after initialization.""" def at_revision(self, revision: str) -> 'JobDefinition': """Set this job definition to a specific revision.""" self._revision = revision return self def make_job_name(self, moment: Optional[datetime] = None) -> str: """Format a Batch job name from this definition.""" moment = datetime.now() if moment is None else moment return format_ISO8601(moment) + '_' + self.name def to_dict(self) -> Dict[str, str]: """Return a dictionary of all parameters and their values as strings.""" mapping: Dict[str, str] = { key: str(getattr(self, key)) for key in self.parameters } return mapping def __str__(self) -> str: return f'{self.name}:{self.revision}' def __repr__(self) -> str: parts = [ f'{key}={repr(getattr(self, key))}' for key in self.parameters ] signature = ', '.join(parts) return f'{self.__class__.__qualname__}({signature})'
class Schedule(metaclass=DocInheritMeta(style="numpy")): """Base class for value schedule, used for decaying values in exploration methods. Parameters ---------- start_value : float initial value at first timestep. interval : int update interval in number of timesteps (default is -1, which means every episode). Attributes ---------- t : int current timestep index. value : float current value. interval : int update interval in number of timesteps. """ def __init__(self, start_value, interval=-1): self.timestep = 0 self.value = start_value self.interval = interval def _step(self): """Implement the logic of the schedule for one iteration of the value.""" pass def step(self, new_episode): """Wrap the schedule logic to determine based on interval when to iterate value. Parameters ---------- new_episode : bool flag to indicate whether this step is one that resets the environment. """ if self.interval > 0: if self.timestep % self.interval == 0: self._step() else: if new_episode: self._step() self.timestep += 1 def get(self): """Get value, bounded in [0, 1]. Returns ------- float current value given by schedule. """ return max([0., min([1., self.value])])
class Market(metaclass=DocInheritMeta(style="numpy", abstract_base_class=True)): """ An abstract market class where an implementation needs to implement send_messages. A market always has an order book which is set in :py:meth:`__init__`. Attributes ---------- ob: OrderBook The markets order book. tick_size: float The minimum multiple of price difference in a market. tick_dec: int Number of decimals in tick_size multipler: int 10**tick_dec. Used to multiply price to int """ def __init__(self, tick_size=0.01, ob_type='py', price_level_type='ordered_dict', price_levels_type='sorted_dict'): """ Parameters ---------- tick_size: float The minimum multiple of price difference in a market. ob_type: str Type of order book. price_levels_type: str Type or price levels for the order book. price_level_type: str Type of order level for the price levels """ self.ob = get_ob(ob_type, price_level_type, price_levels_type) self.tick_size = tick_size self.tick_dec = int(np.log10(1 / tick_size)) self.multiplier = 10**self.tick_dec @abc.abstractmethod def send_message(self, message: dict) -> (list, tuple): """ The market receives a message and returns a possible trade or an order in the book.
from itertools import islice, repeat from copy import deepcopy from custom_inherit import DocInheritMeta DOC_INHERIT = DocInheritMeta(style='numpy', abstract_base_class=False) DOC_INHERIT_ABSTRACT = DocInheritMeta(style='numpy', abstract_base_class=True) class SaveLoad(metaclass=DOC_INHERIT_ABSTRACT): """Base class for converting to/from JSON. Attributes ---------- classes : `dict` Class group. Examples -------- >>> class SaveLoadGroup(SaveLoad): ... classes = {} ... >>> class SaveLoadObject(SaveLoadGroup): ... def __init__(self, attr=None): ... self.attr = attr ... def save(self): ... data = super().save() ... data['attr'] = self.attr ... return data ... >>> SaveLoadGroup.add_class(SaveLoadObject)
class _Node(metaclass=DocInheritMeta(style="numpy_with_merge", include_special_methods=True)): """ Base class of all nodes in a wonterfact tree, i.e. the graphical representation of a tensor factorization model. """ def __init__(self, name=None, **kwargs): """ Parameters ---------- name: str or None, optional, default None A name for the node. Very useful for debugging but not necessary. Each node's name in a tree should be unique. If None, a unique name is generated. """ self.name = name if not self.name: base62.sign = "_" self.name = "n_" + base62.encode(id(self)) def census(self, nodebook=None): # Needs to be overridden in ChildNode class. """ Returns a single element set with itself in it. Returns ------- set """ if nodebook is None: nodebook = set() nodebook.add(self) return nodebook # Needs to be overridden in DynNodeData def should_update(self, iteration_number=None): """ Always returns True Returns ------- bool """ return True def _set_inference_mode(self, mode="EM"): if mode not in ("EM", "VBEM"): raise ValueError( "Wrong inference mode value: must be 'EM of 'VBEM' ") self._inference_mode = mode def _check_filiation_ok(self, child=None, parent=None, **kwargs): """ Raise an error if the node cannot accept to bound with either a new parent or a new child, given self class, input node's class and input kwargs. """ pass def _check_model_validity(self): """ Raise an error if one suspects that the model (i.e. the tree structure) is locally wrong """ pass def __repr__(self): str_out = "{}(name='{}')".format(type(self).__name__, self.name) return str_out def clear_cache(self): list_of_names = [ name for name, value in inspect.getmembers(self.__class__) if isinstance(value, cached_property) ] for name in list_of_names: try: self.__delattr__(name) except AttributeError: pass list_of_names = [ name for name, value in inspect.getmembers(self.__class__) if isinstance(value, _LruCacheWire) ] for name in list_of_names: getattr(self, name).cache_clear()
class MoodleBasicHelper(metaclass=DocInheritMeta(style='google_with_merge', include_special_methods=True) ): @classmethod def format_string(cls, string: str) -> str: ''' Replace all invalid characters with underscore. Args: string (str): input string Returns: str: valid string in lowercase. ''' if not string: raise ValueError('string is empty') string = re.sub(r'[^\w-]+', '_', string) return string.lstrip('_.-').lower()[:50] @classmethod def email_to_username(cls, email: str) -> str: ''' Normalizes an email to get a username. This function calculates the username by getting the string before the @ symbol, removing special characters, removing comments, converting string to lowercase, and adds 1 if the username has an integer value already in the string. Args: email: A valid email address Returns: str: A username string Raises: ValueError: if email is empty ''' if not email: raise ValueError('email is missing') username = email.split('@')[0] username = username.split('+')[0] username = re.sub(r'\([^)]*\)', '', username) username = re.sub(r'[^\w-]+', '', username) username = username.lower() logger.debug(f'Normalized email {email!r} to {username!r}') return username @classmethod def get_user_group(cls, user: User) -> str: '''Map LMS user's role to one of student, instructor or a grader. Args: user (User): User dict with 'role' key Returns: str: Jupyterhub role Raises: KeyError: No appropriate role was found. ''' if user['role'] == 'student': group = 'students' elif user['role'] in ('editingteacher', 'manager', 'coursecreator', 'instructional_support'): group = 'instructors' elif user['role'] in ('teaching_assistant', 'teacher'): group = 'graders' else: raise KeyError(user['role']) return group @classmethod def format_course(cls, course: JsonType) -> Course: '''Format raw json response to convinient dictionary. Args: course (JsonType): Raw Json from LMS containing course data. Returns: Course: JsonDict with course information ''' return JsonDict({ 'id': course['id'], 'course_id': cls.format_string(course['shortname']), 'title': course['displayname'], 'category': course['categoryid'], 'need_nbgrader': course['categoryid'] == int( os.environ['MOODLE_NBGRADER_CATEGORY_ID']), 'instructors': [], 'students': [], 'graders': [], 'lms_lineitems_endpoint': f'{os.environ["MOODLE_BASE_URL"]}/mod/lti/services.php/{course["id"]}/lineitems' }) @classmethod def format_user(cls, user: JsonType) -> User: '''Format raw json response to convinient dictionary. Args: user (JsonType): Raw Json from LMS containing user data. Returns: User: JsonDict with user information ''' return JsonDict({ 'id': user['id'], 'first_name': user['firstname'], 'last_name': user['lastname'], 'username': cls.format_string(user['username']), 'email': user['email'], 'roles': [role['shortname'] for role in user['roles']], }) @classmethod def skip_course( cls, course: Course, filters: t.Dict[str, t.Union[t.Sequence[t.AnyStr], t.AnyStr]], ) -> bool: '''Determines should the course be skipped according to provided filters Args: course (Course): Course created by calling format_course method. filters (t.Dict[str, t.Union[t.Sequence[t.AnyStr], t.AnyStr]]): key-value pairs where value can be both single value or list of valid items. Returns: bool: True if the course failed filters check. False otherwise. ''' for field, value in filters.items(): # if provided filter is a sequence # check that course's value is in that sequence if not isinstance(value, str) and hasattr(type(value), '__iter__'): if not value: raise ValueError(f'Empty sequence found: {field}') if course[field] not in value: return True else: if course[field] != value: return True return False
class MarketEnv(metaclass=DocInheritMeta(style="numpy", abstract_base_class=True)): """ A market environment extending the `OpenAI gym class <https://github.com/openai/gym/blob/master/gym/core.py/>`_ . The main functions are the step and reset functions. In the step function the agent sends orders and receives a reward based on the orders/trades. A basic rendering function using `Dash <https://plot.ly/products/dash/>`_ is implemented. This opens a Flask server in another thread that renders different information about the market. Attributes ---------- capital : float The capital of the agent funds : float The cash/funds of the agent possession : float The size of the asset the agent possesses price_n : int Number of price points to render back in time. trades_list : list All trades """ # Set this in SOME subclasses metadata = {'render.modes': []} reward_range = (-float('inf'), float('inf')) spec = None def __init__(self, market_type='cyext', tick_size=0.01, ob_type='cy', price_level_type='cydeque', price_levels_type='cylist', initial_funds=10000, T_ID=1, **kwargs): """ Parameters ---------- market_type : str The type of market to be used market_setup : dict Parameters for the market initial_funds : float The initial cash of the agent T_ID : int The agent's trader id """ # Market self._market_type = market_type self._market_setup = dict(tick_size=tick_size, ob_type=ob_type, price_level_type=price_level_type, price_levels_type=price_levels_type) # Init Funds self.capital = initial_funds self.funds = initial_funds self.initial_funds = initial_funds # Rendering self.render_app = None self.open_tab = False self.first_render = True self.price_n = 10000 self.trades_list = [] self.T_ID = T_ID self.obs_shape_n = 4 # Quotes def step(self, action): """ Sends orders based on the agents action and receives a reward based on trades that have occurred. The action is first converted to messages to the market. Then the messages are sent and trades are received. The reward is then calculated based on the trades. Parameters ---------- action : numpy.array Returns ------- obs : list[tuple, tuple] The current quotes of the market and private information about the agents portfolio reward : float done : bool If the episode has finished info : dict Additional information """ messages = self.get_messages(action) trades, done, info = self.send_messages(messages) reward = self.get_reward(trades, done) obs = self.get_obs() return obs, reward, done, info @abc.abstractmethod def get_messages(self, action: np.array) -> tuple: """ Returns messages based on the action received. Parameters ---------- action : numpy.array Returns ------- messages: tuple """ @abc.abstractmethod def get_reward(self, trades: list) -> tuple: """ Returns a reward based on trades Parameters ---------- trades : list Returns ------- reward : float """ @abc.abstractmethod def send_messages(self, messages: tuple) -> (list, bool, dict): """ Sends all the messages to the market Parameters ---------- messages : tuple Returns ------- trades : list done : bool If the episode has finished or not. info : dict Extra information about the environment """ @abc.abstractmethod def get_private_variables(self) -> tuple: """ Returns private variables of for the agent to observe. For example the possession of the agent. Returns ------- private_variables : tuple """ def get_obs(self) -> tuple: """ Returns an observation of the market. Currently the quotes. Returns ------- observation : tuple """ if hasattr(self, 'quotes'): obs = self.quotes else: obs = self.market.ob.price_levels.get_quotes() self.quotes = obs obs = (obs, self.get_private_variables()) return obs def reset(self, market=None): """ Resets the environment with a market or creates a new one. Also resets the render app if needed. Parameters ---------- market : Market If to use a specific market instead of a newly created one. Returns ------- observation : tuple """ if market is not None: self.market = market else: self.market = get_market(self._market_type, **self._market_setup) obs = self.get_obs() self.capital = self.initial_funds self.possession = 0 self.funds = self.initial_funds if self.render_app: self.render_app.use_reloader = False self.render_app.__setattr__('ask', deque(maxlen=self.price_n)) self.render_app.__setattr__('bid', deque(maxlen=self.price_n)) self.render_app.__setattr__('time', deque(maxlen=self.price_n)) self.render_app.__setattr__('sellbook', {}) self.render_app.__setattr__('buybook', {}) self.render_app.__setattr__('trades_', []) return obs, self.get_private_variables() def render(self, mode=None): """ Renders the environment in a dash app. """ if self.first_render: self.render_app.use_reloader = False self.render_app.__setattr__('ask', deque(maxlen=self.price_n)) self.render_app.__setattr__('bid', deque(maxlen=self.price_n)) self.render_app.__setattr__('time', deque(maxlen=self.price_n)) self.render_app.__setattr__('sellbook', {}) self.render_app.__setattr__('buybook', {}) self.render_app.__setattr__('trades_', []) self.app_thread = threading.Thread( target=self.render_app.run_server) self.app_thread.start() if not self.open_tab: self.open_tab = webbrowser.open_new('http://127.0.0.1:8050') self.first_render = False time_ = pd.to_datetime(self.market.time) ask, ask_vol, bid, bid_vol = self.quotes self.render_app.ask.append(ask) self.render_app.bid.append(bid) self.render_app.time.append(time_) self.render_app.trades_ = self.trades_list[-40:] ask, ask_vol, bid, bid_vol = self.quotes n = 50 buy_book = {} for i in range(n): p = bid - i try: size = self.market.ob.price_levels.get_level(BUY, p).size except (KeyError, IndexError) as e: pass buy_book[p] = size n = 50 sell_book = {} for i in range(n): p = ask + i try: size = self.market.ob.price_levels.get_level(SELL, p).size except (KeyError, IndexError) as e: pass sell_book[p] = size self.render_app.buybook = buy_book self.render_app.sellbook = sell_book def close(self): """ Shuts down the render app if needed. """ if self.render_app: requests.post('http://127.0.0.1:8050/shutdown') return @abc.abstractmethod def seed(self, seed=None): """Sets the seed for this env's random number generator(s). """ return # Set these in ALL subclasses @property @abc.abstractmethod def action_space(self): """gym.spaces : The action space of the environment.""" @property @abc.abstractmethod def observation_space(self): """gym.spaces : The obervation space of the environment.""" @property def unwrapped(self): """Completely unwrap this env. Returns: gym.Env: The base non-wrapped gym.MarketEnv instance """ return self def __str__(self): if self.spec is None: return '<{} instance>'.format(type(self).__name__) else: return '<{}<{}>>'.format(type(self).__name__, self.spec.id)
class AbstractParams(metaclass=DocInheritMeta(style="numpy")): """ An abstract class to hold the parameters of a QAOA run and compute the angles from them. Parameters ---------- hyperparameters: The hyperparameters containing the hamiltonian, the number of steps and possibly more (e.g. the total annealing time). ``hyperparametesr = (hamiltonian, n_steps, ...)`` parameters: Tuple The QAOA parameters, that can be optimized. E.g. the gammas and betas or the annealing timesteps. AbstractParams doesn't implement this, but all child classes do. Attributes ---------- pair_qubit_coeffs : np.array Coefficients of the pair terms in the hamiltonian qubits_pairs : List List of the qubit pairs in the hamiltonian. Same ordering as `pair_qubit_coeffs` single_qubit_coeffs : np.array Coefficients of the bias terms qubits_singles : List List of the bias qubits in the hamiltonian. Same ordering as `single_qubit_coeffs` reg : List List of all qubits for the X-rotations """ # pylint: disable=too-many-instance-attributes def __init__(self, hyperparameters: Tuple): # Note # ---- # ``AbstractParams.__init__`` doesn't do anything with the # argument ``parameters``. In the child classes we have to super from # them and handle them correctly. Additionally we might need to handle # extra hyperparameters. hamiltonian, self.n_steps = hyperparameters[:2] # extract the qubit lists from the hamiltonian self.reg = hamiltonian.get_qubits() self.qubits_pairs = [] self.qubits_singles = [] # and fill qubits_singles and qubits_pairs according to the terms in the hamiltonian for term in hamiltonian: if len(term) == 1: self.qubits_singles.append(term.get_qubits()[0]) elif len(term) == 2: self.qubits_pairs.append(term.get_qubits()) elif len(term) == 0: pass # could give a notice, that multiples of the identity are # ignored, since unphysical else: raise NotImplementedError( "As of now we can only handle hamiltonians with at most two-qubit terms" ) # extract the cofficients of the terms from the hamiltonian # if creating `ExtendedParams` form this, we can delete this attributes # again. Check if there are complex coefficients and issue a warning. self.single_qubit_coeffs = np.array( [term.coefficient for term in hamiltonian if len(term) == 1]) if np.any(np.iscomplex(self.single_qubit_coeffs)): warnings.warn( "hamiltonian contained complex coefficients. Ignoring imaginary parts" ) self.single_qubit_coeffs = self.single_qubit_coeffs.real # and the same for the pair qubit coefficients self.pair_qubit_coeffs = np.array( [term.coefficient for term in hamiltonian if len(term) == 2]) if np.any(np.iscomplex(self.pair_qubit_coeffs)): warnings.warn( "hamiltonian contained complex coefficients. Ignoring imaginary parts" ) self.pair_qubit_coeffs = self.pair_qubit_coeffs.real def __repr__(self): """Return an overview over the parameters and hyperparameters Todo ---- Split this into ``__repr__`` and ``__str__`` with a more verbose output in ``__repr__``. """ string = "Hyperparameters:\n" string += "\tregister: " + str(self.reg) + "\n" string += "\tqubits_singles: " + str(self.qubits_singles) + "\n" string += "\tsingle_qubit_coeffs: " + str( self.single_qubit_coeffs) + "\n" string += "\tqubits_pairs: " + str(self.qubits_pairs) + "\n" string += "\tpair_qubit_coeffs: " + str(self.pair_qubit_coeffs) + "\n" string += "\tn_steps: " + str(self.n_steps) + "\n" return string def __len__(self): """ Returns ------- int: the length of the data produced by self.raw() and accepted by self.update_from_raw() """ raise NotImplementedError() @property def x_rotation_angles(self): """2D array with the X-rotation angles. 1st index goes over n_steps and the 2nd index over the qubits to apply X-rotations on. These are needed by ``qaoa.cost_function.make_qaoa_memory_map`` """ raise NotImplementedError @property def z_rotation_angles(self): """2D array with the ZZ-rotation angles. 1st index goes over the n_steps and the 2nd index over the qubit pairs, to apply ZZ-rotations on. These are needed by ``qaoa.cost_function.make_qaoa_memory_map`` """ raise NotImplementedError @property def zz_rotation_angles(self): """2D array with Z-rotation angles. 1st index goes over the n_steps and the 2nd index over the qubit pairs, to apply Z-rotations on. These are needed by ``qaoa.cost_function.make_qaoa_memory_map`` """ raise NotImplementedError def update_from_raw(self, new_values: Union[list, np.array]): """ Update all the parameters from a 1D array. The input has the same format as the output of ``self.raw()``. This is useful for ``scipy.optimize.minimize`` which expects the parameters that need to be optimized to be a 1D array. Parameters ---------- new_values: A 1D array with the new parameters. Must have length ``len(self)`` and the ordering of the flattend ``parameters`` in ``__init__()``. """ raise NotImplementedError() def raw(self): """ Return the parameters in a 1D array. This 1D array is needed by ``scipy.optimize.minimize`` which expects the parameters that need to be optimized to be a 1D array. Returns ------- np.array: The parameters in a 1D array. Has the same output format as the expected input of ``self.update_from_raw``. Hence corresponds to the flattened `parameters` in `__init__()` """ raise NotImplementedError() def raw_rotation_angles(self): """ Flat array of the rotation angles for the memory map for the parametric circuit. Returns ------- np.array: Returns all single rotation angles in the ordering ``(x_rotation_angles, gamma_singles, zz_rotation_angles)`` where ``x_rotation_angles = (beta_q0_t0, beta_q1_t0, ... , beta_qn_tp)`` and the same for ``z_rotation_angles`` and ``zz_rotation_angles`` """ raw_data = np.concatenate((self.x_rotation_angles.flatten(), self.z_rotation_angles.flatten(), self.zz_rotation_angles.flatten())) return raw_data @classmethod def linear_ramp_from_hamiltonian(cls, hamiltonian: PauliSum, n_steps: int, time: float = None): """Alternative to ``__init__`` that already fills ``parameters``. Calculate initial parameters from a hamiltonian corresponding to a linear ramp annealing schedule and return a ``QAOAParams`` object. Parameters ---------- hamiltonian: ``hamiltonian`` for which to calculate the initial QAOA parameters. n_steps: Number of timesteps. time: Total annealing time. Defaults to ``0.7*n_steps``. Returns ------- Type[AbstractParams] The initial parameters for a linear ramp for ``hamiltonian``. """ raise NotImplementedError() @classmethod def from_AbstractParameters(cls, abstract_params, parameter: Tuple = None): """Alternative to ``__init__`` that takes ``hyperparameters`` from an existing ``AbstractQAOAParameter`` object. Create a ConcreteQAOAParameters instance from an AbstractQAOAParameters instance with hyperparameters from ``abstract_params`` and normal parameters from ``parameters`` Parameters ---------- abstract_params: AbstractParams An AbstractQAOAParameters instance to which to add the parameters parameter: A tuple containing the parameters. Must be the same as in ``__init__`` Returns ------- AbstractParams A copy of itself. This function makes only sense for the child classes of ``AbstractQAOAParameters`` """ params = copy.deepcopy(abstract_params) params.__class__ = cls return params def plot(self, ax=None, **kwargs): """ Plots ``self`` in a sensible way to the canvas ``ax``, if provided. Parameters ---------- ax: matplotlib.axes._subplots.AxesSubplot The canvas to plot itself on kwargs: All remaining keyword arguments are passed forward to the plot function """ raise NotImplementedError()
class BaseModel(metaclass=DocInheritMeta(style="numpy")): """Base class for all kinds of reinforcement learning models. All models should take in some form of feature-extracting topology, which is how the user defines the architecture of the model. From there this model should configure the input and output according to the other components it's connected to via the Agent. The public interface should also be common among models, so that is defined here as well. Parameters ---------- topology : pavlov.Topology feature-extracting Keras model graph object defining the body of the model. Attributes ---------- topology : pavlov.Topology feature-extracting Keras model graph object defining the body of the model. """ def __init__(self, topology): self.topology = topology def has_nan(self): """Throw an informative error if any model weights have gone to nan.""" pass def _configure_model(self, session): """Generate necessary models, probably finishing off `self.topology` to do so.""" pass def configure(self, agent, session): """Associate model with action space from environment; convert topology to model.""" self.batch_size = agent.batch_size self.action_space = agent.env.action_space self.action_type = self.action_space.__class__.__name__.lower() self.topology.configure(agent) self._configure_model(session) def fit(self, states, actions, rewards, next_states, dones): """Fit model to batch from experience. May or may not use all inputs. Parameters ---------- states : np.ndarray states to fit to. actions : np.ndarray actions to fit to. rewards : np.ndarray rewards to fit to. next_states : np.ndarray next states to fit to. dones : np.ndarray done flags to fit to. Raises ------ NotImplementedError. """ raise NotImplementedError def predict(self, state): """Return model output for given state input. May be policy or value. Parameters ---------- state : np.ndarray state input to produce output for. Raises ------ NotImplementedError. """ raise NotImplementedError
class AbstractClient(metaclass=DocInheritMeta(style="numpy")): """Abstract base class for all clients. Parameters ---------- session : ``pycf3.Session`` (default: ``None``) The session to use to send the requests. By default a ``pyc3.RetrySession`` with 3 retry is created. More info: https://2.python-requests.org, https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html. cache : ``diskcache.Cache``, ``diskcache.Fanout``, ``pycf3.NoCache`` or ``None`` (default: ``None``) Any instance of ``diskcache.Cache``, ``diskcache.Fanout`` or ``None`` (Default). If it's ``None`` a ``diskcache.Cache`` istance is created with the parameter ``directory = pycf3.DEFAULT_CACHE_DIR``. More information: http://www.grantjenks.com/docs/diskcache cache_expire : ``float`` or None (default=``None``) Seconds until item expires (default ``None``, no expiry) More information: http://www.grantjenks.com/docs/diskcache """ session: requests.Session = attr.ib(factory=RetrySession, repr=False) cache: t.Union[dcache.Cache, dcache.FanoutCache] = attr.ib() cache_expire: float = attr.ib(default=None, repr=False) @cache.default def _cache_default(self): return dcache.Cache(directory=DEFAULT_CACHE_DIR) def _determine_coordinate_system(self, ra, dec, glon, glat, sgl, sgb): # first we put all the parameters in a single dictionary params = { "ra": ra, "dec": dec, "glon": glon, "glat": glat, "sgl": sgl, "sgb": sgb, } # next we remove all keys with the default value `None` params = {k: v for k, v in params.items() if v is not None} # if we have 0 values no coordinate was given if len(params) == 0: raise ValueError( "No coordinate was provided. " "Please provide (ra, dec)', '(glon, glat)' or '(sgl, sgb)'.") # if we only have one we need to check wich one and inform about # the mising companinon if len(params) == 1: pname = list(params.keys())[0] coordinate_system, companion_dict = ((ALPHA_TO_COORDINATE[pname], DELTA) if pname in ALPHA_TO_COORDINATE else (DELTA_TO_COORDINATE[pname], ALPHA)) companion = companion_dict[coordinate_system] raise ValueError(f"No {companion} provided") # if we have more than two parameter we have mixed coordinate system if len(params) > 2: raise MixedCoordinateSystemError(", ".join(params)) # now we need to detemine the coordinate system coordinate_system_candidates = { ALPHA_DELTA_TO_COORDINATE[p] for p in params.keys() } # if we have more than 1 candidate we mix coordinates again if len(coordinate_system_candidates) > 1: raise MixedCoordinateSystemError(", ".join(params)) # we have one coordinate system and we need to dermermine which # is alpha and wich is delta coordinate_system = coordinate_system_candidates.pop() alpha_coordinate_name = ALPHA[coordinate_system] delta_coordinate_name = DELTA[coordinate_system] alpha = params[alpha_coordinate_name] delta = params[delta_coordinate_name] return coordinate_system, alpha, delta def _search( self, coordinate_system, alpha, delta, distance, velocity, **get_kwargs, ): # The validations if coordinate_system not in CoordinateSystem: raise TypeError("coordinate_system must be a member of " "pycf3.core.CoordinateSystem enum") if not isinstance(alpha, (int, float)): raise TypeError(f"{ALPHA[coordinate_system]} must be int or float") if not isinstance(delta, (int, float)): raise TypeError(f"{DELTA[coordinate_system]} must be int or float") elif not (-90 <= delta <= 90): raise ValueError( f"{DELTA[coordinate_system]} must be >= -90 and <= 90") if (distance, velocity) == (None, None): raise ValueError( "You must provide the distance or the velocity value") elif distance is not None and velocity is not None: raise ValueError( "You cant provide velocity and distance at the same time") if distance is not None: if not isinstance(distance, (int, float)): raise TypeError("'distance' must be int or float") elif not (0 < distance <= self.MAX_DISTANCE): raise ValueError( f"'distance' must be > 0 and <= {self.MAX_DISTANCE}") parameter, value = Parameter.distance, distance elif velocity is not None: if not isinstance(velocity, (int, float)): raise TypeError("'velocity' must be int or float") elif not (0 < velocity <= self.MAX_VELOCITY): raise ValueError( f"'velocity' must be > 0 and <= {self.MAX_VELOCITY}") parameter, value = Parameter.velocity, velocity payload = { "coordinate": [float(alpha), float(delta)], "system": coordinate_system.value, "parameter": parameter.value, "value": float(value), } # start the cache orchestration base = ( self.CALCULATOR, coordinate_system.value, ) key = dcache.core.args_to_key(base=base, args=(self.URL, ), kwargs=payload, typed=False) with self.cache as cache: cache.expire() response = cache.get(key, default=dcache.core.ENOVAL, retry=True) if response == dcache.core.ENOVAL: response = self.session.get(self.URL, json=payload, **get_kwargs) response.raise_for_status() cache.set( key, response, expire=self.cache_expire, tag="@".join(key[:2]), retry=True, ) result = Result( calculator=self.CALCULATOR, url=self.URL, coordinate=coordinate_system, calculated_by=parameter, alpha=alpha, delta=delta, distance=distance, velocity=velocity, response_=response, ) return result # ========================================================================= # INTERNALS # ========================================================================= def __repr__(self): """x.__repr__() <==> repr(x).""" cls = type(self).__name__ calculator = f"calculator='{self.CALCULATOR}'" cachedir = f"cache_dir='{getattr(self.cache, 'directory', '')}'" expire = f"cache_expire={self.cache_expire}" return f"{cls}({calculator}, {cachedir}, {expire})" # ========================================================================= # API # ========================================================================= def calculate_distance( self, velocity, *, ra=None, dec=None, glon=None, glat=None, sgl=None, sgb=None, **get_kwargs, ): """Calculate a distance based on the given velocity and location. The mandatory parameters are ``velocity`` and a position expressed in two components depending on the chosen coordinate system: - ``ra`` and ``dec`` for an equatorial system. - ``glon`` and ``glat`` for a galactic system. - ``sgl`` and ``sgb`` for a supergalactic system. Coordinates cannot be mixed between systems, and must be expressed in J2000 as 360° decimal. The returned distance(s) are expressed in Mpc, and potentially can be more than one value. Parameters ---------- velocity : ``int`` or ``float`` Model velocity in km/s. ra : ``int`` or ``float`` (optional) Right ascension. If you provide ``ra`` you need to provide also ``dec``. dec : ``int`` or ``float`` (optional) Declination. ``dec`` must be >= -90 and <= 90. If you provide ``dec`` you need to provide also ``ra``. glon : ``int`` or ``float`` (optional) Galactic longitude. If you provide ``glon`` you need to provide also ``glat``. glat: ``int`` or ``float`` (optional) Galactic latitude. ``glat`` must be >= -90 and <= 90. If you provide ``glat`` you need to provide also ``glon``. sgl : ``int`` or ``float`` (optional) Super-galactic longitude. If you provide ``sgl`` you need to provide also ``sgb``. sgb: ``int`` or ``float`` (optional) Super-galactic latitude. ``sgb`` must be >= -90 and <= 90. If you provide ``sgb`` you need to provide also ``sgl``. get_kwargs: Optional arguments that ``request.get`` takes. Returns ------- pycf3.Result : Result object that automatically parses the entire model returned by the remote calculator. """ coordinate_system, alpha, delta = self._determine_coordinate_system( ra=ra, dec=dec, glon=glon, glat=glat, sgl=sgl, sgb=sgb) response = self._search( coordinate_system=coordinate_system, alpha=alpha, delta=delta, distance=None, velocity=velocity, **get_kwargs, ) return response def calculate_velocity( self, distance, *, ra=None, dec=None, glon=None, glat=None, sgl=None, sgb=None, **get_kwargs, ): """Calculate a velocity based on the given distance and location. The mandatory parameters are ``distance`` and a position expressed in two components depending on the chosen coordinate system: - ``ra`` and ``dec`` for an equatorial system. - ``glon`` and ``glat`` for a galactic system. - ``sgl`` and ``sgb`` for a supergalactic system. Coordinates cannot be mixed between systems, and must be expressed in J2000 as 360° decimal. The returned velocity are expressed in Km/s. Parameters ---------- distance : ``int`` or ``float`` Distance(s) in Mpc. ra : ``int`` or ``float`` (optional) Right ascension. If you provide ``ra`` you need to provide also ``dec``. dec : ``int`` or ``float`` (optional) Declination. ``dec`` must be >= -90 and <= 90. If you provide ``dec`` you need to provide also ``ra``. glon : ``int`` or ``float`` (optional) Galactic longitude. If you provide ``glon`` you need to provide also ``glat``. glat: ``int`` or ``float`` (optional) Galactic latitude. ``glat`` must be >= -90 and <= 90. If you provide ``glat`` you need to provide also ``glon``. sgl : ``int`` or ``float`` (optional) Super-galactic longitude. If you provide ``sgl`` you need to provide also ``sgb``. sgb: ``int`` or ``float`` (optional) Super-galactic latitude. ``sgb`` must be >= -90 and <= 90. If you provide ``sgb`` you need to provide also ``sgl``. get_kwargs: Optional arguments that ``request.get`` takes. Returns ------- pycf3.Result : Result object that automatically parses the entire model returned by the remote calculator. """ coordinate_system, alpha, delta = self._determine_coordinate_system( ra=ra, dec=dec, glon=glon, glat=glat, sgl=sgl, sgb=sgb) response = self._search( coordinate_system=coordinate_system, alpha=alpha, delta=delta, distance=distance, velocity=None, **get_kwargs, ) return response # ========================================================================= # OLD API # ========================================================================= @deprecated( category=CFDeprecationWarning, action="once", reason="Use `calculate_velocity` or `calculate_distance` instead", version="2020.12", ) def equatorial_search( self, ra=187.78917, dec=13.33386, distance=None, velocity=None, **get_kwargs, ): """Search by equatorial coordinates. The coordinates are expressed in J2000 as 360° decimal. .. deprecated:: 2020.12 "Use `calculate_velocity` or `calculate_distance` instead" Parameters ---------- ra : ``int`` or ``float`` (default: ``187.78917``) Right ascension. dec : ``int`` or ``float`` (default: ``13.33386``) Declination. dec must be >= -90 and <= 90 distance : ``int``, ``float`` or ``None`` (default: ``None``) Distance(s) in Mpc. velocity : ``int``, ``float`` or ``None`` (default: ``None``) Velocity in km/s. The returned distance potentially can be more than ine value. get_kwargs: Optional arguments that ``request.get`` takes. Returns ------- pycf3.Result : Result object that automatically parses the entire model returned by the remote calculator. """ response = self._search( CoordinateSystem.equatorial, alpha=ra, delta=dec, distance=distance, velocity=velocity, **get_kwargs, ) return response @deprecated( category=CFDeprecationWarning, action="once", reason="Use `calculate_velocity` or `calculate_distance` instead", version="2020.12", ) def galactic_search( self, glon=282.96547, glat=75.41360, distance=None, velocity=None, **get_kwargs, ): """Search by galactic coordinates. The coordinates are expressed in J2000 as 360° decimal. .. deprecated:: 2020.12 "Use `calculate_velocity` or `calculate_distance` instead" Parameters ---------- glon : ``int`` or ``float`` (default: ``282.96547``) Galactic longitude. glat: ``int`` or ``float`` (default: ``75.41360``) Galactic latitude. dec must be >= -90 and <= 90 distance : ``int``, ``float`` or ``None`` (default: ``None``) Distance(s) in Mpc. velocity : ``int``, ``float`` or ``None`` (default: ``None``) Velocity in km/s. The returned distance potentially can be more than ine value. get_kwargs: Optional arguments that ``request.get`` takes. Returns ------- pycf3.Result : Result object that automatically parses the entire model returned by the remote calculator. """ response = self._search( CoordinateSystem.galactic, alpha=glon, delta=glat, distance=distance, velocity=velocity, **get_kwargs, ) return response @deprecated( category=CFDeprecationWarning, action="once", reason="Use `calculate_velocity` or `calculate_distance` instead", version="2020.12", ) def supergalactic_search( self, sgl=102.0, sgb=-2.0, distance=None, velocity=None, **get_kwargs, ): """Search super-galactic coordinates. The coordinates are expressed in J2000 as 360° decimal. .. deprecated:: 2020.12 "Use `calculate_velocity` or `calculate_distance` instead" Parameters ---------- sgl : ``int`` or ``float`` (default: ``102``) Super-galactic longitude. sgb: ``int`` or ``float`` (default: ``-2``) Super-galactic latitude. dec must be >= -90 and <= 90 distance : ``int``, ``float`` or ``None`` (default: ``None``) Distance(s) in Mpc. velocity : ``int``, ``float`` or ``None`` (default: ``None``) Velocity in km/s. The returned distance potentially can be more than ine value. get_kwargs: Optional arguments that ``request.get`` takes. Returns ------- pycf3.Result : Result object that automatically parses the entire model returned by the remote calculator. """ response = self._search( CoordinateSystem.supergalactic, alpha=sgl, delta=sgb, distance=distance, velocity=velocity, **get_kwargs, ) return response
class GradesBaseSender(metaclass=DocInheritMeta(style='google_with_merge', include_special_methods=True)): ''' This class helps to send student grades from nbgrader database. Classes that inherit from this class must implement the send_grades() method. Args: course_id (str): Course id or name used in nbgrader assignment_name (str): Assignment name that needs to be processed and from which the grades are retrieved Attributes: helper (NBGraderHelper): . course (Course): . all_lineitems (list): . headers (dict): . course_id (str): . assignment_name (str): . ''' def __init__(self, course_id: str, assignment_name: str): ''' Check if required enviroments is set. Args: course_id (str): . assignment_name (str): . Raises: EnvironmentError: If one of environment variable is not set. ''' envs = ('LTI13_PRIVATE_KEY', 'LTI13_TOKEN_URL', 'LTI13_CLIENT_ID') if any(os.environ.get(env) is None for env in envs): raise EnvironmentError(', '.join(envs) + ' should be set.') self.course_id = course_id self.assignment_name = assignment_name self.helper = NBGraderHelper() self.course = self.helper.get_course(self.course_id) self.all_lineitems = [] self.headers = {} async def send_grades(self): raise NotImplementedError @property def grader_name(self) -> str: return f'grader-{self.course_id}' @property def gradebook_dir(self) -> str: return f'/home/{self.grader_name}/{self.course_id}' def _retrieve_grades_from_db(self) -> t.Tuple[int, t.List[dict]]: '''Gets grades from the database''' out: t.List[dict] = [] max_score = 0 # Create the connection to the gradebook database with self.helper.get_db(self.course_id) as gb: try: # retrieve the assignment record assignment_row = gb.find_assignment(self.assignment_name) max_score = assignment_row.max_score submissions = gb.assignment_submissions(self.assignment_name) logger.info( f'Found {len(submissions)} submissions for assignment: {self.assignment_name}' ) except MissingEntry as exc: logger.error(f'Assignment not found in database: {exc}') raise GradesSenderMissingInfoError(self.course_id) from exc for submission in submissions: # retrieve the student to use the lms id student = gb.find_student(submission.student_id) out.append({ 'score': submission.score, 'lms_user_id': student.lms_user_id }) logger.info(f'Grades found: {out}') logger.info(f'Maximum score for this assignment {max_score}') return max_score, out
class BaseAPIClient(metaclass=DocInheritMeta(style='google_with_merge', include_special_methods=True)): '''Moodle webservice interface. Based on gist of https://gist.github.com/kaqfa Initialize Moodle API Client. In order to make REST API requests a client need to obtain both Moodle URL and Moodle API token. Read more about Moodle REST API in docs/Setup Moodle.md You can pass url or token directly as a keyword-only string values or pass url_env_name or key_env_name keyword-only arguments to obtain url or token from environment if they differ from defaults. You can also provide client with a custom endpoint if it's neccesary in your case. With classic setup it defaults to /webservice/rest/server.php Args: url (:obj:`str`, optional): Moodle domain name. Defaults to None. key (:obj:`str`, optional): Moodle web service token. Defaults to None. endpoint (:obj:`str`, optional): Custom endpoint. Defaults to None. url_env_name (:obj:`str`, optional): Custom environment variable name. Defaults to None. key_env_name (:obj:`str`, optional): Custom environment variable name. Defaults to None. ''' DEFAULT_ENDPOINT: str = '/webservice/rest/server.php' URL_ENV_NAME: str = 'MOODLE_BASE_URL' KEY_ENV_NAME: str = 'MOODLE_API_TOKEN' key: str url: str endpoint: str def __init__( self, url: t.Optional[str] = None, key: t.Optional[str] = None, endpoint: t.Optional[str] = None, url_env_name: t.Optional[str] = None, key_env_name: t.Optional[str] = None, ): # If url or key is not provided, # Check that appropriate enviroment variable is set # if env_name is provided prefer it over default one. # raise EnvironmentError if env is not set. for attr, env_name in ( (url, 'url_env_name'), (key, 'key_env_name'), ): if attr is None: attr_name = locals()[env_name] or getattr( self, env_name.upper()) attr = os.getenv(attr_name) if not attr: raise EnvironmentError(f'{attr_name} must be set.') setattr(self, env_name[:3], attr) self.endpoint: str = endpoint or self.DEFAULT_ENDPOINT def rest_api_parameters( self, in_args: t.Union[t.Sequence, t.Mapping], prefix: str = '', out_dict: t.Optional[dict] = None, ) -> JsonType: ''' Transform dictionary/array structure to a flat dictionary, with key names defining the structure. Examples: Convert parameters:: rest_api_parameters({'courses':[{'id': 1,'name': 'course1'}]}) >>> {'courses[0][id]': 1, 'courses[0][name]':'course1'} ''' if out_dict is None: out_dict = {} if not isinstance(in_args, (list, dict)): out_dict[prefix] = in_args return out_dict prefix += '{0}' if not prefix else '[{0}]' sequence = enumerate(in_args) if isinstance(in_args, list) else in_args.items() for idx, item in sequence: self.rest_api_parameters(item, prefix.format(idx), out_dict) return out_dict def call(self, api_func: str, **kwargs: t.Any) -> FluidResponse: ''' Calls Moodle API <api_func> with keyword arguments. Args: api_func (:obj:`str`): name of function in Moodle web client. **kwargs: any parameters to send with the request. Examples: Calling *core_course_update_courses* function:: call_mdl_function( 'core_course_update_courses', courses = [{'id': 1, 'fullname': 'My favorite course'}] ) Returns: FluidResponse: Convinient wrapper for received data. Raises: requests.HTTPError: Network or server error MoodleHTTPException: non successful HTTP status. MoodleAPIException: Syntax error in API call. ''' parameters = self.rest_api_parameters(kwargs) logger.debug(f''' Calling {api_func!r} {dump_json(parameters)[1:-1] or 'Without parameters'} ''') parameters.update({ 'wstoken': self.key, 'moodlewsrestformat': 'json', 'wsfunction': api_func, }) resp: Response = requests.post(self.url + self.endpoint, parameters) resp.raise_for_status() # can raise MoodleHTTPException and MoodleAPIException return FluidResponse(resp, api_func)
from custom_inherit import DocInheritMeta try: from inspect import signature except ImportError: from inspect import getargspec as signature def style(x, y): return "valid" """ With ABC""" @add_metaclass(DocInheritMeta(style=style, abstract_base_class=True)) class Parent(object): def method(self, x, y=None): """""" pass @classmethod def clsmthd(cls): """""" pass @staticmethod def static(): """""" pass
class OrderBook(metaclass=DocInheritMeta(style="numpy", abstract_base_class=True)): """ A abstract class defining a order book interface. The main functions are the functions for the messages. Sending a limit order, market order etc. The order book handles the matching and storing of limit orders. Important notes is the speed of adding a limit order, cancelling a limit order or updating a limit order. Attributes ---------- orders : dict All current orders in the order book, key is the order id order_id : int The internal order id set by the order book. Is incremented for each order sent to the order book. """ def __init__(self, price_level_type='cydeque', price_levels_type='cylist'): self.price_levels = get_price_levels(price_levels_type, price_level_type) self.orders = {} self.order_id = 0 @abc.abstractmethod def limit(self, price: int, side: int, size: float, trader_id: int, time: str) -> (list, tuple): """ Handles a limit order sent to the order book. Matches the limit order if possible, otherwise puts it in the order book. Parameters ---------- price: float Price of the order. side: int BUY or SELL, see :py:mod:´OrderBookRL.order_book.constants´ size: float Size of the order. trader_id: int Id of the trader sending the order time: str Returns ------- trades, order_in_book trades: list If trades have occurred due the the message, all the trades are returned. Otherwise empty list order_in_book: tuple If a limit order has been placed which size has not fully been matched, the remaining order in book is returned. Otherwise -1. """ @abc.abstractmethod def market_order(self, size: float, side: int, trader_id: int, time: str) -> list: """ Handles a market order sent to the order book. Matches the market order if possible. Parameters ---------- side: int BUY or SELL, see :py:mod:´OrderBookRL.order_book.constants´ size: float Size of the order. trader_id: int Id of the trader sending the order time: str Returns ------- trades: list If trades have occurred due the the message, all the trades are returned. Otherwise empty list """ @abc.abstractmethod def market_order_funds(self, funds: float, side: int, trader_id: int, time: str) -> list: """ Handles a market order sent to the order book. Matches the market order if possible. Parameters ---------- funds: float Size of the order. side: int BUY or SELL, see :py:mod:´OrderBookRL.order_book.constants´ trader_id: int Id of the trader sending the order time: str Returns ------- trades: list If trades have occurred due the the message, all the trades are returned. Otherwise empty list """ @abc.abstractmethod def cancel(self, order_id: int): """ Attempts to cancel a order by its order id Parameters ---------- order_id: int The order id of the order to be cancelled """ @abc.abstractmethod def update(self, order_id: int, size: float): """
class Actor(metaclass=DocInheritMeta(style="numpy")): """Component responsible both for exploration and converting model predictions to actions. Returns 2 actions at a time: the first to be consumed by the model, the second to be consumed by the environment. Attributes ------- explore_and_convert_fns : dict of {str : dict of {str : function}} Functions corresponding to every combination of action space and model type. Action space is the first key, model type is the second key. The functions add exploration and convert the chosen action to the correct format. action_space : gym.Space The action space object of the associated environment. action_type : str The class name of the action space as a lowercased string. prediction_type : {'value', 'policy'} Indicates whether the model outputs action-values or a policy vector for the state. """ def __init__(self): # a dictionary of functions makes choosing the method of conversion easier self.explore_and_convert_fns = { 'discrete': { 'policy': self._discrete_policy, 'value': self._discrete_value }, 'multidiscrete': { 'policy': self._multidiscrete_policy, 'value': self._multidiscrete_value }, 'box': { 'policy': self._box_policy, 'value': self._box_value }, 'multibinary': { 'policy': self._multibinary_policy, 'value': self._multibinary_value } } self.action_space = None self.action_type = None self.prediction_type = None def configure(self, agent): """Associate actor with agent, picking up information about its action space and model.""" self.action_space = agent.env.action_space self.action_type = self.action_space.__class__.__name__.lower() self.prediction_type = agent.model.prediction_type def convert_pred(self, pred): """Look up explore_and_convert function and apply it to model prediction.""" return self.explore_and_convert_fns[self.action_type][ self.prediction_type](pred) def warming_action(self): """Choose a random action without involving the model. Returns ------- action_for_model : numpy.ndarray The action that will be consumed by the model for learning. action_for_env : numpy.ndarray or int or float The action that will be consumed by the environment to step. """ action_for_env = self.action_space.sample() if self.action_type == 'discrete': action_for_model = np.eye(self.action_space.n)[action_for_env] elif self.action_type == 'multidiscrete': action_for_model = [ np.eye(n)[action_for_env[i]] for i, n in enumerate(self.action_space.nvec) ] elif self.action_type == 'box': action_for_model = action_for_env elif self.action_type == 'multibinary': action_for_model = [np.eye(2)[env_a] for env_a in action_for_env] return action_for_model, action_for_env def step(self, new_episode): """Move one timestep ahead. Main purpose is to advance value schedules. Parameters ---------- new_episode : bool A flag indicating whether this step is one that resets the environment. """ pass def _discrete_policy(self, pred): """Exploration and conversion function for a Discrete action space + Policy model. Parameters ---------- pred : numpy.ndarray The prediction output by the model, to be converted. Raises ------ NotImplementedError. """ raise NotImplementedError def _discrete_value(self, pred): """Exploration and conversion function for a Discrete action space + Value model. Parameters ---------- pred : numpy.ndarray The prediction output by the model, to be converted. Raises ------ NotImplementedError. """ raise NotImplementedError def _multidiscrete_policy(self, pred): """Exploration and conversion function for a Multi-Discrete action space + Policy model. Parameters ---------- pred : numpy.ndarray The prediction output by the model, to be converted. Raises ------ NotImplementedError. """ raise NotImplementedError def _multidiscrete_value(self, pred): """Exploration and conversion function for a Multi-Discrete action space + Value model. Parameters ---------- pred : numpy.ndarray The prediction output by the model, to be converted. Raises ------ NotImplementedError. """ raise NotImplementedError def _box_policy(self, pred): """Exploration and conversion function for a Box action space + Policy model. Parameters ---------- pred : numpy.ndarray The prediction output by the model, to be converted. Raises ------ NotImplementedError. """ raise NotImplementedError def _box_value(self, pred): """Exploration and conversion function for a Box action space + Value model. Parameters ---------- pred : numpy.ndarray The prediction output by the model, to be converted. Raises ------ NotImplementedError. """ raise NotImplementedError def _multibinary_policy(self, pred): """Exploration and conversion function for a Multi-Binary action space + Policy model. Parameters ---------- pred : numpy.ndarray The prediction output by the model, to be converted. Raises ------ NotImplementedError. """ raise NotImplementedError def _multibinary_value(self, pred): """Exploration and conversion function for a Multi-Binary action space + Value model. Parameters ---------- pred : numpy.ndarray The prediction output by the model, to be converted. Raises ------ NotImplementedError. """ pass
""" Python 2 and python 3 differ in decorator for abstract property. in python 3 (gt 3.3) it is: @property @abstractproperty in python 2 @abstractproperty """ if PY2: # pylint: disable=no-else-return return abstractproperty(func) else: return property(abstractmethod(func)) @add_metaclass(DocInheritMeta(style="google", abstract_base_class=True)) class BaseObjectStore(object): """ An abstract class that defines the APIs for the methods of an object store for CIM objects including CIMClass, CIMInstance, and CIMQualifierDeclaration objects that constitute a WBEM server repository. This class provides the abstract methods for creating, accessing, and deleting, CIM objects of a single CIM object type in the repository. CIM objects in the object store are identified by a name which is part of the methods that access the CIM objects and must be unique within a single object store. Each object store conatins only a single CIM object type. """ @abstractmethod
class PriceLevel(metaclass=DocInheritMeta(style="numpy", abstract_base_class=True)): """ A price level containing orders. A price level contains orders with the same price. The total size of the level is the sum of all order sizes. Attributes ---------- size : float The total size of all orders in the price level. orders All the orders in the price level. """ def __init__(self): """ Initializes the size to 0. """ self.size = 0 def append(self, order: list): """ Adds an order to the end of the price level and adds the size. Parameters ---------- order: list The order to be added. """ self._add(order) self.size += order[O_SIZE] @abc.abstractmethod def _add(self, order: list): """ Adds the order to the end of the list Parameters ---------- order: list The order to be added. """ def delete(self, order: list): """ Deletes an order from the price level and removes the size. Parameters ---------- order: list The order to be added. """ self._remove(order) self.size -= order[O_SIZE] @abc.abstractmethod def _remove(self, order: list): """ Removes an order from the price level and removes the size. Parameters ---------- order: list The order to be removed. """ def update(self, order, diff: float): """ Updates an order from the price level with an added difference. Parameters ---------- diff order: list The order to be added. """ self.size += diff order[O_SIZE] += diff @abc.abstractmethod def get_first(self): """ Returns the first order of the price level Returns ------- order The first order of the price level. """ def delete_first(self, order: list): """ Deletes the first order from the price level and removes the size. Parameters ---------- order: list The order to be removed. """ self._remove_first() self.size -= order[O_SIZE] @abc.abstractmethod def _remove_first(self): """ Removes the first order from the price level. Parameters ---------- order: list The order to be removed. """ @abc.abstractmethod def is_not_empty(self) -> bool: """ Returns true if the price level is not empty Returns ------- bool True if successful, False otherwise. """ @abc.abstractmethod def get_last(self): """ Returns the last order of the price level Returns ------- order The last order of the price level. """ def delete_last(self, order: list): """ Deletes the last order from the price level and removes the size. Parameters ---------- order: list The order to be removed. """ self._remove_last() self.size -= order[O_SIZE] @abc.abstractmethod def _remove_last(self): """ Removes the last order from the price level. Parameters ---------- order: list The order to be removed. """ @abc.abstractmethod def is_empty(self): """ Returns true if the price level is empty
from types import MethodType, FunctionType try: from inspect import signature except ImportError: from inspect import getargspec as signature def style(x, y): return "valid" """ With ABC""" @six.add_metaclass(DocInheritMeta(style=style, abstract_base_class=True)) class Parent(object): def method(self, x, y=None): """""" pass @classmethod def clsmthd(cls): """""" pass @staticmethod def static(): """""" pass
class Agent(metaclass=DocInheritMeta(style="numpy")): """Composes an environment, data pipeline, model, and actor to perform reinforcement learning. The modular philosophy of the library means that this agent should be responsible for no more than calling its members to run timesteps, and to loop over timesteps to run episodes. The setup is: The environment provides a state, it's passed through the pipeline, the model makes a prediction for that state, the actor converts that prediction to an action, the environment consumes that action, the monitor keeps track of the important things, and sometimes the model learns from the replay buffer. Each of these components knows its Agent. This is how information is passed between modules. Parameters ---------- env : gym.Env the environment the agent is acting in. state_pipeline : list of functions a list of functions that the state is passed through sequentially. model : pavlov.Model a reinforcement learning model that guides the agent. actor : pavlov.Actor responsible for converting model predictions to actions. buffer_size : int limit for how many observations to hold in replay buffer. batch_size : int number of observations to pull from replay_buffer at fit time. warmup_length : int number of random timesteps to execute before beginning to learn and apply the model. Replay buffer will be populated. repeated_actions : int number of env timesteps to repeat a chosen action for. report_frequency : int interval for printing to stdout, in number of episodes. state_dtype : type numpy datatype for states to be stored in in replay buffer. Attributes ---------- env : gym.Env the environment the agent is acting in. env_state : np.array current state of environment. state_pipeline : list of functions a list of functions that the state is passed through sequentially. model : pavlov.Model a reinforcement learning model that guides the agent. actor : pavlov.Actor responsible for converting model predictions to actions. replay_buffer : pavlov.ReplayBuffer collection of historical observations. batch_size : int number of observations to pull from replay_buffer at fit time. warmup_length : int number of random timesteps to execute before beginning to learn and apply the model. Replay buffer will be populated. repeated_actions : int number of env timesteps to repeat a chosen action for. monitor : pavlov.Monitor keeps track of metrics and logs them. renders_by_episode : list of np.array environment timestep renderings by episode. """ # incompatible pairs of action space type and model type incompatibles = [('box', 'dqnmodel'), ('discrete', 'ddpgmodel'), ('multidiscrete', 'ddpgmodel'), ('multibinary', 'ddpgmodel')] def __init__(self, env, state_pipeline, model, actor, buffer_size, batch_size, warmup_length, repeated_actions=1, report_frequency=100, state_dtype=np.float32): # check if model and action space are compatible for space_type, model_type in self.incompatibles: if (env.action_space.__class__.__name__.lower() == space_type and model.__class__.__name__.lower() == model_type): raise util.exceptions.ActionModelMismatchError( space_type, model_type) self.session = tf.Session() K.set_session(self.session) self.env = env self.env_state = self.env.reset() self.batch_size = batch_size self.warmup_length = warmup_length self.repeated_actions = repeated_actions self.renders_by_episode = [[]] self.start_timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%s') self.state_pipeline = state_pipeline self.replay_buffer = ReplayBuffer(buffer_size, state_dtype) self.model = model self.actor = actor self.monitor = Monitor(report_frequency, '/var/log') self.state_pipeline.configure(self) self.replay_buffer.configure(self) self.model.configure(self, self.session) self.actor.configure(self) self.monitor.configure(self, self.session) self._warmup_replay_buffer() @property def episode(self): """Gives the current episode number.""" return len(self.renders_by_episode) def _warmup_replay_buffer(self): """Run replay buffer-populating timesteps before actually starting.""" for i in range(self.warmup_length): done = self.run_timestep(warming=True) if done: self.reset() def episode_to_mp4(self, episode_num, out_dir): """Generates mp4 of agent's timesteps for given episode number. Only works with environments that render images at each timestep. Parameters ---------- episode_num : int episode number that you want to take a video of (one-indexed). out_dir : str directory where you would like to place video file. filename is auto-generated. """ frames = self.renders_by_episode[episode - 1] shape = frames[0].shape if not ((len(shape) == 2) or (len(shape) == 3 and shape[2] == 3)): raise TypeError("Environment renderings are not images") size = frames[0].shape[:-1] fps = 20 fourcc = cv2.VideoWriter_fourcc(*'mp4v') vid_out = cv2.VideoWriter() filename = '{}/{}-episode{}.mp4'.format(self.start_timestamp, out_dir, episode) vid_out.open(filename, fourcc, fps, size, True) for frame in frames: vid_out.write(frame) vid_out.release() def reset(self): """Resets environment to initial state for new episode.""" self.env_state = self.env.reset() def run_timestep(self, warming=False, render=False): """Run one timestep in the environment. - Process current state with `state_pipeline`. - Generate prediction from `model`. - Convert prediction to action twice; one format for replay buffer, one for environment. - Apply action in environment, observe reward and next state. - Store experience in replay buffer. - Every `batch_size` timesteps, fit model to random batch from replay buffer. Parameters ---------- warming : bool indicate whether this is a step meant to simply populate the replay buffer. render : bool indicate whether to log an image of the environment for video generation. """ start_state = self.state_pipeline.transform(self.env_state) if warming: action_for_model, action_for_env = self.actor.warming_action() else: pred = self.model.predict(start_state) action_for_model, action_for_env = self.actor.convert_pred( pred) # includes exploration timestep_reward = 0. for i in range(self.repeated_actions): if render: if not warming: rendered_frame = self.env.render(mode='rgb_array') self.renders_by_episode[-1].append(rendered_frame) else: self.renders_by_episode[-1].append([]) self.env_state, frame_reward, done, info = self.env.step( action_for_env) timestep_reward += frame_reward if done: break if not warming: self.actor.step(done) self.monitor.step(timestep_reward) if done: next_state = np.zeros(self.replay_buffer['states'].shape[1:]) else: next_state = self.state_pipeline.transform(self.env_state) self.replay_buffer.add(start_state, action_for_model, timestep_reward, next_state, done) if not warming and self.replay_buffer.current_size >= self.batch_size: batch = self.replay_buffer.get_batch(self.batch_size) self.model.fit(**batch) return done def run_episode(self, render=False, do_logging=True): """Apply `run_timestep` until episode terminates. Parameters ---------- render : bool indicate whether to log an image of the environment for video generation. do_logging : bool indicate whether to print out metrics for episode. """ self.renders_by_episode.append([]) while True: done = self.run_timestep(render=render) if done: self.monitor.new_episode(do_logging) self.reset() break def run_indefinitely(self, render=False, log=True): """Apply run_episode in an infinite loop; terminated by KeyboardInterrupt. The keyboard interrupt will be handled by first finishing the running episode, then terminating the loop and thus function. Parameters ---------- render : bool indicate whether to log an image of the environment for video generation. do_logging : bool indicate whether to print out metrics for episode. """ with util.interrupt.AtomicLoop() as loop: while loop.run: self.run_episode(render=render, do_logging=log)