// Copyright 2024 DeepMind Technologies Limited // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef MUJOCO_SRC_ENGINE_ENGINE_SORT_H_ #define MUJOCO_SRC_ENGINE_ENGINE_SORT_H_ // threshold size of a run to do insertion sort on #define _mjRUNSIZE 32 // insertion sort sub-macro that runs on a sub-array [start, ..., end) #define _mjINSERTION_SORT(type, arr, start, end, cmp, context) \ { \ for (int j = start + 1; j < end; j++) { \ type tmp = arr[j]; \ int k = j - 1; \ for (; k >= start && cmp(arr + k, &tmp, context) > 0; k--) { \ arr[k + 1] = arr[k]; \ } \ arr[k + 1] = tmp; \ } \ } // sub-macro that merges two sub-sorted arrays [start, ..., mid), [mid, ..., end) together #define _mjMERGE(type, src, dest, start, mid, end, cmp, context) \ { \ int i = start, j = mid, k = start; \ while (i < mid && j < end) { \ if (cmp(src + i, src + j, context) <= 0) { \ dest[k++] = src[i++]; \ } else { \ dest[k++] = src[j++]; \ } \ } \ if (i < mid) memcpy(dest + k, src + i, (mid - i) * sizeof(type)); \ else if (j < end) memcpy(dest + k, src + j, (end - j) * sizeof(type)); \ } // defines an inline stable sorting function via tiled merge sorting (timsort) // function is of form: // void name(type* arr, type* buf, int n, void* context) // where arr is the array of size n to be sorted inplace and buf is a buffer of size n. #define mjSORT(name, type, cmp) \ static inline void name(type* arr, type* buf, int n, void* context) { \ for (int start = 0; start < n; start += _mjRUNSIZE) { \ int end = (start + _mjRUNSIZE < n) ? start + _mjRUNSIZE : n; \ _mjINSERTION_SORT(type, arr, start, end, cmp, context); \ } \ type* src = arr, *dest = buf, *tmp; \ for (int len = _mjRUNSIZE; len < n; len *= 2) { \ for (int start = 0; start < n; start += 2*len) { \ int mid = start + len; \ int end = (start + 2*len < n) ? start + 2*len : n; \ if (mid < end) { \ _mjMERGE(type, src, dest, start, mid, end, cmp, context); \ } else { \ memcpy(dest + start, src + start, (end - start) * sizeof(type)); \ } \ } \ tmp = src; src = dest; dest = tmp; \ } \ if (src != arr) memcpy(arr, src, n * sizeof(type)); \ } static inline void name(type* arr, type* buf, int n, void* context) // sub-macro that sifts down a node in a max heap to its correct position #define _mjSIFT_DOWN(type, buf, start, end, cmp, context) \ { \ int root = start; \ while (2 * root + 1 < end) { \ int child = 2 * root + 1; \ int swap = root; \ if (cmp(buf + swap, buf + child, context) < 0) swap = child; \ if (child + 1 < end && cmp(buf + swap, buf + child + 1, context) < 0) swap = child + 1; \ if (swap == root) break; \ type tmp = buf[root]; buf[root] = buf[swap]; buf[swap] = tmp; \ root = swap; \ } \ } // defines an inline function that selects the bottom k elements using partial heap sort // buf needs to be of size k #define mjPARTIAL_SORT(name, type, cmp) \ static inline void name(type* arr, type* buf, int n, int k, void* context) { \ if (k <= 0 || n < k) return; \ /* fill initial heap */ \ for (int i = 0; i < k; i++) buf[i] = arr[i]; \ for (int j = (k - 2) / 2; j >= 0; j--) _mjSIFT_DOWN(type, buf, j, k, cmp, context); \ /* scan remaining elements */ \ for (int i = k; i < n; i++) { \ if (cmp(arr + i, buf, context) < 0) { \ buf[0] = arr[i]; \ _mjSIFT_DOWN(type, buf, 0, k, cmp, context); \ } \ } \ /* copy back and sort the result */ \ for (int j = 0; j < k; j++) arr[j] = buf[j]; \ _mjINSERTION_SORT(type, arr, 0, k, cmp, context); \ } static inline void name(type* arr, type* buf, int n, int k, void* context) #endif // MUJOCO_SRC_ENGINE_ENGINE_SORT_H_