CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
_partitioner.pyx822 linesDownload Raw Back to tree
1"""Partition samples in the construction of a tree.
2
3This module contains the algorithms for moving sample indices to
4the left and right child node given a split determined by the
5splitting algorithm in `_splitter.pyx`.
6
7Partitioning is done in a way that is efficient for both dense data,
8and sparse data stored in a Compressed Sparse Column (CSC) format.
9"""
10# Authors: The scikit-learn developers
11# SPDX-License-Identifier: BSD-3-Clause
12
13from cython cimport final
14from libc.math cimport isnan, log2
15from libc.stdlib cimport qsort
16from libc.string cimport memcpy
17
18import numpy as np
19from scipy.sparse import issparse
20
21
22# Constant to switch between algorithm non zero value extract algorithm
23# in SparsePartitioner
24cdef float32_t EXTRACT_NNZ_SWITCH = 0.1
25
26# Allow for 32 bit float comparisons
27cdef float32_t INFINITY_32t = np.inf
28
29
30@final
31cdef class DensePartitioner:
32    """Partitioner specialized for dense data.
33
34    Note that this partitioner is agnostic to the splitting strategy (best vs. random).
35    """
36    def __init__(
37        self,
38        const float32_t[:, :] X,
39        intp_t[::1] samples,
40        float32_t[::1] feature_values,
41        const uint8_t[::1] missing_values_in_feature_mask,
42    ):
43        self.X = X
44        self.samples = samples
45        self.feature_values = feature_values
46        self.missing_values_in_feature_mask = missing_values_in_feature_mask
47
48    cdef inline void init_node_split(self, intp_t start, intp_t end) noexcept nogil:
49        """Initialize splitter at the beginning of node_split."""
50        self.start = start
51        self.end = end
52        self.n_missing = 0
53
54    cdef inline void sort_samples_and_feature_values(
55        self, intp_t current_feature
56    ) noexcept nogil:
57        """Simultaneously sort based on the feature_values.
58
59        Missing values are stored at the end of feature_values.
60        The number of missing values observed in feature_values is stored
61        in self.n_missing.
62        """
63        cdef:
64            intp_t i, current_end
65            float32_t[::1] feature_values = self.feature_values
66            const float32_t[:, :] X = self.X
67            intp_t[::1] samples = self.samples
68            intp_t n_missing = 0
69            const uint8_t[::1] missing_values_in_feature_mask = self.missing_values_in_feature_mask
70
71        # Sort samples along that feature; by copying the values into an array and
72        # sorting the array in a manner which utilizes the cache more effectively.
73        if missing_values_in_feature_mask is not None and missing_values_in_feature_mask[current_feature]:
74            i, current_end = self.start, self.end - 1
75            # Missing values are placed at the end and do not participate in the sorting.
76            while i <= current_end:
77                # Finds the right-most value that is not missing so that
78                # it can be swapped with missing values at its left.
79                if isnan(X[samples[current_end], current_feature]):
80                    n_missing += 1
81                    current_end -= 1
82                    continue
83
84                # X[samples[current_end], current_feature] is a non-missing value
85                if isnan(X[samples[i], current_feature]):
86                    samples[i], samples[current_end] = samples[current_end], samples[i]
87                    n_missing += 1
88                    current_end -= 1
89
90                feature_values[i] = X[samples[i], current_feature]
91                i += 1
92        else:
93            # When there are no missing values, we only need to copy the data into
94            # feature_values
95            for i in range(self.start, self.end):
96                feature_values[i] = X[samples[i], current_feature]
97
98        sort(&feature_values[self.start], &samples[self.start], self.end - self.start - n_missing)
99        self.n_missing = n_missing
100
101    cdef inline void find_min_max(
102        self,
103        intp_t current_feature,
104        float32_t* min_feature_value_out,
105        float32_t* max_feature_value_out,
106    ) noexcept nogil:
107        """Find the minimum and maximum value for current_feature.
108
109        Missing values are stored at the end of feature_values. The number of missing
110        values observed in feature_values is stored in self.n_missing.
111        """
112        cdef:
113            intp_t p, current_end
114            float32_t current_feature_value
115            const float32_t[:, :] X = self.X
116            intp_t[::1] samples = self.samples
117            float32_t min_feature_value = INFINITY_32t
118            float32_t max_feature_value = -INFINITY_32t
119            float32_t[::1] feature_values = self.feature_values
120            intp_t n_missing = 0
121            const uint8_t[::1] missing_values_in_feature_mask = self.missing_values_in_feature_mask
122
123        # We are copying the values into an array and finding min/max of the array in
124        # a manner which utilizes the cache more effectively. We need to also count
125        # the number of missing-values there are.
126        if missing_values_in_feature_mask is not None and missing_values_in_feature_mask[current_feature]:
127            p, current_end = self.start, self.end - 1
128            # Missing values are placed at the end and do not participate in the
129            # min/max calculation.
130            while p <= current_end:
131                # Finds the right-most value that is not missing so that
132                # it can be swapped with missing values towards its left.
133                if isnan(X[samples[current_end], current_feature]):
134                    n_missing += 1
135                    current_end -= 1
136                    continue
137
138                # X[samples[current_end], current_feature] is a non-missing value
139                if isnan(X[samples[p], current_feature]):
140                    samples[p], samples[current_end] = samples[current_end], samples[p]
141                    n_missing += 1
142                    current_end -= 1
143
144                current_feature_value = X[samples[p], current_feature]
145                feature_values[p] = current_feature_value
146                if current_feature_value < min_feature_value:
147                    min_feature_value = current_feature_value
148                elif current_feature_value > max_feature_value:
149                    max_feature_value = current_feature_value
150                p += 1
151        else:
152            min_feature_value = X[samples[self.start], current_feature]
153            max_feature_value = min_feature_value
154
155            feature_values[self.start] = min_feature_value
156            for p in range(self.start + 1, self.end):
157                current_feature_value = X[samples[p], current_feature]
158                feature_values[p] = current_feature_value
159
160                if current_feature_value < min_feature_value:
161                    min_feature_value = current_feature_value
162                elif current_feature_value > max_feature_value:
163                    max_feature_value = current_feature_value
164
165        min_feature_value_out[0] = min_feature_value
166        max_feature_value_out[0] = max_feature_value
167        self.n_missing = n_missing
168
169    cdef inline void next_p(self, intp_t* p_prev, intp_t* p) noexcept nogil:
170        """Compute the next p_prev and p for iterating over feature values.
171
172        The missing values are not included when iterating through the feature values.
173        """
174        cdef:
175            float32_t[::1] feature_values = self.feature_values
176            intp_t end_non_missing = self.end - self.n_missing
177
178        while (
179            p[0] + 1 < end_non_missing and
180            feature_values[p[0] + 1] <= feature_values[p[0]] + FEATURE_THRESHOLD
181        ):
182            p[0] += 1
183
184        p_prev[0] = p[0]
185
186        # By adding 1, we have
187        # (feature_values[p] >= end) or (feature_values[p] > feature_values[p - 1])
188        p[0] += 1
189
190    cdef inline intp_t partition_samples(
191        self,
192        float64_t current_threshold
193    ) noexcept nogil:
194        """Partition samples for feature_values at the current_threshold."""
195        cdef:
196            intp_t p = self.start
197            intp_t partition_end = self.end - self.n_missing
198            intp_t[::1] samples = self.samples
199            float32_t[::1] feature_values = self.feature_values
200
201        while p < partition_end:
202            if feature_values[p] <= current_threshold:
203                p += 1
204            else:
205                partition_end -= 1
206
207                feature_values[p], feature_values[partition_end] = (
208                    feature_values[partition_end], feature_values[p]
209                )
210                samples[p], samples[partition_end] = samples[partition_end], samples[p]
211
212        return partition_end
213
214    cdef inline void partition_samples_final(
215        self,
216        intp_t best_pos,
217        float64_t best_threshold,
218        intp_t best_feature,
219        intp_t best_n_missing,
220    ) noexcept nogil:
221        """Partition samples for X at the best_threshold and best_feature.
222
223        If missing values are present, this method partitions `samples`
224        so that the `best_n_missing` missing values' indices are in the
225        right-most end of `samples`, that is `samples[end_non_missing:end]`.
226        """
227        cdef:
228            # Local invariance: start <= p <= partition_end <= end
229            intp_t start = self.start
230            intp_t p = start
231            intp_t end = self.end - 1
232            intp_t partition_end = end - best_n_missing
233            intp_t[::1] samples = self.samples
234            const float32_t[:, :] X = self.X
235            float32_t current_value
236
237        if best_n_missing != 0:
238            # Move samples with missing values to the end while partitioning the
239            # non-missing samples
240            while p < partition_end:
241                # Keep samples with missing values at the end
242                if isnan(X[samples[end], best_feature]):
243                    end -= 1
244                    continue
245
246                # Swap sample with missing values with the sample at the end
247                current_value = X[samples[p], best_feature]
248                if isnan(current_value):
249                    samples[p], samples[end] = samples[end], samples[p]
250                    end -= 1
251
252                    # The swapped sample at the end is always a non-missing value, so
253                    # we can continue the algorithm without checking for missingness.
254                    current_value = X[samples[p], best_feature]
255
256                # Partition the non-missing samples
257                if current_value <= best_threshold:
258                    p += 1
259                else:
260                    samples[p], samples[partition_end] = samples[partition_end], samples[p]
261                    partition_end -= 1
262        else:
263            # Partitioning routine when there are no missing values
264            while p < partition_end:
265                if X[samples[p], best_feature] <= best_threshold:
266                    p += 1
267                else:
268                    samples[p], samples[partition_end] = samples[partition_end], samples[p]
269                    partition_end -= 1
270
271
272@final
273cdef class SparsePartitioner:
274    """Partitioner specialized for sparse CSC data.
275
276    Note that this partitioner is agnostic to the splitting strategy (best vs. random).
277    """
278    def __init__(
279        self,
280        object X,
281        intp_t[::1] samples,
282        intp_t n_samples,
283        float32_t[::1] feature_values,
284        const uint8_t[::1] missing_values_in_feature_mask,
285    ):
286        if not (issparse(X) and X.format == "csc"):
287            raise ValueError("X should be in csc format")
288
289        self.samples = samples
290        self.feature_values = feature_values
291
292        # Initialize X
293        cdef intp_t n_total_samples = X.shape[0]
294
295        self.X_data = X.data
296        self.X_indices = X.indices
297        self.X_indptr = X.indptr
298        self.n_total_samples = n_total_samples
299
300        # Initialize auxiliary array used to perform split
301        self.index_to_samples = np.full(n_total_samples, fill_value=-1, dtype=np.intp)
302        self.sorted_samples = np.empty(n_samples, dtype=np.intp)
303
304        cdef intp_t p
305        for p in range(n_samples):
306            self.index_to_samples[samples[p]] = p
307
308        self.missing_values_in_feature_mask = missing_values_in_feature_mask
309
310    cdef inline void init_node_split(self, intp_t start, intp_t end) noexcept nogil:
311        """Initialize splitter at the beginning of node_split."""
312        self.start = start
313        self.end = end
314        self.is_samples_sorted = 0
315        self.n_missing = 0
316
317    cdef inline void sort_samples_and_feature_values(
318        self,
319        intp_t current_feature
320    ) noexcept nogil:
321        """Simultaneously sort based on the feature_values."""
322        cdef:
323            float32_t[::1] feature_values = self.feature_values
324            intp_t[::1] index_to_samples = self.index_to_samples
325            intp_t[::1] samples = self.samples
326
327        self.extract_nnz(current_feature)
328        # Sort the positive and negative parts of `feature_values`
329        sort(&feature_values[self.start], &samples[self.start], self.end_negative - self.start)
330        if self.start_positive < self.end:
331            sort(
332                &feature_values[self.start_positive],
333                &samples[self.start_positive],
334                self.end - self.start_positive
335            )
336
337        # Update index_to_samples to take into account the sort
338        for p in range(self.start, self.end_negative):
339            index_to_samples[samples[p]] = p
340        for p in range(self.start_positive, self.end):
341            index_to_samples[samples[p]] = p
342
343        # Add one or two zeros in feature_values, if there is any
344        if self.end_negative < self.start_positive:
345            self.start_positive -= 1
346            feature_values[self.start_positive] = 0.
347
348            if self.end_negative != self.start_positive:
349                feature_values[self.end_negative] = 0.
350                self.end_negative += 1
351
352        # XXX: When sparse supports missing values, this should be set to the
353        # number of missing values for current_feature
354        self.n_missing = 0
355
356    cdef inline void find_min_max(
357        self,
358        intp_t current_feature,
359        float32_t* min_feature_value_out,
360        float32_t* max_feature_value_out,
361    ) noexcept nogil:
362        """Find the minimum and maximum value for current_feature."""
363        cdef:
364            intp_t p
365            float32_t current_feature_value, min_feature_value, max_feature_value
366            float32_t[::1] feature_values = self.feature_values
367
368        self.extract_nnz(current_feature)
369
370        if self.end_negative != self.start_positive:
371            # There is a zero
372            min_feature_value = 0
373            max_feature_value = 0
374        else:
375            min_feature_value = feature_values[self.start]
376            max_feature_value = min_feature_value
377
378        # Find min, max in feature_values[start:end_negative]
379        for p in range(self.start, self.end_negative):
380            current_feature_value = feature_values[p]
381
382            if current_feature_value < min_feature_value:
383                min_feature_value = current_feature_value
384            elif current_feature_value > max_feature_value:
385                max_feature_value = current_feature_value
386
387        # Update min, max given feature_values[start_positive:end]
388        for p in range(self.start_positive, self.end):
389            current_feature_value = feature_values[p]
390
391            if current_feature_value < min_feature_value:
392                min_feature_value = current_feature_value
393            elif current_feature_value > max_feature_value:
394                max_feature_value = current_feature_value
395
396        min_feature_value_out[0] = min_feature_value
397        max_feature_value_out[0] = max_feature_value
398
399    cdef inline void next_p(self, intp_t* p_prev, intp_t* p) noexcept nogil:
400        """Compute the next p_prev and p for iterating over feature values."""
401        cdef:
402            intp_t p_next
403            float32_t[::1] feature_values = self.feature_values
404
405        if p[0] + 1 != self.end_negative:
406            p_next = p[0] + 1
407        else:
408            p_next = self.start_positive
409
410        while (p_next < self.end and
411                feature_values[p_next] <= feature_values[p[0]] + FEATURE_THRESHOLD):
412            p[0] = p_next
413            if p[0] + 1 != self.end_negative:
414                p_next = p[0] + 1
415            else:
416                p_next = self.start_positive
417
418        p_prev[0] = p[0]
419        p[0] = p_next
420
421    cdef inline intp_t partition_samples(
422        self,
423        float64_t current_threshold
424    ) noexcept nogil:
425        """Partition samples for feature_values at the current_threshold."""
426        return self._partition(current_threshold, self.start_positive)
427
428    cdef inline void partition_samples_final(
429        self,
430        intp_t best_pos,
431        float64_t best_threshold,
432        intp_t best_feature,
433        intp_t n_missing,
434    ) noexcept nogil:
435        """Partition samples for X at the best_threshold and best_feature."""
436        self.extract_nnz(best_feature)
437        self._partition(best_threshold, best_pos)
438
439    cdef inline intp_t _partition(self, float64_t threshold, intp_t zero_pos) noexcept nogil:
440        """Partition samples[start:end] based on threshold."""
441        cdef:
442            intp_t p, partition_end
443            intp_t[::1] index_to_samples = self.index_to_samples
444            float32_t[::1] feature_values = self.feature_values
445            intp_t[::1] samples = self.samples
446
447        if threshold < 0.:
448            p = self.start
449            partition_end = self.end_negative
450        elif threshold > 0.:
451            p = self.start_positive
452            partition_end = self.end
453        else:
454            # Data are already split
455            return zero_pos
456
457        while p < partition_end:
458            if feature_values[p] <= threshold:
459                p += 1
460
461            else:
462                partition_end -= 1
463
464                feature_values[p], feature_values[partition_end] = (
465                    feature_values[partition_end], feature_values[p]
466                )
467                sparse_swap(index_to_samples, samples, p, partition_end)
468
469        return partition_end
470
471    cdef inline void extract_nnz(self, intp_t feature) noexcept nogil:
472        """Extract and partition values for a given feature.
473
474        The extracted values are partitioned between negative values
475        feature_values[start:end_negative[0]] and positive values
476        feature_values[start_positive[0]:end].
477        The samples and index_to_samples are modified according to this
478        partition.
479
480        The extraction corresponds to the intersection between the arrays
481        X_indices[indptr_start:indptr_end] and samples[start:end].
482        This is done efficiently using either an index_to_samples based approach
483        or binary search based approach.
484
485        Parameters
486        ----------
487        feature : intp_t,
488            Index of the feature we want to extract non zero value.
489        """
490        cdef intp_t[::1] samples = self.samples
491        cdef float32_t[::1] feature_values = self.feature_values
492        cdef intp_t indptr_start = self.X_indptr[feature],
493        cdef intp_t indptr_end = self.X_indptr[feature + 1]
494        cdef intp_t n_indices = <intp_t>(indptr_end - indptr_start)
495        cdef intp_t n_samples = self.end - self.start
496        cdef intp_t[::1] index_to_samples = self.index_to_samples
497        cdef intp_t[::1] sorted_samples = self.sorted_samples
498        cdef const int32_t[::1] X_indices = self.X_indices
499        cdef const float32_t[::1] X_data = self.X_data
500
501        # Use binary search if n_samples * log(n_indices) <
502        # n_indices and index_to_samples approach otherwise.
503        # O(n_samples * log(n_indices)) is the running time of binary
504        # search and O(n_indices) is the running time of index_to_samples
505        # approach.
506        if ((1 - self.is_samples_sorted) * n_samples * log2(n_samples) +
507                n_samples * log2(n_indices) < EXTRACT_NNZ_SWITCH * n_indices):
508            extract_nnz_binary_search(X_indices, X_data,
509                                      indptr_start, indptr_end,
510                                      samples, self.start, self.end,
511                                      index_to_samples,
512                                      feature_values,
513                                      &self.end_negative, &self.start_positive,
514                                      sorted_samples, &self.is_samples_sorted)
515
516        # Using an index to samples  technique to extract non zero values
517        # index_to_samples is a mapping from X_indices to samples
518        else:
519            extract_nnz_index_to_samples(X_indices, X_data,
520                                         indptr_start, indptr_end,
521                                         samples, self.start, self.end,
522                                         index_to_samples,
523                                         feature_values,
524                                         &self.end_negative, &self.start_positive)
525
526
527cdef int compare_SIZE_t(const void* a, const void* b) noexcept nogil:
528    """Comparison function for sort.
529
530    This must return an `int` as it is used by stdlib's qsort, which expects
531    an `int` return value.
532    """
533    return <int>((<intp_t*>a)[0] - (<intp_t*>b)[0])
534
535
536cdef inline void binary_search(const int32_t[::1] sorted_array,
537                               int32_t start, int32_t end,
538                               intp_t value, intp_t* index,
539                               int32_t* new_start) noexcept nogil:
540    """Return the index of value in the sorted array.
541
542    If not found, return -1. new_start is the last pivot + 1
543    """
544    cdef int32_t pivot
545    index[0] = -1
546    while start < end:
547        pivot = start + (end - start) / 2
548
549        if sorted_array[pivot] == value:
550            index[0] = pivot
551            start = pivot + 1
552            break
553
554        if sorted_array[pivot] < value:
555            start = pivot + 1
556        else:
557            end = pivot
558    new_start[0] = start
559
560
561cdef inline void extract_nnz_index_to_samples(const int32_t[::1] X_indices,
562                                              const float32_t[::1] X_data,
563                                              int32_t indptr_start,
564                                              int32_t indptr_end,
565                                              intp_t[::1] samples,
566                                              intp_t start,
567                                              intp_t end,
568                                              intp_t[::1] index_to_samples,
569                                              float32_t[::1] feature_values,
570                                              intp_t* end_negative,
571                                              intp_t* start_positive) noexcept nogil:
572    """Extract and partition values for a feature using index_to_samples.
573
574    Complexity is O(indptr_end - indptr_start).
575    """
576    cdef int32_t k
577    cdef intp_t index
578    cdef intp_t end_negative_ = start
579    cdef intp_t start_positive_ = end
580
581    for k in range(indptr_start, indptr_end):
582        if start <= index_to_samples[X_indices[k]] < end:
583            if X_data[k] > 0:
584                start_positive_ -= 1
585                feature_values[start_positive_] = X_data[k]
586                index = index_to_samples[X_indices[k]]
587                sparse_swap(index_to_samples, samples, index, start_positive_)
588
589            elif X_data[k] < 0:
590                feature_values[end_negative_] = X_data[k]
591                index = index_to_samples[X_indices[k]]
592                sparse_swap(index_to_samples, samples, index, end_negative_)
593                end_negative_ += 1
594
595    # Returned values
596    end_negative[0] = end_negative_
597    start_positive[0] = start_positive_
598
599
600cdef inline void extract_nnz_binary_search(const int32_t[::1] X_indices,
601                                           const float32_t[::1] X_data,
602                                           int32_t indptr_start,
603                                           int32_t indptr_end,
604                                           intp_t[::1] samples,
605                                           intp_t start,
606                                           intp_t end,
607                                           intp_t[::1] index_to_samples,
608                                           float32_t[::1] feature_values,
609                                           intp_t* end_negative,
610                                           intp_t* start_positive,
611                                           intp_t[::1] sorted_samples,
612                                           bint* is_samples_sorted) noexcept nogil:
613    """Extract and partition values for a given feature using binary search.
614
615    If n_samples = end - start and n_indices = indptr_end - indptr_start,
616    the complexity is
617
618        O((1 - is_samples_sorted[0]) * n_samples * log(n_samples) +
619          n_samples * log(n_indices)).
620    """
621    cdef intp_t n_samples
622
623    if not is_samples_sorted[0]:
624        n_samples = end - start
625        memcpy(&sorted_samples[start], &samples[start],
626               n_samples * sizeof(intp_t))
627        qsort(&sorted_samples[start], n_samples, sizeof(intp_t),
628              compare_SIZE_t)
629        is_samples_sorted[0] = 1
630
631    while (indptr_start < indptr_end and
632           sorted_samples[start] > X_indices[indptr_start]):
633        indptr_start += 1
634
635    while (indptr_start < indptr_end and
636           sorted_samples[end - 1] < X_indices[indptr_end - 1]):
637        indptr_end -= 1
638
639    cdef intp_t p = start
640    cdef intp_t index
641    cdef intp_t k
642    cdef intp_t end_negative_ = start
643    cdef intp_t start_positive_ = end
644
645    while (p < end and indptr_start < indptr_end):
646        # Find index of sorted_samples[p] in X_indices
647        binary_search(X_indices, indptr_start, indptr_end,
648                      sorted_samples[p], &k, &indptr_start)
649
650        if k != -1:
651            # If k != -1, we have found a non zero value
652
653            if X_data[k] > 0:
654                start_positive_ -= 1
655                feature_values[start_positive_] = X_data[k]
656                index = index_to_samples[X_indices[k]]
657                sparse_swap(index_to_samples, samples, index, start_positive_)
658
659            elif X_data[k] < 0:
660                feature_values[end_negative_] = X_data[k]
661                index = index_to_samples[X_indices[k]]
662                sparse_swap(index_to_samples, samples, index, end_negative_)
663                end_negative_ += 1
664        p += 1
665
666    # Returned values
667    end_negative[0] = end_negative_
668    start_positive[0] = start_positive_
669
670
671cdef inline void sparse_swap(intp_t[::1] index_to_samples, intp_t[::1] samples,
672                             intp_t pos_1, intp_t pos_2) noexcept nogil:
673    """Swap sample pos_1 and pos_2 preserving sparse invariant."""
674    samples[pos_1], samples[pos_2] = samples[pos_2], samples[pos_1]
675    index_to_samples[samples[pos_1]] = pos_1
676    index_to_samples[samples[pos_2]] = pos_2
677
678
679cdef inline void shift_missing_values_to_left_if_required(
680    SplitRecord* best,
681    intp_t[::1] samples,
682    intp_t end,
683) noexcept nogil:
684    """Shift missing value sample indices to the left of the split if required.
685
686    Note: this should always be called at the very end because it will
687    move samples around, thereby affecting the criterion.
688    This affects the computation of the children impurity, which affects
689    the computation of the next node.
690    """
691    cdef intp_t i, p, current_end
692    # The partitioner partitions the data such that the missing values are in
693    # samples[-n_missing:] for the criterion to consume. If the missing values
694    # are going to the right node, then the missing values are already in the
695    # correct position. If the missing values go left, then we move the missing
696    # values to samples[best.pos:best.pos+n_missing] and update `best.pos`.
697    if best.n_missing > 0 and best.missing_go_to_left:
698        for p in range(best.n_missing):
699            i = best.pos + p
700            current_end = end - 1 - p
701            samples[i], samples[current_end] = samples[current_end], samples[i]
702        best.pos += best.n_missing
703
704
705def _py_sort(float32_t[::1] feature_values, intp_t[::1] samples, intp_t n):
706    """Used for testing sort."""
707    sort(&feature_values[0], &samples[0], n)
708
709
710# Sort n-element arrays pointed to by feature_values and samples, simultaneously,
711# by the values in feature_values. Algorithm: Introsort (Musser, SP&E, 1997).
712cdef inline void sort(float32_t* feature_values, intp_t* samples, intp_t n) noexcept nogil:
713    if n == 0:
714        return
715    cdef intp_t maxd = 2 * <intp_t>log2(n)
716    introsort(feature_values, samples, n, maxd)
717
718
719cdef inline void swap(float32_t* feature_values, intp_t* samples,
720                      intp_t i, intp_t j) noexcept nogil:
721    # Helper for sort
722    feature_values[i], feature_values[j] = feature_values[j], feature_values[i]
723    samples[i], samples[j] = samples[j], samples[i]
724
725
726cdef inline float32_t median3(float32_t* feature_values, intp_t n) noexcept nogil:
727    # Median of three pivot selection, after Bentley and McIlroy (1993).
728    # Engineering a sort function. SP&E. Requires 8/3 comparisons on average.
729    cdef float32_t a = feature_values[0], b = feature_values[n / 2], c = feature_values[n - 1]
730    if a < b:
731        if b < c:
732            return b
733        elif a < c:
734            return c
735        else:
736            return a
737    elif b < c:
738        if a < c:
739            return a
740        else:
741            return c
742    else:
743        return b
744
745
746# Introsort with median of 3 pivot selection and 3-way partition function
747# (robust to repeated elements, e.g. lots of zero features).
748cdef void introsort(float32_t* feature_values, intp_t *samples,
749                    intp_t n, intp_t maxd) noexcept nogil:
750    cdef float32_t pivot
751    cdef intp_t i, l, r
752
753    while n > 1:
754        if maxd <= 0:   # max depth limit exceeded ("gone quadratic")
755            heapsort(feature_values, samples, n)
756            return
757        maxd -= 1
758
759        pivot = median3(feature_values, n)
760
761        # Three-way partition.
762        i = l = 0
763        r = n
764        while i < r:
765            if feature_values[i] < pivot:
766                swap(feature_values, samples, i, l)
767                i += 1
768                l += 1
769            elif feature_values[i] > pivot:
770                r -= 1
771                swap(feature_values, samples, i, r)
772            else:
773                i += 1
774
775        introsort(feature_values, samples, l, maxd)
776        feature_values += r
777        samples += r
778        n -= r
779
780
781cdef inline void sift_down(float32_t* feature_values, intp_t* samples,
782                           intp_t start, intp_t end) noexcept nogil:
783    # Restore heap order in feature_values[start:end] by moving the max element to start.
784    cdef intp_t child, maxind, root
785
786    root = start
787    while True:
788        child = root * 2 + 1
789
790        # find max of root, left child, right child
791        maxind = root
792        if child < end and feature_values[maxind] < feature_values[child]:
793            maxind = child
794        if child + 1 < end and feature_values[maxind] < feature_values[child + 1]:
795            maxind = child + 1
796
797        if maxind == root:
798            break
799        else:
800            swap(feature_values, samples, root, maxind)
801            root = maxind
802
803
804cdef void heapsort(float32_t* feature_values, intp_t* samples, intp_t n) noexcept nogil:
805    cdef intp_t start, end
806
807    # heapify
808    start = (n - 2) / 2
809    end = n
810    while True:
811        sift_down(feature_values, samples, start, end)
812        if start == 0:
813            break
814        start -= 1
815
816    # sort by shrinking the heap, putting the max element immediately after it
817    end = n - 1
818    while end > 0:
819        swap(feature_values, samples, 0, end)
820        sift_down(feature_values, samples, 0, end)
821        end = end - 1
822 
Aluode/PerceptionLabPortable · CoolFace