nodes_value_containers.hpp 18.2 KB
Newer Older
1
// Copyright 2021 Thomas A. R. Purcell
2
//
3
4
5
// 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
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
10
11
12
13
// 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.
14
/** @file feature_creation/node/value_storage/node_value_containers.hpp
Thomas Purcell's avatar
Thomas Purcell committed
15
 * @brief Centeral storage area for all descriptor matrix data
16
17
18
19
20
21
22
 *
 *  This file contains definitions where all descriptor/feature data is stored permanently or in temporary storage arrays
 *  All Nodes's (except ModelNodes) value_ptr/test_value_ptrs point to data here.
 *
 *  @author Thomas A. R. Purcell (tpurcell)
 *  @bug No known bugs.
 */
23
24
#ifndef NODE_VALUE_ARR
#define NODE_VALUE_ARR
Thomas Purcell's avatar
Thomas Purcell committed
25

Thomas Purcell's avatar
Thomas Purcell committed
26
#include <algorithm>
27
#include <cmath>
Thomas Purcell's avatar
Thomas Purcell committed
28
#include <memory>
Thomas Purcell's avatar
Thomas Purcell committed
29
#include <numeric>
Thomas Purcell's avatar
Thomas Purcell committed
30
#include <vector>
31
#include <iostream>
Thomas Purcell's avatar
Thomas Purcell committed
32

33
34
#include <omp.h>

Thomas Purcell's avatar
Thomas Purcell committed
35
#include"utils/enum.hpp"
Thomas Purcell's avatar
Thomas Purcell committed
36

37
#ifdef PY_BINDINGS
38
#include "python/py_binding_cpp_def/conversion_utils.hpp"
39
40
41
42
namespace np = boost::python::numpy;
namespace py = boost::python;
#endif

Thomas Purcell's avatar
Thomas Purcell committed
43
44
namespace node_value_arrs
{
Thomas Purcell's avatar
Thomas Purcell committed
45
46
47
48
49
50
51
52
53
54
55
56
57
    extern std::vector<double> VALUES_ARR; //!< The central storage location for the values of the training data for each feature with a rung <= N_RUNGS_STORED
    extern std::vector<double> TEST_VALUES_ARR; //!< The central storage location for the values of the test data for each feature with a rung <= N_RUNGS_STORED

    extern std::vector<double> TEMP_STORAGE_ARR; //!< The vector used to temporarily store the values of each feature with a rung > N_RUNGS_STORED (These are calculated on the fly when the values are required)
    extern std::vector<double> TEMP_STORAGE_TEST_ARR; //!< The vector used to temporarily store the values of each feature with a rung > N_RUNGS_STORED (These are calculated on the fly when the values are required)
    extern std::vector<int> TEMP_STORAGE_REG; //!< Register that maps the slots in TEMP_STORAGE_ARR to the index of the feature whose data is currently stored there (reset by setting all elements of this vector to -1)
    extern std::vector<int> TEMP_STORAGE_TEST_REG; //!< Register that maps the slots in TEMP_STORAGE_TEST_ARR to the index of the feature whose data is currently stored there (reset by setting all elements of this vector to -1)

    extern std::vector<double> PARAM_STORAGE_ARR; //!< The vector used to temporarily store the values of each feature that has free-parameters (These are calculated on the fly when the values are required)
    extern std::vector<double> PARAM_STORAGE_TEST_ARR; //!< The vector used to temporarily store the values of each feature that has free-parameters (These are calculated on the fly when the values are required)

    extern std::vector<double> D_MATRIX; //!< The descriptor matrix (Central storage for the selected feature space)

Thomas Purcell's avatar
Thomas Purcell committed
58
59
60
    extern std::vector<int> TASK_SZ_TRAIN; //!< Number of training samples per task
    extern std::vector<int> TASK_SZ_TEST; //!< Number of test sample per task

Thomas Purcell's avatar
Thomas Purcell committed
61
62
63
64
65
66
    extern int N_SELECTED; //!< Number of selected features

    extern int N_SAMPLES; //!< Number of training samples for each feature (Sum of all elements in TASK_SZ_TRAIN)
    extern int N_SAMPLES_TEST; //!< Number of test samples for each feature (Sum of all elements in TASK_SZ_TEST)

