A perturbation that can be used as a step in a pipeline

Expand source code
"""A perturbation that can be used as a step in a pipeline
"""

from typing import Callable

import ray


def _check_vfunc(vfunc):
    if not (hasattr(vfunc, "fit") or callable(vfunc)):
        raise TypeError("vfunc must be an object with a fit method or a callable")


def _method_or_self(vfunc, method: str):
    """`vfunc.<method>` (e.g. an estimator's fit) if it has one, else `vfunc`
    itself (a plain function)."""
    return getattr(vfunc, method, vfunc)


class Vfunc:
    """Vfunc is basically a function along with a name attribute.
    It may support a "fit" function, but may also just have a "transform" function.
    If none of these is supported, it need only be a function
    """

    def __init__(self, name: str = "", vfunc=lambda x: x):
        _check_vfunc(vfunc)
        self.name = name
        self.vfunc = vfunc

    def fit(self, *args, **kwargs):
        """Calls the vfunc's fit method, or the vfunc itself if it has none"""
        return _method_or_self(self.vfunc, "fit")(*args, **kwargs)

    def transform(self, *args, **kwargs):
        """Calls the vfunc's transform method, or the vfunc itself if it has none"""
        return _method_or_self(self.vfunc, "transform")(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        """Same as fit"""
        return self.fit(*args, **kwargs)


@ray.remote
def _remote_fun(vfunc, *args, **kwargs):
    return vfunc(*args, **kwargs)


class AsyncVfunc:
    """An asynchronous version of the Vfunc class."""

    def __init__(self, name: str = "", vfunc=lambda x: x):
        self.name = name
        if isinstance(vfunc, Vfunc):
            vfunc = vfunc.vfunc
        _check_vfunc(vfunc)
        self.vfunc = vfunc

    def fit(self, *args, **kwargs):
        """Like Vfunc.fit, run as a Ray task; returns a Ray reference"""
        return _remote_fun.remote(_method_or_self(self.vfunc, "fit"), *args, **kwargs)

    def transform(self, *args, **kwargs):
        """Like Vfunc.transform, run as a Ray task; returns a Ray reference"""
        return _remote_fun.remote(
            _method_or_self(self.vfunc, "transform"), *args, **kwargs
        )

    def __call__(self, *args, **kwargs):
        """Same as fit"""
        return self.fit(*args, **kwargs)


class VfuncPromise:
    """A Vfunc promise used for lazy evaluation."""

    def __init__(self, vfunc: Callable, *args):
        self.vfunc = vfunc
        self.args = args
        self.called = False
        self.value = None

    def __call__(self):
        """Evaluates the promise the first time; returns the stored value after"""
        if self.called:
            return self.value
        tmp_args = []
        for arg in self.args:
            while isinstance(arg, VfuncPromise):
                arg = arg()
            tmp_args.append(arg)
        while isinstance(self.vfunc, VfuncPromise):
            self.vfunc = self.vfunc()
        # an upstream lazy async Vset leaves Ray references; a remote call
        # resolves them itself, but a plain function must get the values
        if not isinstance(getattr(self.vfunc, "__self__", None), AsyncVfunc):
            tmp_args = [
                ray.get(a) if isinstance(a, ray.ObjectRef) else a for a in tmp_args
            ]
        self.value = self.vfunc(*tmp_args)
        self.called = True
        return self.value

    def _get_value(self):
        if isinstance(self(), ray.ObjectRef):
            self.value = ray.get(self.value)
        return self.value

    def transform(self, *args):
        """This function transforms its input in some way"""
        return self._get_value().transform(*args)

    def predict(self, *args):
        """This function calls predict on its inputs"""
        return self._get_value().predict(*args)

    def predict_proba(self, *args):
        """This function calls predict_proba on its inputs"""
        return self._get_value().predict_proba(*args)

    def __repr__(self):
        if self.called:
            return f"Fulfilled VfuncPromise({self.value})"
        return f"Unfulfilled VfuncPromise(func={self.vfunc}, args={self.args})"

Classes

class AsyncVfunc (name: str = '', vfunc=<function AsyncVfunc.<lambda>>)

An asynchronous version of the Vfunc class.

Expand source code
class AsyncVfunc:
    """An asynchronous version of the Vfunc class."""

    def __init__(self, name: str = "", vfunc=lambda x: x):
        self.name = name
        if isinstance(vfunc, Vfunc):
            vfunc = vfunc.vfunc
        _check_vfunc(vfunc)
        self.vfunc = vfunc

    def fit(self, *args, **kwargs):
        """Like Vfunc.fit, run as a Ray task; returns a Ray reference"""
        return _remote_fun.remote(_method_or_self(self.vfunc, "fit"), *args, **kwargs)

    def transform(self, *args, **kwargs):
        """Like Vfunc.transform, run as a Ray task; returns a Ray reference"""
        return _remote_fun.remote(
            _method_or_self(self.vfunc, "transform"), *args, **kwargs
        )

    def __call__(self, *args, **kwargs):
        """Same as fit"""
        return self.fit(*args, **kwargs)

Methods

def fit(self, *args, **kwargs)

Like Vfunc.fit, run as a Ray task; returns a Ray reference

Expand source code
def fit(self, *args, **kwargs):
    """Like Vfunc.fit, run as a Ray task; returns a Ray reference"""
    return _remote_fun.remote(_method_or_self(self.vfunc, "fit"), *args, **kwargs)
def transform(self, *args, **kwargs)

Like Vfunc.transform, run as a Ray task; returns a Ray reference

Expand source code
def transform(self, *args, **kwargs):
    """Like Vfunc.transform, run as a Ray task; returns a Ray reference"""
    return _remote_fun.remote(
        _method_or_self(self.vfunc, "transform"), *args, **kwargs
    )
class Vfunc (name: str = '', vfunc=<function Vfunc.<lambda>>)

Vfunc is basically a function along with a name attribute. It may support a "fit" function, but may also just have a "transform" function. If none of these is supported, it need only be a function

Expand source code
class Vfunc:
    """Vfunc is basically a function along with a name attribute.
    It may support a "fit" function, but may also just have a "transform" function.
    If none of these is supported, it need only be a function
    """

    def __init__(self, name: str = "", vfunc=lambda x: x):
        _check_vfunc(vfunc)
        self.name = name
        self.vfunc = vfunc

    def fit(self, *args, **kwargs):
        """Calls the vfunc's fit method, or the vfunc itself if it has none"""
        return _method_or_self(self.vfunc, "fit")(*args, **kwargs)

    def transform(self, *args, **kwargs):
        """Calls the vfunc's transform method, or the vfunc itself if it has none"""
        return _method_or_self(self.vfunc, "transform")(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        """Same as fit"""
        return self.fit(*args, **kwargs)

Methods

def fit(self, *args, **kwargs)

Calls the vfunc's fit method, or the vfunc itself if it has none

Expand source code
def fit(self, *args, **kwargs):
    """Calls the vfunc's fit method, or the vfunc itself if it has none"""
    return _method_or_self(self.vfunc, "fit")(*args, **kwargs)
def transform(self, *args, **kwargs)

Calls the vfunc's transform method, or the vfunc itself if it has none

Expand source code
def transform(self, *args, **kwargs):
    """Calls the vfunc's transform method, or the vfunc itself if it has none"""
    return _method_or_self(self.vfunc, "transform")(*args, **kwargs)
class VfuncPromise (vfunc: Callable, *args)

A Vfunc promise used for lazy evaluation.

Expand source code
class VfuncPromise:
    """A Vfunc promise used for lazy evaluation."""

    def __init__(self, vfunc: Callable, *args):
        self.vfunc = vfunc
        self.args = args
        self.called = False
        self.value = None

    def __call__(self):
        """Evaluates the promise the first time; returns the stored value after"""
        if self.called:
            return self.value
        tmp_args = []
        for arg in self.args:
            while isinstance(arg, VfuncPromise):
                arg = arg()
            tmp_args.append(arg)
        while isinstance(self.vfunc, VfuncPromise):
            self.vfunc = self.vfunc()
        # an upstream lazy async Vset leaves Ray references; a remote call
        # resolves them itself, but a plain function must get the values
        if not isinstance(getattr(self.vfunc, "__self__", None), AsyncVfunc):
            tmp_args = [
                ray.get(a) if isinstance(a, ray.ObjectRef) else a for a in tmp_args
            ]
        self.value = self.vfunc(*tmp_args)
        self.called = True
        return self.value

    def _get_value(self):
        if isinstance(self(), ray.ObjectRef):
            self.value = ray.get(self.value)
        return self.value

    def transform(self, *args):
        """This function transforms its input in some way"""
        return self._get_value().transform(*args)

    def predict(self, *args):
        """This function calls predict on its inputs"""
        return self._get_value().predict(*args)

    def predict_proba(self, *args):
        """This function calls predict_proba on its inputs"""
        return self._get_value().predict_proba(*args)

    def __repr__(self):
        if self.called:
            return f"Fulfilled VfuncPromise({self.value})"
        return f"Unfulfilled VfuncPromise(func={self.vfunc}, args={self.args})"

Methods

def predict(self, *args)

This function calls predict on its inputs

Expand source code
def predict(self, *args):
    """This function calls predict on its inputs"""
    return self._get_value().predict(*args)
def predict_proba(self, *args)

This function calls predict_proba on its inputs

Expand source code
def predict_proba(self, *args):
    """This function calls predict_proba on its inputs"""
    return self._get_value().predict_proba(*args)
def transform(self, *args)

This function transforms its input in some way

Expand source code
def transform(self, *args):
    """This function transforms its input in some way"""
    return self._get_value().transform(*args)