Пример #1
0
 def test_001_curve_knot_vector_too_short(self):
     knot_vector = (0.0, )  # too short
     coef = (1.0, )
     degree = 0
     C = bsp.Curve(knot_vector, coef, degree)
     result = C.is_valid()
     self.assertIsInstance(result, AssertionError)
     self.assertTrue(
         result.args[0] == "Error: knot vector mininum length is two.")
Пример #2
0
 def test_002_curve_degree_too_small_and_verbose(self):
     knot_vector = (0.0, 1.0)
     coef = (1.0, )
     degree = -1  # integer >= 0, so -1 is out of range for test
     verbosity = True  # to test the verbose code lines
     C = bsp.Curve(knot_vector, coef, degree, verbosity)
     result = C.is_valid()
     self.assertIsInstance(result, AssertionError)
     self.assertTrue(
         result.args[0] == "Error: degree must be non-negative.")
Пример #3
0
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        COEF = kwargs.get(
            "coefficients", None
        )  # None is basis, not None is curve, surface, or volume
        assert COEF is None

        # build up B-spline basis functions
        for i in np.arange(self.NCP):

            coef = np.zeros(self.NCP)
            coef[i] = 1.0

            _B = bsp.Curve(self.knot_vector_t, coef, self.degree_t)

            if _B.is_valid():
                _y = _B.evaluate(self.evaluation_times)
                self.evaluated_bases.append(_y)

        # plot B-spline basis functions
        self.fig = plt.figure(figsize=plt.figaspect(1.0 / (self.nel + 1)), dpi=self.dpi)
        ax = self.fig.gca()

        # loop over each basis function and plot it
        for i in np.arange(self.NCP):
            _CPTXT = f"{i}"
            _DEGTXT = f"{self.degree_t}"
            ax.plot(
                self.evaluation_times,
                self.evaluated_bases[i],
                "-",
                lw=2,
                label="$N_{" + _CPTXT + "}^{" + _DEGTXT + "}$",
                linestyle=self.linestyles[np.remainder(i, len(self.linestyles))],
            )

        ax.set_xlabel(r"$t$")
        ax.set_ylabel(f"$N^{self.degree_t}_i(t)$")

        _eps = 0.1
        ax.set_xlim(
            [self.knot_vector_t[0] - 2 * _eps, self.knot_vector_t[-1] + 2 * _eps]
        )
        ax.set_ylim([0.0 - 2 * _eps, 1.0 + 2 * _eps])

        ax.legend(bbox_to_anchor=(1.05, 1), loc="upper left", borderaxespad=0.0)

        ax.xaxis.set_major_locator(MultipleLocator(1.0))
        ax.xaxis.set_minor_locator(MultipleLocator(0.25))
        ax.yaxis.set_major_locator(MultipleLocator(1.0))
        ax.yaxis.set_minor_locator(MultipleLocator(0.25))

        # finish figure by calling method with common figure functions
        ViewBSplineFigure(self)
Пример #4
0
 def test_004_curve_basis_degree_zero_seven_knots(self):
     knot_vector = (0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)
     degree = 0  # constant
     coef = (0.0, 1.0, 0.0, 0.0, 0.0, 0.0)
     N1_p0 = bsp.Curve(knot_vector, coef, degree)
     result = N1_p0.is_valid()
     self.assertTrue(result)
     tmin, tmax, npts = knot_vector[0], knot_vector[-1], 13
     t = np.linspace(tmin, tmax, npts, endpoint=True)
     y = N1_p0.evaluate(t)
     y_known = (0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                0.0)
     self.assertTrue(self.same(y_known, y))
Пример #5
0
    def test_003_curve_basis_recover_bezier_linear(self):
        knot_vector = (0.0, 0.0, 1.0, 1.0)
        degree = 1  # linear

        coef_N0_p1 = (1.0, 0.0)
        N0_p1 = bsp.Curve(knot_vector, coef_N0_p1, degree)
        result = N0_p1.is_valid()
        self.assertTrue(result)
        tmin, tmax, npts = 0.0, 1.0, 5
        t = np.linspace(tmin, tmax, npts, endpoint=True)
        y = N0_p1.evaluate(t)
        y_known = (1.0, 0.75, 0.5, 0.25, 0.0)
        self.assertTrue(self.same(y_known, y))

        coef_N1_p1 = (0.0, 1.0)
        N1_p1 = bsp.Curve(knot_vector, coef_N1_p1, degree)
        result = N1_p1.is_valid()
        self.assertTrue(result)
        # tmin, tmax, npts = 0.0, 1.0, 5
        t = np.linspace(tmin, tmax, npts, endpoint=True)
        y = N1_p1.evaluate(t)
        y_known = (0.0, 0.25, 0.5, 0.75, 1.0)
        self.assertTrue(self.same(y_known, y))