    extern int N_PRIMARY_FEATURES; //!< Number of primary features
Thomas Purcell's avatar
Thomas Purcell committed
67
    extern int N_STORE_FEATURES; //!< Number of features with stored values
Thomas Purcell's avatar
Thomas Purcell committed
68
    extern int N_RUNGS_STORED; //!< Maximum rung for permanently storing a features value
69
    extern int MAX_RUNG; //!< The maximum rung for all features
Thomas Purcell's avatar
Thomas Purcell committed
70

Thomas Purcell's avatar
Thomas Purcell committed
71
72
73
74
    extern int MAX_N_THREADS; //!< Get the maximum number of threads possible for a calculation
    extern int N_OP_SLOTS; //!< The number of possible nodes of the binary expression tree that maybe calculated on the fly
    extern int N_PARAM_OP_SLOTS; //!< The number of possible non-leaf nodes of the binary expression tree

Thomas Purcell's avatar
Thomas Purcell committed
75
    /**
Thomas Purcell's avatar
Thomas Purcell committed
76
     * @brief Initialize all central storage vectors/descriptive variables
Thomas Purcell's avatar
Thomas Purcell committed
77
     *
Thomas Purcell's avatar
Thomas Purcell committed
78
79
80
81
     * @param n_samples The number of training samples for each feature (Sum of all elements in TASK_SZ_TRAIN)
     * @param n_samples_test The number of test samples for each feature (Sum of all elements in TASK_SZ_TEST)
     * @param n_primary_feat The number of primary features
     * @param max_rung The maximum rung for all features
Thomas Purcell's avatar
Thomas Purcell committed
82
     * @param set_test_task_sz If True reset the task_sz vectors
Thomas Purcell's avatar
Thomas Purcell committed
83
     * @param use_params If True set up parameterized feature storage as well
Thomas Purcell's avatar
Thomas Purcell committed
84
     */
85
86
87
88
89
90
91
92
    void initialize_values_arr(
        const int n_samples,
        const int n_samples_test,
        const int n_primary_feat,
        const int max_rung,
        const bool set_task_sz,
        const bool use_params
    );
Thomas Purcell's avatar
Thomas Purcell committed
93

94
    // DocString: node_vals_init_no_ts
Thomas Purcell's avatar
Thomas Purcell committed
95
    /**
Thomas Purcell's avatar
Thomas Purcell committed
96
     * @brief Initialize all central storage vectors/descriptive variables
Thomas Purcell's avatar
Thomas Purcell committed
97
     *
Thomas Purcell's avatar
Thomas Purcell committed
98
99
100
101
     * @param n_samples The number of training samples for each feature (Sum of all elements in TASK_SZ_TRAIN)
     * @param n_samples_test The number of test samples for each feature (Sum of all elements in TASK_SZ_TEST)
     * @param n_primary_feat The number of primary features
     * @param max_rung The maximum rung for all features
Thomas Purcell's avatar
Thomas Purcell committed
102
     */
103
104
105
106
107
108
    inline void initialize_values_arr(
        const int n_samples,
        const int n_samples_test,
        const int n_primary_feat,
        const int max_rung
    )
Thomas Purcell's avatar
Thomas Purcell committed
109
    {
110
        initialize_values_arr(n_samples, n_samples_test, n_primary_feat, max_rung, true, false);
Thomas Purcell's avatar
Thomas Purcell committed
111
112
113
    }

    /**
Thomas Purcell's avatar
Thomas Purcell committed
114
     * @brief Initialize all central storage vectors/descriptive variables
Thomas Purcell's avatar
Thomas Purcell committed
115
     *
Thomas Purcell's avatar
Thomas Purcell committed
116
117
118
119
120
     * @param task_sz_train The number of training samples per task
     * @param task_sz_test The number of test sample per task
     * @param n_primary_feat The number of primary features
     * @param max_rung The maximum rung for all features
     * @param use_params If True set up parameterized feature storage as well
Thomas Purcell's avatar
Thomas Purcell committed
121
     */
122
123
124
125
126
127
128
    void initialize_values_arr(
        const std::vector<int> task_sz_train,
        const std::vector<int> task_sz_test,
        const int n_primary_feat,
        const int max_rung,
        const bool use_params
    );
Thomas Purcell's avatar
Thomas Purcell committed
129
130

