예제 #1
0
def create_class_Tolerant_Cartesian_2D_Vector(name, component_names, *, brackets='<>', sep=', ', cnull=0, cunit=1, functions=None, abs_tol=1e-12, rel_tol=1e-9):
    """
    Function that creates a tolerant cartesian vector class with 2 dimensions
    The number of dimensions are determined by the number of component names
    """

    hf.verify_class_name(name)
    if functions is None:
        functions = { }
    C2DV = \
        create_class_Cartesian_2D_Vector(
            name = 'C2DV_' + name,
            component_names = component_names,
            brackets = brackets,
            sep = sep,
            cnull = cnull,
            cunit = cunit,
            functions = functions
        )
    TC2DV = \
        make_Cartesian_Vector_Tolerant(
            cartesian_vector_class = C2DV,
            name = name,
            functions = functions,
            abs_tol = abs_tol,
            rel_tol = rel_tol
        )


    @hf.ensure_other_is_vector
    def are_parallel(self, other):
        """Check if two vectors are parallel (within a calculated tolerance)"""

        try:
            vsn = self.normalize()
        except ZeroDivisionError:
            vsn = None
        try:
            von = other.normalize()
        except ZeroDivisionError:
            von = None
        if (vsn is None) or (von is None):
            parallel = self._true
        else:
            perp_dot = vsn.perp_dot(von)
            parallel = self._equal_cnull(perp_dot)
            parallel = self.component_or(parallel, self.is_zero_vector())
            parallel = self.component_or(parallel, other.is_zero_vector())

        return parallel


    TC2DV.are_parallel = are_parallel

    return TC2DV
def create_class_Tolerant_Cartesian_Vector(name,
                                           component_names,
                                           *,
                                           brackets='<>',
                                           sep=', ',
                                           cnull=0,
                                           cunit=1,
                                           functions=None,
                                           abs_tol=1e-12,
                                           rel_tol=1e-9):
    """
    Function that creates a tolerant cartesian vector class
    The number of dimensions are determined by the number of component names
    """

    hf.verify_class_name(name)
    if functions is None:
        functions = {}
    CV = \
        create_class_Cartesian_Vector(
            name = 'CV_' + name,
            component_names = component_names,
            brackets = brackets,
            sep = sep,
            cnull = cnull,
            cunit = cunit,
            functions = functions
        )
    TCV = \
        make_Cartesian_Vector_Tolerant(
            cartesian_vector_class = CV,
            name = name,
            functions = functions,
            abs_tol = abs_tol,
            rel_tol = rel_tol
        )

    return TCV
예제 #3
0
def create_class_Simple_Vector(name,
                               component_names,
                               *,
                               brackets='<>',
                               sep=', '):
    """
    Function that creates a simple vector class
    The number of dimensions are determined by the number of component names
    """

    hf.verify_class_name(name)
    FV = \
        create_class_Fundamental_Vector(
            name = 'FV_' + name,
            component_names = component_names,
            brackets = brackets,
            sep = sep
        )

    def init_Simple_Vector(cls):
        """Initialize class"""

        hf.setup_vector_class(cls=cls, name=name, functions=None)
        hf.make_dunder_methods(cls, [(1, '', 'abs', operator.abs),
                                     (1, '', 'neg', operator.neg),
                                     (1, '', 'pos', operator.pos),
                                     (2, '', 'add', operator.add),
                                     (2, '', 'sub', operator.sub),
                                     (2, '', 'mul', operator.mul),
                                     (2, '', 'pow', operator.pow),
                                     (2, '', 'truediv', operator.truediv),
                                     (2, '', 'floordiv', operator.floordiv),
                                     (2, '', 'mod', operator.mod),
                                     (2, 'r', 'add', operator.add),
                                     (2, 'r', 'sub', operator.sub),
                                     (2, 'r', 'mul', operator.mul),
                                     (2, 'r', 'pow', operator.pow),
                                     (2, 'r', 'truediv', operator.truediv),
                                     (2, 'r', 'floordiv', operator.floordiv),
                                     (2, 'r', 'mod', operator.mod),
                                     (2, 'i', 'add', operator.add),
                                     (2, 'i', 'sub', operator.sub),
                                     (2, 'i', 'mul', operator.mul),
                                     (2, 'i', 'pow', operator.pow),
                                     (2, 'i', 'truediv', operator.truediv),
                                     (2, 'i', 'floordiv', operator.floordiv),
                                     (2, 'i', 'mod', operator.mod)])

        return cls

    @init_Simple_Vector
    class Simple_Vector(FV):
        """
        A simple vector class with {dimensions} dimensions and the component names '{cs_cnames}'
        """

        _component_operators = \
            {
                'arg1_n':
                    {
                        'abs': operator.abs,
                        'neg': operator.neg,
                        'pos': operator.pos,
                        'floor': math.floor,
                        'ceil': math.ceil,
                        'trunc': math.trunc
                    },
                'arg2_n':
                    {
                        'round': round,
                        'add': operator.add,
                        'sub': operator.sub,
                        'mul': operator.mul,
                        'pow': operator.pow,
                        'truediv': operator.truediv,
                        'floordiv': operator.floordiv,
                        'mod': operator.mod,
                    },
                'arg1_o':
                    {
                    },
                'arg2_o':
                    {
                    },
                'arg2_i':
                    {
                        'iadd': operator.iadd,
                        'isub': operator.isub,
                        'imul': operator.imul,
                        'ipow': operator.ipow,
                        'itruediv': operator.itruediv,
                        'ifloordiv': operator.ifloordiv,
                        'imod': operator.imod
                    }
            }

        def _cvalues_present(self, cnames):

            cnames_present = \
                (
                    cns in cnames
                    for cns in self._cnames
                )
            result = zip(self._cvalues, cnames_present)

            return result

        def _apply_operator_arg1_n(self, op, cnames):

            cvalues_present = self._cvalues_present(cnames)

            def apply_op_arg1_n():

                vector = \
                    self._vector(
                        op(cvs) if present else cvs
                        for cvs, present in cvalues_present
                    )

                return vector

            return apply_op_arg1_n

        def _apply_operator_arg2_n(self, op, cnames):

            cvalues_present = self._cvalues_present(cnames)

            def apply_op_arg2_n(value):

                vector = \
                    self._vector(
                        op(cvs, value) if present else cvs
                        for cvs, present in cvalues_present
                    )

                return vector

            return apply_op_arg2_n

        def _apply_operator_arg1_o(self, op, cnames):

            cvalues_present = self._cvalues_present(cnames)

            def apply_op(cvs, value):

                cvs = copy(cvs)
                op(cvs, value)

                return cvs

            def apply_op_arg1_o(value):

                vector = \
                    self._vector(
                        apply_op(cvs, value) if present else cvs
                        for cvs, present in cvalues_present
                    )

                return vector

            return apply_op_arg1_o

        def _apply_operator_arg2_o(self, op, cnames):

            cvalues_present = self._cvalues_present(cnames)

            def apply_op(cvs, value0, value1):

                cvs = copy(cvs)
                op(cvs, value0, value1)

                return cvs

            def apply_op_arg2_o(value0, value1):

                vector = \
                    self._vector(
                        apply_op(cvs, value0, value1) if present else cvs
                        for cvs, present in cvalues_present
                    )

                return vector

            return apply_op_arg2_o

        def _apply_operator_arg2_i(self, op, cnames):

            cvalues_present = self._cvalues_present(cnames)

            def apply_op_arg2_i(value):

                self._cvalues = \
                    [
                        op(cvs, value) if present else cvs
                        for cvs, present in cvalues_present
                    ]

            return apply_op_arg2_i

        def __round__(self, ndigits=0):
            """Round each component value to a given precision in decimal digits"""

            vector = \
                self._vector(
                    round(cvs, ndigits)
                    for cvs in self._cvalues
                )

            return vector

        def __floor__(self):
            """Apply math.floor to each of the vector component values"""

            vector = self._vector(map(math.floor, self._cvalues))

            return vector

        def __ceil__(self):
            """Apply math.ceil to each of the vector component values"""

            vector = self._vector(map(math.ceil, self._cvalues))

            return vector

        def __trunc__(self):
            """Apply math.trunc to each of the vector component values"""

            vector = self._vector(map(math.trunc, self._cvalues))

            return vector

        def _decode_attr_name(self, attr_name):

            if attr_name.startswith('c_'):
                attr_name = attr_name[2:]
                method_name, *decoded_cnames = attr_name.split('_')
                for op_type, operator_names in self._component_operators.items(
                ):
                    if method_name in operator_names:
                        break
                else:
                    op_type = None
                if len(decoded_cnames) == 0:
                    bar = False
                else:
                    bar = decoded_cnames[0] == 'bar'
                    if bar:
                        del decoded_cnames[0]
                if verify_names(chk_names=decoded_cnames,
                                ref_names=self._cnames):
                    if bar:
                        decoded_cnames = \
                            [
                                cns
                                for cns in self._cnames
                                if cns not in decoded_cnames
                            ]
                else:
                    decoded_cnames = None
            else:
                method_name, op_type, decoded_cnames = None, None, None

            return method_name, op_type, decoded_cnames

        def __setattr__(self, attr_name, value):
            """TODO"""

            decoded_attr_name = self._decode_attr_name(attr_name)
            if any(val is None for val in decoded_attr_name):
                super().__setattr__(attr_name, value)
            else:
                cls = type(self)
                msg = "'{cls.__name__}' object attribute '{attr_name}' is read-only"
                raise AttributeError(msg.format_map(vars()))

        def __getattr__(self, attr_name):
            """TODO"""

            try:
                attr = super().__getattribute__(attr_name)
            except AttributeError:
                attr = None
            if attr is None:
                decoded_attr_name = self._decode_attr_name(attr_name)
                if any(val is None for val in decoded_attr_name):
                    cls = type(self)
                    msg = "'{cls.__name__}' object has no attribute '{attr_name}'"
                    raise AttributeError(msg.format_map(vars()))
                else:
                    method_name, op_type, cnames = decoded_attr_name
                    op = self._component_operators[op_type][method_name]
                    methods_apply_op = \
                        {
                            'arg1_n': self._apply_operator_arg1_n,
                            'arg2_n': self._apply_operator_arg2_n,
                            'arg1_o': self._apply_operator_arg1_o,
                            'arg2_o': self._apply_operator_arg2_o,
                            'arg2_i': self._apply_operator_arg2_i
                        }
                    method = methods_apply_op[op_type]
                    attr = method(op, cnames)
                    attr.__name__ = attr_name
                    attr.__doc__ = \
                        "Applies operator '{method_name}' to these vector components: {component_names}" \
                        .format(method_name=method_name, component_names=', '.join(cnames))

            return attr

    return Simple_Vector