Пример #6
0
 def test_000_curve_initialization(self):
     knot_vector = (0.0, 1.0)
     coef = (1.0, )
     degree = 0
     C = bsp.Curve(knot_vector, coef, degree)
     self.assertIsInstance(C, bsp.Curve)
Пример #7
0
    def test_101_Bingol_2D_curve(self):
        """See
        https://nurbs-python.readthedocs.io/en/latest/visualization.html#curves
        """

        # knot vector
        KV = (0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.0, 1.0, 1.0,
              1.0)
        DEGREE = 4  # quadratic
        NBI = 1  # number of bisection intervals per knot span
        # NCP = 9  # number of control points

        knots_lhs = KV[0:-1]  # left-hand-side knot values
        knots_rhs = KV[1:]  # right-hand-side knot values
        knot_spans = np.array(knots_rhs) - np.array(knots_lhs)
        dt = knot_spans / (2**NBI)
        assert all([dti >= 0
                    for dti in dt]), "Error: knot vector is decreasing."

        num_knots = len(KV)
        t = [
            knots_lhs[k] + j * dt[k] for k in np.arange(num_knots - 1)
            for j in np.arange(2**NBI)
        ]
        t.append(KV[-1])
        t = np.array(t)

        COEF = (
            (5.0, 10.0),
            (15.0, 25.0),
            (30.0, 30.0),
            (45.0, 5.0),
            (55.0, 5.0),
            (70.0, 40.0),
            (60.0, 60.0),
            (35.0, 60.0),
            (20.0, 40.0),
        )
        NSD = len(COEF[0])  # number of space dimensions

        C = bsp.Curve(KV, COEF, DEGREE)
        result = C.is_valid()
        self.assertTrue(result)
        y = C.evaluate(t)
        # retain only non-repeated points at begnning and end
        # (which drops redundant points and beginning and end)
        y = y[2**NBI * DEGREE:-(2**NBI * DEGREE)]

        P_known = (
            (5.0, 10.0),
            (21.809895833333336, 24.60503472222222),
            (33.95833333333333, 20.347222222222218),
            (42.94270833333333, 11.692708333333336),
            (49.79166666666665, 7.84722222222222),
            (55.9157986111111, 12.174479166666664),
            (61.527777777777786, 23.61111111111111),
            (64.11458333333333, 38.29427083333332),
            (59.8611111111111, 51.31944444444444),
            (45.4079861111111, 57.37413194444444),
            (20.0, 40.0),
        )  # from geomdl at curve.delta = 1./11., thus 11 evaluation points
        # The 11 evaluation points bisects the five elements plus the endpoint
        # will correspond to NBI=1 (number of bisection intervals) used here.

        for i in np.arange(NSD):
            P_known_e = [e[i] for e in P_known]  # e is evaluation point
            self.assertTrue(self.same(P_known_e, y[:, i]))