    /**
Thomas Purcell's avatar
Thomas Purcell committed
131
     * @brief Resize the central storage array given a new number of features and the current rung of the features
Thomas Purcell's avatar
Thomas Purcell committed
132
     *
Thomas Purcell's avatar
Thomas Purcell committed
133
134
     * @param n_rung The current rung of the generated features
     * @param n_feat The new number of features to store
Thomas Purcell's avatar
Thomas Purcell committed
135
     */
Thomas Purcell's avatar
Thomas Purcell committed
136
    void resize_values_arr(const int n_dims, const int n_feat);
Thomas Purcell's avatar
Thomas Purcell committed
137

Thomas Purcell's avatar
Thomas Purcell committed
138
    // DocString: node_vals_init_param
139
140
141
    /**
     * @brief Initialize the parameter storage array
     */
Thomas Purcell's avatar
Thomas Purcell committed
142

143
144
    void initialize_param_storage();

Thomas Purcell's avatar
Thomas Purcell committed
145
    // DocString: node_vals_init_d_mat
146
    /**
147
     * @brief Initialize the descriptor matrix
148
149
150
151
152
     *
     */
    void initialize_d_matrix_arr();

    /**
Thomas Purcell's avatar
Thomas Purcell committed
153
     * @brief Resize the descriptor matrix for the new number of selected features
154
155
156
     *
     * @param n_select Number of features to select
     */
157
    void resize_d_matrix_arr(const int n_select);
158

Thomas Purcell's avatar
Thomas Purcell committed
159
160
161
162
163
    /**
     * @brief Reset the global TASK_SZ_TRAIN vector
     *
     * @param task_sz_train the new task_sz train
     */
164
    void set_task_sz_train(const std::vector<int> task_sz_train);
Thomas Purcell's avatar
Thomas Purcell committed
165
166
167
168
169
170

    /**
     * @brief Reset the global TASK_SZ_TEST vector
     *
     * @param task_sz_train the new test_sz train
     */
171
    void set_task_sz_test(const std::vector<int> task_sz_test);
Thomas Purcell's avatar
Thomas Purcell committed
172

173
174
175
176
    /**
     * @brief Get the operator slot associated with a given rung/offset
     *
     * @param rung Rung of the feature
Thomas Purcell's avatar
Thomas Purcell committed
177
     * @param offset Offset used to prevent overwrites (determines where in the binary expression tree this operator is)
178
     * @param for_comp If true get a slot dedicated to comparing features
179
180
181
     *
     * @return The operator slot to use
     */
182
    inline int get_op_slot(const int rung, const int offset, const bool for_comp)
183
184
185
    {
        return std::abs(N_OP_SLOTS / (1 + !for_comp) - static_cast<int>(std::pow(2, MAX_RUNG - rung)) - offset);
    }
186

187
188
189
190
    /**
     * @brief Get the parameter operator slot associated with a given rung/offset
     *
     * @param rung Rung of the feature
Thomas Purcell's avatar
Thomas Purcell committed
191
     * @param offset Offset used to prevent overwrites (determines where in the binary expression tree this operator is)
192
193
194
195
196
197
198
199
200
     * @param for_comp If true get a slot dedicated to comparing features
     *
     * @return The operator slot to use
     */
    inline int get_param_op_slot(const int rung, const int offset, const bool for_comp)
    {
        return std::abs(N_PARAM_OP_SLOTS / (1 + !for_comp) - static_cast<int>(std::pow(2, MAX_RUNG - rung)) - offset);
    }

Thomas Purcell's avatar
Thomas Purcell committed
201
    /**
202
     * @brief Get a reference slot/feature register of the training data
Thomas Purcell's avatar
Thomas Purcell committed
203
     *
204
     * @param ind The Node's arr_ind
205
206
207
208
     * @param op_slot(int) Offset integer for TEMP_STORE_ARRAY
     *
     * @return The register element for a given feature index and op_slot
     */
209
    inline int& temp_storage_reg(const unsigned long int ind, const int op_slot=0)
210
211
    {
        return TEMP_STORAGE_REG[
Thomas Purcell's avatar
Thomas Purcell committed
212
            (ind % N_PRIMARY_FEATURES) + (op_slot % N_OP_SLOTS) * N_PRIMARY_FEATURES + omp_get_thread_num() * (N_PRIMARY_FEATURES * N_OP_SLOTS + 1)
213
214
        ];
    }
215
216
217
218
219
220
221
222
223

