<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def ortho_basis(normal, ref_vec=None):
 """Generates an orthonormal basis in the plane perpendicular to `normal` The orthonormal basis generated spans the plane defined with `normal` as its normal vector. The handedness of `on1` and `on2` in the returned basis is such that: .. math:: \\mathsf{on1} \\times \\mathsf{on2} = {\\mathsf{normal} \\over \\left\\| \\mathsf{normal}\\right\\|} `normal` must be expressible as a one-dimensional |nparray| of length 3. Parameters normal length-3 |npfloat_| -- The orthonormal basis output will span the plane perpendicular to `normal`. ref_vec length-3 |npfloat_|, optional -- If specified, `on1` will be the normalized projection of `ref_vec` onto the plane perpendicular to `normal`. Default is |None|. Returns ------- on1 length-3 |npfloat_| -- First vector defining the orthonormal basis in the plane normal to `normal` on2 length-3 |npfloat_| -- Second vector defining the orthonormal basis in the plane normal to `normal` Raises ------ ~exceptions.ValueError If `normal` or `ref_vec` is not expressible as a 1-D vector with 3 elements ~opan.error.VectorError (typecode :attr:`~opan.error.VectorError.NONPRL`) If `ref_vec` is specified and it is insufficiently non- parallel with respect to `normal` """


    # Imports for library functions
    import numpy as np
    from scipy import linalg as spla
    from scipy import random as sprnd
    from ..const import PRM
    from ..error import VectorError

    # Internal parameters
    # Magnitude of the perturbation from 'normal' in constructing a random rv
    RAND_MAG = 0.25

    # Test 'normal' for shape and length
    if not len(normal.shape) == 1:
        raise ValueError("'normal' is not a vector")
    ## end if
    if not normal.shape[0] == 3:
        raise ValueError("Length of 'normal' is not three")
    ## end if

    # Normalize to concise variable 'nv'
    nv = normal / spla.norm(normal)

    # Test for specification of ref_vec in the function call
    if ref_vec is None:
        # ref_vec not specified.
        #
        # Generate reference vector by generation of a random perturbation
        #  vector suitably non-parallel to norm_vec
        # Generate suitable randomizer, looping as needed
        rv = nv
        while parallel_check(nv, rv):
            rv = np.float64(1.0 - RAND_MAG + 2 * RAND_MAG * sprnd.rand(3))
        ## do loop

        # Calculate rejection of perturbed vector on the normal, then
        #  normalize
        rv = rej(rv, nv)
        rv = rv / spla.norm(rv)

    else:
        # ref_vec specified, go ahead and use.  Start with validity check.
        if not len(ref_vec.shape) == 1:
            raise ValueError("ref_vec is not a vector")
        ## end if
        if not ref_vec.shape[0] == 3:
            raise ValueError("ref_vec length is not three")
        ## end if

        # Normalize ref_vec to 'rv'
        rv = ref_vec / spla.norm(ref_vec)

        # Check for collinearity of nv and rv; raise error if too close
        if parallel_check(nv, rv):
            # Essentially equal or opposite vectors, making them too nearly
            #  parallel.
            raise VectorError(VectorError.NONPRL,
                    "'normal' and 'ref_vec' are too nearly parallel.", "")
        ## end if

        # rv is ok to use from here

    ## end try

    # on2 is the unit vector parallel to nv x rv
    on2 = np.cross(nv, rv)
    on2 = on2 / spla.norm(on2)

    # on1 is on2 x nv (normalization should not be necessary here, but is
    #  performed just in case)
    on1 = np.cross(on2, nv)
    on1 = on1 / spla.norm(on1)

    # Return the spanning vectors
    return on1, on2