예제 #4
0
def create_class_Fundamental_Vector(name,
                                    component_names,
                                    *,
                                    brackets='<>',
                                    sep=', '):
    """
    Function that creates a fundamental vector class
    The number of dimensions are determined by the number of component names
    """
    def verify_arguments(component_names, dimensions, brackets):

        if len(component_names) != dimensions:
            msg = "Some of the component names are not unique"
            raise ValueError(msg)
        if dimensions == 0:
            msg = "The number of component names is 0"
            raise ValueError(msg)
        for cname in component_names:
            if not isinstance(cname, str):
                msg = "One (or more) of the component names is not a string"
                raise TypeError(msg)
            if not check_identifier(cname):
                msg = \
                    "All characters in the component names must be either " \
                    "a latin letter, a greek letter or a decimal digit, except " \
                    "for the first character which should not be a digit"
                raise ValueError(msg)
        if len(brackets) != 2:
            msg = "The number of characters (or strings) for the brackets is not 2"
            raise ValueError(msg)

    hf.verify_class_name(name)
    component_names = [*component_names]
    dimensions = len(set(component_names))
    brackets = [str(br) for br in brackets]
    sep = str(sep)
    verify_arguments(component_names, dimensions, brackets)

    def setup_components_access(cls):

        for index, cname in enumerate(cls._cnames):

            def cget(self, _index=index):

                value = copy(self._cvalues[_index])

                return value

            def cset(self, value, _index=index):

                self._cvalues[_index] = copy(value)


            cdoc = \
                "Value of {cname}-component of vector (component no. {index})" \
                .format_map(vars())
            setattr(cls, cname, property(fget=cget, fset=cset, doc=cdoc))

    def init_Fundamental_Vector(cls):
        """Initialize class"""

        cls._dimensions = dimensions
        cls._cnames = component_names
        cls.brackets = brackets
        cls.sep = sep
        hf.setup_vector_class(cls=cls, name=name, functions=None)
        setup_components_access(cls)

        return cls

    @init_Fundamental_Vector
    class Fundamental_Vector:
        """
        A fundamental vector class with {dimensions} dimensions and the component names '{cs_cnames}'
        """
        @classmethod
        def dimensions(cls):
            """Number of dimensions for vectors in the class"""

            dim = cls._dimensions

            return dim

        @classmethod
        def component_names(cls):
            """List of component names for vectors in the class"""

            cnames = cls._cnames.copy()

            return cnames

        @classmethod
        def is_vector(cls, va):
            """Check if something is a vector"""

            result = isinstance(va, cls)

            return result

        @classmethod
        def fill(cls, value):
            """A vector with all component values set to value"""

            cvalues = \
                (
                    copy(value)
                    for _ in range(cls._dimensions)
                )
            vector = cls(*cvalues, _internal=True)

            return vector

        @classmethod
        def _ensure_all_are_vectors(cls, vectors):

            vectors = \
                (
                    v if cls.is_vector(v) else cls.fill(v)
                    for v in vectors
                )

            return vectors

        def __init__(self, *cvalues, _internal=False, **named_cvalues):
            """TODO"""

            if _internal:
                self._cvalues = [*cvalues]
            else:
                self._check_arguments(cvalues, named_cvalues)
                if len(named_cvalues) > 0:
                    self._cvalues = \
                        [
                            copy(named_cvalues[cns])
                            for cns in self._cnames
                        ]
                else:
                    self._cvalues = \
                        [
                            copy(cv)
                            for cv in cvalues
                        ]

        def _check_arguments(self, cvalues, named_cvalues):

            no_of_cvalues = len(cvalues)
            no_of_named_cvalues = len(named_cvalues)
            cls = type(self)
            if (no_of_cvalues == self._dimensions) and (no_of_named_cvalues
                                                        == 0):
                pass
            elif (no_of_cvalues == 0) and (no_of_named_cvalues
                                           == self._dimensions):
                if set(named_cvalues) == set(self._cnames):
                    pass
                else:
                    msg = "One or more keyword argument to {cls.__name__}() was given with wrong name)"
                    raise ValueError(msg.format_map(vars()))
            else:
                if no_of_cvalues == 0:
                    if no_of_named_cvalues == 0:
                        msg = "{cls.__name__}() takes {self._dimensions} argument(s) (0 was given)"
                    else:
                        msg = "{cls.__name__}() takes {self._dimensions} argument(s) ({no_of_named_cvalues} was given)"
                else:
                    if no_of_named_cvalues == 0:
                        msg = "{cls.__name__}() takes {self._dimensions} argument(s) ({no_of_cvalues} was given)"
                    else:
                        msg = "Arguments to {cls.__name__}() must either be given with or without keywords (both was given)"
                raise TypeError(msg.format_map(vars()))

        def component_values(self):
            """List of a vector's component values"""

            cvalues = \
                [
                    copy(cvs)
                    for cvs in self._cvalues
                ]

            return cvalues

        def _vector(self, cvalues):

            cls = type(self)
            vector = cls(*cvalues, _internal=True)

            return vector

        def copy(self):
            """Copy of vector"""

            vector = self._vector(map(copy, self._cvalues))

            return vector

        @property
        def cnames(self):
            """List of a vector's component names"""

            component_names = self.component_names()

            return component_names

        @property
        def cvalues(self):
            """List of a vector's component values"""

            component_values = self.component_values()

            return component_values

        def as_dict(self):
            """A vector's component names and values in a dictionary"""

            result = \
                dict(
                    zip(
                        self.component_names(),
                        self.component_values()
                    )
                )

            return result

        def __len__(self):
            """Number of dimensions for vector"""

            dimensions = self._dimensions

            return dimensions

        def __str__(self):
            """Apply str() to each component value"""

            csv = self.sep.join(map(str, self._cvalues))
            string = csv.join(self.brackets)

            return string

        def __repr__(self):
            """Apply repr() to each component value"""

            csv = \
                self.sep.join(
                    cns + '=' + repr(cvs)
                    for cns, cvs in zip(self._cnames, self._cvalues)
                )
            cls = type(self)
            string = cls.__name__ + '(' + csv + ')'

            return string

        def __format__(self, format_spec=''):
            """Apply format() to each component value"""

            csv = \
                self.sep.join(
                    format(cvs, format_spec)
                    for cvs in self._cvalues
                )
            string = csv.join(self.brackets)

            return string

        def __iter__(self):
            """Iterable for iterating over the vector component values"""

            yield from map(copy, self._cvalues)

        def __getitem__(self, index):
            """Retrive vector component values by indexing"""

            cvalues = self.component_values()

            return cvalues[index]

        def __setitem__(self, index, values):
            """Change vector component values by indexing"""

            if isinstance(index, int):
                cvalues = copy(values)
            elif isinstance(index, slice):
                cvalues = \
                    [
                        copy(cv)
                        for cv in values
                    ]
                indices = range(*index.indices(self._dimensions))
                no_of_cvalues = len(cvalues)
                no_of_indices = len(indices)
                if no_of_cvalues != no_of_indices:
                    msg = \
                        "The number of given values ({no_of_cvalues}) does not match " \
                        "the number of components to be set ({no_of_indices})" \
                        .format_map(vars())
                    raise ValueError(msg)
            else:
                msg = \
                    "Vector indices must be integers or slices, not {type_index.__name__}" \
                    .format(type_index=type(index))
                raise TypeError(msg)
            self._cvalues[index] = cvalues

        @hf.ensure_other_is_vector
        def __eq__(self, other):

            for cvs, cvo in zip(self._cvalues, other._cvalues):
                if cvs != cvo:
                    equal = False
                    break
            else:
                equal = True

            return equal

        @hf.ensure_other_is_vector
        def __ne__(self, other):

            for cvs, cvo in zip(self._cvalues, other._cvalues):
                if cvs != cvo:
                    not_equal = True
                    break
            else:
                not_equal = False

            return not_equal

        def __contains__(self, value):
            """Check if a value is equal to any of the vector component values"""

            contains = value in self._cvalues

            return contains

        def __call__(self, function, needs_index=False):
            """Apply a function to each of the vector component values"""

            if needs_index:
                dimensions = self._dimensions
                vector = \
                    self._vector(
                        function(cvs, index, dimensions)
                        for index, cvs in enumerate(self._cvalues)
                    )
            else:
                vector = self._vector(map(function, self._cvalues))

            return vector

        @classmethod
        def _verify_function(cls, name, function, no_of_arguments):

            if function is None:
                method_name = 'component_' + name
                if not hasattr(cls, method_name):
                    msg = \
                        "class {cls.__name__} has no method named '{method_name}'" \
                        .format_map(vars())
                    raise AttributeError(msg)
            else:
                # function_types = \
                #     (
                #         types.FunctionType,
                #         types.BuiltinFunctionType,
                #         functools.partial
                #     )
                # if not isinstance(function, function_types):
                function_name = getattr(function, '__name__', str(function))
                if not (isfunction(function) or ismethod(function)):
                    msg = \
                        "{function_name} can not be used here" \
                        .format_map(vars())
                    raise TypeError(msg)
                msg = \
                    "{function_name} can not be called with {n} argument{s}" \
                    .format(
                        function_name = function_name,
                        n = no_of_arguments,
                        s = '' if no_of_arguments == 1 else 's'
                    )
                argspec = getfullargspec(function)
                arg_count = len(argspec.args)
                if argspec.defaults is not None:
                    arg_count -= len(argspec.defaults)
                if arg_count > no_of_arguments:
                    raise TypeError(msg)
                if arg_count < no_of_arguments and argspec.varargs is None:
                    raise TypeError(msg)
                kwonly_arg_count = len(argspec.kwonlyargs)
                if argspec.defaults is not None:
                    kwonly_arg_count -= len(argspec.kwonly_defaults)
                if kwonly_arg_count > 0:
                    raise TypeError(msg)

        @classmethod
        def create_vector_method_arg1(cls, name, function=None):
            """TODO"""

            cls._verify_function(name, function, 1)
            if function is None:
                function = getattr(cls, 'component_' + name)
            name = 'vector_' + name
            vector_function = hf.make_method_arg1(name, function)
            setattr(cls, name, vector_function)

        @classmethod
        def create_vector_method_arg2(cls, name, function=None):
            """TODO"""

            cls._verify_function(name, function, 2)
            if function is None:
                function = getattr(cls, 'component_' + name)
            name = 'vector_' + name
            vector_function = hf.make_method_arg2(name, function)
            setattr(cls, name, vector_function)

        @classmethod
        def create_vector_method_arg3(cls, name, function=None):
            """TODO"""

            cls._verify_function(name, function, 3)
            if function is None:
                function = getattr(cls, 'component_' + name)
            name = 'vector_' + name
            vector_function = hf.make_method_arg3(name, function)
            setattr(cls, name, vector_function)

    return Fundamental_Vector