Пример #8
0
    def test_100_curve_basis_quadratic_eight_knots(self):
        """Known example from NURBS Book, Piegl and Tiller, Ex2.2, Fig 2.6 page 55."""
        KV = tuple(map(float,
                       [0, 0, 0, 1, 2, 3, 4, 4, 5, 5, 5]))  # knot vector
        DEGREE = 2  # quadratic
        NBI = 3  # number of bisection intervals per knot span
        NCP = 8  # number of control points

        knots_lhs = KV[0:-1]  # left-hand-side knot values
        knots_rhs = KV[1:]  # right-hand-side knot values
        knot_spans = np.array(knots_rhs) - np.array(knots_lhs)
        dt = knot_spans / (2**NBI)
        assert all([dti >= 0
                    for dti in dt]), "Error: knot vector is decreasing."

        num_knots = len(KV)
        t = [
            knots_lhs[k] + j * dt[k] for k in np.arange(num_knots - 1)
            for j in np.arange(2**NBI)
        ]
        t.append(KV[-1])
        t = np.array(t)

        N_calc = []  # basis functions calculated by bsp.Curve

        for i in np.arange(NCP):

            coef = np.zeros(NCP)
            coef[i] = 1.0

            C = bsp.Curve(KV, coef, DEGREE)

            if C.is_valid():
                y = C.evaluate(t)
                N_calc.append(y)

        N_known = np.zeros((NCP, t.size), dtype=t.dtype)

        # Citation to give credit for Pythonic test implementation on knot intervals:
        # Roberto Agromayor (RoberAgro) Ph.D. candidate in turbomachinery design and
        # optimization at the Norwegian University of Science and Technology (NTNU)
        # https://github.com/RoberAgro/nurbspy/blob/master/tests/test_nurbs_basis_functions.py

        for j, t in enumerate(t):
            N0_p2 = (1 - t)**2 * (0 <= t < 1)
            N1_p2 = (2 * t -
                     3 / 2 * t**2) * (0 <= t < 1) + (1 / 2 *
                                                     (2 - t)**2) * (1 <= t < 2)
            N2_p2 = ((1 / 2 * t**2) * (0 <= t < 1) + (-3 / 2 + 3 * t - t**2) *
                     (1 <= t < 2) + (1 / 2 * (3 - t)**2) * (2 <= t < 3))
            N3_p2 = ((1 / 2 *
                      (t - 1)**2) * (1 <= t < 2) + (-11 / 2 + 5 * t - t**2) *
                     (2 <= t < 3) + (1 / 2 * (4 - t)**2) * (3 <= t < 4))
            N4_p2 = (1 / 2 *
                     (t - 2)**2) * (2 <= t < 3) + (-16 + 10 * t -
                                                   3 / 2 * t**2) * (3 <= t < 4)
            N5_p2 = (t - 3)**2 * (3 <= t < 4) + (5 - t)**2 * (4 <= t < 5)
            N6_p2 = (2 * (t - 4) * (5 - t)) * (4 <= t < 5)
            N7_p2 = (t - 4)**2 * (4 <= t <= 5)
            N_known[:, j] = np.asarray(
                [N0_p2, N1_p2, N2_p2, N3_p2, N4_p2, N5_p2, N6_p2, N7_p2])