    /**
     * @brief Get a reference slot/feature register of the test data
     *
     * @param ind The Node's arr_ind
     * @param op_slot(int) Offset integer for TEMP_STORE_TEST_ARRAY
     *
     * @return The register element for a given feature index and op_slot
     */
224
    inline int& temp_storage_test_reg(const unsigned long int ind, const int op_slot=0)
225
226
    {
        return TEMP_STORAGE_TEST_REG[
Thomas Purcell's avatar
Thomas Purcell committed
227
            (ind % N_PRIMARY_FEATURES) + (op_slot % N_OP_SLOTS) * N_PRIMARY_FEATURES + omp_get_thread_num() * (N_PRIMARY_FEATURES * N_OP_SLOTS + 1)
228
229
        ];
    }
230

Thomas Purcell's avatar
Thomas Purcell committed
231
    /**
232
     * @brief Get a reference slot/feature register of the training data
Thomas Purcell's avatar
Thomas Purcell committed
233
     *
234
     * @param ind The Node's arr_ind
235
     * @param rung Rung of the feature
Thomas Purcell's avatar
Thomas Purcell committed
236
     * @param offset Offset used to prevent overwrites (determines where in the binary expression tree this operator is)
237
     * @param for_comp If true get a slot dedicated to comparing features
Thomas Purcell's avatar
Thomas Purcell committed
238
239
240
     *
     * @return The register element for a given feature index and offset
     */
241
    inline int& temp_storage_reg(const unsigned long int ind, const int rung, const int offset, const bool for_comp)
242
243
    {
        return TEMP_STORAGE_REG[
Thomas Purcell's avatar
Thomas Purcell committed
244
245
246
            (ind % N_PRIMARY_FEATURES) +
            (get_op_slot(rung, offset, for_comp) % N_OP_SLOTS) * N_PRIMARY_FEATURES +
            omp_get_thread_num() * (N_PRIMARY_FEATURES * N_OP_SLOTS + 1)
247
248
        ];
    }
Thomas Purcell's avatar
Thomas Purcell committed
249

250
    /**
251
     * @brief Get a reference slot/feature register of the test data
252
     *
253
     * @param ind The Node's arr_ind
254
     * @param rung Rung of the feature
Thomas Purcell's avatar
Thomas Purcell committed
255
     * @param offset Offset used to prevent overwrites (determines where in the binary expression tree this operator is)
256
     * @param for_comp If true get a slot dedicated to comparing features
257
258
259
     *
     * @return The register element for a given feature index and offset
     */
260
    inline int& temp_storage_test_reg(const unsigned long int ind, const int rung, const int offset, const bool for_comp)
261
262
    {
        return TEMP_STORAGE_TEST_REG[
Thomas Purcell's avatar
Thomas Purcell committed
263
            (ind % N_PRIMARY_FEATURES) +
Thomas Purcell's avatar
Thomas Purcell committed
264
265
            (get_op_slot(rung, offset, for_comp) % N_OP_SLOTS) * N_PRIMARY_FEATURES +
            omp_get_thread_num() * (N_PRIMARY_FEATURES * N_OP_SLOTS + 1)
266
267
        ];
    }
268

Thomas Purcell's avatar
Thomas Purcell committed
269
    /**
270
     * @brief Access element of the permanent training data storage array
Thomas Purcell's avatar
Thomas Purcell committed
271
     *
Thomas Purcell's avatar
Thomas Purcell committed
272
     * @param feature_ind The _feat_ind of Node to get the training data of
Thomas Purcell's avatar
Thomas Purcell committed
273
     *
274
     * @return pointer to the Node's training data
Thomas Purcell's avatar
Thomas Purcell committed
275
     */
276
    inline double* access_value_arr(const unsigned long int feature_ind){return &VALUES_ARR[feature_ind*N_SAMPLES];}
277

278
    /**
279
     * @brief Access element of the permanent test data storage array
280
     *
Thomas Purcell's avatar
Thomas Purcell committed
281
     * @param feature_ind The _feat_ind of Node to get the test data of
282
     *
283
     * @return pointer to the Node's test data
284
     */
285
    inline double* access_test_value_arr(const unsigned long int feature_ind){return &TEST_VALUES_ARR[feature_ind*N_SAMPLES_TEST];}
286

Thomas Purcell's avatar
Thomas Purcell committed
287
    /**
288
     * @brief Access element of temporary storage array for the training data
Thomas Purcell's avatar
Thomas Purcell committed
289
     *
290
     * @param slot The slot of the temporary storage array
Thomas Purcell's avatar
Thomas Purcell committed
291
     *
292
     * @return pointer to the data stored in the specified slot
Thomas Purcell's avatar
Thomas Purcell committed
293
     */
294
    inline double* access_temp_storage(const unsigned long int slot){return &TEMP_STORAGE_ARR[slot*N_SAMPLES];}
295

296
    /**
297
     * @brief Access element of temporary storage array for the test data
298
     *
299
     * @param slot The slot of the temporary storage array
300
     *
301
     * @return pointer to the data stored in the specified slot
302
     */
303
    inline double* access_temp_storage_test(const unsigned long int slot){return &TEMP_STORAGE_TEST_ARR[slot*N_SAMPLES_TEST];}
304

305
306
307
308
    /**
     * @brief Access the param storage array
     *
     * @param rung Rung of the feature
Thomas Purcell's avatar
Thomas Purcell committed
309
     * @param offset Offset used to prevent overwrites (determines where in the binary expression tree this operator is)
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
     * @param for_comp If true get a slot dedicated to comparing features
     * @return pointer to the correct element of the PARAM_STORAGE_ARR
     */
    inline double* access_param_storage(const int rung=0, const int offset=0, const bool for_comp=false)
    {
        return &PARAM_STORAGE_ARR[
            N_SAMPLES *
            (
                (get_param_op_slot(rung, offset, for_comp) % N_PARAM_OP_SLOTS) +
                omp_get_thread_num() * (N_PARAM_OP_SLOTS + 1)
            )
        ];
    }