예제 #5
0
def create_class_Vector(name,
                        component_names,
                        *,
                        brackets='<>',
                        sep=', ',
                        cnull=None,
                        cunit=None,
                        functions=None):
    """
    Function that makes a creates class
    The number of dimensions are determined by the number of component names
    """

    hf.verify_class_name(name)
    if functions is None:
        functions = {}

    def verify_equal(eq, op_all):

        try:
            equal = op_all(eq)
        except TypeError:
            equal = None
        if equal is None:
            equal = bool(eq)
        if not equal:
            msg = "Invalid value(s) for cnull and/or cunit"
            raise ValueError(msg)

    def verify_equal_args(arg0, arg1):

        if functions is None:
            op_eq = operator.eq
            op_all = all
        else:
            op_eq = functions.get('eq', operator.eq)
            op_all = functions.get('all', all)
        verify_equal(op_eq(arg0, arg1), op_all)
        verify_equal(op_eq(arg1, arg0), op_all)

    def verify_units(cnull, cunit):
        """Verify that some fundamental statements with cnull and cunit are true"""

        verify_equal_args(cnull, -cnull)  # ?
        verify_equal_args(cnull, +cnull)
        verify_equal_args(cnull, --cnull)
        verify_equal_args(cunit, --cunit)
        verify_equal_args(cunit, +cunit)
        verify_equal_args(cnull, cnull + cnull)
        verify_equal_args(cnull, cnull - cnull)
        verify_equal_args(cnull, +cnull - cnull)
        verify_equal_args(cnull, -cnull + cnull)
        verify_equal_args(cunit, cnull + cunit)
        verify_equal_args(cunit, cunit + cnull)
        verify_equal_args(cnull, +cunit - cunit)
        verify_equal_args(cnull, -cunit + cunit)
        verify_equal_args(cnull, cnull * cnull)
        verify_equal_args(cnull, cnull * cunit)
        verify_equal_args(cnull, cunit * cnull)
        verify_equal_args(cunit, cunit * cunit)
        verify_equal_args(cnull, cnull / cunit)
        verify_equal_args(cunit, cunit / cunit)
        verify_equal_args(cunit, cnull**cnull)  # ?
        verify_equal_args(cnull, cnull**cunit)
        verify_equal_args(cunit, cunit**cnull)
        verify_equal_args(cunit, cunit**cunit)
        # verify_equal_args(cunit, cunit**-cunit)  # Problem with Pandas
        verify_equal_args(cnull, cnull * 0)
        verify_equal_args(cnull, 0 * cnull)
        verify_equal_args(cnull, cnull * 1)
        verify_equal_args(cnull, 1 * cnull)
        verify_equal_args(cnull, cunit * 0)
        verify_equal_args(cnull, 0 * cunit)
        verify_equal_args(cunit, cunit * 1)
        verify_equal_args(cunit, 1 * cunit)
        verify_equal_args(cnull, cnull / 1)
        verify_equal_args(cnull, 0 / cunit)
        verify_equal_args(cunit, cunit / 1)
        verify_equal_args(cunit, 1 / cunit)
        verify_equal_args(cunit, cnull**0)  # ?
        verify_equal_args(cnull, cnull**1)
        verify_equal_args(cunit, cunit**0)
        verify_equal_args(cunit, cunit**1)
        # verify_equal_args(cunit, cunit**-1)  # Problem with Pandas
        verify_equal_args(cunit, 0**cnull)  # ?
        verify_equal_args(cnull, 0**cunit)
        verify_equal_args(cunit, 1**cnull)
        verify_equal_args(cunit, 1**cunit)
        # verify_equal_args(cunit, 1**-cunit)  # Problem with Pandas

    if (cnull is None) and (cunit is None):
        cnull = 0
        cunit = 1
    else:
        if (cnull is None) or (cunit is None):
            msg = \
                "If a value for either cnull or cunit is provided, " \
                "a value for the other must also be provided"
            raise TypeError(msg)
        cnull = copy(cnull)
        cunit = copy(cunit)
    verify_units(cnull, cunit)
    SV = \
        create_class_Simple_Vector(
            name = 'SV_' + name,
            component_names = component_names,
            brackets = brackets,
            sep = sep
        )

    def make_zero_vector_method(cls):

        cnull = cls._cnull
        cvalues = [cnull] * cls._dimensions

        def zero(cls):
            """Vector with all components values set to 'cnull'"""

            return cls(*cvalues, _internal=True)

        cls.zero = classmethod(zero)

    def make_one_vector_method(cls):

        cunit = cls._cunit
        cvalues = [cunit] * cls._dimensions

        def one(cls):
            """Vector with all components values set to 'cunit'"""

            return cls(*cvalues, _internal=True)

        cls.one = classmethod(one)

    def setup_vector_bases(cls):
        class Basis:
            """Descriptor class for basis vectors"""
            def __init__(self, owner, index, cname, basis_name):

                self.method_name = basis_name
                self.cname = cname
                cnull = owner._cnull
                cunit = owner._cunit
                self.cvalues = \
                    [
                        cunit if (i == index) else cnull
                        for i in range(owner._dimensions)
                    ]

            def __set__(self, instance, value):

                owner = type(instance)
                msg = \
                    "'{owner.__name__}' object attribute '{self.method_name}' is read-only" \
                    .format_map(vars())
                raise AttributeError(msg)

            def __get__(self, instance, owner):
                def basis_vector():

                    vector = owner(*self.cvalues, _internal=True)

                    return vector

                basis_vector.__name__ = self.method_name
                basis_vector.__doc__ = \
                    "Basis vector, with length 'cunit' along the {self.cname}-axis" \
                    .format_map(vars())

                return basis_vector

        for index, cname in enumerate(cls._cnames):
            basis_name = 'basis_' + cname
            setattr(cls, basis_name, Basis(cls, index, cname, basis_name))

    def init_Vector(cls):
        """Initialize class"""

        hf.setup_vector_class(cls=cls, name=name, functions=functions)
        cls._cnull = cnull
        cls._cunit = cunit
        cls._true = cls.component_eq(cnull, cnull)
        cls._false = cls.component_ne(cnull, cnull)
        make_zero_vector_method(cls)
        make_one_vector_method(cls)
        setup_vector_bases(cls)
        hf.make_dunder_methods(
            cls,
            [
                (2, '', 'matmul', operator.matmul),
                (2, 'r', 'matmul', operator.matmul),
                # (2, 'i', 'matmul', operator.matmul)
            ])

        return cls

    @init_Vector
    class Vector(SV):
        """
        A vector class with {dimensions} dimensions and the component names '{cs_cnames}'
        """

        _internal_functions = \
            [
                'eq',
                'ne',
                'and',
                'or',
                'all',
                'floor',
                'ceil',
                'trunc'
            ]
        _component_operators = \
            {
                'arg1_n':
                    {
                        'abs': operator.abs,
                        'neg': operator.neg,
                        'pos': operator.pos,
                        'floor': math.floor,
                        'ceil': math.ceil,
                        'trunc': math.trunc
                    },
                'arg2_n':
                    {
                        'round': round,
                        'add': operator.add,
                        'sub': operator.sub,
                        'mul': operator.mul,
                        'pow': operator.pow,
                        'matmul': operator.matmul, # ?
                        'truediv': operator.truediv,
                        'floordiv': operator.floordiv,
                        'mod': operator.mod,
                    },
                'arg1_o':
                    {
                    },
                'arg2_o':
                    {
                    },
                'arg2_i':
                    {
                        'iadd': operator.iadd,
                        'isub': operator.isub,
                        'imul': operator.imul,
                        'ipow': operator.ipow,
                        'imatmul': operator.imatmul, # ?
                        'itruediv': operator.itruediv,
                        'ifloordiv': operator.ifloordiv,
                        'imod': operator.imod
                    }
            }

        @classmethod
        def component_null(cls):
            """Null value for vector components in class"""

            cnull = copy(cls._cnull)

            return cnull

        @classmethod
        def component_unit(cls):
            """Unit value for vector components in class"""

            cunit = copy(cls._cunit)

            return cunit

        @classmethod
        def fill(cls, value):
            """A vector with all component values set to value"""

            cunit = cls._cunit
            cvalues = \
                (
                    cunit * value
                    for _ in range(cls._dimensions)
                )
            vector = cls(*cvalues, _internal=True)

            return vector

        @classmethod
        def sum_of_vectors(cls, vectors):
            """The sum of several vectors"""

            vectors = cls._ensure_all_are_vectors(vectors)
            vector = reduce(operator.add, vectors, cls.zero())

            return vector

        @classmethod
        def prod_of_vectors(cls, vectors):
            """The product of several vectors"""

            vectors = cls._ensure_all_are_vectors(vectors)
            vector = reduce(operator.mul, vectors, cls.one())

            return vector

        def __init__(self, *cvalues, _internal=False, **named_cvalues):
            """TODO"""

            if _internal:
                self._cvalues = [*cvalues]
            else:
                self._check_arguments(cvalues, named_cvalues)
                cunit = self._cunit
                if len(named_cvalues) > 0:
                    self._cvalues = \
                     [
                         cunit * named_cvalues[cns]
                         for cns in self._cnames
                     ]
                else:
                    self._cvalues = \
                        [
                            cunit * cv
                            for cv in cvalues
                        ]

        def is_zero_vector(self):
            """Check if the vector is a zero vector"""

            cnull = self._cnull
            are_zeros = \
                (
                    self.component_eq(cvs, cnull)
                    for cvs in self._cvalues
                )
            is_zero = reduce(self.component_and, are_zeros)

            return is_zero

        def contains(self, value):
            """Check if a value is equal to any of the vector component values"""

            are_present = \
                (
                    self.component_eq(value, cvs)
                    for cvs in self._cvalues
                )
            does_contain = reduce(self.component_or, are_present)

            return does_contain

        def contains_not(self, value):
            """Check if a value is not equal to any of the vector component values"""

            are_not_present = \
                (
                    self.component_ne(value, cvs)
                    for cvs in self._cvalues
                )
            does_not_contain = reduce(self.component_and, are_not_present)

            return does_not_contain

        def __setitem__(self, index, values):
            """Change vector component values by indexing"""

            cunit = self._cunit
            if isinstance(index, int):
                cvalues = cunit * values
            elif isinstance(index, slice):
                cvalues = \
                    [
                        cunit * cv
                        for cv in values
                    ]
                indices = range(*index.indices(self._dimensions))
                no_of_cvalues = len(cvalues)
                no_of_indices = len(indices)
                if no_of_cvalues != no_of_indices:
                    msg = \
                        "The number of given values ({no_of_cvalues}) does not match " \
                        "the number of components to be set ({no_of_indices})" \
                        .format_map(vars())
                    raise ValueError(msg)
            else:
                msg = \
                    "Vector index must be an integer or a slice, not {type_index.__name__}" \
                     .format(type_index=type(index))
                raise TypeError(msg)
            self._cvalues[index] = cvalues

        def __floor__(self):
            """Apply 'component_floor' to each of the vector component values"""

            vector = self._vector(map(self.component_floor, self._cvalues))

            return vector

        def __ceil__(self):
            """Apply 'component_ceil' to each of the vector component values"""

            vector = self._vector(map(self.component_ceil, self._cvalues))

            return vector

        def __trunc__(self):
            """Apply 'component_trunc' to each of the vector component values"""

            vector = self._vector(map(self.component_trunc, self._cvalues))

            return vector

        def __bool__(self):
            """Check if the vector is not a zero vector"""

            is_zero_vector = self.is_zero_vector()
            try:
                not_zero_vector = not self.component_all(is_zero_vector)
            except TypeError:
                not_zero_vector = None
            if not_zero_vector is None:
                not_zero_vector = not is_zero_vector

            return not_zero_vector

        def sum_of_components(self):
            """The sum of a vector's component values"""

            csum = self.component_null()
            for cvs in self._cvalues:
                csum += cvs

            return csum

        def prod_of_components(self):
            """The product of a vector's component values"""

            cprod = self.component_unit()
            for cvs in self._cvalues:
                cprod *= cvs

            return cprod

        @property
        def cnull(self):
            """Null value for vector components"""

            component_null = self.component_null()

            return component_null

        @property
        def cunit(self):
            """Unit value for vector components"""

            component_unit = self.component_unit()

            return component_unit

        @property
        def csum(self):
            """The sum of a vector's component values"""

            sum_of_components = self.sum_of_components()

            return sum_of_components

        @property
        def cprod(self):
            """The product of a vector's component values"""

            prod_of_components = self.prod_of_components()

            return prod_of_components

    return Vector