Пример #9
0
    def __init__(
        self,
        sample_points: Union[List[float], Tuple[float], ndarray],
        degree: int = 0,
        verbose: bool = False,
        sample_time_method: str = "chord",
        knot_method: str = "average",
    ):
        """Creates a B-Spline curve based on fits to sample_points on curve.
        Sample points are either interpolated (default) or approximated (to come).

        Args:
            sample_points (ArrayLike[float]): [[a0, a1, a2, ... am], [b0, b1, b2, ... bm],
                [c0, c1, c2, ... cm]] composed of (m+1) samples points on the curve
                used to generate a fitted B-sline curve.  Coordinate measurements
                (a, b, c) are made in the (x, y, z) directions.
            degree (int >= 0): B-spline polynomial degree
            verbose (bool): prints additional information, default False
            sample_time_method:
                "chord": default, standard method to determine sample times
                "centripetal": (optional), suited for data with sharp turns
            knot_method:
                "average": default, recommended method for placing knots
                "equal": (optional), not recommended, can lead to a singular matrix,
                implemented herein for unit tests and testing purposes only.
        """
        if not isinstance(sample_points, (list, tuple, ndarray)):
            raise TypeError(
                "Error: sample points must be a list, tuple, or ndarray.")

        if degree < 0:
            raise ValueError("Error: degree must be non-negative.")

        if sample_time_method not in ("chord", "centripetal"):
            raise ValueError(
                "Error: sample_time_method must be 'chord' or 'centripetal'")

        if knot_method not in ("average", "equal"):
            raise ValueError("Error: knot_method must be 'average' or 'equal'")

        self.samples = np.asarray(sample_points)
        self.n_samples = len(self.samples)  # (m + 1)
        self._m = self.n_samples - 1  # m index
        self.n_control_points = (
            self.n_samples)  # special case n = m, number of control points

        # self.verbose = verbose
        self.valid = False
        self._bspline = None

        # Piegl 1997 page 364-365, chord length method or centripetal method
        chord_lengths = np.zeros(self.n_samples)

        for k in range(1, self.n_samples):
            chord_length = np.linalg.norm(self.samples[k] -
                                          self.samples[k - 1])
            if sample_time_method == "chord":
                chord_lengths[
                    k] = chord_length  # norm from Eq. (9.5) Piegl 1997
            else:  # "centripetal"
                chord_lengths[k] = np.sqrt(chord_length)  # sqrt norm Eq. (9.6)

        total_chord_length = sum(chord_lengths)

        self._sample_times = np.zeros(self.n_samples)
        for k in range(1, self.n_samples):
            self._sample_times[k] = (self._sample_times[k - 1] +
                                     chord_lengths[k] / total_chord_length
                                     )  # Eq. (9.5) or (9.6) Piegl 1997

        # averaging knots from sample times, Piegl 1997, page 365, Eq. (9.8)
        self._n_knots = degree + 1 + self.n_samples  # (kappa + 1)
        self._kappa = self._n_knots - 1
        self._knot_vector = np.zeros(self._n_knots)
        if knot_method == "average":
            # averaging knots from sample times
            for j in range(1, self._m - degree + 1):  # +1 for Python 0-index
                # _phi_high = degree - 1 + j
                _phi_high = degree + j  # add +1 back in to account for Python 0-index
                _knot_subspan_average = (1 / degree) * sum(
                    self._sample_times[j:_phi_high])
                self._knot_vector[degree + j] = _knot_subspan_average
        else:
            # knot_method is "equal"
            for j in range(1, self._m - degree + 1):  # +1 for Python 0-index
                self._knot_vector[degree + j] = j / (self._m - degree + 1)
        # append end of knot vector with 1.0 repeated (degree + 1) times
        _kappa_minus_p = self._kappa - degree
        self._knot_vector[_kappa_minus_p:self._kappa +
                          1] = 1.0  # Python 0-index

        # matrix A u = f -> (notation) N u = f
        self._sample_basis_matrix = []  # (m x 1) by (n x 1) coefficient matrix
        for column in range(self.n_control_points):
            coef = np.zeros(self.n_control_points)
            coef[column] = 1.0

            # build the B-spline basis functions column-by-column
            _bspline_basis_function = bsp.Curve(self.knot_vector, coef, degree)

            if _bspline_basis_function.is_valid():
                _y = _bspline_basis_function.evaluate(self.sample_times)
                self._sample_basis_matrix.append(_y)

        # now transpose the N matrix, since we filled column-wise
        self._sample_basis_matrix = np.transpose(self._sample_basis_matrix)

        # self.control_points = np.linalg.solve(self._N, sample_points)
        self._control_points = np.linalg.solve(self._sample_basis_matrix,
                                               self.samples)

        self.valid = True  # if we come to the end of __init__, all is valid