    /**
     * @brief Access the param storage array for the test set
     *
     * @param rung Rung of the feature
Thomas Purcell's avatar
Thomas Purcell committed
328
     * @param offset Offset used to prevent overwrites (determines where in the binary expression tree this operator is)
329
330
331
332
333
334
335
336
337
338
339
340
341
342
     * @param for_comp If true get a slot dedicated to comparing features
     * @return pointer to the correct element of the PARAM_STORAGE_ARR
     */
    inline double* access_param_storage_test(const int rung=0, const int offset=0, const bool for_comp=false)
    {
        return &PARAM_STORAGE_TEST_ARR[
            N_SAMPLES_TEST *
            (
                (get_param_op_slot(rung, offset, for_comp) % N_PARAM_OP_SLOTS) +
                omp_get_thread_num() * (N_PARAM_OP_SLOTS + 1)
            )
        ];
    }

Thomas Purcell's avatar
Thomas Purcell committed
343
    /**
344
     * @brief Get a Node's value_ptr
Thomas Purcell's avatar
Thomas Purcell committed
345
     *
346
347
     * @param arr_ind Nodes _arr_ind
     * @param feat_ind Nodes _feat_ind
348
     * @param rung Rung of the feature
Thomas Purcell's avatar
Thomas Purcell committed
349
     * @param offset Offset used to prevent overwrites (determines where in the binary expression tree this operator is)
350
     * @param for_comp If true get a slot dedicated to comparing features
Thomas Purcell's avatar
Thomas Purcell committed
351
352
353
     *
     * @return The value pointer
     */
354
355
356
357
358
    double* get_value_ptr(
        const unsigned long int arr_ind,
        const unsigned long int feat_ind,
        const int rung=0,
        const int offset=0,
359
        const bool for_comp=false
360
    );
Thomas Purcell's avatar
Thomas Purcell committed
361

362
    /**
363
     * @brief Get a Node's test_value_ptr
364
     *
365
366
     * @param arr_ind Nodes _arr_ind
     * @param feat_ind Nodes _feat_ind
367
     * @param rung Rung of the feature
Thomas Purcell's avatar
Thomas Purcell committed
368
     * @param offset Offset used to prevent overwrites (determines where in the binary expression tree this operator is)
369
     * @param for_comp If true get a slot dedicated to comparing features
370
371
372
     *
     * @return The value pointer
     */
373
374
375
376
377
    double* get_test_value_ptr(
        const unsigned long int arr_ind,
        const unsigned long int feat_ind,
        const int rung=0,
        const int offset=0,
378
        const bool for_comp=false
379
    );
380

381
    /**
382
     * @brief Get the pointer to a particular selected Node from sis
383
     *
384
     * @param ind Index of the data in the descriptor matrix
385
     * @return The pointer to the descriptor matrix's data
386
     */
387
    inline double* get_d_matrix_ptr(const int ind){return &D_MATRIX[ind * N_SAMPLES];}
Thomas Purcell's avatar
Bug Fix    
Thomas Purcell committed
388

389
390
391
392
    /**
     * @brief Flush the temporary storage register (training data)
     * @details Reset all slots in the register to -1
     */
393
    inline void clear_temp_reg(){std::fill_n(TEMP_STORAGE_REG.begin(), TEMP_STORAGE_REG.size(), -1);}
394

395
396
397
398
    /**
     * @brief Flush the temporary storage register (training data)
     * @details Reset all slots in the register to -1
     */
399
400
    inline void clear_temp_reg_thread()
    {
Thomas Purcell's avatar
Thomas Purcell committed
401
        std::fill_n(TEMP_STORAGE_REG.begin() + (N_PRIMARY_FEATURES * N_OP_SLOTS + 1) * omp_get_thread_num(), N_PRIMARY_FEATURES * N_OP_SLOTS + 1, -1);
402
    }
403

404
405
406
407
    /**
     * @brief Flush the temporary storage register (test data)
     * @details Reset all slots in the register to -1
     */
408
    inline void clear_temp_test_reg(){std::fill_n(TEMP_STORAGE_TEST_REG.begin(), TEMP_STORAGE_TEST_REG.size(), -1);}
409

410
411
412
413
414
415
416
#ifdef PY_BINDINGS