예제 #6
0
def create_class_Versatile_Vector(name, component_names, *, brackets='<>', sep=', ', functions=None):
    """
    Function that creates a versatile vector class
    The number of dimensions are determined by the number of component names
    """

    hf.verify_class_name(name)
    if functions is None:
        functions = { }

    SV = \
        create_class_Simple_Vector(
            name = 'SV_' + name,
            component_names = component_names,
            brackets = brackets,
            sep = sep
        )


    def init_Versatile_Vector(cls):
        """Initialize class"""

        hf.setup_vector_class(cls=cls, name=name, functions=functions)
        hf.make_dunder_methods(
            cls,
            [
                (1, '', 'not', operator.not_),
                (1, '', 'index', operator.index),
                (1, '', 'inv', operator.inv),
                (1, '', 'invert', operator.invert),
                (2, '', 'matmul', operator.matmul),
                (2, '', 'eq', operator.eq),
                (2, '', 'ne', operator.ne),
                (2, '', 'lt', operator.lt),
                (2, '', 'gt', operator.gt),
                (2, '', 'le', operator.le),
                (2, '', 'ge', operator.ge),
                (2, '', 'and', operator.and_),
                (2, '', 'or', operator.or_),
                (2, '', 'xor', operator.xor),
                (2, '', 'lshift', operator.lshift),
                (2, '', 'rshift', operator.rshift),
                (2, 'r', 'matmul', operator.matmul),
                (2, 'r', 'and', operator.and_),
                (2, 'r', 'or', operator.or_),
                (2, 'r', 'xor', operator.xor),
                (2, 'r', 'lshift', operator.lshift),
                (2, 'r', 'rshift', operator.rshift),
                (2, 'i', 'matmul', operator.matmul),
                (2, 'i', 'and', operator.and_),
                (2, 'i', 'or', operator.or_),
                (2, 'i', 'xor', operator.xor),
                (2, 'i', 'lshift', operator.lshift),
                (2, 'i', 'rshift', operator.rshift)
            ]
        )

        return cls


    @init_Versatile_Vector
    class Versatile_Vector(SV):
        """
        A versatile vector class with {dimensions} dimensions and the component names '{cs_cnames}'
        """

        _internal_functions = \
            [
                'any'
            ]
        _component_operators = \
            {
                'arg1_n':
                    {
                        'not': operator.not_,
                        'truth': operator.truth,
                        'abs': operator.abs,
                        'neg': operator.neg,
                        'pos': operator.pos,
                        'index': operator.index,
                        'inv': operator.inv,
                        'invert': operator.invert,
                        'floor': math.floor,
                        'ceil': math.ceil,
                        'trunc': math.trunc
                    },
                'arg2_n':
                    {
                        'round': round,
                        'and': operator.and_,
                        'or': operator.or_,
                        'xor': operator.xor,
                        'eq': operator.eq,
                        'ne': operator.ne,
                        'lt': operator.lt,
                        'gt': operator.gt,
                        'le': operator.le,
                        'ge': operator.ge,
                        'add': operator.add,
                        'sub': operator.sub,
                        'mul': operator.mul,
                        'pow': operator.pow,
                        'matmul': operator.matmul,
                        'truediv': operator.truediv,
                        'floordiv': operator.floordiv,
                        'mod': operator.mod,
                        'lshift': operator.lshift,
                        'rshift': operator.rshift,
                        'is': operator.is_,
                        'isnot': operator.is_not,
                        'concat': operator.concat,
                        'contains': operator.contains,
                        'countof': operator.countOf,
                        'indexof': operator.indexOf,
                        'getitem': operator.getitem
                    },
                'arg1_o':
                    {
                        'delitem': operator.delitem
                    },
                'arg2_o':
                    {
                        'setitem': operator.setitem
                    },
                'arg2_i':
                    {
                        'iand': operator.iand,
                        'ior': operator.ior,
                        'ixor': operator.ixor,
                        'iadd': operator.iadd,
                        'isub': operator.isub,
                        'imul': operator.imul,
                        'ipow': operator.ipow,
                        'imatmul': operator.imatmul,
                        'itruediv': operator.itruediv,
                        'ifloordiv': operator.ifloordiv,
                        'imod': operator.imod,
                        'ilshift': operator.ilshift,
                        'irshift': operator.irshift
                    }
            }


    return Versatile_Vector