Пример #10
0
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        COEF = kwargs.get(
            "coefficients", None
        )  # None is basis, not None is curve, surface, or volume
        assert COEF is not None

        # show/hide control point locations
        _control_points_shown = kwargs.get("control_points_shown", False)

        # show/hide knot locations
        _knots_shown = kwargs.get("knots_shown", False)
        _evaluated_knots = []
        if _knots_shown:
            _knots_t = np.unique(self.knot_vector_t)

        # show/hide sample points in case of a B-spline fit
        _samples_shown = kwargs.get("samples_shown", False)

        # build up B-spline basis functions, multiplied by coefficients
        _NSD = len(COEF[0])  # number of space dimensions
        assert _NSD == 2  # only 2D curves implemented for now, do 3D later

        for i in np.arange(_NSD):

            coef = np.array(COEF)[:, i]

            _B = bsp.Curve(self.knot_vector_t, coef, self.degree_t)

            if _B.is_valid():
                _y = _B.evaluate(self.evaluation_times)
                self.evaluated_curve.append(_y)

                if _knots_shown:
                    _y = _B.evaluate(_knots_t)
                    _evaluated_knots.append(_y)

        # plot B-spline curve, assume 2D for now
        self.fig = plt.figure(dpi=self.dpi)
        ax = self.fig.gca()

        if _control_points_shown:
            # plot control points
            _cp_x = np.array(COEF)[:, 0]  # control points x-coordinates
            _cp_y = np.array(COEF)[:, 1]  # control points y-coordinates

            ax.plot(
                _cp_x,
                _cp_y,
                color="red",
                linewidth=1,
                alpha=0.5,
                marker="o",
                markerfacecolor="white",
                markersize=9,
                linestyle="dashed",
            )

        if _samples_shown:
            _samples_x = np.array(kwargs.get("samples"))[:, 0]
            _samples_y = np.array(kwargs.get("samples"))[:, 1]
            ax.plot(
                _samples_x,
                _samples_y,
                color="darkorange",
                alpha=1.0,
                linestyle="none",
                linewidth="6",
                marker="+",
                markersize=20,
            )  # plus mark
            ax.plot(
                _samples_x,
                _samples_y,
                color="orange",
                linestyle="none",
                marker="D",
                markerfacecolor="none",
                markersize=14,
            )  # diamond border around plus mark

        ax.plot(
            self.evaluated_curve[0],
            self.evaluated_curve[1],
            color="navy",
            linestyle="solid",
            linewidth=3,
        )

        if _samples_shown:  # yet another layer for samples, small little dot atop curve
            _samples_x = np.array(kwargs.get("samples"))[:, 0]
            _samples_y = np.array(kwargs.get("samples"))[:, 1]
            ax.plot(
                _samples_x,
                _samples_y,
                color="darkorange",
                linestyle="none",
                marker=".",
                markersize=2,
            )  # dot

        if _knots_shown:
            for i, knot_num in enumerate(self.knot_vector_t):
                # plot only the first knot of any repeated knot
                if i == 0:
                    # print(f"first index {i}")
                    k = i  # first evaluated knot index
                    if self.latex:
                        _str = r"$\mathsf T_{0}$"
                    else:
                        # _str = r"$T_{0}$"
                        continue  # GitHub unit test doesn't like LaTex
                else:
                    if self.knot_vector_t[i] == self.knot_vector_t[i - 1]:
                        continue  # don't plot subsequently repeated knots

                    # print(f"subsequent index {i}")
                    k += 1  # next non-repeated knot index in evaluated knots

                    # _Ti = str(int(i + self.degree_t))
                    _Ti = str(int(i))
                    if self.latex:
                        _str = r"$\mathsf T_{" + _Ti + "}$"
                    else:
                        # _str = r"$T_{" + _Ti + "}$"
                        continue  # GitHub unit test doesn't like LaTex

                # print(_str)
                ax.plot(
                    _evaluated_knots[0][k],
                    _evaluated_knots[1][k],
                    color="white",
                    alpha=0.6,
                    marker="o",
                    markersize=14,
                    markeredgecolor="darkcyan",
                )  # background circle
                ax.plot(
                    _evaluated_knots[0][k],
                    _evaluated_knots[1][k],
                    color="black",
                    marker=_str,
                    markersize=9,
                    markeredgecolor="none",
                )  # knot number text

        # self.fig.patch.set_facecolor("whitesmoke")
        # ax.set_facecolor("lightgray")

        ax.set_xlabel(r"$x$")
        ax.set_ylabel(r"$y$")

        # finish figure by calling method with common figure functions
        ViewBSplineFigure(self)
Пример #11
0
# fig = plt.figure(figsize=plt.figaspect(1.0 / (len(knot_vector) - 1)), dpi=dpi)
fig = plt.figure(figsize=plt.figaspect(1.0 / max(knot_vectors[-1])), dpi=dpi)
ax = fig.gca()
# ax.grid()
ax.grid(True, which="major", linestyle="-")
ax.grid(True, which="minor", linestyle=":")

for i in np.arange(len(degrees)):

    knot_vector = knot_vectors[i]
    coef = coefs[i]
    degree = degrees[i]
    label = labels[i]

    N0_p = bsp.Curve(knot_vector, coef, degree)
    result = N0_p.is_valid()

    npts = int((knot_vector[-1] - knot_vector[0]) * (2**n_bisections) + 1)
    # tmin, tmax, npts = knot_vector[0], knot_vector[-1], 13
    tmin, tmax, npts = knot_vector[0], knot_vector[-1], npts

    t = np.linspace(tmin, tmax, npts, endpoint=True)
    y = N0_p.evaluate(t)
    ax.plot(
        t,
        y,
        "-",
        linewidth=2,
        label=label,
        linestyle=linestyles[np.remainder(i, len(linestyles))],