    // DocString: node_vals_ts_list
    /**
     * @brief Initialize the node value arrays
     * @details Using the size of the initial feature space constructor the storage arrays
     *
Thomas Purcell's avatar
Thomas Purcell committed
417
418
419
420
     * @param task_sz_train (list) The number of training samples per task
     * @param task_sz_test (list) The number of test sample per task
     * @param n_primary_feat (int) The number of primary features
     * @param max_rung (int) The maximum rung for all features
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
     */
    inline void initialize_values_arr(
        py::list task_sz_train,
        py::list task_sz_test,
        int n_primary_feat,
        int max_rung
    )
    {
        initialize_values_arr(
            python_conv_utils::from_list<int>(task_sz_train),
            python_conv_utils::from_list<int>(task_sz_test),
            n_primary_feat,
            max_rung,
            false
        );
    }

    // DocString: node_vals_ts_arr
    /**
     * @brief Initialize the node value arrays
     * @details Using the size of the initial feature space constructor the storage arrays
     *
Thomas Purcell's avatar
Thomas Purcell committed
443
444
445
446
     * @param task_sz_train (np.ndarray) The number of training samples per task
     * @param task_sz_test (np.ndarray) The number of test sample per task
     * @param n_primary_feat (int) The number of primary features
     * @param max_rung (int) The maximum rung for all features
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
     */
    inline void initialize_values_arr(
        np::ndarray task_sz_train,
        np::ndarray task_sz_test,
        int n_primary_feat,
        int max_rung
    )
    {
        initialize_values_arr(
            python_conv_utils::from_ndarray<int>(task_sz_train),
            python_conv_utils::from_ndarray<int>(task_sz_test),
            n_primary_feat,
            max_rung,
            false
        );
    }
#endif
Thomas Purcell's avatar
Thomas Purcell committed
464
465
}

466
#endif