def create_class_Cartesian_2D_Vector(name,
                                     component_names,
                                     *,
                                     brackets='<>',
                                     sep=', ',
                                     cnull=0,
                                     cunit=1,
                                     functions=None):
    """
    Function that creates a cartesian vector class with 2 dimensions
    """

    hf.verify_class_name(name)
    dimensions = 2
    component_names = [*component_names]
    if len(component_names) != dimensions:
        msg = "The number of component names must be {dimensions}"
        raise ValueError(msg.format_map(vars()))
    if functions is None:
        functions = {}
    CV = \
        create_class_Cartesian_Vector(
            name = 'CV_' + name,
            component_names = component_names,
            brackets = brackets,
            sep = sep,
            cnull = cnull,
            cunit = cunit,
            functions = functions
        )

    def init_Cartesian_2D_Vector(cls):
        """Initialize class"""

        hf.setup_vector_class(cls=cls, name=name, functions=functions)

        return cls

    @init_Cartesian_2D_Vector
    class Cartesian_2D_Vector(CV):
        """
        A cartesian vector class with {dimensions} dimensions and the component names '{cs_cnames}'
        """

        _internal_functions = \
            [
                # 'eq',
                # 'ne',
                # 'and',
                # 'or',
                # 'all',
                # 'min',
                # 'max',
                # 'floor',
                # 'ceil',
                # 'trunc',
                # 'pi',
                # 'atan2',
                'cos',
                'sin'
            ]

        @classmethod
        def from_polar(cls, radius, azimuth):
            """A vector created from polar coordinates"""

            cunit = cls._cunit
            azimuth = cunit * azimuth
            cos_a = cls.component_cos(azimuth)
            sin_a = cls.component_sin(azimuth)
            radius = cunit * radius
            vector = \
                cls(
                    radius * cos_a,
                    radius * sin_a,
                    _internal = True
                )

            return vector

        def perp(self):
            """A vector that is perpendicular to a vector"""

            cvs0, cvs1 = self._cvalues
            vector = self._vector((-cvs1, cvs0))

            return vector

        def perp_dot(self, other):
            """The dot product of a vector that is perpendicular to a vector and another vector"""

            scalar = self.perp().dot(other)

            return scalar

        @hf.ensure_other_is_vector
        def sin(self, other, clip=False):
            """The sine of the angle between two vectors (from -cunit to +cunit)"""

            ls = self.length()
            lo = other.length()
            pd = self.perp_dot(other)
            try:
                sine = pd / (ls * lo)
            except ZeroDivisionError as err:
                msg = "One (or both) of the vectors is a zero vector"
                raise ZeroDivisionError(msg) from err
            if clip:
                cunit = self._cunit
                sine = self.clip(sine, -cunit, cunit)

            return sine

        @hf.ensure_other_is_vector
        def angle(self, other):
            """The angle in radians (from -cunit*pi to +cunit*pi) between two vectors"""  ### Verify

            cvs0, cvs1 = self._cvalues
            cvo0, cvo1 = other._cvalues
            angle_s = self.component_atan2(cvs1, cvs0)
            angle_o = other.component_atan2(cvo1, cvo0)
            angle_between = angle_o - angle_s
            # angle_between = self._angle_to_minus_plus_pi(angle_between)

            return angle_between

        def rotate(self, angle):
            """The vector rotated by an angle in radiands"""

            cunit = self._cunit
            angle = cunit * angle
            cos = self.component_cos(angle)
            sin = self.component_sin(angle)
            vector = \
                self._mmult(
                    self._vector(( cos, -sin)),
                    self._vector(( sin,  cos))
                )

            return vector

        @hf.ensure_other_is_vector
        def are_parallel(self, other):
            """Check if two vectors are parallel"""

            try:
                vsn = self.normalize()
            except ZeroDivisionError:
                vsn = None
            try:
                von = other.normalize()
            except ZeroDivisionError:
                von = None
            if (vsn is None) or (von is None):
                parallel = self._true
            else:
                perp_dot = vsn.perp_dot(von)
                parallel = self._equal_cnull(perp_dot)
                parallel = self.component_or(parallel, self.is_zero_vector())
                parallel = self.component_or(parallel, other.is_zero_vector())

            return parallel

        @hf.ensure_others_are_vectors
        def reorient(self, other, other_):
            """Reorient a vector from one direction to another direction"""

            angle = other.angle(other_)
            vector = self.rotate(angle)

            return vector

        def polar_as_dict(self):
            """Polar coordinates of a vector as a dictionary"""

            result = \
                {
                    'radius': self.radius,
                    'azimuth': self.azimuth
                }

            return result

        @property
        def radius(self):
            """TODO"""

            length = self.length()

            return length

        @property
        def azimuth(self):
            """TODO"""

            cvs0, cvs1 = self._cvalues
            angle = self.component_atan2(cvs1, cvs0)
            # angle = self._angle_to_minus_plus_pi(angle)

            return angle

    return Cartesian_2D_Vector
def create_class_Tolerant_Versatile_Vector(name,
                                           component_names,
                                           *,
                                           brackets='<>',
                                           sep=', ',
                                           functions=None,
                                           abs_tol=1e-12,
                                           rel_tol=1e-9):
    """
    Function that creates a tolerant versatile vector class
    The number of dimensions are determined by the number of component names
    """

    hf.verify_class_name(name)
    if functions is None:
        functions = {}
    VV = \
        create_class_Versatile_Vector(
            name = 'VV_' + name,
            component_names = component_names,
            brackets = brackets,
            sep = sep,
            functions = functions
        )

    def init_Tolerant_Versatile_Vector(cls):
        """Initialize class"""

        hf.setup_vector_class(cls=cls, name=name, functions=functions)

        def prepare_tolerances(name, tolerances, dimensions):

            try:
                all_tolerances = [*tolerances]
            except TypeError:
                all_tolerances = None
            if all_tolerances is None:
                all_tolerances = [tolerances] * dimensions
            else:
                if len(all_tolerances) != dimensions:
                    msg = "The number of tolerances in {name} does not match the number of dimensions"
                    raise ValueError(msg.format_map(vars()))
            result = \
                [
                    float(tol)
                    for tol in all_tolerances
                ]

            return result

        cls.abs_tolerances = prepare_tolerances('abs_tol', abs_tol,
                                                cls._dimensions)
        cls.rel_tolerances = prepare_tolerances('rel_tol', rel_tol,
                                                cls._dimensions)
        compare_methods = \
            {
                'eq': \
                    "Check if the corresponding component values of " \
                    "two vectors are equal (within calculated tolerances)",
                'ne': \
                    "Check if the corresponding component values of " \
                    "two vectors are not equal (within calculated tolerances)",
                'lt': \
                    "TODO",
                'gt': \
                    "TODO",
                'le': \
                    "TODO",
                'ge':
                    "TODO"
            }
        for comp_type, method_doc in compare_methods.items():

            def compare_method(self, other, _comp_type=comp_type):

                vector = self._vector(self._compare_cvalues(other, _comp_type))

                return vector

            method_name = comp_type
            compare_method.__name__ = method_name
            compare_method.__doc__ = method_doc
            setattr(cls, method_name, compare_method)
        dunder_compare_methods = \
            {
                'eq': \
                    "Check if all corresponding component values of " \
                    "two vectors are equal (within calculated tolerances)",
                'ne': \
                    "Check if all corresponding component values of " \
                    "two vectors are not equal (within calculated tolerances)",
                'lt': \
                    "TODO",
                'gt': \
                    "TODO",
                'le': \
                    "TODO",
                'ge':
                    "TODO"
            }
        for comp_type, method_doc in dunder_compare_methods.items():

            def dunder_compare_method(self, other, _comp_type=comp_type):

                compare_results = self._compare_cvalues(other, _comp_type)
                cmp = reduce(self.component_and, compare_results)
                try:
                    result = self.component_all(cmp)
                except TypeError:
                    result = None
                if result is None:
                    result = bool(cmp)

                return result

            method_name = '__' + comp_type + '__'
            dunder_compare_method.__name__ = method_name
            dunder_compare_method.__doc__ = method_doc
            setattr(cls, method_name, dunder_compare_method)

        return cls

    @init_Tolerant_Versatile_Vector
    class Tolerant_Versatile_Vector(VV):
        """
        A tolerant versatile vector class with {dimensions} dimensions and the component names '{cs_cnames}'
        """

        _internal_functions = \
            [
                'and',
                'or',
                # 'any',
                'max',
                'abs'
            ]

        ### Needs more testing
        @classmethod
        def _epsilon_1(cls, cvalues):

            eps = \
                (
                    cls.component_max(
                        cls.abs_tolerances[i],
                        cvalues[i]*cls.rel_tolerances[i]
                    )
                    for i in range(cls._dimensions)
                )

            return eps

### Needs more testing

        @classmethod
        def _epsilon_2(cls, cvalues_1, cvalues_2):

            eps = \
                (
                    cls.component_max(
                        cls.abs_tolerances[i],
                        cls.component_max(cvalues_1[i], cvalues_2[i])*cls.rel_tolerances[i]
                    )
                    for i in range(cls._dimensions)
                )

            return eps

### Needs more testing

        @classmethod
        def _epsilon_n(cls, cvalues_n, i):
            """Calculates a common tolerance value (epsilon) for all the values"""

            abs_tol = cls.abs_tolerances[i]
            rel_tol = cls.rel_tolerances[i]
            eps = \
                cls.component_max(
                    abs_tol,
                    cls.component_max(
                        cls.component_abs(cv)
                        for cv in cvalues_n
                    )*rel_tol  ### This does not always work
                )

            return eps

        @classmethod
        def _component_from_vectors(cls, vectors, index):

            vectors = cls._ensure_all_are_vectors(vectors)
            result = \
                (
                    v._cvalues[index]
                    for v in vectors
                )

            return result

### Needs more testing

        @classmethod
        def tolerance_all(cls, vectors):
            """Calculate a tolerance for each of the components of several vectors"""  ###

            vectors = tuple(cls._ensure_all_are_vectors(vectors))
            cvalues = \
                (
                    cls._epsilon_n(cls._component_from_vectors(vectors, i), i)
                    for i in range(cls._dimensions)
                )
            vector = cls(*cvalues, _internal=True)

            return vector

### TODO
#         def tolerance_with(self, other):
#             """Calculate a common tolerance for two vectors based on their lengths"""
#
#             return

        @hf.ensure_other_is_vector
        def _compare_cvalues(self, other, comp_type):

            compare_fn = \
                {
                    'eq': lambda s, o, t: self.component_and((o - t) <= s, s <= (o + t)),
                    'ne': lambda s, o, t: self.component_or(s < (o - t), (o + t) < s),
                    'lt': lambda s, o, t: s < (o - t),
                    'gt': lambda s, o, t: (o + t) < s,
                    'le': lambda s, o, t: s <= (o + t),
                    'ge': lambda s, o, t: (o - t) <= s
                }
            fn = compare_fn[comp_type]
            csot = \
                zip(
                    self._cvalues,
                    other._cvalues,
                    self.tolerance_with(other)._cvalues
                )
            result = \
                (
                    fn(cvs, cvo, cvt)
                    for cvs, cvo, cvt in csot
                )

            return result

    return Tolerant_Versatile_Vector
예제 #9
0
def create_class_Cartesian_Vector(name,
                                  component_names,
                                  *,
                                  brackets='<>',
                                  sep=', ',
                                  cnull=None,
                                  cunit=None,
                                  functions=None):
    """
    Function that creates a cartesian vector class
    The number of dimensions are determined by the number of component names
    """

    hf.verify_class_name(name)
    if functions is None:
        functions = {}
    V = \
        create_class_Vector(
            name = 'V_' + name,
            component_names = component_names,
            brackets = brackets,
            sep = sep,
            cnull = cnull,
            cunit = cunit,
            functions = functions
        )

    def init_Cartesian_Vector(cls):
        """Initialize class"""

        hf.setup_vector_class(cls=cls, name=name, functions=functions)
        cls.__abs__ = cls.length
        cls.__matmul__ = cls.dot
        cls.__rmatmul__ = cls.dot

        return cls

    @init_Cartesian_Vector
    class Cartesian_Vector(V):
        """
        A cartesian vector class with {dimensions} dimensions and the component names '{cs_cnames}'
        """

        _internal_functions = \
            [
                # 'eq',
                # 'ne',
                # 'and',
                # 'or',
                # 'all',
                'min',
                'max',
                # 'floor',
                # 'ceil',
                # 'trunc',
                'pi',
                'atan2'
            ]

        @classmethod
        def clip(cls, value, min_value, max_value):
            """Limits a value so that it lies between two values"""

            cunit = cls._cunit
            value = cunit * value
            min_value = cunit * min_value
            max_value = cunit * max_value
            clipped_value = cls.component_max(
                min_value, cls.component_min(value, max_value))

            return clipped_value

        @classmethod
        def _equal_cnull(cls, value):

            cnull = cls._cnull
            cunit = cls._cunit
            value = cunit * value
            result = cls.component_eq(value, cnull)

            return result

        @classmethod
        def _not_equal_cnull(cls, value):

            cnull = cls._cnull
            cunit = cls._cunit
            value = cunit * value
            result = cls.component_ne(value, cnull)

            return result

        @classmethod
        def _equal_cunit(cls, value):

            cunit = cls._cunit
            value = cunit * value
            result = cls.component_eq(value, cunit)

            return result

        @classmethod
        def _not_equal_cunit(cls, value):

            cunit = cls._cunit
            value = cunit * value
            result = cls.component_ne(value, cunit)

            return result

        def is_zero_vector(self):
            """Check if the length of a vector is equal to cnull"""

            ls = self.length()
            result = self._equal_cnull(ls)

            return result

        def is_unit_vector(self):
            """Check if the length of a vector is equal to cunit"""

            ls = self.length()
            result = self._equal_cunit(ls)

            return result

        @hf.ensure_other_is_vector
        def __eq__(self, other):
            """
            Check if two vectors are equal
            based on their distance from each other
            """

            distance_between = self.distance(other)
            equal = self._equal_cnull(distance_between)

            return equal

        @hf.ensure_other_is_vector
        def __ne__(self, other):
            """
            Check if two vectors are not equal
            based on their distance from each other
            """

            distance_between = self.distance(other)
            not_equal = self._not_equal_cnull(distance_between)

            return not_equal

        @hf.ensure_other_is_vector
        def are_orthogonal(self, other):
            """Check if two vectors are orthogonal"""

            dot = self.dot(other)
            orthogonal = self._equal_cnull(dot)
            ### Perhaps these are needed with numpy:
            # orthogonal = self.component_or(orthogonal, self.is_zero_vector())
            # orthogonal = self.component_or(orthogonal, other.is_zero_vector())

            return orthogonal

        @hf.ensure_other_is_vector
        def equal_lengths(self, other):
            """Check if two vectors have equal lengths"""

            ls = self.length()
            lo = other.length()
            equal_vector_lengths = self.component_eq(ls, lo)

            return equal_vector_lengths

        @hf.ensure_other_is_vector
        def shorter(self, other):
            """Check if a vector is shorter than another"""

            ls = self.length()
            lo = other.length()
            is_shorter = ls < lo

            return is_shorter

        @hf.ensure_other_is_vector
        def longer(self, other):
            """Check if a vector is longer than another"""

            ls = self.length()
            lo = other.length()
            is_longer = ls > lo

            return is_longer

        @hf.ensure_other_is_vector
        def dot(self, other):
            """The dot product (inner product) of two vectors"""

            scalar = (self * other).sum_of_components()

            return scalar

        def length(self):
            """The length (norm) of a vector"""

            cunit = self._cunit
            length_of_vector = (self**(cunit *
                                       2)).sum_of_components()**(cunit / 2)

            return length_of_vector

        @hf.ensure_other_is_vector
        def distance(self, other):
            """The distance between two vectors"""

            length_between = (other - self).length()

            return length_between

        def normalize(self):
            """Vector scaled so that its length is cunit"""

            ls = self.length()
            try:
                vector = self / ls
            except ZeroDivisionError as err:
                msg = "The length of the vector is zero"
                raise ZeroDivisionError(msg) from err

            return vector

        @hf.ensure_other_is_vector
        def project(self, other):
            """Projection of a vector onto another"""

            num = self.dot(other)
            den = other.dot(other)
            try:
                s = num / den
            except ZeroDivisionError as err:
                msg = "The length of the vector to project onto is zero"
                raise ZeroDivisionError(msg) from err
            vector = other * s

            return vector

        @hf.ensure_other_is_vector
        def inv_project(self, other):
            """Inverse projection of a vector from another"""

            num = self.dot(self)
            den = self.dot(other)
            try:
                s = num / den
            except ZeroDivisionError as err:
                msg = "The vectors are orthogonal to each other"
                raise ZeroDivisionError(msg) from err
            vector = other * s

            return vector

        @hf.ensure_other_is_vector
        def reject(self, other):
            """Rejection of a vector from another"""

            num = self.dot(other)
            den = other.dot(other)
            try:
                s = num / den
            except ZeroDivisionError as err:
                msg = "The length of the vector to reject from is zero"
                raise ZeroDivisionError(msg) from err
            vector = self - other * s

            return vector

        @hf.ensure_other_is_vector
        def scalar_project(self, other):
            """Scalar projection of a vector onto another vector"""

            try:
                von = other.normalize()
            except ZeroDivisionError as err:
                msg = "The length of the vector to project onto is zero"
                raise ZeroDivisionError(msg) from err
            scalar = self.dot(von)

            return scalar

        @hf.ensure_other_is_vector
        def angle(self, other):
            """
            The smallest angle in radians (from cnull to +cunit*pi) between two vectors
            Kahan, W. (2016). Computing Cross-Products and Rotations in 2- and 3-Dimensional Euclidean Spaces
            https://people.eecs.berkeley.edu/~wkahan/MathH110/Cross.pdf
            """

            ls = self.length()
            lo = other.length()
            vs = self * lo
            vo = other * ls
            ln = (vs - vo).length()
            ld = (vs + vo).length()
            angle_between = self.component_atan2(ln, ld) * 2

            return angle_between

        @hf.ensure_other_is_vector
        def cos(self, other, clip=False):
            """The cosine of the angle between two vectors (from -cunit to +cunit)"""

            ls = self.length()
            lo = other.length()
            dot = self.dot(other)
            try:
                cosine = dot / (ls * lo)
            except ZeroDivisionError as err:
                msg = "One (or both) of the vectors is a zero vector"
                raise ZeroDivisionError(msg) from err
            if clip:
                cunit = self._cunit
                cosine = self.clip(cosine, -cunit, cunit)

            return cosine

        def _mmult(self, *others):

            assert len(others) == self._dimensions
            vector = self._vector(map(self.dot, others))

            return vector

### Consider if this should be added
#         def __imatmul__(self, other):
#             """TODO"""
#
#             self = self * self.dot(other)
#
#             return self

    return Cartesian_Vector